Refactor many duplicate ProducesResponseType to make it DRY

Viewed 607

Currently, I have 20 endpoints with same code with these 3 lines of ProducesResponseType. I am using .NET Core 3.1 and Swagger. How do I refactor my code to make it DRY (Don't Repeat Yourself)?

[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SuccessResponse))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
[ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ErrorResponse))]
public IActionResult Controller1(){
 // code removed for brevity
}

[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SuccessResponse))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
[ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ErrorResponse))]
public IActionResult Controller2(){
 // code removed for brevity
}

[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SuccessResponse))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
[ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ErrorResponse))]
public IActionResult Controller3(){
 // code removed for brevity
}
1 Answers

ASP.NET Core supports Web API Conventions, which:

...includes a way to extract common API documentation and apply it to multiple actions, controllers, or all controllers within an assembly.

The first step is to create a static class, with methods that define the conventions:

public static class SomeApiConvention
{
    [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SuccessResponse))]
    [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
    [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ErrorResponse))]
    [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)]
    public static void Default() { }
}

The name of the class isn't important, but the name of the method can impact how the convention is applied. In the example above, the [ApiConventionNameMatch] attribute disables this particular functionality so that the convention is used regardless of the action's method name.

To apply this convention to a controller, use the [ApiConventionType] attribute:

[ApiConventionType(typeof(SomeApiConvention))]
public class Controller1 : ControllerBase

This approach applies the convention to every action the controller defines, because of the [ApiConventionNameMatch] configuration already highlighted. Alternatively, if you changed Default to something else, e.g. Get, and removed the [ApiConventionType] attribute, the convention would apply only to the actions named Get.

To apply the convention only to a specific action, use the [ApiConventionMethod] attribute:

[ApiConventionMethod(typeof(SomeApiConvention), nameof(SomeApiConvention.Default))]
public IActionResult Action1()

This approach expects both the Type of the convention class and the name of the method to apply.


There's a lot more to this feature, which is described in the docs I've already mentioned. I won't replicate all of that here, as I hope the information included above is enough to get you to where you want to be.

Related