I've gotten some requests for sample code showing how to get the resulting html for a EPiServer PageData so I thought I would share some ideas I had around this.
The question is: "I have got a PageData instance. Now how can I (server side) get the html code that would be rendered for it?"
Well, you can of course just initiate a web request to the local server using the Page's url and simply capture the retuened html response stream into a string and use it. But, the downside with this approach, besides it's performance overhead, is that it requires (and occupies) one additional request pipe in the server.
So, I came up with a (somewhat 'hackyish') way to shortcut the request by getting the IHttpHandler directly and then let it execute its ProcessRequest() using a dummy web request.
I've put up a sample VS2008 web application project for you at http://labs.episerver.com/PageFiles/111555/SampleGetPageHtml.zip
(should work with VS2005 as well)
Unzip to your EPiServer CMS5 web root folder and open the project in the SampleGetPageHtml folder.
Compile the project and then request the url "/YourServer/SampleGetPageHtml/Sample.aspx" should show you this page:
Some notes on the code.
All interesting stuff (as you'll see in the project) really happens in the Sample.GetHtmlForPage() method (Sample.aspx.cs:36).
First, we get the url for the requested page (pageData.LinkURL) and then we chop that up into a virtualPath and a queryString:
1: PageData pageData = DataFactory.Instance.GetPage(pageRef);
2:
3: // get page's RelativeUrl
4: string pageRelativeUrl = pageData.LinkURL;
5:
6: // split relative url into parts consisting of path and querystring
7: pageVirtualPath = pageRelativeUrl;
8:
9: int index;
10: if(0 < (index = pageRelativeUrl.IndexOf('?'))) 11: { 12: pageVirtualPath = pageRelativeUrl.Substring(0, index);
13: queryString = pageRelativeUrl.Substring(index+1);
14: }
15:
16: // remove the initial RootDir from pageVirtualPath
17: string siteRoot = EPiServer.Configuration.Settings.Instance.SiteUrl.LocalPath;
18: if(0 == pageVirtualPath.IndexOf(siteRoot))
19: { 20: pageVirtualPath = pageVirtualPath.Remove(0, siteRoot.Length);
21: }
Then, we split the virtualPath into a folderPath and a filename:
1: // split path into "path" and "page",
2: // example:
3: // "templates/test/default.aspx" => "templates/test" and "default.aspx"
4: page = pageVirtualPath;
5: if(0 < (index = pageVirtualPath.LastIndexOf('/'))) 6: { 7: path = pageVirtualPath.Substring(0, index+1);
8: page = pageVirtualPath.Substring(index+1);
9: }
Now that we've extracted the needed paths we instantiate a DummyWorkerRequest, and then use it to construct a new HttpContext which replaces the current one. We save away the original HttpContext.Current so we can restore later on.
(This DummyWorkerReques simply captures all output from the httphandler to a TextWriter which we later can use to get the rendered html)
1: // create a workerrequest to write the output to a stringwriter
2: StringWriter writer = new StringWriter();
3: // DummyWorkerRequest wRequest = new DummyWorkerRequest(Global.BaseDirectory.Replace('/', '\\'), Global.EPConfig.RootDir, pageVirtualPath, queryString, writer); 4: string applicationPhysicalPath = HttpContext.Current.Request.PhysicalApplicationPath;
5:
6: DummyWorkerRequest wRequest = new DummyWorkerRequest( applicationPhysicalPath, siteRoot, pageVirtualPath, queryString, writer);
7:
8: // save old HttpContext
9: HttpContext oldContext = HttpContext.Current;
10:
11: // create a HttpContext to be used by pageprocessing
12: HttpContext.Current = new HttpContext(wRequest);
13: HttpContext.Current.User = oldContext.User;
Now we are done setting up the "environment" and can finally request the appropriate IHttpHandler from the ASP.Net framework. This is done in a call to PageParsers.GetCompiledPageInstance(), and the returned IHttpHandler is then first assigned to HttpContext.Current.Handler and then we call its ProcessRequest() to let it process as it normally would do.
1: // get physical path from the virtual path
2: // NOTE: virtual path must be relative to the "working" folder of the reqest
3: string pagePhysicalPath = HttpContext.Current.Server.MapPath(page);
4:
5: // get a pagehandler for the requested page
6: HttpContext.Current.Handler = PageParser.GetCompiledPageInstance(pageVirtualPath, pagePhysicalPath, HttpContext.Current);
7:
8: string capturedHtml="";
9:
10: // let pagehandler process the request
11: try
12: { 13: // let handler process and generate its html
14: HttpContext.Current.Handler.ProcessRequest(HttpContext.Current);
15:
16: // important to flush Response stream before trying to get the underlying stringbuilder
17: HttpContext.Current.Response.Flush();
18:
19: // extract the captured html from stringwriter
20: capturedHtml = writer.GetStringBuilder().ToString();
21: }
22: catch(Exception ex)
23: { 24: // simply rethrow
25: throw ex;
26: }
And, thats really all there is to it.
We can nowuse the rendered html (stored in variable capturedHtml) and use it as we find appropriate.
The one caveat for the moment is that all url's rendered in the htmlcode will not be of "friendly type" but rendered in the "old" default.aspx?id=3 way.
Regards,
Johan Olofsson