Fixed contractResolver showed up when using Swashbuckle Swagger .NET 6

When using Swagger and I want to implement JsonPatchDocument to do Patch operation in my WebApi, I noticed the class can’t be recognized automatically in the Swagger UI.

contractResolver object detected in SwaggerUI

Below is a sample of my code for my Controller that uses JsonPatchDocument:

using LearningCqrs.Core;
using LearningCqrs.Core.Handler;
using LearningCqrs.Data;
using LearningCqrs.Features.Users;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;

namespace LearningCqrs.Controllers;

[Authorize]
[Route("api/users")]
[ApiController]
public class UsersController : ApiController
{

	private readonly IMediator _mediator;

    public UsersController(IMediator mediator)
    {
        _mediator = mediator;
    }
	
	[HttpPatch("blog/{id:guid")]
    public async Task<ActionResult> PatchDemo(Guid id,
        [FromBody] JsonPatchDocument<Update.UpdateUserCommand> updateUserCommand, CancellationToken cancellationToken)
    {
        var result = await _mediator.Send(
            new UpdateDocument<Update.UpdateUserCommand, User>(id, updateUserCommand),
            cancellationToken);
        return Ok(result);
    }
}

After investigating this issue, I found that the root cause is that there is no Serialization that supports the document. This led to the system failing to identify the object and show the contractResolver object.

There are multiple NuGet packages that you need to install to fix the above problem:

<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.4" />

In your Program.cs/Startup.cs (where you can set up your services registration), you need to set the below code:

builder.Services.AddControllers()
    .AddNewtonsoftJson();

Once you install those NuGet packages and call the AddNewtonsoftJson method, you should see the JsonPatchDocument can be generated properly:

Correct JsonPatchDocument model generated

But we still have a problem if you use JsonPatchDocument class being used inside a class. For example, I have a class like the below that is being used in UsersController.cs (not showing all code):

...

[HttpPatch("blog1/{id:guid}")]
public async Task<ActionResult> PatchDemo1(Guid id,
	[FromBody] TestingInput testingInputCommand, CancellationToken cancellationToken)
{
	var result = await _mediator.Send(
		new UpdateDocument<Update.UpdateUserCommand, User>(id, testingInputCommand.Patches, testingInputCommand.Version),
		cancellationToken);
	return Ok(result);
}

public class TestingInput
{
	public JsonPatchDocument<Update.UpdateUserCommand> Patches { get; set; }
	public string Version { get; set; }
}

...

When you run and check the Swagger UI, you will still see the contractResolver error like the previous image:

contractResolver error detected if the JsonPatchDocument being used inside a class

To solve this, you need to install this NuGet package:

<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.3.0" />

You need to call the below code in your Startup.cs/Program.cs:

builder.Services.AddSwaggerGenNewtonsoftSupport();

When you run it, here is the result:

The model generated was wrong

As you can see, I compare the previous sample endpoint that directly uses JsonPatchDocument vs the JsonPatchDocument that is inside the TestingInput class. The generated model added one extra Operation class that is not valid (when you execute it, it will throw an error).

To fix this, we need to modify our Schema manually (if you are a perfectionist like me). But before you can do it, you need to install another NuGet package:

<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.2" />

You need to create the below class:

using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace LearningCqrs.Core.Swagger;

public class JsonPatchDocumentFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var schemas = swaggerDoc.Components.Schemas.ToList();
        foreach (var item in schemas)
        {
            if (!item.Key.Contains("JsonPatchDocument")) continue;
            if(item.Value.Properties.All(e => e.Key != "operations")) continue;
            swaggerDoc.Components.Schemas.Remove(item);
            swaggerDoc.Components.Schemas.Add(item.Key, item.Value.Properties["operations"]);
        }
    }
}

Call the Filter when you call the AddSwaggerGen method:

builder.Services.AddSwaggerGen(opt => opt.DocumentFilter<JsonPatchDocumentFilter>());

If you run your app and check the Swagger UI again, it will show the correct model:

Get the correct Schema finally!

Happy .NET-ing!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.