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

asp.net asp.net-core c# dto entity-framework-core

Question

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();
        }
    }
}
1
6
5/6/2019 5:38:51 AM

Accepted Answer

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.

4
5/6/2019 4:47:47 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow