Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating CRUD API in ASP.NET Core 2.0

0.00/5 (No votes)
31 Aug 2017 1  
How to create a CRUD Web API using ASP.NET Core. Continue reading...

Problem

How to create a CRUD Web API using ASP.NET Core.

Solution

In an empty project, update Startup class to add services and middleware for MVC:

public void ConfigureServices(
            IServiceCollection services)
        {
            services.AddSingleton<IMovieService, MovieService>();
            services.AddMvc();
        }

        public void Configure(
            IApplicationBuilder app, 
            IHostingEnvironment env)
        {
            app.UseExceptionHandler(configure =>
            {
                configure.Run(async context =>
                {
                    var ex = context.Features
                                    .Get<IExceptionHandlerFeature>()
                                    .Error;

                    context.Response.StatusCode = 500;
                    await context.Response.WriteAsync($"{ex.Message}");
                });
            });

            app.UseMvcWithDefaultRoute();
        }

Add a service and domain model:

public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int ReleaseYear { get; set; }
        public string Summary { get; set; }
    }    

    public interface IMovieService
    {
        List<Movie> GetMovies();
        Movie GetMovie(int id);
        void AddMovie(Movie item);
        void UpdateMovie(Movie item);
        void DeleteMovie(int id);
        bool MovieExists(int id);
    }

Add input and output models (to receive and send data via API):

public class MovieInputModel
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int ReleaseYear { get; set; }
        public string Summary { get; set; }
    }

    public class MovieOutputModel
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int ReleaseYear { get; set; }
        public string Summary { get; set; }
        public DateTime LastReadAt { get; set; }
    }

Add a controller for the API with service injected via constructor:

[Route("movies")]
    public class MoviesController : Controller
    {
        [HttpGet]
        public IActionResult Get()
        {
            var model = service.GetMovies();

            var outputModel = ToOutputModel(model);
            return Ok(outputModel);
        }

        [HttpGet("{id}", Name = "GetMovie")]
        public IActionResult Get(int id)
        {
            var model = service.GetMovie(id);
            if (model == null)
                return NotFound();

            var outputModel = ToOutputModel(model);
            return Ok(outputModel);
        }

        [HttpPost]
        public IActionResult Create([FromBody]MovieInputModel inputModel)
        {
            if (inputModel == null)
                return BadRequest();

            var model = ToDomainModel(inputModel);
            service.AddMovie(model);

            var outputModel = ToOutputModel(model);
            return CreatedAtRoute("GetMovie", 
                       new { id = outputModel.Id }, outputModel);
        }

        [HttpPut("{id}")]
        public IActionResult Update(int id, [FromBody]MovieInputModel inputModel)
        {
            if (inputModel == null || id != inputModel.Id)
                return BadRequest();

            if (!service.MovieExists(id))
                return NotFound();

            var model = ToDomainModel(inputModel);
            service.UpdateMovie(model);

            return NoContent();
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            if (!service.MovieExists(id))
                return NotFound();

            service.DeleteMovie(id);

            return NoContent();
        }
    }

Discussion

ASP.NET Core gives a unified mechanism for creating MVC and Web API application. The key difference is that Web API will return JSON (or XML) and HTTP status codes instead of views, in order to communicate with the client.

Routing

It is common to use attribute based routing (along with verb attributes) for Web API and conventional routing for MVC. The following URIs will reach the controller:

We can also define nested URIs, e.g., below is a Reviews controller such that for every movie, there could be zero or more reviews:

Note that identifier for parent resource (Movie) is part of the route and can be part of action methods. The following URIs will reach the controller:

Models

Model in MVC is a misunderstood term because it implies that there is only one model, which isn’t correct. We have various different type of models in our application, for instance, input, output, view, domain, entity, etc. These models are part of different layers and abstract away different concepts. See DDD and SOLID book for a detailed discussion.

The key point to remember when developing Web API is that the models being received and sent by the controller are Data Transfer Objects (DTO) and are distinct from domain or entity models.

Retrieve (GET)

A successful GET returns 200 (OK) status code along with the data.

If an item isn’t found, it returns 404 (Not Found).

Create (POST)

A successful POST returns 201 (Created) and sets the location header on HTTP response to point to the new item’s URI.

For issues with input model, a 400 (Bad Request) is returned. Also for nested resources, if parent isn’t found, a 404 (Not Found) can be returned.

Update (PUT)

A successful PUT returns 204 (No Content).

For issues with input model, a 400 (Bad Request) is returned. Also for nested resources, if parent isn’t found, a 404 (Not Found) can be returned.

Update (PATCH)

Although less frequently used, we can update parts of our data using PATCH verb. Content-Type for PATCH update should be application/json-patch+json.

Request body needs to contain an array of patch operations to apply to our model and action parameter needs to use JsonPatchDocument<T> to receive these operations:

[HttpPatch("{id}")]
        public IActionResult UpdatePatch(
            int id, [FromBody]JsonPatchDocument<MovieInputModel> patch)
        {
            if (patch == null)
                return BadRequest();

            var model = service.GetMovie(id);
            if (model == null)
                return NotFound();

            var inputModel = ToInputModel(model);
            patch.ApplyTo(inputModel);

            TryValidateModel(inputModel);
            if (!ModelState.IsValid)
                return new UnprocessableObjectResult(ModelState);

            model = ToDomainModel(inputModel);
            service.UpdateMovie(model);

            return NoContent();
        }

Here, we:

  1. Retrieve a model from data source
  2. Convert into input model
  3. Apply patch
  4. Validate and
  5. Update data source

The table below lists patch operations and their usage description:

Delete (DELETE)

A successful DELETE returns 204 (No Content).

For nested resources, if parent isn’t found, a 404 (Not Found) can be returned.

Validation (POST/PUT/PATCH)

Input models can be validated using data annotations or custom code, as discussed here. A status code of 422 (Unprocessable Entity) can be returned to indicate validation failure to client. There is no built-in Action Result for this, however it is easy to create one:

public class UnprocessableObjectResult : ObjectResult
    {
        public UnprocessableObjectResult(object value) 
            : base(value)
        {
            StatusCode = StatusCodes.Status422UnprocessableEntity;
        }

        public UnprocessableObjectResult(ModelStateDictionary modelState) 
            : this(new SerializableError(modelState))
        { }
    }

Now in POST/PUT/PATCH, you could check for validation issues and return this result:

if (!ModelState.IsValid)
                return new UnprocessableObjectResult(ModelState);

In the sample code, I’ve also created a helper method in base controller:

if (!ModelState.IsValid)
                return Unprocessable(ModelState);

    public class BaseController : Controller
    {
        [NonAction]
        public UnprocessableObjectResult Unprocessable(
           ModelStateDictionary modelState)
        {
            return new UnprocessableObjectResult(modelState);
        }

        [NonAction]
        public ObjectResult Unprocessable(object value)
        {
            return new UnprocessableObjectResult(value);
        }
    }

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here