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


    原文地址:

    http://www.cnblogs.com/liulun/archive/2011/12/16/2290800.html

    索引


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


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


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


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


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


    闲话休提~


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


    image


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


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


    如下:


            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的)


    点此下载


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


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


    欢迎与我交流!!!


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

  • 相关阅读:
    php 导出csv文件
    dns 服务器配置
    ettercap ARP dns 欺骗
    for循环内 执行$ajax(){}
    js 如何生成二维数组
    jquery读取csv文件并用json格式输出
    echo 换行
    cmd命令运行php,php通过cmd运行文件
    Git 常用命令整理
    图像裁剪插件
  • 原文地址:https://www.cnblogs.com/fcsh820/p/2294879.html
Copyright © 2020-2023  润新知