angularjs和ajax的結(jié)合使用 (四)
知道的朋友了解 我不是屬于講按部就班技術(shù)的那種人。什么xx入門 ,入門到精通,入門到入土。 其實(shí)非要嚴(yán)格說的話已經(jīng)跟angularjs 什么ajax 偏的有點(diǎn)遠(yuǎn)了,之所以還是叫這個(gè)名稱,因?yàn)槎紝儆趙eb應(yīng)用 ,叫這個(gè)名稱是一種延續(xù),其實(shí)這個(gè)系列持續(xù)了幾年了 是我自己從學(xué)習(xí)到一種適合我自己環(huán)境的特有應(yīng)用方式的一種總結(jié)。主題還是一個(gè):web應(yīng)用,往細(xì)了裝逼了說一種同時(shí)適合web 和winform 客戶端 獨(dú)到的 數(shù)據(jù)架構(gòu) 處理方式。當(dāng)然所有的都是基于以前的基礎(chǔ)之上的。
主題:一種同時(shí)適合web 和winform 客戶端 獨(dú)特的 數(shù)據(jù)架構(gòu) 處理方式
后臺(tái)API權(quán)限控制
首先是后臺(tái)的接口 ,使用webapi的方式 返回 json 數(shù)據(jù) 。當(dāng)然這里有一個(gè)技巧 , 也就是權(quán)限控制。眾所周知 http 有一種 方式 可以把授權(quán)放在header 里。后臺(tái)驗(yàn)證 ,每個(gè)接口都要權(quán)限符合才能 請(qǐng)求到數(shù)據(jù)。都知道asp.net MVC有filter 可以用來先進(jìn)行過濾 ,都在Java做web后臺(tái)滿大街 的年代 我們還在用中古時(shí)期的ASP.Net MVC。首先我們對(duì)后臺(tái)代碼和web部分進(jìn)行了分層,數(shù)據(jù)訪問對(duì)象為Entity ,controllers 為各個(gè)請(qǐng)求的API web的和winform的在一起,我們依舊使用了簡(jiǎn)單的三層架構(gòu),xxxLogic.cs 其實(shí)是實(shí)際的業(yè)務(wù)邏輯代碼:

所有的是基于WebAPI形式的 老套路在初始化時(shí)進(jìn)行 router注冊(cè) 以便讓請(qǐng)求映射到對(duì)應(yīng)的controller 不用多說了,還有是asp.net MVC是可以配置返回?cái)?shù)據(jù)格式為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 //下面這句務(wù)必加上 否則就只能在IE下才能得到正確的json數(shù)據(jù) 7 //必須要添加http.formating 那個(gè)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 }
來看下我們的后臺(tái)進(jìn)行權(quán)限控制的代碼,我們并未進(jìn)行復(fù)雜的權(quán)限控制 僅僅只是判斷登錄:
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 }
數(shù)據(jù)處理
然后是數(shù)據(jù)處理,我們?cè)诜?wù)端構(gòu)建了統(tǒng)一的數(shù)據(jù)格式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請(qǐng)求 ,get和post 注意promise 應(yīng)用的機(jī)巧 ,以及提交時(shí)在登錄成功了的情況下 會(huì)自動(dòng)往header里加權(quán)限 。
1 var app = angular.module("scpSite", ["ui.router"]); 2 3 //注入一個(gè)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 //服務(wù)端有正常的錯(cuò)誤消息返回 17 if (response && response.data && response.data.Success == false) { 18 alert("獲取數(shù)據(jù)失敗:" + response.data.Message); 19 } 20 //throw exception 500錯(cuò)誤 21 else { 22 alert("獲取數(shù)據(jù)失敗:" + response.data.ExceptionMessage); 23 } 24 25 if (fun2 != undefined) 26 fun2(response); 27 } 28 }, function () {//在剛httpget的時(shí)候可以 而在此處根本就不會(huì)執(zhí)行此函數(shù) 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 全局變量就會(huì)有用戶信息 ,提交時(shí)post就會(huì)自動(dòng)往權(quán)限里加。結(jié)合上面的一起運(yùn)作 ,體會(huì)一下這樣設(shè)計(jì) 以及聯(lián)動(dòng)運(yùn)作的精妙之處,并且如果服務(wù)端有錯(cuò)誤 在post之初就會(huì)自動(dòng)報(bào)出錯(cuò)誤 并且利用promise的機(jī)制 自動(dòng)阻斷 不讓執(zhí)行進(jìn)程傳到下層 不讓邏輯往后走,如果數(shù)據(jù)成功 則會(huì)自動(dòng)調(diào)用下層promise的數(shù)據(jù)展現(xiàn)操作進(jìn)行頁面渲染,這樣來達(dá)到規(guī)范化:
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 //登錄測(cè)試 否則彈出登錄對(duì)話框 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("登錄遇到錯(cuò)誤"); 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 });
如果登錄成功后那么后續(xù)就是簡(jiǎn)單的平鋪直述的調(diào)用了,下面是一個(gè)簡(jiǎn)單的示例。
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 });
看下我們的使用效果


不用想當(dāng)然的在客戶端調(diào)試把mask去掉以為就可用了哈,我們是做了完善的后臺(tái)校驗(yàn)機(jī)制的。

同一接口應(yīng)用于winform端
看,跟上面相結(jié)合的統(tǒng)一處理 WebAPI promise post 的結(jié)合應(yīng)用正是這個(gè)框架的優(yōu)雅之處 ,并且 APIResult格式 是統(tǒng)一的 在winform客戶端只要實(shí)現(xiàn)一個(gè)json解析 ,同一個(gè)接口或者業(yè)務(wù)邏輯就可應(yīng)用于Windows客戶端了 就這樣簡(jiǎn)單的就達(dá)到了同步。關(guān)于winform 接口 結(jié)果數(shù)據(jù)的處理,很簡(jiǎn)單 我們構(gòu)建一個(gè)跟json一致c#的類 反序列化即可。這是winform 構(gòu)造出的請(qǐng)求 和其他代碼,注意我們是用上面同一規(guī)格APIResult 來進(jìn)行json解析的,通過httprequest 請(qǐng)求 ,原先設(shè)計(jì)的我們有廣告 也就是圖片 還有文本。那么應(yīng)該怎么處理呢? 不是有泛型嗎? 看我的:注意泛型的應(yīng)用 ,如果是img則轉(zhuǎn)而使用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秒超時(shí) 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("與服務(wù)器連接失敗"); 94 Application.Exit(); 95 } 96 return default(T); 97 }


還有,我們的客戶端是支持升級(jí)的,固定的連接上服務(wù)器后先進(jìn)性API版本校驗(yàn) ,如果校驗(yàn)失敗 會(huì)強(qiáng)迫客戶端進(jìn)行版本更新。強(qiáng)不強(qiáng)迫客戶端更新的決定權(quán)在我們 只要把服務(wù)端的ver接口數(shù)字調(diào)高 就可以把低于某版本的客戶端淘汰掉了。看資本的力量如此強(qiáng)大 ,這不就像手機(jī)上的某某xxAPP嗎 其實(shí)啥實(shí)質(zhì)功能都沒更新 ,更新了一堆的廣告。
1 private void LoginForm_Load(object sender, EventArgs e) 2 { 3 4 //先進(jìn)行聯(lián)網(wǎng)和版本檢測(cè) 失敗則退出 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("請(qǐng)先退出Dicom打印服務(wù)軟件,再運(yùn)行此授權(quán)機(jī)程序。");//請(qǐng)先退出Dicom打印服務(wù)軟件(任務(wù)管理器NewScp進(jìn)程)再運(yùn)行此授權(quán)機(jī)程序 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 }

好了 全部 結(jié)束 ,感謝各位看官觀賞,周末愉快。

浙公網(wǎng)安備 33010602011771號(hào)