I have the next form:
<form method="POST" action="test">
<input type="text" name="personFirstName" />
<input type="text" name="personLastName" />
...
</form>
Also, I have next class:
[Serializable]
public class Person
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
And I have the ActionResult:
public ActionResult(Person p)
{
...
}
So how to serialize form in Person object without rename "name" in form? Is there any alias attributes? (personFirstName -> FirstName, personLastName -> LastName)
You need to create your own custom model binder.
public class CustomModelBinder:DefaultModelBinder
{
protected override void BindProperty( ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor )
{
}
}
Related
I have an id received in my action parameter. From where I got my scheme details. By this details I found my desired scheme name. And now I want to show this name in my .cshtml as an input field where I want to show the value in "read-only" format (not changeable). So in my controller I sent it inside a ViewData instead of SelectList as shown below:
ViewData["SchemeNum"] = schemeInfo.SchemeNum;
instead of
ViewData["SchemeInfoId"] = new SelectList(_context.PackagewiseSchemeInfo.Where(p => p.Id == id), "Id", "SchemeNum", schemeInfo.SchemeNum);
I know if I use a selectlist, it would have been easier for me to catch the Key after form submitting like mentioned above -- "Id", "SchemeNum" ...
HTML:
<input class="form-control" value="#ViewBag.SchemeNum" readonly />
Now, I'm getting SchemeNum instead of Id after form submission. I want to know how to catch the KEY instead of value by an input tag? Please help.
You probably wanna disable your input add a hidden field for the id.
<input class="form-control" value="#ViewBag.SchemeNum" disabled />
<input type="hidden" value="#ViewBag.SchemeId" />
Besides,you could custom Model Binder to get the matched key of the value.
Here is a working demo like below:
Model:
public class PackagewiseSchemeInfo
{
public int Id { get; set; }
public string SchemeNum { get; set; }
}
View:
#model PackagewiseSchemeInfo
<form asp-action="Test">
<input asp-for="SchemeNum" class="form-control" value="#ViewBag.SchemeNum" readonly />
<input type="submit" value="create" />
</form>
Custom Model Binder:
public class CustomModelBinder : IModelBinder
{
private readonly YourDbContext _context;
public CustomModelBinder(YourDbContext context)
{
_context = context;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var data = bindingContext.ActionContext.HttpContext.Request.Form["SchemeNum"];
var model = _context.PackagewiseSchemeInfo.Where(a => a.SchemeNum == data.ToString())
.Select(a=>a.Id).FirstOrDefault();
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Controller:
public IActionResult Index()
{
var schemeInfo = new PackagewiseSchemeInfo()
{
Id = 1,
SchemeNum = "aaa"
};
ViewData["SchemeNum"] = schemeInfo.SchemeNum;
return View();
}
[HttpPost]
public IActionResult Test([ModelBinder(typeof(CustomModelBinder))]int Id)
{
//do your stuff...
}
Result:
I am trying to read from HTML form some data. I succeeded to do it in ASP.NET MVC but not in ASP.NET MVC CORE.
Any help will be appreciated!
Haim
This CSHTML code on ASP.NET MVC (worked fine):
#using (Html.BeginForm("Login", "Account")){
<form class="form-group" action="Login" method="post">
<input name="AccountAddressTxt" type="text" class="form-control" required="required" id="AccountAddress" placeholder="Enter Your Account Address" style="width:50%" onclick="SetOx()">
<br />
<input name="AccountPasswordTxt" type="password" class="form-control" required="required" id="AccountprivateKey" placeholder="Enter Your Account Private Key" style="width:50%">
<input id="loginBtn" type="submit" class="btn btn-success" style="width:50% ;" value=" Login">
</form>
And the data binding (worked fine):
namespace RealEstate-Web_app.ModelsBinders
{
public class AccountBinder: IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpContextBase objContext = controllerContext.HttpContext;
String _accountAddress = objContext.Request.Form["AccountAddressTxt"];
String _accountPassword = objContext.Request.Form["AccountPasswordTxt"];
Account obj = new Account()
{
AccountAddress = _accountAddress;
AccountPassword = _accountPassword;
};
return obj;
}
}
}
Right into the controller (worked fine):
[HttpPost]
public IActionResult Login([ModelBinder(typeof(AccountBinder))] Account acc)
{
return View("Login", acc);
}
But in ASP.NET MVC CORE I am getting another method with no idea what to do:
...
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace RealEstate_Web_app.ModelBinders
{
public class AccountBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
//What am I suppose to do here?
}
}
}
Follow a similar format to what was done previously. There are some changes in the new version that should be noted.
namespace RealEstate_Web_app.ModelBinders {
public class AccountBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) {
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
HttpContext httpContext = bindingContext.HttpContext;
String _accountAddress = httpContext.Request.Form["AccountAddressTxt"];
String _accountPassword = httpContext.Request.Form["AccountPasswordTxt"];
Account model = new Account() {
AccountAddress = _accountAddress;
AccountPassword = _accountPassword;
};
//TODO: validate and update model state if not valid
//...
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
}
Reference Custom Model Binding in ASP.NET Core
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!
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.
Assuming I have two classes (Product & ProductSearch) with the same Property "Title"
If I have a field in my form:
<input type="textbox" name="Product.Title" id="Product_Title"/>
I can bind it in the controller using:
public ActionResult Search(Product product)
But is there any way I can specify a bind argument so that it binds to:
public ActionResult Search(ProductSearch productSearch)
I tried [Bind(Prefix = "Product")] to no avail.
The [Bind(Prefix = "Product")] should work. Example:
Model:
public class ProductSearch
{
public string Title { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index([Bind(Prefix = "Product")]ProductSearch productSearch)
{
return Content(productSearch.Title);
}
}
View:
#using (Html.BeginForm())
{
<input type="text" name="Product.Title" id="Product_Title" />
<button type="submit">OK</button>
}
Given the following derived types:
public class Base {
public string Id {get;set;}
}
public class Contact : Base {
public string FirstName {get;set;}
public string LastName {get;set;
}
public class Organization : Base {
public string Name {get;set;}
}
I would like to bind something like this using a custom model binder:
[HttpPost]
public ActionResult UpdateMultiple(List<Base> items) {
for each (var item in items) {
if (item.GetType().Equals(typeof(Contact)) {
// update
} else if (item.GetType().Equals(typeof(Organization)) {
// update
}
}
return RedirectToAction("index");
}
My plan is that each item will have a custom type descriptor:
<input type="hidden" name="items[0].EntityType" value="MyNamespace.Contact, MyNamespace, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<input type="text" name="items[0].FirstName" />
<input type="text" name="items[0].LastName" />
<input type="hidden" name="items[1].EntityType" value="MyNamespace.Organization, MyNamespace, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<input type="text" name="items[1].Name" />
I've developed a custom model binder for a single (non-collection) object:
public class EntityTypeModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".EntityType");
var type = Type.GetType((string)typeValue.ConvertTo(typeof(string)),true);
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
But does anyone have any suggestions on how I could convert this model binder to handle a collection? I'm at a loss.
Best regards and thanks for the response.
Hal
This model binder you have shown already handles collections. All you need is to put the following line in your Application_Start:
ModelBinders.Binders.Add(typeof(Base), new EntityTypeModelBinder());
It is intelligent enough and it will work with collections as well because there is not registered model binder for List<Base> it will be the default model binder that will be invoked. It detects that you have a collection and invokes the corresponding model binder for each element of the collection. And because you have registered a model binder for the Base type you custom binder will be automatically used.