Question Details

No question body available.

Tags

c# asp.net-core .net-core asp.net-core-mvc asp.net-core-webapi

Answers (8)

March 11, 2026 Score: 1 Rep: 41,864 Quality: Low Completeness: 50%

That's very opinionated.

Usually I hear more opinion that you should write your mappers yourself, because it provides most readability.

I find AutoMapper useful when I have mostly identical classes. However if there's some logic to retrieve mapped value, AutoMapper can quickly become just unnecessary noise, as it does not save you from writing the mapping logic, but additionally you need to wire it up with AutoMapper configuration (with ForMember and MapFrom methods).

March 11, 2026 Score: 0 Rep: 32,662 Quality: Medium Completeness: 80%

Instead of re-iterating what's already been said here's a simplistic example.

Notes:

  1. The Domain Entity uses Value Objects rather than primitives with built in validation.
  2. Everything is Immutable, and therefore a record with init properties.
  3. The mappers are manually generated.
  4. The DTOs and mappers are all internal as they only apply in the Infrastructure domain.

This is the Domain entity:

public sealed record DmoInvoice
{
    public InvoiceId Id { get; init; } = InvoiceId.NewId;
    public FkoCustomer Customer { get; init; } =  FkoCustomer.Default;
    public Money TotalAmount { get; init; } = Money.Default;
    public Date Date { get; init; }

public static DmoInvoice CreateNew() => new() { Id = InvoiceId.NewId, Date = new(DateTime.Now) }; }

And the DTO to get the data out of the data source - the Query of CQS.

internal sealed record DvoInvoice
{
    [Key] public Guid InvoiceID { get; init; }
    public Guid CustomerID { get; init; }
    public string CustomerName { get; init; } = string.Empty;
    public decimal TotalAmount { get; init; }
    public DateTime Date { get; init; }
}

And the DTO to write back - the Command of CQS:

internal sealed record DboInvoice 
{
    [Key] public Guid InvoiceID { get; init; }
    public Guid CustomerID { get; init; }
    public decimal TotalAmount { get; init; }
    public DateTime Date { get; init; }
}

And the Mappers:

internal static class InvoiceMapExtensions
{
    extension(DmoInvoice item)
    {
        public DboInvoice MapToDbo => new DboInvoice
        {
            InvoiceID = item.Id.Value,
            CustomerID = item.Customer.Id.Value,
            TotalAmount = item.TotalAmount.Value,
            Date = item.Date.ToDateTime
        };
    }

extension(DvoInvoice item) { public DmoInvoice MapToDmo => new DmoInvoice { Id = InvoiceId.Load(item.InvoiceID), Customer = new FkoCustomer(CustomerId.Load(item.CustomerID), new(item.CustomerName)), TotalAmount = new(item.TotalAmount), Date = new(item.Date) }; } }
March 11, 2026 Score: 0 Rep: 111,221 Quality: Low Completeness: 30%

I agree with Michał - I usually write explicit mapping code for non-trivial mapping. However, if it's trivial (pretty much a one-to-one correspondence) then I use Mapster (which is good and also free to use) rather than AutoMapper (which is also good, but is not free for commercial use).

March 11, 2026 Score: 0 Rep: 47,472 Quality: Low Completeness: 0%

I'll just give the AI the two classes and let it create a "manual" mapping.

March 11, 2026 Score: 0 Rep: 39,281 Quality: Low Completeness: 50%

I'm not really a webdeveloper, but I have always done DTO-mapping myself when needed. Sometimes just with a .ToDto()-method, sometimes by using the visitor pattern if I want to separate the mapping code.

I have no opinion on AutoMapper/Mapster due to lack of experience.

It is difficult to tell if your example is a good idea since it is not complete. I would be somewhat careful when using base classes, for both DTOs and Entities. Inheritance can be very useful in some cases, but it is often overused, and you may want to consider composition instead.

I have started to prefer records for DTOs, since they are immutable by default, and tend to work well with serialization libraries.

As a side note, I would try to make any API idempotent if possible, so that a client can retry failed operations without risking duplicates. This typically means using either natural keys or GUIDs rather than database generated sequential primary keys.

March 11, 2026 Score: 0 Rep: 19,288 Quality: Low Completeness: 40%

Whether you use AutoMapper, AI, or write the code yourself, first make sure you actually need a data transfer object. I've seen way too many applications with an unnecessary plethora of "DTOs" done in the name of "best practices" and "decoupling" without thinking through the implications to maintenance.

I only create DTOs for use at the boundary between my application and another; think: "response objects in a REST service" or "view models in an MVC application."

It's a little hard to make a concrete recommendation because the purpose a class serves determines whether it is a proper DTO; not a standard suffix to a class name.

March 11, 2026 Score: 0 Rep: 369 Quality: Low Completeness: 10%

DTO's are "simple" data transporters; between layers, platforms, etc. In this case, subclassing is over-engineering (class overhead for one); and you're better of with some redundancy and using a "record type". And "List" s/b replaced with ICollection (for example), which is more "portable" in a DTO. (serializing/deserializing)

March 11, 2026 Score: 0 Rep: 3,749 Quality: Low Completeness: 40%

I've built a low-code framework where you don't have to write all that stuff anymore.

On the other hand, you can consider using a source generator to generate your dto-classes automatically. For example, I've written a MapperGenerator that automatically generates Mappers for you.