How to set log4net context property specific to an ASP.NET request? - asp.net

I've been using log4net to log our ASP.NET web site's log messages, and lately I wanted to add information about the page/handler where the error happened. I decided therefore to add the following line to Global.asax:
void Application_BeginRequest(object sender, EventArgs e)
{
log4net.ThreadContext.Properties["page"] = HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath;
}
and like wise I added %property{page} to my conversion pattern:
<conversionPattern value="%newline%date %-5level %property{page} - %message%newline%newline%newline" />
This worked fine for single requests. But then I noticed in my logs that the page property may change during an ASP.NET request. I have logging in one ASHX handler, and in the midst of its processing, the page property would change to a different value that points to an ASPX page. I concluded that there is another request coming to ASP.NET and its BeginRequest gets executed and the static page property in log4net.ThreadContext gets changed to another value.
Now, I would like to maintain the page property per request, so that I can have the path of the executing page logged to the log consistently. I tried to find an answer, but I came out with nothing. What is the recommended way to solve this problem? I'm sure this is very basic functionality of web server event logging.

Since ASP.NET does not guarantee that the entire page request will be processed on the same thread, I prefer getting the answer from HttpContext.Current as log4net processes the logging event.
The following GetCurrentPage class implements what the log4net manual calls an "Active Property Value" by overriding its ToString method:
public class GetCurrentPage
{
public override string ToString()
{
if (null != HttpContext.Current)
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath;
}
return string.Empty; // or "[No Page]" if you prefer
}
}
Register this class in Global.asax's Application_Start in log4net's GlobalContext.
protected void Application_Start(object sender, EventArgs e)
{
XmlConfigurator.Configure();
GlobalContext.Properties["page"] = new GetCurrentPage();
}
When log4net writes the %property{page} part of the line it will call the ToString method of our GetCurrentPage class which will lookup the value in the current request.

Have you tried using Application_PostAcquireRequestState instead of Application_BeginRequest as outlined in this article? How can I include SessionID in log files using log4net in ASP.NET?
We have never felt the need to add the page to the logging since we create our logger in each class with the method name:
private static new readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
and have a conversion pattern like this:
<conversionPattern value="%date %P{user} %P{addr} [%property{SessionID}] %level %logger - %message%newline" />
So we end up seeing the class name in the log output. This also lets you differentiate whether the logging happened in the page class file itself, vs. a base class it inherits from. Your solution would have the advantage of showing the page name even when the code is executing in the base class. I think we will look at adding {page} to our setup.

Store property value in ASP.NET context HttpContext.Current.Items.Add("yourProperty", value)
It will be available from log4net layout:
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%aspnet-context{yourProperty}" />
</layout>
Find more detail here.

Related

How to handle SizeLimitExceededException from CommonsMultipartResolver in Spring WebFlow?

I have the following situation. I have a CommonsMultipartResolver bean configured the following way.
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2100000" />
And I have a few file upload fields in a Spring Web Flow view state jsp.
Everything works fine if the file is under the limit, but if the file exceeds the limit of 2MB-s I have to add a validation error to the binding result on my form.
My problem is that the multipart file resolver throws a org.apache.commons.fileupload.FileUploadBase.SizeL imitExceededException exception when the file limit is exceeded and I can't find a way to catch this in Spring Web Flow and add my FieldError to the form.
I tried using the on-exception attribute of the transition tag, but if I understand correctly it only works for exceptions that are thrown within Spring Web Flow.
I've also tried to use SimpleMappingExceptionResolver in spring mvc, but I do not want to redirect to a page, I want to handle this exception.
I also found this: https://jira.springsource.org/browse/SWF-158
But it's from version 1.0 and I'm assuming that this has been incorporated since or that a better way was found to handle these situations.
Any ideas on how to deal with this would be greatly appreciated.
Thanks.
In your SimpleMappingExceptionResolver you should be able to override the resolveException method, determine the exception type being caught and handle appropriately.
I've found some old code in our project that seems to be a solution to a similar exception;
public class GeneralMappingExceptionResolver extends SimpleMappingExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
if(exception instanceof MaxUploadSizeExceededException) {
MaxUploadSizeExceededException maxe = (MaxUploadSizeExceededException)exception;
String errorMessage = "Max filesize exceeded, please ensure filesize is too large.");
HashMap<String, Object> model = new HashMap<String, Object>(2);
model.put("errorMessage", errorMessage);
return new ModelAndView("verification/psv/consent", model);
} else {
return super.resolveException(request, response, handler, exception); // Do whatever default behaviour is (ie throw to error page).
}
}
Note that the "verification/psv/consent" is the flow where this exception would have been thrown from and where it needs to return to. We only have the one page that has a file upload.
Obviously the errorMessage is just a parameter passed into the view so will need to be handled and displayed like an error message. You may also need to re-populate any other form fields that were submitted. Hopefully this is a point in the right direction though.

The Application_PreRequestHandlerExecute event doesn't fire for PageMethods. What can I use instead?

This article explains that the PreRequestHandlerExecute event does not fire for PageMethod calls for whatever reason. However, I'm trying to use that event to populate the Principal object with the user's permissions so they can be checked within any web request (PageMethod call or not). I'm caching the permissions in the Session, so I need an event that fires whenever a PageMethod is called, and I need to have access to the Session. This way I can populate the Principal object with the security permissions cached in the session, and User.IsInRole() calls will work as expected. What event can I use?
You should implement an authorization module that will be run with every request that goes up to the server. This way you are able to authorize your principal for any request that come up to the server (page request, method, etc.)
public class AuthorizationModule : IHttpModule, IRequiresSessionState
{
//not going to implement it fully, might not compile
public void Init( HttpApplication context )
{
//you'll prolly want to hook up to the acquire request state event, but read up to make sure this is the one you want on the msdn
context.AcquireRequestState += AuthorizeRequest;
}
public void AuthorizeRequest( HttpContextBase httpContext )
{
// do you work in here
// you can redirect them wherever if they don't have permssion, log them out, etc
}
}
}
After you've crated the module, you'll need to hook it up in the web.config. Your type should include the namespace if it has one.
<httpModules>
<add name="AuthorizationModule" type="AuthorizationModule"/>
</httpModules>
I hope this helps.
You can use the Application_OnPostAuthenticateRequest as shown below (assuming you are using Forms Authentication. Else, pls replace the code with your Authentication mechanism):
public void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
IPrincipal usr = HttpContext.Current.User;
if (usr.Identity.IsAuthenticated && usr.Identity.AuthenticationType == "Forms")
{
var fIdent = (FormsIdentity)usr.Identity;
var ci = new CustomIdentity(fIdent.Ticket);
var p = new CustomPrincipal(ci);
HttpContext.Current.User = p;
Thread.CurrentPrincipal = p;
}
}
Page Methods are static, and bypass the normal Page lifecycle, its objects and its events. The best you can do is pass authentication information as parameters to the Page Method itself.
From my point of view, you can:
1.- Use a common method you can call from every page method server code that have access to Session variables. Please refer to:
http://mattberseth.com/blog/2007/06/aspnet_ajax_use_pagemethods_pr.html
2.- Try to capture a similar behaviour later using __doPostBack() function to run server code. See if this work for you to capture page method async posbacks:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=256
Hope that helps,

ASP.NET Application Lifecycle - how to check configuration properties exist?

I've written a singleton class that exposes the web.config properties in a nice get property kind of way.
I want a Load method to parse the data in the config and set the public properties, and I want to throw exceptions (so they are logged in the EventLog) when a configuration key is missing or can't be parsed.
I tried placing the Load() code in Application_Start of the global.asax but then remembered this will only be run once, or until the application restarts.
Where is the best place to put code that you need to run 'everytime' your site is started/run by the user? I basically want the website to stop functioning if certain config properties cannot be loaded.
Thanks.
When you change your web.config file, the application pool is recycled. This means that the next hit will cause your Application_Start method to be called.
Altering the following files will also
trigger an immediate restart of the
application pool:
- web.config
- machine.config
- global.asax
- Anything in the bin directory or it's sub-directories
On that basis, as soon as your configuration is changed, it will be reloaded the next time a user hits the site, which should resolve the problem with the minimum number of configuration reloads, as opposed to reloading whenever a session starts for example. Therefore, you can do this (in your global.asax):
static bool configValid = false;
void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext context = base.Context;
HttpResponse response = context.Response;
HttpRequest request = context.Request;
// Redirect users to an alternate page if the current config is invalid
// I happen to pass the Url they were attempting to access in the query string
// that way you can give them a "try again" link
if ((!configValid) && (!request.Url.ToString().Contains("BadConfig.aspx")))
{
response.Redirect("BadConfig.aspx?originalUrl=" + context.Server.UrlEncode(request.Url.ToString()));
}
}
void Application_Start(object sender, EventArgs e)
{
// Load config and determine if it's valid, thus setting configValid to true/false
//
//
configValid = false;
}

Intercept Unity 2.0 HandlerAttribute without an interface

I'm a first-time user of the AOP features of Unity 2.0 and would like some advice. My goal is to be able to log method calls in an ASPX page, like so:
public partial class Page2 : Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
[Log]
private void Testing()
{
}
}
Here is the code for the LogAttribute:
public class LogAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new LogHandler(Order);
}
}
Now the LogHandler:
public class LogHandler : ICallHandler
{
public LogHandler(int order)
{
Order = order;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
string className = input.MethodBase.DeclaringType.Name;
string methodName = input.MethodBase.Name;
string preMethodMessage = string.Format("{0}.{1}", className, methodName);
System.Diagnostics.Debug.WriteLine(preMethodMessage);
return getNext()(input, getNext);
}
public int Order { get; set; }
}
The problem I have is how to use the [Log] attribute. I've seen plenty of example of how to configure the interception settings, for example:
container.AddNewExtension<Interception>();
container.Configure<Interception>().SetDefaultInterceptorFor<ILogger>(new InterfaceInterceptor());
But this implies that I have an interface to intercept, which I don't. I have the ASPX page which uses the [Log] attribute.
so how can I configure Unity to make use of the [Log] attribute? I've done this before using PostSharp and would like to be able to use Unity to do the same.
Cheers.
Jas.
You're unfortunately not going to get this to work in an ASP.NET page with Unity interception.
Unity interception uses a runtime interception model. Depending on the interceptor you choose, you'll either get a subclass with virtual method overrides to call the call handlers (VirtualMethodInterceptor) or a separate proxy object (Interface or TransparentProxyInterceptor) which execute the call handlers and then forward to the real object.
Here's the issue - ASP.NET controls creation and calls to your page, and there's no easy way to hook into them. Without controlling the creation of the page object, you can't use the VirtualMethodInterceptor, because that requires that you instantiate a subclass. And you can't use the proxy version either, because you need ASP.NET to make calls through the proxy.
PostSharp gets around this because it's actually rewriting your IL at compile time.
Assuming you could hook into the creation of the page object, you'd have to use the VirtualMethodInterceptor here. It's a private method, so you want logging on "self" calls (calls from one method of the object into another method on the same object). The proxy-based interceptors can't see those, since the proxy is a separate instance.
I expect there is a hook somewhere to customize how ASP.NET creates object - BuildManager maybe? But I don't know enough about the details, and I expect it'll require some pretty serious hacking to get work.
So, how do you get around this? My recommendation (actually, I'd recommend this anyway) is to use the Model-View-Presenter pattern for your ASP.NET pages. Make the page object itself dumb. All it does is forward calls to a separate object, the Presenter. The Presenter is where your real logic is, and is independent of the details of ASP.NET. You get a huge gain in testability, and you can intercept calls on the presenter without all the difficulty that ASP.NET gives you.

IHttpModule.BeginRequest firing 2X, Application_BeginRequest firing 1X

I'm running VS 2008 and .NET 3.5 SP1.
I want to implement hit tracking in an HttpModule in my ASP.NET app. Pretty simple, I thought. However, the BeginRequest event of my HttpModule is firing twice for each page hit. The site is very simple right now...no security, just a bit of database work. Should log one row per page hit. Why is this event firing twice?
Moreover, IHttpModule.BeginRequest actually fires a different number of times for the first page hit when running for the first time (from a closed web browser)...3 times when I'm hitting the DB to provide dynamic data for the page, and only 1 time for pages where the DB isn't hit. It fires 2 times for every page hit after the first one, regardless of whether or not I'm touching the DB.
It's interesting to note that Application_BeginRequest (in Global.asax) is always firing only once.
Here's the code:
using System;
using System.Data;
using System.Data.Common;
using System.Net;
using System.Web;
using BluHeron.BusinessLayer;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;
namespace BluHeron.HttpModules
{
public class SiteUsageModule : IHttpModule
{
public void Init(HttpApplication httpApp)
{
httpApp.BeginRequest += OnBeginRequest;
}
static void OnBeginRequest(object sender, EventArgs a)
{
UsageLogger.LogSiteUsage(((HttpApplication)sender).Context.Request);
}
public void Dispose()
{ }
}
public static class UsageLogger
{
public static void LogSiteUsage(HttpRequest r)
{
string ipAddress = GetHostAddress(Dns.GetHostAddresses(Dns.GetHostName()));
string browserVersion = r.Browser.Type;
string[] urlChunks = r.RawUrl.Split('/');
string page = urlChunks[urlChunks.GetLength(0)-1];
SqlDatabase db = new SqlDatabase(Common.GetConnectionString());
DbCommand cmd = db.GetStoredProcCommand("LogUsage");
db.AddInParameter(cmd, "IPAddress", SqlDbType.NVarChar, ipAddress);
db.AddInParameter(cmd, "BrowserVersion", SqlDbType.NVarChar, browserVersion);
db.AddInParameter(cmd, "PageName", SqlDbType.NVarChar, page);
db.AddInParameter(cmd, "Notes", SqlDbType.NVarChar, "");
db.ExecuteNonQuery(cmd);
}
private static string GetHostAddress(IPAddress[] addresses)
{
foreach (IPAddress ip in addresses)
{
if (ip.ToString().Length <= 15)
{
return ip.ToString();
}
}
return "";
}
}
}
This might be too late for the answer but can be useful for someone else. I faced with the same problem. BeginRequest event triggered for twice for each request. I debugged the code and realized that the first trigger for actual resource request but the second is result of "favicon.ico" request. At the beginning of BeginRequest event, a simple check for favicon.ico request eliminates second execution of the method.
public void Application_BeginRequest(object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
HttpContext ctx = app.Context;
if (ctx.Request.Path == "/favicon.ico") { return; }
quite late on this, but ran into the same issue. In our case it was due to the anonymous request first that returns the 401 per the RFC. The second request authenticates.
The "Default Document" part of IIS seems to fire a second BeginRequest event.
If you have determined that the Request.Path is the same for the HttpApplication in both event handlers and your URL ends with a slash, try adding a URL Rewrite rule to shortcut the "Default Document" processing.
This is interesting. I removed the reference to the CSS file from the master page and I'm getting fewer repeat hits in the HttpModule for certain browsers (as was suggested), but I'm still getting repeats. I have 6 browsers installed, and I'm getting some variation between them.
For reference, this is the URL I'm plugging in to my browsers for this test:
http://localhost/BluHeron
default.aspx is set as the start page and is indeed returned for the aforementioned URL. I'm using HttpRequest.RawUrl for reporting which page the user hit. Specifically, I'm splitting the RawUrl string and just reporting the last item in the array of strings (see code).
Every single browser is reporting hitting default.aspx, as expected (RawUrl = /BluHeron/default.aspx).
4 of the 6 browsers are also reporting BluHeron (RawUrl = /BluHeron).
3 of the 6 browsers are also recording a blank in the database (RawUrl = /BluHeron/).
There are a couple ways I can get accurate reporting of how many people are hitting which pages.
Select from the database only rows that actually list one of my pages (ignore /BluHeron and blanks)
Just use Application_BeginRequest in the global.asax file, which seems to consistently get called only once per page hit.
Get this figured out.
So, I've got options for getting good reports even with crappy data in the database. I would prefer to understand what's going on here and not to have junk in the database.
Thanks for looking, everyone!
We solved this by using
HttpContext.Current.ApplicationInstance.CompleteRequest();
This should prevent the the twice fire you are seeing.
One possibility is that there are other requests going on that you might not be considering. For example, let's say your ASPX page references some images or CSS files. If those requests go through the ASP.NET pipeline then your module will be called and they'll register as hits.
Also, when you say IHttpModule.BeginRequest, do you mean that in IHttpModule.Init() you are hooking up HttpApplication.BeginRequest? If so then the reason I mention above might still apply.
Disable Browser Link in Visual Studio 2013 and up, which causes the second request.
This occurs when an Application is run from Visual Studio.

Resources