I have described in my previous post that it is possible to build CSLA model binder for ASP.NET MVC. There are two ways we can do this: The first method is to create custom binder by implementing IModelBinder. The second method is to create custom binding attribute (parameter attribute) by implementing CustomModelBinderAttribute. I have also explained how to create CslaBindAttribute. You can read the first part by clicking the following link http://forums.lhotka.net/forums/thread/26553.aspx
Implementing IModelBinder allow us to define model binding definitions at controller level or even at application level. Again, just like CslaBindAttribute, the challenge is to tell our binder how to instantiate CSLA objects. In addition, we need to be able to tell our binder to use which factory method on which action method. We need to be able to register mapping of action method to factory method. Our custom binder would use the mapping table to locate a factory method when trying to instantiate CSLA object. Similar to URL routing, I think we can register the mapping on global.asax
So the first step is to create a module to register an action to factory mapping. Inspired by this post, I thought we can use Fluent Interface for defining our factory methods. Here is a sample of defining factory method in fluent interface:
1: //use separate factory class
2: CslaFactory
3: .ForController<ProjectTracker.Web.Controllers.ProjectController>()
4: .WhereActionIs(a => a.Index(null))
5: .UseMethod<ProjectTracker.Web.Models.CatalogService>((x) => x.GetAllProjects());
6:
7: //use standard factory method
8: CslaFactory
9: .ForController<ProjectTracker.Web.Controllers.ProjectController>()
10: .WhereActionIs(a => a.Edit(null))
11: .UseStaticMethod(()=> Project.GetProject(Guid.Empty), new [] {"Id"});
Final step is to create custom binder CslaModelBinder. I choose to inherit from DefaultModelBinder instead of creating the binder from scratch and implementing IModelBinder. We can override the GetValue method only when the object is CSLA type and let other types handled by default binder. Here is the code snippet:
1: public override object GetValue(ControllerContext controllerContext, string modelName, Type modelType, ModelStateDictionary modelState)
2: {
3: if (!(typeof(Csla.Core.IBusinessObject).IsAssignableFrom(modelType)))
4: return base.GetValue(controllerContext, modelName, modelType, modelState);
5:
6: //1. get controller type
7: //2. get action name
8: Type controllerType = controllerContext.Controller.GetType();
9: string actionName = controllerContext.RouteData.GetRequiredString("action");
10:
11: //3. get csla type object
12: var factoryMap = CslaFactory.GetMapTable(controllerType, actionName);
13: if(factoryMap == null)
14: throw new InvalidOperationException("Factory method not defined for model type " + modelType);
15:
16: object[] parValues = _binder.GetArgumentValues(controllerContext, factoryMap.FactoryArguments);
17:
18: object obj = _instantiator.CreateInstance(factoryMap, parValues);
19:
20: if (obj is Csla.Core.IReadOnlyCollection || obj is Csla.Core.IReadOnlyObject)
21: return obj;
22:
23: //4. bind data, update model state from broken rules
24: _binder.Map(controllerContext, obj, modelState);
25: _binder.UpdateModelState(modelState, (Csla.Core.BusinessBase)obj, controllerContext.HttpContext.Request.Form);
26:
27: return obj;
28: }
In summary, I was able to show through this prove of concept that we can build model binder that works with CSLA object. The source code of this prove of concept is attached on this post. The code by no means complete; Complex data binding such as binding on child object and root collection object are not yet supported. Data filter (include/exclude) is also not yet supported. SmatDate type conversion is not available.
There are also improvements that I think could be made. The factory registration can be somewhat cumbersome for large application with large number of controllers and large business objects. MVC follows the Convention over Configuration design paradigm. To follow the CoC paradigm, I think we can improve or complement the factory mapping by using default convention. Therefore the registration can be simplify to just registering the controller to factory class mapping. The convention could be:
action: create*|add* | <==> | factory: New* |
action: edit* | <==> | factory: Get* |
action: index*|list* | <==> | factory: Get*List|Get*Collection |
Finally, I'm hoping that MVC can get the same love as Silverlight in CSLA and
Rocky can include MVC features like model binder on CSLA future version.
Comments and ideas are welcome,
Ricky Supit
NOTE: The source code is too large to be attached on this post. So you can download the source code here.
10/24/2008: I have updated the source code to work with MVC Beta version and made some improvement by implementing the instantiator by convention. I hope to write up these new features when I get the chance. In the meantime, the source code can be found at the same place.
I was hoping MVC would be farther along and could be part of 3.6, but it appears unlikely to release before Silverlight 2.0 and so support build into CSLA for MVC will have to wait for some later release.
In terms of invoking factory methods, you should look at the work done in Csla.Silverlight.CslaDataProvider (I think the good parts are in MethodCaller). Sergey wrote code for the data provider that is capable of dynamically invoking the correct factory method, and I suspect the exact same solution would solve the binder issue for MVC.
I don't know what Fluent is, but it sounds like a potential dependency? Dependencies are something I avoid as much as possible.
Simply put Fluent Interfaces are ability to chain actions on
your object, class. So basically if you have an object ATM that you
instantiate and use like this:
ATM atm = new ATM(bankcard);
atm.EnterPassword(pwdValue);
atm.SelectAccount(checking);
int newBalance= atm.Withdraw(500);
with Fluent Interface the code above would look like:
int newBalance = newATM(bankcard)
.EnterPassword(pwdValue)
.SelectAccount(checking)
.Withdraw(500);
So as you can see the list of actions is kind of described in
more something that resembles a sentence flow. Instantiate ATM, enter
password, select account – checking, and withdraw $500.
While this is not perfect example for Fluent Interface as EnterPassword()
and SelectAccount() can branch out have different results, at least I shows how
the “Fluent” code looks like. The trick is that EnterPassword()
and SelectAccount() return ATM() instance – “this” instead of
“void”.
Most common usage for this is in Building Query criterias, like:
Var query =new SelectQuery()
.WhereContinentIdEqual(35)
.CompanyNameIn(“IBM”,”Microsoft”);
And also there are examples where it is useful in certain test
scenarios- I think I have a blog post or two about it.
Nermin
From: RockfordLhotka
[mailto:cslanet@lhotka.net]
Sent: Sunday, September 28, 2008 6:27 PM
To: Nermin Dibek
Subject: Re: [CSLA .NET] MVC Model Binder for CSLA Part II
I was hoping MVC would be farther along and could be part of 3.6, but it
appears unlikely to release before Silverlight 2.0 and so support build into
CSLA for MVC will have to wait for some later release.
In terms of invoking factory methods, you should look at the work done in
Csla.Silverlight.CslaDataProvider (I think the good parts are in MethodCaller).
Sergey wrote code for the data provider that is capable of dynamically invoking
the correct factory method, and I suspect the exact same solution would solve
the binder issue for MVC.
I don't know what Fluent is, but it sounds like a potential dependency?
Dependencies are something I avoid as much as possible.
Ricky,
You should take a look at the MVC +ExtJs + Csla.Net demo series
of posts I have on my blog:
There will be few more articles in this series, and part 5
should deal with Factories, and Unit Testing. But the source code that can be
downloaded from either one of those posts already has the factory code.
Great thing about Csla, is that it encourages Factory Method,
but does not force you to use it. The reason to use Factory Method is the
fact that Csla objects are Instantiated on the server and then serialized to
the client. But one could use an Abstract Facory Patter as well.
Since Factory Method pattern makes it lot harder to mock Csla
objects, and MVC is all about TDD- Test Driven Development, in that demo I am
using an Abstract Factory:
[HandleError]
public class CustomerController : Controller
{
#region Constructor
private readonly AbstractBusinessFactory<CustomerList> customerFactory;
public CustomerController()
: this(new ReadOnlyListFactory<CustomerList, CustomerInfo>())
{
}
public CustomerController(AbstractBusinessFactory<CustomerList>
customerFactory)
{
this.customerFactory = customerFactory;
}
#endregion
#region Actions
public ActionResult Index()
{
return RedirectToAction("List");
}
public ActionResult List()
{
var selectList = new SelectList(customerFactory.RetreiveAll(),
"CustomerID", "CompanyName");
return View("List", selectList);
}
#endregion
}
If you take a look you will notice 2 constructors on a
controller. Default one call second one that accepts the AbstractFactory
as a parameter. The second constructor also can be later called
from the IoC container – where IoC will register specific factory for each
controller same as your code does. This also helps as during testing as
we can Inject Mock Csla objects to test that Controller does what it is
supposed to do. Tests like:
[Fact]
public void ListAction_ReturnsSelectListOfCustomers()
{
var controller = new CustomerController(GetMockCustomerFactory());
var result = (ViewResult)controller.List();
Assert.True(result.ViewName == "List");
Assert.True(result.ViewData.Model is SelectList);
}
AbstractBusinessFactory<T>
where T : ReadOnlyListBase<T,C>
{
public override T CreateNew()
{
return DataPortal.Create<T>();
}
public override T RetreiveAll()
{
return DataPortal.Fetch<T>();
}
public override T RetreiveBy(CriteriaBase criteria)
{
return DataPortal.Fetch<T>(criteria);
}
}
As far as when will I have this example - it will have to wait. Unfortunately lately I have
been working on some other things lately like VS templates and wizards for Csla 3.6. I find those features extremely high priority, and MVC
will have to wait a bit. Other than the Binding example you have given us
we really do not need anything MVC specific in Csla. All we need is fully
functional Project Tracker.
Unfortunately based on my schedule that will be most likely mid
November at earliest. The biggest problem is that we will be using few
new features like ObjectFactory, and then not use FactoryModel. When you
combine that with custom ajax, the fact that MVC itself does not resemble
anything like Web Form, it is really important that we have good, and well
documented examples. That takes time.
Nermin
ScottGu:
One of the new capabilities in "Preview 5" that can make this scenario cleaner is its "Model Binder" support. Model Binders provide a way for complex types to be de-serialized from the incoming HTTP input, and passed to a Controller action method as arguments. They also provide support for handling input exceptions, and make it easier to redisplay forms when errors occur (without requiring the end-user to have to re-enter all their data again - more on this later in this blog post).
nermin:
My instinct is also that your CslaFactory registry duplicates the ability of Inversion Of Control IoC containers. In fact if you take a look at the StructureMap registry (non-xml version- the fluent version) you will notice that it does the same thing, and the code looks quite simillar.
nermin:
Other than the Binding example you have given us we really do not need anything MVC specific in Csla.
Copyright (c) Marimer LLC