重温ASP.NET WebAPI(二)进阶
重温ASP.NET WebAPI(二)进阶
介绍
本文为个人对WebApi的回顾无参考价值。
本文内容:
- Rest和UnitOfWork
- 创建WebAPi的流程
- IOC-Unity的使用
- MEF
- 自定义URL
- Base认证和Token自定义权限
- 日志NLog
- OData的使用
- Owin自宿主的使用
代码地址:https://github.com/OtherRuan/Review-Serials
WebApi的几点特性
WebApi 提供了几点特性:
1. 自动匹配HTTP方法
GetMethod(), 惯例上会直接匹配Get的http方法。
Web Api允许方法同时拥有多种type,比如:
[AcceptVerbs(“Delete”,”Get”)]
Public HttpResponseMessage MultiMethod();
MultiMethod可以同时为Delete、Get两种类型的http请求使用
2. Web Api还提供自定义Route的功能,比如定义自己的参数,如下
[Route(“data/{param1}/{param2}”)]
Public object GetData(string param1, string param2){}
请求地址:http://localhost/data/1/2
Route属性有以下5种,用于帮你重定义你的API路由
a) ActionName: 定义你自己的路由的Action名称
b) Http Method(httpGet,HttpPost,AcceptVerbs…): 定义你的HTTP方法类型和版本等相关信息
c) NonAction: 预防当前的Action没有被调用
d) Route: 自定义路由,可设置参数
e) RoutePrefix:在controller上定义前缀,Controller下的所有Action路由都会自动带上此前缀
3. HttpClient, HttpRequestMessage, HttpResponseMessage
Https下的Web Api
Https/SSL 是管理计算机互联网消息资源传输安全的协议。先介绍一下两者
Certificate :也就是电子证书,是一种电子签名,它通过绑定公钥来确认用户、设备、服务,以及对应的私钥
Certificate Authority(CA):主要用途是指定哪些URL是可信任的URL
Repository和Unit of Work
Repository模式好处:
- 集中了数据和web服务访问逻辑
- 支持单元测试
- 提供了灵活架构以适应整个应用设计的扩展
Unit of Work的职责:
- 管理事务
- 有序化数据的插入、删除、更新操作
- 预防重复的更新。
使用Unit of Work模式的好处是能够让你更关注于业务逻辑。
创建WebAPI项目流程
- 创建DAL
- 创建Repository和Unit of Work
- 创建Entities
- 创建Services
- 创建WebAPI
- 创建IOC
- 使用MEF解耦依赖注册关系
- 自定义路由
- 创建Exception处理和日志功能
- 创建单元测试
Questions:
- Context.Entry(entity).State = EntityState.Modified;
- IQueryable
- UnitOfWork继承IDisposable
Cause:他需要释放链接
- DbEntityValidationException e.EntityValidationErrors
- GC.SuppressFinalize
IOC – Unity
Unity 是轻量级、可扩展的依赖注入容器,支持构造函数注入、属性注入和方法调用注入。
Unity的优势:
- 提供简单的对象创建,特别是分层对象结构和依赖。
- 提供抽象需求。允许开发人员在运行时或者配置中指定依赖。
- 增加灵活性,推迟组件配置到容器中
- 它有一个本地服务能力。允许用户保存或缓存容器。这个特别适用于Asp.Net web应用程式中,持久化Session和application容器
创建流程:
- 安装Unity for MVC
- 创建Bootstrapper.cs文件
- Initialise()
注册对应反转依赖
- Application_Start()
Bootstrapper.Initialise();
2. 构造函数注入
private IEmployeeService _employeeService; public UserController(IEmployeeService employeeService) { _employeeService = employeeService; }
MEF(Managed Extensibility Framework)
虽然之前IOC以前减少了部分依赖,但是Domain Model依然依赖在API中。
轻耦合架构需要做到以下几点:
- Domain Model:只跟Service层关联
- Services:只跟REST终端和Domain Model关联
- REST API,也就是Controller,通过IOC,跟Services暴露的接口关联
为了解决API依赖Domain Model,我们采用MEF进行解耦。
MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。(摘自MSDN)
流程:
- 删除原有容器的注册配置
container.RegisterType<IEmployeeService, EmployeeBusinessLayer>() .RegisterType<UnitOfWork>(new HierarchicalLifetimeManager());
2. 创建Resolver类库
a. 添加Unity.MVC
b. 添加引用:System.ComponentModel.Composition
这个DLL是MEF的一部分,提供MEF的核心类
c. 添加接口IComponent
包含方法initialization方法Setup, 组合IRegisterComponent
d. 添加接口IRegisterComponent
定义RegisterType等容器的方法
e. 添加ComponentLoder加载类。
包含LoadContainer,加载指定path路径的dll里的所有带有Export属性并继承IComponent的modules,执行他们的setup方法也就是注册依赖关系。
var dirCat = new DirectoryCatalog(path, pattern); var importDef = BuildImportDefinition(); try { using (var aggregateCatalog = new AggregateCatalog()) { aggregateCatalog.Catalogs.Add(dirCat); using (var componsitionContainer = new CompositionContainer(aggregateCatalog)) { var exports = componsitionContainer.GetExports(importDef); var modules = exports.Select(export => export.Value as IComponent).Where(m => m != null); var registerComponent = new RegisterComponent(container); foreach (var module in modules) { module.SetUp(registerComponent); } } }
f. 每个需要做IOC的类库下,添加类DependencyResolver,继承IComponent,实现SetUp方法,方法内部实现RegisterType功能,达到依赖注册的作用。添加属性Export
[Export(typeof(IComponent))] public class DependencyResolver : IComponent { public void SetUp(IRegisterComponent registerComponent) { registerComponent.RegisterType<IUnitOfWork, UnitOfWork>(); } }
3. 修改原来配置为如下:
ComponentLoader.LoadContainer(container, ".\\bin", "Services.dll");
使用MEF的好处:
- 使应用程序更解耦和可扩展。扩展的时候,只需要以同样的方法添加新的DependencyResolver类,没有依赖。
- 依赖注册通过反射自动生成。只需要指定对应的dll的位置,如在bin下。
- 数据库事务或者一些模块不想暴露到服务终端时,MEF就变得更安全且不破坏当前设计结构。
使用AttributeRouting重写自定义URL
流程:
- 在WebConfig的Register方法中,替换为MapHttpAttributeRoute();如下:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { //config.Routes.MapHttpRoute( // name: "DefaultApi", // routeTemplate: "api/{controller}/{id}", // defaults: new { id = RouteParameter.Optional } //); config.MapHttpAttributeRoutes(); } }
2. Global.ascx中,替换原来的注册
protected void Application_Start() { //WebApiConfig.Register(GlobalConfiguration.Configuration); GlobalConfiguration.Configure(WebApiConfig.Register); }
3. 几种Route自定义方式
a. Controller上设置前缀
[RoutePrefix("users/user")] [RoutePrefix("v1/users/user")]
b. Action上自定义路由
[Route("u/{id?}")] [Route("u/{id:range(1,3)}")] [Route("u/id/{e:regex(^[0-9]$)}")] [Route("~/myroute/users")]
使用ActionFilter创建基于WebApi认证安全和基于Token的自定义权限
企业级应用的安全尤为重要,特别是通过服务暴露我们的业务数据。先介绍一下内容:
- Authentication认证
Authentication认证用于确认终端用户,验证用户是否有权限访问系统。等会通过Basic Authentication技术来理解如何在webapi中实现authentication功能。
- Authorization授权
Authorization授权可以理解为做完Authentication认证后的第二部实现安全机制。并不是所有可以访问系统的用户都能访问所有模块比如action。Authorization通过设置角色和许可给终端用户,或者通过提供安全的token,来指定用户是否能够访问具体系统资源。
- 持久化Session
RESTful服务工作在无状态的协议,比如HTTP。我们可以通过基于token授权技术来实现持久化Session的功能。一个授权过的用户,允许你在一定时间内访问资源,且能通过延长session的有效时间来重新实例化请求访问资源。使用WebAPI的站点可以通过Basic Authentication和Token Base authorization来持久化session.
Basic Authentication
Basic认证是一种机制。当用户请求服务时,会将用户名密码等嵌套在请求头。服务接收请求后验证证书是否有效,然后返回响应结果。无效证书对应的响应结果是401,代表无权限访问。
优点:容易实现,支持所有浏览器,并且成为RESTful的标准认证。
缺点:用户证书包含在请求头,很容易受到攻击。没有持久化Session,一旦用户登录且多次发送过证书给服务的时候,就不能退出。而且非常容易受到攻击,如CSRF
基于Token授权
Token一般为加密后的key,只有服务器或者服务知道它的含义。当用户发送请求并传递token的时候,服务器通过token判断用户是否有权限访问系统。生成后的Token可以被存到数据库或者配置文件中。Token有自己的生命周期,有失效时间。
WebAPI使用Basic认证和Token授权的流程:
- 创建用户表
- 创建Service和Repository
- IOC
- 创建AuthorizationFilterAttribute
- 实现OnAuthrozation
i. 获取用户相关信息GenericIdentity
filterContext.Request.Headers.Authorization. Scheme==”Basic” Parameter.split(“:”) [0] username, [1]password
ii. 调用Service获取数据库用户信息,进行验证
5. 通过添加属性过滤,或者在global添加全局过滤
[ApiAuthenticationFilter] GlobalConfiguration.Configuration.Filters.Add(new ApiAuthenticationFilter());
设计缺陷
每一次请求都发送用户密码。假设我创建一个应用,这个应用的认证只在我登录的时候发生一次。这时我也有权限访问其他服务。我们的应用应该更安全,他要能约束及时认证过的用户,不能访问没有授权给他的服务。
通过Token来实现授权。只暴露比如登录的服务给用户。当用户登录成功后,发送一个token(可以是GUID或者加密的key)给用户,每一次请求时都要带上这个token。在持久化session方面,token有失效时间,一般为15分钟,可以在web.config做个入口配置。在session过期后,用户登出,重新登录获取新的token。
使用Action Filter, Exception Filter实现WebAPI的日志容错功能
NLog的使用
- Nuget下载NLog
- 配置NLog
- ConfigSection的配置
2. NLog节点的配置
3. Web项目中添加APILog文件夹
4. 添加Helpers文件夹,添加以下几个文件
- NLogger类
继承ITraceWriter的Trace方法,主要用于记录所有类型的错误和信息的日志。
5. 添加LoggingFilterAttribute继承Action Filter
将NLogger类引入
6. 注册LoggingFilterAttribute到Global
错误日志
- 添加GlobalExceptionAttribute类,继承ExceptionFilterAttribute,实现OnException方法
将NLogger注入
2. 其余操作如日志一样
自定义错误日志
可以将错误分为三大类:API级别的错误、业务错误、数据错误
- 添加接口IApiException
包含属性Error的基本信息属性
2. 各添加三个类ApiException、ApiBusinessException、ApiDataException,继承IApiException和Exception
3. 引用JSon序列化
Log需要能够序列化成json以便我们能够将日志对象传输到各个模块中去。
4. 修改之前的NLogger类
5. 修改GlobalExceptionAttribute
6. 在Controller中,抛出我们自定义的错误类
WebAPI中的OData
OData是一种协议,它提供灵活创建可查询的REST服务,准确的说,它提供各种查询选项,如参数,来决定你具体要查询的数据。如下面链接
http://localhost/Products?$orderby=Name
OData允许你创建可查询的服务,当终端服务支持OData时,你可以过滤请求结果,比如提取前n条数据,排序,选取某条数据等等。
查询选项
ASP.NET WebAPI支持以下几种:
- $orderby:排序
- $select:选择某列或者某个属性
- $skip:与Linq类似,跳过前N条数据,提取N+1后数据
- $top:前N条
- $expand:扩展实体
- $filter:过滤
- $inlinecount:类似分页
使用流程
- nuget安装OData
- 在Api里的结果集中引用方法AsQueryable(),设置成可查询
_employeeService.GetAll().AsQueryable()
3. 各种查询选项的使用
a. $top
/Employee/All?$top=2
b. $filter
/Employee/All?$ filter =name eq ‘Ryan ’ 获取name等于Ryan的数据
/Employee/All?$ filter =id lt 3 获取id小于3的数据
c. $orderby
/Employee/All?$ orderby =name desc
d. $orderby和 $top一起用
/Employee/All?$top=2&orderby = name desc
e. $skip
/Employee/All?$top=5$skip=2 取第3条开始的前5条数据,3-7
Filter的操作符
eq |
等于 |
$filter=revenue eq 100 |
ne |
不等于 |
$filter=revenue ne 100 |
gt |
大于 |
$filter=revenue gt 100 |
ge |
大于等于 |
$filter=revenue ge 100 |
lt |
小于 |
$filter=revenue lt 100 |
le |
小于等于 |
$filter=revenue le 100 |
and |
且 |
$filter=revenue lt 100 and revenue gt 2000 |
Or |
或 |
$filter=contains(name,'(sample)') or contains(name,'test') |
Not |
不包含 |
$filter=not contains(name,'sample') |
( ) |
括号域 |
(contains(name,'sample') or contains(name,'test')) and revenue gt 5000 |
查询函数
Contains |
$filter=contains(name,'(sample)') |
endswith |
$filter=endswith(name,'Inc.') |
startswith |
$filter=startswith(name,'a') |
f. 分页
添加属性[Queryable(PageSize = 10)]
g. 设置可允许的选项
[Queryable(AllowedQueryOptions =AllowedQueryOptions.Filter | AllowedQueryOptions.OrderBy)]
h. 允许具体的排序范围
[Queryable(AllowedOrderByProperties = "ProductId")]
i. 允许操作符范围
[Queryable(AllowedLogicalOperators = AllowedLogicalOperators.GreaterThan)]
j. 允许计算操作符的范围
[Queryable(AllowedArithmeticOperators = AllowedArithmeticOperators.Add)]
创建自宿主的WebAPI-OWIN
- 添加console应用
- Nuget安装webapi owin self-host
- 添加api
- 添加Startup类
使用httpConfiguration创建路由,通过appBuilder.UseWebApi方法添加到请求管道中
5.Main函数中使用Api项目里的Startup类
6. 运行console
参考文献
http://www.codeproject.com/Articles/659131/Understanding-and-Implementing-ASPNET-WebAPI
http://www.codeproject.com/Articles/838274/Web-API-Thoughts-of-Data-Streaming
http://www.codeproject.com/Articles/990492/RESTful-Day-sharp-Enterprise-Level-Application#_Toc418969124
http://www.codeproject.com/Articles/889242/WebAPI-Self-Hosting-Using-OWIN
http://www.codeproject.com/Articles/631668/Learning-MVC-Part-Repository-Pattern-in-MVC-App
http://www.codeproject.com/Articles/640294/Learning-MVC-Part-Generic-Repository-Pattern-in