准备知识:
1。
为了在web页面上呈现DotNET资源程序集里的任意资源,我们需要编写一个自定义的Handler程序,用这个Handler来接管处理特定扩展名的资源。DotNET Framework 提供了一个IHttpHandler接口,我们只要继承此接口,然后实现它提供的两个方法即可。
2。
为了显示各种不同的Web资源,我们需要封装一个资源相关的类,用它来从资源程序集提取指定名称的资源,资源程序集内部的各种资源都是以嵌入的方式编译的,也就是说将图片,文字,音视频都嵌入到一个单独的DotNET Dll里,我们把它编译成一个类库,也就是DotNET资源程序集。如果你的资源程序集的namespace为SanXia.Resources,而且你在此项目下建立了文件夹,分了很多层,比如建立了Res文件夹,在Res下又建立了Text文件夹,Text下面你又建立了Html文件夹,Html下有demo.html文件,那么这个demo.html的resourceFilename就是:Res.Text.Html.demo.html。 资源程序集中资源的命名方式为:assemblyName+"."+resourceFilename 。因此我们需要得到资源程序集的名称和所要呈现在Web页面上的资源文件名称,于是需要给自定义的Handler传递这两个参数,这里设计的传递格式为resourceFilename-assemblyName,如果没有提供assemblyName,也就是说如果传递的格式为resourceFilename,那么我们就会在web.config文件里appSettings节查找key为ResourceAssemblyName的项,读取它所对应的value,我们就把它当作资源程序集的名称。
3。
为了防止其它不相关的网站随意盗链本网站上的资源,因此在资源类内部对次进行了必要的处理,如果是来自非本站主机的链接,程序就是去读取一个默认资源程序集里的资源,提供给web页来呈现,也就是一个提示性的说明图片,比如说你出错了等等。这个默认的资源程序集的名称同样可以在web.config里的appSettings节配置,它的key为DefaultResourceAssemblyName。同时,关于本站资源主机的设置也是可以在配置文件中处理的,它所对应的Key为ResourceHostName。
4。
在web上呈现的资源名称是大小写免感的,不能写错噢,如果不小心写错了,系统会自动加载默认资源程序集里的错误资源,提示加载资源出错了。
我们把前面的理解一下,如果不明白可以多读几次,下面我们就要开始编写代码了。
一。SanXia.Components.Resource类
最主要的我们需要编写一个资源类,让它负责处理所有各种不同的资源,比如文字资源,图像资源,音视频资源等。我们把这个资源类的命名空间设为SanXia.Components.Resource,生成后的DotNET Dll名称也设为SanXia.Components.Resource。
我们定义两个接口,分别为IResourceProvider和IRenderResource,以及两个抽象基类,分别为ResourceProviderBase和RenderResourceBase
我们先介绍一下IResourceProvider接口,它很简单,简单到只有一个方法,返回另外的一个接口IRenderResource。
为什么要这么设计?是因为要充分利用运行时多态特性,也就是即于接口编码,恩,这句话好像是很熟悉,是不是在那里看到过......“到处都是这么说滴~~“。有什么好处吗?接口和实现分开,这还不够好吗?当然不只是这个的原因,其实你也不这么设计,不过前人已经总结出了那么好的解决方案干嘛重新造轮子去呢。
namespace SanXia.Components.Resource.Provider
{
/// <summary>
/// Description : 资源提供者接口
/// Author:CHONGCHONG-天真的好蓝
/// QQ:1921069
/// MSN:chongchong2008@msn.com
/// Address : YanJiang Road 7# YiChang HuBei CHINA
/// Date:2006-08-29
/// LastModifyDate:2006-08-29
/// </summary>
public interface IResourceProvider
{
IRenderResource RenderResource(); //返回生成资源接口
}
}
再说说IRenderResource接口,噢......它同样设计得相当简单,就一个方法Render,用它来呈现资源。

namespace SanXia.Components.Resource
{
public interface IRenderResource
{
void Render();
}
}
看看其它两个抽象类,首先来瞄瞄ResourceProviderBase abstract class,它是这样定义的:
namespace SanXia.Components.Resource.Provider
{
public abstract class ResourceProviderBase
{
#region //字段定义
protected string resourceType; //资源类型
protected string resourceName; //资源名称
protected string assemblyName; //程序集名称
#endregion
#region //构造方法
public ResourceProviderBase(string resourceType, string resourceName, string assemblyName)
{
this.resourceType = resourceType;
this.resourceName = resourceName;
this.assemblyName = assemblyName;
}
#endregion
}
}
看上去没啥作用,只是用作公共状态的存储而已嘛,这个倒是没什么好说的了。
继续看看另为一个抽象类的定义,RenderResourceBase abstract class,它是这样定义的:
namespace SanXia.Components.Resource
{
public abstract class RenderResourceBase
{
#region //字段定义
private NameValueCollection appSettings;
private string defaultResourceAssemblyName;
private Cache assemblyCache;
protected string resourceType; //资源类型
protected string resourceName; //资源名称
protected string assemblyName; //程序集名称
protected Assembly resourceAssembly = null; //资源程序集
protected HttpContext context; //当前上下文
protected byte[] buffer; //资源数据
protected Stream resourceStream; //资源流
protected string contentType; //资源输出内容类型
protected Encoding contentEncoding; //资源输出内容编码
#endregion
#region //构造方法
public RenderResourceBase(string resourceType, string resourceName, string assemblyName)
{
appSettings = System.Web.Configuration.WebConfigurationManager.AppSettings;
defaultResourceAssemblyName = appSettings["DefaultResourceAssemblyName"];
string defaultResourceFilename = defaultResourceAssemblyName + ".Res.FileNotFound.gif";
this.assemblyCache = new System.Web.Caching.Cache();
this.resourceType = resourceType;
this.resourceName = assemblyName + "." + resourceName;
this.assemblyName = assemblyName;
this.context = HttpContext.Current;
this.resourceAssembly = Assembly.Load(this.assemblyName);//可以自己加入缓存机制
if (ResourceHelp.IsResourceExists(this.resourceName, this.resourceAssembly))
{
this.resourceStream = resourceAssembly.GetManifestResourceStream(this.resourceName);
int bytes = (int)resourceStream.Length;
buffer = new byte[bytes];
resourceStream.Read(buffer, 0, bytes);
}
else
{
//指定的资源不存在,显示默认资源
this.resourceAssembly = Assembly.Load(defaultResourceAssemblyName);
this.resourceStream = resourceAssembly.GetManifestResourceStream(defaultResourceFilename);
int bytes = (int)resourceStream.Length;
buffer = new byte[bytes];
resourceStream.Read(buffer, 0, bytes);
}
}
#endregion
}
}
稍微有点繁琐,完成了大部分重要工作,就是从web.config读取默认资源程序集得名称,判断传递过来的resourceFilename是否在assembly里存在,这里用了ResourceHelp.IsResourceExists这个方法来判断。获取资源流赋给resourceStream保护变量,然后将它读入buffer字节数组保护变量,如果判断资源不存在或是来自非本站的盗链,就会加载默认资源程序集,
重要的任务都做好了,下面再继续处理ResourceProvider,比如我们要加入一个ImageResourceProvider,我们只需要继承ResourceProviderBase抽象基类,同时实现IResourceProvider接口,DotNET不允许同时继承两个基类,那么利用接口就可以解决此限制。
看看ImageResourceProvider类是如何设计的:
namespace SanXia.Components.Resource.Provider
{
public sealed class ImageResourceProvider : ResourceProviderBase, IResourceProvider
{
#region //构造方法
public ImageResourceProvider(string imageType, string resourceName, string assemblyName)
: base(imageType, resourceName, assemblyName)
{ }
#endregion
#region //接口实现
/// <summary>
/// Description:返回生成资源接口
/// </summary>
/// <returns></returns>
public IRenderResource RenderResource()
{
return new RenderImageResource(base.resourceType, base.resourceName, base.assemblyName);
}
#endregion
}
}
首先构造函数它接受imageType,resourceName,assemblyName三个参数,然后把它传给基类的相关保护型变量。
由于它继承IResourceProvider接口,所有要实现IResourceProvider接口的RenderResource方法,前面提到过这个接口,实现它即可以,
因为是图像资源提供者类,所有它也是有选择的返回生成图像资源类,RenderImageResource实现了IRenderResource接口。
那么RenderImageResource类是如何设计的呢,下面来看看代码:
namespace SanXia.Components.Resource
{
public class RenderImageResource : RenderResourceBase, IRenderResource
{
#region //字段定义
protected ImageFormat imageFormat; //图像格式
#endregion
#region //构造方法
public RenderImageResource(string imageType, string resourceName, string assemblyName)
: base(imageType, resourceName, assemblyName)
{ }
#endregion
#region //接口实现
/// <summary>
/// Description:实现接口方法,生成资源
/// </summary>
public void Render()
{
switch (base.resourceType)
{
case "gif":
base.contentType = "image/gif";
this.imageFormat = ImageFormat.Gif;
break;
case "jpg":
case "jpeg":
base.contentType = "image/jpeg";
this.imageFormat = ImageFormat.Jpeg;
break;
case "png":
base.contentType = "image/png";
this.imageFormat = ImageFormat.Png;
break;
default:
base.contentType = "application/octet-stream";
break;
}
context.Response.Clear();
context.Response.ContentType = base.contentType;
Image image = Image.FromStream(new System.IO.MemoryStream(base.buffer)); //其实流也是可以直接访问到的,因为resourceStream是保护型的变量。
image.Save(context.Response.OutputStream, imageFormat);
}
#endregion
}
}
由于RenderImageResource继承了RenderResourceBase抽象基类,所以RenderImageResource不需要做很复杂的工作,它直接调用基类来帮自己处理。构造方法就只把相关参数传给了基类。这样看来,设计一个抽象基类还是很不错的,子类继承了基类的保护型变量,可以直接访问到,比如可以直接访问到stream类型的resourceStream。
它现在要做的只是实现IRenderResource接口就可以了,就只有一个Render方法,用来呈现具体的web资源。
其它的资源,比如文字等都可以按照上面那样设计,需要一个provider和render。具体代码请下载全部源文件自己研读。
到此,可以放松一下了,需要的各种资源类都设计好了,现在把它们提供给外部调用,为此又加了一个资源工厂类。把各种资源提供给外部,它的定义如下:
namespace SanXia.Components.Resource
{
public sealed class ResourcesFactory
{
#region //返回资源
public static IRenderResource CreateResource(string resourceName, string assemblyName)
{
IResourceProvider resourceProvider = null; //资源提供者接口
IRenderResource renderResource = null; //生成资源接口
string[] imageType = "gif,jpg,jpeg,png".Split(",".ToCharArray());
string[] textType = "vbs,js,htc,css,txt,xsl,xml,htm,html".Split(",".ToCharArray());
string[] videoType = "mp3,wmv,wma,mpg,mpa,mpe,mov,qt,avi,asf,asr,asx,swf".Split(",".ToCharArray());
string resourceExt = resourceName.Substring(resourceName.LastIndexOf(".") + 1).ToLower();
if (!ResourceHelp.IsRobber())
{
if (ResourceHelp.IsTypeExist(resourceExt, imageType))
{
resourceProvider = new ImageResourceProvider(resourceExt, resourceName, assemblyName);
renderResource = resourceProvider.RenderResource();
}
else if (ResourceHelp.IsTypeExist(resourceExt, textType))
{
resourceProvider = new TextResourceProvider(resourceExt, resourceName, assemblyName);
renderResource = resourceProvider.RenderResource();
}
else if (ResourceHelp.IsTypeExist(resourceExt, videoType))
{
resourceProvider = new VideoResourceProvider(resourceExt, resourceName, assemblyName);
renderResource = resourceProvider.RenderResource();
}
}
else //如果是盗链
{
if (ResourceHelp.IsTypeExist(resourceExt, imageType))
{
resourceProvider = new ImageResourceProvider(resourceExt, resourceName + "Robber", assemblyName);//这里人为添加Robber字符是为了显示默认错误图像,此资源在SanXia.Resources.DefaultResource.dll里,可以在Web.config配置
renderResource = resourceProvider.RenderResource();
}
}
return renderResource; //返回资源接口,运行时多态
}
#endregion
}
}
就这么简单,资源类就算是完成了。
二。SanXia.Components.Handler类
回忆一下最前面所说的,要在Web页面上呈现资源程序集里的资源,还需要提供一个自定义的Handler,只需要继承IHttpHandler接口即可。
这个类需要引用上面封装好的Resource类。
using SanXia.Components.Resource;//引入资源处理类
namespace SanXia.Components.Handler
{
/*
* 功能:
* 提取指定程序集中嵌入的资源文件,并输出到客户端
* 支持的资源类型如下
* gif,jpg
* vbs,js,css,txt,xml,htm,html
* flash,mpeg
* wav,mp3,avi,wma
*
* 调用:
* 此资源类提供HttpHandler来提取指定程序集中的资源
* 因此你需要在你的IIS数据库里映射扩展类型,本资源类默认使用.res扩展名
* <img src=".res?Res.Image.demo.jpg" />
* <img src=".res?Res.Image.demo.jpg-SanXia.Resources" />
*
*/
/// <summary>
/// Description : 资源Handler类
/// Author:CHONGCHONG-天真的好蓝
/// QQ:1921069
/// MSN:chongchong2008@msn.com
/// Address : YanJiang Road 7# YiChang HuBei CHINA
/// Date:2006-08-29
/// LastModifyDate:2006-08-29
/// </summary>
public class ResourceHandler : IHttpHandler
{
#region //字段定义
private NameValueCollection appSettings;
#endregion
#region //构造方法
public ResourceHandler()
{
appSettings = System.Web.Configuration.WebConfigurationManager.AppSettings;
}
#endregion
#region //接口实现
void IHttpHandler.ProcessRequest(HttpContext context)
{
IRenderResource renderResource ; //生成资源接口
string resourceName = String.Empty; //资源名称
string assemblyName = String.Empty; //程序集名称
string queryParams = String.Empty;
string[] queryParams1,queryParams2;
//传递过来的全部路径参数,包含cookie , forms 等
string fullParams = context.Request.Params.ToString();
//截取QueryString查询参数偏移量
int offset = fullParams.IndexOf("&ALL_HTTP");
//获取query参数
queryParams = fullParams.Substring(0, offset);
queryParams1 = HttpUtility.UrlDecode(queryParams).Split(new char[] { '&' });
queryParams2 = queryParams1[0].Split(new char[] { '-' });
if (queryParams2.Length == 1) //只传递了资源名
{
resourceName = queryParams2[0].ToString();
assemblyName = appSettings["ResourceAssemblyName"];
}
else //同时传递了资源名和资源程序集名称
{
resourceName = queryParams2[0].ToString();
assemblyName = queryParams2[1].ToString();
}
renderResource = ResourcesFactory.CreateResource(resourceName, assemblyName); //直接调用封装好的ResourcesFactory类的静态方法CreateResource
renderResource.Render(); //向Web页面呈现当前资源
}
bool IHttpHandler.IsReusable
{
get { return true; }
}
#endregion
}
}
三。Web Test






一切都准备好了,剩下的就要说说web.config配置文件了,在appSettings 节需要添加3个Key-Value对。如下:
<appSettings>
<!--
ResourceHostName:资源所在主机,如同www.aaa.net可以防止其他网站盗链图像资源,大家在测试的时候要注意填写正确
ResourceAssemblyName:资源程序集名称
DefaultResourceAssemblyName:默认资源程序集名称,当资源加载失败时会从默认资源程序集提取资源
-->
<add key="ResourceHostName" value="localhost"/>
<add key="ResourceAssemblyName" value="SanXia.Resources"/>
<add key="DefaultResourceAssemblyName" value="SanXia.Resources.DefaultResource"/>
</appSettings>
然后配置自定义的Handler,同时你也需要的IIS里添加映射。我这里取的名字是.res,当然这个可以随意取。
<system.web>
<httpHandlers>
<add verb="*" path="*.res" type="SanXia.Components.Handler.ResourceHandler,SanXia.Components.Handler.ResourceHandler"/>
</httpHandlers>
<globalization requestEncoding="utf-8" responseEncoding="utf-8" />
</system.web>
在Html页面显示资源,调用如下:
<link href=".res?Res.Text.Css.demo.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src=".res?Res.Text.Script.demo.js"></script>
<img src=".res?Res.Image.demo1.jpg" />
<img src=".res?Res.Image.demo2.jpg-SanXia.Resources" />
源代码下载
http://files.cnblogs.com/CHONGCHONG2008/SanXia.Resource.Setup.rar