I've been toying around with reorganizing the default MVC template and thought I would share it with people. I like the way it's come together.
Reorganized
When I was working heavily with Rails I came to really enjoy the way the applications are structured. It seemed... clean in the way all parts of the application had an especially relevant bucket to live in. I especially liked the way the application bits (logic, data access, controllers and views...) were kept nicely in the "app" folder.
I'll be honest and say I'd like to see our default MVC template change a little bit. Phil's team and I (I'm not on Phil's team - I just annoy them) discuss this stuff a lot - and it's been brought up a few times that perhaps I should make a template of my own to show what I'm thinking. And I think that's a great idea...
So I reorganized it some. Here's what I'd love to have my site look like:
You'll notice that I'm keeping the "App" folder approach, and sticking the Controllers in there as well as the Views. In addition I have a place for DataAccess and Services (business logic) as well as Logs and a folder for the Dependencies.
I've also partitioned a folder called "Public" - this is where (I think, at least) the support files should go, as well as any static files (like HTML, etc). I've also added CSS and Scripts folders too.
Yes it looks a lot like a Rails site, but if it works - why change it?
Basic Hookups
But why stop at folder organization? I did a few other things as well:
A ViewEngine of My Very Own
The default ViewEngine that comes with ASP.NET MVC (the WebFormViewEngine) looks to the root of the application for the Views folder, and I needed to change that if I wanted to shove everything into the App folder - so I had to create my own :). Thanks to Phil and team for making this rockin easy - all I had to do was to overwrite the location formats and I was good to go:
public class SubSonicViewEngine : WebFormViewEngine { public SubSonicViewEngine() { MasterLocationFormats = new[] { "~/App/Views/{1}/{0}.master", "~/App/Views/Shared/{0}.master" }; ViewLocationFormats = new[] { "~/App/Views/{1}/{0}.aspx", "~/App/Views/{1}/{0}.ascx", "~/App/Views/Shared/{0}.aspx", "~/App/Views/Shared/{0}.ascx" }; PartialViewLocationFormats = ViewLocationFormats; } }
To implement this ViewEngine I had to add one line of code to Application_Start in the Global.asax:
ViewEngines.Engines.Add(new SubSonicViewEngine());
... And that's all there was to it!
Open ID Support
I've also added Open ID support to the Login page, and I've left a place there for you to put your own ID Selector. To implement, you simply need to head over to IDSelector.com and fill in 3 bits of information. They give you some code, you come back and put it in place et voila! You have OpenID:
I've included the OpenIdAuthenticationService class that I've been using with the Storefront as well. This class handles the redirect to OpenID (and also the callback) - you simply need to implement the code for integrating the user and their URL.
To get this to work, I have a new Action in the AccountController which I call directly:
public ActionResult OpenIDLogin() { bool isValid = false; string claimedUrl = Request["openid.claimed_id"]; if (!String.IsNullOrEmpty(claimedUrl)) { Uri openIDUri = new Uri(claimedUrl); var svc = new OpenIDAuthenticationService(); isValid = svc.IsValidLogin(openIDUri); if (isValid) { string returnUrl = Request.Form["ReturnUrl"]; FormsAuthentication.SetAuthCookie(claimedUrl, false); if (!String.IsNullOrEmpty(returnUrl)) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "Home"); } } } return View("Login"); }
The line "Request["openid.claimed_id"] might look weird - and you might be wondering why I'm 1) using "Request" instead of Request.Form and 2) why I've named it something like that.
The answer is that this action gets called twice - once by the login form (via POST when the user clicks "login"), and once by the OpenID provider (via GET on the redirect). The Open ID provider will return a querystring which has the "openid.claimed_id" key in it, and I want to be able to read that in - so I need to access the entire Request name/value collection.
The Service class uses DotNetOpenID to redirect off to the provider, as well as receive the callback:
public bool IsValidLogin(Uri serviceUri) { bool result = false; var openid = new OpenIdRelyingParty(); if (openid.Response == null) { // Stage 2: user submitting Identifier openid.CreateRequest(serviceUri.AbsoluteUri).RedirectToProvider(); } else { result = openid.Response.Status == AuthenticationStatus.Authenticated; if(result) { //synch the users } } return result; }
... and there you have it.
And Finally - What This Isn't
I have bigger and badder plans for SubSonic than just this (I'm working on that stuff now) so even though this has the SubSonic name to it - please don't think it's what I've been working on for the last 9 months :). This is the result of me geeking out over the weekend and wondering if others would find this useful.
What I'd really like is your feedback with respect to the things I've done with the template - specifically:
The main reason is that if you like what you see here I can raise the ideas to the team and say "look what comments I've received on my blog". Or, conversely, if this is too complicated I can stop bugging them :).
Download the Template Here (uses Preview 5). Stick the zip file into your C:\Users\[USERNAME]\Documents\Visual Studio 2008\Templates\ProjectTemplates folder and restart Visual Studio. The template will show up under "My Templates":
Looking forward to your thoughts!
>> I don't see JQuery or calendar included in the template.
"I have bigger and badder plans for SubSonic ..."
I think there may be a few of us here intersted in what these "bigger" and "badder" plans are ... when they will be out in the wild ... and if folks like me who start with a structure similar to the one you present here are going to have a bunch of headaches should we choose to move towards the bigger/badder release.
Thanks again
I want to speak in specifics - especially with dates. The problem is that I'm working on a platform that's also evolving and it takes time. We're not even at beta yet - I need to ask for your patience.
RE: Themes. I put my themes in the content folder thought. My theme engine is very simple, look at a setting and based on it, I route all images and css request to the appropriate theme folder e.g. content\themes\red, content\themes\blue, etc.
I also vote for a separate Model project. I just opened this MVC project template and I easily found the V and the C folders but where is the M folder?
thanks again.
a) Simple
b) Pro BBQHAXWTFRoflChopterPEWPEW
Simple will be used by those starting out or want to do simple, quick, small sites.
Pro-BBQ will be used by the rest of us.
Simple == the suggested template above.
Pro-BBQ == multi projects in the solution, with things like the data access and services in their own projects. Don't forget web deployment and tests :)
Paul - seriously :). Folder structure has nothing to do with code separation. Rails and Django do just fine without it...
1) VS Express can't do multiple projects
2) Separation of projects is an aesthetic. If you like it that way you're welcome to do it that way :).
On using the Rails structure - I agree, why change it (at the same time not really that fussed, the main structure is there with controllers, models etc). I noticed "DataAccess" as opposed to "Models"...
Great idea with using IAuthenticationService in the accounts controller.
Big +1 on the Mailer - I find most sites send emails so why not include it.
+1 on the logging too - I have not coded a site that does not have logging.
I like that DI is in there by default but is MSFT going let that fly (as opposed to using Unity etc).
I usually find that I end up with at least 2 base controllers - one for public (PublicController) stuff and one for "members" (MemberController) with the main difference being authentication etc. Not a big thing but if we include authentication by default...
One biff - I hate seeing the "aspnetdb.mdf" db in the app_data folder - it's fine for development land but a long way from deployment. I would prefer to see setup scripts etc (I know there is aspnet_regsql.exe but again there's a bit of a disconnect with the sites code).
All other bits I passively agree with!
BTW, nice job ;-)
(PS, check line 11 of Site.Master and the MasterPageFile path in Error.aspx)
- pull the Dependencies dir out of the app directory, and into a "Vendor" (like rails) or "Dependencies' on the root.
- move the Helper directory into the app dir.
I would love to see this the default for the MVC project, though!
The Helpers folder was moved in recently (forgot that one:) and should be present in /App in this download.
\ExternalArtifacts\Dependencies - for DLLs
\ExternalArtifacts\Libs - for c# code..
Any updates on the chances of this becoming the default?
User.cs, Category.cs, Order.cs, OrderItem.cs, Product.cs, etc..
You originally had these in \Commerce.MVC.Data\Model ... so i'm guessing this might be in
App\DataAccess\Model?
what about interfaces for these classes? do you or will you worry about that? i'm thinking if u wanted to moq out a list of Products .. u'd need to have an IProduct. ... and if u agree with that .. where would you throw these interfaces?
i've never used VSE so i didn't know about that multi-project limitation. I agree whole-heartingly that it should be targeted :)
Looking forward to the next cast :)
The one thing is that, in the Rails world, defaults matter. Having a name for a directory that everybody uses matters. I don't care what the name is, as long as everybody knows exactly why it exists, what should go in there, and why you should keep the convention.
Either way, though, this MVC template is thousands of times better than the default project already, and it's great to see this happening!
I would love to see this get extended to a SOLUTION template... which would then closely mimic your storefront solution layout.
I like how you have included in the template above
a) NLog
b) OpenID (and interfaced it)
c) StructureMap for DI
I think those are some very important and common components.
Lastly, if i had to nit-pick .. maybe i would put the Log folder with Site.log in another location ... maybe in the root? But it still works, where u have it now.
Awesome!
anyway... If I want themes to be one of subfolder, how would you place it? inside Views? or inside root folder?
I'd also mimic Liam McLennan's earlier post - I tend to place services and data access in separate projects. Kind of hard to champion 'separation of concerns' when everything is wacked into one fat assembly.
To be honest I think 90% of ASP web apps are over-complicated in terms of structure. Most can just be placed under the app root.
Other than that - I think Views/Themes works for me... thoughts?
Separation of concerns doesn't have anything to do with the file system :) and that's sort of what we're talking about here :).
I share the notion that entities should _probably_ be in a separate dll, which enforces things like persistence ignorance. Also for my apps, we have many web applications share a common set of libraries, which you have baked in here. "90% of web apps" seems way too high to me (though I do agree with you in principal... most apps are too complicated).
In the future I'd like to see the File->New Project wizard go something like this:
-Name of project
-Create Test Project?
-nunit/mbunit/mstest (I think these templates should ship w/ the product)
-Which View engine?
- webforms
-nvelocity
-brails
-nhaml (again, these should ship)
-Entities/services/data access included? if not create separate projects, prompt for names
-DI framework
-StructureMap
-Windsor
-Ninject
-Spring
-Logging? (great idea)
NLog / log4net / EntLib
Another point of feedback... you shouldn't have to create your own view engine implementation if you just want to change the folder structure. You should be able to set those properties on whatever current view engine is in place.
Opinionated is good, but ultimately you'll get flamed for the choices you make :) I'm also thinking that this "uber-wizard" would probably be annoying and confusing to the beginner.
Maybe an MVC Template Generator where you decide these things once and it packages it up into a project template?
Agreed RE entities however :).
RE the ViewEngine - didn't know that :). I had tried to do that and it didn't let me but maybe I did it wrong :).
RE Flamed - meh that's my job :). I appreciate your feedback :).
as someone with experience in both the asp.net and rails world ... i love seeing how you have (and continue) to incorporate so many of the elements that make RoR a great framework. ASP.NET MVC + Subsonic, imho, put together the right way and with some of the code gen bells & whistles we have in RoR, would prove to be a very viable alternative to Rails (especially for folks deploying to Windows). i'm just waiting for you to tell us that we can do a "script/generate
does the template here incorporate subsonic as well?
btw, love the migrations addition in Subsonic.
btw btw ... I think there are plenty of folks out there who would love to see one of your screencasts discussing the interplay of ASP.NET MVC with Subsonic. there is some serious potential, like i said above, to put something together that could even rival Rails.
thanks again
As it appears you're essentially starting from scratch with your framework structure, perhaps you should look at build and deployment too?
If you're going to have an opinionated framework, you're able to stipulate where items are, hence you can create an msbuild script that'll build your web app?
Everything else looks like good changes Rob!
Re: ViewEngine - When I said "you shouldn't" I didn't mean you can do it the way I suggested. I meant you should poke the team and ask them if they can add this. :)
Also - but this is irrelevant when you are removing the forced Uri part - you shouldn't just rely on parsing the user´s input as an Uri. If something goes wrong by parsing the identifier (or getting the AbsoluteUri), your current validation will throw an exception. I think we both agree that we would give the user some feedback instead.
Kudos for your work with the template!
do you plan to reorganize the MVC Storefront as well according to this template or do you plan to keep it as it is now?
I like the way you structured the MVC Storefront and I don't understand why I should use this template instead.
Thanks
You should consider updating the AccountController and associated views to utilize the new Preview5 code. David Cumps has already done the work at http://blog.cumps.be/modified-accountcontroller-preview-5/
and give up ruby for c sharp???? never! :)
The project file 'C:\Documents and Settings\[USERNAME]\Local Settings\Temp\q53i4ukk.yax\Temp\SubsonicMVCSite1.csproj' cannot be opened.
The project type is not supported by this operation.
I am running Xp, VS 2008 SP1, .Net 3.5 SP1, MS SQLExpress 2005
What can I do to fix this?
Lets say my i-name is =example. When you are receiving this and parsing it to a `Uri`, a `UriFormatException` is thrown (=example is obviously not a valid URI). Unfortunately you are not only limiting my support, but you are also throwing me an exception instead of a friendly advice or any other feedback.
For supporting XRI (as the DotNetOpenId for sure does), you have to remove the line parsing the identifier to a `Uri`. You also need to change the parameter type of the `OpenIDAuthenticationService.IsValidLogin` method from `Uri` to `string`. This will enable support for XRI identifiers.
To resolve the usability issue (i.e. validate the identifier) you should call the `Identifier.IsValid` method from within the `AccountController.OpenIDLogin`. If it is false, tell the user that the provided identifier is invalid. Maybe you would have this logic in the `OpenIDAuthenticationService` class - it may fit better there. The `OpenIDAuthenticationService.IsValidLogin` is still needed. This is for performing the authentication process where the `Identifier.IsValid` is solely for confirming the validity of the user´s input.
I would love to contribute with better samples. disqus however is far from ideal when it comes to sharing code. Where did you find the sample on our DotNetOpenId site? We really need to fix this, if it includes the URI parsing part.
And great work on the template Rob, love to see this kind of structure coming with the Framework :)
Also - this is an MVC template so you need Preview 5 installed.
http://codebetter.com/blogs/jeremy.miller/archive/2008/09/30/separate-assemblies-loose-coupling.aspx
I like the idea of picking a convention. I would put the routing inplace to prevent access to the /App/Dependencies/ and other folders within the app.
I really like putting StructureMap into the mix and showing how you inject the Logger.
I had one issue though. The references to System.Web.Mvc and Microsoft.Web.Mvc were invalid and I had to re add them.
Don't know what that is all about.
anyways..
Cheers.
+1 for Unit Tests and Test repositories/moq repositories, whatever/etc.
@lowell ... don't get me wrong, I love and prefer Rails for a Linux/Unix environment where things like Passenger and Capistrano make it baby talk to deploy your apps but things sure aren't that easy when it comes to deploying your Rails app on a Windows box. As someone who lives in both worlds, I'd love to see either (a) Rails apps to be as easy to deploy and run on Windows as in a Unix OS or (b) a viable alternative for Windows apps that encompasses all of the Rails goodness and more. And I think ASP.NET MVC + Subsonic + JQuery may be that alternative.
note: :)
I like it, BUT, I too am having a problem with the template. After creating a MVC project using the Subsonic template I get 44 errors when compiling (I do have Preview 5 installed). Looking at the Global.asax and HomeController following an attempted compile, I see exclamation icons on the Microsoft.Web.Mvc and the System.Web.Mvc references.
Im using VS2008 PRO on an XP Professional notebook with the ASP.NET 2.0 and 3.5 Extensions, etc.
Any ideas?
TIA,
--Steve Shaffar
I'm reloading the zip right now - should be done in 30 seconds or so...
Please make it happens, throw some light on it!
We are also starting to reuse some of our controllers and creating yet another project for them and it has been great for reuse accross multiple mvc sites with simliar functionality. it maybe a little overkill for a template, but we really get a lot of reuse out of controllers that we inherit from in another dll.
Maybe it ends up being two versions, one for bigger scale projects and one for one off sites.
Basically what I meant was that the _whole thing_ is application specific so why even have an app folder. In (for example) Cakephp (php mvc framework) you have the framework (to correlate this would be ASP.NET MVC and not the whole .NET framework) which has to be deployed with the app, so you need to seperate, but in the ASP.NET MVC case there is no framework that get's deployed with the app (other than some dll files) so there is no need to separate them.