MVC Model Binder for CSLA Part II

MVC Model Binder for CSLA Part II

Old forum URL: forums.lhotka.net/forums/t/5484.aspx


rasupit posted on Sunday, September 28, 2008

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. Wink [;)]

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.

RockfordLhotka replied on Sunday, September 28, 2008

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.

nermin replied on Sunday, September 28, 2008

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.



rasupit replied on Monday, September 29, 2008

Rocky,

I read somewhere that they plan to relase MVC at the end of this year.

I think you're referring to the following method in MethodCaller:
public static object CallFactoryMethod(Type objectType, string method, params object[] parameters);
The method is similar with the code I used when calling factory method from CslaBindAttribute; however mine also accepting factoryType in case it uses separate factory class.

There is no outside dependency on Fluent Interface other than System.Linq.Expression.  Linq expression was used to avoid hard coded string and to take advantage of intellisense.  So instead of UseStaticMethod(typeof(Project), "GetProject", new [] {typeof(Guid)}) we can do the following with the help of intellisense UseStaticMethod(() => Project.GetProject(Guid.Empty))

Ricky

nermin replied on Monday, September 29, 2008

Normal 0 false false false EN-US X-NONE X-NONE MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable {mso-style-name:"Table Normal"; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0in 5.4pt 0in 5.4pt; mso-para-margin:0in; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman","serif";}

Ricky,

 

You should take a look at the MVC +ExtJs + Csla.Net demo series of posts I have on my blog:

http://nermins.net/post/2008/09/Implementing-MVC-site-using-CSLA-and-ExtJs-%E2%80%93-Part-1---Introduction.aspx

http://nermins.net/post/2008/09/Implementing-MVC-SIte-using-Csla-and-ExtJs---Part-2---Solution-Structure.aspx

http://nermins.net/post/2008/09/Implementing-MVC-Site-using-Csla-and-ExtJs---Part-3---UI%2c-ExtJs-Customer-DropDown-List.aspx

 

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);

    }

 Here is then the actual implementation of one of the factories (this one being for ReadOnlyListBase):


  public class ReadOnlyListFactory<T, C> :
    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);
    }
  }


Model Binder that I implement will be based on this abstract factory pattern, and will circumvent Factory Method implementation.  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. 


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

rasupit replied on Monday, September 29, 2008

Nermin,
Thanks for giving your thought about my Model Binder implementation.

Based from your reply, I'm not sure if I was clear enough what I was trying to address with Model Binder.  I was not trying to address the testability issue of CSLA object or trying to build another IoC container specific for CSLA object.  Your idea about separating the factory method to a separate class and making the constructor on csla object public will work and will make CSLA object more testable because it is easier to mock.

There are many ways to create an action method. Before preview 5, the method signature is to pass primitive types as arguments. With this approach we have to write code to perform model instantiation, data binding, validation, and redirects.  Because we have to do all these tasks, we need factory method to instantiate the object, binding helper to help put the value from form to object (unless you want to hand code the left hand right hand assignments), and model state helper to update ModelState if there is any broken rules.

Because we need to instantiate the object inside the controller, having separate factory class (that implement an interface of course) along with IoC helps makes the controller loosely coupled therefore make it testable.  You have showcased your solution to address this scenario very well (although you did not give POST example) on your articles.

In preview 5, ScottGu introduce another approach of creating an action method.
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).

Because we _don't_ need to instantiate the object inside the controller, there is no need to have a provider class inside the controller which also eliminate the need of IoC help to inject a provider class.  Of course this is only true on CSLA object because the save method attach on the object itself.

This approach (which I like because of its simplicity) by no means makes the controller less testable compare to the first approach. In fact you don't have to inject mock object when testing the controller because you can pass the object as parameter.  Testing the object of course is a separate concern and your solution works very well.

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.

If you can show me some sample code on how I can tell StructureMap to invoke a factory method with provided arguments when I have an action method; I'll be happy to ditch the CslaFactory.

nermin:

Other than the Binding example you have given us we really do not need anything MVC specific in Csla.

I'm sure we can make the same argument on CslaDataSource in webform or other similar helper that CSLA provided for winform, wpf or silverlight.  I've been following CSLA since pre .NET days and I found that the framework always look for opportunity to reduce redundant, error prone and boring code.  I hope to see it continues.

Ricky Supit

rasupit replied on Friday, October 24, 2008

New version is available to work with MVC Beta.  The link to download can be found at the top of this thread.

fretje replied on Wednesday, April 22, 2009

Hello,
I'm trying to get this to work on the release version of asp.net mvc...
I'm having trouble with the "ShouldUpdateProperty" method which apparently doesn't exist anymore on ModelBindingContext...
Anybody has any suggestions, or already implemented this in the new version?
Any other hints, tips or links are appreciated!

Greetings,

Fretje

Copyright (c) Marimer LLC