tags:

views:

312

answers:

4

We're migrating an application to use IIS7 integrated mode. In library code that is designed to work either within the context of an HTTP request or not, we commonly have code like this:

if (HttpContext.Current != null &&
    HttpContext.Current.Request != null) {

    // do something with HttpContext.Current.Request

} else {

    // do equivalent thing without HttpContext..

}

But in IIS7 integrated mode the check for HttpContext.Current.Request throws an exception whenever this code is called from Application_Start.

protected void Application_Start(object sender, EventArgs e)
{
    SomeLibrary.DoSomethingWithHttpContextCurrentDetection();
}

Results in:

System.Web.HttpException: Request is not available in this context

How can I detect whether the request is really available without wrapping these calls in an exception handler and taking action based on whether an exception is generated or not.

Looking at HttpContext in Reflector I see it has an internal bool HideRequestResponse field but it's internal so I can only get to it with reflection and that's fragile. Is there a more official/approved way to determine if it's ok to call HttpContext.Request?

This blog post about the subject says not to use HttpContext, but how, in generic library code, can you determine if it's ok to use HttpContext?

http://mvolo.com/blogs/serverside/archive/2007/11/10/Integrated-mode-Request-is-not-available-in-this-context-in-Application_5F00_Start.aspx

I'm using the work-around mentioned there which is to use Application_BeginRequest and an initialized field to only initialize once as part of BeginRequest, but that has to be done in every calling application whereas I'd prefer to make the library code more robust and handle this situation regardless of where it's called from.

+2  A: 

I would refactor your code to this:

if (IsRequestAvailable())
{
    // do something with HttpContext.Current.Request...
}
else
{
    // do equivalent thing without HttpContext...
}

public Boolean IsRequestAvailable()
{
    if (HttpContext.Current == null)
        return false;

    try
    {
        if (HttpContext.Current.Request == null)
            return false;
    }
    catch (System.Web.HttpException ex)
    {
        #if DEBUG
            // Testing exception to a magic string not the best practice but
            // it works for this demo.
            if (ex.Message == "Request is not available in this context")
                return false;

            throw;
        #else
            return false;
        #endif
    }

    return true;
}

Your question asked not to use exception handling (I assume for performance reasons) and my answer does. However, by changing your code from using "If (HttpContext.Current != null && HttpContext.Current.Request != null)" to "If (IsRequestAvailable())" you only have one place to change the code when you find an answer how not to use exception handling.

Tim Murphy
Thanks for the suggestion, but that's exactly what I said I was trying to avoid.. using error handling to detect whether it checking `Request` will return null.
Sam
Why do you consider it so important to avoid exception handling?
Tim Murphy
Another issue is when you use such a class in non-english environment, the message won't match.
Jaroslav Jandek
Sure so just test for the exception. The try ... catch is around a single line of code so I think it would be safe to do so.
Tim Murphy
@Tim Murphy, I'm trying to avoid exception handling for what I would consider a non-exceptional situation, but if there is no option then wrapping the call in a separate method that has exception handling is a viable option.
Sam
So you'll give me an up vote instead of a down vote :-)
Tim Murphy
@Tim Murphy, your answer is still exactly what I said I want to avoid in my question, even though it appears there's no way to avoid exception handling in this situation.
Sam
A: 

You should not even use Request (or Response) in the Application_Start since application could be started without a request. So in the future your application won't even run when other parts of framework stop providing the Request object.

If you want to just hack it temporarily, you could use Reflection (if you have above-medium trust) or catching an exception (even though you don't want to) and store the result in a static variable or possibly use a static HttpContext wrapper:

Also you could use HttpRuntime.UsingIntegratedPipeline.

So the best approach is remove the dependance of your classes on HttpContext when they are being initialized or not initalize them in appstart.

What is your reasoning to use Request in the app start anyway? For statistics? Or just telling the user he woke the application?

Edited with code to explain better:

public static class ContextWrapper
{
    public static HttpRequest Request
    {
        get
        {
            HttpContext context = HttpContext.Current;
            if (context == null) return null;

            if (HttpRuntime.UsingIntegratedPipeline)
            {
                try { return context.Request; }
                catch (HttpException e) { /* Consume or log e*/ return null; }
                // Do not use message comparison - .NET translates messages for multi-culture environments.
            }

            return context.Request;
        }
    }
}

And in code:

if (ContextWrapper.Request != null) //...

Or a user-controlled faster way:

public static class ContextWrapper2
{
    public static bool IsIis7IntegratedAppStart { get; set; }

    public static HttpRequest Request
    {
        get
        {
            if (ContextWrapper2.IsIis7IntegratedAppStart) return null;

            HttpContext context = HttpContext.Current;
            if (context == null) return null;

            return context.Request;
        }
    }
}

And in app start:

protected void Application_Start(object sender, EventArgs e)
{
    yourLibraryNamespace.ContextWrapper2.IsIis7IntegratedAppStart = true;
    //...
    yourLibraryNamespace.yourClass.Init();
    //...
    yourLibraryNamespace.ContextWrapper2.IsIis7IntegratedAppStart = false;
}

You could note this behaviour in your documentation and all should be well. AppStart-like context should be the only place where you get such an exception.

You could also implement IDisposable on a member and use it in appStart with the using statement so you do not forget to set IsIis7IntegratedAppStart = false.

Jaroslav Jandek
Please re-read the question. The concern is in a library that's meant to be used from anywhere, how can one properly detect whether or not Request will throw an error. The library author has no control of where a user calls the library from.
Sam
Anyway, the answer is you cannot (without exception or reflection) detect it (from your library)! That's the whole point of those answers that got downvoted... You would have to 1) check that you are in `Application_Start` 2) check you are running IIS7 3) check it is in integrated pipeline mode. IMHO too much work when you can just catch the exception or use a wrapper with a parameter.
Jaroslav Jandek
I understand, the problem is you **should not** be handling such error because the user **should not** be using your library in such a way (or maybe you use it wrong in your library - hard to say without more data). Also, I do not understand a downvote since I have told you about a workaround (using a `HttpContext wrapper`) - how you initialize said wrapper is up to you.
Jaroslav Jandek
Jaroslav Jandek
A: 

I'm afraid the answer is that you can't get what you want - Microsoft sees this case as an 'exceptional circumstance' and so it will throw an exception.

You can use reflection as you describe in your answer but you don't want to and so are limited by the API that Microsoft have provided, for better or for worse.

If you do decide to use reflection, of note is the HttpApplication.InitInternal method which is what sets the HideRequestResponse flag.

Hope that helps. I would suggest you file a report with Microsoft Connect.

Kieren Johnstone
How is it of note? Can't do anything about it anyway. Not running or modifying that method would more likely mess the application up more than anything else.
Jaroslav Jandek
It's of note as I said because it contains the algorithm that determines whether the exception will be thrown or not.
Kieren Johnstone
Ah, I see. It depends on whether the property `HttpRuntime.UsingIntegratedPipeline` is true which is true if the application runs in IIS7 with integrated pipeline mode. Anyway, it was already pointed out even by the OP.What is interesting is the fact that `HideRequestResponse` is only enabled while processing special requests (like Application Start and End + Session End) => those are the only special requests where are you going to get the exception.
Jaroslav Jandek
A: 

I added a comment, but it gets auto-hidden.

I think it's more important to have an idea of what it is that you need from the request.

For instance, the link you provided which provides a workaround is looking for Request.ApplicationPath.

If that's actually what you're looking for (for, say, loading the web.config vs the app.config), you could do this:

        if (HttpRuntime.AppDomainAppId != null)
            return WebConfigurationManager.OpenWebConfiguration(HttpRuntime.AppDomainAppVirtualPath);
        else
            return ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

If this (or HttpRuntime.ApplicationPath) isn't what you're actually looking for, it would be helpful to know which properties of the Request you are actually looking for. Maybe there's a better, safer way to get there.

Toby