0

I'm using AutoMapper for mapping between my DTOs and business objects. This works fine.

However, I have a Web API which accepts PUT requests in order to update entities. The action looks like this:

public virtual async Task<IHttpActionResult> Put(string id, [FromBody] ProjectDto projectDto)

When mapping from ProjectDto to Project (business object), it maps all values from ProjectDto. Let's say I want to update a project's name, by sending the following JSON to the API:

{
    "projectId": 10,
    "name": "The new name"
}

Since I haven't given a value for all of the other properties of ProjectDto, AutoMapper will map default values where I haven't supplied a value.

For example, if ProjectDto had a couple of extra fields, they would now be null:

{
   "projectId": 10,
   "name": "The new name",
   "createdDate": null,
   "manager": null
} 

..etc.

My mapping looks like this:

cfg.CreateMap<ProjecDto, Project>()
                .ForAllMembers(opt => opt.Condition(x => x != null));

.. but AutoMapper is still mapping null values.

I have looked at this question, but I just get "ProjectDto does not contain a definition for IsSourceValueNull" when I try to copy their solution.

The Web API controller action (it's generic, TDto is an empty marker interface. T and TDto resolves at runtime to Project and ProjectDto, respectively.):

 public virtual async Task<IHttpActionResult> Put(string id, [FromBody] TDto model)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        var entity = _dataContext.GetRepository<T>().GetById(id);

        if (entity == null)
            return NotFound();

        var entityToUpdate = _mapper.Map<T>(model);
        var updatedEntity = _dataContext.GetRepository<T>().Update(entity);
        var updatedEntityDto = _mapper.Map<TDto>(updatedEntity);

        return Ok(updatedEntityDto);
    }
msk
  • 1,052
  • 3
  • 15
  • 31
  • You want to merge onto an initialized object and preserve target values that are not specified or in someway optional from api request json? Be careful, how do you know that null means an intent to clear a value or send no value? – Ross Bush Jun 02 '17 at 12:46
  • Not sure if I understand what you mean, but I only want to update the fields that are specified in the ProjectDto coming from the client – msk Jun 02 '17 at 12:54
  • Hmm. Really? As you see in the updated question, I have a generic controller and don't really know what properties that exist on the object at compile time. – msk Jun 02 '17 at 13:06

1 Answers1

0

Fundamentally this is how it should be, You need to put full object with changed values. PUT expect full object as per REST standards (api/project) - and not partial or dirty object.

if you want to update particular property then use different end point for ex.

api/project/prop1    
api/project/prop2

and then put relevant values to them.

If you want to override this behaviour then write your own mappings to put those conditions.

please use null substitution feature in auto-mapper.. https://github.com/AutoMapper/AutoMapper/wiki/Null-substitution

option: 2 (may be Hack)

cfg.CreateMap<ProjecDto, Project>();

cfg.CreateMap<Project, Project>()
            .ForAllMembers(opt => opt.Condition(x => x != null));

and API

public virtual async Task<IHttpActionResult> Put(string id, [FromBody] TDto model)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var entity = _dataContext.GetRepository<T>().GetById(id);

    if (entity == null)
        return NotFound();

    var entityToUpdate = _mapper.Map<T>(model);
    entityToUpdate = _mapper.Map<T, T>(entityToUpdate, entity); // overwriting the db object with new vals, may be swap around it, I haven't tested.    
    var updatedEntity = _dataContext.GetRepository<T>().Update(entityToUpdate);
    var updatedEntityDto = _mapper.Map<TDto>(updatedEntity);

    return Ok(updatedEntityDto);
}
dipak
  • 2,011
  • 2
  • 17
  • 24
  • I updated the question with the Web API action method that does the updating. – msk Jun 02 '17 at 13:01
  • I'm not trying to create a new object, and I'm not using POST. I'm using PUT. – msk Jun 02 '17 at 13:27
  • POST / PUT need full object as per REST spec. However its not the end of world. – dipak Jun 02 '17 at 13:34
  • You're right. I should accept a PATCH request for what I want to accomplish. My problem remains the same, though :/ – msk Jun 02 '17 at 13:52
  • As I said, Use the overload that takes the existing destination: Mapper.Map(source, destination); see the answer – dipak Jun 02 '17 at 14:23