After doing a few MVC 3 pet projects, I've found myself starting each new one with a copy-and-paste sprint from the last. In one code session, I can have the SQL Membership provider, error handling, Email, user registration, and Entity Framework design and integration all ready to rock. However, as anyone who can spell "TFS" knows, this copy-and-pasting is of course bad. And I'm not just talking about manually touching up namespaces or hacking connection strings; those are more annoying than harmful.
But if your new application has some other company's logo? Or favicon? Or Email template? Or masthead? Not good. Sometimes a global find and replace is magical; other times, it can create far more (and more difficult) problems than it solves. So what I decided to do is create a generic MVC web application (and the encompassing Visual Studio solution) that has everything so I can clone it and strip out what I don't need when I'm onto the next project.
To avoid yet another blog past that has a massive procedure with dozens and dozens of steps, I'm going to break this up into different sections for each component of the application "template." (I put template in quotes here because it's not really a Visual Studio template; I have code that actually clones a directory and outputs new code to more easily facilitate uniqueness of guids, give control over which file types are analyzed, etc. More information about this "Project Cloner," written by Jonathon Rupp, will be available at the end of this post's series.)
Before going through the steps I've taken to build out each project, let's start with the database. Of course, if you have no database, (nor a need for any other component of this structure) just skip over that particular skip. We're doing the database first to keep the order of operations aligned with project dependencies, so that we never have to "go back" to a project once we're done configuring it.
First things first: create a new database (named ClientB) by any means you choose. (SQL Server Management Studio is about the only time I use a designer; it's the fastest way for me to rig up my database model.) Then build out the rest of your tables, views, etc. Something I almost always have in my applications (the ones that require administrative/CMS functionality at least) is the typical hierarchical term table to model the site's taxonomy. I'll be including that here so that there's something in the data model:
Next, since almost ALL of my MVC apps have users and forms authentication, I install the ASP.NET SQL Server schema. Real quick:
There are slightly different versions of this infrastructure depending on which version of ASP.NET and SQL Server are installed; we'll deal with this later.
The generic starting point for this project structure is called "ClientB" (which you might have guessed from the database name). Sub in your app name wherever you see that here. When the Architecture Council at Rightpoint came up with our project template for SharePoint work, we really flexed our creative wings and went with "ClientA" for that. Following suite, and as part of the ensuing inside joke, this mess is heretofore going to be un-intuitively pronounced "CientB." So to start off on the code side, create a new blank Visual Studio 2010 solution called ClientB.
The Visual Studio solution is made up of five projects, but you probably only need three. Technically, I guess, you can have just one and shove everything in there, but that's simply not proper. The three main ones are:
The two more optional ones are:
That said, let's start with the most optional one: ClientB.Dependencies. Add a new class library project, and delete the "class1.cs" that comes along with it. To start, just add the following file to the project: C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 3\Assemblies\System.Web.Mvc.dll. Even though this is a DLL, add it as an "Existing Item," not a reference.
This way, when we reference this (or any other) DLL in a project, we'd do it by browsing to this file, so all relative references come down whenever the latest is gotten form TFS. The goal here is to be able to get, compile, and run in a new developer's first few seconds on the project.
Let's finish up with the optional projects. Add another class library named "ClientB.Common" and once again delete "class1.cs." I usually add two classes to this project: Constants.cs and Utilities.cs. Both are static and public, and, like I said, contain any and all functionality that is to be shared across multiple projects in the solution. If you or your organization has a common library, that could replace or compliment this.
There are going to be a lot of places around the application where the name of the app is displayed: page titles, Email signatures, etc. In order to avoid hardcoding the name of the app everywhere, I like to refactor that into a constant - especially when the name of the app (or even the company!) changes half way through the project. This is especially helpful to support us safely cloning this code from project to project. Here's what the constants class looks like:
Sending Email is a great example of logic that belongs in the Common project's Utilities class. I use two methods: one to send the Email itself, and one that wraps it for sending error Emails (when you're in a hosted environment and don't have access to logs (beyond writing to a text file)). Add a reference to System.Configuration and check out the following code in Utilities:
That's actually as far as I'm going with this one; refactoring is specific to each project as well as each developer's tastes. The above Email methods might be a bit too specific for our purposes here, but the code I use to generate paragraph tags seems to look consistent across most Email clients, so I thought it might be useful to just include it. But remember: if you don't want it in your template, just delete it. The goal here isn't to create a rigid one-stop-shop for all web projects you'd ever do. Instead it's just a starting point; it's stuff I've had to replicate on almost all MVC work I've done.
SQL Server projects were a game changer for me. Synchronizing database schemas across my laptop, desktop, other team member's machines, as well development, staging, and production servers, was always a nightmare. What was the latest version? What state is my database in? Why doesn't my application work? These are all questions easily answered by bringing your database under source control, and throwing a nice schema comparison tool into the mix for good measure. If you have any database dependencies on your project, never leave home without one of these.
First, create a SQL Server 2008 Database project, named ClientB.Database. If you have the SQL Server tools package installed from the Web Platform Installer, there's a better version of the Schema Compare tool; you'll need to upgrade your project to take advantage of it. In this case, right click your project and select "Convert to SQL Server Database project." Then *poof* you're there.
Next let's import our current schema. Right click again, and select "Schema Compare." In the first dropdown, select "Select Source..." and configure a connection to the database. Then specify "Select Target..." in the second and configure it to talk to the project we just created. When you've got that, click "Compare" at the top.
Here's where I'd like to revisit the ASP.NET SQL Server schema discrepancies that can crop up. Not only will your development machine (no doubt running the latest version of everything; possibly the betas of the next versions of everything) probably have a different version of aspnet_regsql than your production environment; (no doubt running an older version of things, which, for example, Go Daddy does) there will also be security concerns between your database project in Visual Studio and the database itself. Namely, I'm talking about importing users, roles, and schemas. In a SQL Server Data project, think of all SQL files as "executable" scripts. If you have bad TSQL in one of them, things won't compile.
So to combat this, I simply ignore slight differences between the schemas of tables and views and the code of stored procedures and database functions, and don't even bother with any SQL objects that could be related to users. Upon every comparison, clear the "Action" checkbox next to any "Type" under the "Add" node that starts with "Role," "Schema," or "User." We don't need it, and like I said above, we don't want to deal with trying to synchronize it across our different databases.
Really the only parts of the SQL Membership infrastructure that I use are the authentication and authorization subsystems, which is only a slice of the technology. If you're doing profiles and whatnot, there's more to consider; the basic functionality, however, works all the same across the various versions. So, despite all this hacking on the backend, no .NET code will change in the middle layer.
Schema Comparison and reconciliation is about all I use this project for, although it can do a lot more. Really the only other functionality I need here is a place to store any random data-population scripts I might find useful. I usually have something that truncates my tables so I can test new code against a clean slate; it's so specific to the model, however, that there's no use for it in this template.
The concept of the anonymous user, however, is fairly pervasive in public-facing web sites. I like to go ahead and create an actual record in the database for the anonymous user with an empty guid for its UserId. This way, instead of nullable foreign keys, every entity in my model that has a relationship with a user can have a nice tidy link to one.
While we're at it with scripting users, let's add in an administrator. If our application has users and administrative functionality, chances are we're going to need some concept of administrators. I implement this by using the SQL Membership's cousin: the Role Provider, which is a simple implementation of groups in the database.
So let's take a look at the following script (or a slight derivation of it, depending on requirements) to make sure these users exist:
Now that we have our users scripted, go ahead and execute this against the database to get those guys in there. Finally, we'll add it to our database project for safe keeping. Right click the "Scripts" folder and select "Add..." and then "Script..." In the window that pops up, make sure to select the "Script (Not in build)" template. This allows us to store arbitrary SQL code in TFS that the project doesn't attempt to "compile" upon each build. Name it "Users.sql" and we're done.
You might be wondering why, after pulling all this wonderful new SQL integration technology into our project, we're still manually running scripts in SQL Management Studio and using it only for source control. It really comes down to the fact that deploying the project (which you can (and I do) turn off in Configuration Manager for one or all the build configurations) is slooooow.
Even on my home computer, which is an i7 with a million gigs of RAM, it adds a few seconds of churn to every F5 without any updates actually being made. Even though I love love love scripted deployments of any kind, the overhead required to configure this single run-once script simply isn't worth it. Script deployments to save yourself time, not create more work.
Next is the data layer, for which I turn to the Entity Framework to implement every time. If you want to use Linq-To-Sql or CSLA, (juuust kidding) feel free. But EF, especially the 4.0 release, has been real solid for me. So add another class library project, delete class1, and add a reference to System.Web. Once that's all set up, we can import our data model.
Add a new item to the project of type "ADO.NET Entity Data Model" named "Entities.dbmx" and complete the ensuing wizard to select your database connection (which will of course be to the database we created at the start of this process). Something I always screw up in this wizard is which setting dictates the name of the class that will represent my context. This is the screen were you specify the name of the connection string. I go with "Database" but you can use whatever you want. I feel this is a little cleaner than "[name of solution]Entities" (which is what is generated by default).
At the end of the wizard, you specify which tables, views, and procs you want imported into the model. On this screen, expand the "Tables" node and tick off "aspnet_Users," "aspnet_Membership," "aspnet_Applications," and "Terms." In most cases, you'd probably only need the users table, but depending in your style of usage for membership, it might be nice to take all three. The terms table, again, is just for example purposes.
EF actually does a fairly good job of coming up with singularized and pluralized names for your entities and collections of them. However, the membership tables all have that "aspnet_" prefix and are pluralized; the OCD in me cannot stand for this. If grammatical correctness, properly naming your entities, and destroying all superfluous navigation properties is a must for you as well, perform the following clean up:
The above exercise, renaming the "Term" table's navigation properties, is the main reason I included the table at all in this structure. I wanted to demonstrate that even when EF gets the name wrong, it's really easy to correct it. Maybe your brain is fine with "Term1" and "Term2" and can deal with it; mine can't. Once you have all your entities the way you want them, the next step is to start extending them.
The partial class-ed-ness of EF entities (and the context itself) is, in my opinion, one of its strongest extensibilities, and makes tasks like databinding, just, well, stupid easy. I will demonstrate this by extending the context to return the current user, and one of the entities to add a read-only property for databinding purposes.
So first, let's get the current user. Add a class called "Database" or whatever you named your entity model to the project. Here's what it'll look like:
Extending entities is just as easy. Let's say we want to display a user's Email. In the SQL Membership infrastructure, Email is actually on the aspnet_Membership table, so we'd have to join and pull it in. But if we partial class our User entity, we can add a new ready-only property, abstract that join out, and have it display nicely (in a grid, for example) along with the rest of the "native" user properties. To see this, add another class to our Data project named "User.cs" and use the following code:
As long as you manage the disposal of your context properly, you can add a bit of logic to extended properties and make your UI databinding logic a lot easier.
So this post turned out to be massive; I'm going to present the Web project in a separate one, since it's far and away the biggest. Doing so sort of creates a nice separation between the idea of an MVC solution, which is all of the physical tiers of a web application and an MVC site, which is just the web components. Perhaps you have a completely different approach to setting up your MVC infrastructure, and just are interested in the web bits. If that's the case, I apologize for however long you spent reading this, as your question your question won't be answered until the next post. My bad!
Read on here.