Having the following classes:
public class MyObjB {
[Required(ErrorMessage = "I need it")]
public string Name { get; set; }
}
public class MyObjA {
[Required(ErrorMessage = "I need it")]
public string Name { get; set; }
public MyObjB MyObjB { get; set; }
}
The View: using the model as MyObjA
#{ Html.EnableClientValidation(); }
#{Html.BeginForm();}
#Html.ValidationSummary("Some errors") #Html.AntiForgeryToken()
// MyObjA part
#Html.LabelFor(model => model.Name):
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessage("Name", "*")
// MyObjB part
#Html.LabelFor(model => model.MyObjB.Name):
#Html.TextBoxFor(model => model.MyObjB.Name)
#Html.ValidationMessage("MyObjB.Name", "*")
#{ Html.EndForm(); }
The EnableClientValidation will not work....
I think this is related with the "." (dot) used and problems with the javacript, but I can be wrong and the cause be different.
If I do the same form but only for the MyObjB, it will work fine and the ClientValidation is done correctly.
How do you people use EnableClientValidation with subproperties?
Thank you.
EDIT 1 - For byte request
View:
#Html.TextBoxFor(model => model.MyObjB.Name)
HTML Result:
<input id="MyObjB_Name" name="MyObjB.Name" type="text" value="" />
I think you must use following in you web.config:
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
It will work as long as you have the reference and initialization of objB in objA, because your model to the view is ObjA
what you need to do is create a viewmodel for your view
which will have properties of Class A and Class B
public class ViewModelAB
{
[validation error message]
A PropertyA { get; set; }
[validation error message]
B PropertyB { get; set;}
}
and use this viewmodel model strogly types to your view
Related
Until some days ago it was quite easy to manage model binding in my application. I had a view model, called PersonOfferDTO, containing a collection of PersonProductOfferDTO. (yes, I'm using the DTO as a view model because a view model in this case would be equal to the DTO). Here below a simplified version of PersonOfferDTO
public class PersonOfferDTO
{
[DataMember]
public Guid PersonOfferId { get; private set; }
[DataMember]
public ICollection<PersonProductOfferDTO> Offers { get; set; }
}
And here below a simplified version of PersonProductOfferDTO
public class PersonProductOfferDTO
{
[DataMember]
public Guid PersonProductOfferId { get; private set; }
[DataMember]
public Guid PersonOfferId { get; set; }
[DataMember]
public int Quantity { get; set; }
[DataMember]
public decimal UnitPrice { get; set; }
}
I was able to populate the ICollection thanks to the method shown below (HTML code).
<form method="POST" action="/Offers/AddNewPersonOffer">
<input name="PersonProductOffers.Index" value="myKey1" hidden>
<input name="PersonProductOffers[myKey1].Quantity">
<input name="PersonProductOffers[myKey1].UnitPrice">
<input name="PersonProductOffers.Index" value="myKey2" hidden>
<input name="PersonProductOffers[myKey2].Quantity">
<input name="PersonProductOffers[myKey2].UnitPrice">
</form>
But during the last days I have increased the depth of my objects tree, so now I have the following code.
public class PersonOfferDTO
{
[DataMember]
public Guid PersonOfferId { get; private set; }
[DataMember]
public ICollection<PersonOfferParagraphDTO> Paragraphs { get; set; }
}
[DataContract]
public class PersonOfferParagraphDTO
{
[DataMember]
public Guid PersonOfferParagraphId { get; set; }
[DataMember]
public ICollection<PersonProductOfferDTO> PersonProductOffers { get; set; }
}
As you can see there is now one further level between PersonOfferDTO and PersonProductOfferDTO, and I can't figure out how to perform a "multilevel binding": create a PersonOfferDTO with more PersonOfferParagraphDTO each one containing more PersonProductOfferDTO.
NOTE: I don't want to use an incremental index ([0] , [1], ....)... but a string (["myKey"])
EDIT
By request, I add the controller here below
public ActionResult AddNewPersonOffer(PersonOfferDTO offer)
{
if (!UserHasPermissions())
{
return PartialView("_forbidden");
}
var errors = OffersCRUD.AddNewPersonOffer(offer);
if(errors.Count() == 0)
{
return RedirectToAction("Index");
}
return PartialView("_errors", new ErrorsViewModel(errors));
}
If you want to populate them with your own keys, you can define your collections within your view model as a Dictionary<string, YOURCLASS>it accepts a non-integer index value.
Example view model with Dictionary:
public class ViewModelTest
{
public Dictionary<string, Class1> Values { get; set; }
}
Example class to be used in the dictionary collection:
public class Class1
{
public int MyProperty { get; set; }
public Dictionary <string, Class2> MoreValues { get; set; }
}
public class Class2
{
public int AnotherProperty { get; set; }
}
Here's a form that populates the values:
#using (Html.BeginForm())
{
<input type="text" name="Values[yourkey1].MyProperty" />
<input type="text" name="Values[yourkey1].MoreValues[anotherKey1].AnotherProperty" />
<input type="text" name="Values[yourkey2].MyProperty" />
<input type="text" name="Values[yourkey2].MoreValues[anotherKey2].AnotherProperty" />
<input type="submit" />
}
Instead of writing your input tags yourself, you can use the helper methods and enjoy intellisense, assuming that you have your view model defined within the view with the same structure defined in your action method:
#model ViewModelTest
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.Values[yourkey1].MyProperty)
#Html.TextBoxFor(x => x.Values[yourkey1].MoreValues[anotherKey1].AnotherProperty)
#Html.TextBoxFor(x => x.Values[yourkey2].MyProperty)
#Html.TextBoxFor(x => x.Values[yourkey2].MoreValues[anotherKey2].AnotherProperty)
<input type="submit" />
}
You'll have to introduce a view model for this of course and not just get away with using your DTO ;).
PS: A DTO shouldn't be used as a domain model either, it's for transporting information around your layers.
I have a view with the name "Create". This view gets the "SchoolViewModel" which contains two classes:
public class SchoolViewModel
{
public List<Teacher> ListTeacher { get; set; }
public List<SchoolClass> ListSchoolClass { get; set; }
public ClassComplete ClassComplete { get; set; }
}
Each list in "SchoolViewModel" provides data from a database.
At the "Create" page you should be able now to select a teacher and class (DropDownList). The "ClassComplete" object contains the two classes (Teacher and SchoolClass) and the roomname
public class ClassComplete
{
public string RoomName { get; set; }
public SchoolClass SchoolClass { get; set; }
public Teacher Teacher { get; set; }
}
I want only to post the "ClassComplete" object.
My ActionResult
[HttpPost]
public ActionResult Create(ClassComplete cp)
{
// Do something
return View();
}
Edit:
Razor View
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.ListTeacher[0].TeacherName)
#Html.EditorFor(m => m.ListSchoolClass[0].ClassName)
#Html.TextBoxFor(m => m.cl.RoomName)<br />
<input type="submit" value="Click" />
}
Is this the right way ?
best regards
If you want to POST only ClassComplete model you will need to indicate the binding prefix:
[HttpPost]
public ActionResult Create([Bind(Prefix="ClassComplete")] ClassComplete cp)
{
// Do something
return View();
}
and in your view:
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.ClassComplete.RoomName)
<br />
<input type="submit" value="Click" />
}
The TextBoxFor will generate the following input field in the resulting markup:
<input type="text" name="ClassComplete.RoomName" />
Notice the name of the input field. That's the reason why you need to indicate this prefix in your controller action.
This will also work for the other properties if you want to send them you just need to include the corresponding input fields:
#Html.TextBoxFor(m => m.ClassComplete.SchoolClass.SomeProperty)
#Html.TextBoxFor(m => m.ClassComplete.Teacher.SomeOtherProperty)
...
Driving me crazy.... validation not firing.
When I leave the field blank and hit submit it behaves as if the model were valid.
I have used dbFirst entity framework to create the model and added the required data annotation after the fact.
My Model (Entity)
public partial class Certificate
{
public Certificate()
{
this.Travelers = new HashSet<Traveler>();
}
public int ID { get; set; }
public System.DateTime InsertDate { get; set; }
[Required(ErrorMessage = "Please enter your card number.")]
public string CertificateNumber { get; set; }
public Nullable<int> BuyerID { get; set; }
public string Used { get; set; }
public string Active { get; set; }
public virtual ICollection<Traveler> Travelers { get; set; }
}
My Controller:
[HttpPost]
public ActionResult Index(Certificate CertificateNumber)
{
if (ModelState.IsValid)
{
return RedirectToAction("Create", "Travelers");
}
return View();
}
My View:
#model GrandCelebration.Models.Certificate
#{
ViewBag.Title = "Validation";
}
<div>
<h2>Please enter your card number below to validate</h2>
<div id="searchbar">
#using (Html.BeginForm())
{#Html.ValidationSummary(true)
<fieldset>
#Html.TextBoxFor(model => model.CertificateNumber)
<input type="submit" class="btn" alt="Validate" style="display:inline" value="Validate" />
#Html.ValidationMessageFor(model => model.CertificateNumber)
</fieldset>
}
</div>
</div>
The problem was that the required js files weren't included.
Firstly make sure that unobtrusive validation is enabled in Web.config:
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
Then include 3 js files in the _Layout page:
<script src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.4.2.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
When cient-side validation is successfully enabled, if you look at the textbox HTML you will see a data-val property has been added.
I have a drop down list in MVC
Model:
[Required]
[Display(Name = "State of Residency:")]
public string StateCode { get; set; }
My View:
#Html.DropDownList("StateCode", "Select")
#Html.ValidationMessageFor(model => model.StateCode)
The drop down list works fine but how to make the validator validate the select. So it say please select a value in Select is there.
Example for ASP.NET MVC 5 web application:
In your case I would make like so:
1) Enable validation in web.config
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
2) add proper jQuery files
<script src="~/Scripts/jquery-1.11.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script> <!--v1.13.0-->
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> <!-- For ASP.NET MVC 5.0 -->
3) StateCode.cs
public class StateCode
{
public int Id { get; set; }
public int Code { get; set; }
}
4) MyModel.cs
public class MyModel
{
[Required(ErrorMessage = "Please select")]
public int IdSelected { get; set; }
[Display(Name = "State of Residency:")]
public IEnumerable<StateCode> StateCodes { get; set; }
}
5) Action
[HttpGet]
public virtual ActionResult Index()
{
var model = new MyModel
{
StateCodes = new List<StateCode>
{
new StateCode{Id=1, Code=22333},
new StateCode{Id=2, Code=44433},
new StateCode{Id=3, Code=55533},
}
};
return View(model);
}
6) View
#model MyModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(model => model.IdSelected, new SelectList(Model.StateCodes, "Id", "Code"), "Please Select")
#Html.ValidationMessageFor(model => model.IdSelected)
<input type="submit" value="OK" />
}
btw. here very good list of jQuery files for ASP.NET MVC unobtrusive validation.
Morning all.
I can see this has been discussed elsewhere but was wondering if anything had change or things made simpler in MVC 4 for simpletons like me?!
Scenario
I have the following,edited, model:
public class CorporateDetails
{
public Guid? Id { get; set; }
[Key]
public int CorporateDetailId { get; set; }
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual EmsType EmsType { get; set; }
}
public class EmsType
{
[Key]
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual ICollection<EmsType> EmsTypes { get; set; }
}
With the following standard create view:
<fieldset>
<legend>CorporateDetails</legend>
<div class="editor-label">
#Html.LabelFor(model => model.EmsId, "EmsType")
</div>
<div class="editor-field">
#Html.DropDownList("EmsId", String.Empty)
#Html.ValidationMessageFor(model => model.EmsId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.EmsName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmsName)
#Html.ValidationMessageFor(model => model.EmsName)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
This gives me, out of the box, a beautiful drop down list a la Scott Gu's blog
Now my real question is this - how can I effectively convert this drop down box to what will effectively be a multi select,checkbox list?
Again, apologies for going over trodden ground but I was just testing the water to see if any updates have occurred.
Please note, first MVC project so go gently, I'm feeling very thick again :'(
Right ok, I have got it sorted - hurrah! As you can see from the comments, there were a few issues that came up but please find below the complete solution which DOES work :D
Model
public class CorporateDetails
{
public Guid? Id { get; set; }
[Key]
public int CorporateDetailId { get; set; }
public int[] EmsId { get; set; }
}
public class EmsType
{
[Key]
public int EmsId { get; set; }
public string EmsName { get; set; }
public virtual ICollection<EmsType> EmsTypes { get; set; }
}
Controller
public ActionResult Create()
{
CorporateDetails corporatedetails = new CorporateDetails();
ViewBag.EmsId = new MultiSelectList(db.EmsTypes, "EmsId", "EmsName");
return View(corporatedetails);
}
Extension (placed in a folder in the root of the project)
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty[]>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
TProperty[] list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder divTag = new TagBuilder("div");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<div><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></div>",
propertyName,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}
Extension registered in web config of the Views
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="MyProject.Extensions" />
</namespaces>
</pages>
View
#model Valpak.Websites.HealthChecker.Models.CorporateDetails
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>CorporateDetails</legend>
<div class="editor-label">
#Html.CheckBoxListFor(model => model.EmsId, (MultiSelectList) ViewBag.EmsId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Which gives me a lovely list of check boxes. Hurrah!
Thanks Darin for your help, I've marked this as the answer but +50 for your time and effort.
Nice solution -
Just for others reference - I, like you, had run into a need for a checkboxlist - I have been using this here:
http://www.codeproject.com/Articles/292050/CheckBoxList-For-a-missing-MVC-extension
It works great... very well written - hope this can help someone.
Loren
No changes occurred in ASP.NET MVC 4 RC in this aspect and are unlikely to occur when it hits RTM.
But you could still implement a custom helper to achieve that. And you could even improve this helper so that it takes a lambda expression as first argument instead of a string in order to have strongly typed version.
And if you are not using enums here's another example.
If you pass selected value to MultiSelected (parameter #4)
ViewBag.VfonctionIds = new MultiSelectList(db.tbIntervenantFonctionTypes, "intervenantFonctionType_id", "Nom", fonctionSelected);
Change the Helper to
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty[]>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Create div
TagBuilder divTag = new TagBuilder("div");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<div><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></div>",
propertyName,
item.Value,
(item.Selected) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}