NSwag Tutorial: Implement a custom operation processor to define ReDoc code samples

November 1, 2017, (updated on November 2, 2017), Software Development

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 Task<bool> ProcessAsync(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 Task.FromResult(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.

Tweet about this on TwitterShare on FacebookEmail this to someoneShare on TumblrShare on LinkedIn

Tags: , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax