Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Post a List<Interface> .net core 1.0

I'm building a dynamic form creator in .net core. A "form" will consist of many different form elements. So the form model will look something like this:

public class FormModel {
  public string FormName {get;set;}
  public List<IElements> Elements{get;set;}
}

I have classes for TextBoxElement, TextAreaElement, CheckBoxElement that all implement the IElemets interface. And I have EditorTemplates for each element. The code to render the form works great. Though posting the form does not work because of the List of Interfaces.

I've been looking on how to implement a custom model binder, and seen some few examples on the web but I did not get anyone to work.

I would appreciate if someone could show me how to implement a custom model binder for this example.

Plan B: Post form as json to a web api and let JSON.Net covert it. I have tried it and it worked. In startup.cs i added:

services.AddMvc().AddJsonOptions(opts => opts.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto);

It returns type when it has to, eg. the objects in the Elements-list but not on the FormModel. But i really would like to know how to solve it with a custom model binder instead.

like image 780
jessew Avatar asked Aug 21 '16 08:08

jessew


1 Answers

Ok, this works for me. I'm still getting to grips with the new model binding so I may be doing something silly but it's a start!

TEST FORM

<form method="post">
    <input type="hidden" name="Elements[0].Value" value="Hello" />
    <input type="hidden" name="Elements[0].Type" value="InterfacePost.Model.Textbox" />

    <input type="hidden" name="Elements[1].Value" value="World" />
    <input type="hidden" name="Elements[1].Type" value="InterfacePost.Model.Textbox" />

    <input type="hidden" name="Elements[2].Value" value="True" />
    <input type="hidden" name="Elements[2].Type" value="InterfacePost.Model.Checkbox" />

    <input type="submit" value="Submit" />
</form>

INTERFACE

public interface IElement
{
    string Value { get; set; }
}

TEXTBOX IMPLEMENTATION

public class Textbox : IElement
{
    public string Value { get; set; }
}

CHECKBOX IMPLEMENTATION

public class Checkbox : IElement
{
    public string Value { get; set; }
}

MODEL BINDER PROVIDER

public class ModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(IElement))
        {
            return new ElementBinder();
        }

        // else...
        return null;
    }
}

MODEL BINDER

public class ElementBinder : IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType == typeof(IElement))
        {
            var type = bindingContext.ValueProvider.GetValue($"{bindingContext.ModelName}.Type").FirstValue;

            if (!String.IsNullOrWhiteSpace(type))
            {

                var element = Activator.CreateInstance(Type.GetType(type)) as IElement;

                element.Value = bindingContext.ValueProvider.GetValue($"{bindingContext.ModelName}.Value").FirstValue;

                bindingContext.Result = ModelBindingResult.Success(element);
            }
        }
    }
}

HOOK UP MODEL BINDER PROVIDER

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.ModelBinderProviders.Insert(0, new ModelBinderProvider());
        });
    }
}

FORM MODEL

public class FormModel
{
    public string FormName { get; set; } // Not using this

    public List<IElement> Elements { get; set; }
}

ACTION

Notice the three types, Textbox, Textbox and Checkbox.

enter image description here

like image 138
Lee Gunn Avatar answered Oct 18 '22 22:10

Lee Gunn