重温ASP.NET WebAPI(二)进阶

介绍

本文为个人对WebApi的回顾无参考价值。

本文内容:

  1. Rest和UnitOfWork
  2. 创建WebAPi的流程
  3. IOC-Unity的使用
  4. MEF
  5. 自定义URL
  6. Base认证和Token自定义权限
  7. 日志NLog
  8. OData的使用
  9. 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模式好处:

  1. 集中了数据和web服务访问逻辑
  2. 支持单元测试
  3. 提供了灵活架构以适应整个应用设计的扩展

Unit of Work的职责:

  1. 管理事务
  2. 有序化数据的插入、删除、更新操作
  3. 预防重复的更新。

使用Unit of Work模式的好处是能够让你更关注于业务逻辑。

创建WebAPI项目流程

  1. 创建DAL
  2. 创建Repository和Unit of Work
  3. 创建Entities
  4. 创建Services
  5. 创建WebAPI
  6. 创建IOC
  7. 使用MEF解耦依赖注册关系
  8. 自定义路由
  9. 创建Exception处理和日志功能
  10. 创建单元测试

Questions:

  1. Context.Entry(entity).State = EntityState.Modified;
  2. IQueryable
  3. UnitOfWork继承IDisposable

Cause:他需要释放链接

  1. DbEntityValidationException e.EntityValidationErrors
  2. GC.SuppressFinalize

IOC – Unity

Unity 是轻量级、可扩展的依赖注入容器,支持构造函数注入、属性注入和方法调用注入。

Unity的优势:

  1. 提供简单的对象创建,特别是分层对象结构和依赖。
  2. 提供抽象需求。允许开发人员在运行时或者配置中指定依赖。
  3. 增加灵活性,推迟组件配置到容器中
  4. 它有一个本地服务能力。允许用户保存或缓存容器。这个特别适用于Asp.Net web应用程式中,持久化Session和application容器

创建流程:

  1. 安装Unity for MVC
  2. 创建Bootstrapper.cs文件
    1. Initialise()

注册对应反转依赖

  1. Application_Start()

Bootstrapper.Initialise();

  2. 构造函数注入

private IEmployeeService _employeeService;

        public UserController(IEmployeeService employeeService)

        {

            _employeeService = employeeService;

        }

MEF(Managed Extensibility Framework)

虽然之前IOC以前减少了部分依赖,但是Domain Model依然依赖在API中。

轻耦合架构需要做到以下几点:

  1. Domain Model:只跟Service层关联
  2. Services:只跟REST终端和Domain Model关联
  3. REST API,也就是Controller,通过IOC,跟Services暴露的接口关联

为了解决API依赖Domain Model,我们采用MEF进行解耦。

MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。(摘自MSDN)

流程:

  1. 删除原有容器的注册配置
    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的好处:

  1. 使应用程序更解耦和可扩展。扩展的时候,只需要以同样的方法添加新的DependencyResolver类,没有依赖。
  2. 依赖注册通过反射自动生成。只需要指定对应的dll的位置,如在bin下。
  3. 数据库事务或者一些模块不想暴露到服务终端时,MEF就变得更安全且不破坏当前设计结构。

使用AttributeRouting重写自定义URL

流程:

  1. 在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的自定义权限

企业级应用的安全尤为重要,特别是通过服务暴露我们的业务数据。先介绍一下内容:

  1. Authentication认证

Authentication认证用于确认终端用户,验证用户是否有权限访问系统。等会通过Basic Authentication技术来理解如何在webapi中实现authentication功能。

  1. Authorization授权

Authorization授权可以理解为做完Authentication认证后的第二部实现安全机制。并不是所有可以访问系统的用户都能访问所有模块比如action。Authorization通过设置角色和许可给终端用户,或者通过提供安全的token,来指定用户是否能够访问具体系统资源。

  1. 持久化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授权的流程:

  1. 创建用户表
  2. 创建Service和Repository
  3. IOC
  4. 创建AuthorizationFilterAttribute
    1. 实现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的使用

  1. Nuget下载NLog
  2. 配置NLog
    1. ConfigSection的配置

      2. NLog节点的配置

  3. Web项目中添加APILog文件夹

  4. 添加Helpers文件夹,添加以下几个文件

    1. NLogger类

      继承ITraceWriter的Trace方法,主要用于记录所有类型的错误和信息的日志。

  5. 添加LoggingFilterAttribute继承Action Filter

    将NLogger类引入

  6. 注册LoggingFilterAttribute到Global

错误日志

  1. 添加GlobalExceptionAttribute类,继承ExceptionFilterAttribute,实现OnException方法

将NLogger注入

  2. 其余操作如日志一样

自定义错误日志

可以将错误分为三大类:API级别的错误、业务错误、数据错误

  1. 添加接口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支持以下几种:

  1. $orderby:排序
  2. $select:选择某列或者某个属性
  3. $skip:与Linq类似,跳过前N条数据,提取N+1后数据
  4. $top:前N条
  5. $expand:扩展实体
  6. $filter:过滤
  7. $inlinecount:类似分页

使用流程

  1. nuget安装OData
  2. 在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

  1. 添加console应用
  2. Nuget安装webapi owin self-host
  3. 添加api
  4. 添加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

posted @ 2016-05-03 15:22  RyanRuan  阅读(12509)  评论(2编辑  收藏  举报
View Code