We typically use HttpPut to edit information. In this case, we have to send all the fields related to the entity. But if only one or two fields need to be edited, what is the need to send the rest of the fields?

Using HttpPatch we can only send the fields that need to be edited to the API. In this article, we want to create a patch operation for a User entity :

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public DateTime BirthDay { get; set; }
}

Then we create a class to perform the operations required by the user called UserService:

public class UserService : IUserService
{
    private readonly ApplicationDbContext _context;

    public UserService(ApplicationDbContext context)
    {
        _context = context;
    }
    public async Task<User> GetUserByIdAsync(int id, CancellationToken cancellationToken)
	{
   		 var user = await _context.Users.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
   		 return user;
	}

    public async Task SaveChanges(CancellationToken cancellationToken)
    {
        await _context.SaveChangesAsync(cancellationToken);
    }
}

We used Automapper to map information between viewmodels and entities. To register Automapper, you must install the following package:

<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />

By installing the AutoMapper.Extensions.Microsoft.DependencyInjection package, the Automapper package is automatically added to the project. Then we need to create a profile to register the models and Viewmodels. To do this, we create a class called UserProfile:

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<CreateUserViewModel, User>();
        CreateMap<User, SelectUserViewModel>()
            .ForMember(a => a.Age, map => map.MapFrom(src => DateTime.Now.Year - src.BirthDay.Year));
    }
}

CreateUserViewModel and SelectUserViewModel video models:

public class SelectUserViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public int Age { get; set; }
}
public class CreateUserViewModel
{
    [Required]
    [MaxLength(20)]
    public string Name { get; set; }
    [Required]
    [MaxLength(20)]
    public string Family { get; set; }
    public DateTime BirthDay { get; set; }
}

Next we create a controller for the User entity:

public class UserController : ControllerBase
{
    private readonly IUserService _userRepository;
    private readonly IMapper _mapper;

    public UserController(IUserService userRepository, IMapper mapper)
    {
        _userRepository = userRepository;
        _mapper = mapper;
    }
    [HttpGet("{id}", Name = "GetUser")]
    public async Task<IActionResult> GetUser(int id, CancellationToken cancellationToken)
    {
        var user = await _userRepository.GetUserByIdAsync(id, cancellationToken);
        if (user is null)
            return NotFound();
        var mappedUser = _mapper.Map<SelectUserViewModel>(user);
        return Ok(mappedUser);
    }
    [HttpPatch("{id}")]
    public async Task<IActionResult> Patch(int id, JsonPatchDocument<User> document, CancellationToken cancellationToken)
    {
        if (document is null)
        {
            return BadRequest();
        }
        var user = await _userRepository.GetUserByIdAsync(id, cancellationToken);
        document.ApplyTo(user, ModelState);
        if (ModelState.IsValid)
        {
            await _userRepository.SaveChanges(cancellationToken);
            var result = _mapper.Map<SelectUserViewModel>(user);
            return CreatedAtRoute("GetUser", new { id = result.Id }, result);
        }
        return BadRequest();
    }
}

The Action Patch receives a JsonPatchDocument input that requires you to install the Microsoft.AspNetCore.JsonPatch package.

<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="5.0.9" />

The JsonPatchDocument class, which is generic, accepts a type that specifies what fields we can send to Action Patch. For example, in the Patch action where the input of the JsonPatchDocument class is User, we can send fields that exist in the User class. For example, the following json specifies that the user name should be changed from Farhad to Mojtaba:

[
  {
    "op": "replace",
    "path": "/name",
    "value": "Mojtaba"
  }
]

This json is an array, so we can specify several fields to edit. For example, the following json specifies that the value of the Name property is equal to Mojtaba and the Family value is equal to "Khadem Sedaghat".

[
  {
    "op": "replace",
    "path": "/name",
    "value": "Mojtaba"
  },
  {
    "op": "replace",
    "path": "/family",
    "value": "Khadem Sedaghat"
  }
]

Then, to apply the sent changes, we must first read the desired record from the database and then apply the sent changes to the read record using the ApplyTo method. In the second parameter of the ApplyTo method, we set the ModelState property, which does not perform the editing operation if an invalid value is sent. To enable this feature to send ModelState to the ApplyTo method, you must install the following package:

<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.9" />

In the following, we will call only the SaveChangesAsync method to apply the changes to record the information. This is because the records read by ChangeTracker are tracked by default and the changes are applied to the user variable using the ApplyTo method. and finally register services in  ConfigureServices :

services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString(nameof(ApplicationDbContext)));
});
services.AddScoped<IUserService, UserService>();
services.AddControllers(setupAction =>
{
    setupAction.ReturnHttpNotAcceptable = true;

}).AddNewtonsoftJson(setupAction =>
{
    setupAction.SerializerSettings.ContractResolver =
       new CamelCasePropertyNamesContractResolver();
});
services.AddAutoMapper(options =>
{
    options.AddProfile(new UserProfile());
});

:)

Powered by Froala Editor

Comments

Users Comments
  • pedram norizi

    بینظیر بود

    Posted at Saturday, March 18, 2023