Hi,I just want to present you quite interesting approach of ASP.NET utilization. This approach
shows how to render html files using ASP.NET pages and server controls.For instance, we have some amount of read-only data which we want to present to the user with
high level of user interactivity and ability to print. Let's consider html documents and embedded browser as ActiveX control. Here we already have pretty good printing capability and also we can provide users with rich interactivity using JavaScript. If we
could generate html using compiled .aspx pages it would be the best, because we can edit and create web forms in Visual Studio (and also we can use all ASP.NET powerful controls like DataGrid) and then all we have to do is to produce html using generated ASP.NET
page handlers. Flow is as follows: 1. Retrieve data from database2. Bind
ASP.NET page to retrieved data3. Render ASP.NET page to output html stream in order to retrieve necessary documentThe
principle of my solution is as follows: create pseudo-HTTPContext (Request, Response, Response stream) and pseudo-Browser -> omit all unnecessary HTTP Modules and directly assign this context to page handler -> launch lifecycle of the page -> retrieve rendered
html.I've already created proof-of-concept (this proof-of-concept uses simple controls like button with JavaScript as well as complex controls like DataGrid). Everything
works great.I’ve looked through web and haven’t found anything similar. Please, let me know your opinion regarding approach described above.
Could you explain again why you are doing this? Why not just use asp.net? Aren't you just saying in essence: when a page is requested, request it from the server using a server side webclient, send the response to the user? why do the server side web
client when the client's browser would suffice? Is your client a desktop (windows) application? Is your client using a browser. Please explain this further.
--JJ
Please mark as answered if I helped.
I don't answer personal emails unless I know you or of you. Feel free to post in the forum to get an answer from me.
I had a need to do something similar - in my case, it was to generate .html files for a static hosting provider. I set up databound template pages (a Page implementing IDataItemContainer that called this.DataBind OnLoadComplete), and a console app that hosted the
ASP.NET runtime (surprisingly easy to do). The program would query a database, and then for each record, it'd put the data into HttpRuntime.Cache for the page to databind against, and use a SimpleWorkerRequest to get the ASP.NET runtime to generate the necessary
HTML which was saved to disk. It's kind of an ASP.NET html generation system. All in all, it worked - but felt a little clunky. I think using CodeSmith or something would've been easier and more straightforward.
On another project, I needed to email the actual output of a page. I created an IHttpHandler that inserted a MemoryStream into Response.Filter. Then I was able to use the PageParser class to process the request as a normal request, but it'd be adding the
HTML to my MemoryStream. A little RegEx magic to fix up the URL's to be absolute references, and it worked great. Again, though, while it was interesting - I'd probably just do an HTTP request with wget or curl or something, and email the output next time.
;)
The reason is availability offline. We have Windows client which downloads necessary data during synchronization process. It is necessary to display generated document within embeded browser in Windows client application. And using all features of ASP.NET
is quite havy task in my opinion, especially host IIS on client machine.
I had a need to do something similar - in my case, it was to generate .html files for a static hosting provider. I set up databound template pages (a Page implementing IDataItemContainer that called this.DataBind OnLoadComplete), and a console app that hosted the
ASP.NET runtime (surprisingly easy to do). The program would query a database, and then for each record, it'd put the data into HttpRuntime.Cache for the page to databind against, and use a SimpleWorkerRequest to get the ASP.NET runtime to generate the necessary
HTML which was saved to disk. It's kind of an ASP.NET html generation system. All in all, it worked - but felt a little clunky. I think using CodeSmith or something would've been easier and more straightforward.
On another project, I needed to email the actual output of a page. I created an IHttpHandler that inserted a MemoryStream into Response.Filter. Then I was able to use the PageParser class to process the request as a normal request, but it'd be adding the
HTML to my MemoryStream. A little RegEx magic to fix up the URL's to be absolute references, and it worked great. Again, though, while it was interesting - I'd probably just do an HTTP request with wget or curl or something, and email the output next time.
;)
This is quite interesting... Could you please explain second "e-mail" approach in more details?
This is quite interesting... Could you please explain second "e-mail" approach in more details?
This is the reusable code for the actual handler. It takes care of inserting the CaptureStream into the Response.Filter, loading the page, and fixing up the url's to be absolute links. It fires events when it creates the page (to allow modification of the
page), right before emailing the message (to add recipients, or change the body, etc.), and after emailing (to report an error sending). It must be constructed with the url to email.
Imports System
Imports System.IO
Imports System.Web
Imports System.Web.UI
Imports System.Text.RegularExpressions
Imports System.Net.Mail
Public Class EmailPageHandler
Implements IHttpHandler
Private Class CaptureStream
Inherits MemoryStream
Private _sink As Stream
Public Sub New(ByVal sink As Stream)
MyBase.New()
_sink = sink
End Sub
Public Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer)
MyBase.Write(buffer, offset, count)
_sink.Write(buffer, offset, count)
End Sub
End Class
Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable
Get
Return False
End Get
End Property
Protected Property PageUrl() As String
Get
Return _pageUrl
End Get
Set(ByVal value As String)
_pageUrl = value
End Set
End Property
Private _pageUrl As String
Public Sub New(ByVal pageUrl As String)
Me.PageUrl = pageUrl
End Sub
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim pageParser As New PageParser()
Dim pageHandler As IHttpHandler = pageParser.GetCompiledPageInstance(PageUrl, Nothing, context)
Using page As Page = CType(pageHandler, Page)
OnPageCreated(New EmailPageCreatedEventArgs(page))
Using cs As New CaptureStream(context.Response.Filter)
context.Response.Filter = cs
pageHandler.ProcessRequest(context)
context.Response.Flush()
Using sr As New StreamReader(cs)
cs.Seek(0, SeekOrigin.Begin)
SendMail(sr.ReadToEnd(), page.ResolveUrl("~"), page.Title)
End Using
End Using
End Using
End Sub
Private Sub SendMail(ByVal html As String, ByVal appRoot As String, ByVal pageTitle As String)
Using msg As New MailMessage()
msg.Subject = pageTitle
msg.IsBodyHtml = True
msg.Body = MakeAbsolute(html, appRoot)
Dim e As New EmailPageEventArgs(msg)
OnEmailing(e)
Dim smtp As New SmtpClient()
Try
smtp.Send(msg)
Catch ex As Exception
e.Exception = ex
End Try
OnEmailed(e)
End Using
End Sub
Private Function MakeAbsolute(ByVal s As String, ByVal appRoot As String) As String
Const SEARCH_REGEX As String = "(<\s*(a|img|link|script|form)\s+[^>]*(href|src|action)\s*=\s*[""'])(?!http)([^""'>]+[""'>])"
Dim uriB As New UriBuilder(HttpContext.Current.Request.Url)
uriB.Query = Nothing
uriB.Path = appRoot
uriB.Fragment = Nothing
Dim replacement As String = "$1" & uriB.Uri.ToString() & "$4"
Return Regex.Replace(s, SEARCH_REGEX, replacement, RegexOptions.Compiled)
End Function
Protected Overridable Sub OnPageCreated(ByVal e As EmailPageCreatedEventArgs)
RaiseEvent PageCreated(Me, e)
End Sub
Protected Overridable Sub OnEmailing(ByVal e As EmailPageEventArgs)
RaiseEvent Emailing(Me, e)
End Sub
Protected Overridable Sub OnEmailed(ByVal e As EmailPageEventArgs)
RaiseEvent Emailed(Me, e)
End Sub
Public Event PageCreated As EventHandler(Of EmailPageCreatedEventArgs)
Public Event Emailing As EventHandler(Of EmailPageEventArgs)
Public Event Emailed As EventHandler(Of EmailPageEventArgs)
End Class
Public Class EmailPageCreatedEventArgs
Inherits EventArgs
Public ReadOnly Property Page() As Page
Get
Return _page
End Get
End Property
Private _page As Page
Public Sub New(ByVal page As Page)
_page = page
End Sub
End Class
Public Class EmailPageEventArgs
Inherits ComponentModel.CancelEventArgs
Public ReadOnly Property MailMessage() As MailMessage
Get
Return _mailMessage
End Get
End Property
Private _mailMessage As MailMessage
Public Property Exception() As Exception
Get
Return _exception
End Get
Friend Set(ByVal value As Exception)
_exception = value
End Set
End Property
Private _exception As Exception
Public Sub New(ByVal mailMessage As MailMessage, ByVal exception As Exception)
_mailMessage = mailMessage
_exception = exception
End Sub
Public Sub New(ByVal mailMessage As MailMessage)
Me.New(mailMessage, Nothing)
End Sub
End Class
To use the class, I set up another HttpHandler to check for security, etc., manipulate the page (certain things are left out of the email), and set the addresses. Something like (not actual code, but it'll give you an idea):
<%@ WebHandler Language="VB"Class="EmailPage" %>
Public Class EmailPage
Implements IHttpHandler
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return True
End Get
End Property
Private _recipients as String
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
If Not Security.AllowHostToEmail(context.Request.UserHostAddress) < 0 Then
context.Response.StatusCode = 403
context.Response.End()
Return
End If
Dim pageUrl as String = "~/" & context.Request.QueryString("pageUrl")
_recipients = context.Request.QueryString("emailTo")
Dim emailHandler As New EmailPageHandler(pageUrl)
AddHandler emailHandler.PageCreated, AddressOf EmailHandler_PageCreated
AddHandler emailHandler.Emailing, AddressOf EmailHandler_Emailing
AddHandler emailHandler.Emailed, AddressOf EmailHandler_Emailed
emailHandler.ProcessRequest(context)
End Sub
Private Sub EmailHandler_PageCreated(ByVal sender As Object, ByVal e As EmailPageCreatedEventArgs)
AddHandler e.Page.Init, AddressOf Page_Init
End Sub
Private Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs)
Dim page As Page = CType(sender, Page)
Dim ph As PlaceHolder = CType(page.Master.FindControl("phDontShowOnEmail"), PlaceHolder)
ph.Visible = False
Dim btnEmail As Button = CType(page.Master.FindControl("phMainContent$btnEmail"), Button)
btnEmail.Visible = False
End Sub
Private Sub EmailHandler_Emailing(ByVal sender As Object, ByVal e As EmailPageEventArgs)
e.MailMessage.To.Add(_recipients)
End Sub
Private Sub EmailHandler_Emailed(ByVal sender As Object, ByVal e As EmailPageEventArgs)
If (e.Exception IsNot Nothing) Then
Throw New Exception("Could not send email", e.Exception)
End If
End Sub
End Class
Vitaliy Lipt...
Member
12 Points
9 Posts
Quite interesting ASP.NET utilization
Jan 02, 2008 04:49 PM|LINK
ASP.NET 2.0 reports
jose_jimenez
Contributor
2504 Points
484 Posts
Re: Quite interesting ASP.NET utilization
Jan 02, 2008 05:52 PM|LINK
Could you explain again why you are doing this? Why not just use asp.net? Aren't you just saying in essence: when a page is requested, request it from the server using a server side webclient, send the response to the user? why do the server side web client when the client's browser would suffice? Is your client a desktop (windows) application? Is your client using a browser. Please explain this further.
--JJ
I don't answer personal emails unless I know you or of you. Feel free to post in the forum to get an answer from me.
brackett@ufl...
Member
204 Points
52 Posts
Re: Quite interesting ASP.NET utilization
Jan 02, 2008 08:32 PM|LINK
I had a need to do something similar - in my case, it was to generate .html files for a static hosting provider. I set up databound template pages (a Page implementing IDataItemContainer that called this.DataBind OnLoadComplete), and a console app that hosted the ASP.NET runtime (surprisingly easy to do). The program would query a database, and then for each record, it'd put the data into HttpRuntime.Cache for the page to databind against, and use a SimpleWorkerRequest to get the ASP.NET runtime to generate the necessary HTML which was saved to disk. It's kind of an ASP.NET html generation system. All in all, it worked - but felt a little clunky. I think using CodeSmith or something would've been easier and more straightforward.
On another project, I needed to email the actual output of a page. I created an IHttpHandler that inserted a MemoryStream into Response.Filter. Then I was able to use the PageParser class to process the request as a normal request, but it'd be adding the HTML to my MemoryStream. A little RegEx magic to fix up the URL's to be absolute references, and it worked great. Again, though, while it was interesting - I'd probably just do an HTTP request with wget or curl or something, and email the output next time. ;)
Vitaliy Lipt...
Member
12 Points
9 Posts
Re: Quite interesting ASP.NET utilization
Jan 03, 2008 08:56 AM|LINK
The reason is availability offline. We have Windows client which downloads necessary data during synchronization process. It is necessary to display generated document within embeded browser in Windows client application. And using all features of ASP.NET is quite havy task in my opinion, especially host IIS on client machine.
Vitaliy Lipt...
Member
12 Points
9 Posts
Re: Quite interesting ASP.NET utilization
Jan 03, 2008 09:06 AM|LINK
This is quite interesting... Could you please explain second "e-mail" approach in more details?
brackett@ufl...
Member
204 Points
52 Posts
Re: Quite interesting ASP.NET utilization
Jan 03, 2008 02:31 PM|LINK
This is the reusable code for the actual handler. It takes care of inserting the CaptureStream into the Response.Filter, loading the page, and fixing up the url's to be absolute links. It fires events when it creates the page (to allow modification of the page), right before emailing the message (to add recipients, or change the body, etc.), and after emailing (to report an error sending). It must be constructed with the url to email.
Imports System Imports System.IO Imports System.Web Imports System.Web.UI Imports System.Text.RegularExpressions Imports System.Net.Mail Public Class EmailPageHandler Implements IHttpHandler Private Class CaptureStream Inherits MemoryStream Private _sink As Stream Public Sub New(ByVal sink As Stream) MyBase.New() _sink = sink End Sub Public Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) MyBase.Write(buffer, offset, count) _sink.Write(buffer, offset, count) End Sub End Class Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable Get Return False End Get End Property Protected Property PageUrl() As String Get Return _pageUrl End Get Set(ByVal value As String) _pageUrl = value End Set End Property Private _pageUrl As String Public Sub New(ByVal pageUrl As String) Me.PageUrl = pageUrl End Sub Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest Dim pageParser As New PageParser() Dim pageHandler As IHttpHandler = pageParser.GetCompiledPageInstance(PageUrl, Nothing, context) Using page As Page = CType(pageHandler, Page) OnPageCreated(New EmailPageCreatedEventArgs(page)) Using cs As New CaptureStream(context.Response.Filter) context.Response.Filter = cs pageHandler.ProcessRequest(context) context.Response.Flush() Using sr As New StreamReader(cs) cs.Seek(0, SeekOrigin.Begin) SendMail(sr.ReadToEnd(), page.ResolveUrl("~"), page.Title) End Using End Using End Using End Sub Private Sub SendMail(ByVal html As String, ByVal appRoot As String, ByVal pageTitle As String) Using msg As New MailMessage() msg.Subject = pageTitle msg.IsBodyHtml = True msg.Body = MakeAbsolute(html, appRoot) Dim e As New EmailPageEventArgs(msg) OnEmailing(e) Dim smtp As New SmtpClient() Try smtp.Send(msg) Catch ex As Exception e.Exception = ex End Try OnEmailed(e) End Using End Sub Private Function MakeAbsolute(ByVal s As String, ByVal appRoot As String) As String Const SEARCH_REGEX As String = "(<\s*(a|img|link|script|form)\s+[^>]*(href|src|action)\s*=\s*[""'])(?!http)([^""'>]+[""'>])" Dim uriB As New UriBuilder(HttpContext.Current.Request.Url) uriB.Query = Nothing uriB.Path = appRoot uriB.Fragment = Nothing Dim replacement As String = "$1" & uriB.Uri.ToString() & "$4" Return Regex.Replace(s, SEARCH_REGEX, replacement, RegexOptions.Compiled) End Function Protected Overridable Sub OnPageCreated(ByVal e As EmailPageCreatedEventArgs) RaiseEvent PageCreated(Me, e) End Sub Protected Overridable Sub OnEmailing(ByVal e As EmailPageEventArgs) RaiseEvent Emailing(Me, e) End Sub Protected Overridable Sub OnEmailed(ByVal e As EmailPageEventArgs) RaiseEvent Emailed(Me, e) End Sub Public Event PageCreated As EventHandler(Of EmailPageCreatedEventArgs) Public Event Emailing As EventHandler(Of EmailPageEventArgs) Public Event Emailed As EventHandler(Of EmailPageEventArgs) End Class Public Class EmailPageCreatedEventArgs Inherits EventArgs Public ReadOnly Property Page() As Page Get Return _page End Get End Property Private _page As Page Public Sub New(ByVal page As Page) _page = page End Sub End Class Public Class EmailPageEventArgs Inherits ComponentModel.CancelEventArgs Public ReadOnly Property MailMessage() As MailMessage Get Return _mailMessage End Get End Property Private _mailMessage As MailMessage Public Property Exception() As Exception Get Return _exception End Get Friend Set(ByVal value As Exception) _exception = value End Set End Property Private _exception As Exception Public Sub New(ByVal mailMessage As MailMessage, ByVal exception As Exception) _mailMessage = mailMessage _exception = exception End Sub Public Sub New(ByVal mailMessage As MailMessage) Me.New(mailMessage, Nothing) End Sub End ClassTo use the class, I set up another HttpHandler to check for security, etc., manipulate the page (certain things are left out of the email), and set the addresses. Something like (not actual code, but it'll give you an idea):