Unity&Webform(2):自定义LifetimeManager和TypeConverter使Unity从HttpContext中取值注入WebForm页面
上一篇Unity&WebForm(1): 自定义IHttpHandlerFactory使用Unity对ASP.NET Webform页面进行依赖注入中让Unity和WebForm结合在一起,通过使用HttpHandlerFactory实现了对页面的依赖注入,本文将在上篇的基础上,通过对Unity的LifetimeManager的扩展实现从WEB Application特有的HttpContext中取值注入页面
在Unity中,如何获取对象的实例及如何销毁对象都是由LifetimeManager完成的,其定义如下
有了这3个方法,就可以通过自定义LifetimeManager来实现从HttpContext中取值,但此处又会出现一个问题:
LifetimeManager如何得知需要取得的键值?
如果使用硬编码方式生成LifetimeManager的实例,那么在构造的时候传入键值即可,是十分方便的,但如果使用配置文件呢?
为了解决以上问题,就需要引入Unity中的另一个概念:TypeConverter
Unity为了解决在配置文件中写入值的问题,在定义Lifetime或者Property等参数的时候,会允许有一个typeConverter属性,Unity自动使用此属性中指出的继承自TypeConverter类的类实现字符串(保存于value属性)到具体对象的转换
因此,我们可以利用这一点,在value中保存需要的键值,再自定义TypeConverter生成LifetimeManager,也算是一个小小的Hack吧
这里的问题便是如何将需要的Key传入到对象中,根据原理中说的,还需要一个TypeConverter,代码如下
在配置文件中,可以将value属性设为所需的键值,再提供此TypeConverter即可,我的配置文件如下
为了演示,修改Default页面的代码如下
2.当然也可以有QueryStringLifetimeManager,不过这就更麻烦了,因为QueryString的值是string类型,要转换到其他类型还需要一个TypeConverter,一个可行的解决方案是在配置文件中的value项以一种约定的方式加入TypeConverter的类型,比如value="key&TypeConverterType"
背景
在很多情况下,会有较为持久地保存对象的需求,但由于对象的类度太细,也许不会考虑使用数据库,此时HttpContext的Items属性是一个很好的归宿,而在某些页面,则需要从HttpContext中获得此类对象,一个比较普遍的做法是使用属性进行封装,代码如下public UserInfo CurrentUser
{
get
{
return HttpContext.Current.Items["CurrentUser"] as UserInfo;
}
set
{
HttpContext.Current.Items["CurrentUser"] = value;
}
}
这样写的效果是显而易见的,避免了每一次接触CurrentUser都要写HttpContext.Current...之类的代码,但是就简洁性来说尚有不足,如果可以简单地写一个自动生成的属性,再由外界将HttpContext.Current.Items["CurrentUser"]注入其中,将会很大程度上提高程序编写的效率{
get
{
return HttpContext.Current.Items["CurrentUser"] as UserInfo;
}
set
{
HttpContext.Current.Items["CurrentUser"] = value;
}
}
原理
上一篇中已经将WebForm的页面和Unity合在了一起,在页面被创建之后即可由Unity对其进行注入,所以这次将在上一篇的基础上,通过扩展Unity实现功能。在Unity中,如何获取对象的实例及如何销毁对象都是由LifetimeManager完成的,其定义如下
public abstract class LifetimeManager : ILifetimePolicy, IBuilderPolicy
{
protected LifetimeManager();
public abstract object GetValue();
public abstract void RemoveValue();
public abstract void SetValue(object newValue);
}
其中GetValue方法获取对象实例,RemoveValue方法销毁对象,SetValue方法为对外引用的保存提供新的实例{
protected LifetimeManager();
public abstract object GetValue();
public abstract void RemoveValue();
public abstract void SetValue(object newValue);
}
有了这3个方法,就可以通过自定义LifetimeManager来实现从HttpContext中取值,但此处又会出现一个问题:
LifetimeManager如何得知需要取得的键值?
如果使用硬编码方式生成LifetimeManager的实例,那么在构造的时候传入键值即可,是十分方便的,但如果使用配置文件呢?
为了解决以上问题,就需要引入Unity中的另一个概念:TypeConverter
Unity为了解决在配置文件中写入值的问题,在定义Lifetime或者Property等参数的时候,会允许有一个typeConverter属性,Unity自动使用此属性中指出的继承自TypeConverter类的类实现字符串(保存于value属性)到具体对象的转换
因此,我们可以利用这一点,在value中保存需要的键值,再自定义TypeConverter生成LifetimeManager,也算是一个小小的Hack吧
实现
首先自定义一个LifetimeManager,在此叫ContextLifetimeManager,方法的实现非常简单,不多作解释,代码如下public class ContextLifetimeManager : LifetimeManager
{
public string Key
{
get;
set;
}
public ContextLifetimeManager(string key)
{
Key = key;
}
public override object GetValue()
{
return HttpContext.Current.Items[Key];
}
public override void RemoveValue()
{
HttpContext.Current.Items[Key] = null;
}
public override void SetValue(object newValue)
{
HttpContext.Current.Items[Key] = newValue;
}
}
注意这只是最为简单的实现,没有考虑多线程同步等复杂的问题,也仅仅是作为一个演示,如果需要线程安全等特性,可以参考官方的ContainerControlledLifetimeManager(就是我们使用singleton作为生命周期时使用的),里面使用了Moniter控制线程同步{
public string Key
{
get;
set;
}
public ContextLifetimeManager(string key)
{
Key = key;
}
public override object GetValue()
{
return HttpContext.Current.Items[Key];
}
public override void RemoveValue()
{
HttpContext.Current.Items[Key] = null;
}
public override void SetValue(object newValue)
{
HttpContext.Current.Items[Key] = newValue;
}
}
这里的问题便是如何将需要的Key传入到对象中,根据原理中说的,还需要一个TypeConverter,代码如下
public class ContextLifetimeManagerConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string str = value as string;
if (!String.IsNullOrEmpty(str))
{
return new ContextLifetimeManager(str);
}
return base.ConvertFrom(context, culture, value);
}
}
开发过web控件的人对这种写法会非常熟悉,和web控件中的属性赋值一样,重写2个方法将字符串转换为所需要的类型,在此像是构造一个新的ContextLifetimeManager对象并提供字符串值作为键值{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string str = value as string;
if (!String.IsNullOrEmpty(str))
{
return new ContextLifetimeManager(str);
}
return base.ConvertFrom(context, culture, value);
}
}
在配置文件中,可以将value属性设为所需的键值,再提供此TypeConverter即可,我的配置文件如下
Code
此处的键值为CurrentUser,向Default页面进行了注入为了演示,修改Default页面的代码如下
public partial class Default : System.Web.UI.Page
{
public UserInfo CurrentUser
{
get;
set;
}
protected void Page_Load(object sender, EventArgs e)
{
if (CurrentUser != null)
{
Response.Write("注入成功!");
}
}
}
在页面中有一个CurrentUser属性等待注入,其运行结果自然是显示了"注入成功",也就不再截图了{
public UserInfo CurrentUser
{
get;
set;
}
protected void Page_Load(object sender, EventArgs e)
{
if (CurrentUser != null)
{
Response.Write("注入成功!");
}
}
}
扩展
1.同样可以自定义SessionLifetimeManager,但需要注意的是不可以在HttpHandlerFactory中使用Session,因此要将BuildUp的时机放到页面的PreInit事件中,另外还需要注意的是,如果你将Unity和PIAB一起用,那么在页面的PreInit中使用container.BuildUp(GetType().BaseType, this);并无法提供PIAB的策略注入机制,其原因是PIAB的注入会返回一个全新的对象,而在些单纯地使用BuidUp仅仅是将this对象包装了起来2.当然也可以有QueryStringLifetimeManager,不过这就更麻烦了,因为QueryString的值是string类型,要转换到其他类型还需要一个TypeConverter,一个可行的解决方案是在配置文件中的value项以一种约定的方式加入TypeConverter的类型,比如value="key&TypeConverterType"