知道的朋友了解 我不是属于讲按部就班技术的那种人。什么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 }
复制代码

好了 全部 结束 ,感谢各位看官观赏,周末愉快。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |