FormCollection System.Web.Mvc.ModelErrorCollection - asp.net-mvc-3

csharp newpie here, i have the following controller action that processes a strongly typed view form, some values im processing manually from fromCollection, it they are empty then i get this error:
{ Key = carCount , Errors = System.Web.Mvc.ModelErrorCollection }
but if there are incoming values e.g. "0", "1" etc then its fine!
[HttpPost]
public ActionResult create(Trip trip, FormCollection collection)//Trip trip
{
trip.carCount = TryToParse(collection["carCount"]);//int
trip.busCount = TryToParse(collection["busCount"]);//int
trip.truckCoun = TryToParse(collection["truckCount"]);//int
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => new { x.Key, x.Value.Errors })
.ToArray();
foreach (var error in errors)
{
System.Diagnostics.Debug.WriteLine(error);
}
more code................
This is the method to convert string to int, which works perfectly fine:
private int TryToParse(string value)
{
if (value.Trim() == "" || value.Trim() == null) value = "0";
int number;
bool result = int.TryParse(value, out number);
return number;
}
Any thoughts?
thanks

The default model binder which runs before your action sees that you have this Trip model as parameter to your action and tries to set its properties from the request. Now because you have defined those 3 properties as integers if the Request contains empty values for them, during the binding the default model binder will of course not be able to convert an empty strong to an integer and automatically add an error to the ModelState. Then the controller actin is invoked and all you do is to reset the values of this trip object but of course the error that was added to ModelState is left and that's what you observe.
So the solution to this problem would be to write a custom model binder or make those properties nullable integers:
public int? CarCount { get; set; }
public int? BusCount { get; set; }
public int? TruckCount { get; set; }
and then have your action look like this:
public ActionResult Create(Trip trip)
{
if (!ModelState.IsValid)
{
// there were errors => redisplay the view
return View(trip);
}
// at this stage the model is valid => process it
...
}

Related

Single property not getting bound on HttpPost

I'm working on the first MVC3 project at our company, and I've hit a block. No one can seem to figure out what's going on.
I have a complex Model that I'm using on the page:
public class SpaceModels : List<SpaceModel> {
public bool HideValidation { get; set; }
[Required(ErrorMessage=Utilities.EffectiveDate + Utilities.NotBlank)]
public DateTime EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
}
In the Controller, I create a SpaceModels object with blank SpaceModels for when Spaces get combined (this would be the destination Space).
// Need a list of the models for the View.
SpaceModels models = new SpaceModels();
models.EffectiveDate = DateTime.Now.Date;
models.DisplayEffectiveDate = true;
models.Add(new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
return View("CombineSpaces", models);
Then in the View, I am using that SpaceModels object as the Model, and in the form making a TextBox for the Effective Date:
#model Data.SpaceModels
#using (Html.BeginForm("CombineSpaces", "Space")) {
<div class="EditLine">
<span class="EditLabel LongText">
New Space Open Date
</span>
#Html.TextBoxFor(m => m.EffectiveDate, new {
size = "20",
#class = "datecontrol",
// Make this as a nullable DateTime for Display purposes so we don't start the Calendar at 1/1/0000.
#Value = Utilities.ToStringOrDefault(Model.EffectiveDate == DateTime.MinValue ? null : (DateTime?)Model.EffectiveDate, "MM/dd/yyyy", string.Empty)
})
#Html.ValidationMessageFor(m => m.EffectiveDate)
</div>
<hr />
Html.RenderPartial("_SpaceEntry", Model);
}
The Partial View that gets rendered iterates through all SpaceModels, and creates a containing the Edit fields for the individual SpaceModel objects. (I'm using the List to use the same Views for when the Spaces get Subdivided as well.)
Then on the HttpPost, the EffectiveDate is still back at it's DateTime.MinValue default:
[HttpPost]
public ActionResult CombineSpaces(SpaceModels model, long siteID, long storeID, DateTime? effectiveDate) {
// processing code
}
I added that DateTime? effectiveDate parameter to prove that the value when it gets changed does in fact come back. I even tried moving the rendering of the TextBox into the _SpaceEntry Partial View, but nothing worked there either.
I did also try using the #Html.EditorFor(m => m.EffectiveDate) in place of the #Html.TextBoxFor(), but that still returned DateTime.MinValue. (My boss doesn't like giving up the control of rendering using the #Html.EditorForModel by the way.)
There has to be something simple that I'm missing. Please let me know if you need anything else.
Looking at the source code for DefaultModelBinder, specifically BindComplexModel(), if it detects a collection type it will bind the individual elements but will not attempt to bind properties of the list object itself.
What model binding does is attempt to match the names of things or elements in the view to properties in your model or parameters in your action method. You do not have to pass all of those parameters, all you have to do is add them to your view model, then call TryUpdateModel in your action method. I am not sure what you are trying to do with SpaceModel or List but I do not see the need to inherit from the List. Im sure you have a good reason for doing it. Here is how I would do it.
The view model
public class SpacesViewModel
{
public DateTime? EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
public List<SpaceModel> SpaceModels { get; set; }
}
The GET action method
[ActionName("_SpaceEntry")]
public PartialViewResult SpaceEntry()
{
var spaceModels = new List<SpaceModel>();
spaceModels.Add(
new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
var spacesVm = new SpacesViewModel
{
EffectiveDate = DateTime.Now,
DisplayEffectiveDate = true,
SpaceModels = spaceModels
};
return PartialView("_SpaceEntry", spacesVm);
}
The POST action method
[HttpPost]
public ActionResult CombineSpaces()
{
var spacesVm = new SpacesViewModel();
// this forces model binding and calls ModelState.IsValid
// and returns true if the model is Valid
if (TryUpdateModel(spacesVm))
{
// process your data here
}
return RedirectToAction("Index", "Home");
}
And the view
<label>Effective date: </label>
#Html.TextBox("EffectiveDate", Model.EffectiveDate.HasValue ?
Model.EffectiveDate.Value.ToString("MM/dd/yyyy") : string.empty,
new { #class = "datecontrol" })
Sometimes you need to explicitly bind form data using hidden fields such as
#Html.HiddenField("EffectiveDate", Model.EfectiveDate.)
In order to bind the properties of the SpaceModel object you can add individual properties such as SiteID to the view model or add a SpaceModel property for a single SpaceModel. If you want to successfully bind a complex model, add it as a Dictionary populated with key-value pairs rather than a List. You should then add the dictionary to the view model. You can even add a dictionary of dictionaries for hierarchical data.
I hope this helps :)

implementing dropdownlist in asp.net mvc 3

I am teaching myself asp .net mvc3. I have researched a lot but the more I read the more confused I become. I want to create a page where users can register their property for sale or rent.
I have created a database which looks like this:
public class Property
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
ยทยทยท
public int Furnished { get; set; }
...
}
Now, I want dropdownlistfor = PropertyType and Furnished.
Property type would be
1 Flat
2 House
3 Detached House
...
Furnished would be:
1 Furnished
2 UnFurnished
3 PartFurnished
...
Now, I am really not sure where to keep this information in my code. Should I have 2 tables in my database which store this lookup? Or should I have 1 table which has all lookups? Or should I just keep this information in the model?
How will the model bind to PropertyType and Furnished in the Property entity?
Thanks!
By storing property types and furnished types in the database, you could enforce data integrity with a foreign key, rather than just storing an integer id, so I would definitely recommend this.
It also means it is future proofed for if you want to add new types. I know the values don't change often/will never change but if you wanted to add bungalow/maisonette in the future you don't have to rebuild and deploy your project, you can simply add a new row in the database.
In terms of how this would work, I'd recommend using a ViewModel that gets passed to the view, rather than passing the database model directly. That way you separate your database model from the view, and the view only sees what it needs to. It also means your drop down lists etc are strongly typed and are directly in your view model rather than just thrown into the ViewBag. Your view model could look like:
public class PropertyViewModel
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
public IEnumerable<SelectListItem> PropertyTypes { get; set; }
public int Furnished { get; set; }
public IEnumerable<SelectListItem> FurnishedTypes { get; set; }
}
So then your controller action would look like:
public class PropertiesController : Controller
{
[HttpGet]
public ViewResult Edit(int id)
{
Property property = db.Properties.Single(p => p.Id == id);
PropertyViewModel viewModel = new PropertyViewModel
{
PropertyId = property.Id,
PropertyType = property.PropertyType,
PropertyTypes = from p in db.PropertyTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.PropertyTypeId.ToString()
}
Furnished = property.Furnished,
FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
}
};
return View();
}
[HttpGet]
public ViewResult Edit(int id, PropertyViewModel propertyViewModel)
{
if(ModelState.IsValid)
{
// TODO: Store stuff in the database here
}
// TODO: Repopulate the view model drop lists here e.g.:
propertyViewModel.FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
};
return View(propertyViewModel);
}
}
And your view would have things like:
#Html.LabelFor(m => m.PropertyType)
#Html.DropDownListFor(m => m.PropertyType, Model.PropertyTypes)
I usually handle this sort of situation by using an enumeration in code:
public enum PropertyType {
Flat = 1,
House = 2,
Detached House = 3
}
Then in your view:
<select>
#foreach(var val in Enum.GetNames(typeof(PropertyType)){
<option>val</option>
}
</select>
You can set the id of the option equal to the value of each item in the enum, and pass it to the controller.
EDIT: To directly answer your questions:
You can store them as lookups in the db, but for small unlikely to change things, I usually just use an enum, and save a round trip.
Also look at this approach, as it looks better than mine:
Converting HTML.EditorFor into a drop down (html.dropdownfor?)

Error on TryUpdateModel

I'm getting an error with TryUpdateModel when the field doesn't have a value and it's not nullable in the database. If I change the field in the database to nullable, it works. Anybody know a fix to get this to work with non nullable fields? I'm new to MVC, so I'm just playing with a tutorial to try to understand it....
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(FormCollection form)
{
var movieToAdd = new Movies();
if (Request != null && Request.Form != null && Request.Form.HasKeys() && ValueProvider == null)
{
ValueProvider = new FormCollection(Request.Form).ToValueProvider();
}
//Deserialize (Include white list!)
TryUpdateModel(movieToAdd, new string[] { "Title", "Director" }, form.ToValueProvider());
// Validate
if (string.IsNullOrEmpty(movieToAdd.Title))
ModelState.AddModelError("Title", "Title is required!");
if (string.IsNullOrEmpty(movieToAdd.Director))
ModelState.AddModelError("Director", "Director is required!");
// If valid, save movie to database
if (ModelState.IsValid)
{
_db.AddToMovies1(movieToAdd);
_db.SaveChanges();
return RedirectToAction("Index");
}
// Otherwise, reshow form
return View(movieToAdd);
}
There are a few things I would change.
Modify your method signature to accept the strongly typed model that your ActionResult is expecting. From there you can check if the Model is valid or not and continue with saving to the database
Use strongly typed models and data annotations for validating your model
EG:
Create a model which you would use for the form Get And Post actions:
public class YourModel
{
[Required]
public string Title {get; set;]
[Required]
public string Director {get; set;}
}
Your Post ActionResult then becomes:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(YourModel postedform)
{
// If valid, save movie to database
if (ModelState.IsValid)
{
_db.AddToMovies1(postedform);
_db.SaveChanges();
return RedirectToAction("Index");
}
// Otherwise, reshow form
return View(postedform);
}

ASP.NET MVC change model data type in before send to view

I used Linq to Entity, for fetch a model from DB
var works = db.Work.Where(a => a.StartDate == DateTime.Now).ToList();
this model contain a DateTime that I want to change it to string before send it to view
beacuase need to show it as a persianDate Time,
like this :
foreach (var item in workslist)
{
item.StartDate = "1391/01/01"; //Just For Exapmle as you know this won't work
}
return View(workslist);
So is there any way to change a data type in a strongly typed model? or other way to do this except save DateTime as String in my DB
You can use the [DisplayFormat] attribute with EditorFor/DisplayFor helpers to format the data.
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy/mm/dd}")]
public DateTime StartDate { get; set; }
Then in view:
#Html.EditorFor(m => m.StartDate)
OR, you can also consider to define separate ViewModel for your 'Work' domain model. Though this approach results in more code, it's provide more flexibility and considered as good practice.
Here is sample code:
public class Work
{
public DateTime StartDate {get;set;}
}
public class WorkViewModel
{
public WorkViewModel()
{
}
public string StartDate { get; set; }
public static WorkViewModel Map(Work domainModel)
{
return new WorkViewModel() {
//Apply your Date format logic
StartDate = domainModel.StartDate.ToString("yyyy/MM/dd")
};
}
}
In action method:
var works = db.Work.Where(a => a.StartDate == DateTime.Now).ToList();
var workViewModels = new List<WorkViewModel>(works.Count);
foreach (var work in works)
workViewModels.Add(WorkViewModel.Map(work));
return View(workViewModels);
you can have a function in your controller to change datetime to string
public string changeDateTimeToPersianString ( DateTime time)
{
// your code
}
then you can call this function from your View.
in Razor something like this
#changeDateTimeToPersianString(workslistitem.StartDate)

Primary key set to zero on submit of IEnumerable<T>

So I created a variable length list using the code shown here.
When I submit the form, the primary keys are reset to 0. In the HTML form, they are not zero but the actual values. How do I fix this?
The form
#using (Html.BeginCollectionItem("Kortingartikel")) {
#Html.HiddenFor(x => x.Artikelid)
#Html.TextBoxFor(x => x.Artikelnaam)
#Html.TextBoxFor(x => x.Prijs)</td>
}
The data
According to Chrome, this data is sent to the server:
Kortingartikel.index:ad56efb0-ab7f-4b37-9d9b-712d7c3e3543
Kortingartikel[ad56efb0-ab7f-4b37-9d9b-712d7c3e3543].Artikelid:5
Kortingartikel[ad56efb0-ab7f-4b37-9d9b-712d7c3e3543].Artikelnaam:test artikel een
Kortingartikel[ad56efb0-ab7f-4b37-9d9b-712d7c3e3543].Prijs:10,00
Kortingartikel.index:b9624d8f-38e6-4092-ba1b-d004d0443a43
Kortingartikel[b9624d8f-38e6-4092-ba1b-d004d0443a43].Artikelid:6
Kortingartikel[b9624d8f-38e6-4092-ba1b-d004d0443a43].Artikelnaam:test artikel twee
Kortingartikel[b9624d8f-38e6-4092-ba1b-d004d0443a43].Prijs:5,00
The Action
The data is sent to the following action:
public ActionResult Kortingartikel(IEnumerable<Kortingartikel> Kortingartikel)
The 'Kortingartikel' parameter has the following values:
[0]Artikelnaam:test artikel een
Prijs: 10
Artikelid: 0
[1]Artikelnaam:test artikel twee
Prijs: 5
Artikelid: 0
The property/field Artikelid
Artikelid is generated from a linq-to-sql dbml file. Here is the (autogenerated) code:
[global::System.Data.Linq.Mapping.ColumnAttribute(Name="artikelid", Storage="_artikelid", AutoSync=AutoSync.OnInsert, DbType="BigInt NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true, UpdateCheck=UpdateCheck.Never)]
public long Artikelid
{
get
{
return this._artikelid;
}
set
{
if ((this._artikelid != value))
{
this.OnArtikelidChanging(value);
this.SendPropertyChanging();
this._artikelid = value;
this.SendPropertyChanged("Artikelid");
this.OnArtikelidChanged();
}
}
}
I suspect that the Artikelid on your Kortingartikel view model either doesn't have a setter:
public int Artikelid { get; } // Bad
or it isn't public:
protected int Artikelid { get; set; } // Bad
or it isn't a property at all but it is a field:
public int Artikelid; // Bad
In all those cases the default model binder wan't be able to set its value from the request. So make sure that this property is declared with public getter and setter on your view model:
public int Artikelid { get; set; } // Good
Also to avoid possible conflicts try renaming your action parameter:
public ActionResult Kortingartikel(IEnumerable<Kortingartikel> model)

Resources