Post Action with model class vs DTO

Viewed 5646

This MSDN link explains why it is a good practice to use DTOs classes for web API. This is understandable, what confuses me is in the same page, the post method uses the model class instead of the simple DTO one as following:

[ResponseType(typeof(BookDTO))]
public async Task<IHttpActionResult> PostBook(Book book)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Books.Add(book);
    await db.SaveChangesAsync();

    // New code:
    // Load author name
    db.Entry(book).Reference(x => x.Author).Load();

    var dto = new BookDTO()
    {
        Id = book.Id,
        Title = book.Title,
        AuthorName = book.Author.Name
    };

    return CreatedAtRoute("DefaultApi", new { id = book.Id }, dto);
}

I guess my question is: Should Post/Put action takes model or DTO parameter?

Update: From answers, it looks like using dto is recommended even in case of post/put action, this will lead to another question though, how to map from dto to Model class in case of post action? Let's say we use AutoMapper, I found many links such as this and this warn against using it in reverse mapping (i.e. dto => Model class).

3 Answers

First, yes, you should always use a DTO. Whether you're dealing with a regular website or an API, you should never directly save an object instantiated from post data. This opens a huge security hole where people can manipulate the post data and wreak all sorts of mischief. Ironically, you can actually bind to your entity class, but if you do, you should never save that instance, but rather create a new instance, map over the data from the posted instance, and then save that instance you created instead - the important part is never saving the posted instance. Using a view model/DTO just makes it more obvious that you should do the mapping part of the equation, and as such is the recommended approach.

As far as AutoMapper goes, it's recommended to not use for reverse mapping because there are numerous nuances involved in mapping to something that's going to be saved to a database. Particular when you have ORMs like Entity Framework involved. You can use AutoMapper, but you just need to be aware of all these nuances and handle them accordingly. Generally speaking, it's probably easier in these scenarios to do the mapping manually, anyways, as it usually involves so much configuration for something like AutoMapper that you're not saving yourself much effort in the long run. Manual mapping is just as it sounds. If you're creating a new Book, then you just new up an instance of Book. If you're modifying an existing Book you pull an instance of that from the database. Either way, you now have an instance of Book and an instance of something like BookDTO, which was created from the post data. Then you just:

book.Title = bookDto.Tile;
// etc.

For something like an author relationship, you may need to do additional queries. For example:

var author = _context.Authors.SingleOrDefault(x => x.Name == bookDto.AuthorName);
book.Author = author;

For my personal projects and projects at work, the conventions I put in place are that we are to use DTO objects appended with either Request or Response to aid in maintainability and a clean API surface that is understandable for everyone.

public CreateBookResponse CreateBook(CreateBookRequest request)
{
}

Additionally, there are certain circumstances where we use HttpResponseMessage, IActionResult, or other return values depending on the need of the web service, but having the request and response objects clearly defined make it very easy for all of our developers, even of different skills like ASP, to understand the resulting JSON structures.

I would advise against exposing business entities or data access objects directly. Each of these have a different purpose in your application architecture, and you may be exposing too many details, or find maintaining cross-cutting concerns is difficult.

Short answer: up to you.

Longer answer: I would like to think DTO as way for a layer (e.g. a service) receiving data from or exposing data to outside in a simple/flat data structure. By this definition then the model in Web Api is just a type of DTO, within which you can add validation attributes (e.g. Required, MinLength etc) with the convenience that api controller can do the validation for you by checking ModelState.IsValid (and it's errors). But you can also add those attributes in a DTO class. So it's really not much difference.

Related