Model Binder in ASP.NET webforms
Asked Answered
P

3

6

For number of years I did ASP.NET web forms development I was spoiled by a proprietary library, which allowed me to do things like:

    UpdateToObject(ControlsCollection, obj)
    UpdateFromObject(ControlsCollection, obj)

Conceptually code did something very similar to what MVC Model Binder does, i.e. given form's posted values as input it would populate custom object. Basically it freed developer from doing monkey code such as

employee.Name = txtName.Text;
employee.DOB = DateTime.Parse(txtDOB.Text);

and so on..

Now, this proprietary library is not available on the new project I'm involved with, and it's a Web forms project. So I'm wondering if there is a way of using System.Web.Mvc.DefaultModelBinder in the context of Web forms. Goal is to achieve simple and easy population of controls from domain objects and back, ideally with validation annotations taken into consideration. If such is not possible could somebody point me to an open source solution to address this need. I really don't feel like rewriting such code.

Thanks in advance.

Pindaric answered 9/9, 2010 at 20:43 Comment(0)
C
1

Sherlock, you'll run into some issues trying to use the ModelBinder from MVC since they rely on a ControllerContext.

I answered a similar question earlier ChangeType, Convert - Converting from one type to another but it is really what you're looking for.

Check out this blog post on my blog ChangeType – Changing the type of a variable in C#

Essentially, you get a single method called ChangeType<T> that returns the value of the parameter you're looking for in a strongly typed fashion or a default value if the parameter does not exist.

Now as regards custom classes (DTO type classes mainly), if you don't mind using reflection then I have a solution that will handle most custom classes as well. The DtoBinder class mentioned towards the end of the will job nicely.

Essentially, the last 3 code listings contain all of the code you'll need in order to handle almost every need you have in a typical Web application scenario. Plus it's extensible, so if you need to implement your own binder you can do that very simply and register your binder with the RequestBinder from anywhere in your app.

So if you don't want to use reflection for certain frequently used DTO objects, you can implement a binder for the type and register it and from that point on it will use your custom binder. In many ways it's similar to the MVC ModelBinder in concept.

Edited -

Below is one .cs file with a bunch of classes that I've used in the past to do exactly what you need. The first one MsPropertyAssignerProvider is the one you'd be working with from within your page.

You'd iterate over your controls and call the GetPropertyAssigner method passing it the type name of the control. This method returns an instance of an ObjectPropertyAssigner that has one method called SetPropertyValue that you can pass your object instance and the control instance to.

  internal class MsPropertyAssignerProvider
  {
    private Hashtable propertyAssigners;

    internal MsPropertyAssignerProvider()
    {
      propertyAssigners = new Hashtable();
      RegisterPropertyAssigner(typeof(TextBox).ToString(), new TextBoxValueExtractor());
      RegisterPropertyAssigner(typeof(DropDownList).ToString(), new DropDownListValueExtractor());
      RegisterPropertyAssigner(typeof(Label).ToString(), new LabelValueExtractor());
      RegisterPropertyAssigner(typeof(CheckBox).ToString(), new CheckBoxValueExtractor());
    }

    internal void RegisterPropertyAssigner(string identifier, IMsObjectPropertyAssigner assigner)
    {
      if (propertyAssigners.ContainsKey(identifier))
        throw new DuplicatePropertyAssignerRegistrationException(identifier);
      propertyAssigners.Add(identifier, assigner);
    } 

    internal IMsObjectPropertyAssigner GetPropertyAssigner(string identifier)
    {
      return (propertyAssigners.ContainsKey(identifier)) ? (IMsObjectPropertyAssigner)propertyAssigners[identifier] : null;
    }
  }

The accompanying class are listed below

  public interface IMsObjectPropertyAssigner
  {
    void SetPropertyValue(object obj, System.Web.UI.Control control); 
  }

  internal abstract class BaseValueExtractor : IMsObjectPropertyAssigner
  {
    protected MsReflectionHelper reflectionHelper = new MsReflectionHelper();
    protected string FixStringForNumber(string stringValue)
    {
      if (stringValue.Length == 0)
        return "0";
      else
        return stringValue;
    }
    public abstract void SetPropertyValue(object obj, System.Web.UI.Control control);
  }

  internal class TextBoxValueExtractor : BaseValueExtractor
  {
    public override void SetPropertyValue(object obj, System.Web.UI.Control control)
    {
      TextBox textBox = (TextBox)control;
      PropertyInfo propInfo = reflectionHelper.GetPropertyInfo(obj, control.ID);
      Type propType = propInfo.PropertyType;
      if (propType == typeof(System.String))
        reflectionHelper.SetPropertyValue(obj, control.ID, textBox.Text);
      else if (propType == typeof(System.Int16))
        reflectionHelper.SetPropertyValue(obj, control.ID, Int16.Parse(FixStringForNumber(textBox.Text), System.Globalization.NumberStyles.Currency));
      else if (propType == typeof(System.Int32))
        reflectionHelper.SetPropertyValue(obj, control.ID, Int32.Parse(FixStringForNumber(textBox.Text), System.Globalization.NumberStyles.Currency));
      else if (propType == typeof(System.Int64))
        reflectionHelper.SetPropertyValue(obj, control.ID, Int64.Parse(FixStringForNumber(textBox.Text), System.Globalization.NumberStyles.Currency));
      else if (propType == typeof(System.Double))
        reflectionHelper.SetPropertyValue(obj, control.ID, Double.Parse(FixStringForNumber(textBox.Text), System.Globalization.NumberStyles.Currency));
      else if (propType == typeof(System.Single))
        reflectionHelper.SetPropertyValue(obj, control.ID, Single.Parse(FixStringForNumber(textBox.Text), System.Globalization.NumberStyles.Currency));
      else
        reflectionHelper.SetPropertyValue(obj, control.ID, textBox.Text);
    }
  }

  internal class DropDownListValueExtractor : BaseValueExtractor
  {
    public override void SetPropertyValue(object obj, System.Web.UI.Control control)
    {
      DropDownList dropDownList = (DropDownList)control;
      reflectionHelper.SetPropertyValue(obj, control.ID, dropDownList.SelectedValue);
    }
  }

  internal class LabelValueExtractor : BaseValueExtractor
  {
    public override void SetPropertyValue(object obj, Control control)
    {
      Label label = (Label)control;
      reflectionHelper.SetPropertyValue(obj, control.ID, label.Text);
    }
  }

  internal class CheckBoxValueExtractor : BaseValueExtractor
  {
    public override void SetPropertyValue(object obj, Control control)
    {
      CheckBox checkbox = (CheckBox)control;
      reflectionHelper.SetPropertyValue(obj, control.ID, checkbox.Checked);
    }
  }

Sorry no matter what I do the editor completely messes up the code listing. But I hope this helps.

Cranium answered 8/11, 2010 at 22:55 Comment(3)
Only 1st and last paragraphs are somewhat relevant to the question raised. I don't mind using reflection. This is how proprietary library worked.Pindaric
I guess in your case because you're working with Controls the solution is a bit more involved. The DtoBinder class can create an instance of your Dto object and assign all property values given a collection of NameValues. So what you need to do is assemble this collection from the control's names and values and hand it to the DtoBinder.Cranium
Just edited my original answer with some additional classes that I've used in the past.Cranium
O
1

Wouldn't you be able to use AutoMapper for something like this? Just setup your maps and it will create new objects and copy the values into them.

Outlet answered 6/9, 2011 at 8:28 Comment(0)
M
0

This is quite an old question but I bumped into it while trying to figure out how the default model binder actually works.

I have a project on CodeProject that actually does what you want(ed), have a look.

Cheers!

Moral answered 29/5, 2013 at 9:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.