How to create CRUD operations using ASP.NET Web API 2?

The goal of this post is to explain how to create CRUD operations using ASP.NET Web API 2. I’ll be using attribute routing for CRUD operations that will be consumed by a client.

CRUD Operations

CRUD stands for create, read, update and delete a particular resource.

To keep the scope of this post simple, I’ll not use a database to persist data. I’ll be writing another post as an update. So, please subscribe to my blog to get an update.

I’ll be handling persistence in my application’s memory. So, any object I’ll create, update or delete, will be available just for that session.

Project Template & IDE

The template that I’ve used for this project is a ASP.NET Web API template in Visual Studio 2015.

Scenario

There is a requirement to create a prototype of a e-commerce application. The name of this application is Prodo. We need to manage categories and products for this application. So, a category can have many products.

I’m using this relation for this post so that I can explain how attribute routing can help us in this case in the post below.

The definition of `Category` and `Product` type is very simple. In real world, it may have many other members. Definition of these two types is shown below:

namespace Prodo.API.Models
{
    public class Category
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public List<Product> Products { get; set; }
    }
}
namespace Prodo.API.Models
{
    public class Product
    {
        public int Id { get; set; }

        public int CategoryId { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }

        public string ImageUrl { get; set; }

        public double Cost { get; set; }

        public bool OutOfStock { get; set; }
    }
}

Next, as I said above, I’m not using a database for persisting. So, I’ll create a layer over the in-memory objects using a Repository pattern. The API controllers that will consume this repository doesn’t need to care what is the type of data layer. In my future posts, I’ll move in-memory persistence to database.

The definition of `CategoryRepository` & `ProductRepository` classes is show below. Note that I’ve also created an interface for each repository that the concrete repository class will need to implement. This is useful for unit testing as well as Dependency Injection.

namespace Prodo.API.Models
{
    public interface ICategoryRepository
    {
        IEnumerable<Category> GetCategories();
        Category GetCategory(int id);
    }

    public class CategoryRepository : ICategoryRepository
    {
        public static List<Category> Categories { get; private set; }

        public CategoryRepository()
        {
            if (Categories == null || Categories.Count == 0)
            {
                Seed();
            }
        }

        private void Seed()
        {
            Categories = new List<Category>
            {
                new Category() { Id = 1, Name = "Smartphones" },
                new Category() { Id = 2, Name = "Tablets" }
            };
        }

        public IEnumerable<Category> GetCategories()
        {
            return Categories;
        }

        public Category GetCategory(int id)
        {
            return Categories.FirstOrDefault(p => p.Id.Equals(id));
        }
    }
}
namespace Prodo.API.Models
{
    public interface IProductRepository
    {
        IEnumerable<Product> GetProducts(int categoryId);
        Product GetProduct(int id);
        Product SaveProduct(Product product);
        bool UpdateProduct(int id, Product product);
        void Delete(int id);
    }

    public class ProductRepository : IProductRepository
    {
        public static List<Product> Products { get; private set; }
        private int _nextId = 5;

        public ProductRepository()
        {
            if (Products == null || Products.Count == 0)
            {
                Seed();    
            }
        }

        private void Seed()
        {
            Products = new List<Product>();

            Products.Add(new Product()
            {
                Id = 1,
                CategoryId = 1,
                Name = "Samsung S6 edge",
                Description = "This is Samsung S6 edge smartphone.",
                Cost = 500,
                ImageUrl = "http://www.samsung.com/global/galaxy/galaxys6/galaxy-s6-edge/images/galaxy-s6-edge_gallery_front_black.png"
            });

            Products.Add(new Product()
            {
                Id = 2,
                CategoryId = 1,
                Name = "Samsung S6 edge plus",
                Description = "This is Samsung S6 edge plus smartphone.",
                Cost = 650,
                ImageUrl = "http://www.samsung.com/global/galaxy/galaxys6/galaxy-s6-edge/images/galaxy-s6-edge_gallery_front_black.png"
            });

            Products.Add(new Product()
            {
                Id = 3,
                CategoryId = 2,
                Name = "Apple iPad",
                Description = "This is a revolutionary product!",
                Cost = 500,
                ImageUrl = "https://www.bhphotovideo.com/images/images2500x2500/apple_ml0t2ll_a_256gb_ipad_pro_wi_fi_1241236.jpg"
            });

            Products.Add(new Product()
            {
                Id = 4,
                CategoryId = 2,
                Name = "Apple iPad Pro",
                Description = "This is just another revolutionary product!",
                Cost = 650,
                ImageUrl = "https://www.bhphotovideo.com/images/images2500x2500/apple_ml0t2ll_a_256gb_ipad_pro_wi_fi_1241236.jpg"
            });
        }

        public IEnumerable<Product> GetProducts(int categoryId)
        {
            return Products.Where(p => p.CategoryId.Equals(categoryId)).ToList();
        }

        public Product GetProduct(int id)
        {
            return Products.FirstOrDefault(p => p.Id.Equals(id));
        }

        public Product SaveProduct(Product product)
        {
            product.Id = _nextId;
            _nextId++;

            Products.Add(product);

            return product;
        }

        public bool UpdateProduct(int id, Product product)
        {
            var index = Products.FindIndex(p => p.Id.Equals(id));

            if (index == -1 && !product.Id.Equals(id))
            {
                return false;
            }
            else
            {
                Products[index] = product;                       
            }

            return true;
        }

        public void Delete(int id)
        {
            Products.RemoveAll(p => p.Id.Equals(id));
        }
    }
}

You will see that `CategoryRepository` has only two methods and `ProductRepository` has four methods. The latter one is more interesting as it handles CRUD operations with in-memory persistence.

As the `CategoryRepository` is simple, the `CategoryController` is also very simple with just two methods:

namespace Prodo.API.Controllers
{
    [RoutePrefix("api/Categories")]
    public class CategoriesController : ApiController
    {
        private static readonly ICategoryRepository CategoryRepository = new CategoryRepository();

        // GET: api/Categories
        [Route("")]
        public IHttpActionResult Get()
        {
            return Ok(CategoryRepository.GetCategories());
        }

        // GET: api/Categories/2
        [Route("{id:int}")]
        public IHttpActionResult Get(int id)
        {
            var product = CategoryRepository.GetCategory(id);

            if (product == null)
            {
                return NotFound();
            }

            return Ok(product);
        }
    }
}

Let us now look at the `ProductsController` class to understand what Web API 2 has to offer:

namespace Prodo.API.Controllers
{
    [RoutePrefix("api/Products")]
    public class ProductsController : ApiController
    {
        private static readonly ProductRepository ProductRepository = new ProductRepository();

        [Route("~/api/Categories/{categoryId:int}/Products")]
        public IHttpActionResult Get(int categoryId)
        {
            return Ok(ProductRepository.GetProducts(categoryId));
        }
        
        // GET: api/Products/2
        //[Route("{id:int?}")] optional param
        //[Route("{id:int=4}")] default param value
        [Route("{id:int}")]
        public IHttpActionResult GetProduct(int id)
        {
            var product = ProductRepository.GetProduct(id);

            if (product == null)
            {
                return NotFound();
            }

            return Ok(product);
        }

        // POST: api/Products/
        [Route("")]
        public HttpResponseMessage Post([FromBody]Product product)
        {
            if (product == null) throw new ArgumentNullException("product");

            var savedProduct = ProductRepository.SaveProduct(product);

            var response = Request.CreateResponse(HttpStatusCode.Created, savedProduct);

            var uri = Url.Link("DefaultApi", new { id = product.Id, controller = "Products" });
            response.Headers.Location = new Uri(uri);

            return response;
        }

        // PUT: api/Products/2
        [Route("{id:int}")]
        public void Put(int id, [FromBody]Product product)
        {
            if (product == null) throw new ArgumentNullException("product");

            ProductRepository.UpdateProduct(id, product);
        }

        // DELETE: api/Products/2
        [Route("{id:int}")]
        public void Delete(int id)
        {
            var product = ProductRepository.GetProduct(id);

            if (product == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            ProductRepository.Delete(id);
        }
    }
}

Let us now understand the code above.

`ProductsController` uses `ProductRepository` to create, read, update and delete a `Product` object.

Resources often contain child resources: Categories have products, Customers have orders, movies have actors, books have authors, and so forth. It’s natural to create URIs that reflect these relations: `/categories/1/products`.

Please read my this post, if you would like to learn more about Attribute Routing based on code above. Understanding attribute routing with parameters, constraints is really important.

Next we use different implementations of IHttpActionResult type like OK (for HTTP 200 response) and NotFound (for HTTP 404 response) as response. A relevant response is used based on a what repository returns.

If you run this application and try to consume these REST services using an HTTP client like Fiddler or Postman, you will be able to create, read, update and delete a Product and read Categories.

Make sure that you use `Content-Type: application/json` in headers when using any HTTP client to make HTTP POST, PUT and DELETE requests to the server.

Hope this post explains how to create CRUD operations using ASP.NET Web API 2. If you would like to learn more about this framework, please read my other ASP.NET Web API V2 posts.

Siddharth Pandey

Siddharth Pandey is a Software Engineer with thorough hands-on commercial experience & exposure to building enterprise applications using Agile methodologies. Siddharth specializes in building, managing on-premise, cloud based real-time standard, single page web applications (SPAs). He has successfully delivered applications in health-care, finance, insurance, e-commerce sectors for major brands in the UK. Other than programming, he also has experience of managing teams, trainer, actively contributing to the IT community by sharing his knowledge using Stack Overflow, personal website & video tutorials.

You may also like...

Advertisment ad adsense adlogger