DEV Community

Weekly Dev Tips

Data Transfer Objects (part 2)

Data Transfer Object Tips (Part 2)

One classification of objects in many applications is the Data Transfer Object, or DTO. Here are some more tips that may help you avoid problems when using these objects.

Sponsor - DevIQ

Thanks to DevIQ for sponsoring this episode! Check out their list of available courses and how-to videos.

Show Notes / Transcript

Last week we talked about the definition of a DTO and how they're typically used. This week we'll cover a few more common problems with them and offer some Dos and Don'ts.

Mapping and Factories

It's fairly common to need to map to a DTO and another type, such as an entity. If you're doing this in several places, it's a good idea to consolidate the mapping code in one place. A static factory method on the DTO is a common approach to this. Note that this isn't adding behavior to the DTO, but rather is just a static helper method that we're putting on the DTO type for organizational purposes. I usually name such methods with a From prefix, such as FromCustomer(Customer customer) for a CustomerDTO type. There's a simple example in the show notes for episode 8.

public class CustomerDTO
{
    public string FirstName { get; set; }
    public string LastName { get; set;}

    public static CustomerDTO FromCustomer(Customer customer)
    {
        return new CustomerDTO() 
        {
            FirstName = customer.FirstName,
            LastName = customer.LastName
        };
    }
}

You can also use a tool like AutoMapper, which will eliminate the need to use such static factory methods. I usually quickly end up moving to AutoMapper if I have more than a couple of these methods to write myself.

What about attributes?

It's common in ASP.NET MVC apps to use attributes from the System.ComponentModel.DataAnnotations namespace to decorate model types for validation purposes. For example, you can add a Required attribute to a property, and during model binding if that property isn't, an error will be added to a collection of validation errors. Since these attributes don't impact your ability to work with the class as a DTO, and since typically the DTO is tailor made for the purpose of doing this binding, I think it's perfectly reasonable to use these attributes for this purpose. You can rethink this decision if at some point the attributes start to cause you pain. Follow Pain Driven Development (PDD): if something hurts, take a moment to analyze and correct the problem. Otherwise, keep on delivering value to your customers.

If you're not a fan of attribute-based validation, you can use Fluent Validation and define your validation logic using a fluent interface. You'll find a link in the show notes.

Keeping DTOs Pure

Avoid referencing non-DTO or primitive types from your DTOs. This can pull in dependencies that can make it difficult to secure your DTO. In some cases, it can introduce security vulnerabilities, such as if you have methods accepting input as DTOs, and these DTOs reference entities that your app is directly updating in the database. An attacker could guess at the structure of the entity and perhaps its navigation properties and could add or update data outside of the bounds of what you thought you were accepting. Take care in your update operations to only update specific fields, rather than model binding an entity object from external input and then saving it.

DTO Dos and Don'ts

Let's wrap up with some quick dos and don't for Data Transfer Objects:

  • Don't hide the default constructor
  • Do make properties available via public get and set methods
  • Don't validate inputs to a DTO
  • Don't add instance methods to your DTO
  • Do consolidate mapping logic into static factories
  • Do consider moving to AutoMapper if you have more than a few such factory methods
  • Do feel free to use attributes to help with model validation
  • Don't reference non-DTO types, such as entities, from DTOs

Show Resources and Links

Episode source