此篇是寫給新手的Demo,用於參考和借鑑,用於發散思路。老鳥能夠忽略了。javascript
本身常常有這種狀況,遇到一個新東西或難題,在瞭解和解決以前老是說「等搞定了必定要寫篇文章記錄下來」,可是當掌握了以後,就感受好簡單呀不值得寫下來了。其實這篇也同樣,決定寫下來是想在春節前最後再幹一件正經事兒!php
目錄:html
RESTFul風格響亮好久了,可是我沒用過,之後也不打算用。當系統稍微複雜時,爲了符合RESTFul要吃力地建立一些不直觀的名詞,這不是個人風格。因此此文設計的不是RESTFul風格,是最經常使用的POST和GET請求。java
請求部分就是調用API的參數,抽象出一個接口以下:node
public interface IRequest { ResultObject Validate(); }
這裏面只定義了一個Validate()方法,用於驗證請求參數的有效性,返回值是響應裏的東西,下面會講到。git
對於請求對象,傳遞到業務邏輯層,甚至是數據訪問層均可以,由於它自己就是用來傳輸數據的,俗話叫DTO(Data Transfer Object),不過定義多層傳輸對象,而後複製來複制去也是能夠的~。可是有時候業務處理會須要當前登陸人的信息,而這個信息我並不但願直接從接口層向下傳遞,因此這裏我再抽象一個UserRequestBase,用於附加登陸人相關信息:web
public abstract class UserRequestBase : IRequest { public int ApiUserID { get; set; } public string ApiUserName { get; set; } // ......能夠添加其餘要專遞的登陸用戶相關的信息 public abstract ResultObject Validate(); }
ApiUserID和ApiUserName這樣的字段是不須要客戶端傳遞的,咱們會根據登陸人信息自動填充。ajax
根據實際中的經驗,咱們每每會作分頁查詢,會用到頁碼和每頁條數,所爲咱們再定義個PageRequestBase:數據庫
public abstract class PageRequestBase : UserRequestBase { public int PageIndex { get; set; } public int PageSize { get; set; } }
由於.net只能繼承單個父類,並且有些分頁查詢可能須要用戶信息,因此咱們選擇繼承UserRequestBase。
固然,還能夠根據本身的實際狀況抽象出更多的公用類,在這不一一枚舉。
響應的設計分爲兩部分,第一個是實際響應部分,第二個會把響應包裝一下,加上code和msg,用於表示調用狀態和錯誤信息(好老的方法了,你們都懂)。
響應接口IResponse裏什麼也沒有,就是一個標記接口,不過咱們也能夠抽象出來兩個經常使用的公用類用於響應列表和分頁數據:
public class ListResponseBase<T> : IResponse { public List<T> List { get; set; } } public class PageResponseBase<T>: ListResponseBase<T> { /// <summary> /// 頁碼數 /// </summary> public int PageIndex { get; set; } /// <summary> /// 總條數 /// </summary> public long TotalCount { get; set; } /// <summary> /// 每頁條數 /// </summary> public int PageSize { get; set; } /// <summary> /// 總頁數 /// </summary> public long PageCount { get; set; } }
包裝響應的時候,有兩種狀況,第一種是操做類接口,好比添加商品,這些接口是不用響應對象的,只要返回是否成功就好了,第二種查詢類,這個時候必需要返回一些具體的東西了,因此響應包裝設計成兩個類:
public class ResultObject { /// <summary> /// 等於0表示成功 /// </summary> public int Code { get; set; } /// <summary> /// code不爲0時,返回錯誤消息 /// </summary> public string Msg { get; set; } } public class ResultObject<TResponse> : ResultObject where TResponse : IResponse { public ResultObject() { } public ResultObject(TResponse data) { Data = data; } /// <summary> /// 返回的數據 /// </summary> public TResponse Data { get; set; } }
IRequest接口的Validate()方法返回值就是第一個ResultObject,當請求參數驗證不經過的時候,確定是沒有數據返回了。
在業務邏輯層,我選擇以包裝類做爲返回類型,由於有不少錯誤都會在業務邏輯層出現,咱們的接口是須要這些錯誤信息的。
如今先後端分離大行其道,咱們作後端的一般會返回JSON格式給前端,響應的Content-Type爲application/json,前端經過一些框架能夠直接做爲js對象使用。可是前端請求後端的時候還有不少是以form表單形式,也就是請求的Content-Type爲:application/x-www-form-urlencoded,請求體爲id=23&name=loogn這樣的字符串,若是數據格式複雜了,前端很差傳,後端解析起來也麻煩。還有的直接用一個固定參數傳遞json字符串,好比json={id:23,name:'loogn'},後端用form[‘json’]取出來後再反序列化。這些方法均可以,可是不夠好,最好的方法是前端也直接傳json,幸虧如今不少web服務器都是支持請求的Content-Type爲application/json的,這個時候請求的參數會以有效負荷(Payload)的形式傳遞過去,好比用jQuery的ajax來請求:
$.ajax({ type: "POST", url: "/product/editProduct", contentType: "application/json; charset=utf-8", data: JSON.stringify({id:1,name:"name1"}), success: function (result) { console.log(result); } })
除了contentType,還要注意使用了JSON.stringify把對象轉換成了字符串。其實ajax使用的XmlHttpRequest對象只能處理字符串(json字符串呀,xml字符串呀,text純文本呀,base64呀)。這些數據到了後端以後,從請求流裏讀出來就是json形式的字符串了,可直接反序列化成後端對象。
然而這些考慮,.net mvc框架已經幫咱們作好了,這都要歸功於DefaultModelBinder。
關於Form表單形式的請求,能夠參見這位園友的文章:你從未知道如此強大的ASP.NET MVC DefaultModelBinder
我這裏想說的是,DefaultModelBinder足夠智能,並不須要咱們本身作什麼,它會根據請求的contentType的不一樣,用不一樣的方式解析請求,而後綁定到對象,遇到contentType爲application/json時,就直接反序列化獲得對象,遇到application/x-www-form-urlencoded就用form表單的形式綁定對象,惟一要注意的就是前端同窗,不要把請求的contentType和請求的實際內容搞錯就好了。你告訴我你送過來一隻貓,而其實是一隻狗,我以對待貓的方式對待狗固然就有被咬一口的危險了(確定會報錯)。
3、自定義ApiResult和ApiControllerBase
由於我不須要RESTFul風格,也不須要根據客戶端的意願返回json或xml,因此我選擇AsyncController做爲控制器的基類。AsyncController是直接繼承Controller的,並且支持異步處理,具體Controller和ApiController的區別,想了解的同窗能夠看這篇文章difference-between-apicontroller-and-controller-in-asp-net-mvc ,或者直接閱讀源碼。
Controller裏的Action須要返回一個ActionResult對象,結合上面的響應包裝對象ResultObject,我決定自定義一個ApiResult做爲Action的返回值,同時在這裏處理jsonp調用、跨域調用、序列化的小駝峯命名和時間格式問題。
/// <summary> /// api返回結果,控制jsonp、跨域、小駝峯命名和時間格式問題 /// </summary> public class ApiResult : ActionResult { /// <summary> /// 返回數據 /// </summary> public ResultObject ResultData { get; set; } /// <summary> /// 返回數據編碼,默認utf8 /// </summary> public Encoding ContentEncoding { get; set; } /// <summary> /// 是否接受Get請求,默認容許 /// </summary> public JsonRequestBehavior JsonRequestBehavior { get; set; } /// <summary> /// 是否容許跨域請求 /// </summary> public bool AllowCrossDomain { get; set; } /// <summary> /// jsonp回調參數名 /// </summary> public string JsonpCallbackName = "callback"; public ApiResult() : this(null) { } public ApiResult(ResultObject resultData) { this.ResultData = resultData; ContentEncoding = Encoding.UTF8; JsonRequestBehavior = JsonRequestBehavior.AllowGet; AllowCrossDomain = true; } public override void ExecuteResult(ControllerContext context) { var response = context.HttpContext.Response; var request = context.HttpContext.Request; response.ContentEncoding = ContentEncoding; response.ContentType = "text/plain"; if (ResultData != null) { string buffer; if ((JsonRequestBehavior == JsonRequestBehavior.DenyGet) && string.Equals(context.HttpContext.Request.HttpMethod, "GET")) { buffer = "該接口不容許Get請求"; } else { var jsonpCallback = request[JsonpCallbackName]; if (string.IsNullOrWhiteSpace(jsonpCallback)) { //若是能夠跨域,寫入響應頭 if (AllowCrossDomain) { WriteAllowAccessOrigin(context); } response.ContentType = "application/json"; buffer = JsonConvert.SerializeObject(ResultData, JsonSetting.Settings); } else { //jsonp if (AllowCrossDomain) //這個判斷可能非必須 { response.ContentType = "text/javascript"; buffer = string.Format("{0}({1});", jsonpCallback, JsonConvert.SerializeObject(ResultData, JsonSetting.Settings)); } else { buffer = "該接口不容許跨域請求"; } } } try { response.Write(buffer); } catch (Exception exp) { response.Write(exp.Message); } } else { response.Write("ApiResult.Data爲null"); } response.End(); } /// <summary> /// 寫入跨域請求頭 /// </summary> /// <param name="context"></param> private void WriteAllowAccessOrigin(ControllerContext context) { var origin = context.HttpContext.Request.Headers["Origin"]; if (true) //能夠維護一個容許跨域的域名集合,類判斷是否能夠跨域 { context.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", origin ?? "*"); } } }
裏面都是一些常規的邏輯,不作說明了,其中的JsonSetting就是設置序列化的小駝峯和日期格式的:
public class JsonSetting { public static JsonSerializerSettings Settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), DateFormatString = "yyyy-MM-dd HH:mm:ss", }; }
這個時候有個問題,若是一個時間字段須要"yyyy-MM-dd"這種格式怎麼辦呢?這個時候要定義一個JsonConverter的子類,來實現自定義日期格式:
/// <summary> /// 日期格式化器 /// </summary> public class CustomDateConverter : DateTimeConverterBase { private IsoDateTimeConverter dtConverter = new IsoDateTimeConverter { }; public CustomDateConverter(string format) { dtConverter.DateTimeFormat = format; } public CustomDateConverter() : this("yyyy-MM-dd") { } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return dtConverter.ReadJson(reader, objectType, existingValue, serializer); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { dtConverter.WriteJson(writer, value, serializer); } }
在須要的響應屬性上加上 [JsonConverter(typeof(CustomDateConverter))] 或 [JsonConverter(typeof(CustomDateConverter),"yyyy年MM月dd日")] 便可。
ApiResult定義好了,再定義一個控制器基類,目的是便於處理ApiResult:
/// <summary> /// API控制器基類 /// </summary> public class ApiControllerBase : AsyncController { public ApiResult Api<TRequest>(TRequest request, Func<TRequest, ResultObject> handle) { try { var requestBase = request as IRequest; if (requestBase != null) { //處理須要登陸用戶的請求 var userRequest = request as UserRequestBase; if (userRequest != null) { var loginUser = LoginUser.GetUser(); if (loginUser != null) { userRequest.ApiUserID = loginUser.UserID; userRequest.ApiUserName = loginUser.UserName; } } var validResult = requestBase.Validate(); if (validResult != null) { return new ApiResult(validResult); } } var result = handle(request); //處理請求 return new ApiResult(result); } catch (Exception exp) { //異常日誌: return new ApiResult { ResultData = new ResultObject { Code = 1, Msg = "系統異常:" + exp.Message } }; } } public ApiResult Api(Func<ResultObject> handle) { try { var result = handle();//處理請求 return new ApiResult(result); } catch (Exception exp) { //異常日誌 return new ApiResult { ResultData = new ResultObject { Code = 1, Msg = "系統異常:" + exp.Message } }; } } /// <summary> /// 異步api /// </summary> /// <typeparam name="TRequest"></typeparam> /// <param name="request"></param> /// <param name="handle"></param> /// <returns></returns> public Task<ApiResult> ApiAsync<TRequest, TResponse>(TRequest request, Func<TRequest, Task<TResponse>> handle) where TResponse : ResultObject { return handle(request).ContinueWith(x => { return Api(() => x.Result); }); } }
最經常使用的應該就是第一個Api<TRequest>方法,裏面處理了請求參數的驗證,把用戶信息賦給須要的請求對象,異常記錄等。第二個方法是對沒有請求參數的api調用處理。第三個方法是異步處理,能夠對異步IO處理作一些優化,好比你提供的這個接口是調用的另外一個網絡接口的狀況。
關於這個問題,我在一篇文章中貼了一些代碼,其實只要是知道怎麼回事以後,本身能夠想怎麼玩就怎麼玩了,下面講的沒有涉及角色的權限。
根據以往經驗,咱們能夠把資源(也就是一個接口)的權限分爲三個等級(標紅的第二點很重要,會大大簡化後臺權限管理的工做):
1,公開可訪問
2,登陸用戶可訪問
3,有權限的登陸用戶可訪問
因此咱們如此設計驗證的過濾器:
public class AuthFilterAttribute : ActionFilterAttribute { /// <summary> /// 匿名可訪問 /// </summary> public bool AllowAnonymous { get; set; } /// <summary> /// 登陸用戶就能夠訪問 /// </summary> public bool OnlyLogin { get; set; } /// <summary> /// 使用的資源權限名,好比多個接口可使用同一個資源的權限,默認是/ControllerName/ActionName /// </summary> public string PowerName { get; set; } public sealed override void OnActionExecuting(ActionExecutingContext filterContext) { //跨域時,客戶端會用OPTIONS請求來探測服務器 if (filterContext.HttpContext.Request.HttpMethod == "OPTIONS") { var origin = filterContext.HttpContext.Request.Headers["Origin"]; if (true) //能夠維護一個容許跨域的域名集合,類判斷是否能夠跨域 { filterContext.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", origin ?? "*"); } filterContext.Result = new EmptyResult(); return; } if (AllowAnonymous) return; var user = LoginUser.GetUser(); if (user == null) { filterContext.Result = new ApiResult { ResultData = new ResultObject { Code = -1, Msg = "未登陸" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; return; } if (OnlyLogin) return; var url = PowerName; if (string.IsNullOrEmpty(url)) { url = "/" + filterContext.ActionDescriptor.ControllerDescriptor.ControllerName + "/" + filterContext.ActionDescriptor.ActionName; } var hasPower = true; //能夠根據 user和url等信息判斷是否有權限 if (!hasPower) { filterContext.Result = new ApiResult { ResultData = new ResultObject { Code = -2, Msg = "無權限" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } } }
AllowAnonymous屬性和OnlyLogin屬性的功能已經說過了,匿名訪問就是公開的,一個系統總會須要這樣的接口,登陸可訪問通常針對安全性比較低,好比字典數據的獲取,只要登陸了,就能夠訪問,在權限管理裏也不用配置了。
PowerName的屬性是出於什麼考慮呢?有些時候,兩個接口的權限級別是綁定在一塊兒的,好比一個商品的添加和修改接口,能夠設置成同一個資源權限,因此均可以設置成/product/edit,這樣咱們在權限管理裏,只要維護/product/edit,而不須要分別維護/product/add和/product/update了(例子可能不太恰當,由於不少時候添加和修改原本就是一個接口,可是這個狀況的確存在,設置PowerName也是爲了簡化後臺的權限管理)。
對於跨域的狀況,上面代碼也有註釋,客戶端會用OPTIONS動做來探測服務器,除了上述代碼,在web.config也須要配置一下:
<system.webServer> <httpProtocol> <customHeaders> <!--<add name="Access-Control-Allow-Origin" value="*" />--> <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept,apiToken" /> </customHeaders> </httpProtocol> </system.webServer>
配置中註釋掉的一行,我故意留着,就是由於要和代碼裏有個對應的地方,在配置中只能配置爲「*」 或特定域名,咱們要更靈活,因此在程序裏控制,能夠容許一個域名集合。
LoginUser的邏輯和上面的鏈接裏的代碼差很少,再也不貼了,下載裏也有,apiToken從cookie和http頭部均可以取得,這樣無論是同域名網頁,跨域,app都是能夠調用接口的。
之前的模型生產器不少,如今使用T4模板的也很多,並且VS裏自帶T4模板。可是我不太喜歡用T4(主要是沒有智能提示)。我感受Razor引擎就挺好呀,徹底能夠用來生成模型。本身寫的一個ORM新加了兩個方法,來獲取數據庫表的元數據,目前支持MSSql和MySql,稍微寫點代碼就能夠生成模型了,下面是cshtml的內容,截圖是爲了展現代碼高亮效果,哈哈(完整代碼在最下方有下載)
因此有時候,本身動動手仍是挺好的。其實全部web語言均可以生成,jsp,php,nodejs,和動態生成頁面返回給客戶端是同樣的,這個只不過是寫到文件裏。
這裏天然說的是API文檔,和上面那個生成模型不太同樣,雖然說生成基本上都是:模板+數據=結果,可是這個生成在獲取數據的時候有點困難,先看效果圖:
api文檔自動生成的重要性想必你們都知道了,若是仍是手動寫word或excel,工做量大不說,是很難保持一致性的。
1. asp.net webapi 自帶一個Help Page 有興趣能夠了解。
2. Swagger 是個生成api的框架,很強大,也支持接口測試,可是.net下的swagger好像只能使用在webapi中,通常的mvc不行,有興趣的也能夠了解。
下面主要說一下本輪子的實現。從一個類型獲得一個該類型的對象圖,在不嚴謹的狀況下,仍是比容易實現的,主要用反射和遞歸就能夠了。
上面截圖中的C#類:
public class GetProductRequest : IRequest { /// <summary> /// 商品編號 /// </summary> public int? ProductID { get; set; } public ResultObject Validate() { if (ProductID == null || ProductID.Value <= 0) { return new ResultObject { Code = 1, Msg = "商品編號有誤" }; } return null; } } public class GetProductResponse : IResponse { /// <summary> /// 編號 /// </summary> public int? ID { get; set; } /// <summary> /// 商品名稱 /// </summary> public string Name { get; set; } /// <summary> /// 顏色集合 /// </summary> public List<string> Colors { get; set; } public List<ProductTag> TagList { get; set; } } public class ProductTag { /// <summary> /// 標籤編號 /// </summary> public int ID { get; set; } /// <summary> /// 標籤名稱 /// </summary> public string TagName { get; set; } }
轉換成JSON字符串:
{ "data": { "id": 0, "name": "str", "colors": [ "str" ], "tagList": [ { "id": 0, "tagName": "str" } ] }, "code": 0, "msg": "str" }
這樣咱們就顯示了對象的結構,可是若是加上註釋呢? 如何顯示成下面的結果呢?這也是本輪子的特點,仍是以json的格式展現中文說明。
{ "data": { "id": "編號", "name": "商品名稱", "colors": [ "顏色集合" ], "tagList": [ { "id": "標籤編號", "tagName": "標籤名稱" } ] }, "code": "等於0表示成功", "msg": "code不爲0時,返回錯誤消息" }
思考一下,一個什麼樣的對象才能被序列化成上面顯示的JSON字符串呢?
沿着這個思路,我打算在生成對象圖的時候再生成一個對象B,對象B用字典表示,並且末端的值填充成爲對象圖對應屬性的Summary。
好比 一個C#類:
public class A { /// <summary> /// 編號 /// </summary> public int ID { get; set; } /// <summary> /// 字符串列表 /// </summary> public List<string> StrList { get; set; } public List<Sub> SubList { get; set; } public class Sub { /// <summary> /// Sub名稱 /// </summary> public int SubName { get; set; } } }
在構建A的對象圖的同時會像執行以下代碼同樣構建另外一個對象B:
Dictionary<string, object> dict = new Dictionary<string, object>(); dict.Add("ID", "編號"); dict.Add("StrList", new List<string> { "字符串列表" }); var subDict = new Dictionary<string, object>(); subDict.Add("SubName", "Sub名稱"); dict.Add("SubList", new List<Dictionary<string, object>> { subDict });
ObjectGenerator的代碼以下:
public class ObjectGenerator { public static string GetSummary(PropertyInfo prop, Dictionary<string, string> summaryDict) { if (summaryDict == null || summaryDict.Count == 0) return string.Empty; var objType = prop.DeclaringType; var propName = prop.Name; var key = "P:" + objType.Namespace + "." + GetPrettyName(objType) + objType.Name + "." + propName; if (summaryDict.ContainsKey(key)) { return summaryDict[key]; } else { return ""; } } private static string GetPrettyName(Type objType, string namespaceStr = "") { if (objType.DeclaringType != null) { return GetPrettyName(objType.DeclaringType, objType.DeclaringType.Name + "." + namespaceStr); } else { return namespaceStr; } } public static Tuple<object, object> GetObjectMapDict(Type type, PropertyInfo typeProp, Dictionary<string, string> summaryDict, HashSet<string> ignoreProps = null) { if (typeProp != null) { var p = typeProp; } // if (type.IsPrimitive || type == typeof(decimal)) { var v1 = Convert.ChangeType(0, type); var v2 = v1.ToString(); if (typeProp != null) { v2 = GetSummary(typeProp, summaryDict); } return new Tuple<object, object>(v1, v2); } else if (type == typeof(string)) { var v1 = "str"; var v2 = v1.ToString(); if (typeProp != null) { v2 = GetSummary(typeProp, summaryDict); } return new Tuple<object, object>(v1, v2); } else if (type == typeof(DateTime)) { var v1 = DateTime.Now; var v2 = v1.ToString("yyyy-MM-dd HH:mm:ss"); if (typeProp != null) { v2 = GetSummary(typeProp, summaryDict); } return new Tuple<object, object>(v1, v2); } else if (type.IsArray) { var eleType = type.GetElementType(); var arr = Array.CreateInstance(eleType, 1); var list = new List<object>(); var ele_tuple = GetObjectMapDict(eleType, typeProp, summaryDict, ignoreProps); arr.SetValue(ele_tuple.Item1, 0); list.Add(ele_tuple.Item2); return new Tuple<object, object>(arr, list); } else if (type.Name.Equals("List`1")) { var list = (IList)Activator.CreateInstance(type); var list1 = new List<object>(); var eleType = type.GetGenericArguments()[0]; var ele_tuple = GetObjectMapDict(eleType, typeProp, summaryDict, ignoreProps); list.Add(ele_tuple.Item1); list1.Add(ele_tuple.Item2); return new Tuple<object, object>(list, list1); } else if (type.Name.Equals("Dictionary`2")) { var dict = (IDictionary)Activator.CreateInstance(type); var dict1 = new Dictionary<string, object>(); var keyType = type.GetGenericArguments()[0]; var valueType = type.GetGenericArguments()[1]; var key = GetObjectMapDict(keyType, null, summaryDict, ignoreProps); var value = GetObjectMapDict(valueType, null, summaryDict, ignoreProps); dict.Add(key.Item1, value.Item1); dict1.Add(key.Item2.ToString(), value.Item2); return new Tuple<object, object>(dict, dict1); } else if (type.IsClass) { var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); try { var obj = Activator.CreateInstance(type); var dict = new Dictionary<string, object>(); foreach (var prop in props) { if (ignoreProps != null && ignoreProps.Contains(prop.Name)) { continue; } var pType = DealNullable(prop.PropertyType); var val = GetObjectMapDict(pType, prop, summaryDict, ignoreProps); dict.Add(prop.Name, val.Item2); var setter = prop.GetSetMethod(true); if (setter != null) { prop.SetValue(obj, val.Item1, null); } } return new Tuple<object, object>(obj, dict); } catch (Exception e) { return null; } } else { try { var obj = Activator.CreateInstance(type); return new Tuple<object, object>(obj, obj); } catch (Exception e) { return null; } } } private static Type DealNullable(Type type) { if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { return type.GetGenericArguments()[0]; } return type; } }
這段代碼是很不完善的,可是目前夠用了,不夠用能夠再改嘛,javascript數據類型原本也很少,接口定義固然也是越簡單越好了。可巧的是webapi的 help page裏也有個同名同功的ObjectGenerator,它的實現是比較完善的,可是隻返回了對象圖,我開始還打算要在它上面按照個人思路修改一下呢,嘗試以後就做罷了,改動太多了,並且對我來講,上面代碼夠用了。
上面的summaryDict能夠從外部讀取註釋文件獲取,要讀取哪些項目的註釋都須要設置一下:
讀取的代碼也很簡單,由於我只關注屬性的註釋,因此我只讀取屬性的:
Dictionary<string, string> getSummaryDict() { var path = Server.MapPath("~/") + "bin\\"; var files = Directory.GetFiles(path, "*.xml"); Dictionary<string, string> msDict = new Dictionary<string, string>(); foreach (var file in files) { XmlDocument xmldoc = new XmlDocument(); xmldoc.Load(file); var memberNodes = xmldoc.SelectNodes("/doc/members/member"); foreach (XmlNode item in memberNodes) { var name = item.Attributes["name"].Value; if (name.StartsWith("P:")) //只取屬性 { var summaryNode = item.SelectSingleNode("summary"); if (summaryNode != null) { msDict[name] = summaryNode.InnerText.Trim(); } } } } return msDict; }
Demo並不完整,沒有真正讀取數據庫,有興趣的同窗能夠下載下來玩玩。(因爲上傳大小有限,我把packages文件夾刪除了)