I stuck on this issue for a while..
I've created a simple view model:
public class AddTranslationViewModel
{
public List<ProjectTranslation> ProjectTranslations { get; set; }
public AddTranslationViewModel()
{
ProjectTranslations = new List<ProjectTranslation>();
}
}
ProjectTranslation class:
public class ProjectTranslation
{
public int ProjectTranslationId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Address { get; set; }
public int LanguageId { get; set; }
public Language Language { get; set; }
public int ProjectId { get; set; }
public Project Project { get; set; }
}
A simple view which uses the AddTranslationViewModel
<table class="table">
#foreach (var item in Model.ProjectTranslations)
{
#Html.HiddenFor(modelItem => item.ProjectTranslationId)
<tr>
<td>
#Html.DisplayFor(modelItem => item.Language.LanguageCode)
</td>
<td>
#Html.EditorFor(modelItem => item.Title)
</td>
</tr>
}
</table>
<input type="submit" value="Send" />
and finally my POST Method:
public ViewResult AddTranslation(AddTranslationViewModel projectTranslations)
{
if (ModelState.IsValid)
{
//...
}
return View(projectTranslations);
}
The idea is very basic, I want to show a list of items where it should be possible to change/edit the values.
However, the model binding is not working, the projectsTranslations param in the HTTPPost-Method AddTranslation is always empty.
What's the mistake here?
Binding to a list of object requires creating input field structure with names containing indexes, i.e:
<input type="text" name="YourArrayOrList[0].SomeProperty" value="123" />
<input type="text" name="YourArrayOrList[0].SomeOtherProperty" value="321" />
<input type="text" name="YourArrayOrList[1].SomeProperty" value="123" />
<input type="text" name="YourArrayOrList[1].SomeOtherProperty" value="321" />
Moreover, you need to point the form to the proper Action Method in your Controller using Razor's Html.BeginFrom method (see documentation).
In you case it should look like this:
#using(Html.BeginForm("AddTranslation","YourControllerName"))
{
for (int i=0;i<Model.ProjectTranslations.Count; i++)
{
#Html.HiddenFor(model => model.ProjectTranslations[i].ProjectTranslationId)
<tr>
<td>
#Html.DisplayFor(model => model.ProjectTranslations[i].Language.LanguageCode)
</td>
<td>
#Html.EditorFor(model => model.ProjectTranslations[i].Title)
</td>
</tr>
}
}
If your method is not edit, but CREATE method, then obviously your List in model will have 0 elements. In this case, change the stop condition in for loop to desired count.
Keep in mind that this topic was discussed many times before:
ASP.NET MVC bind array in model
ASP.NET MVC - Can't bind array to view model
Related
Before i started i would like to say i searched and found nothing similar
in my solution i have a model that contains a list of some of my objects
public class ModelView
{
public Owner owner = new Owner();
public Tenant tnt = new Tenant();
}
In my view i call that class as a model which is this way
#model WebApp.Models.ModelView
<form name="export_form" action="Export" method="post">
<table cellpadding="2" cellspacing="2" border="0">
#if (Condition_1)
{
<tr>
<td>
<!-- ID -->
</td>
<td>
#Html.HiddenFor(model => model.owner.ID)
</td>
</tr>
<tr>
<td>
Name
</td>
<td>
#Html.EditorFor(model => model.owner.name)
</td>
</tr>
<tr>
<td>
Phone
</td>
<td>
#Html.CheckBoxFor(model => model.owner.is_Checked_Phone)
</td>
</tr>
}
else
{
<tr>
<td>
<!-- ID -->
</td>
<td>
#Html.HiddenFor(model => model.tnt.ID)
</td>
</tr>
<tr>
<td>
Name
</td>
<td>
#Html.EditorFor(model => model.tnt.name)
</td>
</tr>
<tr>
<td>
Adress
</td>
<td>
#Html.CheckBoxFor(model => model.tnt.is_Checked_Adress)
</td>
</tr>
}
</table>
<input type="submit" name="SaveStuff" value="Save" />
<input type="submit" name="ExportStuff" value="Export" />
</form>
In my controller i have a class that handles multiple submit buttons and depending on the button name it would redirect to a method. below is the SaveStuff method
[HttpPost]
[SubmitButtonClass(Name = "SaveStuff")]
public ActionResult Save_Definition(Owner owner, Tenant tnt)
{
/*
Stuff Here
*/
}
the problem here is i keep getting null values even thought the entities are not null. is there a reason why? no values are returned.
Update
Model A
public partial class Owner
{
public long ID { get; set; }
public bool is_Checked_Name { get; set; }
public bool is_Checked_Phone { get; set; }
}
Model B
public partial class Tenant
{
public long ID{ get; set; }
public bool is_Checked_Name { get; set; }
public bool is_Checked_Adress { get; set; }
}
these are auto generated using EF
You have multiple issues with your code. Firstly the model in your view is ModelView and you generating form controls prefixed with that models property names, for example
<input type="checkbox" name="owner.is_Checked_Phone" ... />
which means your POST method needs to match the model in the view
public ActionResult Save_Definition(ModelView model)
The next issue is that you model only has fields, not properties with { get; set; }, so the DefaultModelBinder cannot set any values. Your view model would need properties such as public Owner owner { get; set; } and you set the value in either the controller before you pass the model to the view, or in a parameterless constructor for ModelView.
However a view model should not contain properties which are data models, but rather be a flat structure containing only the properties you need. In your case, it would be
public class ModelView
{
public long ID { get; set; }
[Display(Name = "Name")]
public bool IsNameSelected { get; set; }
[Display(Name = "Phone")]
public bool IsPhoneSelected { get; set; }
[Display(Name = "Address")]
public bool IsAddressSelected { get; set; }
// additional property to define if the form is for an Owner or Tenant
public bool IsOwner { get; set; }
}
and in the GET method
var model = new ModelView()
{
IsOwner = true // or false
};
return View(model);
and in the view
#model ModelView
....
#using (Html.BeginForm("Save_Definition"))
{
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.IsOwner)
#Html.CheckBoxFor(m => m.IsNameSelected )
#Html.LabelFor(m => m.IsNameSelected )
if (Model.IsOwner)
{
#Html.CheckBoxFor(m => m.IsPhoneSelected)
#Html.LabelFor(m => m.IsPhoneSelected)
}
else
{
#Html.CheckBoxFor(m => m.IsAddressSelected)
#Html.LabelFor(m => m.IsAddressSelected)
}
.... // submit buttons
}
and the POST method, you can check the value of model.IsOwner to know if you have submitted an Owner or Tenant and take the appropriate action.
Side notes:
Recommend you read What is ViewModel in
MVC?
The <table> element is for tabular data. Do not use it for layout.
Your view has <form action="Export" .. > yet your POST method is
named Save_Definition so unsure which method you intending to
submit the form to.
If your post controller is changing original model data, you will need to issue a ModelState.Remove("propertyname") followed by model.propertyname = "new value" and then a simple return View(model) will update the changed value on your already posted view.
public class VerifyLicensorModel
{
public int TempId { get; set; }
public string Licensor { get; set; }
public string Address { get; set; }
public int LicensorId { get; set; }
public int ActionId { get; set; }
public int ReferenceId { get; set; }
}
This is my model and i am getting this list as a result of excel import and i am asking user to verify the data
So for each licensor users should select an action [ actionId ] and there is a reference based on action user selcted [ ReferenceId ] as well
So to pick ActionId and ReferenceId i am using a modal popup which allow user to select action and referenceID [ The process of deciding these 2 params is but complex include a lot of conditional checks and parallel selection of inputs ,
Example First decide liceonsor is new or existing . If decision is existing find existing id , related addressses. Then decide use new address or replace existing address . If decision is to replace existing select the one to replace
Thats why planning tokeep its logic as a seprate one and return with result only ie 2 Ids actionid and referenceID]
On submission how can i set the values of these 2 properties
My view i am planning like this
#model IList<ApplicationLab.Models.VerifyLicensorModel>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table class="table">
<tr>
<th>
Licensor
</th>
<th>
Address
</th>
<th>
Action
</th>
<th>
Verify
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => Model[i].TempId)
#Html.HiddenFor(modelItem => Model[i].LicensorId)
#Html.DisplayFor(modelItem => Model[i].Licensor)
</td>
<td>
#Html.TextAreaFor(modelItem => Model[i].Address)
</td>
<td>
#Html.HiddenFor(modelItem => Model[i].ActionId)
#Html.HiddenFor(modelItem => Model[i].ReferenceId)
</td>
<td>
<a >Verify</a> // onclick plannig to show popup
</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-10" style="text-align:center;">
<input type="submit" value="Submit" class="btn btn-primary" />
#Html.ActionLink("Cancel", "LicenseImport", "Import", new { #id = #ViewBag.EditionId }, new { #class = "btn btn-link" })
</div>
</div>
}
So is my approach is correct ? if so how can i set the selected row property ?
Or is any better approach or examples to suggest to implement the same?
Typically, your view model should be a representation of the page and the elements on the page. If this was to be used on the modal, You could use a partial view template.
You need to create new properties of List to represent the options available for ActionIds and ReferenceIds and pass as parameters for use in the helper functions #Html.DropDownListFor in the view
public class VerifyLicensorModel
{
public int TempId { get; set; }
public string Licensor { get; set; }
public string Address { get; set; }
public int LicensorId { get; set; }
public int ActionId { get; set; }
public List<SelectListItem> ActionOptions { get; set; }
public int ReferenceId { get; set; }
public List<SelectListItem> ReferenceOptions { get; set; }
}
On the View:
#Html.DropDownListFor(m => m.ActionId, Model.ActionOptions);
#Html.DropDownListFor(m => m.ReferenceId, Model.ReferenceOptions);
I have a class like as below.
public class ProductViewGridModel
{
public long Id { get; set; }
public string PackageCode { get; set; }
public string ProductName { get; set; }
public string ProductCategory { get; set; }
public IDictionary<string, string> Localizations { get; set; }
}
I rendered the model in to view like this.(foreach statement running into table, I'm not showing its here.)
#foreach (var localization in Model.Localizations)
{
var p = localization.Value;
<tr>
<td class="adminData">
<input class="k-textbox" type="text" value="#p" name="productView.Localizations[#localization.Key]" />
<input type="hidden" value="#p" name="productView.Localizations[#localization.Key]" />
</td>
</tr>
}
And I have a button, the button sending form values to controller with ajax call.
Other model bindings are okay, but I want to bind all localization inputs to localizations model.
But localizations always null. How can I bind this ?.
The input name is wrong. The default modelbinder will look for the property Localizations:
#foreach (var localization in Model.Localizations)
{
var p = localization.Value;
<tr>
<td class="adminData">
<input class="k-textbox" type="text" value="#p" name="Localizations[#localization.Key]" />
<input type="hidden" value="#p" name="Localizations[#localization.Key]" />
</td>
</tr>
}
I am binding objects in a razor foreach in the index.html:
VIEW
#using (Ajax.BeginForm("Save", "Unit", new AjaxOptions { OnSuccess = "onSuccess" }))
{
<button type="submit" class="btn btn-default" id="saveUnits"><i class="fa fa-save"></i></button>
<table>
<tbody>
#foreach (var item in Model)
{
<tr>
#Html.HiddenFor(modelItem => item.UnitId)
<td>
#Html.EditorFor(modelItem => item.Name)
</td>
<td>
#Html.EditorFor(modelItem => item.ErrorText)
</td>
</tr>
}
</tbody>
</table>
}
I have grabbed the data sent to my action parameter with fiddler and got this:
item.UnitId=5&
item.Name=111111111111&
item.ErrorText=fsdddddddddddddddd+&
item.UnitId=5&
item.Name=+&
item.ErrorText=dddddd+&
ACTION
public ActionResult Save(List<Unit> units )
{
return new EmptyResult();
}
VIEWMODEL
public class Unit
{
[HiddenInput(DisplayValue = false)]
public int UnitId { get; set; }
[DataType(DataType.MultilineText)]
public string Name { get; set; }
[DataType(DataType.MultilineText)]
public string ErrorText { get; set;
}
Why is my units instance null? The properties match so they should be bound!
Did I overlook something?
You need to use a for loop not a foreach loop. Also, it would be better to make your Model class have a property which is a collection.
Your model could be something like:
public class UnitsViewModel
{
public List<Unit> Units { get; set; }
public class Unit
{
[HiddenInput(DisplayValue = false)]
public int UnitId { get; set; }
[DataType(DataType.MultilineText)]
public string Name { get; set; }
[DataType(DataType.MultilineText)]
public string ErrorText { get; set; }
}
}
And you could do the following in your cshtml:
#for (int i = 0; i < Model.Count; i++)
{
<tr>
#Html.HiddenFor(m => m.Units[i].UnitId)
<td>
#Html.EditorFor(m => m.Units[i].Name)
</td>
<td>
#Html.EditorFor(m => m.Units[i].ErrorText)
</td>
</tr>
}
I have this model:
public class WorkflowImport {
public bool IsLive { get; set; }
public string DateTimeCreated { get; set; }
public string CreatedByUser { get; set; }
public string VersionComments { get; set; }
public string VersionNumber { get; set; }
public string FilePath { get; set; }
public List<WorkflowProcess> WorkflowProcesses { get; set; }
}
A partial view:
#model <FullyQualifiedPathTo...>.ViewModels.WorkflowImport
<div class="subSectionHeader">Upload New Workflow Profile</div>
<div class="AccountDetailLine">
#using ( Html.BeginForm( "UploadNewMatrix", "Home", FormMethod.Post, new { enctype = "multipart/form-data" } ) ) {
<table>
<tr>
<td>Select Local File:</td>
<td>
<div class="upload-wrapper">
<input type="file" name="file" id="xlsfile" />
</div>
</td>
</tr>
<tr>
<td>Version Comments:</td>
<td>
#Html.TextBox( "comments", Model.VersionComments, new Dictionary<string, object> { { "class", "textboxUploadField" } } )
</td>
</tr>
<tr>
<td>Version Number:</td>
<td>
#Html.TextBox( "version", Model.VersionNumber, new Dictionary<string, object> { { "class", "textboxUploadField" } } )
</td>
</tr>
<tr>
<td colspan="2"></td>
</tr>
<tr>
<td>Select whether this file upload will update<br />
the live workflow matrix or just the "What If" test matrix</td>
<td>LIVE #Html.RadioButtonFor( model => model.IsLive, "True" )
"What If" #Html.RadioButtonFor( model => model.IsLive, "False" )
</td>
</tr>
</table>
<div class="submit-wrapper">
<input type="submit" value="Import Now" id="form_submit" class="ovobutton" />
</div>
}
</div>
and a receiving controller/method:
public class HomeController: Controller {
// POST /Home/UploadNewMatrix
[HttpPost]
public ActionResult UploadNewMatrix( WorkflowImport workflowImport ) {
return View( workflowImport );
}
}
But when I enter some values into the two textboxes and click the submit button, I get null values in the bound object on the controller, when checking in the debugger.
I don't know why this is happening, because I used an almost identical pattern (including file upload with "multipart/form-data") on a previous project and was able to get the values successfully.
Is there something obvious I have not seen here? The difference between this new application and the previous is that it is a partial view in amongst a lot of jquery, but I don't see how that could make a difference. Also, I need to use traditional file upload as the target browser is IE8 and does not support HTML5.
For correct binding, name html element must equal property name
property:
public string VersionComments { get; set; }
View:
<tr>
<td>Version Comments:</td>
<td>
#Html.TextBox("VersionComments", Model.VersionComments, new Dictionary<string, object> { { "class", "textboxUploadField" } } )
</td>
</tr>
Note: i agree with Jacob. Using #Html.EditorFor(m=>m.VersionComments) more flexible approach, but my example demonstrates binding principle.
You're getting back null because the name of the inputs don't match what the model binder expects. To help you create the right names, ASP.NET MVC has some useful helper methods. Instead of:
#Html.TextBox(
"comments",
Model.VersionComments,
new Dictionary<string, object> { { "class", "textboxUploadField" } } )
...do this instead:
#Html.TextBoxFor(
m => m.VersionComments,
new Dictionary<string, object> { { "class", "textboxUploadField" } } )
This will use the value of VersionComments, and it will also give the input the name VersionComments so that it knows to plug this into the model when posting.