views:

45

answers:

2

I've been looking around for an answer to this, which I can't believe hasn't been asked before, but with no luck I'm attempting here.

I have a signup form which differs slightly based upon what type of participant the requester is. While writing tests for the solution, I realized that all actions did the same things, so I'm attempting to combine the actions into one using a strategy pattern.

public abstract class BaseForm { common properties and methods }
public class Form1 : BaseForm { unique properties and overrides }
....
public class FormX : BaseForm { unique properties and overrides... in all about 5 forms }

Here is the combined action:

[ModelStateToTempData, HttpPost]
public ActionResult Signup(int id, FormCollection collection)
{
    uiWrapper= this.uiWrapperCollection.SingleOrDefault(w => w.CanHandle(collection));
    // nullcheck on uiWrapper, redirect if null
    var /*BaseForm*/ form = uiWrapper.GetForm();  // Returns FormX corresponding to collection.
    this.TryUpdateModel(form, collection.ToValueProvider()); // Here is the problem
    form.Validate(this.ModelState);  // Multi-Property validation unique to some forms.
    if (!this.ModelState.IsValid)
        return this.RedirectToAction(c => c.Signup(id));

    this.Logic.Save(form.ToDomainClass());
    return this.RedirectToAction(c => c.SignupComplete());
}

The problem I'm having is that TryUpdateModel binds only the common properties found in BaseForm. My previous code used public ActionResult FormName(int id, FormX form) which bound properly. However, I did some testing and discovered that if I replace var form with FormX form the form binds and everything works, but I'm back to one action per form type.

I'm hoping to find a way to get this to bind properly. form.GetType() returns the proper non-base class of the form, but I'm not sure of how to grab the constructor, instantiate a class, and then throw that into TryUpdateModel. I know that the other possibility is a custom ModelBinder, but I don't see a way of creating one without running into the same FormBase problem.

Any ideas or suggestions of where to look?

A: 

I was trying to do something similar to Linq, I was trying to create a class that would inherit some standard fields (ID, etc). I found that the default Linq engine would only use fields from the instantiated class, not from any inherited classes or interfaces.

To construct a Type simply use code like:

var form = Activator.CreateInstance(uiWrapper.GetForm()); 
Erik Philips
var form = Activator.CreateInstance(uiWrapper.GetForm().GetType()); does grant me a form, but it is unfortunately boxed as Object at that point and form.Validate(this.ModelState) is no longer a valid method call. Thank you for the attempt.
ARM
A: 

I figured it out!

Erik's answer wasn't the solution, but for some reason it made me think of the solution.

What I really want form to be is a dynamic type. If I change this line:

dynamic form = uiWrapper.GetForm();

Everything works :)

On top of that, logic.Save(form.ToDomainClass()) also goes directly to Save(DomainTypeOfForm) rather than Save(BaseDomainForm) so I can avoid the headache there as well. I knew that once I figured out the problem here I could apply the answer in my logic class as well.

ARM
1) Is it considered bad form to mark my own answer as the solution?2) Erik, if you are using C# 4.0, I hope this solution works for your problem as well.
ARM
Mark this as the answer, I totally forgot aboute Dynamic because I just started a job where we're still using 3.5!
Erik Philips