另一种解决方案是创建一个自定义HttpContent 来为您完成这项工作。如果您想遵守指南,还需要自定义 IHttpActionResult。
假设您有一个控制器,它为这样的给定资源返回 HEAD 操作:
[RoutePrefix("resources")]
public class ResourcesController : ApiController
{
[HttpHead]
[Route("{resource}")]
public IHttpActionResult Head(string resource)
{
// Get resource info here
var resourceType = "application/json";
var resourceLength = 1024;
return Head(resourceType , resourceLength);
}
}
我想出的解决方案如下:
头部处理程序
internal abstract class HeadBase : IHttpActionResult
{
protected HttpStatusCode Code { get; set; } = HttpStatusCode.OK;
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
try
{
response = new HttpResponseMessage(Code)
{
Content = new EmptyContent()
};
FillContentHeaders(response.Content.Headers);
return Task.FromResult(response);
}
catch (Exception)
{
response?.Dispose();
// Good place to log here
throw;
}
}
protected abstract void FillContentHeaders(HttpContentHeaders contentHeaders);
}
// For current need
internal class Head : HeadBase
{
public Head(string mediaType, long contentLength)
{
FakeLength = contentLength;
MediaType = string.IsNullOrWhiteSpace(mediaType) ? "application/octet-stream" : mediaType;
}
protected long FakeLength { get; }
protected string MediaType { get; }
protected override void FillContentHeaders(HttpContentHeaders contentHeaders)
{
contentHeaders.ContentLength = FakeLength;
contentHeaders.ContentType = new MediaTypeHeaderValue(MediaType);
}
}
空内容
internal sealed class EmptyContent : HttpContent
{
public EmptyContent() : this(null, null)
{
}
public EmptyContent(string mediaType, long? fakeContentLength)
{
if (string.IsNullOrWhiteSpace(mediaType)) mediaType = Constant.HttpMediaType.octetStream;
if (fakeContentLength != null) Headers.ContentLength = fakeContentLength.Value;
Headers.ContentType = new MediaTypeHeaderValue(mediaType);
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
// Necessary to force send
stream?.WriteByte(0);
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = Headers.ContentLength.HasValue ? Headers.ContentLength.Value : -1;
return Headers.ContentLength.HasValue;
}
}
缓冲策略选择器
internal class HostBufferPolicySelector : IHostBufferPolicySelector
{
public bool UseBufferedInputStream(object hostContext)
{
if (hostContext == null) throw new ArgumentNullException(nameof(hostContext));
return true;
}
public bool UseBufferedOutputStream(HttpResponseMessage response)
{
if (response == null) throw new ArgumentNullException(nameof(response));
if (StringComparer.OrdinalIgnoreCase.Equals(response.RequestMessage.Method.Method, HttpMethod.Head.Method)) return false;
var content = response.Content;
if (content == null) return false;
// If the content knows, then buffering is very likely
var contentLength = content.Headers.ContentLength;
if (contentLength.HasValue && contentLength.Value >= 0) return false;
var buffering = !(content is StreamContent ||
content is PushStreamContent ||
content is EmptyContent);
return buffering;
}
}
缓冲策略应该设置在Application_Start()中调用的public static void Register(HttpConfiguration config)方法中,
像这样:
config.Services.Replace(typeof(IHostBufferPolicySelector), new HostBufferPolicySelector());
另外,检查服务器是否配置为接受HEAD!
这个解决方案有几个优点:
- 可扩展:通过工厂和继承
- 适应性强:头处理程序是在控制器操作内创建的,您可以在其中拥有响应请求所需的所有信息。
- 资源消耗低,速度快:因为
HEAD是通过WebAPI API处理的
- 易于理解/维护:遵循 WebAPI 处理管道
- 关注点分离
我通过类似的机制创建了一个支持HEAD 的 Web API 2 文件存储控制器。
感谢Henning Krause 提出的问题,感谢answer 引导我到达那里。