Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is This How to Create a Data Transfer Object (DTO) with Entity Framework Core & ASP.NET Core MVC 2.2+ and 3.0

In creating a RESTful Api with ASP.NET Core MVC 2.2 I noticed there wasn't a a DTO example like the 2014 web api example.

ASP.NET Core MVC 2.2 Rest api 2019 example

ASP.NET web-api 2014 example

So, I decided to create DTO's for a few of my controller verbs HTTPGet, HTTPPost and HTTPPut

I have 2 fold questions from my final result.

  1. Is this the recommended way of doing it in a general sense. Or is there something in the new Entity Framework Core that is different or better than the 2014 example that was based on Entity Framework 6 or previous?

  2. Should one utilize the DTO design pattern in general? Or is there something in the Entity Framework Core that is different from a DTO pattern altogether. Specifically is there a way to take data from a database and pass it to the view/client the exact way I need it to be passed over?

More background to the reason for asking question part 2. I have read about DTO's being anti-patterns and people say don't use them for one reason or another. However, many developers implore their usage and when and why they should be used. A personal example for me is working and Angular and React projects. Receiving data I need is a beautiful thing that I can't imagine any other the other alternative which would be to do all types of hoops and parsing to get through a monolithic object to display address and location onto the screen.

But have times changed and is there a design pattern or another pattern that would do the exact same thing but at a lower expense and compute cost.

  1. For that matter is there a great compute cost to the server and dbserver for using this pattern?

  2. Lastly, is the code below how one would expect to utilize a DTO pattern in Entity Framework Core as opposed to the EF 6 or linq to sql frameworks?

I have included the code changes below to illustrate my DTO creation for the TodoItem model from the below exercise.

Project(TodoApi) --> DTOs --> TodoItemDTO.cs:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace TodoApi.Models
{
    public class TodoItemDTO
    {
        [Required]
        public string Names { get; set; }

        [DefaultValue(false)]
        public bool IsCompletes { get; set; }
    }

    public class TodoItemDetailDTO
    {
        public long Id { get; set; }

        [Required]
        public string Names { get; set; }

        [DefaultValue(false)]
        public bool IsCompletes { get; set; }
    }
}

Project(TodoApi) --> Controllers--> TodoController.cs:

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace TodoApi.Controllers
{
    [Produces("application/json")]
    [Route("api/[controller]")]
    [ApiController]
    public class TodoController: ControllerBase
    {
        private readonly TodoContext _context;

        public TodoController(TodoContext context)
        {
            _context = context;

            if (_context.TodoItems.Count() == 0)
            {
                // Create a new TodoItem if collection is empty, 
                // which means you can't delte all TodoItems.
                _context.TodoItems.Add(new TodoItem { Name = "Item1" });
                _context.SaveChanges();
            }

            // Console.WriteLine(GetTodoItems());
        }

        // Get: api/Todo
        [HttpGet]
        public async Task<ActionResult<IQueryable<TodoItem>>> GetTodoItems()
        {
            var todoItems = await _context.TodoItems.Select(t =>
                        new TodoItemDetailDTO()
                        {
                            Id = t.Id,
                            Names = t.Name,
                            IsCompletes = t.IsComplete
                        }).ToListAsync();

            return Ok(todoItems);

            // previous return statement
            //return await _context.TodoItems.ToListAsync();
        }

        // Get: api/Todo/5
        [HttpGet("{id}")]
        [ProducesResponseType(typeof(TodoItemDetailDTO), 201)]
        public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.Select(t =>
            new TodoItemDetailDTO()
            {
                Id = t.Id,
                Names = t.Name,
                IsCompletes = t.IsComplete
            }).SingleOrDefaultAsync(t => t.Id == id);

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

            return Ok(todoItem);

            //var todoItem = await _context.TodoItems.FindAsync(id);

            //////if (todoItem == null)
            //{
            //    return NotFound();
            //}

            //return todoItem;
        }

        // POST: api/Todo
        /// <summary>
        /// Creates a TodoItem.
        /// </summary>
        /// <remarks>
        /// Sample request:
        ///
        ///     POST /Todo
        ///     {
        ///        "id": 1,
        ///        "name": "Item1",
        ///        "isComplete": true
        ///     }
        ///
        /// </remarks>
        /// <param name="item"></param>
        /// <returns>A newly created TodoItem</returns>
        /// <response code="201">Returns the newly created item</response>
        /// <response code="400">If the item is null</response>            
        [HttpPost]
        [ProducesResponseType(typeof(TodoItemDTO), 201)]
        [ProducesResponseType(typeof(TodoItemDTO), 400)]
        public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
        {
            _context.TodoItems.Add(item);
            await _context.SaveChangesAsync();

            _context.Entry(item).Property(x => x.Name);
            var dto = new TodoItemDTO()
            {
                Names = item.Name,
                IsCompletes = item.IsComplete
            };

            // didn't use because CreatedAtAction Worked
            // return CreatedAtRoute("DefaultApi", new { id = item.Id }, dto);

            return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, dto);

            // original item call for new todoitem post
            //return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
        }

        // PUT: api/Todo/5
        [HttpPut("{id}")]
        [ProducesResponseType(typeof(TodoItemDTO), 201)]
        [ProducesResponseType(typeof(TodoItemDTO), 400)]
        public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
        {
            if (id != item.Id)
            {
                return BadRequest();
            }

            _context.Entry(item).State = EntityState.Modified;
            await _context.SaveChangesAsync();

            var dto = new TodoItemDTO()
            {
                Names = item.Name,
                IsCompletes = item.IsComplete
            };

            return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, dto);
        }

        // DELETE: api/Todo/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

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

            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync();

            return NoContent();
        }
    }
}
like image 881
Christian Matthew Avatar asked May 06 '19 05:05

Christian Matthew


People also ask

What is DTO in EF core?

To accomplish this, you can define a data transfer object (DTO). A DTO is an object that defines how the data will be sent over the network. Let's see how that works with the Book entity. In the Models folder, add two DTO classes: C# Copy.

Can entity be used as DTO?

Short answer: Entities may be part of a business domain. Thus, they can implement behavior and be applied to different use cases within the domain. DTOs are used only to transfer data from one processor context to another.

What are data transfer objects C#?

A DTO (Data Transfer Object) is an object that defines how data will be sent between applications. It's used only to send and receive data and does not contain in itself any business logic.

What is the difference between entity and DTO?

Difference between DTO & Entity: Entity is class mapped to table. Dto is class mapped to "view" layer mostly. What needed to store is entity & which needed to 'show' on web page is DTO.


1 Answers

I think you're getting too hung up on semantics. Strictly speaking, an "entity" is merely an object with identity (i.e. has an identifier), in contrast to something like a "value object". Entity Framework (Core or no) is an object/relational mapper (ORM) that abstracts object persistence. The "entity" being fed to EF is a class that represents an object in the persistence layer (i.e. a row in a particular table). That is all.

However, as such, it's often not incredibly useful in other scenarios. The SRP (single-responsibility principle) pretty much dictates that the entity should concern itself only with the actual stuff that's important to persistence. The needs of a handling a particular request, feeding a particular view with data, etc. can and will diverge from that, meaning you either need to make the entity class do too much or you need additional classes specifically for those purposes. That's where the concept of things like DTOs, view models, etc. come into play.

In short, the correct thing to do is to use what makes sense in a particular circumstance. If you're dealing with a CRUD-type API, it might make sense to use the entity class directly in that scenario. However, more often than not, even in the scenario of CRUD, it's still usually preferable to have a custom class to bind the request body to. That allows you to control things like serialization and which properties are viewable, editable, etc. In a sense, you're decoupling the API from the persistence layer, allowing the two to work independently of each other.

For example, let's say you need to change the name of a property on your entity. If your API uses the entity directly, then that would require versioning the API and dealing with deprecation of the previous version with the old property name. Using a separate class for each, you can simply change the mapping layer and the API goes along happily unaware. No change is required by clients interacting with the API. As a general rule, you want to always pursue the path of least coupling between components.

like image 170
Chris Pratt Avatar answered Oct 19 '22 22:10

Chris Pratt