为 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;
}
}
我正在构建一个扩展 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;
}
}