JSON Patch (2) In ASP.NET Core web API For Database

This article is a continuation of Json Patch (1) In ASP.NET Core web API, beginning with part C.
  • A: Introduction
    • Full Update vs. Patial Update
    • What is JSON Patch?
    • How Json Patch works?
  • B: Implementation in ASP.NET Core Web API, in Memory
    • Configure the App
    • Add PATCH related files (Entities and Controller)
    • Discussions:
      • Add Operation
      • Remove Operation
      • Replace Operation
      • Move Operation
      • Copy Operation
      • Test Operation
  • C: Implementation in ASP.NET Core Web API, for Database
    • PreSetup a new App with CRUD
    • Config the App for JsonPatch
      • Enable Json PATCH support
      • Add HttpPatch action
      • Add Initial Data
    • Discussions:
      • Add Operation
      • Remove Operation
      • Replace Operation
      • Move Operation
      • Copy Operation
      • Test Operation

C: Implementation in ASP.NET Core Web API, for Database

 
In the previous article,  we discussed the concept of Json PATCH, and running PATCH for View Models/DTOs from memory,  now we need to apply the patches back onto a database. Years ago, people used  Automapper to do the job, and now with .NET Core 5.0 (at least), we can do it just by the Microsoft code. We will demo a short sample here.
 

1, PreSetup a new App with CRUD

 
Follow the instruction from my previous article Entity Framework With .Net Core MVC (4), Code-First, step by step, we create an ASP.NET Core Web API app with CRUD,  by given:
 
Project Name as JsonPatch
 
Entity class name as VideoGame in /models/VideoGame.cs:
  1. using System;  
  2. using System.ComponentModel.DataAnnotations;  
  3.   
  4. namespace JsonPatch.Models  
  5. {  
  6.     // VideoGame.cs  
  7.     public partial class VideoGame  
  8.     {  
  9.         [Key]  
  10.         public virtual int Id { get; set; }  
  11.   
  12.         public virtual string Title { get; set; }  
  13.   
  14.         public virtual string Publisher { get; set; }  
  15.   
  16.         public virtual DateTime? ReleaseDate { get; set; }  
  17.   
  18.         public VideoGame(int id, string title, string publisher, DateTime? releaseDate)  
  19.         {  
  20.             Id = id;  
  21.             Title = title;  
  22.             Publisher = publisher;  
  23.             ReleaseDate = releaseDate;  
  24.         }  
  25.     }  

DbContext class name as JsonPatchContext in /modes/JsonPatchContext.cs
  1. using Microsoft.EntityFrameworkCore;  
  2.   
  3. namespace JsonPatch.Models  
  4. {  
  5.     public partial class JsonPatchContext : DbContext  
  6.     {  
  7.         public JsonPatchContext()  
  8.         {  
  9.         }  
  10.   
  11.         public JsonPatchContext(DbContextOptions<JsonPatchContext> options)  
  12.             : base(options)  
  13.         {  
  14.         }  
  15.   
  16.         public virtual DbSet<VideoGame> VideoGames { get; set; }  
  17.   
  18.     }  

Connection string in the appsettings.json file
  1. "ConnectionStrings": {  
  2.   "JsonPatchContext""Data Source=localhost;Initial Catalog=JsonPatch;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"  
  3. }, 
Create a VideoGamesController with CRUD:
  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3. using System.Threading.Tasks;  
  4. using Microsoft.AspNetCore.Mvc;  
  5. using Microsoft.EntityFrameworkCore;  
  6. using JsonPatch.Models;  
  7. using AutoMapper;  
  8. using Microsoft.AspNetCore.JsonPatch;  
  9.   
  10. namespace JsonPatch.Controllers  
  11. {  
  12.     [Route("api/[controller]")]  
  13.     [ApiController]  
  14.     public class VideoGamesController : ControllerBase  
  15.     {  
  16.         private readonly JsonPatchContext _context;  
  17.         private readonly IMapper _mapper;  
  18.   
  19.         public VideoGamesController(JsonPatchContext context)  
  20.         {  
  21.             _context = context;  
  22.         }  
  23.   
  24.         // GET: api/VideoGames  
  25.         [HttpGet]  
  26.         public async Task<ActionResult<IEnumerable<VideoGame>>> GetVideoGames()  
  27.         {  
  28.             return await _context.VideoGames.ToListAsync();  
  29.         }  
  30.   
  31.         // GET: api/VideoGames/5  
  32.         [HttpGet("{id}")]  
  33.         public async Task<ActionResult<VideoGame>> GetVideoGame(int id)  
  34.         {  
  35.             var videoGame = await _context.VideoGames.FindAsync(id);  
  36.   
  37.             if (videoGame == null)  
  38.             {  
  39.                 return NotFound();  
  40.             }  
  41.   
  42.             return videoGame;  
  43.         }  
  44.   
  45.         // PUT: api/VideoGames/5  
  46.         [HttpPut("{id}")]  
  47.         public async Task<IActionResult> PutVideoGame(int id, VideoGame videoGame)  
  48.         {  
  49.             if (id != videoGame.Id)  
  50.             {  
  51.                 return BadRequest();  
  52.             }  
  53.   
  54.             _context.Entry(videoGame).State = EntityState.Modified;  
  55.   
  56.             try  
  57.             {  
  58.                 await _context.SaveChangesAsync();  
  59.             }  
  60.             catch (DbUpdateConcurrencyException)  
  61.             {  
  62.                 if (!VideoGameExists(id))  
  63.                 {  
  64.                     return NotFound();  
  65.                 }  
  66.                 else  
  67.                 {  
  68.                     throw;  
  69.                 }  
  70.             }  
  71.   
  72.             return NoContent();  
  73.         }  
  74.   
  75.         // POST: api/VideoGames  
  76.         [HttpPost]  
  77.         public async Task<ActionResult<VideoGame>> PostVideoGame(VideoGame videoGame)  
  78.         {  
  79.             _context.VideoGames.Add(videoGame);  
  80.             await _context.SaveChangesAsync();  
  81.   
  82.             return CreatedAtAction("GetVideoGame"new { id = videoGame.Id }, videoGame);  
  83.         }  
  84.   
  85.         // DELETE: api/VideoGames/5  
  86.         [HttpDelete("{id}")]  
  87.         public async Task<IActionResult> DeleteVideoGame(int id)  
  88.         {  
  89.             var videoGame = await _context.VideoGames.FindAsync(id);  
  90.             if (videoGame == null)  
  91.             {  
  92.                 return NotFound();  
  93.             }  
  94.   
  95.             _context.VideoGames.Remove(videoGame);  
  96.             await _context.SaveChangesAsync();  
  97.   
  98.             return NoContent();  
  99.         }  
  100.   
  101.         private bool VideoGameExists(int id)  
  102.         {  
  103.             return _context.VideoGames.Any(e => e.Id == id);  
  104.         }  
  105.     }  
  106. }
Run the app, the result will be
 
 

2. Config the App for JsonPatch

 

Step 1: Enable Json PATCH support:

 
Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson from NuGet package to anable Json PATCH support
 

Step 2: Add HttpPatch action

 
Add HttpPatch action in VideoGamesController class:
  1. [HttpPatch("{id:int}")]  
  2. public IActionResult Patch(int id, [FromBody] JsonPatchDocument<VideoGame> patchEntity)  
  3. {  
  4.     var entity = _context.VideoGames.FirstOrDefault(videoGame => videoGame.Id == id);  
  5.   
  6.     if (entity == null)  
  7.     {  
  8.         return NotFound();  
  9.     }  
  10.   
  11.     patchEntity.ApplyTo(entity, ModelState); // Must have Microsoft.AspNetCore.Mvc.NewtonsoftJson installed  
  12.   
  13.     if (!ModelState.IsValid)  
  14.     {  
  15.         return BadRequest(ModelState);  
  16.     }  
  17.  
  18.     _context.Update(entity); //Update in the database.   
  19.   
  20.     try  
  21.     {  
  22.         _context.SaveChangesAsync();  
  23.     }  
  24.     catch (DbUpdateConcurrencyException)  
  25.     {  
  26.         if (!VideoGameExists(id))  
  27.         {  
  28.             return NotFound();  
  29.         }  
  30.         else  
  31.         {  
  32.             throw;  
  33.         }  
  34.     }  
  35.     return Ok(entity);  
  36. }
and Remove all other Verbs except GET.
 

Step 3: Add Initial Data:

 
Add initial data in the constructor of VideoGamesController class:
  1. public VideoGamesController(JsonPatchContext context)  
  2. {  
  3.     _context = context;  
  4.   
  5.     if (_context.VideoGames.Count() == 0)  
  6.     {
  7.         _context.VideoGames.Add(new VideoGame(0, "Call of Duty: Warzone""Activision"new System.DateTime(2020, 3, 10)));  
  8.         _context.VideoGames.Add(new VideoGame(0, "Friday the 13th: The Game""Gun Media"new System.DateTime(2017, 5, 26)));  
  9.         _context.VideoGames.Add(new VideoGame(0, "DOOM Eternal""Bethesda"new System.DateTime(2020, 3, 20)));  
  10.         _context.SaveChanges();  
  11.     }  

3, Discussions

 
Run the App:
 
 
Run the App, we have only two action methods: GET and PATCH because we have removed all other actions. Run the GET, we will get the Original input data:
 
 
Run PATCH,
 
 
We will run the PATCH operations: “add”, “remove”, “replace”, “move”, “copy” and “test”, one by one.  Because all results are saved into database, the following step will be against the updated one instead of the original one. We can check this by run GET to see the result for each step.
 

The add operation

 
with id = 2:
  1. [  
  2.     {  
  3.         "value""Tuesday : the Game",  
  4.         "path""/title",  
  5.         "op""add"  
  6.     }  

Shown in app like this,
 
 
if we run GET, we will see the data saved into database:
 
 

The remove operation

 
with id = 2: 
  1. [  
  2.     {  
  3.         "path""/title",  
  4.         "op""remove"  
  5.     }  
Then we got the result:
 
 

The replace operation

 
with id = 2:
  1. [  
  2.     {  
  3.         "value""Top Gun",  
  4.         "path""/publisher",  
  5.         "op""replace"  
  6.     }  
Then we got the result:
 
 

The move operation

 
with id = 2:
  1. [  
  2.     {  
  3.         "from""/title",  
  4.         "path""/publisher",  
  5.         "op""move"  
  6.     }  

Then we got the result:
 
 

The copy operation

 
with id = 2: 
  1. [  
  2.     {  
  3.         "from""/releaseDate",  
  4.         "path""/publisher",  
  5.         "op""copy"  
  6.     }  
Then we got the result:
 
 

The test operation

 
with id = 2:
  1. [  
  2.     {  
  3.         "value""Tuesday : the Game",  
  4.         "path""/title",  
  5.         "op""test"  
  6.     }  
Then we got the result:
 

Summary

 
Thia article described the PATCH Implementation in ASP.NET Core Web API, for Database.
 
Reference