如何将
ASPX
转换为静态文件。
前段日子一直在为
Web
的缓存策略烦恼。微软自己提供的
cache
机制很不灵活,而且喜欢在
header
中加入一些郁闷的
key,
导致
CDN
缓存
时不时的失效。最终反复讨论的结果是:如果太麻烦的话,干脆不要使用微软的页面缓存
就是类似:
<%@ OutputCache Duration="1200" VaryByParam="cid" %>
这样的代码。
除了这样的页面缓存,没有其他办法保存缓存结果了吗?自己拼字符串?
OMG
!页面多的情况下,无疑是个恶梦!
久寻不获,索性自己写了一个模板解析类,自己发明一套语法,将按照规定格式编写的 htm 文件,作为模板载入,设定变量字典后输出页面结果(字符串),然后自己实现缓存机制。花了 3 天时间搞定 ......
久寻不获,索性自己写了一个模板解析类,自己发明一套语法,将按照规定格式编写的 htm 文件,作为模板载入,设定变量字典后输出页面结果(字符串),然后自己实现缓存机制。花了 3 天时间搞定 ......
然而这样对于
asp.net
快速开发来说,仍然是个障碍,难道就没有好的办法吗?
比如直接获取
aspx
的字符串输出之类的
......
。近日网上瞎逛,在
csharpsharper
处看到一篇文章
比较完美的解决这个问题:
How to freeze a dynamic aspx page into a static html page (on the server)
地址如下:
http://30649.aspx.codebetter.com/blogs/peter.van.ooijen/archive/2004/11/01/30649.aspx
不禁欣喜异常,特翻译如下,以飨各位同学:
Take this scenario: On a website a visitor fills in a form after which the form is to be filed as a static html page.
想象这样一个场景:当某位用户在一个网站上填写一个表单(Form),当提交之后,返回的结果是一个静态页面(当有人填写完全一样的表单时,可以直接看到这个静态页面,无须服务器再次计算)。
You might have several reasons for wanting to do that, one of them are search engines. Getting it done in asp.net took me more effort than I had originally expected. Let me share what I have found to be working.
你可能会有很多理由想去这么做,比如说搜索引擎……。为了在asp.net 中实现这个效果,我花费了比我想象中更多的功夫完成了这个效果,让我来和大家分享一下我是如何做到将aspx 结果转换成为静态文件。
The easy way to catch the html rendered by asp.net is to save a page in the client browser
。
To catch it on the server you have to hook into the (html) rendering process.
Asp.net 生成的HTML页面代码很你可以很容易的在浏览器端得到,但要在服务器端获取HTML 页面代码,你必须跟踪 Asp.net 生成页面的过程。
There is no Page.OnRender event but the Page class does have a Render method. The method has a protected visibility, you cannot invoke it form your code.
在Asp.net 生成页面代码的过程中,没有Page.OnRender 这个事件(你无法在页面生成时执行你自己的代码),但是Page 类有个Render 方法,它是protected型,你无法在在你自己的代码中执行它。
The Render method is invoked when asp.net renders the page to the output, to hook in you override the method.
Render 方法在 Asp.net 在生成页面时被执行,并将HTML代码输出。为了能在这一过程中加入你的代码,你需要重载此方法(Render)。
The Render method has a parameter of type HtmlTextWriter, the default implementation of the overriden Render method is invoking Base.Render passing it the HtmlTextWriter object.
Render 方法有一个
HtmlTextWriter
类型的参数,其默认执行过程是Render将
HtmlTextWriter
对象传递给Base.Render执行
protected
override void Render(HtmlTextWriter writer)
{
// Default behavior
base.Render(writer);
}
{
// Default behavior
base.Render(writer);
}
This gives a place to hook in an own textwriter where asp,net can render the html into.
这里提供了一个机会让我们自己构造的textwriter 提供给asp.net 将生成的HTML代码输入进去。
The constructor of the HTMLtextwriter class has a protected visibility. Passing an own htmltextwriter requires specializing the frameworks's HTMLtextwriter class.
HTMLtextwriter 的构造函数是protected 型的,如果想要将自己的htmltextwriter作为参数传递进去,必须要自己订制一个HTMLtextwriter类。
The constructor of the base class requires a stringwriter. A wrapper class to house both custom HTMLtextwriter and stringwriter:
构造函数的基类是一个stringwriter,我们需要将封装一个类,订制HTMLtextwriter和stringwriter。
internal
class MyHtmlFileCreator
{
private StringWriter html;
private MyHtmlTextWriter htmlWriter;
{
private StringWriter html;
private MyHtmlTextWriter htmlWriter;
// override the HtmlTextWriter to reach the constructor
//
重载
HtmlTextWriter
// the constructor in the base class is protected
class MyHtmlTextWriter : HtmlTextWriter
{
internal MyHtmlTextWriter(TextWriter tw): base(tw){}
}
// the constructor in the base class is protected
class MyHtmlTextWriter : HtmlTextWriter
{
internal MyHtmlTextWriter(TextWriter tw): base(tw){}
}
// publish the HTMLwriter
internal HtmlTextWriter RenderHere
{
get {return htmlWriter;}
}
internal HtmlTextWriter RenderHere
{
get {return htmlWriter;}
}
// constructor initializes stringwriter and htmlwriter based on that
// 构造函数在此初始化 stringwriter 和 htmlwriter
// initialize Url
// 初始化 URL
internal MyHtmlFileCreator()
{
html = new StringWriter();
htmlWriter = new MyHtmlTextWriter(html);
newUrl = Context.Request.Url.AbsolutePath.ToString();
newUrl = newUrl.Replace(".aspx",".htm");
}
// 构造函数在此初始化 stringwriter 和 htmlwriter
// initialize Url
// 初始化 URL
internal MyHtmlFileCreator()
{
html = new StringWriter();
htmlWriter = new MyHtmlTextWriter(html);
newUrl = Context.Request.Url.AbsolutePath.ToString();
newUrl = newUrl.Replace(".aspx",".htm");
}
internal
void WriteHTMLFile(string virtualFileName)
{
// Stringreader reads output rendered by asp.net
// Stringreader 用来读取 asp.net 生成的 HTML 代码
// Stringwriter writes html output file
// Stringwriter 将代码生成文件
StringReader sr = new StringReader(html.ToString());
StringWriter sw = new StringWriter();
{
// Stringreader reads output rendered by asp.net
// Stringreader 用来读取 asp.net 生成的 HTML 代码
// Stringwriter writes html output file
// Stringwriter 将代码生成文件
StringReader sr = new StringReader(html.ToString());
StringWriter sw = new StringWriter();
// Read from input
string htmlLine = sr.ReadLine();
while (htmlLine != null)
{
// Filter out ASp.net specific tags
// 将指定的标签代码过滤
if (! ((htmlLine.IndexOf("<form") > 0) ||
(htmlLine.IndexOf("__VIEWSTATE") > 0) ||
(htmlLine.IndexOf("</form>") > 0) ))
{sw.WriteLine(htmlLine);}
string htmlLine = sr.ReadLine();
while (htmlLine != null)
{
// Filter out ASp.net specific tags
// 将指定的标签代码过滤
if (! ((htmlLine.IndexOf("<form") > 0) ||
(htmlLine.IndexOf("__VIEWSTATE") > 0) ||
(htmlLine.IndexOf("</form>") > 0) ))
{sw.WriteLine(htmlLine);}
htmlLine = sr.ReadLine();
}
}
// Write contents stringwriter to html file
// 输出成为静态文件
StreamWriter fs = new StreamWriter(virtualFileName);
fs.Write(sw.ToString());
fs.Close();
}
// 输出成为静态文件
StreamWriter fs = new StreamWriter(virtualFileName);
fs.Write(sw.ToString());
fs.Close();
}
}
A MyHtmlFileCreator object has a place asp.net can render to and it has a method to write the contents of the stringwriter (inside the htmlwriter) to file.
MyHtmlFileCreator 对象提供一个容器,让Asp.net 可以将HTML 输出。并且MyHtmlFileCreator可以通过其内部的stringwriter 将HTML代码写入到一个文件中。
I use the MyHtmlFileCreator in a page base class. The page hooks in the render event. When the freeze flag is set the html is rendered to a file and the application is redirected to the html file just created.
我将在Page基类中使用MyHtmlFileCreator。该Page将在页面生成时触发render事件,当freeze 信号灯被置为true时,asp.net 生成的HTML代码将根据请求时的URL 生成相应的静态文件(xxx.html),并且程序将用户的请求重定向的到该静态文件的地址去(xxx.html)。
public
class FreezablePage : System.Web.UI.Page
{
internal class MyHtmlFileCreator{}
{
internal class MyHtmlFileCreator{}
// When Asp.Net renders the page the Page.Render method is invoked
// 当 Asp.net 输出结果时, page.render 方法将会被执行
// Override the method to hook in
// 当 Asp.net 输出结果时, page.render 方法将会被执行
// Override the method to hook in
protected
override void Render(HtmlTextWriter writer)
{
if (freeze)
{
MyHtmlFileCreator htmlFile = new MyHtmlFileCreator();
// Let Asp.net render the output, catch it in the file creator
// 使用自己订制的 file creator 捕获 Asp.net 的输出
base .Render(htmlFile.RenderHere);
// Write new html file
// 创建一个 HTML 静态文件
htmlFile.WriteHTMLFile(Server.MapPath(NewUrl));
// Redirect
// 将用户的请求转向到静态文件的地址去
Response.Redirect(NewUrl, true);
}
else
{
// Default behavior
// 默认输出器
base .Render(writer);
}
{
if (freeze)
{
MyHtmlFileCreator htmlFile = new MyHtmlFileCreator();
// Let Asp.net render the output, catch it in the file creator
// 使用自己订制的 file creator 捕获 Asp.net 的输出
base .Render(htmlFile.RenderHere);
// Write new html file
// 创建一个 HTML 静态文件
htmlFile.WriteHTMLFile(Server.MapPath(NewUrl));
// Redirect
// 将用户的请求转向到静态文件的地址去
Response.Redirect(NewUrl, true);
}
else
{
// Default behavior
// 默认输出器
base .Render(writer);
}
}
// Flag render event
protected void Freeze()
{
freeze = true;
}
protected void Freeze()
{
freeze = true;
}
protected
void Freeze(string toUrl)
{
freeze = true;
NewUrl = toUrl;
}
{
freeze = true;
NewUrl = toUrl;
}
private
bool freeze = false;
private
string newUrl;
internal
string NewUrl
{
get
{
return newUrl;
}
set
{
newUrl = value;
}
{
get
{
return newUrl;
}
set
{
newUrl = value;
}
}
}
}
Use this on your page like this :
使用示例:
public class _Default : Gekko.Web.UI.FreezablePage
{
private void Button1_Click(object sender, System.EventArgs e)
{
Freeze(string.Format(@"Statisch/{0}.htm", TextBox1.Text));
}
}
使用示例:
public class _Default : Gekko.Web.UI.FreezablePage
{
private void Button1_Click(object sender, System.EventArgs e)
{
Freeze(string.Format(@"Statisch/{0}.htm", TextBox1.Text));
}
}
You will see that the resulting page has lost its form knocking out al buttons. There are no more postback possible. And the viewstate is also cut away.
你将看到结果页面将不会有任何按钮存在,自然也不会有postback 可能,viewstate 也消失不见了。
To process any file asp.net might produce will require more filtering. You can do quite flexible things treating the input as xml. Which requires some cutting and pasting, an html document is not always well-formed
如果要处理不同的请求和页面,asp.net 自然也需要更多的过滤方法(防止在生成的静态页面中产生form的提交造成服务器交互)。你可以对生成的HTML文件象XML一样做灵活活的处理。但是生成的HTML并不总是标准的(标准的HTML可以作为一个XML进行解析,请参考XHTML标准),你需要自行做一些调整(个人认为涉及的工作量比较大,读者自己权衡)。
你将看到结果页面将不会有任何按钮存在,自然也不会有postback 可能,viewstate 也消失不见了。
To process any file asp.net might produce will require more filtering. You can do quite flexible things treating the input as xml. Which requires some cutting and pasting, an html document is not always well-formed
如果要处理不同的请求和页面,asp.net 自然也需要更多的过滤方法(防止在生成的静态页面中产生form的提交造成服务器交互)。你可以对生成的HTML文件象XML一样做灵活活的处理。但是生成的HTML并不总是标准的(标准的HTML可以作为一个XML进行解析,请参考XHTML标准),你需要自行做一些调整(个人认为涉及的工作量比较大,读者自己权衡)。
这篇文章写得比较早了。我修改了一下,使其可以在framework 2.0 下运行
FreezablePage.cs
FreezablePage.cs
using
System;
using
System.IO;
using
System.Data;
using
System.Configuration;
using
System.Web;
using
System.Web.Security;
using
System.Web.UI;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Web.UI.HtmlControls;
///
<summary>
///
Summary description for FreezablePage
///
</summary>
public
class FreezablePage : System.Web.UI.Page
{
// When Asp.Net renders the page the Page.Render method is invoked
// Override the method to hook in
protected override void Render(HtmlTextWriter writer)
{
if (freeze)
{
MyHtmlFileCreator htmlFile = new MyHtmlFileCreator();
// Let Asp.net render the output, catch it in the file creator
base.Render(htmlFile.RenderHere);
newUrl = HttpContext.Current.Request.Url.AbsolutePath.ToString();
newUrl = newUrl.Replace(".aspx", ".htm");
// Write new html file
htmlFile.WriteHTMLFile(Server.MapPath(newUrl));
// Redirect
Response.Redirect(newUrl, true);
}
else
{
// Default behavior
base.Render(writer);
}
}
// Flag render event
protected void Freeze()
{
freeze = true;
}
protected void Freeze(string toUrl)
{
freeze = true;
NewUrl = toUrl;
}
private bool freeze = false;
private string newUrl;
internal string NewUrl
{
get
{
return newUrl;
}
set
{
newUrl = value;
}
}
}
internal
class MyHtmlFileCreator
{
private StringWriter html;
private MyHtmlTextWriter htmlWriter;
// override the HtmlTextWriter to reach the constructor
// the constructor in the base class is protected
class MyHtmlTextWriter : HtmlTextWriter
{
internal MyHtmlTextWriter(TextWriter tw) : base(tw) { }
}
// publish the HTMLwriter
internal HtmlTextWriter RenderHere
{
get { return htmlWriter; }
}
// constructor initializes stringwriter and htmlwriter based on that
// initialize Url
internal MyHtmlFileCreator()
{
html = new StringWriter();
htmlWriter = new MyHtmlTextWriter(html);
}
internal void WriteHTMLFile(string virtualFileName)
{
// Stringreader reads output rendered by asp.net
// Stringwriter writes html output file
StringReader sr = new StringReader(html.ToString());
StringWriter sw = new StringWriter();
// Read from input
string htmlLine = sr.ReadLine();
while (htmlLine != null)
{
// Filter out ASp.net specific tags
if (!((htmlLine.IndexOf("<form") > 0) ||
(htmlLine.IndexOf("__VIEWSTATE") > 0) ||
(htmlLine.IndexOf("</form>") > 0)))
{ sw.WriteLine(htmlLine); }
htmlLine = sr.ReadLine();
}
// Write contents stringwriter to html file
StreamWriter fs = new StreamWriter(virtualFileName);
fs.Write(sw.ToString());
fs.Close();
}
}
点击下面的链接下载示例网站
http://dl2.csdn.net/down4/20070919/19003848422.rar