I am currently looking for an elegant way to decide between request and response objects. Let's say I create an animal and my API generates the ID for the animal. I don't want someone to provide the ID in the body of the request. I also don't want the ID to be displayed as a request example, e.g. in a Swagger UI.
If I return the created animal afterwards, then the ID should be given and also Swagger UI should show the ID for Responses as an example.
My only idea for this is to create two classes for the animal:
Is there a better way to do this?

My only idea for this is to create two classes for the animal:
PetRequestandPetResponse
No, that's exactly how you're meant to do it (well, except for "cast the PetRequest to a Pet, don't do that, use AutoMapper or similar instead).
I argue that it's a shortsighted (or just lazy) mistake to simply reuse a single DTO contract for all use-cases:
A common mantra in object/class/type-design is "make invalid states unrepresentable" (it is not without its detractors though) - which means that if a Pet object cannot have a meaningful PetId property (e.g. because it hasn't been INSERTed yet to get an IDENTITY/AUTONUMBER value back) then it shouldn't have a PetId property at all - which in-turn means you will need separate DTO types: NewPet and ExistingPet (or EditPet or similar).
You can (ab)use inheritance from a base PetValues DTO class to avoid repeating yourself or manually maintaining copy+pasta'd sets of object properties, but I feel it's a bad idea to use inheritance as a substitute for mixins (which C# still doesn't support, argh) because you'll end-up inadvertently including inappropriate members, and because if you want to use immutable types as DTOs then you'll run into lots of pain with immutable types' constructors as C# (still) doesn't have automatic constructor parameter inheritance (i.e. you can't add a new member to a base immutable DTO type without having also update all subclasses' constructors to add those new parameters for the new members).
Unfortunately Swagger doesn't play-nice with DTO type inheritance, either due to limitations in the tooling or other reasons, but in practice I find this isn't an issue because tooling that generates C#/.NET DTO types from Swagger JSON will let you customize the generated output. Polymorphism should not be a feature of DTOs/data-contracts anyway: they're meant to be record-style representations of structured data, not mutable object graphs.
Anyway, I'd do it like this (caution: this is my personal style, also assuming C# 8.0+ non-nullable reference-types are enabled, and all DTO members are non-nullable):
// Use this interface type to facilitate mapping between DTOs and internal business/domain types for as long as they're generally compatible.
// So you won't be able to use this type indefinitely into the future once your business/domain types start to evolve over-time as your DTOs will be frozen or versioned as not to break remote clients.
internal interface IReadOnlyPetValues
{
String Name { get; }
String Kind { get; }
}
// Using `record class` saves on a LOT of repetitiveness.
// Also, be sure to set explicit [JsonProperty] names (in camelCase) so renamed C#/.NET properties won't change the rendered JSON property names (and also protects you from environments using PascalCase serialization by default, philistines!)
public record class NewPetDto(
[JsonProperty("name")] String Name,
[JsonProperty("kind")] String Kind
) : IReadOnlyPetValues;
public record class EditPetDto(
[JsonProperty("petId")] Int32 PetId,
[JsonProperty("name")] String Name,
[JsonProperty("kind")] String Kind
) : IReadOnlyPetValues;
public static class PetDtoMapping
{
public static void CopyDtoToPetEntity( this IReadOnlyPetValues dto, Pet entity )
{
entity.Name = dto.Name;
entity.Kind = dto.Kind;
}
public static EditPetDto ToResponseDto( this Pet entity )
{
if( entity.PetId < 1 ) throw new ArgumentException( message: "This Pet has not been saved yet and so cannot be returned in a response.", paramName: nameof(entity) );
return new EditPetDto(
PetId: entity.PetId,
Name : entity.Name,
Kind : entity.Kind,
);
}
// You don't need a factory for `NewPetDto` as you'll only ever receive those from remote clients, your program won't need to return a `NewPetDto` as a response.
}
So your ASP.NET Core PetApiController class might look like this:
[ApiController]
public class PetApiController : Controller
{
// (ctor PetDbContext DI omited for brevity)
[HttpPost("/pets/new")]
[Produces( typeof(EditPetDto), 201 )]
public async Task<IActionResult> PostNewPet( [FromBody] NewPetDto dto )
{
// (this.ModelState validation omited for brevity)
Pet newPetEntity = new Pet();
dto.CopyDtoToPetEntity( newPetEntity );
this.db.Pets.Add( newPetEntity );
await this.db.SaveChangesAsync();
EditPetDto responseDto = newPetEntity.ToResponseDto();
return this.Created( "/pets/{$newPetEntity.PetId}", responseDto ); // HTTP 201 Created with Location: header.
}
[HttpGet("/pets/{petId:int}")]
[Produces( typeof(EditPetDto), 200 )]
public async Task<IActionResult> PostNewPet( [FromRoute] Int32 petId )
{
Pet pet = await this.db.Pets.Where( p => p.PetId == petId ).SingleAsync();
EditPetDto responseDto = newPetEntity.ToResponseDto();
return this.OK( responseDto );
}
[HttpPut("/pets/{petId:int}")]
[Produces( typeof(EditPetDto), 200 )]
public async Task<IActionResult> PutEditedPet( [FromRoute] Int32 petId, [FromBody] EditPetDto dto ) )
{
Pet pet = await this.db.Pets.Where( p => p.PetId == petId ).SingleAsync();
dto.CopyDtoToPetEntity( pet );
await this.db.SaveChangesAsync();
EditPetDto responseDto = pet.ToResponseDto();
return this.OK( responseDto );
}
}
Another important point I want to make is that you should not be using your domain/business entity types as DTO types, even when inherited (and especially not those used with Entity Framework). This is for numerous reasons:
Users table's PasswordHash column - and because inheritance cannot be used to remove members you'll need to manually maintain lists of properties to "always expose"/"never expose" - which increases the risk of human-error causing inadvertent data exposure.OrderDto will presumably also need to include a list of OrderItemDto objects, unless you really want your remote clients to make hundreds of requests for each entity individually: that's going to give you terrible overall system performance, and also make it impossible to meaningfully perform transacted actions over multiple entity objects (e.g. using a single HTTP request to simultaneously edit an Order's header details while also adding new OrderItem rows).Also, consider using JSON PATCH instead of PUT, as that's generally more forwards-compatible (as if a remote-client wants to update an object by overwriting some property-values they only send a smaller "set these properties to these values" message, instead of "update all properties to all of these values exactly", so if a remote client is using an older version of a DTO with fewer properties but the server is using a more modern version with more properties, then by default the new properties will appear as null in C# (and not undefined as in JSON/TypeScript) so the server will then overwrite any values those new properties have with null, causing unintentional information loss.
Another option: use a GraphQL or OData layer, which handles a lot of this tedium for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With