My viewmodel contains a list of strings:
public class SupplierViewModel
{
public Supplier Supplier { get; set; }
public List<string> Numbers;
}
The user can add any number of strings in the view:
<div class="editor-label">
#Html.LabelFor(model => model.Numbers)
</div>
<div id="allNumbers">
#for (int i = 0; i < Model.Numbers.Count; i++)
{
<div class="editor-label">
#Html.TextBoxFor(m => m.Numbers[i])
</div>
}
</div>
<div id="newNumber" style="display:none;">
<input type="text" name="Numbers[#]" style="display:block;" />
</div>
<button type="button" id="addNumber" >Add</button>
This is done on the client side with jquery:
var container = $('#allNumbers');
$('#addNumber').click(function () {
var index = container.children().length;
var clone = $('#newNumber').clone();
clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']'));
container.append(clone.html());
});
My problem is that none of the dynamically added strings are sent back to the controller even tough they are all named "Numbers[i]".
What am I missing here?
Numbers is a field and the DefaultModelBinder cannot set the value of a field. You need to make it a property by adding getters/setters
public List<string> Numbers { get; set; }
Related
I've been striking out on how to handle this situation. I have a model, the project model, and it has numerous items that need to be dropdowns for the user to change the selected item in question. I started out simple with the ProjectType value, which should have the selectable values populated form the ProjectTypes table. Here is the ViewModel:
public class ProjectViewModel
{
public APT_Projects Project { get; set; }
public System.Linq.IQueryable<APT_ProjectTypes> projecttypes { get; set; }
public APT_ClientTypes ClientTypes {get; set;}
}
Here is the controller:
public ActionResult Edit(int id = 0)
{
APT_Projects apt_projects = db.APT_Projects.Find(id);
if (apt_projects == null)
{
return HttpNotFound();
}
ProjectViewModel Project = new ProjectViewModel();
var apt_projecttypes = from a in db.APT_ProjectTypes
select a;
Project.Project = apt_projects;
Project.projecttypes = apt_projecttypes;
return View(Project);
}
and finally the view:
#model APTII_MVC.ViewModel.ProjectViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>APT_Projects</legend>
<div class="editor-label" style="float: left; width: 500px;" >
#Html.LabelFor(model => model.Project.Project_ID)
</div>
<div class="editor-field" style="float: left; width: 500px;" >
#Html.EditorFor(model => model.Project.Project_ID)
#Html.ValidationMessageFor(model => model.Project.Project_ID)
</div>
<div class="editor-label"">
#Html.LabelFor(model => model.Project.ProjectType_ID)
</div>
<div class="editor-field"">
#Html.DropDownListFor(model => model.Project.ProjectType_ID, Model.projecttypes)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
When I do it in this manner, .net doesn't like the Drowpdownlistfor, but I'm unclear how to use these values in a manner that the dropdownlistfor would accept. What is my mistake?
Edit: Relevent Error for the Dropdownlistfor.
'System.Web.Mvc.HtmlHelper<ViewModel.ProjectViewModel>' does not contain a definition for 'DropDownListFor' and the best extension method overload 'System.Web.Mvc.Html.SelectExtensions.DropDownListFor<TModel,TProperty>(System.Web.Mvc.HtmlHelper<TModel>, System.Linq.Expressions.Expression<System.Func<TModel,TProperty>>, System.Collections.Generic.IEnumerable<System.Web.Mvc.SelectListItem>)' has some invalid arguments c:\Visual Studio 2012\Projects\Test_MVC\Test_MVC\Views\Project\Edit.cshtml
you might need a List<SelectListItem> or SelectList().. so try converting your projecttypes object to
#Html.DropDownListFor(model => model.Project.ProjectType_ID, new SelectList(Model.projecttypes,"ID","Type")
or edit your viewmodel to
public SelectList projecttypes { get; set; }
and your controller code to
Project.projecttypes = new SelectList(db.APT_ProjectTypes,"ID","Type");
just guessing on your value/text field names
I have this code in my controller:
[HttpPost]
public ActionResult Index(double userLat, double userLng)
{
var context = new weddingspreeEntities();
var coordinates = context.Venues
.Select(loc => new { vname = loc.VenueName, lat = loc.VenueLat, lng = loc.VenueLong })
.ToList();
string venueName = string.Empty;
List<SearchModel.DistLocation> venDistList = new List<SearchModel.DistLocation>();
for (int i = 0; i < coordinates.Count; i++)
{
string name = coordinates[i].vname;
double? lat = coordinates[i].lat;
double? lng = coordinates[i].lng;
var loc1Lat = lat.Value;
var loc1Lng = lng.Value;
var loc2Lat = userLat;
var loc2Lng = userLng;
double distance = TrackingHelper.CalculateDistance(
new SearchModel.Location() { Latitude = loc1Lat, Longitude = loc1Lng },
new SearchModel.Location() { Latitude = loc2Lat, Longitude = loc2Lng });
//convert kilometers to miles
double distMiles = distance * 0.621371192;
venueName = name;
venDistList.Add(new SearchModel.DistLocation() { venName = name, Distance = distMiles });
}
return View(venDistList);
}
I have this code in my view:
<div class="row">
<div class="form-group">
<div class="col-md-6">
#using (Html.BeginForm("Search", "Home", FormMethod.Post))
{
#*#Html.TextBoxFor(model => model.cityName)*#
<label>Enter City and State or Zip Code</label>
<input type="text" id="citystate" name="citystate" />
<label>Enter Your Wedding Date</label>
<input class="datefield" data-val="true" data-val-required="Date is required" id="weddingDate" name="weddingDate" type="date" value="1/11/1989" />
<label>Enter Your Guest Count</label>
<input type="text" id="guestcount" name="guestcount" />
<input type="button" id="search" name="search" value="Search for Venues" />
}
</div>
<!--This is the div where the google map will render -->
<div class="col-md-6">
<div id="map_canvas" style="height: 600px;"></div>
</div>
</div>
</div>
<div>
#Html.Partial("_SearchResults")
</div>
I have omitted some of my view for brevity
This is the partial view I am trying to render:
#model IEnumerable<WeddingSpree_Alpha.Models.SearchModel.DistLocation>
#{
Layout = null;
}
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
foreach (var item in Model)
{
#item.venName
#item.Distance
}
}
What I am trying to do is to have the user enter the values in the search box and then after the click post the results (in the list named venDistList) to the view using a foreach statement.
The model looks like this:
public class SearchModel
{
public string cityName { get; set; }
public DateTime weddingDate { get; set; }
public int guestCount { get; set; }
public class Location
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class DistLocation
{
public string venName { get; set; }
public double Distance { get; set; }
}
}
I would like the list results to populate after the button click (post) on the page. I thought my code would do that however. I get the following error:
System.NullReferenceException: 'Object reference not set to an instance of an object'
I know that error happens when you try to use a model that is not populated yet but I thought I did that in my controller code? What exactly could be throwing that error?
This is the controller code for my partial view:
public ActionResult _SearchResults(SearchModel model)
{
return View();
}
If you are not at least instantiating an instance of IEnumerable to pass back (even if it is empty) then it will throw the null reference when you try to iterate throught the model in the partial view.
Edit: (Code trimmed down for example) Your original error is that you are trying to iterate through an object that does not exist. The below will show you how to make user of an Ajax call on your form submit to dynamically generate your partial view and attach it to your main page
Controller:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult _SearchResults(string citystate, DateTime? weddingDate, double? guestcount)
{
List<SearchModel.DistLocation> venDistList = new List<SearchModel.DistLocation>();
venDistList.Add(new SearchModel.DistLocation() { venName = "weee1", Distance = 2 });
venDistList.Add(new SearchModel.DistLocation() { venName = "weee2", Distance = 4 });
venDistList.Add(new SearchModel.DistLocation() { venName = "weee3", Distance = 6 });
return PartialView(venDistList);
}
Index.cshtml:
#{
ViewBag.Title = "Home Page";
}
#*This is our form which will feed our user input and drive our search results output*#
<div class="row">
<div class="form-group">
<div class="col-md-6">
<form id="searchMe">
<label>Enter City and State or Zip Code</label>
<input type="text" id="citystate" name="citystate" />
<label>Enter Your Wedding Date</label>
<input class="datefield" data-val="true" data-val-required="Date is required" id="weddingDate" name="weddingDate" type="date" value="1/11/1989" />
<label>Enter Your Guest Count</label>
<input type="text" id="guestcount" name="guestcount" />
<button type="submit" class="btn btn-primary">Search for Venues</button>
</form>
</div>
</div>
</div>
<div class="row">
#*This is where we want our search results to appear when user hits submit on our form*#
<div id="SearchResult"></div>
</div>
#section scripts {
<script>
$(document).ready(function () {
//When the user hit the submit button we will post the form results to our partial view controller
$('#searchMe').submit(function () {
$.ajax({
method: "POST",
url: "/Home/_SearchResults",
data: $(this).serialize(),
success: function (result) {
//When then load our partial view into our containing div on the main page
$('#SearchResult').html(result);
}
});
return false;
});
});
</script>
}
Partial View (_SearchResult.cshtml)
#model IEnumerable<deletemeweb2.Models.SearchModel.DistLocation>
#{
Layout = null;
}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Search Results</h3>
</div>
<div class="panel-body">
#if (Model != null || Model.Count() < 1)
{
using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
foreach (var item in Model)
{
<p>#item.venName</p>
<p>#item.Distance</p>
}
}
}
else
{
<p>No results found</p>
}
</div>
</div>
hello everyone I have a question. I have a form which consist of dropdown textbox and datetimepicker. I can fill my dropdown from my model but I cannot post the data to the database. Here are my codes
My Controller codes this is where the data selected and shown in view
public ActionResult orderProduct()
{
Repository<OrderProduct> _ro = new Repository<OrderProduct>();
IEnumerable<OrderProduct> _orderProduct = _ro.All().OrderByDescending(o => o.id);
return View(_orderProduct);
}
I am filling the dropdownlist from database
public ActionResult addOrderProduct()
{
/*
Repository<Workshop> _rw = new Repository<Workshop>();
IEnumerable<Workshop> _workshop = _rw.All().OrderByDescending(o => o.id);
IEnumerable<SelectListItem> _selectList = from w in _workshop
select new SelectListItem {
Text = w.name,
Value = w.id.ToString()
};
*/
Repository<Workshop> _rw = new Repository<Workshop>();
IEnumerable<SelectListItem> _workshopSelectListItem = _rw.All().AsEnumerable().Select(s =>
new SelectListItem {
Text = s.name, Value=s.id.ToString()
});
ViewData["dropdown"] = _workshopSelectListItem;
return View();
}
here I am trying to post my data to the database. I cannot select data from dropdown and datetimepicker also I cannot post this data by writing manually.
public ActionResult orderProductAdd(int adet, float cmt)
{
Repository<OrderProduct> _rp = new Repository<OrderProduct>();
OrderProduct _orderProduct = new OrderProduct { workshopId = 1, orderId = 1, value = adet, shipDate = new DateTime(2005, 02, 01), cmt = cmt };
return RedirectToAction("orderProduct");
}
this is my model
[Table("OrderProduct")]
public class OrderProduct
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public int orderId { get; set; }
[ForeignKey("orderId")]
public virtual Order order { get; set; }
public int workshopId { get; set; }
[ForeignKey("workshopId")]
public virtual Workshop workshop { get; set; }
public int value { get; set; }
public float cmt { get; set; }
public DateTime shipDate { get; set; }
/*
[id]
,[orderId]
,[workshopId]
,[value]
,[cmt]
,[shipDate]
*/
}
and also this is my view "addOrderProduct"
<form action="/Order/orderProductAdd" class="form-horizontal">
<div class="control-group">
<label class="control-label">Atölye Seçiniz</label>
<div class="controls">
#Html.DropDownList("dropdown",(IEnumerable<SelectListItem>)ViewData["dropdown"],"secim yapınız", new { #class = "span6 chosen" })
#*<select class="span6 chosen" data-placeholder="Choose a Category" tabindex="1">
<option value=""></option>
<option value="Category 1">A1</option>
<option value="Category 2">A2</option>
<option value="Category 3">A3</option>
<option value="Category 4">A4</option>
</select>*#
</div>
</div>
<div class="control-group">
<label class="control-label">Adet Giriniz</label>
<div class="controls">
<input type="text" class="span6 " name="adet" />
<span class="help-inline">Sadece sayı giriniz</span>
</div>
</div>
<div class="control-group last">
<label class="control-label">İhracat Tarihi</label>
<div class="controls">
<div id="ui_date_picker_inline"></div>
</div>
</div>
<div class="control-group">
<label class="control-label">Cmt</label>
<div class="controls">
<input type="text" class="span6 " name="cmt" />
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-success">Onayla</button>
#*<button type="button" class="btn">Cancel</button>*#
</div>
</form>
How can I solve this ? Thank you.
The first argument in the DDL (below) is the assigned parameter being passed back to the server. When you call the action you're not passing the parameter dropdown. You're only calling int adet, float cmt but not a parameter called dropdown
#Html.DropDownList("dropdown",(IEnumerable<SelectListItem>)ViewData["dropdown"],
"secim yapınız", new { #class = "span6 chosen" })
So update your code to something like the one below:
public ActionResult orderProductAdd(int adet, float cmt, string dropdown){
// DO SOMETHING HERE
}
I can't see the input control which is being constructed for the DATETIME part of your query, however it will be similar to the above. Ensure the name of the INPUT matches the parameters being passed back to the server.
Since I'm handling a extremely complex model and forms I will reduce my problem in a more understandable example (please excuse if there is any typo).
First I will show the scenario:
The model...
public class Foo
{
[Required]
public int fooId {get; set;}
public string fooName {get; set;}
public List<Bar> barList {get; set;}
}
public class Bar
{
[Required]
public int barId {get; set;}
public string barName {get; set;}
}
The view...
#model Foo
#using (Html.BeginForm("Save", "form", FormMethod.Post))
{
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(model => Model.fooId)
</div>
</div>
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(model => Model.fooName)
</div>
</div>
#for (int i = 0; i < Model.barList.Count(); i++)
{
#Html.EditorFor(m => m.barList[i])
}
}
The "bar" editor template...
#model Bar
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(m => m.barId)
</div>
</div>
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(m => m.barName)
</div>
</div>
The problem that I'm having is during the client-side validation for inputs in nested collections, in this case I'm not able to validate the "barId" input field. It simply ignores it...
In the case of the fooId field, it's validated OK.
If we go deeper, a "foo" object with 2 "bar" items would generate something like this:
<div class="control-group">
<div class="controls">
<input class="input-validation-error" id="fooId" name="fooId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="fooName" name="fooName" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_0__barId" name="barList[0].barId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_0__barName" name="barList[0].barName" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_1__barId" name="barList[1].barId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_1__barName" name="barList[1].barName" type="text" value="">
</div>
</div>
As you can see, the items inside the "bar" collection have received a different id and name. This is the normal behaviour for rendering the collections.
But it seems to be that the client-side validation doesn't work with these ids and names. The validation will work only if I change the id & name to "barId", removing the collection index..
After hours of investigation, I've found some articles and posts regarding issues like this, but nothing concrete and I still could not solve this.
IValidatableObject in MVC3 - client side validation
mvc clientside validation for nested (collection) properties
I did not find a solution, but I did find a workaround.
Functional explanation: the Model is called "InsuranceLine", and it has a collection "InsuranceLine.Letters." Each Letter has a nullable Boolean property "Letter.IsDeficient". If "IsDeficient" is changed from False to True then the string field "Letter.ReasonCode" is required. "IsDeficient" is rendered as a checkbox, and "ReasonCode" is rendered as two radio buttons, "Corrected" and "Waived".
Here is the custom attribute:
public class ReasonCodeAttribute : ValidationAttribute, IClientValidatable
{
private const string errorMessage = "When 'Deficient' is changed from True to False you must select a Reason.";
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "reasoncoderequired"
};
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Letter letter = validationContext.ObjectInstance as Letter;
if(!letter.IsDeficient.GetValueOrDefault()
&& letter.IsDeficient_OriginalState.GetValueOrDefault()
&& (value == null || string.IsNullOrEmpty(value.ToString())))
{
return new ValidationResult(errorMessage);
}
else
{
return null;
}
}
}
I decorate Letter.ReasonCode with the custom attribute:
[ReasonCodeAttribute]
public string ReasonCode { get; set; }
I render the nested Letters collection in the *.cshtml page:
#for (int i = 0; i < Model.Letters.Count; i++)
{
#Html.EditorFor(m => m.Letters[i].IsDeficient, "MyCustomTemplate", new { htmlAttributes = new { #class="cb-is-deficient" } })
<div class="rb-reason-code">
<label>#Html.RadioButtonFor(m => m.Letters[i].ReasonCode, myVar == "C", new { id = id + "C", #class ="rb-reason-code" }) Corrected</label>
<label>#Html.RadioButtonFor(m => m.Letters[i].ReasonCode, myVar == "W", new { id = id + "W", #class = "rb-reason-code" }) Waived</label>
</div>
}
The GetClientValidationRules() method of the ReasonCode attribute causes the asp.net runtime to generate the following attribute when it renders the ReasonCode into an html radio button:
data-val-reasoncoderequired="When 'Deficient' is changed from True to False you must select a Reason.".
In JavaScript I add the 'reasoncoderequired' method to the validator like so in the document ready method. As part of my workaround I need to manually add the class "error" to my display so that the user gets a visual hint of the invalid state of the model:
$.validator.addMethod('reasoncoderequired', function (value, element) {
var $parent = $(element).closest('div.parent');
var $cb = $parent.find('input[type="checkbox"].cb-is-deficient');
if ($cb.prop('defaultChecked')) {
var $selectedRadioButton = $parent.find('div.rb-reason-code').find('input[type="radio"]:checked');
if ($selectedRadioButton.length == 0) {
$parent.addClass('error');
return false;
}
}
$parent.removeClass('error');
return true;
});
Finally, I add the reasoncoderequired rule to each ReasonCode radio button like so, also in the document ready method. The "messages" simply reads from the data-val-reasoncoderequired attribute rendered with each input to display the error message:
$form.find('input[type="radio"].rb-reason-code').each(function () {
$(this).rules('add',
{
reasoncoderequired: true,
messages: { reasoncoderequired: $(this).attr('data-val-reasoncoderequired') }
});
})
I follow this example to make n to n relations
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
it work fine but for the n to n relations with payload to the database i figure out and I can do the [HttpGet] and show the view what i want to show but now i want to know how can i get the textbox I have in my view, I can get the checkbox in my controller (see see below the action) and this is my view so my question will be how can get the textbox too? in my controller for every checkbox?
#using (Html.BeginForm("AgregarEmpresas", "Empleado"))
{
<fieldset>
<div class="editor-field">
<table>
<tr>
#{
int cnt = 0;
List<ITCOrganigrama.ViewModel.AsignarEmpresa> empresas = ViewBag.Empresas;
foreach (var empresa in empresas)
{
if (cnt++ % 5 == 0) {
#: </tr> <tr>
}
#: <td>
<input type="checkbox"
name="selectedEmpresa"
value="#empresa.EmpresaId"
#(Html.Raw(empresa.Assigned ? "checked=\"checked\"" : "")) />
#empresa.Nombre
<div>
#Html.LabelFor(model => empresa.cargo)
#Html.TextBoxFor(model => empresa.cargo, new { style = "width: 150px;" })
#Html.ValidationMessageFor(model => empresa.cargo)
</div>
#:</td>
}
#: </tr>
}
</table>
</div>
<p>
<input type="submit" value="Agregar" />
</p>
</fieldset>
}
the action where i get the chekbox
[HttpPost]
public ActionResult AgregarEmpresas(int? id, FormCollection formCollection, string[] selectedEmpresa)
{
}
my final view:
http://s3.subirimagenes.com:81/otros/previo/thump_7406511add1.jpg
http://www.subirimagenes.com/otros-add1-7406511.html
Edited:
ViewModel Class
public class AsignarEmpresa
{
public int EmpresaId { get; set; }
public string Nombre { get; set; }
public string cargo { get; set; }
public bool Assigned { get; set; }
}
Look at your post action and it's parameters. Names of those are really important.
Your checkboxes
<input type="checkbox"
name="selectedEmpresa"
value="#empresa.EmpresaId"
are working fine beacuse look at the name of the input its "selectedEmpresa" it's the same name as in Controller Action definition. Model binder looks for this name in posted data and if he finds it, he creates object from it. In your case he will try to parse data into string[] object.
So ... first of all you have to change action definition to something like.
[HttpPost]
public ActionResult AgregarEmpresas(int? id, FormCollection formCollection, string[] selectedEmpresa,string [] empresaTextBox)
{
}
Then you have to change generated html.
#Html.TextBoxFor(model => empresa.cargo, new { style = "width: 150px;", name="empresaTextBox" })
With those changes you should get some data inside Action. However you will get something weird beacuse you have multiple checkboxes and textboxes in order to tell model binder that there are multiple elements of those you have to prepare special name of inputs that contains index number.
Look at this example
<input name="childs[0]"></input>
<input name="childs[1]"></input>
In this case Model Binder will create array of objects that contains 2 of them.
So finally your code would have to look like this.
#using (Html.BeginForm("AgregarEmpresas", "Empleado"))
{
<fieldset>
<div class="editor-field">
<table>
<tr>
#{
int cnt = 0;
int i=0;
List<ITCOrganigrama.ViewModel.AsignarEmpresa> empresas = ViewBag.Empresas;
foreach (var empresa in empresas)
{
if (cnt++ % 5 == 0) {
#: </tr> <tr>
}
#: <td>
<input type="checkbox"
name="selectedEmpresa[#i]"
value="#empresa.EmpresaId"
#(Html.Raw(empresa.Assigned ? "checked=\"checked\"" : "")) />
#empresa.Nombre
<div>
#Html.LabelFor(model => empresa.cargo)
#Html.TextBoxFor(model => empresa.cargo, new { style = "width: 150px;" ,name=String.Format("empresaTextBox\[{0}\]",i) })
#Html.ValidationMessageFor(model => empresa.cargo)
</div>
#:</td>
i++;
}
#: </tr>
}
</table>
</div>
<p>
<input type="submit" value="Agregar" />
</p>
</fieldset>
}
If you will get this to work. Then i would try to create a class with one boolean and one string value. With this change you would operate on the array of classes instead of two arrays with strings.