介紹
本文為個人對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