Determine size of page via HttpModule - asp.net

Here's an easy one for you:
I'm currently logging request duration via an HttpModule and I'd like to find out the number of bytes each page is as well.
HttpContext.Current.Response.OutputStream.Length throws a NotSupportedException.
What's an easy way to do this?

I have an HttpModule that implements a stream rewriter. It derives from the Stream class. In my HttpModule I have the following code:
void app_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpResponse response = HttpContext.Current.Response;
response.Filter = new MyRewriterStream(response.Filter);
}
In the stream class I have the following code that overrides the default Write method:
public override void Write(byte[] buffer, int offset, int count)
{
string outStr;
outStr = UTF8Encoding.UTF8.GetString(buffer, offset, count);
//Do useful stuff and write back to the stream
}
You can just take the length of the string at the second point

Related

ASP.NET Response.Filter

I need to create filter that replace tags <h2> in the HTML to <h3>:
My filter
public class TagsFilter:Stream
{
HttpContext qwe;
public TagsFilter(HttpContext myContext)
{
qwe = myContext;
}
public override void Write(byte[] buffer, int offset, int count)
{
string html = System.Text.Encoding.UTF8.GetString(buffer);
html = html.Replace("<h2>", "<h3>");
qwe.Response.Write(html.ToCharArray(), 0, html.ToCharArray().Length);
}
My module
public class TagsChanger : IHttpModule
{
public void Init(HttpApplication context)
{
context.Response.Filter = new TagsFilter(context.Context);
}
I get error System.Web.HttpException:In this context, the answer is not available.
Look at Rick Strahl's post about "Capturing and Transforming ASP.NET Output with Response.Filter".
Response.Filter content is chunked. So to implement a Response.Filter effectively requires only that you implement a custom stream and handle the Write() method to capture Response output as it’s written. At first blush this seems very simple – you capture the output in Write, transform it and write out the transformed content in one pass. And that indeed works for small amounts of content. But you see, the problem is that output is written in small buffer chunks (a little less than 16k it appears) rather than just a single Write() statement into the stream, which makes perfect sense for ASP.NET to stream data back to IIS in smaller chunks to minimize memory usage en route.
Unfortunately this also makes it a more difficult to implement any filtering routines since you don’t directly get access to all of the response content which is problematic especially if those filtering routines require you to look at the ENTIRE response in order to transform or capture the output as is needed for the solution the gentleman in my session asked for.
So in order to address this a slightly different approach is required that basically captures all the Write() buffers passed into a cached stream and then making the stream available only when it’s complete and ready to be flushed.
As I was thinking about the implementation I also started thinking about the few instances when I’ve used Response.Filter implementations. Each time I had to create a new Stream subclass and create my custom functionality but in the end each implementation did the same thing – capturing output and transforming it. I thought there should be an easier way to do this by creating a re-usable Stream class that can handle stream transformations that are common to Response.Filter implementations.
Rick Strahl wrote own implementation of stream filter that permits text replacing in right way.
I did a small example. I think you have to access the original stream, rather than accessing the httpContext.
public class ReplacementStream : Stream
{
private Stream stream;
private StreamWriter streamWriter;
public ReplacementStream(Stream stm)
{
stream = stm;
streamWriter = new StreamWriter(stream, System.Text.Encoding.UTF8);
}
public override void Write(byte[] buffer, int offset, int count)
{
string html = System.Text.Encoding.UTF8.GetString(buffer);
html = html.Replace("<h2>", "<h3>");
streamWriter.Write(html.ToCharArray(), 0, html.ToCharArray().Length);
streamWriter.Flush();
}
// all other necessary overrides go here ...
}
public class FilterModule : IHttpModule
{
public String ModuleName
{
// Verweis auf Name in Web.config bei Modul-Registrierung
get { return "FilterModule"; }
}
void context_BeginRequest(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
context.Response.Filter = new ReplacementStream(context.Response.Filter);
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
}
Found the solution at this post on SO. Worked for me.
The problem is that you are applying the filter in the Init event, which only occurs once per application instance (it is essentially close to App_Start).
What you need to do is hook in the BeginRequest event from the Init event, and then apply the filter on BeginRequest.
public void Init(HttpApplication application)
{
application.BeginRequest += BeginRequest;
}
private void BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
var context = app.Context;
context.Response.Filter = new TagsFilter(context);
}

Call the default asp.net HttpHandler from a custom handler

I'm adding ASP.NET routing to an older webforms app. I'm using a custom HttpHandler to process everything. In some situations I would like to map a particular path back to an aspx file, so I need to just pass control back to the default HttpHandler for asp.net.
The closest I've gotten is this
public void ProcessRequest(HttpContext context) {
// .. when we decide to pass it on
var handler = new System.Web.UI.Page();
handler.ProcessRequest(context);
MemoryStream steam = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
handler.RenderControl(htmlWriter);
// write headers, etc. & send stream to Response
}
It doesn't do anything, there's nothing output to the stream. MS's documentation for System.Web.UI.Page (as an IHttpHandler) say something to the effect of "do not call the ProcessRequest method. It's for internal use."
From looking around it seems like you can do this with MVC, e.g. : MvcHttpHandler doesn't seem to implement IHttpHandler
There is also this thing System.Web.UI.PageHandlerFactory which appears that it would just produce a Page handler for an aspx file, but it's internal and I can't use it directly.
This page: http://msdn.microsoft.com/en-us/library/bb398986.aspx refers to the "default asp.net handler" but does not identify a class or give any indication how one might use it.
Any ideas on how I can do this? Is it possible?
Persistence pays off! This actually works, and since this information seems to be available pretty much nowhere I thought I'd answer my own question. Thanks to Robert for this post on instantiating things with internal constructors, this is the key.
http://www.rvenables.com/2009/08/instantiating-classes-with-internal-constructors/
public void ProcessRequest(HttpContext context) {
// the internal constructor doesn't do anything but prevent you from instantiating
// the factory, so we can skip it.
PageHandlerFactory factory =
(PageHandlerFactory)System.Runtime.Serialization.FormatterServices
.GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));
string newTarget = "default.aspx";
string newQueryString = // whatever you want
string oldQueryString = context.Request.QueryString.ToString();
string queryString = newQueryString + oldQueryString!="" ?
"&" + newQueryString :
"";
// the 3rd parameter must be just the file name.
// the 4th parameter should be the physical path to the file, though it also
// works fine if you pass an empty string - perhaps that's only to override
// the usual presentation based on the path?
var handler = factory.GetHandler(context, "GET",newTarget,
context.Request.MapPath(context,newTarget));
// Update the context object as it should appear to your page/app, and
// assign your new handler.
context.RewritePath(newTarget , "", queryString);
context.Handler = handler;
// .. and done
handler.ProcessRequest(context);
}
... and like some small miracle, an aspx page processes & renders completely in-process without the need to redirect.
I expect this will only work in IIS7.
I'm you're using Routing in webforms you should be able to just add an ignore route for the specific .aspx files you want. This will then be handled by the default HttpHandler.
http://msdn.microsoft.com/en-us/library/dd505203.aspx
Another option is to invert the logic by handling the cases in which you do NOT want to return the default response and remap the others to your own IHttpHandler. Whenever myCondition is false, the response will be the "default". The switch is implemented as an IHttpModule:
public class SwitchModule: IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAuthenticateRequest += app_PostAuthenticateRequest;
}
void app_PostAuthenticateRequest(object sender, EventArgs e)
{
// Check for whatever condition you like
if (true)
HttpContext.Current.RemapHandler(new CustomHandler());
}
public void Dispose()
}
internal class CustomHandler: IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.Write("hallo");
}
public bool IsReusable { get; }
}

Example of Asynchronous page processing in ASP.net webforms (.NET 2.0)

Can someone provide me with a simple example of Asynchronous page processing in ASP.NET Webforms 2.0 (I'm using VS 2010, so new syntax like lambdas are ok)?
I have some long running requests that I don't want tying up IIS threads.
For simplicity's sake, let's say my current code looks like this:
protected void Page_Load(object sender, EventArgs e)
{
string param1 = _txtParam1.Text;
string param2 = _txtParam2.Text;
//This takes a long time (relative to a web request)
List<MyEntity> entities = _myRepository.GetEntities(param1, param2);
//Conceptually, I would like IIS to bring up a new thread here so that I can
//display the data after it has come back.
DoStuffWithEntities(entities);
}
How can I modify this code so that it is asynchronous? Let's assume that I already set async="true" in the aspx page.
EDIT
I think I figured out how to get what I'm looking for. I've put the example code in an answer here. Feel free to point out any flaws or changes that can be made.
I asked some folks on the ASP.NET team. Here's their emailed response to me, and now, to you.
All that code ends up doing is spinning up a new thread and performing delegate invocation on that thread. So now there are two threads running: the request thread and the new thread. Hence this sample actually has worse performance than the original synchronous code would have had.
See http://www.asp.net/web-forms/tutorials/aspnet-45/using-asynchronous-methods-in-aspnet-45 for a sample on how to write and consume async methods in ASP.NET.
Here is a simple example of asynchronous processing.
protected void Page_Load(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
ThreadPool.QueueUserWorkItem(state => Dokimes_Programming_multithread_QueryWorkThead.ThreadProc2());
Debug.Write("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
// Thread.Sleep(4000);
txtDebug.Text += "ended";
Debug.Write("end.");
}
// This thread procedure performs the task.
static void ThreadProc(Object stateInfo)
{
// No state object was passed to QueueUserWorkItem, so stateInfo is null.
Debug.Write(" Hello from the thread pool 1.");
}
static void ThreadProc2()
{
// No state object was passed to QueueUserWorkItem, so stateInfo is null.
Debug.Write("Hello from the thread pool 2.");
}
Other way
You can use the PageAsyncTask, see here a full example:
http://msdn.microsoft.com/en-us/library/system.web.ui.pageasynctask.aspx
Something like
clAsynCustomObject oAsynRun = new clAsynCustomObject();
PageAsyncTask asyncTask = new PageAsyncTask(oAsynRun.OnBegin, oAsynRun.OnEnd, oAsynRun.OnTimeout, null, true);
Page.RegisterAsyncTask(asyncTask);
Page.ExecuteRegisteredAsyncTasks();
I think I discovered how to do what I wanted to accomplish... though it may not be the best way, feel free to chime in.
At the time of writing there was only one answer in this thread, by Aristos. While he gave an example of executing an asynchronous request, what I wanted was a way to tell ASP.NET to execute some long running method, release the IIS thread so it can be available to service other requests, and then come back when the method finished.
Here's what I came up with, using the same (or similar) example in the question:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Web.UI;
namespace WebApplication2
{
public class MyEntity
{
public string Name { get; set; }
}
public class MyRepository
{
public List<MyEntity> GetEntities(string param1, string param2)
{
Thread.Sleep(10000);
return new List<MyEntity> {new MyEntity {Name = "John Smith"}};
}
}
public partial class Default : Page
{
private readonly MyRepository _myRepository = new MyRepository();
private List<MyEntity> _myEntities;
protected void Page_Load(object sender, EventArgs e)
{
}
private void DoStuffWithEntities()
{
Response.Write("<br/><br/><b>" + _myEntities[0].Name + "</b><br/><br/>");
}
protected void _btnProcess_Click(object sender, EventArgs e)
{
AddOnPreRenderCompleteAsync(BeginExecution, EndExecution, null);
}
private void GetEntities()
{
string param1 = _txtParam1.Text;
string param2 = _txtParam2.Text;
//This takes a long time (relative to a web request)
_myEntities = _myRepository.GetEntities(param1, param2);
}
private IAsyncResult BeginExecution(object sender, EventArgs e, AsyncCallback callback, object state)
{
var t = new ThreadStart(GetEntities);
return t.BeginInvoke(callback, null);
}
private void EndExecution(IAsyncResult result)
{
//Conceptually, I would like IIS to bring up a new thread here so that I can
//display the data after it has come back.
DoStuffWithEntities();
}
}
}

Json HTTP Module stream issue

I have an HTTP Module that I use to clean up the JSON returned by my web service (see http://www.codeproject.com/KB/webservices/ASPNET_JSONP.aspx?msg=3400287#xx3400287xx for an example of this.) Basically it relates to calling cross-domain JSON web services from javascript.
There is this JsonHttpModule which uses a JsonResponseFilter Stream class to write out the JSON and the overloaded Write method is supposed to wrap the name of the callback function around the JSON, otherwise the JSON errors out as needing a label. However, if the JSON is really long, the Write method in the Stream class is called multiple times, causing the callback function to incorrectly get inserted midway through the JSON. Is there a way in the Stream class to wrap the callback function around the stream at the end or to specify that it write all of the JSON in 1 Write method instead of in chunks??
Here's where it calls the JsonResponseFilter in the JsonHttpModule:
public void OnReleaseRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
if (!_Apply(app.Context.Request)) return;
// apply response filter to conform to JSONP
app.Context.Response.Filter =
new JsonResponseFilter(app.Context.Response.Filter, app.Context);
}
Here's the Write method in the JsonResponseFilter Stream class that gets called multiple times:
public override void Write(byte[] buffer, int offset, int count)
{
var b1 = Encoding.UTF8.GetBytes(_context.Request.Params["callback"] + "(");
_responseStream.Write(b1, 0, b1.Length);
_responseStream.Write(buffer, offset, count);
var b2 = Encoding.UTF8.GetBytes(");");
_responseStream.Write(b2, 0, b2.Length);
}
Thanks for any help!
Justin
The reason it fires the method multiple times is because it will buffer the contents and then send it to the output stream. Here is an example which shows how to create ViewState mover HttpModule. You can get some idea from the implementation. Scroll down to the bottom and see the result.
http://www.highoncoding.com/Articles/464_Filtering_Responses_Using_ASP_NET_Response_Filters.aspx
Another solution is to write ResponseStream in Flush method. Like in this example.
I modified JsonHttpModules Flush method and used StringBuilder to store stream in Write method like Justin.
/// <summary>
/// Override flush by writing out the cached stream data
/// </summary>
public override void Flush()
{
if (_sb.Length > 0)
{
string message = _context.Request.Params["callback"] + "(" + _sb.ToString() + ");";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(message);
_responseStream.Write(buffer, 0, buffer.Length);
}
// default flush behavior
_responseStream.Flush();
}
public override void Write(byte[] buffer, int offset, int count)
{
string json = System.Text.Encoding.UTF8.GetString(buffer, offset, count);
_sb.Append(json);
}
This way you don't have to try to guess the end of incoming stream.

Globally log exceptions from ASP.NET [ScriptService] services

I'm using the [System.Web.Script.Services.ScriptService] tag to use web services callable from client side javascript. What I need is a way of globally logging any unhandled exceptions in those methods. On the client side, I get the error callback and can proceed from there, but I need a server-side catch to log the exception.
The guy at this url:
http://ayende.com/Blog/archive/2008/01/06/ASP.Net-Ajax-Error-Handling-and-WTF.aspx
suggests that this can't be done.
Is that accurate? Do I seriously have to go to every single webmethod in the entire system and try/catch the method as a whole.
You can use an HTTP module to capture the exception message, stack trace and exception type that is thrown by the web service method.
First some background...
If a web service method throws an exception the HTTP response has a status code of 500.
If custom errors are off then the web
service will return the exception
message and stack trace to the client
as JSON. For example:{"Message":"Exception
message","StackTrace":" at
WebApplication.HelloService.HelloWorld()
in C:\Projects\Stackoverflow
Examples\WebApplication\WebApplication\HelloService.asmx.cs:line
22","ExceptionType":"System.ApplicationException"}
When custom errors are on then the
web service returns a default message
to the client and removes the stack
trace and exception type:{"Message":"There was an error processing the request.","StackTrace":"","ExceptionType":""}
So what we need to do is set custom errors off for the web service and plug in an HTTP module that:
Checks if the request is for a web service method
Checks if an exception was thrown - that is, a status code of 500 is being returned
If 1) and 2) are true then get the original JSON that would be sent to the client and replace it with the default JSON
The code below is an example of an HTTP module that does this:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
public class ErrorHandlerModule : IHttpModule {
public void Init(HttpApplication context) {
context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
context.EndRequest += OnEndRequest;
}
static void OnPostRequestHandlerExecute(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
// TODO: Update with the correct check for your application
if (context.Request.Path.StartsWith("/HelloService.asmx")
&& context.Response.StatusCode == 500) {
context.Response.Filter =
new ErrorHandlerFilter(context.Response.Filter);
context.EndRequest += OnEndRequest;
}
}
static void OnEndRequest(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
ErrorHandlerFilter errorHandlerFilter =
context.Response.Filter as ErrorHandlerFilter;
if (errorHandlerFilter == null) {
return;
}
string originalContent =
Encoding.UTF8.GetString(
errorHandlerFilter.OriginalBytesWritten.ToArray());
// If customErrors are Off then originalContent will contain JSON with
// the original exception message, stack trace and exception type.
// TODO: log the exception
}
public void Dispose() { }
}
This module uses the following filter to override the content sent to the client and to store the original bytes (which contain the exception message, stack trace and exception type):
public class ErrorHandlerFilter : Stream {
private readonly Stream _responseFilter;
public List OriginalBytesWritten { get; private set; }
private const string Content =
"{\"Message\":\"There was an error processing the request.\"" +
",\"StackTrace\":\"\",\"ExceptionType\":\"\"}";
public ErrorHandlerFilter(Stream responseFilter) {
_responseFilter = responseFilter;
OriginalBytesWritten = new List();
}
public override void Flush() {
byte[] bytes = Encoding.UTF8.GetBytes(Content);
_responseFilter.Write(bytes, 0, bytes.Length);
_responseFilter.Flush();
}
public override long Seek(long offset, SeekOrigin origin) {
return _responseFilter.Seek(offset, origin);
}
public override void SetLength(long value) {
_responseFilter.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count) {
return _responseFilter.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count) {
for (int i = offset; i < offset + count; i++) {
OriginalBytesWritten.Add(buffer[i]);
}
}
public override bool CanRead {
get { return _responseFilter.CanRead; }
}
public override bool CanSeek {
get { return _responseFilter.CanSeek; }
}
public override bool CanWrite {
get { return _responseFilter.CanWrite; }
}
public override long Length {
get { return _responseFilter.Length; }
}
public override long Position {
get { return _responseFilter.Position; }
set { _responseFilter.Position = value; }
}
}
This method requires custom errors to be switched off for the web services. You would probably want to keep custom errors on for the rest of the application so the web services should be placed in a sub directory. Custom errors can be switched off in that directory only using a web.config that overrides the parent setting.
You run the Stored Procedure in the backend. Then, for a single variable, it returns more than 1 value. Because of that, a conflicts occurs, and, this error is thrown.
I know this doesn't answer the question per-say, but I went on my own quest a while back to find this out and would up empty handed. Ended up wrapping each web service call in a try/catch, and the catch calls our error logger. Sucks, but it works.
In ASP.Net it is possible to catch all run handled exceptions using a global error handler although the blog post suggest this would not work but you could experiment with this approach trying to rethrow the error in some way?
Another idea would be to look at the open source elmah (Error Logging Modules and Handlers) for ASP.Net that might help or someone in that community may have an idea.

Resources