Reputation: 115
In the below code, you'll see a T type (generic) on the ProducesResponseType, but I'm not able to make it works since it's not a specific type:
public class ApiController<T> : ApiBaseController where T : class, IDocument
{
protected IDataService<T> data = null;
[HttpGet("{id}")]
**[ProducesResponseType(typeof(T), 201)]**
[ProducesResponseType(typeof(void), 500)]
public async Task<IActionResult> Get(string id)
{
var result = await data.Get(id);
return Ok(result);
}
}
Any suggestions?
Upvotes: 4
Views: 7047
Reputation: 64180
After having a close look, it seems to be possible (and easier) with operation filters.
Something along the lines of this should work (untested, just made sure it gives no compile errors).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Controllers;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace MyCompany.Common.Web.Swagger.OperationFilters
{
public class GenericOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor controllerDescriptor)
{
var baseType = controllerDescriptor.ControllerTypeInfo.BaseType?.GetTypeInfo();
// Get type and see if its a generic controller with a single type parameter
if (baseType == null || (!baseType.IsGenericType && baseType.GenericTypeParameters.Length == 1))
return;
if (context.ApiDescription.HttpMethod == "GET" && !operation.Responses.ContainsKey("200"))
{
var typeParam = baseType.GenericTypeParameters[0];
// Get the schema of the generic type. In case it's not there, you will have to create a schema for that model
// yourself, because Swagger may not have added it, because the type was not declared on any of the models
string typeParamFriendlyId = typeParam.FriendlyId();
if (!context.SchemaRegistry.Definitions.TryGetValue(typeParamFriendlyId, out Schema typeParamSchema))
{
// Schema doesn't exist, you need to create it yourself, i.e. add properties for each property of your model.
// See OpenAPI/Swagger Specifications
typeParamSchema = context.SchemaRegistry.GetOrRegister(typeParam);
// add properties here, without it you won't have a model description for this type
}
// for any get operation for which no 200 response exist yet in the document
operation.Responses.Add("200", new Response
{
Description = "Success",
Schema = new Schema { Ref = typeParamFriendlyId }
});
}
}
}
}
}
What it does? IOperationFilter
is invoked for each operation (Get, Post, Put etc.). Inside it, you check if it's an ControllerActionDescriptor
and if yes, check the controller type.
You can narrow this down to one specific type, if you wish. I've just taken every controller which inherits from another class and it's base type is generic with one single generic parameter.
Then lastly, it checks if it's an "Get" operation (post, put, delete typically don't return a model, just status code/error response), then checks if the type is already in the Swagger/OpenAPI schema definition. If the model is there, read it's schema and reference it in the response.
If the model is not registered in the schema registry, it gets more complicated. You'll need to use reflection and build the schema file, add it to the repository (which already happend during context.SchemaRegistry.GetOrRegister(typeParam)
call), then reference it as you did above.
This can happen, when the model isn't used as response or action parameter in any other controller.
You get more information on the OpenAPI 2.0 specification.
Upvotes: 4