知道的朋友瞭解 我不是屬於講按部就班技術的那種人。什麼xx入門 ,入門到精通,入門到入土。 其實非要嚴格說的話已經跟angularjs 什麼ajax 偏的有點遠了,之所以還是叫這個名稱,因為都屬於web應用 ,叫這個名稱是一種延續,其實這個系列持續了幾年了 是我自己從學習到一種適合我自己環境的特有應 ...
知道的朋友瞭解 我不是屬於講按部就班技術的那種人。什麼xx入門 ,入門到精通,入門到入土。 其實非要嚴格說的話已經跟angularjs 什麼ajax 偏的有點遠了,之所以還是叫這個名稱,因為都屬於web應用 ,叫這個名稱是一種延續,其實這個系列持續了幾年了 是我自己從學習到一種適合我自己環境的特有應用方式的一種總結。主題還是一個:web應用,往細了裝逼了說一種同時適合web 和winform 客戶端 獨到的 數據架構 處理方式。當然所有的都是基於以前的基礎之上的。
主題:一種同時適合web 和winform 客戶端 獨特的 數據架構 處理方式
後臺API許可權控制
首先是後臺的介面 ,使用webapi的方式 返回 json 數據 。當然這裡有一個技巧 , 也就是許可權控制。眾所周知 http 有一種 方式 可以把授權放在header 里。後臺驗證 ,每個介面都要許可權符合才能 請求到數據。都知道asp.net MVC有filter 可以用來先進行過濾 ,都在Java做web後臺滿大街 的年代 我們還在用中古時期的ASP.Net MVC。首先我們對後臺代碼和web部分進行了分層,數據訪問對象為Entity ,controllers 為各個請求的API web的和winform的在一起,我們依舊使用了簡單的三層架構,xxxLogic.cs 其實是實際的業務邏輯代碼:
所有的是基於WebAPI形式的 老套路在初始化時進行 router註冊 以便讓請求映射到對應的controller 不用多說了,還有是asp.net MVC是可以配置返回數據格式為xml 或者json的。
1 public class Global : System.Web.HttpApplication 2 { 3 4 protected void Application_Start(object sender, EventArgs e) 5 { 6 7 AreaRegistration.RegisterAllAreas(); 8 //GlobalConfiguration.Configuration.ParameterBindingRules. 9 // Insert(0,SimplePostVariableParameterBinding.HookupParameterBinding); 10 WebApiConfig.Register(GlobalConfiguration.Configuration); 11 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 12 RouteConfig.RegisterRoutes(RouteTable.Routes); 13 } 14 15 protected void Session_Start(object sender, EventArgs e) 16 { 17 18 } 19 protected void Application_BeginRequest(object sender, EventArgs e) 20 { 21 if (Context.Request.FilePath == "/") Context.RewritePath("Default.aspx"); 22 } 23 24 25 26 public override void Init() 27 { 28 PostAuthenticateRequest += WebApiApplication_PostAuthenticateRequest; 29 30 base.Init(); 31 } 32 void WebApiApplication_PostAuthenticateRequest(object sender, EventArgs e) 33 { 34 HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required); 35 } 36 }
1 public class RouteConfig 2 { 3 public static void RegisterRoutes(RouteCollection routes) 4 { 5 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 6 7 routes.MapRoute( 8 name: "Default", 9 url: "{controller}/{action}/{id}", 10 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 11 ); 12 } 13 }
1 public static class WebApiConfig 2 { 3 public static void Register(HttpConfiguration config) 4 { 5 System.Web.Mvc.ValueProviderFactories.Factories.Add(new System.Web.Mvc.JsonValueProviderFactory()); 6 //下麵這句務必加上 否則就只能在IE下才能得到正確的json數據 7 //必須要添加http.formating 那個dll的引用 8 GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear(); 9 //GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear(); 10 config.Routes.MapHttpRoute( 11 name: "DefaultApi", 12 routeTemplate: "api/{controller}/{action}/{id}", 13 defaults: new { controller = "Home", action = "Index", id = RouteParameter.Optional }//new { id = RouteParameter.Optional } 14 ); 15 16 } 17 }
來看下我們的後臺進行許可權控制的代碼,我們並未進行複雜的許可權控制 僅僅只是判斷登錄:
1 public class Power : System.Web.Http.Filters.ActionFilterAttribute 2 { 3 public Power(string actioinName) 4 { 5 6 } 7 public override void OnActionExecuting(HttpActionContext actionContext) 8 { 9 HttpRequest request = HttpContext.Current.Request; 10 string username = request.Headers["username"]; 11 string password = request.Headers["password"]; 12 13 14 LicWebUtil util = new LicWebUtil(); 15 var loginuser = util.loginNormal(username, password); 16 if (loginuser==null) 17 { 18 throw new Exception("未登錄啊"); 19 } 20 } 21 }
數據處理
然後是數據處理,我們在服務端構建了統一的數據格式APIResult:
1 public class APIResult 2 { 3 public bool Success { get; set; } 4 public object Data { get; set; } 5 6 public string Msg { get; set; } 7 8 }
web端的 通用 js請求 ,get和post 註意promise 應用的機巧 ,以及提交時在登錄成功了的情況下 會自動往header裡加許可權 。
1 var app = angular.module("scpSite", ["ui.router"]); 2 3 //註入一個util 工具類 4 app.service("Util", function ($http, $rootScope, $q, $compile) { 5 6 var boxPromise = function (promise) { 7 var myPromise = { 8 prom: promise, 9 then: function (fun1, fun2) { 10 promise.then(function (response) { 11 //response.status==200// response.status==500 這裡就不使用這種方式判斷了 12 if (response && response.data && response.data.Success) { 13 fun1(response); 14 } 15 else { 16 //服務端有正常的錯誤消息返回 17 if (response && response.data && response.data.Success == false) { 18 alert("獲取數據失敗:" + response.data.Message); 19 } 20 //throw exception 500錯誤 21 else { 22 alert("獲取數據失敗:" + response.data.ExceptionMessage); 23 } 24 25 if (fun2 != undefined) 26 fun2(response); 27 } 28 }, function () {//在剛httpget的時候可以 而在此處根本就不會執行此函數 29 }); 30 } 31 }; 32 return myPromise; 33 }; 34 35 var _this = { 36 get: function (path, _params) { 37 var promise; 38 if (_params != undefined && _params != null) { 39 promise = $http.get(path, { headers: { username: $rootScope.username, password: $rootScope.password }, params: _params }).then(function (response) { 40 return response; 41 }, function (response) { 42 return response; 43 }); 44 } else { 45 promise = $http.get(path, { headers: { username: $rootScope.username, password: $rootScope.password } }).then(function (response) { 46 return response; 47 }, function (response) { 48 return response; 49 }); 50 } 51 return boxPromise(promise); 52 }, 53 post: function (url, data) { 54 var promise; 55 if (data != undefined && data != null) { 56 promise = $http.post(url,data , { headers: { username: $rootScope.username, password: $rootScope.password }}).then(function (response) { 57 return response; 58 }, function (response) { 59 return response; 60 }); 61 } else { 62 promise = $http.post(url, { headers: { username: $rootScope.username, password: $rootScope.password } }).then(function (response) { 63 return response; 64 }, function (response) { 65 return response; 66 }); 67 } 68 return boxPromise(promise); 69 } 70 71 } 72 73 return _this; 74 75 });
然後是登錄 ,登錄成功了 angularjs 全局變數就會有用戶信息 ,提交時post就會自動往許可權裡加。結合上面的一起運作 ,體會一下這樣設計 以及聯動運作的精妙之處,並且如果服務端有錯誤 在post之初就會自動報出錯誤 並且利用promise的機制 自動阻斷 不讓執行進程傳到下層 不讓邏輯往後走,如果數據成功 則會自動調用下層promise的數據展現操作進行頁面渲染,這樣來達到規範化:
1 app.controller("MainController", function ($rootScope, $scope, $rootScope, $stateParams, $state, Util) { 2 3 $rootScope.loginok = false; 4 5 $scope.username = ""; 6 $scope.password = ""; 7 8 $rootScope.username = ""; 9 $rootScope.password = ""; 10 11 //登錄測試 否則彈出登錄對話框 12 $scope.loginTest = function () { 13 14 if ($scope.loginok == false) { 15 $('#myModal').modal({ backdrop: 'static', keyboard: false, show: true }); 16 } 17 } 18 19 $scope.login = function () { 20 21 Util.get("/api/Util/login", { username: $scope.username, password: $scope.password }).then(function (res) { 22 if (res.data.Data==null||res.data.Data == "") { 23 alert("登錄失敗"); 24 } 25 else { 26 alert("登錄成功"); 27 $rootScope.username = $scope.username; 28 $rootScope.password = $scope.password; 29 $rootScope.loginok = true; 30 31 32 $('#myModal').modal('hide'); 33 } 34 }, function (res) { 35 alert("登錄遇到錯誤"); 36 }); 37 38 39 } 40 41 $scope.copyrightEndYear = "2018"; 42 $scope.GetCopyrightEndYear = function () { 43 Util.get("/api/Util/GetCopyrightEndYear").then(function (res) { 44 $scope.copyrightEndYear = res.data.Data; 45 }); 46 } 47 $scope.GetCopyrightEndYear(); 48 });
如果登錄成功後那麼後續就是簡單的平鋪直述的調用了,下麵是一個簡單的示例。
1 app.controller("DefaultController", function ($scope, Util) { 2 $scope.newsList = []; 3 $scope.inititalData = function () { 4 Util.get("/api/CMS/GetContentByCategory", { category: "新聞中心", top: 5, getContent: false }).then(function (res) { 5 $scope.newsList = res.data.Data; 6 }); 7 } 8 $scope.inititalData(); 9 });
看下我們的使用效果
不用想當然的在客戶端調試把mask去掉以為就可用了哈,我們是做了完善的後臺校驗機制的。
同一介面應用於winform端
看,跟上面相結合的統一處理 WebAPI promise post 的結合應用正是這個框架的優雅之處 ,並且 APIResult格式 是統一的 在winform客戶端只要實現一個json解析 ,同一個介面或者業務邏輯就可應用於Windows客戶端了 就這樣簡單的就達到了同步。關於winform 介面 結果數據的處理,很簡單 我們構建一個跟json一致c#的類 反序列化即可。這是winform 構造出的請求 和其他代碼,註意我們是用上面同一規格APIResult 來進行json解析的,通過httprequest 請求 ,原先設計的我們有廣告 也就是圖片 還有文本。那麼應該怎麼處理呢? 不是有泛型嗎? 看我的:註意泛型的應用 ,如果是img則轉而使用stream解析,普通的則使用APIResult 解析出文本。
1 public static T DownloadSomething<T>(string url,string appendUrl,Dictionary<string,string> pars) 2 { 3 try 4 { 5 if (string.IsNullOrEmpty(appendUrl) == false) 6 { 7 url += appendUrl; 8 } 9 10 if (pars != null) 11 { 12 var enumer = pars.GetEnumerator(); 13 int parIndex = 0; 14 while (enumer.MoveNext()) 15 { 16 if (parIndex == 0) 17 { 18 url += string.Format("?{0}={1}", enumer.Current.Key, enumer.Current.Value); 19 } 20 else 21 { 22 url += string.Format("&{0}={1}", enumer.Current.Key, enumer.Current.Value); 23 } 24 parIndex++; 25 } 26 } 27 28 HttpWebRequest rq = (HttpWebRequest)WebRequest.Create(url); 29 rq.Headers["Accept-Encoding"] = "utf-8"; 30 31 if (WriterRuntime.loginUser != null && string.IsNullOrEmpty(WriterRuntime.loginUser.UserNameOrHardwareID) == false) 32 { 33 rq.Headers["username"] = WriterRuntime.loginUser.UserNameOrHardwareID; 34 rq.Headers["password"] = WriterRuntime.loginUser.Password; 35 } 36 rq.Method = "GET"; 37 rq.Timeout = 10000;//2秒超時 38 T retData = default(T); 39 40 HttpWebResponse rc = (HttpWebResponse)rq.GetResponse(); 41 Stream stream = rc.GetResponseStream(); 42 StreamReader sr = new StreamReader(stream, Encoding.UTF8); 43 44 45 if (typeof(T) == typeof(Image)) 46 { 47 retData = (T)(object)Image.FromStream(stream); 48 } 49 else 50 { 51 APIResult apiResult = JsonConvert.DeserializeObject<APIResult>(sr.ReadToEnd()); 52 53 if (apiResult.Data != null) 54 { 55 if ((typeof(T) == typeof(string))) 56 { 57 retData = (T)(object)apiResult.Data; 58 } 59 else if ((typeof(T) == typeof(Int64))) 60 { 61 retData = (T)(object)apiResult.Data; 62 } 63 else if((typeof(T) == typeof(bool))){ 64 retData = (T)(object)apiResult.Data; 65 } 66 else 67 { 68 retData = ((Newtonsoft.Json.Linq.JObject)apiResult.Data).ToObject<T>(); 69 } 70 71 } 72 } 73 74 sr.Close(); 75 rc.Close(); 76 77 if (typeof(T) == typeof(string)) 78 { 79 return retData; 80 } 81 else if (typeof(T) == typeof(Image)) 82 { 83 return retData; 84 } 85 else 86 { 87 return retData; 88 } 89 90 } 91 catch (Exception ex) 92 { 93 MessageBox.Show("與伺服器連接失敗"); 94 Application.Exit(); 95 } 96 return default(T); 97 }
還有,我們的客戶端是支持升級的,固定的連接上伺服器後先進性API版本校驗 ,如果校驗失敗 會強迫客戶端進行版本更新。強不強迫客戶端更新的決定權在我們 只要把服務端的ver介面數字調高 就可以把低於某版本的客戶端淘汰掉了。看資本的力量如此強大 ,這不就像手機上的某某xxAPP嗎 其實啥實質功能都沒更新 ,更新了一堆的廣告。
1 private void LoginForm_Load(object sender, EventArgs e) 2 { 3 4 //先進行聯網和版本檢測 失敗則退出 5 Int64 serverVer = USBKeyWriterUtil.DownloadSomething<Int64>(WriterRuntime.apiUrl, "Util/ClientAPI_Ver", null); 6 if (serverVer > WriterRuntime.ver) 7 { 8 USBKeyWriter.UI.Upgrade uDlg = new USBKeyWriter.UI.Upgrade(); 9 uDlg.ShowDialog(this); 10 return; 11 } 12 Process[] app = Process.GetProcessesByName("NewScp"); 13 if (app.Length > 0) 14 { 15 MessageBox.Show("請先退出Dicom列印服務軟體,再運行此授權機程式。");//請先退出Dicom列印服務軟體(任務管理器NewScp進程)再運行此授權機程式 16 Application.Exit(); 17 return; 18 } 19 20 //初始化runtime 21 rt = WriterRuntime.GetInstance(); 22 rt.Initial(); 23 24 string deviceUserName = USBKeyWriterUtil.getDefaultLoginId(); 25 if (string.IsNullOrEmpty(deviceUserName)) 26 { 27 tbxuname.Text = "admin"; 28 } 29 else 30 { 31 tbxuname.Text = deviceUserName; 32 } 33 }
好了 全部 結束 ,感謝各位看官觀賞,周末愉快。