This is second part of the ASP.NET Core Web API Ecommerce Project. In this post we will see the requirement of DTO and will amend the previous code using DTOs.
What is DTO?
- DTO stands for Data Transfer Object. It is a class that sits between controller class and domain class.
- DTO transfers data either from controller class to domain class or from domain class to controller class. Data of DTO class and domain class are mapped before data transfer. Data mapping can be done manually or by using package like AutoMapper.
- Relation between Domain model and DTO is one-to-one. By convention, the name of a DTO class should be suffixed with Dto. DTO is an intermediary and temporary object which is used to transfer data but domain class is stable object which properties are fixed. For different kinds of data transfer, we need to create different DTO classes. The same is not true with domain classes.
- Domain classes are used by EF Core DbContext to map database tables with the domain classes. Tables is database are stable structure, similarly domain classes are stable structure. Same is not true with DTO so DTO cannot be used in DbContext.
- DTO is POCO i.e. Plain old CLR object. DTO class has no complex logic nor any method. It has just getter setter properties.
Why is DTO needed?
- To provide a subset of domain model class.
- To provide a superset of domain model class.
- To hide and secure important properties of a domain model class.
- To display data on a user form
- To send data from user form to the controller and ultimately to the DAL(Data access layer).
Does DTO provide data validation?
ASP.NET Core provides data validation out-of-the-box. As a developer we have to use DataAnnotation for this purpose. Annotating properties of a DTO provides data validation out-of-the-box.
Project updates
Create Dtos folder in the EShoppingApi project and add a ProductDto class.cs file. Update the ProductsController in the light of ProductDto class.
ProductDto.cs
namespace EShoppingApi.Dtos
{
public class ProductDto
{
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
}
}
ProductsController.cs
using EShopping.Domain.Models;
using EShoppingApi.Dtos;
using EShoppingApi.Interfaces;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Data;
using System.Linq;
namespace EShoppingApi.Controllers
{
[Route("api/[controller]")]
public class ProductsController : Controller
{
private readonly ISeeder seeder;
public ProductsController(ISeeder seeder)
{
this.seeder = seeder;
}
[HttpGet]
public IActionResult GetAllProducts()
{
List productDtos = new List();
var products = seeder.GetAllProducts();
// map dto to product
foreach (var p in products)
{
var dto = new ProductDto();
dto.Name = p.Name;
dto.Price = p.Price;
dto.Description = p.Description;
productDtos.Add(dto);
}
// user gets productDtos not products
return Ok(productDtos);
}
[HttpGet]
[Route("{id}")]
public IActionResult GetProductById(int id)
{
var products = seeder.GetAllProducts();
var product = products.Where(p => p.Id == id).FirstOrDefault();
if (product == null)
{
return NotFound();
}
// map product to productDto and return productDto
ProductDto productDto = new ProductDto();
productDto.Name = product.Name;
productDto.Price = product.Price;
productDto.Description = product.Description;
return Ok(productDto);
}
[HttpPost]
public IActionResult CreateProduct([FromBody] ProductDto productDto)
{
Product product = new Product();
// map productDto to product
product.Name = productDto.Name;
product.Price = productDto.Price;
product.Description = productDto.Description;
product.Id = 3; // hard-coded. In real database Key is auto-incremented.
// and add product
var success = seeder.AddProduct(product);
if (success)
{
return Created("https://localhost:5001/api/products/{product.Id}", product);
}
return BadRequest("Failed to add product.");
}
[HttpDelete]
[Route("{id}")]
public IActionResult DeleteProduct(int id)
{
var success = seeder.RemoveProduct(id);
if (success)
{
return Ok("Deleted product");
}
return BadRequest("Failed to delete product.");
}
[HttpPut]
[Route("{id}")]
public IActionResult UpdateProduct([FromBody] ProductDto productDto, int id)
{
Product product = new Product()
{
Name = productDto.Name,
Price = productDto.Price,
Description = productDto.Description
};
var success = seeder.UpdateProduct(id, product);
if (success)
{
return Ok("Product updated successfully.");
}
return BadRequest("Failed to update product.");
}
}
}
Note. We have just created DTO class and updated the ProductsController but NOT updated ISeeder.cs, Seeder.cs and Startup.cs files. The result will be same as before but using DTO provides all the benefits as mentioned above.
We have used a single DTO called ProductDto for all Http verbs. It is not good practice. For different Http verbs, we should create different Dto objects such as ProductDtoGet, ProductDtoPost, ProductDtoPut etc.
What is difference between Get DTO and Post DTO in web API?The Get DTO object is used to send data from server to the client (e.g. Postman or Browser). On the other hand, Post DTO object is used to send data from client (e.g. Postman or Browser) to server.
Run the application and test in Postman. We get the same result as before in Part I.
We modify the ProductDto as per Http Verbs then the code will be as follows.
No comments:
Post a Comment