Exclude HttpModule from running for static content on IIS7 - asp.net-mvc

I have a problem with my Authentication HttpModule. The problem is that it obviously runs for every single request I get on my web server (IIS7). Because it also uses Session variable, it fails to work properly on CSS, JS files and similar.
I tried to use:
<add name="AuthModuleName" type="..." preCondition="managedHandler" />
but to no avail. It still runs on every request regardless of its extension or mime type. I should also add, there's a setting
<modules runAllManagedModulesForAllRequests="true">
that seemed suspicious to me and actually disabled preConditions on modules. But changing it to false, breaks the application in a completely different way and with a different exception (The SessionStateTempDataProvider requires SessionState to be enabled).
Can anybody help me how to force IIS7 to exclude my HttpModule when requests are made for static content files?

runAllManagedModulesForAllRequests attribute has to be set to false to actually configure any module the way you want. You will have to also correctly reconfigure Session and others as needed, but the main thing is handlers pipeline execution order that handles requests.
The answer was provided in one of my other questions:
Thanks to Peter that provided the answer that worked correctly.

I don't know about an IIS7 setting for that but you can do this.
The session object will be available only for non-static content :
void yourEventHandler(object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
if (app.Context.Session == null) {
return;
}
// then your code here...
}
This will ensure your code won't be run for files like CSS, JS etc. But keep in mind the session object will also not be ready before PostAcquireRequestState event. (For the order of the HttpApplication events, see this page.)
Edit : Also, it appears with ASP.NET Development Server (though I know you said IIS7 in your question), your HttpModule will still run even for static files.

Related

And still, what is the magic of ASP.NET MVC Content folder?

I've just moved my resource files (javascript, css, images) from Content folder to custom Assets folder. And I've noticed a strange behavior - these files are not longer cached by browser and MvcMiniProfiler shows separate request for each resource located in Assets folder:
I know that Content folder isn't required by ASP.NET MVC convention, but why this happens? Is Content treated somehow especially by anyone (e.g. ASP.NET, IISExpress, etc.)? And how to force caching for other folders that contain static resources?
EDIT: Oh, it appears to be not an ASP.NET MVC odd behavior, but just the standard behavior of MvcMiniProfiler. Currently I'm checking that...
EDIT: Yea, there is no problem with ASP.NET MVC, it's just a default configuration of MvcMiniProfiler to ignore only these paths: "/mini-profiler-", "/content/", "/scripts/", "/favicon.ico". And these defaults can be easily extended:
MiniProfiler.Settings.IgnoredPaths = MiniProfiler.Settings.IgnoredPaths
.Concat(new [] { "/assets/" })
.ToArray();
Sometimes it's a good idea to read documentation before using something ;)
As you're indicating in your update, this appears to be a feature of MvcMiniProfiler:
/// <summary>
/// When <see cref="MiniProfiler.Start"/> is called, if the current request url contains any items in this property,
/// no profiler will be instantiated and no results will be displayed.
/// Default value is { "/mini-profiler-", "/content/", "/scripts/", "/favicon.ico" }.
/// </summary>
[DefaultValue(new string[] { "/mini-profiler-", "/content/", "/scripts/", "/favicon.ico" })]
public static string[] IgnoredPaths { get; set; }
Source.
Presumably, the images were never cached when you were serving them through Cassini, because Cassini is terrible at that (passing png files as application/octet-stream, for instance), but the issue was manually hidden from your view by MvcMiniProfiler.
This is a strange behavior. However, put the following code inside your web.config file which is under the root of your app:
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="30.00:00:00" />
</staticContent>
</system.webServer>
This code appends the necessary response headers in order for browser caching to work. You can tweak the time of course. For further info please refer : http://www.iis.net/ConfigReference/system.webServer/staticContent/clientCache

ASP.Net MVC Nhibernate Session

When moving to MVC, and now IIS7, we started having issues in that our HTTP Module that opens and closes the ISession was called on every request (static files, etc.). I wanted to avoid doing a full rewrite of NH session management, so I implemented this code in my module, to filter out everything but requests going to the mvchandler:
void context_PreRequestHandlerExecute(object sender, System.EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
Type mvcht = typeof(System.Web.Mvc.MvcHandler);
if (context.Handler != null && context.Handler.GetType().IsAssignableFrom(mvcht))
{
// Code Here
}
}
My question is, I have never used this event in the request pipline. So, are there any hidden pitfalls in doing this? Also, am I looking at a performance issue in running this check for every request? I haven't noticed anything yet, but this is a new and still small app.
Although this doesn't specifically address your question, it should be noted that the cost of opening a session is very minimal. So you may consider not even performing this check in the first place.

ASP.NET MVC VirtualPathProvider not working under IIS 6

By ASP.NET MVC plugin architecture, Plug-in architecture for ASP.NET MVC
I have separated DLL(plugin) which contains the views, css and javascript files in the resources. So my own VirtualPathProvider will load the content out from the DLL if that is for the plugin. It works all fine during development. But It appears not working once I deployed it in IIS. (I mapped the whidcard in IIS 6 and the views are showing)
I have registered my VirtualPathProvider in global.asax as
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
}
For example.
http://localhost/Plugin/MyPlugin.dll/Styles.MyStyles.css
This should be loaded from the plugin.dll but IIS returns 404.
I guess the static files are all handled by the IIS and not went through asp.net and my VirtualPathProvider ? Is there way to get around this? Please shed some light.
Thanks in advance.
If this is IIS 6 you will need a wildcard mapping. See this blog post from Phil Haack.
I've found the workaround by adding the staticFileHandler in the web.config httpHandlers element.
<add verb="GET,HEAD,POST" path="*" type="System.Web.StaticFileHandler" validate="true" />
I've had a number of problems getting an external compiled library containing resources and controllers to work in our MVC environment. It's used across multiple projects and different errors have surfaced in different projects so here's all the things I had to do (so far) to ensure static file handling works:
Include StaticFileHandler in web.config, e.g.:
<add verb="GET,HEAD" path="*.js" name="Static for js" type="System.Web.StaticFileHandler" />
Ensure Static items are ignored in routing:
routes.IgnoreRoute("{*staticfile}", new { staticfile = #".*\
.(css|js|gif|jpg)(/.*)?" });
Register a Virtual Path Provider, e.g.:
System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedResourceVirtualPathProvider.Vpp(assemblies.ToArray())
{
//you can do a specific assembly registration too. If you provide the assemly source path, it can read
//from the source file so you can change the content while the app is running without needing to rebuild
//{typeof(SomeAssembly.SomeClass).Assembly, #"..\SomeAssembly"}
});
Not required for static files but worth mentioning are what was needed to get the views / controllers working, which was adding MVCContrib and registering the embedded view engine:
PortableAreaRegistration.RegisterEmbeddedViewEngine();

How best to implement 'website closed' functionality?

We have a requirement to have our ASP.NET MVC websites be automatically closed down by a remote notification (change in database value). Where would be the best place to implement this?
Base Controller Class
Global.asax
Custom attribute
Other
Update
Lots of suggestions to use app_offline but this scenario will be happening daily and will be purely initiated by the database so I would rather have the application take the initiative rather than something external push the file in.
Also, I will probably need to redirect the users to a holding page (preferably an MVC controller method to keep everything consistent). I'm leaning more towards catching it in my BaseController and having that handle it
There's a standard way of "gracefully" terminating ASP.NET 2.0 webapp - just drop a App_Offline.htm to the root directory of your application. See this.
I would go with Global.asax Application_BeginRequest if you have to do it programmatically.
You could Response.Redirect the page to "Offline.aspx" which can retrieve a message from the database or whatever you need. Of course you'd have to look at the request to see if it was trying to get to "Offline.aspx" otherwise you'd end up in an infinite loop.
Or maybe all your applications can be redirected to a single website which would remove most the complication.
I'm going to answer this myself as I did it a different way but thanks to everyone for their responses.
What I ended up doing is overriding OnActionExecuting() in my BaseController class (which all my controllers derived from).
In this method I check the database (using a 1 minute cache) and if the website is closed I load up a view to display a closed message. Code shown below
Protected Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
MyBase.OnActionExecuting(filterContext)
Dim _cfgService = ObjectFactory.GetInstance(Of IConfigService)()
If _cfgService.DynamicConfig.WebSiteClosed Then
filterContext.Result = ErrorHandler(_cfgService.DynamicConfig.WebSiteClosedTitle, _
_cfgService.DynamicConfig.WebSiteClosedMessage)
End If
End Sub
Handling this type of behavior in the Global.asax file sounds like the best solution and redirecting to a static "ofline/closed" page. Handle the request on the Application_BeginRequest method, check to see the the site is active, if it let it continue, if it is not online Response.Redirect the request to the static page.
protected void Application_BeginRequest(object sender, EventArgs e)
{
string redirectURL = "~/Offline.aspx"; //some static page
bool isOnline = false; //SQL Call, config value
if (!isOnline && !string.IsNullOrEmpty(redirectURL))
{
Response.RedirectLocation = redirectURL;
Response.End();
}
}
Sorry, don't know about ASP.NET, but in case helpful:
We have a single APPLICATION.ASP page for our site (CMS / Database merge type stuff); this is possibly not common and therefore may therefore restrict usefulness, but could perhaps be implemented by an INCLUDE at the top of all ASPX files
We rename APPLICATION.ASP to TEST.ASP and copy HOLDING_PAGE.ASP to APPLICATION.ASP
The HOLDING_PAGE.ASP is present in the WWW folder, so always ready-and-available. It contains a "Site not available" message etc. and is self contained for all CSS (no include files, no DB access). Only exception is the company logo (which is external to that file, obviously)
This method prevents all access to the site, is not dependant on having a working DB connection, and allows us to change anything on the site without interfering with the holding page (apart from Company Logo, but changing that is likely to be benign)
We can still access the site, internally, using TEST.ASP - so we can test any new rollout features before removing the holding page and putting the site live. If you want to prevent anonymous use of TEST.ASP then deny anonymous permission.
Remove holding page is: Delete APPLICATION.ASP (i.e. the holding page) and Rename TEST.ASP to APPLICATION.ASP
We also have a database flag that causes the normal APPLICATION.ASP page to show a holding page - which we can use whilst doing more minor changes.

SessionID keeps changing in ASP.NET MVC why?

I am trying to keep track of something and using the SessionID as they key to that object
However the SessionID every 2-3 reqiests changes shouldn't it remain the same?
HttpContext.Session.SessionID
Is the code I am using.
I've seen that happen even without MVC. If I remember correctly, ASP.NET keeps assigning new session ids until you place something into the Session variable.
You should initialize the Session object within Global.asax.cs.
void Session_Start(object sender, EventArgs e)
{
HttpContext.Current.Session.Add("__MyAppSession", string.Empty);
}
This way the session will not change unless your browser window is closed.
I am working on a .NET MVC cart application and I added
Session["myVar"] = "1234";
to the Page_Load method found in the Default.aspx.cs code. I also added
<%= this.Session.SessionID %>
to the Site.Master footer. I then rebuilt my app and browsed the various pages of my app and the footer displays the same session id for all pages as expected!
If you look the seession ID cookie is not even sent to the browser unless it's used on the server.
So when a page roundtrips there is no current session ID cookie, so a new session ID is created, hence it is random.
This is logical, why bother tying up the app to a session if the session is not in use?
I would look into using TempData to keep track of something.
Try with adding machine key into your web.config:
Online key generator: http://aspnetresources.com/tools/keycreator.aspx
It seems that server resets machine key for client and generates new one with new session id, every few minutes, which is much lower than it should. Don't know if that's bug or feature:)
Also, you can increase your session state timeout, which is i think 20min by default.
I was having the same problem using ASP.NET Web Forms. I just had to add a global.asax file to the solution and the fixed it for me.
Summing up answers from #jrojo and #Maxam above, with what I am using.
I am using AWS DynamoDB as the session store (out of scope of the question a little, but gives sample).
Add package via NUGET:
Install-Package AWS.SessionProvider
Update Web.config to have keys in appSettings:
<add key="AWSAccessKey" value="XXX" />
<add key="AWSSecretKey" value="YYY" />
And session provider to system.web:
<sessionState timeout="20"
mode="Custom"
customProvider="DynamoDBSessionStoreProvider">
<providers>
<add name="DynamoDBSessionStoreProvider"
type="Amazon.SessionProvider.DynamoDBSessionStateStore, AWS.SessionProvider"
AWSProfilesLocation=".aws/credentials"
Table="ASP.NET_SessionState"
Region="us-east-1"
/>
</providers>
</sessionState>
Add anything to session in global.asax on session start:
void Session_Start(object sender, EventArgs e) {
HttpContext.Current.Session.Add("somethingToForceSessionIdToStick", string.Empty);
}
Verify by adding this to razor of any page. Refresh that page, then open an ignito window and see a different session:
#HttpContext.Current.Session.SessionID
BobsYourUncle
please see if you have set the cookie samesite attribute to strict.
remove cookieSameSite="Strict" and check.

Resources