原文地址:
http://www.cnblogs.com/liulun/archive/2011/12/16/2290800.html
索引
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的)
点此下载
亲,也希望你们如我所愿的帮我点推荐吧->->->->->->->->->->->->->->
因为你们的支持才是我的动力
欢迎与我交流!!!
-----------------------------------------