My default asp.net architecture
OK, so a few days ago, we at Elegant Code hosted an Open Spaces dinner. A question came up about what architecture we use for various projects — specifically Asp.Net WebForms. This sort of thing has been published by others, but I thought I would throw mine out there. Also note: this is where I am right now. This has changed a lot over the years, and will change again in the future.
Here is a basic outline. First you have your project name. I’ll call mine: Project51. So I set up the following projects:
- Project51.Presenter or Project51.Service
Project51.Web is a Web Solution Project. None of that Web Site junk, thank-you-very-much. I’ve been forced to work with Web Site projects far too many times, I don’t like them. Anyway, more on this project later.
Project51.Domain will contain the classes used to host data from the database. These could be LinqToSQL classes, NHibernate domain classes, SubSonic, or just custom classes.
Project51.Repository will contain my classes for talking to the database. If I’m dealing with LinqToSQL, then my LinqToSql queries go here. Same for HQL or SubSonic expressions. Also, if I’m dealing with NHibernate, this is where I put my mapping files. And this is the ONLY layer that talks to the database.
As for what the classes here look like: typically I pick an aggregate root (top node that everything else stems from) or a specific task and name them that. So I could have a SecretProjectsRepository, which would have a SaveSecretProject(SecredProject project) method. This method would then take care of saving any of the data underneath it as well (like alien bodies and UFOs)
Next is Project51.Presenter and/or Project51.Service. I go back and forth on this one. Depending on the size and complexity of your project you may need both, or just one of these projects. But, I keep with the idea that you should probably have more presenter classes than you have web forms. Something like WebForm.Count <= Presenter.Count. But a presenter is designed to talk to a view (through an interface) to perform a specific task. I remember talking to Scott Cate, and he actually has separate presenters for saving and loading specific data. That would have saved me a bunch of trouble if I had done this. I’ll show what a presenter looks like below.
Next a word of warning. A trap that I got into a while back was to use primitives to get data from the view to my presenter. If I had a Name text box, sure enough, my view had a Name property. If I had a Birthday text box on my form, then I would have a corresponding Birthday property. Don’t do that. It makes your interfaces huge and chatty. This hurts testing and maintainability in very bad ways. Now, when sending information between my presenters and views, I use objects.
But what goes in Project51.Service? In a typical application there are a whole host of things that really don’t belong in the presenter, they are just general needs. Typically for logic that gets passed around. That is what I put in services. For a simple project, sometimes I’ll forgo the Presenter and just have a bunch of services. Please note on this one: I’m still reading Domain Driven Design, this definition is bound to change.
Project51.Common mainly contains interfaces. Interfaces for the repositories, services, presenters, and views. Lately I’ve also been putting some extension methods here as well.
Finally is Project51.Web. I’m bringing this one up again, because this is where I hook everything up. Specifically, this is where I initialize my IOC. You can use Unity, StructureMap, Spring.Net, or Castle Windsor for this (there are others, but I haven’t used them). I add all of my Services and Repositories to the IOC using various methods. The two layers that my IOC does not know about are my views (the web pages) and the presenters.
The final bit is some code. How to hook the first few layers together.
First the Page:
Next, my starter Presenter will look like this:
The only real “tricks” that I’m using here are constructor chaining in the presenter (the first constructor is calling the second presenter), and I added a Page_Init handler (most pages have a Page_Load, but not a Page_Init).
Is this the best way to do things? Probably not. In fact, please tell me where I could do things better.
Also not mentioned is how to test this…but that requires mock objects, and I really don’t want to get into that right now. OK.