ASP.NET Web Api 服务器端变了,客户端该如何修改请求(转载)
转载地址:http://www.cnblogs.com/fzrain/p/3558765.html
前言
一旦我们将API发布之后,消费者就会开始使用并和其他的一些数据混在一起。然而,当新的需求出现时变化是不可避免的,你也许会庆幸API变了对现有客户端没受到影响,但是这种情况不会一直发生。
因此,在具体实现之前仔细考虑一下ASP.NET Web Api的版本策略就变得很有必要了。在我们的案例中,需求发生了变化而且我们通过创建不同版本的API来解决变化,同时不影响已经在使用API的客户端。我们把新的API版本和旧的API版本一起返回给客户端,让它有足够的时间迁移到最新版本的API,有时候多版本共存也是有可能的。
实现版本控制的方式有好多,本文主要介绍URI,query string,自定义Header和接收Header
API变了
简单起见,我们让“StudentsController”中的Get方法发生变化——在响应报文的body中,我们用“CoursesDuration”和“FullName”属性替换原来的“FirstName”和“LastName”属性。
最简单的做法就是创建一个与“StudentsController”一样的Controller并命名为“StudentsV2Controller”,我们将根据不同的API版本选择合适的Controller。在新的Controller中我们实现上述变化并使用相同的Http方法,同时不做任何介绍
现在我们请求“StudentsController”的Get方法是,会返回如下数据:

[{
"id": 2,
"url": "http://localhost:8323/api/students/HasanAhmad",
"firstName": "Hasan",
"lastName": "Ahmad",
"gender": 0,
"enrollmentsCount": 4
},
{
"id": 3,
"url": "http://localhost:8323/api/students/MoatasemAhmad",
"firstName": "Moatasem",
"lastName": "Ahmad",
"gender": 0,
"enrollmentsCount": 4
}]

我们期待访问“StudentsV2Controller”的Get方法后应该的到:

[{
"id": 2,
"url": "http://localhost:8323/api/students/HasanAhmad",
"fullName": "Hasan Ahmad",
"gender": 0,
"enrollmentsCount": 4,
"coursesDuration": 13
},
{
"id": 3,
"url": "http://localhost:8323/api/students/MoatasemAhmad",
"fullName": "Moatasem Ahmad",
"gender": 0,
"enrollmentsCount": 4,
"coursesDuration": 16
}]

ok,下面来实现,复制粘贴”StudnetsController”并重命名为“StudnetsV2Controller”,更改Get方法的实现:

public IEnumerable<StudentV2BaseModel> Get(int page = 0, int pageSize = 10)
{
IQueryable<Student> query; query = TheRepository.GetAllStudentsWithEnrollments().OrderBy(c => c.LastName); var totalCount = query.Count();
var totalPages = Math.Ceiling((double)totalCount / pageSize); var urlHelper = new UrlHelper(Request);
var prevLink = page > 0 ? urlHelper.Link("Students", new { page = page - 1, pageSize = pageSize }) : "";
var nextLink = page < totalPages - 1 ? urlHelper.Link("Students", new { page = page + 1, pageSize = pageSize }) : ""; var paginationHeader = new
{
TotalCount = totalCount,
TotalPages = totalPages,
PrevPageLink = prevLink,
NextPageLink = nextLink
}; System.Web.HttpContext.Current.Response.Headers.Add("X-Pagination",
Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader)); var results = query
.Skip(pageSize * page)
.Take(pageSize)
.ToList()
.Select(s => TheModelFactory.CreateV2Summary(s)); return results;
}

可以看到,这里我们改的很少,返回的类型变成了“StudentV2BaseModel”,而这个类型是由ModelFactory的CreateV2Summary方法创建的。因此我们需要添加StudentV2BaseModel类和CreateV2Summary方法:

public class StudentV2BaseModel
{
public int Id { get; set; }
public string Url { get; set; }
public string FullName { get; set; }
public Data.Enums.Gender Gender { get; set; }
public int EnrollmentsCount { get; set; }
public double CoursesDuration { get; set; }
} public class ModelFactory
{
public StudentV2BaseModel CreateV2Summary(Student student)
{
return new StudentV2BaseModel()
{
Url = _UrlHelper.Link("Students", new { userName = student.UserName }),
Id = student.Id,
FullName = string.Format("{0} {1}", student.FirstName, student.LastName),
Gender = student.Gender,
EnrollmentsCount = student.Enrollments.Count(),
CoursesDuration = Math.Round(student.Enrollments.Sum(c => c.Course.Duration))
};
}
}

到目前为止,我们的准备工作就算做完了,下面介绍四种方式实现版本变化
使用URI控制Web Api的版本
在URI中包含版本号是最常见的做法,如果想用V1版本的api(使用http://localhost:{your_port}/api/v1/students/),同理,如果想用V2版本的api(使用http://localhost:{your_port}/api/v2/students/)
这种做法的好处就是客户端知道自己用的是哪一版本的api,实现方法就是在“WebApiConfig”中添加2条路由:

config.Routes.MapHttpRoute(
name: "Students",
routeTemplate: "api/v1/students/{userName}",
defaults: new { controller = "students", userName = RouteParameter.Optional }
); config.Routes.MapHttpRoute(
name: "Students2",
routeTemplate: "api/v2/students/{userName}",
defaults: new { controller = "studentsV2", userName = RouteParameter.Optional }
);

在上面代码中,我们添加了2条路由规则,它们彼此对应了相应的Controller。如果以后我们打算添加V3,那么就得再加一条。这里就会变得越来越混乱。
这种技术的主要缺点就是不符合REST规范因为URI一直会变,换句话说一旦我们发布一个新版本,就得添加一条新路由。
在我们讲解另外3种实现模式之前,我们先来看一下在web api框架是怎么根据我们的请求来选择相应的Controller的:在web api中有一个“DefaultHttpControllerSelector”类,其中有一个方法“SelectController()”,这个方法接收一个“HttpRequestMessage”类型的参数。这个对象包含一个含key/value键值对的route data,其中就包括在“WebApiConfig”中配置的controller的名字。根据这一条信息,通过反射获取所有实现“ApiController”的类,web api就会匹配到这个Controller,如果匹配结果不等于1(等于0或大于等于2),那么就会抛出一个异常。
我们自定义一个类“LearningControllerSelector”继承自“Http.Dispatcher.DefaultHttpControllerSelector”,重写“SelectController()”方法,具体代码如下:

public class LearningControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config;
public LearningControllerSelector(HttpConfiguration config)
: base(config)
{
_config = config;
} public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping(); //Will ignore any controls in same name even if they are in different namepsace var routeData = request.GetRouteData(); var controllerName = routeData.Values["controller"].ToString(); HttpControllerDescriptor controllerDescriptor; if (controllers.TryGetValue(controllerName, out controllerDescriptor))
{ var version = "2"; var versionedControllerName = string.Concat(controllerName, "V", version); HttpControllerDescriptor versionedControllerDescriptor;
if (controllers.TryGetValue(versionedControllerName, out versionedControllerDescriptor))
{
return versionedControllerDescriptor;
} return controllerDescriptor;
} return null; }
}

上述代码主要意思如下:
1.调用父类方法GetControllerMapping()获取所有实现了ApiController的类。
2.通过request对象获取routeData ,然后进一步获得Controller的name
3.根据我们刚刚得到的Controller,名字创建“HttpControllerDescriptor”对象,这个对象包含了描述Controller的信息
.4.接着,在我们找到的Controller的名字后面加上“V”和版本号,重复上面步骤即可。关于如何获得版本号,我们一会儿讨论,这里暂时写死成“2”。
为了使我们自定义的“Controller Selector”生效,因此需要在“WebApiConfig”中做如下配置:
config.Services.Replace(typeof(IHttpControllerSelector), new LearningControllerSelector((config)));
接下来我们就来实现请求如何发送版本号
使用Query String设置版本
使用query string设置版本顾名思义,就是在请求URI后面加上”?v=2“,例如这个URI:http://localhost:{your_port}/api/students/?v=2
我们可以认为客户端没有提供query string的版本号,那么版本号默认为“1”。
实现起来也不复杂,在我们的“LearningControllerSelector”类中添加一个“GetVersionFromQueryString()”方法,该方法接收一个HttpRequestMessage参数,并从这个请求对象中获取客户端所需要的版本:

private string GetVersionFromQueryString(HttpRequestMessage request)
{
var query = HttpUtility.ParseQueryString(request.RequestUri.Query); var version = query["v"]; if (version != null)
{
return version;
} return "1"; }

我们只需要在SelectController方法中调用这个方法即可,唯一的缺点依然是URI会变,不符合REST规范。
通过自定义请求头设置版本
现在我们使用另一种方式来发生版本号——自定义请求头,它不是URI的一部分,添加一个头“X-Learning-Version”并把版本号设置在里面,当客户端没有这条头信息是我们可以认为它需要V1版本。
实现这个技术,我们在“LearningControllerSelector”中添加一个“GetVersionFromHeader”方法,代码如下:

private string GetVersionFromHeader(HttpRequestMessage request)
{
const string HEADER_NAME = "X-Learning-Version"; if (request.Headers.Contains(HEADER_NAME))
{
var versionHeader = request.Headers.GetValues(HEADER_NAME).FirstOrDefault();
if (versionHeader != null)
{
return versionHeader;
}
} return "1";
}

这里做法很简单,我们先确定好请求头的名字,然后去request的Header中找,如果有数据,就获得。
客户端发送的请求如下:
这么做也有缺点,就是添加了一个请求头(注:这个缺点不是很理解),下面介绍第四种方式
使用Accept Header设置版本
这种方法是直接使用Accept Header, 请求的时候将它设置为“Accept:application/json; version=2”,我们依旧这么认为:如果客户端不提供版本号,我们就给他V1的数据。
在“LearningControllerSelector”类中添加“GetVersionFromAcceptHeaderVersion”方法,具体实现如下:

private string GetVersionFromAcceptHeaderVersion(HttpRequestMessage request)
{
var acceptHeader = request.Headers.Accept; foreach (var mime in acceptHeader)
{
if (mime.MediaType == "application/json")
{
var version = mime.Parameters
.Where(v => v.Name.Equals("version", StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if (version != null)
{
return version.Value;
}
return "1";
}
}
return "1";
}

这个实现看上去比上面更标准,更专业了。
ASP.NET Web Api 服务器端变了,客户端该如何修改请求(转载)的更多相关文章
- ASP.NET WEB API回发到客户端消息体的格式化
首先基于SOA的消息通信基本都是按照一个统一的协议规范进行交互,那么必须定义消息体.不同的交互结构设计了不同的消息体. 业界统一使用的SOAP是一种规范的XML消息内容.例如在WCF中返回的结果. 随 ...
- ASP.NET Web API运行提示:找到了与该请求匹配的多个操作的解决方法
- 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用
由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...
- 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【开篇】【持续更新中。。。】
最近发现web api很火,园内也有各种大神已经在研究,本人在asp.net官网上看到一个系列教程,原文地址:http://bitoftech.net/2013/11/25/detailed-tuto ...
- 一个ASP.NET Web API 2.0应用
在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用 由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.N ...
- 【ASP.NET Web API教程】4.1 ASP.NET Web API中的路由
原文:[ASP.NET Web API教程]4.1 ASP.NET Web API中的路由 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. ...
- ASP.NET WEB API构建基于REST风格
使用ASP.NET WEB API构建基于REST风格的服务实战系列教程[开篇] 最近发现web api很火,园内也有各种大神已经在研究,本人在asp.net官网上看到一个系列教程,原文地址:http ...
- Asp.Net Web API(三)
Routing Tables路由表 在Asp.Net Web API中,一个控制器就是一个处理HTTP请求的类,控制器的public方法就被叫做action方法或简单的Action.当Web API接 ...
- Asp.Net Web API(二)
创建一个Web API项目 第一步,创建以下项目 当然,你也可以创建一个Web API项目,利用 Web API模板,Web API模板使用 ASP.Net MVC提供API的帮助页. 添加Model ...
随机推荐
- [转载]Linux下tomcat的catalina.out文件过大,以及目录更改解决办法
原文链接:http://liyan0524.iteye.com/blog/1285644 方法1—分割流 使用cronolog工具切分Tomcat的catalina.out日志文件 cronolog一 ...
- Genymotion加速下载虚拟镜像速度慢失败Connection timeout
Genymotion也算是个android的模拟程序了, Add new device后下载速度太慢了,容易失败 解决方法有二: 1.设置HTTP代理,在Setting->Network,自己设 ...
- 字符串驱动技术—— MethodAddress , MethodName , ObjectInvoke
首先看一段Delphi帮助中的介绍(After Delphi 6 ): Returns the address of a published method. class function Method ...
- zend studio安装svn插件
进入zend->Help->Install New Software 输入如下地址: http://subclipse.tigris.org/update_1.8.x 选择subclips ...
- Centos安装firefox/chrome
centos安装chrome:去官网下载chrome安装包(xxx.rpm),带软件安装工具的系统双击该xxx.rpm就能自动安装,或者sudo rpm -i xxx.rpm安装. centos卸载自 ...
- 用Kibana和logstash快速搭建实时日志查询、收集与分析系统
Logstash是一个完全开源的工具,他可以对你的日志进行收集.分析,并将其存储供以后使用(如,搜索),您可以使用它.说到搜索,logstash带有一个web界面,搜索和展示所有日志. kibana ...
- Strobogrammatic Number
Strobogrammatic Number I A strobogrammatic number is a number that looks the same when rotated 180 d ...
- CentOS 7部署Kafka和Kafka集群
CentOS 7部署Kafka和Kafka集群 注意事项 需要启动多个shell脚本交互客户端进行验证,运行中的客户端不要停止. 准备工作: 安装java并设置java环境变量,在`/etc/prof ...
- vs2013 控制台程序exe图标
工程右击选择添加resource->Icon. 在工程目录就会生成 工程名.rc 和 XX.ico文件. 重新编译程序,就会生成有图标的exe. 对应的配置在 工程名.rc 文件里,用记事 ...
- STL_fill()用法
以前很多次会给某个区间赋值,今天才知道有个函数可以满足这个功能. void fill (ForwardIterator first, ForwardIterator last, const T& ...