am getting an error when i try saving my form, one of the field is alternative, so you can add it or not but it should still run.
This is my code
[HttpPost]
public ActionResult Save(EntrtyForm form)
{
var addForm = new M2CPDAL.Models.CustomerPortal.SerialUser();
addForm.ser_num = form.SerialNumber;
addForm.prod_num = form.ProductNumber;
addForm.UserName = form.UserName;
addForm.AltUserName = form.AltUserName;
cp.SerialUsers.Add(addForm);
cp.SaveChanges();
return RedirectToAction("Index");
}
My save button saves 4 fields as seen above, however
addForm.AltUserName = form.AltUserName;
is an optional field.
when i run this code it shows null on the database but gives me an error on cp.SaveChanges();
how can i make this optional so it doesnt matter if users enter a field or not..
html code:
<div>
#Html.LabelFor(m => m.AltUserName)
#Html.TextBoxFor(m => m.AltUserName, new { #class = "form" })
#Html.ValidationMessageFor(m => m.AltUserName)
</div>
Make sure that you removed the [required] attribute from your field in your ViewModel:
[Required(ErrorMessage = "")]
public string MyAttribute { get; set; }
If your field is not a string, make sure that you add a ? keyword like this:
public int? MyAttribute { get; set; }
Also check that there is no JQuery Validation on your field in client-side.
Note that if you want to disable validation for all your fields, you can modify your controller to do not test for ModelState by removing the ModelState check:
if(ModelState.IsValid)
{
// TODO:
}
Related
I have a simple model which uses a multi select listbox for a many-many EF relationship.
On my Create action, I'm getting the error
The parameter conversion from type 'System.String' to type 'MyProject.Models.Location' failed because no type converter can convert between these types.
I have 2 models, an Article and a Location:
Article.cs
namespace MyProject.Models
{
public class Article
{
public Article()
{
Locations = new List<Location>();
}
[Key]
public int ArticleID { get; set; }
[Required(ErrorMessage = "Article Title is required.")]
[MaxLength(200, ErrorMessage = "Article Title cannot be longer than 200 characters.")]
public string Title { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
Location.cs:
namespace MyProject.Models
{
public class Location
{
[Key]
public int LocationID { get; set; }
[Required(ErrorMessage = "Location Name is required.")]
[MaxLength(100, ErrorMessage = "Location Name cannot be longer than 100 characters.")]
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
}
I have a ViewModel:
namespace MyProject.ViewModels
{
public class ArticleFormViewModel
{
public Article article { get; set; }
public virtual List<Location> Locations { get; set; }
public ArticleFormViewModel(Article _article, List<Location> _locations)
{
article = _article;
Locations = _locations;
}
}
}
create.cshtml:
#model MyProject.ViewModels.ArticleFormViewModel
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Article</legend>
<div class="editor-label">
#Html.LabelFor(model => model.article.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.article.Title)
#Html.ValidationMessageFor(model => model.article.Title)
</div>
<h3>Locations</h3>
#Html.ListBoxFor(m=>m.article.Locations,new MultiSelectList(Model.Locations,"LocationID","Name"))
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Finally my controller actions:
// GET: /Article/Create
public ActionResult Create()
{
var article = new Article();
var AllLocations = from l in db.Locations
select l;
ArticleFormViewModel viewModel = new ArticleFormViewModel(article, AllLocations.ToList());
return View(viewModel);
}
//
// POST: /Article/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Article article)
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid)
{
var locations = Request.Form["article.Locations"];
if (locations != null)
{
var locationIDs = locations.Split(',');
foreach (var locationID in locationIDs)
{
int id = int.Parse(locationID);
Location location = db.Locations.Where(l => l.LocationID == id).First();
article.Locations.Add(location);
}
}
db.Articles.Add(article);
db.SaveChanges();
return RedirectToAction("Index");
}
var AllLocations = from l in db.Locations
select l;
ArticleFormViewModel viewModel = new ArticleFormViewModel(article, AllLocations.ToList());
return View(viewModel);
}
This all works relatively well, my Locations listbox is populated properly:
If I do not select a Location then my model is saved properly. If I select one or more locations then my Model.IsValid check fails with the exception
The parameter conversion from type 'System.String' to type 'MyProject.Models.Location' failed because no type converter can convert between these types.
However if I remove the ModelState.IsValid check then despite the error my values are all correctly saved into the database - just that I lose validation for things such as the model title.
Hope someone can help!
Unless you create a type converter, you cannot directly bind the results of your list box directly to a complex object like that. The reason lies in the fact that MVC can only deal with posted HTTP values, which in this case are an array of strings that contain the selected ID's. Those strings do not directly map to your Locations object (ie the number 1 cannot be directly converted to a Locations object with an ID of 1).
Your best bet is to have a list of location ID's in your View Model of type string or int to accept the posted values, then in your post method create the Location objects and fill them with the correct ID's.
FYI, the reason your code works is because you are bypassing the model binding and going directly to the Request.Form collection. You will notice that the bound Article object will not have any Location objects.
EDIT:
I don't even see how your code would work even without this problem. Your ArticleFormViewModel does not have a parameterless constructor, so that will fail in model binding (unless you have a custom model binder).
In any event, what you want to do is this (note, you will have to populate SelectedLocationIDs if you want them to be selected when the view is rendered):
public class ArticleFormViewModel
{
...
List<int> SelectedLocationIDs { get; set; }
...
}
Then, in your view you have:
#Html.ListBoxFor(m=>m.SelectedLocationIDs,
new MultiSelectList(Model.Locations,"LocationID","Name"))
In your Post method, instead of the code that calls Request.Form, you have something like this:
foreach(var locationID in article.SelectedLocationIDs) {
... // look up your locations and add them to the model
}
I am having trouble specifying the error message for the validation of a DateTime input value using data annotations in my model. I would really like to use the proper DateTime validator (as opposed to Regex, etc).
[DataType(DataType.DateTime, ErrorMessage = "A valid Date or Date and Time must be entered eg. January 1, 2014 12:00AM")]
public DateTime Date { get; set; }
I still get the default date validation message of "The field Date must be a date."
Am I missing something?
Add the following keys into Application_Start() at Global.asax
ClientDataTypeModelValidatorProvider.ResourceClassKey = "YourResourceName";
DefaultModelBinder.ResourceClassKey = "YourResourceName";
Create YourResourceName.resx inside App_GlobalResources folder and add the following keys
FieldMustBeDate The field {0} must be a date.
FieldMustBeNumeric The field {0} must be a number.
PropertyValueInvalid The value '{0}' is not valid for {1}.
PropertyValueRequired A value is required.
I found one simple workaround.
You can leave your model untouched.
[DataType(DataType.Date)]
public DateTime Date { get; set; }
Then override the 'data-val-date' attribute in a view.
#Html.TextBoxFor(model => model.Date, new
{
#class = "form-control",
data_val_date = "Custom error message."
})
Or if you want to parameterize your messages, you can just use static function String.Format:
#Html.TextBoxFor(model => model.Date, new
{
#class = "form-control",
data_val_date = String.Format("The field '{0}' must be a valid date.",
Html.DisplayNameFor(model => model.Date))
})
Similar with resources:
#Html.TextBoxFor(model => model.Date, new
{
#class = "form-control",
data_val_date = String.Format(Resources.ErrorMessages.Date,
Html.DisplayNameFor(model => model.Date))
})
I have one dirty solution.
Create custom model binder:
public class CustomModelBinder<T> : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if(value != null && !String.IsNullOrEmpty(value.AttemptedValue))
{
T temp = default(T);
try
{
temp = ( T )TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(value.AttemptedValue);
}
catch
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "A valid Date or Date and Time must be entered eg. January 1, 2014 12:00AM");
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
}
return temp;
}
return base.BindModel(controllerContext, bindingContext);
}
}
And then in Global.asax.cs:
protected void Application_Start()
{
//...
ModelBinders.Binders.Add(typeof(DateTime), new CustomModelBinder<DateTime>());
I got around this issue by modifying the error in the ModelState collection at the start of my action method. Something like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyAction(MyModel model)
{
ModelState myFieldState = ModelState["MyField"];
DateTime value;
if (!DateTime.TryParse(myFieldState.Value.AttemptedValue, out value))
{
myFieldState.Errors.Clear();
myFieldState.Errors.Add("My custom error message");
}
if (ModelState.IsValid)
{
// Do stuff
}
else
{
return View(model);
}
}
Try with a regular expression annotation like
[Required]
[RegularExpression("\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}:\d{2})?")]
public string Date { get; set; }
or check this
I have the following model
public class FilterSetting
{
public FilterType Type { get; set; }
public ShowOption Option { get; set; }
public bool IsMultiple { get; set; }
public List<int> SelectedValues { get; set; }
public List<SelectListItem> Values { get; set; }
public int RowCount { get; set; }
}
public enum ShowOption
{
Common,
Also,
All
}
I want to create a radio button group for the ShowOption enum, but I want to display that group using an editor template because I have another model that creates a list of FilterSetting and it will display about 7 FilterSettings each of one must have a group of 3 radio buttons for the ShowOption property.
The problem is when razor is rendering the radio buttons from the editor template it uses the same name for every radio button (name="filter.Option") and I don't know how to set the value.
This is my editor template
#model Models.FilterSetting
#Html.RadioButtonFor(f => f.Option, "Common", new { id = ""+ Model.Type })
#Html.Label(""+Model.Type, "Show only common assets")
<br />
#Html.RadioButtonFor(f => f.Option, "Also", new { id = "" + Model.Type })
#Html.Label(""+Model.Type, "Also show assets matching the values selected below")
<br />
#Html.RadioButtonFor(f => f.Option, "All", new { id = "" + Model.Type })
#Html.Label(""+Model.Type, "Show all assets")
<br />
#if (Model.IsMultiple)
{
#Html.ListBoxFor(f => f.SelectedValues, Model.Values)
}
else
{
#Html.DropDownListFor(f => f.SelectedValues, Model.Values, new { size = "4" })
}
I tried passing the name on the htmlAttributes object but it doesn't work.
EDIT:
Apparently the issue is the way I am calling the editor.
I'm calling it in this way
#foreach (var filter in Model.FilterSettings)
{
....
#Html.EditorFor(f => filter)
}
And when I call it like this
#Html.EditorFor(f => f.filterSettings)
The radio buttons work properly. What's happening when I do the for each?
Thanks for all the help!
When you call the Editor/EditorFor for a collection Razor will keep track of the names and ids generated and so you will get the names generated as for ex. Addresses[0].Line1, Addresses[1].Line1...
But when you iterate the collection yourself and call the editor for the each model razor don't keeps the track for the names and ids (how should it be? is that won't be more complicated?) and it's thinks as a new control going to be rendered in the page.
Usually I use an #Html.HiddenFor(x=>x.id) and #Html.HiddenFor(x=>x.name), try this
I have following code, but validation for the custom type(UserDetails) are not firing. Is there any way to overcome this problem? I know that if i define all the properties of UserDetails inside UserModel, it will work fine. but i need to reuse the UserDetails
Model,
public class UserModel
{
public string Something { get; set; }
public UserDetails User { get; set; }
}
Custom object,
public class UserDetails
{
[Required]
public string FirtstName { get; set; }
[Required]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "{0} can not be greater than {1} characters")]
public string Address { get; set; }
}
View,
#Html.ValidationSummary(true)
#Html.TextAreaFor(model => model.UserDetails.Address , new { rows = "5", cols = "20"})
#Html.ValidationMessageFor(model => model.UserDetails.Address )
....
This is just because of the ID and name of the element. for example in this case name of the FirtstName control is UserModel.FirtstName and ID is UserModel_FirtstName so client side validation will not fire in this case. if you want to add validation you have to add client validation manually. but you can validate it in the server side by using ModelState.IsValid
if (!ModelState.IsValid)
{
if( ModelState.IsValidField("UserDetails.FirstName"))
{
ModelState.AddModelError("UserDetails.FirstName", "Error in save");
}
......
}
client side validation
$("form").validate({
rules: {
"UserDetails.FirstName": { required: true }
}
});
why not create your own validation rules. you can use Ivalidatable Object. check for this link, it has nice explanationation
Asp.net Ivalidatable object implementation
Validation in asp.net mvc3
If you are talking about client side validation - make sure that your view code is placed within
#using(Html.BeginForm(...))
{
...
}
block and you have client side validation enabled with something like #{Html.EnableClientValidation(); }
I.e.
#using (Html.BeginForm())
{
#{ Html.EnableClientValidation(); }
#Html.ValidationSummary(true, "Password change was unsuccessful")
<fieldset>
<legend>Change Password Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.OldPassword)
#Html.PasswordFor(m => m.OldPassword)
#Html.ValidationMessageFor(m => m.OldPassword)
..............
As for triggering server-side validation - you should call Model.IsValid in your action
EDIT:
Just remembered something else:
Try putting [Required] attibute on the User property in UserModel
I'm using the client side validation for my views, and have just created a ViewModel which contains an organisation object and an address object.
I used to have a ViewModel that just mapped to the domain entity. On my domain entity I had the following:
[NotMapped]
[Remote("ValidOrganisation", "Manage", "Organisations", ErrorMessage = "That organisation doesn't exist")]
public string Organisation { get; set; }
However, I have now created a ViewModel for the view that contains the following:
public class PersonModel
{
public Person Person { get; set; }
public AddressModel Address { get; set; }
}
The person object contains the Organisation property.
In my view, I have the following:
<div>
<label for="Organisation">Organisation</label>
<div class="input large">
#Html.TextBoxFor(m => m.Person.Organisation)
<span class="help-block">Type the first letter of the organisation to search</span>
#Html.ValidationMessageFor(m => m.Person.Organisation)
#Html.Hidden("OrganisationID")
</div>
</div>
The only thing that changed was:
#Html.TextBoxFor(m => m.Organisation)
to:
#Html.TextBoxFor(m => m.Person.Organisation)
My remote validation code is:
public JsonResult ValidOrganisation(string organisation)
{
var exists = orgs.SelectAll().Where(o => o.Name.ToLower() == organisation.ToLower()).Count() > 0;
return Json(exists, JsonRequestBehavior.AllowGet);
}
The problem is that NULL is now always being passed in, which is always returning false.
Is this something to do with the Organisation property now changing to be Person.Organisation?
1] Open your view Page Source and see what is Id renderd for the
#Html.TextBoxFor(m => m.Person.Organisation)
2] and rename organisation in method to accept same ID I am expecting it to be Person_Organisation
public JsonResult ValidOrganisation(string organisation)
use below
public JsonResult ValidOrganisation(string Person_Organisation)