while writing an Api wtih .NET Core 6(This is my first Api), I'm having some trouble while writing an Update method. I need to update my Application table below
public partial class Application
{
public Guid Id { get; set; }
public DateTime Createddate { get; set; }
public DateTime Modifieddate { get; set; }
public string? Companyid { get; set; }
public Guid? Customerid { get; set; }
public Guid? Productid { get; set; }
public string Status { get; set; } = null!;
}
But only the Status should change and rest must remain same. For this purpose, I wrote a DTO like below,
public class ApplicationUpdateDto
{
public Guid Id { get; set; }
public string Status { get; set; }
public DateTime ModifiedDate = DateTime.Now;
}
And my HttpPut Method is
[HttpPut]
public async Task<IActionResult> Update(ApplicationUpdateDto applicationUpdateDto)
{
await _service.UpdateAsync(_mapper.Map<Application>(applicationUpdateDto));
return CreateActionResult(CustomResponseDto<List<NoContentDto>>.Success(204));
}
In this case, request has only 2 data which are ID and Status and when executed,Status changes but also, other values that dont exist in DTO but exists in Application get default values or null values. I'm using Automapper 11 and this is my MapProfile;
public MapProfile()
{
CreateMap<ApplicationUpdateDto, Application>();
}
How can i solve this and what is the best practise while handling this kind of situations?
I appreciate any help. Thank you.
CodePudding user response:
Firstly you should get data and then update it, also your request Id may not exists you should check it too.
Now ModifiedDate property not neded in your request model
public class ApplicationUpdateDto
{
public Guid Id { get; set; }
public string Status { get; set; }
}
[HttpPut]
public async Task<IActionResult> Update(ApplicationUpdateDto model)
{
var applicationEntity = await _service.GetByIdAsync(model.Id);
if (applicationEntity == null)
return BadRequest(400);
applicationEntity.Status = model.Status;
applicationEntity.ModifiedDate = DateTime.Now;
await _service.UpdateAsync(applicationEntity);
return CreateActionResult(CustomResponseDto<List<NoContentDto>>.Success(204));
}
CodePudding user response:
The IMapperBase
interface (which the IMapper
interface extends from), has the overload method Map<TSource, TDestination>(TSource source, TDestination destination)
defined:
/// <summary>
/// Execute a mapping from the source object to the existing destination object.
/// </summary>
/// <typeparam name="TSource">Source type to use</typeparam>
/// <typeparam name="TDestination">Destination type</typeparam>
/// <param name="source">Source object to map from</param>
/// <param name="destination">Destination object to map into</param>
/// <returns>The mapped destination object, same instance as the <paramref name="destination"/> object</returns>
TDestination Map<TSource, TDestination>(TSource source, TDestination destination);
It allows you you specify the destination object which should be updated instead of creating a new TDestination
instance. You can use it to first load your existing Application
object and then use it as the destination for your Map(source, destination)
call. See the following example:
var config = new MapperConfiguration(cfg => cfg.CreateMap<ApplicationUpdateDto, Application>());
IMapper mapper = config.CreateMapper();
Guid id = Guid.NewGuid();
ApplicationUpdateDto update = new ApplicationUpdateDto
{
Id = id,
Status = "completed"
};
Application existing = new Application
{
Id = id,
Status = "loading",
Companyid = "some company id",
Customerid = Guid.NewGuid(),
Createddate = DateTime.Today.AddDays(-10),
Modifieddate = DateTime.Now.AddSeconds(-100000),
Productid = Guid.NewGuid()
};
Console.WriteLine("Before mapping:");
Console.WriteLine($"Existing Id: {existing.Id}");
Console.WriteLine($"Existing Companyid: {existing.Companyid}");
Console.WriteLine($"Existing Createddate: {existing.Createddate}");
Console.WriteLine($"Existing Modifieddate: {existing.Modifieddate}");
Console.WriteLine($"Existing Customerid: {existing.Customerid}");
Console.WriteLine($"Existing status: {existing.Status}");
mapper.Map(update, existing);
Console.WriteLine("After mapping:");
Console.WriteLine($"Existing Id: {existing.Id}");
Console.WriteLine($"Existing Companyid: {existing.Companyid}");
Console.WriteLine($"Existing Createddate: {existing.Createddate}");
Console.WriteLine($"Existing Modifieddate: {existing.Modifieddate}");
Console.WriteLine($"Existing Customerid: {existing.Customerid}");
Console.WriteLine($"Existing status: {existing.Status}");
This will generate the following output before the mapping:
Before mapping:
Existing Id: a242a615-7e88-404d-b364-1b8a8f6e16ab
Existing Companyid: some company id
Existing Createddate: 8/11/2022 12:00:00 AM
Existing Modifieddate: 8/20/2022 6:52:24 AM
Existing Customerid: cdcf2a2b-528d-40f3-9d25-5b33d5255304
Existing status: loading
And after the mapping:
After mapping:
Existing Id: a242a615-7e88-404d-b364-1b8a8f6e16ab
Existing Companyid: some company id
Existing Createddate: 8/11/2022 12:00:00 AM
Existing Modifieddate: 8/21/2022 10:39:04 AM
Existing Customerid: cdcf2a2b-528d-40f3-9d25-5b33d5255304
Existing status: completed
The Status
and ModifiedDate
properties has been changed, but the other properties stay the same.
CodePudding user response:
Thank you both, both are working very well. At the end I combined these two solutions and come up a solution like below;
[HttpPut]
public async Task<IActionResult> Update(ApplicationUpdateDto applicationUpdateDto)
{
var application = await _service.GetByIdAsync(applicationUpdateDto.Id);
if (application == null)
return BadRequest(400);
await _service.UpdateAsync(_mapper.Map<ApplicationUpdateDto, Application>(applicationUpdateDto,application));
return CreateActionResult(CustomResponseDto<List<NoContentDto>>.Success(204));
}