WinForm企业应用框架设计【五】系统登录以及身份验证+源码

索引

WinForm企业应用框架设计【一】界限划分与动态创建WCF服务(no svc!no serviceActivations!)

WinForm企业应用框架设计【二】团队内部的约定和客户端按约定识别WCF服务

WinForm企业应用框架设计【三】框架窗体设计;动态创建菜单;

WinForm企业应用框架设计【四】动态创建业务窗体

WinForm企业应用框架设计【五】系统登录以及身份验证+源码

闲话休提~

一:登录的画面与客户端逻辑

为了在打开程序的时候先弹出登录窗体

我们修改了主窗体的构造函数

如下:

        public MainForm()
        {
            var loginForm = new LoginForm();
            var result = loginForm.ShowDialog();
            if (result != System.Windows.Forms.DialogResult.OK)
            {
                System.Environment.Exit(0);
            }
            InitializeComponent();
        }

 

登录窗体中登录和取消按钮的事件代码如下

        private void Cancel_Click(object sender, EventArgs e)
        {
            DialogResult = System.Windows.Forms.DialogResult.Cancel;
        }

        private void LoginBtn_Click(object sender, EventArgs e)
        {
            var factory = new Common.ClientFactory<ILogin>();
            UserModel CurUser = null;
            try
            {
                var client = factory.CreateClient();
                CurUser = client.Login(UserNameTB.Text.Trim(), PassWordTB.Text.Trim());
            }
            catch (Exception ex)
            {
                Utils.OnException(ex);
            }
            factory.Dispose();
            if (CurUser == null)
            {
                Utils.Alert("用户名或者密码错误;请重新登录!");
                return;
            }
            CacheStrategy.CurUser = CurUser;
            DialogResult = System.Windows.Forms.DialogResult.OK;
        }

 

当点击登录之后,

会把用户输入的用户名和密码传迪到服务端,并得到当前用户实体

CacheStrategy.CurUser = CurUser;

这里只是一个静态属性,没有做额外的工作,就不多解释了,

二:每次与WCF交互都传递标识信息

登录的过程其实没有什么特殊的

特殊的是,登录之后的每次服务端交互,

服务端都要确认当前的客户端的正确性

为了做到这一点,

我们就要在每次与WCF交互的时候,

把客户端的身份传递给服务器端,并在服务端缓存起来。

我们修改了之前文章中提到的ClientFactory类

        ChannelFactory<TClient> factory;
        TClient proxy;
        OperationContextScope scope;
        public TClient CreateClient()
        {
            factory = new ChannelFactory<TClient>(binding, serviceAddress);
            proxy = factory.CreateChannel();
            ((IClientChannel)proxy).Faulted += new EventHandler(a_Faulted);
            scope = new OperationContextScope(((IClientChannel)proxy));
            var curId = CacheStrategy.CurUser == null ? Guid.Empty : CacheStrategy.CurUser.Id;
            MessageHeader<Guid> mhg = new MessageHeader<Guid>(Guid.Empty);
            MessageHeader untyped = mhg.GetUntypedHeader("token", "ns");
            OperationContext.Current.OutgoingMessageHeaders.Add(untyped);
            return proxy;
        }
        void a_Faulted(object sender, EventArgs e)
        {
            //todo:此处得不到异常的内容
        }
        public void Dispose()
        {
            try
            {
                scope.Dispose();
                ((IClientChannel)proxy).Close();
                factory.Close();
            }
            catch
            {
            }
        }

 

var curId = CacheStrategy.CurUser == null ? Guid.Empty : CacheStrategy.CurUser.Id;
            MessageHeader<Guid> mhg = new MessageHeader<Guid>(Guid.Empty);
            MessageHeader untyped = mhg.GetUntypedHeader("token", "ns");
            OperationContext.Current.OutgoingMessageHeaders.Add(untyped);

这几句为SOAP消息头增加了一个值

这个值就是登录成功后的UserId

每次与WCF的交互操作都会传递这个值

三.服务端的验证

为了对客户端的操作进行身份验证

我们设计了一个所有服务类的基类

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class ServiceBase
    {
        protected ServiceBase()
        {
            CheckLogin();
        }
        /// <summary>
        /// 判断是否登录
        /// </summary>
        protected virtual void CheckLogin()
        {
            var curId = OperationContext.Current.IncomingMessageHeaders.GetHeader<Guid>("token", "ns");
            if (!CacheStrategy.HasKey(curId))
            {
                throw new Exception("#请重新登录#");
            }
        }
    }

 

虚方法CheckLogin负责验证客户端是否登录成功

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]

此两个类属性放在服务基类里

服务子类就不用再写这两个属性了

var curId = OperationContext.Current.IncomingMessageHeaders.GetHeader<Guid>("token", "ns");

这一句得到了我们在客户端传上来的UserId

 

在登录逻辑的服务类里,我们重写了CheckLogin方法

    public class LoginService :ServiceBase, ILogin
    {
        UserDA DA = new UserDA();

        protected override void CheckLogin()
        {

        }

        public UserModel Login(string UserName,string PassWord)
        {
            var result = DA.GetModel(UserName, PassWord);
            if (result != null)
            {
                CacheStrategy.AddObject(result.Id, result);
            }
            return result;
        }
    }

 

因为登录的时候就不用再做验证了,所以我们的重写方法就没有任何代码

CacheStrategy.AddObject(result.Id, result);

就是把当前登录的用户存入缓存里

缓存我们用的是HttpRuntime的Cache

因为我们的WCF是基于WEB的

所以很自然的用了这个

代码如下

public static class CacheStrategy
    {
        //一个小时
        private static int timeOut = 3600;
        /// <summary>
        /// 添加一个对象到缓存
        /// </summary>
        /// <param name="id"></param>
        /// <param name="obj"></param>
        public static void AddObject(Guid id, object obj)
        {
            var outTime = DateTime.Now.AddSeconds(timeOut);
            CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(onRemove);
            HttpRuntime.Cache.Insert(id.ToString(), obj, null, outTime, Cache.NoSlidingExpiration, CacheItemPriority.High, callBack);
        }
        /// <summary>
        /// 移除对象
        /// </summary>
        /// <param name="id"></param>
        /// <param name="obj"></param>
        public static void RemoveObject(Guid id)
        {
            HttpRuntime.Cache.Remove(id.ToString());
        }
        /// <summary>
        /// 获取对象
        /// </summary>
        /// <param name="id"></param>
        public static object GetObject(Guid id)
        {
            var result = HttpRuntime.Cache.Get(id.ToString());
            return result;
        }
        /// <summary>
        /// 判断对象是否存在
        /// </summary>
        /// <param name="id"></param>
        public static bool HasKey(Guid id)
        {
            var result = HttpRuntime.Cache.Get(id.ToString()) != null;
            return result;
        }
        /// <summary>
        /// 缓存被移除的事件
        /// do what you want
        /// </summary>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <param name="reason"></param>
        public static void onRemove(string key, object val, CacheItemRemovedReason reason)
        {
            switch (reason)
            {
                case CacheItemRemovedReason.DependencyChanged://依赖项已更改
                    {
                        break;
                    }
                case CacheItemRemovedReason.Expired://过期移除
                    {
                        break;
                    }
                case CacheItemRemovedReason.Removed://修改和删除
                    {
                        break;
                    }
                case CacheItemRemovedReason.Underused://释放内存移除
                    {
                        break;
                    }
                default: break;
            }
        }
    }

 

四:客户端对验证消息的处理

在服务端基类里我们对验证不通过的客户抛出了一个异常

throw new Exception("#请重新登录#");

(Exception这个类型的异常相对于其他类型的异常来说,是最后被处理的)

再来看看我们获取所有菜单的代码

        /// <summary>
        /// 从WCF获取所有菜单
        /// </summary>
        private void PrepareMenus()
        {
            var factory = new Common.ClientFactory<IMenu>();
            try
            {
                var client = factory.CreateClient();
                Menus = client.GetAllMenu();
            }
            catch (Exception ex)
            {
                Utils.OnException(ex);
            }
            factory.Dispose();
        }

 

当服务端有异常发生时,我们交给了Utils类去处理

        /// <summary>
        /// 服务端发生错误
        /// </summary>
        /// <param name="ex"></param>
        public static void OnException(Exception ex)
        {
            if (ex.Message.Equals("#请重新登录#"))
            {
                Alert("请重新登录");
                ReLogin();
                return;
            }
            Alert(ex.Message);
        }
        /// <summary>
        /// 重新登录
        /// </summary>
        public static void ReLogin()
        {
            var path = Application.ExecutablePath;
            System.Diagnostics.Process.Start(path);
            System.Environment.Exit(0);
        }

 

如果异常,服务端“特意”返回的

我们就让客户端重新登录

好吧!

就这些东西~

----------------------------------------

遗留问题

我试图在ClientFactory中获取服务端反馈的错误

((IClientChannel)proxy).Faulted += new EventHandler(a_Faulted);

但这个事件是抓不到服务端错误消息的内容的

不能优美的解决客户端对验证消息的处理逻辑

----------------------------------------

这个系列到此将告一段落

以后或许我会写增加更多东西

比如通用的权限、人事管理、定制表单、定制流程等

此为后话

----------------------------------------

我正在研究一个在silverlight上实现的类似的框架

已略有小成

但我想,我还是应该先把DotNet4应用程序打包工具系列写完

再写silverlight的东西

(透露一下,我已经把那个工具做成了,自由度非常高的打包工具,您可以用他来打包dotnet 2\3.5\4,以及其他的在注册表里留下痕迹的东西)

----------------------------------------

如各位所愿

我公布出代码和数据库备份(亲,数据库是SQL2008的)

点此下载

亲,也希望你们如我所愿的帮我点推荐吧->->->->->->->->->->->->->->

因为你们的支持才是我的动力

欢迎与我交流!!!

-----------------------------------------

时间: 2024-10-07 23:26:59

WinForm企业应用框架设计【五】系统登录以及身份验证+源码的相关文章

WinForm企业应用框架设计【一】界限划分与动态创建WCF服务(no svc!no serviceActivations!)

WinForm企业应用框架设计[一]界限划分与动态创建WCF服务(no svc!no serviceActivations!) WinForm企业应用框架设计[二]团队内部的约定和客户端按约定识别WCF服务 WinForm企业应用框架设计[三]框架窗体设计:动态创建菜单: WinForm企业应用框架设计[四]动态创建业务窗体 WinForm企业应用框架设计[五]系统登录以及身份验证+源码 先来张图片!我们这个系列就是要做一个这样的框架!    我曾写过几个"系列"的东西,如 PL/SQ

WinForm企业应用框架设计【三】框架窗体设计;动态创建菜单;

要不是我的朋友乔乔==乔不死跟我聊到领域驱动设计~ 我也不会发现第一篇中关于"充血实体"的错误说法(至少~我写文章的时候~内心的想法是错的~) 我个人不是很喜欢领域驱动设计~感觉这种思路(我们暂且叫它思路~虽然它有一些既有的原则和模式) 重点要求架构师深入到业务领域中去~ 但是在国内往往很难真正的与领域专家做深入交流~ 架构师划分的领域模型和聚合往往与真实的情况差别较大~ 即使划分的较好~新的业务和变化的业务也另设计师非常头疼~ 另外 设计师很难将庞大复杂的业务抽象成领域模型 往往需要

WinForm企业应用框架设计【二】团队内部的约定和客户端按约定识别WCF服务

本系列第一篇发出来之后,与钧梓昊逑讨论了一些问题,现整理出来 (钧梓昊逑是我的入门老师~非常牛的技术天才~现在开始涉足西洋乐器领域~希望他能早日超过贝多芬~为山寨党众同仁争光~) 一:关于职责问题 客户端的主要职责负责呈现,不宜有过多的业务逻辑 与业务相关的代码和访问数据库相关的代码放在服务器端 与呈现相关的代码放在客户端  至于哪些代码是与业务相关的,哪些代码是与呈现相关的 呈现的代码是不是包含了业务,业务的代码是不是牵涉到呈现 这属于边界划分的问题,仁者见仁~智者见智~也要根据项目具体问题具

SilverLight企业应用框架设计【一】整体说明

Silverlight企业应用框架设计[六]自定义系统菜单(使用自己的DataForm) SilverLight企业应用框架设计[五]客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务) SilverLight企业应用框架设计[四]实体层设计+为客户端动态生成服务代理(自己实现RiaService) SilverLight企业应用框架设计[三]服务端设计 SilverLight企业应用框架设计[二]框架画面 SilverLight企业应用框架设计[一]整体说明 闲言碎语~

Silverlight企业应用框架设计【六】自定义系统菜单(使用自己的DataForm)

索引 SilverLight企业应用框架设计[五]客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务) SilverLight企业应用框架设计[四]实体层设计+为客户端动态生成服务代理(自己实现RiaService) SilverLight企业应用框架设计[三]服务端设计 SilverLight企业应用框架设计[二]框架画面 SilverLight企业应用框架设计[一]整体说明   首先我们设计的窗体如下 xaml代码如下: <location:BasePage x:Cl

SilverLight企业应用框架设计【五】客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务)

来个索引 SilverLight企业应用框架设计[四]实体层设计+为客户端动态生成服务代理(自己实现RiaService) SilverLight企业应用框架设计[三]服务端设计 SilverLight企业应用框架设计[二]框架画面 SilverLight企业应用框架设计[一]整体说明   在上一节中讲到的自动生成的服务代理类核心代码,如下 public event ServiceEventHandler Completed; public void GetAllMenu() { var si

asp.net-模拟登录猎聘网,能取到登录后的HTML源码,但是跳到liepin的时候还是显示登录框

问题描述 模拟登录猎聘网,能取到登录后的HTML源码,但是跳到liepin的时候还是显示登录框 HttpHelper hh = new HttpHelper(); HttpResult hr = hh.GetHtml(new HttpItem() { Method = "POST", URL = "http://www.liepin.com/user/ajaxlogin/", PostDataType = PostDataType.String, Postdata

急,求助!我想用c#实现查看一个,net系统里任一文件的源码的功能

问题描述 帮个忙好吗?各位大侠,小弟求助!我想用c#实现查看一个,net系统里任一文件的源码的功能.大概是这样的,当我进入某个页面,在页面的左边有系统文件如aspx后缀的文件的列表,当我点击任一文件名的时候,右边显示这个文件的源码.怎么能实现这样的功能呢.类似与下图: 解决方案 解决方案二:问题最好具体一些!!!

android-4.3系统webview显示html 的源码不是显示加载后的网页是怎么回事

问题描述 4.3系统webview显示html 的源码不是显示加载后的网页是怎么回事 4.4正常加载 解决方案 Android显示webview加载的网页源码