Click here to Skip to main content
15,891,946 members
Articles / Web Development / ASP.NET / ASP.NET Core

Creating CRUD API in ASP.NET Core 2.0

Rate me:
Please Sign up or sign in to vote.
3.21/5 (6 votes)
31 Aug 2017CPOL3 min read 21.6K   6   12
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:

C#
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:

C#
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):

C#
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:

C#
[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:

Image 1

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:

Image 2

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:

Image 3

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:

C#
[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:

Image 4

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:

C#
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:

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

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

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
QuestionHow about to creating a ASP.NET CORE CRUD Web Site from any RESTFull API. Pin
Marcos Tito12-Dec-17 13:06
Marcos Tito12-Dec-17 13:06 
QuestionOne request for REST question Pin
Mou_kol31-Aug-17 23:03
Mou_kol31-Aug-17 23:03 
AnswerRe: One request for REST question Pin
User 104326431-Aug-17 23:54
User 104326431-Aug-17 23:54 
AnswerRe: One request for REST question Pin
E. Anderson1-Sep-17 7:36
E. Anderson1-Sep-17 7:36 
QuestionWhy no content is showing for 204 http status code Pin
Mou_kol31-Aug-17 23:00
Mou_kol31-Aug-17 23:00 
AnswerRe: Why no content is showing for 204 http status code Pin
User 104326431-Aug-17 23:49
User 104326431-Aug-17 23:49 
QuestionPossible follow-up article and question Pin
E. Anderson31-Aug-17 11:53
E. Anderson31-Aug-17 11:53 
AnswerRe: Possible follow-up article and question Pin
User 104326431-Aug-17 22:16
User 104326431-Aug-17 22:16 
GeneralRe: Possible follow-up article and question Pin
E. Anderson1-Sep-17 7:48
E. Anderson1-Sep-17 7:48 
GeneralRe: Possible follow-up article and question Pin
User 10432641-Sep-17 8:50
User 10432641-Sep-17 8:50 
GeneralRe: Possible follow-up article and question Pin
E. Anderson1-Sep-17 10:50
E. Anderson1-Sep-17 10:50 
Okay, that was one of the missing pieces.

In ASP.NET Core, IActionResult[^] is the functional equivalent of IHttpActionResult[^] used in ASP.NET Web API as further explained in "Migrating from ASP.NET Web API"[^].

Thank you!
GeneralRe: Possible follow-up article and question Pin
User 10432641-Sep-17 8:52
User 10432641-Sep-17 8:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.