一、问题源起

从Web From过来的人应该会比较熟悉以下下载文件的代码;

[HttpPost]
[Route("Download")]
public void Download()
{
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.BufferOutput = true;
response.AddHeader("Content-Type", "application/octet-stream");
response.AddHeader("Content-Disposition", "attachment;filename=myfile.txt");
GetFileContent().CopyTo(response.OutputStream);
response.Flush();
}

代码中直接修改Response的header,并叫文件内容写入Response的OutputStream中,最后进行Flush刷新;执行之后可以正常下载文件,但是发现执行的过程中会报如下的错误

System.Web.HttpException (0x80004005): Server cannot set status after HTTP headers have been sent.
at System.Web.HttpResponse.set_StatusCode(Int32 value)
at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseStatusAndHeadersAsync>d__25.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseAsync>d__15.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.WebHost.HttpControllerHandler.<ProcessRequestAsyncCore>d__12.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.TaskAsyncHelper.EndTask(IAsyncResult ar)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

二、问题原因分析

从错误的堆栈可以看到是ASP.NET的Web框架在请求执行的最后要设置StatusCode的时候抛出了异常;

查看.NET Framework 4.7.2中Response的StatusCode的代码,可以看到首先会检测_headersWritten字段,如果字段值为true,则会抛出我们看到的异常;

public int StatusCode {
get {
return _statusCode;
} set {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_set_status_after_headers_sent)); if (_statusCode != value) {
_statusCode = value;
_subStatusCode = 0;
_statusDescription = null;
_statusSet = true;
}
}
}

我们查看HttpHeaderCollection的Add及SetHeader方法,发现并没有修改_headersWritten的值,所以直接向Response添加header并不会导致这个异常;

我们查看Response的Flush方法,发现里边会将_headersWritten的值设置为true;但是如果我们注释掉Flush则就无法下载文件了;

三、从REST角度理解Web API的返回值

我们使用的是Web API,它是从REST借鉴过来的概念;

REST与技术无关,代表的是一种软件架构风格。REST是Representational State Transfer的简称,中文翻译为“表征状态转移”;REST从资源的角度来审视整个网络,它将分布在网络中某个节点中的资源通过URI进行标识,客户端应用通过URI来获取资源的表征,获得这些表征致使这些应用程序转变了状态。随着不断获取资源的表征,客户端应用不断地在转变着状态。

Web API是一种web形式的服务,其需要可以表征对资源操作的各种情况,也就是需要可以通过返回值来表征操作的结果;

平时我们都是如下直接返回对应的模型对象

public class ProductsController : ApiController
{
public IEnumerable<Product> Get()
{
return GetAllProductsFromDB();
}
}

Web API 使用请求中的 Accept 标头来选择格式化程序,默认情况下会返回json格式的数据;

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
Date: Mon, 27 Jan 2014 08:53:35 GMT
Content-Length: 56 [{"Id":1,"Name":"Yo-yo","Category":"Toys","Price":6.95}]

虽然框架提供了这种简单的方式,但这种方法的缺点是不能直接返回错误代码如404;Web API内部还是会将各种形式的返回结果转化为HttpResponseMessage,最终将HttpResponseMessage转换为 HTTP 响应消息;

public class ValuesController : ApiController
{
public HttpResponseMessage Get()
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent("hello", Encoding.Unicode);
response.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromMinutes(20)
};
return response;
}
}

Web API返回的响应形式如下

HTTP/1.1 200 OK
Cache-Control: max-age=1200
Content-Length: 10
Content-Type: text/plain; charset=utf-16
Server: Microsoft-IIS/8.0
Date: Mon, 27 Jan 2014 08:53:35 GMT hello

四、使用Web API的方式实现文件的下载

我们通过HttpResponseMessage来承载文件内容,并修改对应的header;

[HttpPost]
[Route("Download")]
public HttpResponseMessage Download()
{
HttpResponse response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StreamContent(GetFileContent());
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = filename
}; return response;
}

五、Web API处理管道简介

整个的Web API处理管道如下图所示;

首先Web API通过承载URL模式和对应的处理类的HttpWebRoute对象添加到路由集合;当请求到来的时候,会通过路由模块定位到对应的HttpControllerHandler,HttpControllerHandler会将请求转化成HttpRequestMessage,然后转发给HttpServer,HttpServer会将请求逐个传递个HttpMessageHandler链中的对象进行处理,并最终通过HttpControllerDispatcher转发给实现服务的那个控制器的action;

从文件下载视角来理解Web API的更多相关文章

  1. 理解WEB API网关

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  2. 如何理解 Web API

    什么是web API? web API 控制器.路由 测试  Web  API  什么是web API ? 简单说,API是接口,访问程序的某一个功能或者数据,实现移动端和客户端的程序之间的数据交互: ...

  3. [ASP.NET MVC 小牛之路]18 - Web API

    Web API 是ASP.NET平台新加的一个特性,它可以简单快速地创建Web服务为HTTP客户端提供API.Web API 使用的基础库是和一般的MVC框架一样的,但Web API并不是MVC框架的 ...

  4. ASP.NET MVC WEB API必知必会知识点总结

    一.理解WEB API:提供基于RESTful架构的WEB服务,通过HTTP请求方法(GET, PUT, POST, DELETE)映射到服务器端相应的ACTION方法(CRUD). RESTful架 ...

  5. 我所理解的RESTful Web API [Web标准篇]

    REST不是一个标准,而是一种软件应用架构风格.基于SOAP的Web服务采用RPC架构,如果说RPC是一种面向操作的架构风格,而REST则是一种面向资源的架构风格.REST是目前业界更为推崇的构建新一 ...

  6. 对RESTful Web API的理解与设计思路

    距离上一篇关于Web API的文章(如何实现RESTful Web API的身份验证)有好些时间了,在那篇文章中提到的方法是非常简单而有效的,我在实际的项目中就这么用了,代码经过一段时间的磨合,已经很 ...

  7. 我所理解的RESTful Web API [Web标准篇]【转】

    原文:http://www.cnblogs.com/artech/p/restful-web-api-01.html REST不是一个标准,而是一种软件应用架构风格.基于SOAP的Web服务采用RPC ...

  8. 我所理解的RESTful Web API [设计篇]

    <我所理解的RESTful Web API [Web标准篇]>Web服务已经成为了异质系统之间的互联与集成的主要手段,在过去一段不短的时间里,Web服务几乎清一水地采用SOAP来构建.构建 ...

  9. ASP.NET Web API 2 之文件下载

    Ø  前言 目前 ASP.NET Web API 的应用非常广泛,主要承载着服务端与客户端的数据传输与处理,如果需要使用 Web API 实现文件下载,该 实现呢,其实也是比较简单,以下示例用于下载安 ...

随机推荐

  1. Linux性能优化之磁盘I/O性能指标

    讨论指标之前,得先解决两个概念:文件系统和磁盘I/O栈. 文件系统是什么?文件系统是在磁盘的基础上,提供了一个用来管理文件的树状结构.简言之,文件系统是树状结构,一种数据结构~逻辑上的概念.磁盘大家都 ...

  2. 解决Springboot中的日期解析错误

    错误信息: error: Failed to parse Date value '2022-01-12 15:00:00': Cannot parse date "2022-01-12 15 ...

  3. 干掉Session?这个跨域认证解决方案真的优雅!

    用户登录认证是 Web 应用中非常常见的一个业务,一般的流程是这样的: 客户端向服务器端发送用户名和密码 服务器端验证通过后,在当前会话(session)中保存相关数据,比如说登录时间.登录 IP 等 ...

  4. [题解]Codeforces Round #519 - D. Mysterious Crime

    [题目] D. Mysterious Crime [描述] 有m个n排列,求一共有多少个公共子段. 数据范围:1<=n<=100000,1<=m<=10 [思路] 对于第一个排 ...

  5. Django框架表关系外键-多对多外键(增删改查)-正反向的概率-多表查询(子查询与联表查询)

    目录 一:表关系外键 1.提前创建表关系 2.目前只剩 书籍表和 书籍作者表没创建信息. 3.增 4.删 5.修改 二:多对多外键增删改查 1.给书籍绑定作者 2.删 3.修改 4.清空 三:正反向的 ...

  6. 递归Recursion

    从开始自学写代码开始,就感觉递归是个特别美丽的算法. "如果使用循环,程序的性能可能更高:如果使用递归,程序可能更容易理解.如何选择要看什么对你来说更重要." 编写递归函数时,必须 ...

  7. vmware扩容centos根目录

    在vmware中编辑,给磁盘扩容 在centos中使用命令fdisk /dev/sda 输入n创建新分区 输入p创建主分区 回车,默认分区号 回车,默认起始扇区 回车,默认last扇区 输入t,改变分 ...

  8. 决策树CART回归树——算法实现

    决策树模型 选择最好的特征和特征的值进行数据集划分 根据上面获得的结果创建决策树 根据测试数据进行剪枝(默认没有数据的树分支被剪掉) 对输入进行预测 模型树 import numpy as np de ...

  9. kNN(k近邻)算法代码实现

    目标:预测未知数据(或测试数据)X的分类y 批量kNN算法 1.输入一个待预测的X(一维或多维)给训练数据集,计算出训练集X_train中的每一个样本与其的距离 2.找到前k个距离该数据最近的样本-- ...

  10. ElasticSearch内部基于_version乐观锁控制机制

    1.悲观锁与乐观锁机制 为控制并发问题,我们通常采用锁机制.分为悲观锁和乐观锁两种机制. 悲观锁:很悲观,所有情况都上锁.此时只有一个线程可以操作数据.具体例子为数据库中的行级锁.表级锁.读锁.写锁等 ...