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.
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!
<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>
public interface IElement
{
string Value { get; set; }
}
public class Textbox : IElement
{
public string Value { get; set; }
}
public class Checkbox : IElement
{
public string Value { get; set; }
}
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;
}
}
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);
}
}
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new ModelBinderProvider());
});
}
}
public class FormModel
{
public string FormName { get; set; } // Not using this
public List<IElement> Elements { get; set; }
}
Notice the three types, Textbox, Textbox and Checkbox.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With