JsonPatchDocument problems with Minimal API in .NET6 / 7 parameter Json problems

Shamus Schubert 0 Reputation points
2023-08-18T19:46:20.15+00:00

truggling to get MapPatch to work with minimal API and JsonPatchDocument. The result when passing in a valid json PATCH object is always

'Failed to read parameter "JsonPatchDocument patchDocument" from the request body as JSON'

and

'The JSON value could not be converted to Microsoft.AspNetCore.JsonPatch.JsonPatchDocument'.

Have read many articles on this with no luck.

Method has as a parameter

app.MapPatch("/*****/{id}", async ([FromServices] IMediator mediator, string id, [FromBody] JsonPatchDocument patchDocument) =>

and the body being passed in is:

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

The works in Web API but not minimal API. Maybe something simple?

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,623 questions
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,382 questions
ASP.NET API
ASP.NET API
ASP.NET: A set of technologies in the .NET Framework for building web applications and XML web services.API: A software intermediary that allows two applications to interact with each other.
317 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. Bruce (SqlWork.com) 61,181 Reputation points
    2023-08-19T16:54:22.1333333+00:00

    Your issue is the JsonPatch code was written for newtonsoft and does not work with system.text.json used by asp.net core. Mvc supports custom formatters, so typically you would add the Microsoft.AspNetCore.Mvc.NewtonsoftJson package and configure its use.

    builder.Services.AddControllers(options =>
    {
        options.InputFormatters.Insert(0, MyJPIF.GetJsonPatchInputFormatter());
    });
    

    But with min api you either add a custom binder to the object you are binding to, or just get the context and bind yourself. Add newtonsoft and calling newton for serialization is probably the best option if you want to continue with minapi.

    0 comments No comments

  2. Zhi Lv - MSFT 32,091 Reputation points Microsoft Vendor
    2023-08-21T07:09:17.1066667+00:00

    Hi @Shamus Schubert

    First, JSON Patch support in ASP.NET Core web API is based on Newtonsoft.Json and requires the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package. To enable JSON Patch support, we need to install the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package and call the NewtonsoftJson service. More detail information, see JsonPatch in ASP.NET Core web API.

    Second, in Asp.net core Minimal API, the body binding source uses System.Text.Json for deserialization. It is not possible to change this default. So, we can't use the AddNewtonsoftJon()method like in ASP.NET Core web API application. More detail information, see Configure JSON deserialization options for body binding.

    'Failed to read parameter "JsonPatchDocument patchDocument" from the request body as JSON' and 'The JSON value could not be converted to Microsoft.AspNetCore.JsonPatch.JsonPatchDocument'.

    So, to solve the above issue, you can try to read the request body as string first, then use the Newtonsoft.Json.JsonConvert.DeserializeObject method to convert the json string to JsonPatchDocument, code like this:

    app.MapPatch("/JsonPatchForDynamic/{id}", async (HttpContext context, string id ) =>
    {
        if (!context.Request.HasJsonContentType())
        {
            throw new BadHttpRequestException(
                    "Request content type was not a recognized JSON content type.",
                    StatusCodes.Status415UnsupportedMediaType);
        }
        using var sr = new StreamReader(context.Request.Body);
        var str = await sr.ReadToEndAsync();
        var result = JsonConvert.DeserializeObject<JsonPatchDocument>(str); 
    
        return result; 
    });
    

    Besides, you can also create a wrappers and use the Custom Binding method to bind the value, code like this:

        //using Newtonsoft.Json;
        //using System.Reflection;
        public class Wrapper<TModel>
        {
            public Wrapper(TModel? value)
            {
                Value = value;
            }
    
            public TModel? Value { get; }
    
            public static async ValueTask<Wrapper<TModel>?> BindAsync(HttpContext context, ParameterInfo parameter)
            {
                if (!context.Request.HasJsonContentType())
                {
                    throw new BadHttpRequestException(
                        "Request content type was not a recognized JSON content type.",
                        StatusCodes.Status415UnsupportedMediaType);
                }
    
                using var sr = new StreamReader(context.Request.Body);
                var str = await sr.ReadToEndAsync();
    
                return new Wrapper<TModel>(JsonConvert.DeserializeObject<TModel>(str));
            }
        }
    

    and:

    app.MapPatch("/JsonPatchForDynamic2/{id}", async (string id, Wrapper<JsonPatchDocument> po) =>
    {
        var patchdocument = po.Value;
        return patchdocument;
    });
    

    The result as below:

    image1

    Update:

    If you want to use Swagger to transfer the JSON string, you can use the JsonElement parameter, then after getting the Json string, use the JsonConvert.DeserializeObject method to convert the json string to JsonPatchDocument. Code like this:

    app.MapPatch("/JsonPatchForDynamic3/{id}", async (string id, [FromBody] JsonElement jsonElement) =>
    {
        var json = jsonElement.GetRawText();
    
        var doc = JsonConvert.DeserializeObject<JsonPatchDocument>(json);
          
    
        return Results.Ok(doc);
    }).WithOpenApi();
    

    The output as below:

    User's image


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Dillion

    0 comments No comments

  3. Shamus Schubert 0 Reputation points
    2023-08-21T12:49:56.7433333+00:00

    I appreciate both of these answers. It sort of tells me Min API is not really ready to support PATCH. I tried both. Don't really want to use string and then convert to JsonPatchDocument.

    Create a custom wrapper sort of breaks the PATCH document in the Swagger UI and creates some other issues. I can get them to work this way and appreciate the suggestions, but it is easier to conver the project to a traditional Web API and not use Min API as much as I would like to.

    Thanks.