您的位置:首页 > 编程语言 > ASP

二进制度序列化碰到的问题---- 切实认识Asp.net2.0页面编译机制

2008-08-15 10:59 344 查看
[b]问题描述[/b]

手上有一行业门户站点,每个用户都有一个子站点(或说模板),每个子站会有一些配置数据(如:logo,横栏图片或者其它一些没想到的东西)为了编程方便以及日后扩充方便,我使用直接在.aspx文件中定义的类来保存这些数据,同样为了修改方便(编译一次站的时间已经到了让人无法接受地步),我这里使用的是单独的.aspx文件(不关联.cs文件),配置数据会被分配给定义的类,然后将类序列化,使用是则执行反序列化,(基本上也就类似那个profile的过程)

下面是序列化代码

public static class SerializationHelper

{

public static string Serialize(object data)

{

using (MemoryStream streamMemory = new MemoryStream())

{

BinaryFormatter formatter = new BinaryFormatter();

// 2. Serialize the dataset object using the binary formatter

formatter.Serialize(streamMemory, data);

// 3. Encrypt the binary data

string binaryData = Convert.ToBase64String(streamMemory.GetBuffer());

// 4. Write the data to a file

return binaryData;

}

}

public static object Deserialize(string serialData)

{

object data = new object();

try

{

MemoryStream streamMemory;

BinaryFormatter formatter = new BinaryFormatter();

// 2. Read the binary data, and convert it to a string

string cipherData = serialData;

// 3. Decrypt the binary data

byte[] binaryData = Convert.FromBase64String(cipherData);

// 4. Rehydrate the dataset

streamMemory = new MemoryStream(binaryData);

data = formatter.Deserialize(streamMemory);

}

catch(Exception ex)

{

// data could not be deserialized

data = null;

}

return data;

}

}

这里使用了二进制序列化,现有如下两个文件TemplateConfig.aspx跟Index.aspx.

TemplateConfig.aspx文件里面代码如下

<script runat="server">

//配置数据保持类的定义

[Serializable]

public class Temp_EntProperty

{

private string _bar;

private string _logo;

private System.Collections.Generic.List<SubItem> size;

public System.Collections.Generic.List<SubItem> Size

{

get

{

if (size == null)

{

size = new System.Collections.Generic.List<SubItem>();

}

return size;

}

}

public string Bar

{

get { return _bar; }

set { _bar = value; }

}

public string Logo

{

get { return _logo; }

set { _logo = value; }

}

}

[Serializable]

public class SubItem

{

public SubItem(string name, int value)

{

_name = name;

_value = value;

}

private string _name;

private int _value;

public string Name

{

get { return _name; }

set { _name = value; }

}

public int Value

{

get { return _value; }

set { _value = value; }

}

}

protected void Buttion1_Click(object sender, EventArgs e)

{

Temp_EntProperty properties = new Temp_EntProperty();

config.SerializeData = SerializationHelper.Serialize(properties); //将序列化后的数据保存到数据库

//数据库操作....

}

</script>

页面标签省略.......

Index.aspx文件代码如下

需要引用TemplateConfig.aspx页

<%@ Reference Page="~/TemplateConfig.aspx" %>

<script runat="server">

protected void Page_Load(object sender, EventArgs e)

{

ASP.templateconfig_aspx.Temp_EntProperty properties=SerializationHelper.Deserialize(serialData) as ASP.templateconfig_aspx.Temp_EnterProperty ;

//serialData是数据库中的序列化后数据.

}

</script>

页面标签省略.......

这个过程很多时候都能正常工作(现在知道,这是3种情况中的一种)

第一种:我先运行了TemplateConfig.aspx文件,然后再运行Index.aspx文件发现一切正常.

第二种:我修改了一下TemplateConfig.aspx文件,接着再次运行Index.aspx文件时却出现错误.

单步调试发现反序列化是正常的,但是在将反序列化后的object类型的实例转化为Temp_EntProperty时出问题了,

单步调试明明显示Object类型的实例就是Temp_EntProperty,但是却不允许转化....

第三种:有时候还会碰到反序列化错误,提示找不到程序集,而奇怪的是,TemplateConfig.aspx跟Index.aspx文件的代码一行没变动过.

问题的解决

当然这个3个问题不是同发现的,事实上这些问题是在站点测试的个把月中陆续发现的,为了避免这些情况,我一度把自定义类(如Temp_EntProperty等)改成Hashtable ,因为Hashtable不会出现以上问题,直到今天回过头来认真的找下原因.

根据错误信息大致可以判断出是反序列化时程序集问题(根据第三种情况),实际上以上问题的根源是,在.aspx文件中定义的类是动态编译的,其生成的程序集保存在系统盘的.net目录中(可以使用HttpRuntime.CodegenDir找到这个目录),你每次对.aspx文件的修改多会迫使asp.net运行时再次编译这个类,生成新的程序集(以App_Web_开头),而二进制序列化跟反序列化是跟程序集相关的,即使用二进制序列化后的数据里面保留了程序集信息,而反序列化时会根据这些信息找到对应的程序集,完成反序列化工作,另一方面Asp.net页面编译模型中,当页面改变后,运行时生成新的页面程序集,会有一种机制来删除旧的页面程序集.

拿TemplateConfig.aspx页面为例, 当第一次运行时,系统在临时目录中生成其对应 App_Web_2342usaj(注意文件扩展名是.dll,这里省略,下同) 程序集,接着你修改了这个TemplateConfig.aspx页面(打个空格什么的保存下就好),你第二次请求时,运行时又会生成App_Web_asdfasd程序集,并且运行时试图删除App_Web_2342usaj程序集,但是不幸的是App_Web2342usaj程序集中已经被架载到AppDomain中了,Asp.net运行时无法删除已经架载到AppDomain中的程序集,除非你重新启动应用程序(修该下web.config或global.asax,重新启动IIS或应用程序池,当然重启电脑也可以), 在无法删除的情况下,Asp.net运行时会在临时目录下添加一个名为App_Web_2342usaj.dll.delete的空文件,这是一个标记,在应用程序重新启动时,AppDomain会被重新加载,而在加载之前,App_Web_2342usaj.dll文件会被删除,另外当你连续15次修改TemplateConfig.aspx文件后,应用程序也会重新启动,不然AppDomain中就会有太多程序集而消耗内存(参考Asp.net2.0高级编程),另外关于在页面中使用Reference后,在观察Index.aspx对应的.compiled文件(临时目录下),可以发现在index.aspx.2342s.compiled(中间数字或字符是随机)的<filedeps>节中有对文件TemplateConfig.aspx的引用,这个说明在修改了TemplateConfig.aspx文件后或触发index.aspx依赖缓存清除,也就是index.aspx会随之重新编译生成新的程序集.上面这些过程你也可以自己观察到.

好了有了上面这些知识我们现在可以解释前面的三种情况了

第二种情况,我们修改了一下TemplateConfig.aspx文件后接着运行Index.aspx文件,由于Index.aspx依赖于TemplateConfig.aspx文件,因此TemplateConfig.aspx文件跟Index.aspx文件都会被重新编译生成新的程序集,问题在于 index.aspx 代码中

protected void Page_Load(object sender, EventArgs e)

{

ASP.templateconfig_aspx.Temp_EntProperty properties=SerializationHelper.Deserialize(serialData) as ASP.templateconfig_aspx.Temp_EnterProperty ;

//serialData是数据库中的序列化后数据.

}

使用的serialData是原先Temp_EntProperty程序集序列化的数据,因此在还原时由于老的程序集也还在AppDomain中,故反序列化是成功的,但是转化却失败了,原因是两个类是同名类,但是位于不同的程序集中,如果我们这个时候重新启动下应用程序,那么就会出现第三种情况。

第三种情况:在应用程序重新启动后,老的程序集会被删除,而数据库里保存的恰是旧程序集中Temp_EntProperty类的序列化后的数据(其中含旧程序集名称),应次在你进行反序列化时无疑会碰到程序集无法找到问题.

问题找到了,那么如何解决呢,其实也很简单,如果你需要二进制序列化带来的高性能跟数据量少的优势,那么就把需要序列化的类放到固定的程序集中(独立建立一个项目), 如果你想在.aspx页面中直接定义需要序列化的类,那么你只能使用xml序列化,因为xml序列化后的数据不保留程序集信息.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: