我试着找现有的扩展,但我没有立即找到(也许是因为我的搜索技巧不够好)。
我的第一个想法是你需要创建两个新类。
首先,创建一个从ActionMethodSelectorAttribute
继承的类。这与HttpGet
、HttpPost
等的基类相同。在这个类中,你将覆盖IsValidForRequest
。在该方法中,检查头部以查看是否请求了范围。现在,你可以使用此属性装饰控制器中的方法,当有人请求流的一部分(iOS、Silverlight等)时,将调用该方法。
其次,创建一个从ActionResult
或者也许是FileResult
继承的类,并重写ExecuteResult
方法,以添加你识别出的字节范围的标头。像返回JSON对象一样返回它,带有字节范围开始、结束、总大小的参数,以便它可以正确生成响应标头。
看一下FileContentResult
的实现方式,以了解如何访问上下文的HttpResponse
对象以更改标头。
看一下HttpGet
,以了解它如何实现对IsValidForRequest
的检查。源代码可在CodePlex上获得,或者像我刚刚做的那样使用Reflector。
你可以使用这些信息来进行更多的搜索,看看是否已经有人创建了这个自定义ActionResult
。
供参考,这是AcceptVerbs属性的样子:
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
string httpMethodOverride = controllerContext.HttpContext.Request.GetHttpMethodOverride();
return this.Verbs.Contains<string>(httpMethodOverride, StringComparer.OrdinalIgnoreCase);
}
这是FileResult的样子。注意使用了AddHeader:
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = this.ContentType;
if (!string.IsNullOrEmpty(this.FileDownloadName))
{
string headerValue = ContentDispositionUtil.GetHeaderValue(this.FileDownloadName);
context.HttpContext.Response.AddHeader("Content-Disposition", headerValue);
}
this.WriteFile(response);
}
我只是把这些东西拼凑在一起了。我不知道它是否适合你的需求(或者能不能工作)。
public class ContentRangeResult : FileStreamResult
{
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public int TotalSize { get; set; }
public ContentRangeResult(int startIndex, int endIndex, string contentType, Stream fileStream)
:base(fileStream, contentType)
{
StartIndex = startIndex;
EndIndex = endIndex;
TotalSize = endIndex - startIndex;
}
public ContentRangeResult(int startIndex, int endIndex, string contentType, string fileDownloadName, Stream fileStream)
: base(fileStream, contentType)
{
StartIndex = startIndex;
EndIndex = endIndex;
TotalSize = endIndex - startIndex;
FileDownloadName = fileDownloadName;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(this.FileDownloadName))
{
System.Net.Mime.ContentDisposition cd = new System.Net.Mime.ContentDisposition() { FileName = FileDownloadName };
context.HttpContext.Response.AddHeader("Content-Disposition", cd.ToString());
}
context.HttpContext.Response.AddHeader("Accept-Ranges", "bytes");
context.HttpContext.Response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", StartIndex, EndIndex, TotalSize));
this.WriteFile(response);
}
protected override void WriteFile(HttpResponseBase response)
{
Stream outputStream = response.OutputStream;
using (this.FileStream)
{
byte[] buffer = new byte[0x1000];
int totalToSend = EndIndex - StartIndex;
int bytesRemaining = totalToSend;
int count = 0;
while (bytesRemaining > 0)
{
if (bytesRemaining <= buffer.Length)
count = FileStream.Read(buffer, 0, bytesRemaining);
else
count = FileStream.Read(buffer, 0, buffer.Length);
outputStream.Write(buffer, 0, count);
bytesRemaining -= count;
}
}
}
}
使用方式如下:
return new ContentRangeResult(50, 100, "video/x-m4v", "SomeOptionalFileName", contentFileStream);