Custom DTOs

In addition to the generated Generated DTOs that Coalesce will create for you, you may also create your own implementations of an IClassDto. These types are first-class citizens in Coalesce - you will get a full suite of features surrounding them as if they were entities. This includes generated API Controllers, Admin Views, and full TypeScript ViewModels and TypeScript ListViewModels.

The difference between a Custom DTO and the underlying entity that they represent is as follows:

  • The only time your custom DTO will be served is when it is requested directly from one of the endpoints on its generated controller. It will not be used when making a call to an API endpoint that was generated from an entity.
  • When mapping data from your database, or unmapping data from the client, the DTO itself must manually map all properties, since there is no corresponding Generated DTO. Attributes like [DtoIncludes] & [DtoExcludes] and property-level security through Security Attributes have no effect on custom DTOs, since those attribute only affect what get generated for Generated DTOs.

Creating a Custom DTO

To create a custom DTO, define a class that implements IClassDTO<T>, where T is an EF Core POCO, and annotate it with [Coalesce] . Add any Properties to it just as you would add model properties to a regular EF model.

Next, ensure that one property is annotated with [Key] so that Coalesce can know the primary key of your DTO in order to perform database lookups and keep track of your object uniquely in the client-side TypeScript.

Now, populate the required MapTo and MapFrom methods with code for mapping from and to your DTO, respectively (the methods are named with respect to the underlying entity, not the DTO). Most properties probably map one-to-one in both directions, but you probably created a DTO because you wanted some sort of custom mapping - say, mapping a collection on your entity with a comma-delimited string on the DTO. This is also the place to perform any user-based, role-based, property-level security. You can access the current user on the IMappingContext object.

[Coalesce]
public class CaseDto : IClassDto<Case>
{
    [Key]
    public int CaseId { get; set; }

    public string Title { get; set; }

    [Read]
    public string AssignedToName { get; set; }

    public void MapTo(Case obj, IMappingContext context)
    {
        obj.Title = Title;
    }

    public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
    {
        CaseId = obj.CaseKey;
        Title = obj.Title;
        if (obj.AssignedTo != null)
        {
            AssignedToName = obj.AssignedTo.Name;
        }
    }
}

Warning

Custom DTOs do not utilize property-level Security Attributes nor [DtoIncludes] & [DtoExcludes], since these are handled in the Generated DTOs. If you need property-level security or trimming, you must write it yourself in the MapTo and MapFrom methods.

If you have any child objects on your DTO, you can invoke the mapper for some other object using the static Mapper class. Also seen in this example is how to respect the Include Tree when mapping entity types; however, respecting the IncludeTree is optional. Since this DTO is a custom type that you’ve written, if you’re certain your use cases don’t need to worry about object graph trimming, then you can ignore the IncludeTree. If you do ignore the IncludeTree, you should pass null to calls to Mapper - don’t pass in the incoming IncludeTree, as this could cause unexpected results.

using IntelliTect.Coalesce.Mapping;

[Coalesce]
public class CaseDto : IClassDto<Case>
{
    public int ProductId { get; set; }
    public Product Product { get; set; }
    ...

    public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
    {
        ProductId = obj.ProductId;

        if (tree == null || tree[nameof(this.Product)] != null)
            Product = Mapper.MapToDto<Product, ProductDtoGen>(obj.Product, context, tree?[nameof(this.Product)]
        ...
    }
}

Using Custom DataSources and Behaviors

When you create a custom DTO, it will use the Standard Data Source and Standard Behaviors just like any of your regular EF Entity Models. If you wish to override this, your custom data source and/or behaviors MUST be declared in one of the following ways:

  1. As a nested class of the DTO. The relationship between your data source or behaviors and your DTO will be picked up automatically.

    [Coalesce]
    public class CaseDto : IClassDto<Case>
    {
        [Key]
        public int CaseId { get; set; }
    
        public string Title { get; set; }
    
        ...
    
        public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
        {
            ...
        }
    }
    
  2. With a [DeclaredFor] attribute that references the DTO type:

    [Coalesce]
    public class CaseDto : IClassDto<Case>
    {
        [Key]
        public int CaseId { get; set; }
    
        public string Title { get; set; }
    
        ...
    }
    
    [Coalesce, DeclaredFor(typeof(CaseDto))]
    public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
    {
        ...
    }