Partial update with PATCH using DTOs and AutoMapper in REST WebApi

How to partially update an entity in REST WebApi services with ASP.NET Core

Implementing partial updates on the REST API is probably the trickiest method to implement. Unlike PUT HTTP method where you pass the whole DTO when you are updating an entity, PATCH method is intended to update only one or few properties of the entity you want to apply change to. For that reason it uses something called JsonPatchDocument<T> on top of the DTO you use for updating the entity.

Unlike the plain DTO JsonPatchDocument contains some instructions on how you want to update the entity. More about JsonPatch specification you can find at jsonpatch.com where you have nice explanation on how and why you should use Json PATCH. In this article I will focus on one of the implementation of Json PATCH in ASP.NET Core WebAPI which might work for your project case. I will explain one of the implementations that worked for me with using some sample entity and sample DTO.

Mapping Entity to HTTP GET DTOs

You are probably aware that refencing your entity directly in you facade (in ASP.NET Core project this means in your controller) is pretty bad idea. Quite often, people who worked on the database are to CRUD oriented and entities with their properties are not so close to the real world terms and domain context, so when exposing them, you have to use different, often simplified objects. These are our DTO (data transfer objects) which represent data to the client and the outside world in more business logical format. This means property names in the DTO and the entity may not have the same names. Let's see this on a sample case.

Let's assume we are dealing with our user profiles and our entity for user profile looks like this

    public class UserProfile
    {
        [Required]
        public Guid UserId { get; set; }

        [Required]
        [MaxLength(100)]
        [MinLength(3)]
        public string Username { get; set; }

        [Required]
        [Range(1,10)]
        public int AvatarId { get; set; }

        public DateTime RegistrationDate { get; set; }

    }
    

This does not look so bad at all, but certainly there is a way to represent this in more readable way to the broad audience, something like this

/// <summary>
/// Player profile
/// </summary>
public class Profile
{
/// <summary>
/// Player profile unique identity
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Player profile nickname
/// </summary>
public String Nickname { get; set; }

        /// <summary>
        /// Player avatar icon id
        /// </summary>
        public int Avatar { get; set; }
    }
    
Note

Having comments for the DTO classes and their properties is a good practice as it can increase usability of your API with automatic document generation which can be consumed and presented by Swagger. You can check out more on using Swagger for documenting the API in this article http://bit.ly/2OnbQTF

We can easily map this using AutoMapper and consumers of our API will see the nice structure of our DTO. Further more, we separated our clients from our data persistence and if our data in some case in future needs to be changed, we can exclude or in worst case minimize the impact of the change for our clients.

    public class ProfileMapping : AutoMapper.Profile
    {

        public ProfileMapping()
        {
            CreateMap<UserProfile, Profile>()
                .ForMember(dest => dest.Avatar, opt => opt.MapFrom(src => src.AvatarId))
                .ForMember(dest => dest.Nickname, opt => opt.MapFrom(src => src.Username))
                .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.UserId));
        }

    }
    

Mapping HTTP PUT DTOs to Entity

Now since we are going to deal with data update in this article, some of the properties in our Profile DTO are not needed when we update the entity, like for example ID field, which is generated when the entity is generated and it is immutable, meaning it will not change. For update purpose we would need a new DTO since we do not need all the properties (in our case the ID property) for the update.

    /// <summary>
    /// Player profile
    /// </summary>
    public class ProfileUpdate
    {
        /// <summary>
        /// Player profile nickname
        /// </summary>
        public String Nickname { get; set; }

        /// <summary>
        /// Player avatar icon id
        /// </summary>
        public int Avatar { get; set; }
    }
    
Note

Using multiple DTOs for different HTTP methods is a good practice as it will encapsulate only data needed for the action to be performed, so the client needs exactly what to pass in the call. On another hand, it minimizes the data transfer through the wire, so only needed data is transferred over the network

We can use ProfileUpdate DTO to update the entity via PUT HTTP method

        [HttpPut("{id}")]
        public async Task<IActionResult> PatchAsync(Guid id, [FromBody]ProfileUpdate profile)
        {
            var entity = new UserProfile()
            {
                UserId = id,
                AvatarId = 2,
                Username = "User1"
            };

            AutoMapper.Mapper.Map(profile, entity);

            //TODO: Persist entity

            await Task.CompletedTask;
            return NoContent();
        }
    

And mapping profile for PUT would be something like this

            CreateMap<ProfileUpdate, UserProfile>()
                .ForMember(dest => dest.AvatarId, opt => opt.MapFrom(src => src.Avatar))
                .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Nickname))
                .ForMember(dest => dest.UserId, opt => opt.Ignore());
    

This way we are updating the entity by changing all the fields in the entity which are provided in the DTO, but this is not partial update. If we refer to jsonpatch.com we need to use JsonPatchDocument<T> where T will be our update DTO.

Mapping HTTP PATCH DTOs to Entity

Partial update via HTTP PATCH payload is a bit more complicated than just a simple DTO payload as in POST or PUT request. We need to use JsonPatchDocument which is basically collection of operations that we are going to apply to a DTO. Our PATCH payload for ProfileUpdate DTO would look something like this



[
  {
    "value": "test",
    "path": "/nickname",
    "op": "replace"
  }
]


    

In our controller action we nare going to expect JsonPatchDocument<ProfileUpdate> instead of ProfileUpdate like for PUT.

        [HttpPatch("{id}")]
        public async Task<IActionResult> PatchAsync(int id, [FromBody]JsonPatchDocument<ProfileUpdate> patchDocument)
        {
            //TODO: Apply mapping to the Entity

            await Task.CompletedTask;
            return NoContent();
        }
    

Now luckily for us AutoMapper is aware of JsonPathDocument class, so we can easily do the mapping for JsonPatchDocumet and all Operation elements contained in it

            CreateMap<JsonPatchDocument<ProfileUpdate>, JsonPatchDocument<UserProfile>>();
            CreateMap<Operation<ProfileUpdate>, Operation<UserProfile>>();
    

It is so easy, but there is a catch. This kind of mapping works only if DTO and Entity class have same names of the properties. Unfortunately this is not always the case and DTO structure may vary. In our sample DTO and Entity classes this is the case, so we need to use a different aproach. 

For partial update we also want to be able to change all the fields, so re-use of ProfileUpdate may look like a logical idea, but this is not a good thing to do since we need to have conditional mapping for partial update as our DTO and Entity properties do not have the same names.

    /// <summary>
    /// Player profile
    /// </summary>
    public class ProfilePartialUpdate
    {
        /// <summary>
        /// Player profile nickname
        /// </summary>
        public String Nickname { get; set; }

        /// <summary>
        /// Player avatar icon id
        /// </summary>
        public int Avatar { get; set; }
    }
    

You can see that our ProfileUpdate and ProfilePartialUpdate DTOs are the same but we need a different type do declare new mapping different than the one we have for PUT and ProfileUpdate DTO. Re-use of DTO for different purpose is actually a good practice as your methods do not depend on each other and changing one of them does not affect the other.

The problem with partial update when we do not have same property names in DTO and Entity is how to ignore the fields that are not declared in JsonPatchDocument structure. We can parse the Operations collection and build our logic on top of that, but Operations do not only perform replace actions on the target properties. They can also perform other actions like add or remove elements of collections which makes things even more complicated. However, most of the time you will probably use replace action, so one of the ways of determining what is changed and what not is to simply ignore the properties which have default values considering then not changed ones in the DTO JsonPatchDocument applies changes. Mapping for this would be conditional and would look like this

            CreateMap<ProfilePartialUpdate, UserProfile>()
                .ForMember(dest => dest.Avatar, opt => opt.Condition(src => src.Avatar != default(int)))
                .ForMember(dest => dest.Avatar, opt => opt.MapFrom(src => src.Avatar))
                .ForMember(dest => dest.Username, opt => opt.Condition(src => src.Nickname != default(string)))
                .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Nickname))
                .ForMember(dest => dest.UserId, opt => opt.Ignore());
    

This means only properties which do not have default values will be considered for the mapping from DTO to Entity.

Note

Conditional mapping with default(T) condition works only for mandatory properties. In case you have optional parameter for which you want to remove value, for example set to null, this conditional mapping will not work for this case

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform includion ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article