On a page in a Razor MVC app a bunch of lines are written from a model, like this:
#foreach (var item in Model)
{
// write lots of data
#Html.CheckBoxFor(item.Id) // incorrect syntax, I know
}
<input type="button" value="Update" onclick="Update();" />
The javascript that is run from the button is supposed to get the item.Id (which is a unique integer) from each checkbox that has been checked, put the ids in an array and pass the array to another function. This is what I'm working with so far:
function Update() {
var ids = document.getElementsByName("myCheckbox");
var idList = new Array(ids.length);
for (var i = 0; i < ids.length; i++) {
if (ids[i].checked {
idList.push(ids[i]);
}
}
$.ajax({
type: "POST",
url: "#Url.Action("Update", "Home")",
data: idList,
success: function (data) {
},
error: function (data) {
console.log(data);
}
});
}
How do I add the same name to all the checkboxes and have their ID be that of item.Id?
Edit
I tried C Murphy's suggestion by making a class for the checkboxes:
public class ToBeUpdated
{
public int ID { get; set; }
public bool IsSelected { get; set; }
}
And then adding a List to the class that's in the model:
public List<ToBeUpdated> ToBeUpdatedList { get; set; }
However, I can't figure out how to write the CheckBoxFor. His/her suggestion was:
#Html.CheckboxFor(modelItem => modelItem.SomeNameList[i].IsSelected)
But I'm not doing a for loop but a foreach:
foreach (var item in Model)
So I had to do this instead:
#Html.CheckBoxFor(modelItem => item.ToBeUpdatedList.Find(m => m.ID == item.Id).IsSelected)
Then I have my button that sends me to the function in my controller:
<input type="button" value="Update" onclick="location.href='#Url.Action("UpdateStatuses", "Home")'" />
public IActionResult UpdateStatuses(IEnumerable<FileData> model)
{
// iterate through the items in the model
}
The problem is that when I get to UpdateStatuses the model is empty even though I had a ton of checkboxes that I checked before clicking the button. The model in the View is
#model IEnumerable<MyData>
And MyData is a simple class:
public class MyData
{
public int Id { get; set; }
public List<ToBeUpdated> ToBeUpdatedList { get; set; }
}
Edit 2: Finally found a solution
I made my checkboxes like this:
<input type="checkbox" name="chkUpdateThis" id="#item.Id" />
And then I could, with the help of T.J's code, edit my javascript to do this:
function UpdateStatuses() {
const ids = [...document.querySelectorAll("[name=chkUpdateThis]")].filter(({checked}) => checked).map(({id}) => id)
var idList = new Array();
for (var i = 0; i < ids.length; i++) {
idList.push(parseInt(ids[i]));
}
$.ajax({
type: "POST",
url: "#Url.Action("UpdateStatuses", "Home")",
data: ({ ids: idList }),
success: function (data) {
},
error: function (data) {
console.log(data);
}
});
}
Then I can call the UpdateStatuses function in my Home controller:
public IActionResult UpdateStatuses(List<int> ids)
Since T.J didn't post a suggestion that I can give a checkbox to, I'll give it to C Murphy who was also helpful. I didn't use his suggestion, but I don't want to give myself a checkbox even though the solution I went for is right here.
#Html.CheckBoxFor(model => model.Id, new { Name = "myCheckbox" })
On a similar note to what Heretic Monkey commented, you should probably set up a class. Without knowing what your Update ActionResult is doing in your controller, I can't give you a complete answer, but you could follow this general structure:
Model:
public class SomeNameViewModel
{
public List<SomeName> SomeNameList { get; set; }
}
public class SomeName
{
public int Id { get; set; }
public bool IsSelected { get; set; }
}
View:
#model ProjectName.Models.SomeNameViewModel
#for (var i = 0; i < Model.Count(); i++)
{
<table>
<tr>
<td>#Html.HiddenFor(modelItem => modelItem.SomeNameList[i].Id)</td>
<td>#Html.CheckboxFor(modelItem => modelItem.SomeNameList[i].IsSelected)</td>
</tr>
</table>
}
Edit (due to information provided via comment):
(note I also adjusted the previous section to use a ViewModel instead of a list of the new class)
You would then adjust your ActionResult to instead accept the ViewModel class as an argument and perform the database update as follows:
Controller:
public ActionResult Update(SomeNameViewModel viewModel)
{
List<int> selectedIds = new List<int>();
foreach(var row in viewModel.SomeNameList)
{
if(row.IsSelected == true)
{
selectedIds.Add(row.Id);
}
else
{
// wasn't selected, so do nothing
}
}
// Now you have a list of Ids (selectedIds) that were selected
// and can then be passed to your other function
}
Second Edit:
I think there is still some confusion surrounding the business requirement around what you are trying to accomplish.
The reason for the error was I assumed you would be passing those IDs (in the form of the ToBeUpdated view model class) to the ActionResult, instead of passing in the IEnumerable<FileData>. That is why it is empty when it reaches your controller. The structure of MVC (Model View Controller) dictates that you pass information in the form of a model (class) to the controller from the view (and vice versa).
Please provide your full model list along with a more detailed explanation of what you are trying to achieve and why.
The reason why I utilized a for loop instead of a foreach loop is to ensure that the list is serialized with unique indexes for each item in the list. The only reason you need to use checkboxes is to identify which IDs are to be updated. The logic to determine which are going to be updated should be used on the controller side. Think of a checkbox as an input field for a boolean (true/false, 0/1) property. The goal of a unique ID is just to identify your unique data records. The boolean (checkbox field on the view side) is just to have your end user designate a specific record(s) as needing to be updated.Whatever your model may be, you should add a boolean property (like I gave as an example) to have the user determine which record(s) to update, and call the SQL update command from your controller ONLY on the records from your list/IEnumerable that have a value of true for your IsSelected property.
Related
My question revolves around appending items to a dropdownlist based upon choices made on a previous dropdownlist. Here's my code in JS:
function propogateProgramListOptions(data) {
$.post('PostProgramData',
{ clientId: data },
function(result) {
var programs = result.data;
for (var i = 0; i < programs.length; i++) {
alert(programs[i]);
}
});
}
Here's my code in C#:
public JsonResult PostProgramData(string[] clientId) {
string domainUserName = GetDomainUserName();
ProgramRef[] programsArray = _manager.GetCPRPrograms(domainUserName, clientId);
List<ProgramRef> programsList = programsArray.ToList();
return Json(new {data = programsList}, JsonRequestBehavior.AllowGet);
}
Now each ProgramRef has a property of ProgramCode and ProgramName. Usually in C#, you can just iterate through the array and do programsArray[i].ProgramCode etc. But in this case i'm returning it as a Jsonresult. How would i access the ProgramCode and ProgramName properties of the returned item in JS and do result[i].programName?
Got the answer.
programs[i].ProgramCode
programs[i].ProgramName
I didn't think this would work because my IDE was complaining about it being a global variable.
i am creating a form which can be accessed based on condition in MVC. I have first view with dropdownlist and submit button, i want when the submit button is clicked, the value in dropdownlist is passed and compared with condition set for that value, and if the condition is not ok, it shows alert rather than processing to the form.
Here is my code:
public ActionResult ChooseType()
{
var x = DataAccess.GetEmployee(#User.Identity.Name);
var lists = new SelectList(RuleAccess.GetAllRule(), "ID", "TypeDetail");
ViewBag.CategoryId = lists;
/*rule*/
ViewBag.comp1 = Logic.AnnualToogle(#User.Identity.Name);
if (x.EmpSex == "F" && x.EmpMaritalSt == "NIKAH")
{ ViewBag.comp2 = 1; }
else ViewBag.comp2 = 0;
return View();
}
[HttpGet]
public ActionResult Create(int lv_type)
{
var type = RuleAccess.GetTypeByID(lv_type);
ViewBag.type = type;
var model = new LeaveApplicationViewModels();
model.X = DataAccess.GetEmployee(#User.Identity.Name);
model.C = DataAccess.GetLeaveApp(#User.Identity.Name);
/*disable*/
ViewBag.dis = DataAccess.GetDisabledDate(#User.Identity.Name);
/*max*/
var max= RuleAccess.GetMaxByID(lv_type);
ViewBag.c = max;
if (lv_type == 1)
{
var used = RuleAccess.CountUsedAnnual(#User.Identity.Name);
var rem = max - used;
ViewBag.a = used;
ViewBag.b = rem;
}
else
{
ViewBag.b = max;
}
return View(model);
}
I used the Viewbag.comp 1 & 2 in my view:
<script type="text/javascript">
var x = #ViewBag.comp1;
var y = #ViewBag.comp2;
function validatecreate()
{
var value= document.getElementById("lv_type").value;
if (value==1)
{
if(x==1)
document.getElementById('validatecreate').submit();
else { alert('Action cant be done. You either have another annual leave application in pending status or you have reach the limit of annual leave'); }
}
else if(value==2)
{
if(y==1)
document.getElementById('validatecreate').submit();
else { alert('Action cant be done. You either are Male or Not Married Yet'); }
}
else if(value==3)
{
document.getElementById('validatecreate').submit();
}
else {
document.getElementById('validatecreate').submit();
//alert('Http Not Found');
}
}
#Html.DropDownList(
"lv_type", (SelectList) ViewBag.CategoryId,
"--Select One--",
new{ //anonymous type
#class = "form-control input-sm"
}
)
I feel like im doing it wrong especially because if someone manually put the url with ?lv_type=2, they not validate and can go to the form directly. But i need the value of lv_type bcs i use that in my view. Please Helpp :(
Validation must always be done on the server, and client side validation should only be considered a nice bonus that minimizes the need for to a call to the server. And presenting options in a dropdownlist to a user and then telling them thay can't select that option is an awful user experience. Instead, you should be presenting only those options which are applicable to the user (and delete all the scripts you have shown).
Create an additional method in your RuleAccess class, say GetEmployeeRules(Employee employee) which returns only the rules that are applicable to that employee, for example
public static List<Rule> GetEmployeeRules(Employee employee)
{
// Get the list of all rules
if (employee.EmpSex == "F" && employee.EmpMaritalSt == "NIKAH")
{
// Remove the appropriate Rule from the list
}
.....
// Return the filtered list
}
In addition, you should be using a view model in the view
public class LeaveTypeVM
{
[Required(ErrorMessage = "Please select a leave type")]
public int SelectedLeaveType { get; set; }
public IEnumerable<SelectListItem> LeaveTypeList { get; set; }
}
Then in the ChooseType() method
public ActionResult ChooseType()
{
var employee = DataAccess.GetEmployee(#User.Identity.Name);
var rules = RuleAccess.GetEmployeeRules(employee);
var model = new LeaveTypeVM()
{
LeaveTypeList = new SelectList(rules, "ID", "TypeDetail")
};
return View(model);
}
and in the view
#model LeaveTypeVM
#using (Html.BeginForm())
{
#Html.DropDownListFor(m => m.SelectedLeaveType, Model.LeaveTypeList, "--Select One--", new { #class = "form-control input-sm" }
#Html.ValidationMessageFor(m => m.SelectedLeaveType)
<input type="submit" value="Submit" />
}
and submit to a POST method which allows you to easily return the view if its invalid, or to redirect to the Create method.
[HttpPost]
public ActionResult ChooseType(LeaveTypeVM model)
{
if (!ModelState.IsValid)
{
model.LeaveTypeList = .... // as per GET method
}
return RedirectToAction("Create", new { leaveType = model.SelectedLeaveType });
and in the Create() method
public ActionResult Create(int leaveType)
{
var employee = DataAccess.GetEmployee(#User.Identity.Name);
var rule = RuleAccess.GetEmployeeRules(employee).Where(x => x.ID == leaveType).FirstOrDefault();
if (rule == null)
{
// Throw exception or redirect to an error page
}
var model = new LeaveApplicationViewModels();
....
return View(model);
}
Note your LeaveApplicationViewModels should contain additional properties so that you can avoid all those ViewBag properties and generate a strongly typed view.
i have two Models, first:
class Tutorial extends Eloquent {
protected $table = 'tutorials';
public function rating()
{
return $this->hasMany('Rating');
}
}
and:
class Rating extends Eloquent {
protected $table = 'ratings';
public $timestamps = false;
public function tutorial()
{
return $this->belongsTo('Tutorial');
}
}
now in my controller i have this:
public function get_index() {
$tutorials = tutorial::orderBy('created_at', 'desc')
->with('rating')
->paginate(25);
return View::make('site/home/index')->with('tutorials', $tutorials);
}
So how do i get all ratings from one tutorial in my View?!
EDIT:
Now i have this:
public function ratings()
{
return $this->hasMany('Rating');
}
public function getRating()
{
// Grab the ratings from this tutorial
$ratings = $this->ratings;
$summedRatings = 0;
// Loop through them all and add them together
foreach($ratings as $rating)
{
console.log($rating->value);
$summedRatings += $rating->value;
}
// Return the calculated average
return $summedRatings / count($ratings);
}
public function get_index() {
$tutorials = Tutorial::with('ratings')
->with('user')
->orderBy('created_at', 'desc')
->paginate(25);
return View::make('site/home/index')->with('tutorials', $tutorials);
}
and in my View:
#foreach($tutorials as $tutorial)
<span>{{$tutorial->rating}}</span>
#endforeach
But all my < span >´s are empty!
UPDATE: if i do this:
#foreach($tutorials as $tutorial)
#foreach($tutorial->ratings as $rate)
<span>{{$rate->value}}</span>
#endforeach
everything is good....So what´s wrong?
Depending on the platform you're site is on you should always use the correct case.
$tutorials = tutorial::orderBy(...) // Wrong
$tutorials = Tutorial::orderBy(...) // Correct
To eager load the ratings you should always declare your 'with' method before anything else.
$tutorials = Tutorial::with('rating')
->orderBy('created_at', 'DESC')
->paginate(25);
This has, for some reason, been left out of the L4 docs.
In your view you can now access the rating with this
foreach($tutorials as $tutorial)
{
echo $tutorial->rating->{rating table column name};
}
First, as far as naming conventions go, to make things easier to understand: The rating() method within your tutorial method should be called ratings(), so when you grab your ratings, it will look better ($tutorial->ratings)
After renaming this, in your view, while looping through the array of $tutorials, you could access the ratings of each one like this:
foreach($tutorials as $tutorial)
{
$ratings = $tutorial->ratings;
}
Which would retrieve the ratings object of each.
What you should know is that you can create properties for your model if you need to return the calculation of the ratings, instead of the ORM objects
For example, if each rating is a number from 1-5 in the ratings table stored in an amount column, you can do this to set the average of each rating as a property:
class Tutorial extends Eloquent {
protected $table = 'tutorials';
public function ratings()
{
return $this->hasMany('Rating');
}
public function getRating()
{
// Grab the ratings from this tutorial
$ratings = $this->ratings;
$summedRatings = 0;
// Loop through them all and add them together
foreach($ratings as $rating)
{
$summedRatings += $rating->amount;
}
// Return the calculated average
return $summedRatings / count($ratings);
}
}
Then in your view, you can echo out the property as if it were part of the database
foreach($tutorials as $tutorial)
{
echo $tutorial->rating;
}
My forms have inputs with default helper text that guides the user on what to enter (rather than using labels). This makes validation tricky because the input value is never null.
How can I extend unobtrusive validation to handle this? The form shouldn't be valid if the Name input is equal to "Please enter your name..."
I started reading Brad Wilson's blog post on validation adapters, but I'm not sure if this is the right way to go? I need to be able to validate against different default values depending on the field.
Thanks
Here's a sample illustrating how you could proceed to implement a custom validation attribute:
public class NotEqualAttribute : ValidationAttribute, IClientValidatable
{
public string OtherProperty { get; private set; }
public NotEqualAttribute(string otherProperty)
{
OtherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(OtherProperty);
if (property == null)
{
return new ValidationResult(
string.Format(
CultureInfo.CurrentCulture,
"{0} is unknown property",
OtherProperty
)
);
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (object.Equals(value, otherValue))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "notequalto",
};
rule.ValidationParameters["other"] = OtherProperty;
yield return rule;
}
}
and then on the model:
public class MyViewModel
{
public string Prop1 { get; set; }
[NotEqual("Prop1", ErrorMessage = "should be different than Prop1")]
public string Prop2 { get; set; }
}
controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
Prop1 = "foo",
Prop2 = "foo"
});
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and view:
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
jQuery.validator.unobtrusive.adapters.add(
'notequalto', ['other'], function (options) {
options.rules['notEqualTo'] = '#' + options.params.other;
if (options.message) {
options.messages['notEqualTo'] = options.message;
}
});
jQuery.validator.addMethod('notEqualTo', function(value, element, param) {
return this.optional(element) || value != $(param).val();
}, '');
</script>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Prop1)
#Html.EditorFor(x => x.Prop1)
#Html.ValidationMessageFor(x => x.Prop1)
</div>
<div>
#Html.LabelFor(x => x.Prop2)
#Html.EditorFor(x => x.Prop2)
#Html.ValidationMessageFor(x => x.Prop2)
</div>
<input type="submit" value="OK" />
}
Yes thats the right way to go. You should implement your own atribute and implement IClientValidatable.
You could also have a required boolean value set initially to false as a hidden form field. When the user changes the textbox, set it to true.
You could make your ViewModel implement IValidatableObject and when implementing the Validate method (from IValidatableObject) add some logic to check the values of the properties e.g.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
var results = new List<ValidationResult>();
if (Name == "Please enter your name...")
results.Add(new ValidationResult("You must enter a name");
...
Enter other validation here
...
return results;
}
Now, when Model.IsValid is called in your controller, this bit of logic will be ran and will return validation errors as normal.
It took a little while since your question was asked, but if you still like data annotations, this problem can be easily solved using this library:
[Required]
[AssertThat("FieldA != 'some text'")]
public string FieldA { get; set; }
Above, the field value is compared with some pre-defined text. Alternatively, you can compare fields values with each other:
[AssertThat("FieldA != FieldB")]
...and when the case of the strings being compared does not matter:
[AssertThat("CompareOrdinalIgnoreCase(FieldA, FieldB) != 0")]
To improve a little bit of #Darin Dimitrov answer, if you want to add messages from the resources using ErrorMessageResourceName and ErrorMessageResourceType, just add this to the to the Error message ErrorMessage = ErrorMessage ?? ErrorMessageString
The ErrorMessageString will look for the localized version of error message that you set in the model using those parameters (ErrorMessageResourceName and ErrorMessageResourceType)
The ideal solutions is a custom Attribute where you specify minimum and maximum lengths as well as MustNotContain="Please enter your name...".
I have created a variable length list according to the many great posts by Steve Sanderson on how to do this in MVC 2. His blog has a lot of great tutorials.
I then created a custom "requiredif" conditional validator following this overview http://blogs.msdn.com/b/simonince/archive/2010/06/11/adding-client-side-script-to-an-mvc-conditional-validator.aspx
I used the JQuery validation handler from the MSDN blog entry which adds the following to a conditional-validators.js I include on my page's scripts:
(function ($) {
$.validator.addMethod('requiredif', function (value, element, parameters) {
var id = '#' + parameters['dependentProperty'];
// Get the target value (as a string, as that's what actual value will be)
var targetvalue = parameters['targetValue'];
targetvalue = (targetvalue == null ? '' : targetvalue).toString().toLowerCase();
// Get the actual value of the target control
var actualvalue = ($(id).val() == null ? '' : $(id).val()).toLowerCase();
// If the condition is true, reuse the existing required field validator functionality
if (targetvalue === actualvalue)
return $.validator.methods.required.call(this, value, element, parameters);
return true;
});
})(jQuery);
Alas, this does not cause a client-side validation to fire ... only the server-side validation fires. The inherent "required" validators DO fire client-side, meaning I have my script includes set-up correctly for basic validation. Has anyone accomplished custom validators in a variable length list in MVC 2 using JQuery as the client-side validation method?
NOTE that this same custom validator works client-side using the exact same set-up on a non-variable length list.
Turns out that it was a field ID naming issue with the way that collection IDs render in a variable length list. The validator was attempting to name the element ID of the dependent property with the expected statement of:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty);
I analyzed the HTML viewsource (posted in my comment, above), and actually, [ and ] characters are not output in the HTML of the collection-index elements... they're replaced with _... so, when I changed my CustomValidator.cs to have the dependent property set to:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
... then the client-side validator works since the name matches. I'll have to dig deeper to see WHY the ID is getting renamed in Sanderson's collection index method, below...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Sendz.WebUI.Helpers
{
public static class HtmlPrefixScopeExtensions
{
private const string IdsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
var itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(
string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />",
collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
var key = IdsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (var previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
#region Nested type: HtmlFieldPrefixScope
private class HtmlFieldPrefixScope : IDisposable
{
private readonly string _previousHtmlFieldPrefix;
private readonly TemplateInfo _templateInfo;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
_templateInfo = templateInfo;
_previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
#region IDisposable Members
public void Dispose()
{
_templateInfo.HtmlFieldPrefix = _previousHtmlFieldPrefix;
}
#endregion
}
#endregion
}
}
A complete validator / attribute reference...
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute) { }
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// Get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// Get the value of the dependent property
var value = field.GetValue(container, null);
// Compare the value against the target value
if ((value == null && Attribute.TargetValue == null) ||
(value != null && value.ToString().ToLowerInvariant().Equals(Attribute.TargetValue.ToString().ToLowerInvariant())))
{
// A match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// Validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
ValidationType = "requiredif"
};
var viewContext = (ControllerContext as ViewContext);
var depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
rule.ValidationParameters.Add("dependentProperty", depProp);
rule.ValidationParameters.Add("targetValue", Attribute.TargetValue.ToString());
yield return rule;
}