MVC in WebForms: The impossible fight to get rid of the views centric world
Let me start out by saying that I know of a lot of people that are doing MVC with WebForms, I am doing it myself right now, which put me in the position to talk about the pain that it brings. Let us consider the traditional MVC pattern:
Now, in such a pattern, who is supposed to be in control? To drive the application? If you have guessed that the "Controller" is the one that is supposed to be in control, you got the jackpot, but missed the boat (am I mixing methapors here?) as far as WebForms-based MVC platforms are concerned.
The WebForm model, a PageController with complex life cycle, has a direct affect on the structure of the rest of the application. After trying to get true MVC working on WebForms, I now believe it to be impossible to get it to work in a way that would satisfy me. My view on views is that they are:
- Stupid
- Presentation logic only
- Do not affect the rest of the application
For the purpose of this dicussion, I am going to ignore the differences between MVC, MVP, Passive View, etc. I don't have an issue with the view doing data binding, UI logic, etc. The problem is that there are only three models that can be used with WebForms & MVC:
- Front Controller that later invokes the Page - Basically, the way that MonoRail integrates the WebForm view engine. The problem with that is that ASPX isn't really that smart about stuff, so you end up having to do a lot of stuff in the code behind, which means that you usually need to call the controller for stuff.
This gives you wierd state issues, where the controller method has run, but then it is called again to get more information that the view is needed.
The main issue is with this are stuff like pressing a button, which is very hard to do in a web-nice way with WebForms, neccesiating to catch the event in the view and then call to the controller again. Ugly.
This is probably the best approach, but unless you are doing it with MonoRail, it is also the most expensive one in terms of time and investment. JB has some thoughts about this subject, from using MonoRail & WebForms.
Even when you are using MonoRail, this is awkward. - The Page is calling the Controller's methods. This is what I am using currently, but I find that this introduce the same state based issues that I am trying to avoid (orderring of method calls, etc) and it still gives too much responsability to the view.
It basically make the view a semi controller, because it is what drives the controller. The most painful parts are stuf like GetPolicies(), GetUsers(), etc, which are called directly on the controller by the view to get the data it needs.
I could do it the other way around, FillData(IView), I suppose, but that gives me awkward model for the state changes that is required in most complex screens. Basically, the FillData() would need to get the purpose for this, so I would have FillDataForDefaultCustomer, FillDataForChangedCustomer, etc.
Another issue is that my controllers are inherintly multi-views capable. That is mostly because of the architecture of WebForms & Atlas, this requires me to put some of the stuff that should respond to an Ajax call in a web service or HttpHandler. Those are all going to the same controller (same use case), but there have very different needs. - Registering to the view events in the controller. This seems to be a fairly favoraite pattern for MVP in ASP.Net. I have issues with that because we now has strong ties from the view's behavior to the controller behavior. I think that methods like: "void OnLoad(object sender, EventArgs e)" are code smell, and they don't give me much separation from the view. In fact, they are Code Behind done right, without dealing with the UI at all.
To be clear, all three methods give me a few things very important that it is very hard to get from naked WebForms, namely: Separation of Concerns & Testability.
The most problamatic part in this is that it is very hard to get view-ignorance capabilities using the WebForm approach, it is much harder than it should, and the page lifecycle issue is paramount in both view and controller.
My current approach in WebForms MVC is the second approach, but it is very easy to add additonal concerns to the view without noticing, and my controllers looks more like a service class than a true controller.
As you can guess from the title, I don't think that this is somethig that can be solved when using the WebForms framework. The view is so centric to the very idea of WebForms that it is impossible to work without its influence.
I am probably going to do another screen cast on this, but at this point I am giving up. I am fed up with approximations. If you want to do MVC in ASP.Net, you wouldn't be able to get the real thing with WebForms.
Comments
The Code-Behind file is your controller. The ASPX file is your view. Once you understand that, you will find that much of the pain goes away.
Ahh, I see you haven't read the original Smalltalk paper.
In a real MVC triade, the View and Controller are supposed to be tightly coupled. Only the model is supposed to not know about the other components. This is where a lot of people get it wrong and we end up with bizarre hacks like inversion of control containers.
No, it isn't. Try handling even slightly complex UI and you'll see what happens to the code behind. Row_DataBound is one example of violating the SoC principal here.
I have, I don't feel like being constrained by the classic definition. I think that I made it clear what I would like to have.
Please expand on this topic
I agree with most things you say and understand the issues you are talking about.
What I am having problem integrating (when thinking about it) in the idea (most of the projects for my current employer are still the "classic" web forms arch.) is what if you need a more complex UI operation which integrates JavaScript, async web service calls from the client, and generally want a more usability driven approach (e.g. very simple idea of improving the usability of the UI with the help of JavaScript... show hide sections, panels, etc...), how do you integrate it then in your approach? Because, a view grows up and becomes more "smart" and that is usually not something recommended when thinking in the true MVx way? Also, a classic example.. client side validations, great for usability, but from the MVx perspective..?
One better example, how would you implement the following... We have a complex import procedure that is executed through a service in the business layer. The service consists of actions for moving files, unpacking them, executing data integrity validations, file integrity validations, etc... The service receives by interfaces a dependency to a "Log" service which is application specific (to abstract the implementation and state persistance, in the current state it saves the import progress by task to Application log). The process is governed by web services which are called from the client (web ui in this case) in a way that on the client you configure your import (select data file and some other info) and start an async call to the web service to start up a new import process again async. on the server. Another web service call polls every 10 seconds the server to get the progress from the import service (via the Log) and shows the appropriate progress on the client.... Now... this is great because it abstracts a whole bunch of stuff and is interexchangeable in whole different ways, and is (what was the purpose) timeout agnostic from the client perspective (to a point, but still much better then classic sync approach). But from the MVx point of view the part with the client sounds too "smart"? What do you think... how would you approach such a problem?
As you can imagine, I am seeking a way to keep the level of our projects (usability point of view) to our client the same while making a step forward in the architecture of our applications which would in terms give us MUCH more space to implement better testing (e.g. testing the controllers), much more "time-resiliant" architecture to say the least :)
--
Vladan Strigo [MVP]
Vladan,
For complex UI, I tend to use more than a single controller (and more than a single view), so I still have SoC in place.
For most cases, I split the responsibilities so the controller deals with the business case, the view is dealing with the UI, but UI in this case also include the Ajax behavior that is triggered from the UI, which would call back to the controller.
Validation is something that is good to do declaratively if possible, take a look at Castle.Components.Validators, here is a declarative approach that also allows for client side validation. It is not perfect, but it works.
I agree... the only part where I was suspicious is that UI is allot "smarter" in that case and am still not sure if that mixes up the responsibilities of each part of MVx.
As for the validation... I've got a framework set up for that... Basically we have a set of validations (e.g. IsNotEmpty, IsEmail, IsDutchPostcode, etc...) and those are reusable for all applications and can be easily referenced. As I figure each UI has a set of UI Rules... each rule being a combination of one or more reusable validations (e.g. IsNotEmptyAndIsDutchPostcode) which are written per project. To validate them we have a custom written validator with a syntax like this:
You configure it with a configurator:
<nm:UiRuleConfigurator ID="configurator1" runat="server">
<DependencyScripts>
</DependencyScripts>
<RuleScripts>
</RuleScripts>
</nm:UiRuleConfigurator>
and then add validators to validate certain controls:
<nm:UiRuleValidator ID="UiRuleValidator1"
It's quite flexible and covers most of our UI validation scenarios :)
We have something similar in the business layer which validates entities and the complete thing works like a charm.
Btw.... did you try using MS AJAX components (update panel, web service calls, client scripting model) through brails? Is it even possible?
--
Vladan
@Vladan ,
I was thinking about declaring it in the model, and having the UI understand that, hopefully in the framework component level.
Your approach works, but then you need to specify this in the domain model as well, no?
Check out:
http://www.ayende.com/Blog/archive/2007/05/11/MonoRail-and-Update-Panel.aspx
For my thoughts about it.
Some of this stuff is doable, but some of it is simply not worth the both with the different architecture approach.
Well there are several things... To confirm, yes there are rules in the domain which are very similar to these but can also be different on occasion (e.g. complex search entry form... it's fields sometimes span over several entities (dont have a relation to one entity 1-1) and should be validated like that (actually it's representation could be a Value Object in the domain but also usually isn't beacuse it's representation like that doesn't bring much value to the domain).
Also I think the application usability is much higher like this.
One thing I often do is define 3 set of rules:
UI rules - validate UI entry
Domain rules (business) - validate in the business layer
Data integrity rules - validate import data structure, database constraints, etc...
although there are some overlaps as you've noticed (e.g. domain and UI) this usually gives me trust worthy data with enough usability.
Btw. It's great to see your comments on the subject :)
When you say you are giving up, do you mean you are giving up on trying to get a clean implementation of MVC on webforms, or are you giving up your current approach of having the view make calls to the controller?
Can I ask why you choose to have the view call the controllers methods, as opposed to having the view rasie events that are handled by the controller?
In my implementation, I define events from the view as needed, but I don't handle many asp.net events in my controller. For example, rather than catching the ButtonSave_OnClick event in my view, I catch it in my view, and then raise the event Save() which my controller handles.
Maybe this is not useful, but I feel like I am loosening the coupling between the 2 by doing this. I don't have to make controller methods public, I can redo stuff in the controller (rename methods, refactor stuff) w/o effecting the view etc. What do you think?
Ayende, how do you save changes? I assume that you call the controler to save the changes. But how do you send the data?
I am also using the second option but I had to create an interface so that the Controler could see the web page without having a cycle (I have the controler in a separete assembly). This implies creating the interface and then implementing the interface on the page class. Doing this for every page seems like a lot of work. Is there a better way?
Ayende, when you're right, you're right. Its impossible to get a decent MVC model within ASP.NET because the ASP.NET lifecycle (and viewstate). I find myself having to essentially put hacks in my controller to get things plumbed right so that ASP.NET will fire events from my view. To create a truly clean AND testable web application you have to use something other than ASP.NET. ASP.NET was not built to be testable, it was built to allow VB forms guys to do web programming, end of story.
Chris, you don't want to raise UI events on your controller because now your controller has a dependency upon UI specific events. There might be other reasons, but that one is enough for me to avoid it. Monorail gets around this by specifically calling the method you want on the controller rather than hooking a UI event up - so much cleaner.
Sneal,
I am basically following the model described here:
http://haacked.com/archive/2006/08/09/ASP.NETSupervisingControllerModelViewPresenterFromSchematicToUnitTestsToCode.aspx
In Ayende's 3 examples, they are all not pure MVC, but if you are going to assume that, does option 2 work better than option3?
With option 2, you must define public methods on your controller that are expected to be called by the view. With option 3, you have to define events on the IView interface which are handled by the controller.
Is it really worse to handle events on the controller, than to put out public methods for the IView to call?
@Chris
I've tried both option 2 & option 3 from Phil's URL. So far, I like the event raising technique of #3. I'd be curious to hear about the experiences of others though.
Chris,
My main issue is that I have more than a single view per controller, and most of the time the view needs to get information from the controller, not send it a message, I find that using event for this is somewhat awkward, and it doesn't release me from the life cycle issues.
What is more, my views are significantly different, I have System.Web.UI.Page, System.Web.Service.WebService and System.Web.IHttpHandler: all of which may be using the same controller, but in significantly different ways.
@Jesus Garza,
I get the values directly from the request, this allows me to handle everything in a single place, the controller.
The controller and the page communicate by a Scope object, similar to MonoRail property bag approach.
@Ayende,
Not that familiar with MonoRail, so to clarify: this 'Scope' object is a beast of your own making correct? is it just a facade around HttpContext?
Thx.
Here is the scope object, a beast entirely of my own making :-)
https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-igloo/Rhino.Igloo/Scope.cs
Comment preview