为 ApiController 中的 void 方法覆盖 HttpResponseMessage 的创建

Override the creation of an HttpResponseMessage for void methods in an ApiController

我正在构建一个扩展 ApiController 的 class,它又将由我的客户扩展。

在我的控制器 class 中,我需要更改操作的 return 值序列化的方式。

例如:默认情况下,执行以下操作:

public Person Get() { return new Person(); }

将生成一个 HTTP 响应,例如:

HTTP/1.1 200 OK

{"name":null}

如果我想拦截这个 Person 对象,我可以从 MediaTypeFormatter 扩展,并将它附加到控制器的 Configuration.Formatters 集合,并以我想要的任何方式序列化该对象.

public abstract class MyController : ApiController
{
    protected override void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);

        Configuration.Formatters.OfType<JsonMediaTypeFormatter>().ToList()
            .ForEach(f => Configuration.Formatters.Remove(f));

        Configuration.Formatters.Insert(0, new MyPersonFormatter());
    }

操作现在的格式为:

HTTP/1.1 200 OK

{"my custom wrapper":{"name":null}}

但是如果我想拦截 void 动作怎么办?

默认情况下,void 操作将转换为具有空正文的 204 无内容响应。我将如何将数据插入该响应的正文?

注意:如果这可以从控制器内部完成,我更愿意这样做,这样我的抽象控制器就可以自己完成所有的管道工作,而不是强加给客户端任何东西。

在内部,在默认 HttpActionDescriptor 中,框架将 void 方法的 ResultConverter 设置为其自己的 VoidResultConverter,其中 returns HTTP 204内容为空。此外,通过明确地将其称为 VoidResultConverter,您甚至不能强制它使用 Reflection 替换其实现。

这是 HttpActionDescriptor.cs 中的第 34 行:

private static readonly VoidResultConverter _voidResultConverter = new VoidResultConverter();

但是,您可以使用自定义 ActionFilter 替换 void 方法中的 ActionDescriptor 方法(在全局范围内,在您的基本控制器中,或者通过使用属性):

public class VoidActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var retType = actionContext.ActionDescriptor.ReturnType;

        if (retType == typeof(void) || retType == null)
        {
            actionContext.ActionDescriptor = new VoidActionDescriptor(actionContext.ActionDescriptor);
        }

        base.OnActionExecuting(actionContext);
    }
}

这里是 VoidActionDescriptor,它实际上是对常规 ReflectedHttpActionDescriptor:

的包装
public class VoidActionDescriptor : HttpActionDescriptor
{
    private readonly HttpActionDescriptor _currentDescriptor;

    public VoidActionDescriptor(HttpActionDescriptor currentDescriptor)
    {
        if (currentDescriptor == null)
            throw new ArgumentNullException("currentDescriptor");

        this._currentDescriptor = currentDescriptor;
    }

    // this is what we're here for
    public override IActionResultConverter ResultConverter
    {
        get { return new MyVoidResultConverter(); }
    }

    // wrapper methods from now on
    public override Collection<HttpParameterDescriptor> GetParameters()
    {
        return this._currentDescriptor.GetParameters();
    }

    public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
    {
        return this._currentDescriptor.ExecuteAsync(controllerContext, arguments, cancellationToken);
    }

    public override string ActionName
    {
        get { return this._currentDescriptor.ActionName; }
    }

    public override Type ReturnType
    {
        get { return this._currentDescriptor.ReturnType; }
    }
}

它所做的唯一有趣的事情是在其 ResultConverter 属性.

中返回我们的自定义 MyVoidResultConverter

现在最后一块是实际的 IActionResultConverter:

public class MyVoidResultConverter : IActionResultConverter
{
    public HttpResponseMessage Convert(HttpControllerContext controllerContext, object actionResult)
    {
        var res = controllerContext.Request.CreateResponse(HttpStatusCode.OK);
        res.Content = new StringContent("void response");

        return res;
    }
}