Rico Suter's blog.
 


With NSwag you can implement custom operation processors and apply them to ASP.NET Core MVC or Web API controller operations. These processors then get picked up by NSwag and are applied to the given operation in the Swagger specification.

This article shows how to implement a custom operation processor using the example of adding code samples to the ReDoc UI. The ReDoc UI processes the x-code-samples property on operations. Here is a sample Swagger specification:

{
  ...

  "paths": {
    "/person": {
      "post": {
        "summary": "Adds a new person.",
        "operationId": "addPerson",
        "x-code-samples": {
          "lang": "CSharp", 
          "source": "console.log('Hello World');"
        }

        ...
      }
    }
  }
}

With a custom operation processor you can define these sample codes directly in your C# code - via an attribute on the operation. This will look like this:

public class PersonController : Controller
{
    [ReDocCodeSample("CSharp", "console.log('Hello World');")]
    public void AddPerson(Person person)
    {
    }
}

As you can see, we have implemented a custom attribute with the name ReDocCodeSampleAttribute. This attribute just inherits from SwaggerOperationProcessorAttribute calls the base constructor with the correct parameters of the ReDocCodeSampleAppender - our operation processor implementation:

public class ReDocCodeSampleAttribute : SwaggerOperationProcessorAttribute
{
    public ReDocCodeSampleAttribute(string language, string source)
        : base(typeof(ReDocCodeSampleAppender), language, source)
    {
    }
}

Because the ReDocCodeSampleAttribute just acts like an “alias”, it is not needed and you could just use the SwaggerOperationProcessorAttribute directly:

public class PersonController : Controller
{
    [SwaggerOperationProcessor(typeof(ReDocCodeSampleAppender), "CSharp", "console.log('Hello World');")]
    public void AddPerson(Person person)
    {
    }
}

But of course, it looks much better with an own attribute…

Now, lets have a look at the implementation of the actual operation processor:

public class ReDocCodeSampleAppender : IOperationProcessor
{
    private readonly string _language;
    private readonly string _source;
    private const string ExtensionKey = "x-code-samples";

    public ReDocCodeSampleAppender(string language, string source)
    {
        _language = language;
        _source = source;
    }

    public bool Process(OperationProcessorContext context)
    {
        if (context.OperationDescription.Operation.ExtensionData == null)
            context.OperationDescription.Operation.ExtensionData = new Dictionary<string, object>();

        var data = context.OperationDescription.Operation.ExtensionData;
        if (!data.ContainsKey(ExtensionKey))
            data[ExtensionKey] = new List<ReDocCodeSample>();

        var samples = (List<ReDocCodeSample>)data[ExtensionKey];
        samples.Add(new ReDocCodeSample
        {
            Language = _language,
            Source = _source,
        });

        return true;
    }
}

internal class ReDocCodeSample
{
    [JsonProperty("lang")]
    public string Language { get; set; }

    [JsonProperty("source")]
    public string Source { get; set; }
}

As you can see, the IOperationProcessor requires us to implement the ProcessAsync method. It has an OperationProcessorContext parameter which contains the current document and operation which you can transform to your liking. In our case, we just create or modify the x-code-samples property defined on the ExtensionData dictionary and return true to indicate that we want to keep the operation in the resulting Swagger specification.



Discussion