有关一些状态机制的封装

直接使用字符串访问会话字典的方式有几个缺点:
1、很容易由于字符串拼错产生错误;
2、获取的对象是object类型的,需要转换到实际类型

好一点的方式是实现编写一个类,封装成属性来使用,比如:
http://www.codeproject.com/KB/aspnet/typedsessionstate.aspx
其实可以使用BuildProvider+CodeDom来自动生成这个封装代码(类似Profile的原理)

先来实现BuildProvider:

using System.Linq;
using System.Web.Compilation;
using System.CodeDom;
using System.Xml.Linq;

namespace SessionBuildProvider
{
    
public class TestBuildProvider : BuildProvider
    {
        
public override void GenerateCode(AssemblyBuilder ab)
        {
            CodeCompileUnit ccu 
= GenerateClass(@"C:\Users\yzhu.MAGICGRIDS\Documents\Visual Studio 2008\WebSites\SessionTest\App_Code\test.session");
            ab.AddCodeCompileUnit(
this, ccu);
        }

        
private CodeCompileUnit GenerateClass(string filePath)
        {
            var doc 
= XDocument.Load(filePath);
            var q 
= from session in doc.Elements("sessions").Elements("session") select session;
            CodeCompileUnit ccu 
= new CodeCompileUnit();
            CodeNamespace cn 
= new CodeNamespace("Util");
            ccu.Namespaces.Add(cn);
            CodeTypeDeclaration entityClass 
= new CodeTypeDeclaration("MyObj");
            cn.Types.Add(entityClass);
            CodeTypeDeclaration sessionClass 
= new CodeTypeDeclaration("Sessions");
            CodeTypeConstructor defaultConstructor 
= new CodeTypeConstructor();
            defaultConstructor.Statements.Add(
new CodeSnippetStatement("obj = new MyObj(); System.Web.HttpContext.Current.Session[\"data\"] = obj;"));
            sessionClass.Members.Add(defaultConstructor);
            CodeMemberField objField 
= new CodeMemberField(new CodeTypeReference("MyObj"), "obj");
            objField.Attributes 
= MemberAttributes.Static;
            sessionClass.Members.Add(objField);
            cn.Types.Add(sessionClass);
            
foreach (var s in q)
            {
                CodeMemberField field 
= new CodeMemberField(new CodeTypeReference(s.Attribute("Type").Value), s.Attribute("Name").Value.ToLower());
                entityClass.Members.Add(field);
                CodeMemberProperty prop 
= new CodeMemberProperty();
                prop.Name 
= s.Attribute("Name").Value;
                prop.Type 
= new CodeTypeReference(s.Attribute("Type").Value);
                prop.Attributes 
= MemberAttributes.Public;
                prop.GetStatements.Add(
new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), s.Attribute("Name").Value.ToLower())));
                prop.SetStatements.Add(
new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), s.Attribute("Name").Value.ToLower()), new CodePropertySetValueReferenceExpression()));
                entityClass.Members.Add(prop);
                prop 
= new CodeMemberProperty();
                prop.Name 
= s.Attribute("Name").Value;
                prop.Type 
= new CodeTypeReference(s.Attribute("Type").Value);
                prop.Attributes 
= MemberAttributes.Public | MemberAttributes.Static;
                prop.GetStatements.Add(
new CodeSnippetExpression(string.Format("return ((MyObj)System.Web.HttpContext.Current.Session[\"data\"]).{0};", s.Attribute("Name").Value)));
                prop.SetStatements.Add(
new CodeAssignStatement(new CodeSnippetExpression(string.Format("((MyObj)System.Web.HttpContext.Current.Session[\"data\"]).{0}", s.Attribute("Name").Value)), new CodePropertySetValueReferenceExpression()));
                sessionClass.Members.Add(prop);
            }
            
return ccu;
        }
    }

}

然后在web.config进行配置,放到compilation节点下:
<buildProviders>
<add extension=".session" type="SessionBuildProvider.TestBuildProvider"/>
</buildProviders>

然后在app_code目录中加一个.session配置文件test.session(由于是demo代码,上面我直接硬编码了路径,可以从BuildProvider基类的VirtualPath属性获取路径):
<?xml version="1.0" encoding="utf-8"?>
<sessions>
    
<session Type="System.Int32" Name="UserID" Key="userid" />
  
<session Type="System.String" Name="UserName" Key="username" />
</sessions>
使用一个配置文件来定义会话的键值等也可以方便团队协作,避免会话使用上的冲突等。

最后就可以在写代码的时候直接这么使用了:
        Sessions.UserID = 1;
        Response.Write(Sessions.UserID);
        Sessions.UserName 
= "hello";
        Response.Write(Sessions.UserName);

数据并没有直接保存在Session中,而是全部保存在MyObject大对象中,大对象再完整保存到了Session中。
ViewState、Session、Cache的封装其实都可以这么实现,完全可以复杂一点,把值范围检测或对象的生命周期管理也自动生成进去。
由于是demo代码,其中还有很多不完善的地方,大家可以顺这个思路自己去实现。

对于QueryString的封装也可以这样,当然缺点就是不够灵活,而且由于从QueryString中获取的数据也就是简单值类型和string,应该可以这样:
    public static Nullable<T> GetQueryString<T>(this Page p, string param)
        
where T : struct
    {
        
string s = p.Request.QueryString[param];
        Nullable
<T> r = null;
        
if (s == null)
            
return r;
        
try
        {
            r 
= (T)Convert.ChangeType(s, typeof(T));
        }
        
catch
        {
        }
        
return r;
    }

    
public static string GetQueryString(this Page p, string param)
    {
        
return p.Request.QueryString[param];
    }
使用的时候:
        Debug.Assert(this.GetQueryString<int>("i"== null"i == null");
        Debug.Assert(
this.GetQueryString("s"== null"s == null");

不知大家还有没有更好的方法?
posted @ 2008-05-09 14:36 lovecherry 阅读(...) 评论(...) 编辑 收藏