I have a generic ASP.NET Core WebApi controller like:
public abstract class EntityController<TEntity>
{
public IActionResult Get(string id)
{
var entity = ... //load from database by id
if (entity != null)
return new JsonResult(value, this.SerializerSettings()) {StatusCode 200};
return NotFound();
}
}
and I want to apply following attributes on the Get()
method:
[ProducesResponseType(typeof(TEntity), 200)] //this causes compilation error.
[ProducesResponseType(typeof(Object), 404)]
For now, the only work around is to override each method in derived controller and add attributes there:
public class DerivedController :EntityController<MyEntity>
{
[ProducesResponseType(typeof(TEntity), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(Object), (int) HttpStatusCode.NotFound)]
public IActionResult Get(string id)
{
return base.Get(id);
}
}
Ihis is very inconvenient that I should override every REST methods in every controller, just for use the concrete TEntity
type in attributes. :-(
Any better work arounds?
Since .NET Core 2.1
instead of using IActionResult
, you can use ActionResult<TEntity>
as returntype
(or Task<ActionResult<TEntity>>
) and then swagger
will also know the returntype
for 200
calls!
Althrough I found no way to use generic type parameter in ProducesResponseTypeAttribute
, I found another way to make swagger work:
Use IApplicationModelConvention
to update ApplicationModel
, which is used by swagger.
public class EntityControllerConversion : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
ActionModel action = ... // finds the controller action
Type viewModelType = ... // get the view type by reflection from the controller
SetResponseUsingHack(action, viewModelType, HttpStatusCode.OK);
}
private void SetResponseUsingHack(ActionModel actionModel, Type responseType, HttpStatusCode statusCode)
{
if (actionModel == null) throw new ArgumentNullException(nameof(actionModel));
if (responseType == null) throw new ArgumentNullException(nameof(responseType));
var writable = (IList<object>)(actionModel.Attributes);
var attribute = FindResponseAttributeUsingHack(writable, statusCode);
if (attribute != null)
{
attribute.Type = responseType;
}
}
private ProducesResponseTypeAttribute FindResponseAttributeUsingHack(IList<object> attributes, HttpStatusCode statusCode)
{
if (attributes == null) return null;
var result = attributes.OfType<ProducesResponseTypeAttribute>()
.Where(x => x.Type == typeof(ProducesResponseStub))
.FirstOrDefault(x => x.StatusCode == (int) statusCode);
return result;
}
}
public abstract class EntityController<TEntity>
{
[HttpGet]
[ProducesResponseType(typeof(ProducesResponseStub), 200)]
public IActionResult Get(string id)
{
}
}
public static class ProducesResponseStub
{
}
NOTE: Swagger won't work correctly if you just add a new ProducesResponseTypeAttribute
instance to ActionModel.Attributes
, may be it's a bug in swagger or in asp.net core. That why I use ProducesResponseStub
in decorating action methods in EntityController
and replace them with correct types in EntityControllerConversion
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With