In Part 1 of this series, I introduced my version of a template for ASP.NET MVC 3 projects that contains the data access layer, a common library, and some other components that I tend to reuse on every site. Before diving into the final slice of the pie, the web project itself, I want to reiterate that this template isn't supposed to be the end-all and be-all of MVC projects; it's the stuff I need 90% of the time; use the cloner (code that'll be included in this post that clones folder structures, replaces guids, and other fun stuff) and strip out what you don't need.
I usually end up deleting the "Content" folder and all the theme crap under it in favor adding a "Styles" folder with a single Site.css stylesheet under it. All the jQuery theming stuff is cute and all, but it just feels a bit overkill to me. Besides, these assets aren't really "content;" the content is in the MVC views. But I'll leave it in the template (however, the stylesheet will be moved into the above-menioned "Styles" folder) in case you need it. Included in my CSS are a few pervasive styles that I've found useful on many sites I've done. Here they are:
First, the connection strings. Copy in the one for Entity Framework in the Data project's app.config file with a name "Database" and pull out the actual connection string for another entry called "Membership." This second, more conventional one will be used by our authentication provider. Here's what it should look like:
Next we want to use the Visual Studio 2010 feature that allows you to map web.config file modifications to build configurations. Ideally, your "Debug" settings in Configuration Manager match your local / development / staging / test servers, and "Release" mode matches production. If you have different environments or multiple developers, you might want to create additional configurations. This is way better than checking a web.config file into TFS with commented out settings blocks for each team member. Adorn each setting with the following transformation attributes where appropriate in the web.config.release file:
This way, when you build in release (or whatever) mode, Visual Studio will poop out the appropriately-generated web.config file. Notice that one of the app settings here is the URL to the site. Why, you might ask, would some of these settings be here while others are implemented as constants in ClientB.Utilities? Basically, if a string is dynamic (in terms of environment) it should be an app setting. A good example of this is an Email address from which automated notifications are sent. In development, you might want to use your own; in production, a generic one should be selected. However, something like the name of the site or an Id that's being used for a third party integration (like a credit card merchant account) will be constant across all configurations. I throw these in the "constants" class to avoid hard coding and allow site-wide changes (or clones for new sites) to be executed easily and safely.
Next is a tweak that makes areas able to consume assets in the "root" of the application. I'm not too versed in the details here, but it's one of those "do this and it works" kind of things. Take the following blob from the web.config file under "Views" and move it to the root one:
Next, to make EF assemblies available application-wide, add the following under system.web/compilation/assemblies:
Now let's get into the authentication stuff. First of all, here's the basic goo that's needed to wire up the membership and role providers:
That's real basic stuff, but with a bunch of settings defaults I usually go with that are still worth mentioning. In the membership provider, I have it configured to have unique Email and encrypted passwords while allowing them to be retrieved, question and answer turned off, as well as account lockouts disabled. You can't really "disable" this, so they need to get int.MaxValue invalid attempts in one second to get locked out. Finally, I have password strengths that require six characters with at least one being non-alphanumeric.
Finally, in order to facilitate password encryption and decryption, we need to have a valid machine key in the web.config file. These should really be unique for each web app you write, so I use the following generator to get a new one. Here's an example of what an entry (in system.web along with the rest of the authentication configuration) looks like, with the encrypted bits stripped out:
Next we have the Email section. I like to wire this up in the web.config file so that the Email code I have in ClientB.Common will pick up those settings and just work. Much like with the connection strings, these settings might differ from development to production, so use your web.config.whatever files to manage them. Here's what that block looks like:
That's it for configuration! Next up is to discuss is error handling. There are a lot of different ways to implement error handling, as well as a lot of third party tools that can take care of it for you. However, using an external component sort of goes against the grain of a generic template. I'll throw some shameless pugs out for Elmah or Log4net as they are the two I feel are used the most, and then go ahead and do my own.
There are basically three parts to error handling in my eyes: throwing errors, catching errors, and reporting errors. You won't find any examples of the former in this template, because it's more ethereal in the first place and I never encounter errors at all to any extent in the second place. Juuust kidding. Real quick though, throwing errors means deciding which exceptions are handled and which are unhandled. When you're building a particular functionality, ask yourself: "What if this bombs?" "What if there are no elements in this collection?" "What if it's null?"
Basically, if it could be null in some supported (or at least identified) use cases, I protect it with a try...catch. Or if the code is dealing with user input, and I can't anticipate every possible situation: try it and catch it. But if something should not be null, I purposely do not protect it; this exception has to be thrown so I know about it. Catching and eating is way better for your bug count and way worse for your application than throwing into the wind.
On the backend, in ClientB.Common, have some sort of static class through which all errors are routed. When you do purposefully catch an exception, call this method explicitly. What it actually does is up to your implementation. You can throw it in an event log, log file, log database table, or just Email it to yourself if that's the preferred route. It doesn't matter, as long as the "catching" part of the architecture handles them uniformly. But when an unhandled exception occurs, and you don't think the generic ASP.NET error page works well aesthetically in your application, we need to wire up higher-level logging.
And nothing is higher-level than Global.asax. Here of course is where you can handle global events in your application around start up, session, and, as it turns out, Application_Error! This event will be raised when any unhandled exception occurs in the app. Here's my implementation:
The only interesting thing here is in Line #15 when I base 64-encode the error message and pass that explicitly to a view via querystring. Although I haven't tried it (because it's hard to experiment with error handling logic when you have never had an error in your entire career) it might be a bit cleaner to use session or some other mechanism. But when you're handling global errors, you have no idea what might be broken; I always try to respect this and write the leanest code possible here.
So that's throwing and catching; what about reporting? The Emailing is included in the template because Email is always available for error logging; using a database or a log file or a third party control requires some "intrusion" into the architecture and/or surface area of the application. Email will more likely than not already "be" there, so for this template, we'll just piggy back that tier.
When we're still in the context of the website when an unhandled exception occurs, we need to add a UI to the error reporting in addition to the logging. This way, users don't have a false positive when something didn't work. Like I said, it's far worse to hide these errors than it is to show them; your users will be a little annoyed because an error occurred in general, but appreciate the fact that you spent a few extra minutes to make the experience as graceful as possible.
The view display is up to you; let's take a look at the controller that renders it:
Basically, it takes in the encoded error message, and attempts to decode it before passing everything down to the view for rendering. The only reason I screw around with encoding is because I don't like passing human-readable text in query strings. Not only is it sloppy, and lets users see up the skirt of your implementation, but I feel it could be a real easy to get hacked.
That's it for error logging! I won't go into too much detail for the error page itself; just make sure to slap a link on there so they can get back to the home page. You'll notice that the Visual Studio MVC 3 template includes an "Error" view in the "Shared" folder, and that view has code in it that unhinges itself from its layout. This, like my statement about code in the global error handler, is a protection against not knowing what's broken; it could be something in the layout. So be careful not to cause errors by handling errors!
I'm not putting this into the template, but I wanted to call it out real quick. With some of my little MVC 3 sites that are in production, (incidentally all hosted on GoDaddy) I get a lot of HttpExceptions regarding forgery tokens and other "Internet noise." I'm not sure if it cause by dying spam robots defeated by my MVC security measures, actual errors in my URL mappings, or something else entirely. I've found myself adding logic to my global error handling that swallows these, as they are sort of impossible to reproduce and fix. I hate eating exceptions almost as much as I hate Outlook taking ten minutes to load. Just something to think about...
</ Internet Noise>
Let's move on to the controllers, which are of course the brains behind the MVC pattern. Right click the "Controllers" folder and select "Add..." and then "Controller..." In the box that pops up, type in "Base" over the selected text so that the class name becomes "BaseController" and click "Add." We are going to have all of our other controller inherit from this one, so sort of think of it like the code-behind for a master page.
The two pieces of logic I'm including in this template are there to demonstrate two different paradigms of what you'd want to do with a base controller. The first one, which is basic navigation logic, sets Booleans based on which controller is handling the request so the layout can update itself accordingly (by building out a breadcrumb or selecting tab or highlighting a background). Paradigm-wise, this is example of code that needs to run on each load; master pages (or the equivalent) are a great place to take care of these tasks.
Also using the ViewBag, the second sets the current user, which will ideally be cached to avoid database hits on each load. This is the second paradigm a base controller implements: handling do-once-and-cache tasks across the application. Let's look at the code:
Whichever paradigm you're following, use base controllers to do the things that happen upon every request. This differs from ActionFilterAttributes, which instead encapsulate logic that could happen on every instance of a particular request. Whereas every single request cares about the current user (base controller), only certain requests need to make sure the current user is authenticated (action filter attribute). The following are out-of-the-box filters that you'll see all over the place in the source code included with this post:
Like I said, these apply common functionality to particular requests. A cool thing about these filters is that they can be applied to both controllers and action methods. Applying it to a controller is the equivalent to applying it to all action methods on that controller. A good example of functionality like this is an attribute that redirects a request to its HTTPS equivalent. If you have a particular request or controller that handles credit card transactions, decorate it with this attribute. If your entire application requires SSL, put it on your base controller.
From here on in, everything else is optional. I'll be discussing some common patterns or functionality that I include in some, but not all, my applications. The ideas above (and in the previous post) are part of almost all of the MVC work I do. The rest are edge cases, but certainly still "common" enough to be represented in (and potentially ignored or removed from) the template. To start, here's an example of the above attribute, called ForceSSL:
Not only is this a good example of the flexibility of filter attributes specifically, but it also shows just how much control you have in MVC in general. Take advantage of how close you are to the underlying HTTP request. In web forms, these would have to be HTTP modules or handlers (which are sort of obscured from the rest of your code); in MVC, they are right there with the rest of your business logic.
The next optional controller I'm including is the handling of "robots" for SEO purposes. Instead of dealing with a robots.txt file, (which is very un-MVC) you can rig up a path to a controller that does this dynamically. Here's that controller:
Few things here:
Speaking on ContentResults, there's also the nifty FileResult that your actions can return. I use this particularly to map a URL directly to my site's logo. This is useful in both the layout and in the Email template. First, store the relative path (something like "/images/logo.png") in the web.config. Then, create the following action (all of these "optional" actions are on the "Home" or default controller):
Line #4 has the "System.Net.Mime" namespace "using-ed" in. With this mechanism, "http://clientb.local/home/logo" will return the main image for the site. Finally, let's talk about the account stuff. After all of that wiring of the membership provider, we need to throw some UI into the app to facilitate all of the account-ish things users will need: registration, login/off, password request/change, etc. The "MVC 3 Internet" Visual Studio template pulls a lot of this stuff in for us, but I like to reorganize it a bit.
First, some sanity. There are UI services that come along with the out-of-the-box Account models, and the code is intermingled with them. I feel like service-typed things and POCO-typed things should not be neighbors. I like to refactor these services (and their interfaces) from Models/AccountModels.cs into separate classes on the root, named Services.cs and IServices.cs respectively.
There are even more refactoring opportunities in this file. The next thing I do is move all of the custom validators into a separate file. So create "Validation.cs" in the root of ClientB.Web and copy-and-paste anything that inherits from ValidationAttribute there. There's nothing specific about this code (or the services above) to the Account controller, so let's make it available to our entire site. I've also added some other validators that I use from time to time.
There's one more thing to do here, refactoring wise. There's a static method forlornly tucked into the "Status Codes" region that translates "proprietary" membership error codes into English strings. I move this guy, called "ErrorCodeToString," to the Utilities.cs class over in ClientB.Common. (Make sure you add a reference to the obscure System.Web.ApplicationServices assembly.) Once again, this logic not specific to the Account stuff, or even necessarily to our site, so its home should be Common.
Now we can get to the actual M in the MVC. What's left over are a few models describing the data that needs to be transferred to facilitate out-of-the-box authentication-ish functionality. Namely, these are the "ChangePasswordModel," "LogOnModel," and the "RegisterModel." I've basically kept these in fact in the template, but added "RetrievePasswordModel" and some other goodies. Here's what that looks like:
Next we need to build out the controller and views needed to implement these models. Now I'm not going to audaciously re-write Microsoft templates as pass them off as my own; the Account controller and views are fine as they are. I only tore apart the models because that file, in my opinion, is a bit sloppy. So all I'll do is add in the infrastructure to support password retrieval, which follows the same patterns as the rest of the out-of-the-box functionality here.
First, let's pull our nicely-refactored services into Controllers/AccountController.cs:
And then our password retrieval controller functionality:
The views are fairly straight forward, so I won't make this epic post any longer than it was to be.
And that is it! Attached to this post are two ZIP files: one for the MVC project itself, and one for the project cloner. To use the later, unpackage it, and run it as a command line exe or load it up in Visual Studio. If you're in VS, edit the project file, and enter the following as the "Command line arguments" in the "Debug" tab:
-s "[full path to root folder to clone]" -t "[full path to root target folder]" -o -g -n "[taget namespace]" -i bin -i obj -i pkg -i pkgobj -e vspscc -e vssscc -sourcename "[source namespace]"
This code is actually just a utility used by an in-house TFS generation app, and hasn't really been used extensively outside of that context; manipulate it at your own risk! As for the MVC project, unzip it and set it up in Visual Studio as you would any other. You'll see that there's a lot more in there than I write about because, again, this post is way too long. Like I keep saying, this is just a starting point; strip out what you don't need, apply your own patterns and styles, and, as always, have fun MVC-ing!