Load an existent object from IModelCreator.CreateModel

Load an existent object from IModelCreator.CreateModel

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


correodemarques posted on Thursday, November 03, 2011

I was reading Using CSLA 4 ASP.NET MVC and I see the procedure followed when updating an existent object is to create a new instance of the type, assign the Id value from the view, and save the object forcing the update instead the normal insert of a new object.

But what happens if the object have a sensitive property that the current user is not allowed to read, but is necessary to check some business rules before saving a modification?

We cannot send the value of that property to the view, not even in a hidden field, and therefore that value is not going to be available later when we receive the post from the user's browser.

The alternative I see, without using Session, is to load the object from the database in the HttpPost version of the action method and then update it's values using UpdateModel(), but then we have to catch any possible binding exception.

There is some way to access the values from the form in the IModelCreator.CreateModel method and use them -specially the Id-, in order to get the right object before the automatic binding occurs?

RockfordLhotka replied on Monday, November 07, 2011

Unfortunately the MVC binding technology doesn't provide for the idea of passing values through to the object creation provider. In reality, it doesn't even have the concept of an object creation provider, we added that by tapping into the binding workflow in CslaModelBinder. But binding doesn't provide any information beyond the type of object required.

In short, I think you must fetch the object and use UpdateModel.

correodemarques replied on Tuesday, November 08, 2011

Hi Rocky, thanks for your reply.

At the beginning I was going to create a new controller class derived from Csla.Web.Mvc.Controller and call a new interface that will pass the BindingContext through a parameter to the CreateModel method. Then in the CreateModel I would have access to the values I need.

However a while after I decided to create a custom model binder for each of my "special" business objects. It is a little more code, but I see it as a cleaner solution. I also created a custom model binder provider and with this I don't have to register every single custom model binder in Global.asax.

If someone is interested (and maybe someone don't have a good opinion about this solution) I'm posting some of the code here:

Custom model binder:

----------------------------

using System;
using System.Web.Mvc;
using AccountsManagement.Library.Security.UserGroups;

namespace WebUI.CustomModelBinders
{
  /// <summary>
  /// Custom model binder for UserGroups
  /// </summary>
  /// <remarks>
  /// The user group is created based in the values of the IsNew, Id, IsSupport and AccountId properties
  /// </remarks>
  public class UserGroupModelBinder : ApplicationBaseModelBinder
  {
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
      var searchPrefix = SearchPrefix(bindingContext);

      bool isNew;

      if (!bool.TryParse(GetValue(bindingContext, searchPrefix, "IsNew"), out isNew))
      {
        //TODO Validate if we received a valid value
      }

      if (isNew)
      {
        //If the object is new, return a new user group
        bool isSupport = false;
        Guid accountId;

        if (ApplicationContext.CurrentUserIsSupport)
        {
          //If the user is a support user, the values of IsSupport and AccountId are read from the binding data

          if (!bool.TryParse(GetValue(bindingContext, searchPrefix, "IsSupport"), out isSupport))
          {
            //TODO Validate if we received a valid value
          }

          if (!Guid.TryParse(GetValue(bindingContext, searchPrefix, "AccountId"), out accountId))
          {
            //TODO Validate if we received a valid value
          }
        }
        else
          //For non support users the account id of their groups is the same account of the current user
          accountId = ApplicationContext.CurrentUserAccountId;

        return UserGroup.NewUserGroup(isSupport, accountId);
      }
      else
      {
        //If the group is not new, it is loaded from the database
        Guid id;

        if (!Guid.TryParse(GetValue(bindingContext, searchPrefix, "Id"), out id))
        {
          //TODO Validate if we received a valid value
        }

        return UserGroup.GetUserGroup(id);
      }
    }
  }
}

 

Custom model binder provider:

---------------------------------------

using System;
using System.Web.Mvc;

namespace WebUI.CustomModelBinders
{
  public class ApplicationModelBinderProvider : IModelBinderProvider
  {
    public IModelBinder GetBinder(Type modelType)
    {
      if (modelType == typeof(bool) || modelType == typeof(bool?))
        return new BooleanModelBinder();

      if (modelType == typeof(AccountsManagement.Library.Security.UserGroups.UserGroup))
        return new UserGroupModelBinder();

      return null;
    }
  }
}

 

In Global.asax register the model binder provider:

--------------------------------------------------------------

 

    protected void Application_Start()
    {
      ModelBinders.Binders.DefaultBinder = new Csla.Web.Mvc.CslaModelBinder();

      ModelBinderProviders.BinderProviders.Add(new WebUI.CustomModelBinders.ApplicationModelBinderProvider());
...

 

 

 

 

Copyright (c) Marimer LLC