Search code examples
jsonasp.net-corejson.netasp.net-core-3.1

How can I use HttpPatch and JsonPatchDocument in a correct way


After applying this request in Powershell, I encounter the error:

Invoke-RestMethod http://localhost:5000/api/suppliers/1 -Method PATCH -ContentType "application/json"
-Body '[{"op":"replace","path":"City","value":"Los Angeles"}]'

Error Message:

Invoke-RestMethod : {"errors":{"":["A non-empty request body is required."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|c647c045-47f3a2d0bb2bc29c."} At line:1 char:1

  • Invoke-RestMethod http://localhost:5000/api/suppliers/1 -Method PATCH ...
  •   + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
    

eption + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand -Body : The term '-Body' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. At line:2 char:1

  • -Body '[{"op":"replace","path":"City","value":"Los Angeles"}]'
  •   + CategoryInfo          : ObjectNotFound: (-Body:String) [], CommandNotFoundException
      + FullyQualifiedErrorId : CommandNotFoundException
    

How can I solve it?

Startup class:

public class Startup {

    public Startup(IConfiguration config) {
        Configuration = config;
    }

    public IConfiguration Configuration { get; set; }

    public void ConfigureServices(IServiceCollection services) {
        services.AddDbContext<DataContext>(opts => {
            opts.UseSqlServer(Configuration[
                "ConnectionStrings:ProductConnection"]);
            opts.EnableSensitiveDataLogging(true);
        });

        services.AddControllers().AddNewtonsoftJson();
        services.Configure<MvcNewtonsoftJsonOptions>(opts =>
        {
            opts.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });
    }

    public void Configure(IApplicationBuilder app, DataContext context) {
        app.UseDeveloperExceptionPage();
        app.UseRouting();
        app.UseMiddleware<TestMiddleware>();
        app.UseEndpoints(endpoints => {
            endpoints.MapGet("/", async context => {
                await context.Response.WriteAsync("Hello World!");
            });
            endpoints.MapControllers();
        });
        SeedData.SeedDatabase(context);
    }
}

Supplier class:

public class Supplier {

    public long SupplierId { get; set; }
    public string Name { get; set; }
    public string City { get; set; }

    public IEnumerable<Product> Products { get; set; }
}

SuppliersController:

[ApiController]
[Route("api/[controller]")]
public class SuppliersController : ControllerBase
{
    private DataContext context;

    public SuppliersController(DataContext ctx)
    {
        context = ctx;
    }

    [HttpGet("{id}")]
    public async Task<Supplier> GetSupplier(long id)
    {
        Supplier supplier = await context.Suppliers
            .Include(s => s.Products)
            .FirstAsync(s => s.SupplierId == id);

        foreach (Product p in supplier.Products)
        {
            p.Supplier = null;
        }
        return supplier;
    }

    [HttpPatch("{id}")]
    public async Task<Supplier> PatchSupplier(long id, JsonPatchDocument<Supplier> patchDoc)
    {
        Supplier s = await context.Suppliers.FindAsync(id);
        if (s != null)
        {
            patchDoc.ApplyTo(s);
            await context.SaveChangesAsync();
        }
        return s;
    }
}

enter image description here

source code in github: https://github.com/Apress/pro-asp.net-core-3/tree/master/20%20-%20Advanced%20Web%20Services%20Features


Solution

  • $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("Content-Type", "application/json")
    
    $body = "[{`"op`":`"replace`",`"path`":`"City`",`"value`":`"Los Angeles`"}]"
    
    $response = Invoke-RestMethod 'http://localhost:5000/api/suppliers/1' -Method 'PATCH' -Headers $headers -Body $body
    $response | ConvertTo-Json
    

    When call by Postman, it returns normally.

    enter image description here

    If you need access codes in PowerShell, you can generate automatically in Postman.

    enter image description here

    Test in PowerShell

    enter image description here