Friday, May 4, 2012

A Really Sweet MVC 3 Project Template: Part 2 – The Web Project

Chris Domino, Director, Enterprise Architect

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.

Starting from the Visual Studio solution we created in Part 1, add a new MVC 3 Web Application. Choose the "Empty" template, which only provisions the main MVC folders (models, views, and controllers) and pulls in the latest jQuery, ASP.NET AJAX, and modernizer JavaScript files, and a few other goodies. Next add references to our ClientB.Common and ClientB.Data projects.

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:

       
       
 
  1. html
  2. {
  3. overflow-y:scroll;
  4. }
  5. body
  6. {
  7. margin:0px;
  8. height:100%;
  9. font-size:10pt;
  10. font-family:arial;
  11. }
  12. textarea
  13. {
  14. padding:5px;
  15. resize:none;
  16. outline:none;
  17. height:100px;
  18. font-family:arial;
  19. }
  20. textarea:focus,
  21. input:focus
  22. {
  23. outline:none;
  24. }
  25. a
  26. {
  27. font-size:10pt;
  28. text-decoration:none;
  29. }
  30. a:focus
  31. {
  32. outline:none;
  33. }
  34. a img
  35. {
  36. border:none;
  37. }
  38. input[disabled="disabled"],
  39. input.disabled
  40. {
  41. cursor:default;
  42. color:graytext;
  43. }
  44. input[type="text"]
  45. {
  46. padding:5px;
  47. }
  48. input[type="submit"]
  49. {
  50. margin:0px;
  51. border:none;
  52. padding:0px;
  53. color:#0071bc;
  54. font-size:10pt;
  55. cursor:pointer;
  56. background:transparent;
  57. }
  58. .label
  59. {
  60. color:#000000;
  61. font-size:9pt;
  62. }
  63. .labelerror
  64. {
  65. border:none;
  66. color:#ff0000;
  67. font-size:9pt;
  68. }
   
       

To round out the folder maintenance, I also create an "Images" folder at the root to store visual assets, as well as add a catch-all JavaScript file named "ClientB.js" to the "Scripts" folder. The next big chunk to tackle is the web.config file. We're going to be adding not only app settings and connection strings, but also configuring the ASP.NET Membership provider, making things easier when working with areas, and a few other miscellaneous updates.

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:

       
       
 
  1. <connectionStrings>
  2. <add name="Membership" connectionString="[dev connection string]" />
  3. <add name="Database connectionString="metadata=res://*/Entities.csdl|res://*/Entities.ssdl|res://*/Entities.msl;provider=System.Data.SqlClient;provider connection string=&quot;[dev connection string]&quot;" providerName="System.Data.EntityClient" />
  4. </connectionStrings>
   
       

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:

       
       
 
  1. <?xml version="1.0"?>
  2. <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  3. <appSettings>
  4. <add key="URL" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" value="http://clientb.com" />
  5. </appSettings>
  6. <connectionStrings>
  7. <clear />
  8. <add name="Membership" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" connectionString="[prod connection string]" />
  9. <add name="Database" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" connectionString="metadata=res://*/Entities.csdl|res://*/Entities.ssdl|res://*/Entities.msl;provider=System.Data.SqlClient;provider connection string=&quot;[prod connection string]&quot;" providerName="System.Data.EntityClient" />
  10. </connectionStrings>
  11. <system.web>
  12. <compilation xdt:Transform="RemoveAttributes(debug)" />
  13. </system.web>
  14. </configuration>
   
       

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:

       
       
 
  1. <system.web.webPages.razor>
  2. <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  3. <pages pageBaseType="System.Web.Mvc.WebViewPage">
  4. <namespaces>
  5. <add namespace="System.Web.Mvc" />
  6. <add namespace="System.Web.Mvc.Ajax" />
  7. <add namespace="System.Web.Mvc.Html" />
  8. <add namespace="System.Web.Routing" />
  9. </namespaces>
  10. </pages>
  11. </system.web.webPages.razor>
   
       

Next, to make EF assemblies available application-wide, add the following under system.web/compilation/assemblies:

       
       
 
  1. <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
   
       

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:

       
       
 
  1. <membership>
  2. <providers>
  3. <clear />
  4. <add name="AspNetSqlMembershipProvider" enablePasswordRetrieval="true" type="System.Web.Security.SqlMembershipProvider" connectionStringName="Membership" enablePasswordReset="true" requiresQuestionAndAnswer="true" requiresUniqueEmail="true" passwordFormat="Encrypted" maxInvalidPasswordAttempts="2147483647" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="1" applicationName="/" />