This is part of a series – see this post for explanation of the context: http://robseder.wordpress.com/2010/12/23/the-new-asp-net-part-1-of-7-getting-started/
Now there has been a lot written up MVC, but this will cover some of the basics specifically for someone coming from traditional ASP.NET. If you’ve done regular ASP.NET, then you’re familiar with the URL of a page being a one-to-one relationship to the file system. For example, a URL of /Customer/Default.aspx would point to a folder called Customer and a file called Default.aspx.
You are also familiar with the code-behind of a page being directly related to the HTML of the page – and you “post back” information to the same page. This idea made it so we could have “server controls” that maintained state using the concept of ViewState, which is an encrypted and base64-encoded string that was included in every page. This made for very heavy web pages.
Another fundamental problem with this approach is that there is still a fair amount of mixed code for UI logic and data. It’s never been really easy to separate the two. Even if you have a separate data layer, it’s unavoidable to have some data and business logic in the UI.
Model View Controller (MVC) is a fundamentally different way to consider building a site. The idea is to have a very clear separation between routing of a request, getting your data, and presenting it on a web page. This “separation of concerns” makes it so each responsibility can very cleanly just do its job. In the next post, I’ll go into detail about just how ridicuously far you can go with this! There is really no comparison to traditional ASP.NET, with this MVC approach, you can create squeaky-clean separation, which makes the codebase so, so, so much easier to manage and work on!
What is MVC?
With MVC, you have a “controller” which handles processing the URL, it can (optionally) get data from a “model”, and then passes the “view” the “model”, and asks it to render it.
So with this approach, the controller can JUST worry about “I get data from the model and pass it to the view” and that’s it. The model just worries about getting raw data, but not presenting it. The view has no idea who loaded it or where the data came from, but it knows how to present it.
Again, each part knows how to do it’s part and can cleanly separate the responsibilities and concerns.
When it comes to routing and controllers, the default and de facto standard is something like this:
so you might have URLs like these:
In the case of /Customer/index for example, that would end up calling the “Index()” method inside the “CustomerController” class. This method may (or may not) get some data from /Models/CustomerModel and then ultimately asks /Views/Customer/Index.aspx to render a web page.
“OK, so wait – what?” OK first, here are the conventions for an ASP.NET MVC application. There is a “Controllers” directory, which contains all the controllers; a “Models” directory that has models; and Views which have the views – but there is a directory under Views for each matching controller. This is a convention so these directory names MUST be used. So for example:
so if I navigate to /Customer/Index in a browser – the code on line 14 above will run, and loads the matching “View”. What is the matching “View”? By convention, it will be /Views/Customer/Index.aspx (matching “Customer” controller to “Customer” view directory and matching the “Index” action with the “Index.aspx” page in the view directory) like this:
The “model” part is not always used. It’s used when you have to get data from somewhere (file, web service, database, etc) and give it to the view. So, in our simple example above, we aren’t dealing with any model data just yet.
For a more complete example, here is what a more fully fleshed out version might look like:
One key aspect of MVC is that you can more easily write unit test because the bulk of your “work” is in the controller (and arguable in the model, if you want to use unit tests for integration testing). But mostly importantly, the “View” only has logic related to “making the page look pretty” – there is zero intelligence there and need not be tested.
So, as you can see above, I helped address this by making the “Controller” interface-driven and made it so you can pass in a mock model, via “constructor-injection”. In other words, one of the constructors take an object of-type ICustomerModel. When the ASP.NET run-time uses this controller, it will use the CustomerController() constructor which uses the regular CustomerModel class, but from my unit tests, I might create an instance by using CustomerController(mockModel) where mockModel is an implementation of ICustomerModel that has mock data, for example.
An approach for managing model and controller lifetime:
When I first started learning MVC and trying to figure out the best way to do things, I originally had some logic in the controller, to get data – just to get it working. Then I thought: “I need to get this logic out of the controller, and into a model”. This is when it became more obvious on when and why you might have separate model classes.
To be clear, a model class has no default structure and doesn’t need to inherit from anything. A model is just a class that does whatever you need it do. For example, here’s a pretty complete Customer model, as a reference:
Now, one real-world problem that I ran into is how to manage my entity framework instances. I’ll get into this more in a future post, but you need an instance of AtsfCrmEntities to work with, that is the EF .edmx entity model.
That entities instance is an IDisposable object and really should be cleaned up. If you put your code like GetAllCustomers() in a “using” statement, that brings back your data perfectly fine. However, within a view, if you want to work with your EF entity and try to get to related data (via reference properties) – like you want to get the IQueryable that is related to the current t_Customer – then you get an ObjectDisposedException because it doesn’t retrieve those hierarchical chunks of data until they are requested. Since you got the initial object inside of a “using” block, the context is now gone – hence the error.
What this means is that I can’t use “using” statements for how I’m using EF. This means the Model class can’t be static, I can’t really use a singleton pattern for it – it’s going to have to be an instantiable class. But if it is, then how will I take care of the dispose() on the EF entities object?
Well, what I eventually evolved to was that since the controller is what drives everything, and since the Controller class implements IDisposable, I basically wire all of that up. In the Customer Model, I have the Dispose() method dispose of the entity object. In the Customer controller, I have the Dispose() method dispose of the Customer Model. In other words, here’s a more visual way to explain it:
- CustomerController – instance created
- (in the constructor) CustomerModel – instance created
- (in the implicit constructor of CustomerModel) – AtsfCrmEntities – instance created
- (in the Action method) CustomerModel.GetAllJobs() called
- CustomerModel.GetAllJobs() gets and returns data
- CustomerController action method returns view
- CustomerController.Dispose() called by run-time, disposes of Customer Model
- CustomerModel.Dispose() is called CustomerController.Dispose(), disposes of AtsfCrmEntities
That way, everything cleans up after itself nicely – and best of all, I can still use those subordinate objects now within my view, without get an exception. Again, more on that in the next post.
Some of the takeaways for this post are these lessons-learned:
- Start from the MVC template in Visual Studio, that already has some basic functionality
- Create a matching model class for each controller. For CustomerController, create a CustomerModel. For JobController, create a JobModel – again with the model classes in the /Models/ directory.
- Make your model classes implement IDisposable, create a class-level instance of your EF entities and dispose of them in the Dispose()
- In the controller class, create a class-level instance for your model and dispose of it in the Dispose()
- Consider extracting an interface for your model, and allowing constructor-injection on the controller so that unit tests can optionally run tests against a controller that gets data from a mock data source, instead of a real database.