-
Notifications
You must be signed in to change notification settings - Fork 94
GenericServices and DTOs
GenericServices makes heavy use of DTOs (Data Transfer Objects, also known as ViewModels). This page gives an overview of how to set up DTOs and how GenericServices uses them.
There are three parts to a DTO for it to work with GenericServices.
GenericServices needs to know what EF Core database entity class the DTO is linked to.
You provide that information by adding the ILineToEntity<TEntity>
interface to your DTO
(the interface is empty, i.e. you don't have to implement anything extra -
its just the TEntity
info that GenericServices needs).
There MUST be a ILineToEntity<TEntity>
on every DTO.
Here is a simplified example:
public class SimpleDto : ILinkToEntity<Book>
{
public int BookId { get; set; }
public string Title { get; set; }
public DateTime PublishedOn { get; set; }
}
The ILinkToEntity<Book>
says that the SimpleDto
is linked to the Book
entity.
This means on read it will use AutoMapper to build a Select query on the DbSet<Book>
property
to extract the BookId
, Title
and PublishedOn
values and put them in the DTO.
Similarly, on a create/update it will use the BookId
to set the row to update and will use the
value of the properties to update the Book
entity.
When using a DTO for create/update you sometimes need read, but not write a property.
For instance, in the example application I want to show the title of the book that the user is going
to update so that they can confirm its the right one. but I don't want the title updated.
GenericServices looks for the [ReadOnly(true)]
attribute on on a property, and if present (and true)
it will not use that property in a create/update. Here is an example for updating the publication date of a book
public class ChangePubDateDto : ILinkToEntity<Book>
{
[ReadOnly(true)]
public int BookId { get; set; }
[ReadOnly(true)]
public string Title { get; set; }
public DateTime PublishedOn { get; set; }
}
The DTO above would only update the PublishedOn
property in the Book
entity.
Advanced notes: If working with standard-styled entities then the AutoMapper save mapping has a rule to exclude
properties that are null or have the ReadOnly(true)
attribute.
If working with DDD-styled entities then the ReadOnly(true)
attribute removes the property from matching
to a parameter in a access method
(NOTE: in DDD-styled create/update any primary key properties are automatically set as ReadOnly(true)).
You can create a configuration file for a specific DTO by creating a class that inherits
from the abstract class, PerDtoConfig<TDto, TEntity>
. This allows you to change various things,
like the AutoMapper mappings (Read and Write), plus some other features.
Here is an example which alters the AutoMapper Read mapping (used in example application).
class BookListDtoConfig : PerDtoConfig<BookListDto, Book>
{
public override Action<IMappingExpression<Book, BookListDto>> AlterReadMapping
{
get
{
return cfg => cfg
.ForMember(x => x.ReviewsCount, x => x.MapFrom(book => book.Reviews.Count()))
.ForMember(x => x.AuthorsOrdered, y => y.MapFrom(p => string.Join(", ",
p.AuthorsLink.OrderBy(q => q.Order).Select(q => q.Author.Name).ToList())))
.ForMember(x => x.ReviewsAverageVotes,
x => x.MapFrom(p => p.Reviews.Select(y => (double?)y.NumStars).Average()));
}
}
}
See the comments in the code on these two classes.
- PerDtoConfig.Generic.cs - Alter AutoMapper mappings (Read and Write)
- PerDtoConfig.cs - Define DDD-styled create/update methods and validation.