This blog post is about an ASP.NET MVC workaround we implemented in a previous project. We solved the problem by enforcing using a class that extends one of ASP.NET MVC classes, which in itself created another problem, as new developers joining the project may always use the old class. The solution to this problem was not something that I invented, but it’s also not a very common practice.
So, if you are interested, here’s the entire story…
Detecting Session Timeout In AJAX Requests
We wanted to solve a problem where in an AJAX heavy ASP.NET MVC application, if the user triggers an AJAX action after staying inactive for longer than our application timeout, the call to the controller action, which normally gets a JSON response, would instead get the HTML of the login page.
This is a known issue in ASP.NET (particularly System.Web). A feature that’s on by default is returning a redirect to the login page instead of a HTTP Unauthorized Status code (401). After the redirect the response returned is a successful (HTTP Status Code 200) load of the login page. That means even our Angular.JS error interceptors (or jQuery handlers, etc.) don’t notice there was an error.
A fix for this was turning this feature off. We inherited the
Authorize attribute as below:
SuppressFormsAuthenticationRedirect is the property that disables the login redirect. Microsoft set it to false by default so that it’s backwards compatible.
ASP.NET MVC doesn’t recognize AJAX requests through
Accept header or so. It does via checking
X-Requested-With header. Most AJAX-capable frameworks like jQuery and others offer a way to intercept all requests and add extra headers, for example, in that app, we configure Angular.JS to include the header with some code similar to this:
Enforcing The Convention
The obvious problem with the previous solution is that we are ignoring The Power Of Defaults. Any other developer who may join the project needs to know that using
Authorize is a no-no, and even for old devs (myself included),
it’s very easy to just forget and use
AuthorizeRedirect just out of habit.
Solving this problem was quite easy though, we added the following test to our Unit Tests project:
I hope the code is self explanatory. We check the web project assembly for all ASP.NET MVC
Controllers, then we check the
Controllers and all their
Action methods for existence of the
AuthorizeAttribute. We filter those that use the correct attribute (
AuthorizeRedirectAttribute), and we
Assert that there are no the no
Controllers or action remaining, otherwise, we tell the developer which
Action needs to be fixed, and how to fix it as well.
Room For Improvement
The drawback of this is that our Unit Test project had to reference the ASP.NET MVC assemblies and gets more stuff than most tests should need. We can overcome this by moving our “convention” tests into another project completely, but for this project the conventions were very few and it seemed fine.
Of course the same method can be applied to any other convention you enforce in your project. One obvious example is ensuring all
Controllers inherit from a custom base
Controller class instead of the ASP.NET MVC class directly. I know people who already do this, as I mentioned in the opening the technique is not new by any means, but it’s worth even more popularity.
Speaking of improvement, the code for this test class was optimized a bit while writing this blog post, there is always room for improvement :)
In case you were reading the code carefully, the
IsNullOrEmpty() method I used in assertions is a custom extension method we had in the project, a very simple one as you may expect:
And That’s it!
I hope you found the technique useful if you haven’t used it before, or found the post a good place to reference it to those who didn’t.
How did I learn that?
As a bonus for coming here, I'm giving away a free newsletter for web developers that you can sign up for from here.
It's not an anything-and-everything link list. It's thoughtfully collected picks of articles and tools, that focus on Angular 2+, ASP.NET (4.x/MVC5 and Core), and other fullstack developer goodies.
Take it for a test ride, and you may unsubscribe any time.
You might also want to support me by checking these out [Thanks]: