转载地址: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 服务器端变了,客户端该如何修改请求(转载)的更多相关文章

  1. ASP.NET WEB API回发到客户端消息体的格式化

    首先基于SOA的消息通信基本都是按照一个统一的协议规范进行交互,那么必须定义消息体.不同的交互结构设计了不同的消息体. 业界统一使用的SOAP是一种规范的XML消息内容.例如在WCF中返回的结果. 随 ...

  2. ASP.NET Web API运行提示:找到了与该请求匹配的多个操作的解决方法

  3. 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用

    由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...

  4. 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【开篇】【持续更新中。。。】

    最近发现web api很火,园内也有各种大神已经在研究,本人在asp.net官网上看到一个系列教程,原文地址:http://bitoftech.net/2013/11/25/detailed-tuto ...

  5. 一个ASP.NET Web API 2.0应用

    在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用 由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.N ...

  6. 【ASP.NET Web API教程】4.1 ASP.NET Web API中的路由

    原文:[ASP.NET Web API教程]4.1 ASP.NET Web API中的路由 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. ...

  7. ASP.NET WEB API构建基于REST风格

    使用ASP.NET WEB API构建基于REST风格的服务实战系列教程[开篇] 最近发现web api很火,园内也有各种大神已经在研究,本人在asp.net官网上看到一个系列教程,原文地址:http ...

  8. Asp.Net Web API(三)

    Routing Tables路由表 在Asp.Net Web API中,一个控制器就是一个处理HTTP请求的类,控制器的public方法就被叫做action方法或简单的Action.当Web API接 ...

  9. Asp.Net Web API(二)

    创建一个Web API项目 第一步,创建以下项目 当然,你也可以创建一个Web API项目,利用 Web API模板,Web API模板使用 ASP.Net MVC提供API的帮助页. 添加Model ...

随机推荐

  1. PHP微信支付开发实例

    这篇文章主要为大家详细介绍了PHP微信支付开发过程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 PHP微信支付开发过程,分享给大家,供大家参考,具体内容如下 1.开发环境 Thinkphp 3. ...

  2. iOS9开发者测试版下载地址(系转载)

    将链接复制后,打开迅雷即可下载!请根据自己的手机型号下载.不需要udid绑定.开发者账号,下载后直接升级即可.请根据自己的手机型号将链接复制 打开迅雷 点击立即下载即可.不过好像Mac版的迅雷下载不了 ...

  3. 一个IP多个https站点配置

    在一台主机上放置一个https网站,究竟该怎么配置?对于IIS和Apache以及Nginx等不同的服务器,方法是不同的,网上有很多教程,在此就不再赘述了. 至于一台主机,如何配置多个https网站呢? ...

  4. python自动化之装饰器

    1 高阶函数 满足下列条件之一就可成函数为高阶函数 某一函数当做参数传入另一个函数中 函数的返回值包含n个函数,n>0 高阶函数示范 def bar(): print 'in the bar' ...

  5. django 1.7+ default_permissions

    由于做Caption要做权限设计.在核心类的设计的时候需要做好权限的基础设计.django 1.7+以后 django.db.modes新增特性 default_permissions,官方文档语焉不 ...

  6. python——有限状态机

    前言 使用Python,大部分时间花在了处理文本上.在处理文本的时候,如果对有限状态机有所了解的话,处理起来会更加得心应手.可以把文本看成一个流,然后有一个机器对这个流进行操作.这个机器有状态,不同的 ...

  7. (转) 站在C#和JS的角度细谈函数式编程与闭包

    1.函数式编程是什么? 摘自百度的说法是.函数式编程是种编程典范,它将电脑运算视为函数的计算.函数编程语言最重要的基础是 λ 演算(lambda calculus).而且λ演算的函数可以接受函数当作输 ...

  8. strcpy C++实现

    #include <iostream> #include <assert.h> using namespace std; char *strcpy(char *strDest, ...

  9. 【leetcode】Partition List

    Partition List Given a linked list and a value x, partition it such that all nodes less than x come ...

  10. Win7下打开计算机管理时出现错误的解决办法

    计算机管理是用户在使用计算机时经常用到的一个选项,可以在桌面上右击计算机,选择"管理"打开.也可以在运行框中输入:compmgmt.msc打开计算机管理程序,里面有常用系统工具如用 ...