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


点此下载


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


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


欢迎与我交流!!!


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

你可能感兴趣的:(WinForm)