`
黄继华
  • 浏览: 42750 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

MVC4 WebAPI(二)——Web API工作方式

 
阅读更多

在上篇文章中和大家一起学习了建立基本的WebAPI应用,立刻就有人想到了一些问题:
1.客户端和WebService之间文件传输
2.客户端或者服务端的安全控制
要解决这些问题,要了解一下WebAPI的基本工作方式。

(一)WebAPI中工作的Class

在MVC中大家都知道,获取Request和Response使用HttpRequest和HttpResponse两个类,在WebAPI中使用两外两个类:
HttpRequestMessage 和HttpResponseMessage,分别用于封装Requset和Response。除了这两个类之外,还有一个常见的抽象 类:HttpMessageHandler,用于过滤和加工HttpRequestMessage和HttpResponseMessage

(二)解决第一个问题

其 实第一个问题之所以被提出来应该是和客户端有关,如果客户端的请求是我们手写提交的,比如使用HttpClient封装的请求,则要传递文件之前,我们一 般会进行一次序列化,转化为二进制数组之类的,在网络上传输。这样的话,在Controller中的Action参数里,我们只需要接收这个二进制数组类 型的对象就可以了。
但是如果客户端是Web Form呢,比如我们提交一个Form到指定的Controller的Action中,这个Action要接收什么类型的参数呢?
或者我们问另外一个问题,如果我将Web Form提交到一个WebAPI的Action中 ,我要怎么去取出这个表单中的数据呢?
其 实我们应该想到:我们的Action设置的参数之所以能够被赋值,是因为WebAPI的架构中在调用Action时将HTTP请求中的数据解析出来分别赋 值给Action中的参数,如果真是这样的话,我们只需要在Action中获取到HTTP请求,然后直接获取请求里面的数据,就能解决上面的问题。
这 种想法是正确的,只不过,此时的HTTP请求已经不是最原始的HTTP Request,而是已经被转化成了HttpRequestMessage,在Action中,我们可以直接调用base.Requet来得到这个 HttpRequestMessage实例,通过这个实例我们就可以随心所欲的取出HTTP请求中想要的数据。

2.1从RequestMessage中获取普通表单数据

这里的普通表单是指不包含File的表单,也就是说表单的enctype值不是multipart/form-data,这时,表单的数据默认情况下是以Json来传递的
如下页面

复制代码
<form name="form" action="~/api/FormSubmit?key=11234" method="post">
    <input type="text" name="key" id="txtKey" />
    <br />

    <input type="text" name="value" id="txtValue" />
    <br />
    
    <input type="submit" id="btnSubmit" value="Submit" />
     
</form>
复制代码

捕获到的请求为

提交到对应的Action为:

复制代码
        [HttpPost]
        public async void submitForm()
        {
            StringBuilder sb = new StringBuilder();
            HttpContent content = Request.Content;
            JsonObject jsonValue = await content.ReadAsOrDefaultAsync<JsonObject>();
            foreach (var x in jsonValue)
            {
                sb.Append(x.Key);
                string va ;
                if (x.Value.TryReadAs<string>(out va))
                {
                    sb.Append(va);
                }
            }
        }
复制代码

这样最后可以得到 Json的值:{"key":"123","value":"123"} sb处理后的值为:key123value123

:在该action中使用到了关键字async和await,这些在4.5中新提出的关键字主要是用于进行多线程取值的,在MVCAPI的设计中,大部分的方法都被设计成类似于下面的方法

public static Task<T> ReadAsOrDefaultAsync<T>(this HttpContent content);

返 回值是一个Task,这种返回新线程的方法虽然可以提高系统的响应能力,但是多线程取值会给编码带来不便,所以新出的关键字await用于阻塞当前线程并 获取目标线程的返回值,在方法体中使用await关键字后要求将方法声明为async用来表示该方法是异步的,并且返回值必须为void或者将返回者封装 在一个Task中
当然,如果你不喜欢这种写法,上面的action也可以写为:

            Task readTask = content.ReadAsOrDefaultAsync<JsonObject>().ContinueWith((task) => { jsonValue = task.Result; });
            readTask.Wait();


2.2从RequestMessage中获取multipart表单数据
将view页面改写为

复制代码
<form name="form" action="~/api/FormSubmit?key=11234" method="post" enctype="multipart/form-data" >
    <input type="text" name="key" id="txtKey" />
    <br />
    <input type="text" name="value" id="txtValue" />
    <br />
    <input type="file" name="file" id="upFile" />
    <br />
    <input type="submit" id="btnSubmit" value="Submit" />
</form>
复制代码

此时捕获到得请求是


这里的文件内容被捕获软件解析成字符串,当然如果我上传的是其他的非文本格式的文件,文件会被转化为二进制数组
这时如果我们不更改action,而直接调用,会发生错误,原因很明显,这个HTTP的报文内容是无法被转换为JSON的,这时我们需要将表单的报文解析成另外一种格式

复制代码
                IEnumerable<HttpContent> bodyparts = await content.ReadAsMultipartAsync();
                foreach (var bodypart in bodyparts)
                {
                    string name;
                    name = bodypart.Headers.ContentDisposition.Name;
                    sb.Append(name + ":");
                    if (bodypart.Headers.Contains("filename"))
                    {
                        Stream stream = await bodypart.ReadAsStreamAsync();
                        StreamReader reader = new StreamReader(stream);
                        sb.Append(reader.ReadToEnd());
                        sb.Append("----");
                    }
                    else
                    {
                        string val = await bodypart.ReadAsStringAsync();
                        sb.Append(val);
                        sb.Append("----");
                    }
                }
复制代码

得到的处理后的sb值为:

{"key":123----"value":123----"file":******{文件的内容}*****----}
整合后的Action为

复制代码
        [HttpPost]
        public async void submitForm()
        {
            StringBuilder sb = new StringBuilder();
            HttpContent content = Request.Content;
            if (content.IsMimeMultipartContent())
            {
                IEnumerable<HttpContent> bodyparts = await content.ReadAsMultipartAsync();
                foreach (var bodypart in bodyparts)
                {
                    string name;
                    name = bodypart.Headers.ContentDisposition.Name;
                    sb.Append(name + ":");
                    if (bodypart.Headers.Contains("filename"))
                    {
                        Stream stream = await bodypart.ReadAsStreamAsync();
                        StreamReader reader = new StreamReader(stream);
                        sb.Append(reader.ReadToEnd());
                        sb.Append("----");
                    }
                    else
                    {
                        string val = await bodypart.ReadAsStringAsync();
                        sb.Append(val);
                        sb.Append("----");
                    }
                }
            }
            else
            {
                JsonObject jsonValue = await content.ReadAsOrDefaultAsync<JsonObject>();
                foreach (var x in jsonValue)
                {
                    sb.Append(x.Key);
                    string va;
                    if (x.Value.TryReadAs<string>(out va))
                    {
                        sb.Append(va);
                    }
                }
            }
        }
复制代码

(三)WebAPI工作方式

要想解决第二个问题就没这么容易了,我们需要更深入的理解WebAPI的工作方式。
其实对于WebAPI来说,它最初被设计为和WCF一样的:客户端、服务端两套结构,我们到现在之所以还没有提到客户端,是因为我们的请求别的方式来封装成HTTP请求或接收HTTP相应的,比如AJAX和Form表单提交。

在这里先给出一个服务端的响应工作流,让大家有个大体上的认识

(不得已在图片中加了水印,因为看到自己辛苦写的东西被人直接拿走,也不给出原文链接,心里真的不好受..希望不会影响大家的阅读...)
由于图片大小限制,所有的HttpRequestMessage被简写为HttpRequestMsg,HttpResponseMessage被简写了HttpResponseMsg

大 家可以看到,HTTP的请求最先是被传递到HOST中的,如果WebAPI是被寄宿在IIS上的,这个HOST就是IIS上,HOST是没有能力也没有必 要进行请求的处理的,请求通过HOST被转发给了HttPServer此时已经进入WebAPI的处理加工范围,HttpServer是 System.Net.HTTP中的一个类,通过HttpServer,请求被封装成了WebAPI中的请求承载 类:HttpRequestMessage,这个封装后的请求可以经过一系列自定义的Handler来处理,这些handler串联成一个 pipeline,最后请求会被传递给HttpControlDispather,这个类通过对路由表的检索来确定请求将被转发到的具体的 Controller中的Action。

Client端的处理与服务端类似,直接上图:

其实根据微软的说法,他们本身就被设计成类似但是可以独立运行的结构

ASP.NET Web API has a pipeline for processing HTTP messages on both the client and server. The client and server sides are designed to be symmetrical but independent; you can use each half by itself. Both sides are built on some common objects:

  • HttpRequestMessagerepresents the HTTP request.
  • HttpResponseMessagerepresents the HTTP response.
  • HttpMessageHandlerobjects process the request and response.

直接看图,在客户端,Handlers pipeline最终是被传递到HttpClientHandler上的,由他负责HttpRequestMessage到HTTP请求的转换。

这里只说明一下Request,Response与其类似。

(四)解决第二个问题

由 此我们早就可以看出,想要解决第二个问题,可以直接在Handler PipeLine中进行,这种AOP风格的过滤器(拦截器)在REST的Webservice的安全验证中应用很广,一般大家比较乐于在HTTP头或者在 HTTP请求的URL中加上身份验证字段进行身份验证,下面举一个在Http头中添加身份验证信息的小例子

4.1客户端
客户端的customhandler用于将身份验证信息添加入报头

复制代码
    class RequestUpHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            request.Headers.Add("key", "11234");
            return base.SendAsync(request, cancellationToken);
        }
    }
复制代码

注:
1.customhandler继承自DelegatingHandler类,上面已经说过,WebAPI的客户端和服务端被设计为相互对应的两套结构,所以不论是在客户端还是服务端,customhandler都是继承自DelegatingHandler类
2.DelegatingHandler的sendAsync方法便是处理请求和接受请求时会被调用的方法,该方法返回值是HttPResponseMessage,接收的值为HttpRequestMessage,符合我们的一般认知
3.方法的最后,调用base.SendAsync是将Request继续向该pipeline的其他customHandler传递,并获取其返回值。由于该方法不包含Response的处理逻辑,只需直接将上一个CustomHandler的
返回值直接返回
客户端主程序

复制代码
        static void Main(string[] args)
        {
            HttpClient client = new HttpClient(new RequestUpHandler() { InnerHandler = new HttpClientHandler() });
            HttpResponseMessage response = client.GetAsync("http://localhost:60023/api/FormSubmit").Result;
            response.Content.ReadAsAsync<string>().ContinueWith((str) => { Console.WriteLine(str.Result); });
            Console.Read();
        }
复制代码

客 户端的主程序创建了一个HttpClient,HttpClient可以接受一个参数,该参数就是CustomHandler,此处我们嵌入了我们定义的 RequestUpHandler,用于对Request报头进行嵌入身份验证码的处理,CustomHandler通过InnerHandler属性嵌 入其内置的下一个CustomHandler,此处,由于没有下一个CustomerHandler,我们直接嵌入HttpClientHandler用 于将HttpRequestMessage转化为HTTP 请求、将HTTP响应转化为HttpResponseMessage

4.2服务端
服务端的customHandler用于解析HTTP报头中的身份认证码

复制代码
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            int matchHeaderCount = request.Headers.Count((item) =>
            {
                if ("key".Equals(item.Key))
                {
                    foreach (var str in item.Value)
                    {
                        if ("11234".Equals(str))
                        {
                            return true;
                        }
                    }
                }
                return false;
            });
            if (matchHeaderCount>0)
            {
                return base.SendAsync(request, cancellationToken);
            }
            return Task.Factory.StartNew<HttpResponseMessage>(() => { return new HttpResponseMessage(HttpStatusCode.Forbidden); });
        }
复制代码

注:代码的处理逻辑很简单:如果身份验证码匹配成功,则通过base.SendAsync继续将请求向下传递,否则返回直接中断请求的传递,直接返回一个响应码为403的响应,指示没有权限。
注意由于SendAsync的返回值需要封装在Task之中,所以需要使用Task.Factory.StartNew将返回值包含在Task中

将customHandler注入到HOST中
本例中WebAPI HOST在IIS上,所以我们只需将我们定义的CustomHandler在Application_Start中定义即可

复制代码
        protected void Application_Start()
        {
            //省略其他逻辑代码

            GlobalConfiguration.Configuration.MessageHandlers.Add(new HttpUrlHandler());
        }
复制代码

由于WebAPI Host在IIS上,所以HttpServer和HttpControllerDispatcher不用我们手工处理

在加上上面的处理后,如果没有身份验证码的请求,会得到如下的响应

分享到:
评论

相关推荐

    使用.NET Client调用Web API

    除了可以在网站中使用外,Web API更广阔的应用于各种客户端开发和移动应用开发,例如Windows的桌面小工具、Windows...本教程演示了如何用C#调用Web API,至于其他编程语言——例如Java——都有相应的访问网络资源的库。

    C#进阶系列--WebApi

    C#进阶系列——WebApi 路由机制剖析:你准备好了吗? ................................................................................................... 3 一、MVC和WebApi路由机制比较 .......................

    Spring MVC 员工管理系统

    Spring为展现层提供了一个优秀的Web框架——Spring MVC。和众多其它Web框架一样,它基于MVC设计理念,此外,由于它采用了松散耦合可插拔组件结构,具有比其它MVC框架更多的扩展性和灵活性。 Spring MVC框架围绕...

    blogit:MVC Web 应用程序,我在其中试验了许多不同的技术和设计模式

    使用的一些设计模式和技术包括: 使用 Bootstrap 和 MVC 5 框架进行现代 Web 开发通过 Autofac 进行依赖注入使用实体框架的对象关系映射 (ORM) 存储库数据访问模式使用 WCF 的 SOAP Web 服务使用 Web API 的 ...

    外文翻译 stus MVC

    Figure 4 displays the Web adaptation of MVC, also commonly known as MVC Model 2 or MVC 2. The ActionServlet class Do you remember the days of function mappings? You would map some input event to a...

    Java Web编程宝典-十年典藏版.pdf.part2(共2个)

    全书分4篇,共24章,其中,第1篇为技能学习篇,主要包括Java Web开发环境、JSP语法、JSP内置对象、Java Bean技术、Servlet技术、EL与JSTL标签库、数据库应用开发、初识Struts2基础、揭密Struts2高级技术、Hib锄劬e...

    spring web flow demo

    从清单 1 中,应注意到一个很重要的特征—— Spring Web Flow 语义与 Servlet API 3 无关。更确切地讲, Spring Web Flow 语义关注的是业务的流程,并未与 Sun 公司的 Web 规范紧密结 合,这种描述是更高层次的抽象...

    SSM实战项目——Java高并发秒杀API,详细流程+学习笔记.zip

    SSM(Spring + Spring MVC + MyBatis)框架作为Java开发中的黄金组合,为开发者提供了强大的技术支持和丰富的功能。本系列资料将带您从零基础开始,逐步掌握SSM的核心技术和最佳实践,助您在Java Web开发领域更上一...

    JSP设计第二版(附源码).rar

    它包括关于JSP Standard Tag Library(JSTL)的详细内容 —— JSTL是一组殷切加入的JSP元素规范,用于多数JSP应用程序中需要的任务。本书开篇阐述了JSP如何充分利用Java servlet来创建高效、可移植的Web应用程序。书...

    PHP高级程序设计_模式、框架与测试 PDF及源码

    第一部分 OOP和模式 第1章 抽象类、接口和契约式编程 第2章 静态变量、成员和方法 第3章 单例模式和工厂模式 第4章 异常 第5章 PHP 6中的新特性 第二部分 测试和文档编写 第6章 文档编写和编码规范 第7章 反射API ...

    PHP高级程序设计_模式、框架与测试

    书中首先从OOP 采用的机制—— 抽象类、接口、契约式编程开始讲起,然后介绍了静态方法、单例模式、工厂模式和PHP 6 的新特性等内容, 接着介绍了测试和文档方面的内容,还介绍了标准PHP 库SPL 方面的知识以及PHP ...

    PHP高级程序设计-模式-框架-测试

    ” ——Amazon.com 目录第一部分 OOP和模式 第1章 抽象类、接口和契约式编程 第2章 静态变量、成员和方法 第3章 单例模式和工厂模式 第4章 异常 第5章 PHP 6中的新特性第二部分 测试和文档编写 第6章 ...

    医院信息系统(临床诊疗、药品管理、财务管理、患者管理)

    本系统包括以下工作站:门诊医生工作站、药房医生工作站、医技医生工作站、收费员工作站、对帐员工作站、管理员工作站。基于Spring cloud和Spring boot 2.x 实现 *项目演示 username:演示用户 password:test ...

    mickjoust-boot-demo:Spring Boot 实践折腾记,代码实现

    Spring Boot 实践折腾记的代码实现package说明:boot学习Spring Boot的文章集合入门Web应用操作数据库序列化API化Spring Boot 实践折腾记(8):极速简化MVCSpring Boot 实践折腾记(9):快速构建 Web API响应式...

    ABP(2.02)框架相关Demo代码以及报错处理和中文说明手册

    基于最新的.NET技术 (目前是ASP.NET MVC 5、Web API 2、C# 5.0,在ASP.NET 5正式发布后会升级) 实现领域驱动设计(实体、仓储、领域服务、领域事件、应用服务、数据传输对象,工作单元等等) 实现分层体系结构...

    JSP设计第二版(附源码)

    它包括关于JSP Standard Tag Library(JSTL)的详细内容 —— JSTL是一组殷切加入的JSP元素规范,用于多数JSP应用程序中需要的任务。本书开篇阐述了JSP如何充分利用Java servlet来创建高效、可移植的Web应用程序。书...

    JSP设计第二版 - 内附源码(PDF中文版)

    《JSP设计(第二版)》得到了彻底的修订和更新,包括了JSP规范1.2版本中的重大变化。它包括关于JSP Standard Tag Library(JSTL)的详细内容 —— JSTL是一组殷切加入的JSP元素规范,用于多数JSP应用程序中需要的...

    从Java走向Java+EE+.rar

    21.2 实例——利用AXIS开发一个简单的Web Service 310 21.3 小结 314 第22章 Java EE的安全 315 22.1 利用JAAS进行验证和授权 315 22.2 利用JSSE进行安全传输 317 22.3 小结 317 第23章 Java EE的测试 ...

    asp.net知识库

    ASP.NET 2.0使用Web Part创建应用程序之二(共二) 体验 .net2.0 的优雅(2) -- ASP.net 主题和皮肤 NET2.0系列介绍(一).NET 2.0 中Web 应用程序主题的切换 ASP.NET 2.0 中Web 应用程序主题的切换 2.0正式版中...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    传统方式根据主流系统分别开发相应的系统耗时又耗力,为了高效开发并节约开发项目成本,本文采用Android+HTML5相结合的方式进行移动端Web系统的设计研发工作。 然而,由于HTML5的W3C标准规范还未制定,安卓系统中类...

Global site tag (gtag.js) - Google Analytics