Populating list view using a JSON subarray - xamarin.forms

I have the following JSON url. I am trying to populate my list view using the subarrays hair[] and math[] after getting them from the JSON url.
Here is the url http://pastebin.com/raw.php?i=2fzqLYXj
Inside the array "Students" there is a subarray called "Properties" and another sub array inside "Properties" called "Grades". "hair" is inside "Properties" and "math" is inside "Grades".
Here is my WebService class
public class WebService
{
public WebService ()
{
}
public async Task<Rootobject> GetStudentInfoAsync (string apiurl) {
var client = new HttpClient ();
var response = await client.GetStringAsync(string.Format(apiurl));
return JsonConvert.DeserializeObject<Rootobject>(response.ToString());
}
}
And here is my View Model
public class Property
{
public int iq { get; set; }
public string hair { get; set; }
public string glasses { get; set; }
public Grade[] Grades { get; set; }
}
public class StudentInfo
{
public string name { get; set; }
public string lastname { get; set; }
public Property[] Properties { get; set; }
public string StudentName {
get{ return String.Format ("{0}", name); }
}
//I am accessing the JSON sub arrays with the following to get the "hair" and "math" properties.
public string[] HairColors
{
get
{
return (Properties ?? Enumerable.Empty<Property>()).Select(p => p.hair).ToArray();
}
}
public string[] MathGrades
{
get
{
return (Properties ?? Enumerable.Empty<Property>()).SelectMany(p => p.Grades ?? Enumerable.Empty<Grade>()).Select(g => g.Math).ToArray();
}
}
}
public class Rootobject
{
public StudentInfo[] students { get; set; }
}
}
And here is how I populate my list view with student names which works. Notice "StudentName" was not in a subarray.
var sv = new WebService();
var es = await sv.GetStudentInfoAsync(apiurl);
Xamarin.Forms.Device.BeginInvokeOnMainThread( () => {
listView.ItemsSource = es.students;
});
var cell = new DataTemplate (typeof(TextCell));
listView.ItemTemplate = cell;
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "StudentName");
But I need to populate my list view with HairColors and MathGrades as follows but the following code does not work.
var sv = new WebService();
var es = await sv.GetStudentInfoAsync(apiurl);
Xamarin.Forms.Device.BeginInvokeOnMainThread( () => {
listView.ItemsSource = es.students;
});
var cell = new DataTemplate (typeof(TextCell));
listView.ItemTemplate = cell;
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "HairColors");
listView.ItemTemplate.SetBinding (TextCell.DetailProperty, "MathGrades");
What doesn't this code work? How do I make it work?

You could make a custom converter for this:
public class HairColorsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var hairColors = (string[])value;
return string.Join(", ", hairColors);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then bind this like:
SetBinding(TextCell.TextProperty, "HairColors", BindingMode.Default, new HairColorsConverter());

Related

Ensure sensitive data is removed from response [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 11 months ago.
Improve this question
I have some dtos returned by my API that have sensitive data fields like Createdby, CreatedDate, LastModifiedBy and LastModifiedDate. These fields should only be in the response if the user is authenticated and/or have allowed roles in his claims. My current running solution is to use my dto mappers that have a dependency on IUserIdentity (custom interface).
UserDto record
public record UserDto {
public string? CreatedBy { get; init; }
public DateTime? CreatedDate { get; init; }
public string? Email { get; set; }
public string? ExternalProviderUserId { get; set; }
public string? GivenName { get; set; }
public Guid? Id { get; set; }
public string? LastModifiedBy { get; init; }
public DateTime? LastModifiedDate { get; init; }
public string? Name { get; set; }
public string? Surname { get; set; }
}
UserDtoMapper class
public class UserDtoMapper : IUserDtoMapper {
private readonly IUserIdentity _userIdentity;
public UserDtoMapper(IUserIdentity userIdentity) {
_userIdentity = userIdentity;
}
public UserDto ToDto(User user) => new() {
CreatedBy = _userIdentity.IsAuthenticated ? user.CreatedBy : null,
CreatedDate = _userIdentity.IsAuthenticated ? user.CreatedDate : null,
Email = user.Email,
ExternalProviderUserId = user.ExternalProviderUserId,
GivenName = user.GivenName,
Id = user.Id,
LastModifiedBy = _userIdentity.IsAuthenticated ? user.LastModifiedBy : null,
LastModifiedDate = _userIdentity.IsAuthenticated ? user.LastModifiedDate : null,
Name = user.Name,
Surname = user.Surname
};
public List<UserDto> ToDtos(IEnumerable<User> users) {
return users.Select(o => ToDto(o)).ToList();
}
}
It work as entended but I would like to have a global and easier way to set these fields as senstitive and let the api filter them if the user is not authenticed and/or doesn't have allowed roles in his claims. After searching online for many days and did try and error solutions, I finally comes with my own solution and hope to have feedbacks on potential issues I could have. It works great so far.
My solution is to use the Filters in ASP.NET Core and Reflection (C#).
I've created this custom attribute SensitiveDataAttribute
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class SensitiveDataAttribute : Attribute {
public SensitiveDataAttribute() {
AllowedRoles = Array.Empty<string>();
}
public SensitiveDataAttribute(params string[] allowedRoles)
: this((IEnumerable<string>)allowedRoles) { }
public SensitiveDataAttribute(IEnumerable<string> allowedRoles) {
if (allowedRoles == null) {
throw new ArgumentNullException(nameof(allowedRoles));
}
if (!allowedRoles.Any()) {
throw new InvalidOperationException("At least one role must be specified.");
}
AllowedRoles = allowedRoles;
}
/// <summary>
/// Gets the collection of allowed roles.
/// </summary>
public IEnumerable<string> AllowedRoles { get; }
public Task<bool> IsValidAsync(ClaimsPrincipal user) {
bool isUserAuthenticated = user.Identity?.IsAuthenticated ?? false;
if (!isUserAuthenticated) {
return Task.FromResult(false);
}
if (!AllowedRoles.Any()) {
return Task.FromResult(true);
}
bool found = AllowedRoles.Any(r => user.IsInRole(r));
return Task.FromResult(found);
}
public override string ToString() {
if (!AllowedRoles.Any()) {
return $"{nameof(SensitiveDataAttribute)}:User must be authenticated";
}
string roles = string.Join("|", AllowedRoles);
var stringValue = $"User must be authenticated and User.IsInRole must be true for one of the following roles:({roles})";
return $"{nameof(SensitiveDataAttribute)}: {stringValue}";
}
}
And created this action filter SensitiveDataActionFilter
public class SensitiveDataActionFilter : IAsyncActionFilter {
private readonly ILogger<SensitiveDataActionFilter> _logger;
public SensitiveDataActionFilter(ILogger<SensitiveDataActionFilter> logger) {
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext _, ActionExecutionDelegate next) {
ActionExecutedContext executedContext = await next();
if (executedContext.Exception != null) {
return;
}
if (executedContext.Result is not OkObjectResult result) {
return;
}
if (result.Value == null) {
return;
}
_logger.LogDebug("Filtering action result sensitive data of type {Type} started.", result.Value.GetType());
Stopwatch stopwatch = Stopwatch.StartNew();
if (result.Value is IEnumerable elements) {
int index = 0;
foreach (var element in elements) {
await FilterResultAsync(executedContext, element, $"[{index++}].");
}
_logger.LogDebug("Filtering action result sensitive data of type {Type} finished in {Elapsed} ms.", result.Value.GetType(), stopwatch.ElapsedMilliseconds);
return;
}
await FilterResultAsync(executedContext, result.Value, null);
_logger.LogDebug("Filtering action result sensitive data of type {Type} finished in {Elapsed} ms.", result.Value.GetType(), stopwatch.ElapsedMilliseconds);
}
private async Task FilterResultAsync(ActionExecutedContext context, object? source, string? propPath) {
if (source == null) {
return;
}
PropertyInfo[] properties = source.GetType()
.GetProperties(BindingFlags.Instance
| BindingFlags.Public)
.Where(p => p.GetMethod != null
&& p.GetMethod.IsPublic
&& p.GetMethod.IsStatic == false)
.ToArray();
foreach (var propertyInfo in properties) {
if (propertyInfo == null) {
continue;
}
object? propertyValue = propertyInfo.GetValue(source);
string propertyName = propertyInfo.Name;
string path = $"{propPath}{propertyName}";
if (propertyValue == null) {
_logger.LogDebug("Property {Path}: {Value}", path, propertyValue);
continue;
}
SensitiveDataAttribute? sensitiveDataAttribute = propertyInfo
.GetCustomAttribute<SensitiveDataAttribute>(true);
if (sensitiveDataAttribute != null) {
_logger.LogDebug("Property {Path} is sensitive: {Value}", path, propertyValue);
bool isValid = await sensitiveDataAttribute.IsValidAsync(context.HttpContext.User);
if (!isValid) {
_logger.LogDebug("Property {Path} to be cleared: {Reason}", path, sensitiveDataAttribute);
propertyInfo.SetValue(source, default);
continue;
}
}
if (propertyValue is DateTime or string) {
_logger.LogDebug("Property {Path}: {Value}", path, propertyValue);
continue;
}
if (propertyValue is IEnumerable elements) {
int index = 0;
foreach (var element in elements) {
await FilterResultAsync(context, element, $"{path}.[{index++}].");
}
continue;
}
_logger.LogDebug("Property {Path}: {Value}", path, propertyValue);
await FilterResultAsync(context, propertyValue, $"{path}.");
}
}
}
And register the filter SensitiveDataActionFilter like this:
services.AddScoped<SensitiveDataActionFilter>();
services.AddControllers(options => options.Filters.AddService<SensitiveDataActionFilter>())
And add the attribute SensitiveDataAttribute to sensitive field:
public record UserDto {
[SensitiveData]
public string? CreatedBy { get; init; }
[SensitiveData]
public DateTime? CreatedDate { get; init; }
public string? Email { get; set; }
[SensitiveData("SYS_ADMIN")]
public string? ExternalProviderUserId { get; set; }
public string? GivenName { get; set; }
public Guid? Id { get; set; }
[SensitiveData]
public string? LastModifiedBy { get; init; }
[SensitiveData]
public DateTime? LastModifiedDate { get; init; }
public string? Name { get; set; }
public string? Surname { get; set; }
}
Circular reference is one problem I have have with my code. I might need to have a max-depth setting somewhere. I had problem with DateTime and string. Datetime gave me a circular reference issue and I needed to check if propertyValue is DateTime then stopped to go deeper. I needed to do the same with string because string implements IEnumerable.
Any feedbacks is appreciated. :)
My main argument against this solution is that in case of a bug you're revealing too much information. From a security point of view it much better to structure code so that in case of a bug not enough information is returned.

ASP.NET Json Cannot deserialize the current JSON arrray

I can't list a json on the razor page, it gives me this problem:
JsonSerializationException: Cannot deserialize the current JSON array
(e.g. [1,2,3]) into type 'JsonDisplayASP.Models.ListRates' because the
type requires a JSON object (e.g. {"name":"value"}) to deserialize
correctly.
Json:
[{"from":"USD","to":"AUD","rate":"0.93"},{"from":"AUD","to":"USD","rate":"1.08"}]
ratesControtller:
public class rates
{
public rates(string from, string to, double rate)
{
this.from = from;
this.to = to;
this.rate = rate;
}
[JsonPropertyName("from")]
public string from { get; set; }
[JsonPropertyName("to")]
public string to { get; set; }
[JsonPropertyName("rate")]
public double rate { get; set; }
public class ListRates
{
public List<rates> LRates { get; set; }
}
index.cshtml:
#model JsonDisplayASP.Models.ListRates
#foreach (var item in Model.LRates)
{
var from = item.from;
var to = item.to;
var rate = item.rate;
How can I do it? She won't let me show her a view
Sorry, I can get values.
public class HomeController : Microsoft.AspNetCore.Mvc.Controller
public IActionResult Index()
{
List<rates> lrates = new List<rates>();
var wc = new WebClient();
var json = wc.DownloadString(#"C:\Users\hugo\source\repos\JsonDisplayASP\JsonDisplayASP\wwwroot\lib\rates.json");
List<rates> model=JsonConvert.DeserializeObject<List<rates>>(json);
//var model = JsonConvert.DeserializeObject<rates>(json);
//return Json(model);
return View(model);
}

Add checking in controller

I have class User in my project and have model UserRow (for showing user in view)
it's UserRow
using System;
namespace Argussite.SupplierServices.ViewModels
{
public class UserRow
{
public Guid Id { get; set; }
public string FullName { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Status { get; set; }
public int Role { get; set; }
public Guid SupplierId { get; set; }
public bool ActionsAllowed { get; set; }
public bool MailResendRequired { get; set; }
}
}
and I need to add in my controller checking if ActionsAllowed
[HttpPost]
public ActionResult Unlock(Guid id)
{
var user = Context.Users.Find(id);
if (user == null)
{
return Json(CommandResult.Failure("User was not found. Please, refresh the grid and try again."));
}
var checkActionsAllowed = Context.Users.AsNoTracking()
.Select(e => new UserRow
{
Id = e.Id,
ActionsAllowed = e.ActionsAllowed
};
if (checkActionsAllowed == true)
{
user.Status = UserStatus.Active;
return Json(CommandResult.Success(string.Format("User {0} has been unlocked.", user.FullName)));
}
else return;
}
but I got error with ActionsAllowed = e.ActionsAllowed and
in else return;
Help me please to solve this problem.
You have two problems:
Context.Users.AsNoTracking()
.Select(e => new UserRow
{
ActionsAllowed = e.ActionsAllowed
};
returns a list of objects, not a single object.
You have queried the user above, so i guess you can write simply:
if (user.ActionsAllowed) {
user.Status = UserStatus.Active;
return Json(CommandResult.Success...);
}
The second problem is the return; statement.
Your method returns an action result, so you have to return something.
For example
return Json(CommandResult.Failure(
"ActionsAllowed = false"));
First error sounds like you User class doesn't provide a ActionsAllowed Boolean property, while the second error happens because you need to return something from the method that can be interpreted as an ActionResult.
EDIT:
Hmm, I didn't notice this the first time, but this:
var checkActionsAllowed = Context.Users.AsNoTracking()
.Select(e => new UserRow
{
Id = e.Id,
ActionsAllowed = e.ActionsAllowed
};
followed by this:
if (checkActionsAllowed == true)
makes no sense - you're not returning a boolean result from a Select method, but rather an IEnumerable. Perhaps you should add your User schema to your question so that it's more obvious what you're trying to accomplish.

how to explicitly set the Order property of class members using XmlElement asp.net

I am trying to serialize my code.
When I set the Order property of class members using XmlElement ASP.Net, I got exception on this line;
XmlSerializer serializer = new XmlSerializer(typeof(HotelListResponse));
Exception is;
Inconsistent sequencing: if used on one of the class's members,the 'Order' property is required on all particle-like members,please explicitly set 'Order' using XmlElement, XmlAnyElement or XmlArray custom attribute on class member '_hotelId'.
Code is:
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
StreamReader responsereader = new StreamReader(response.GetResponseStream());
var responsedata = responsereader.ReadToEnd();
xmldoc = (XmlDocument)JsonConvert.DeserializeXmlNode(responsedata);
xmldoc.Save(#"C:\New folder\myfile.xml");
DataTable dt = new DataTable();
DataRow dr;
dt.Columns.Add("hotelId");
dt.Columns.Add("name");
dt.Columns.Add("address1");
dt.Columns.Add("address2");
dt.Columns.Add("city");
dt.Columns.Add("postalCode");
dt.Columns.Add("countryCode");
dr = dt.NewRow();
XmlSerializer serializer = new XmlSerializer(typeof(HotelListResponse));
Stream reader = new FileStream(#"C:\New folder\myfile.xml", FileMode.Open);
HotelListResponse htype = (HotelListResponse)serializer.Deserialize(reader);
dt.ReadXml(#"C:\New folder\myfile.xml");
foreach(hoteltype ht in htype.hotel){
GridView1.DataSource = dt;
GridView1.DataBind();
}
//responsereader.Close();
//request.GetResponse().Close();
}
}
catch (WebException ex)
{
if (ex.Response == null)
throw new NullReferenceException("WebException response");
throw ex;
}
}
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlRoot("HotelListResponse")]
public class HotelListResponse
{
[System.Xml.Serialization.XmlElementAttribute("hotel")]
public hoteltype[] hotel;
[System.Xml.Serialization.XmlElement(Order = 0)]
public hoteltype[] Items {
get {
return this.hotel;
}
set {
this.hotel = value;
}
}
}
[Serializable]
[XmlType("hoteltype")]
public class hoteltype
{
hoteltype(){}
public int _hotelId;
public string _name;
public string _address1;
public string _address2;
public string _city;
public int _postalCode;
public string _countryCode;
[XmlElement]
public hoteltype[] htype;
[System.Xml.Serialization.XmlElement(Order=1)]
public int hotelId
{
get {
return _hotelId;
}
set{
_hotelId = value;
}
}
[System.Xml.Serialization.XmlElement(Order=2)]
public string name
{
get
{
return _name;
}
set
{
_name = value;
}
}
[System.Xml.Serialization.XmlElement(Order=3)]
public string address1
{
get
{
return _address1;
}
set
{
_address1 = value;
}
}
[System.Xml.Serialization.XmlElement(Order=4)]
public string address2
{
get
{
return _address2;
}
set
{
_address2 = value;
}
}
[System.Xml.Serialization.XmlElement(Order=5)]
public string city
{
get
{
return _city;
}
set
{
_city = value;
}
}
[System.Xml.Serialization.XmlElement(Order=6)]
public int postalCode
{
get
{
return _postalCode;
}
set
{
_postalCode = value;
}
}
[System.Xml.Serialization.XmlElement(Order=7)]
public string countryCode
{
get
{
return _countryCode;
}
set
{
_countryCode = value;
}
}
}
As described in the exception, as soon as you use Order=xx, all of the serializable properties and fields on the class must be ordered. However, it seems that _hotelId may have been intended to be a private backing field. Since XmlSerializer serializes public fields as well, this may be unintentional. If _hotelId really must be public but you don't want it serialized, then you can use XmlIgnore.
I'm guessing your class might look something like:
[System.SerializableAttribute()]
public partial class HotelListResponse
{
[System.Xml.Serialization.XmlElement(Order = 0)]
public string SomeOrderedField
{
get;
set;
}
// ** Problem may be here
// Because the field is public, XmlSerializer will try to serialize this
public int _hotelId;
[System.Xml.Serialization.XmlElement(Order = 1)]
public int HotelId
{
get
{
return _hotelId;
}
set
{
_hotelId = value;
}
}
Edit Yes, that's exactly the problem
Make your backing fields private - that's why you've got the public Property accessors for.
public int _hotelId; => private int _hotelId;
public string _name; => private string _name;
etc.
Edit
[XmlElement(Order=0)]
public hoteltype[] htype;
I would also change it to a property at the same time. Use the automatic backing fields if you are using .NET 3.5 or higher.
[XmlElement(Order=0)]
public hoteltype[] htype
{
get;
set;
}
Edit
If you applied the above systematically, your serializable classes should look like:
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlRoot("HotelListResponse")]
public class HotelListResponse
{
// This is bad practice - never make a backing field public
//[System.Xml.Serialization.XmlElementAttribute("hotel")]
//public hoteltype[] hotel;
// Use the >= .Net 3.5 automatic properties - this way you don't need
// the backing field at all, which will prevent confusion over
// 'what gets serialized'
[System.Xml.Serialization.XmlElement(Order = 0)]
public hoteltype[] Items
{
get;
set;
}
}
[Serializable]
[XmlType("hoteltype")]
public class hoteltype
{
public hoteltype() { }
[System.Xml.Serialization.XmlElement(Order = 0)]
public hoteltype[] htype
{
get;
set;
}
[System.Xml.Serialization.XmlElement(Order = 1)]
public int hotelId
{
get;
set;
}
[System.Xml.Serialization.XmlElement(Order = 2)]
public string name
{
get;
set;
}
[System.Xml.Serialization.XmlElement(Order = 3)]
public string address1
{
get;
set;
}
[System.Xml.Serialization.XmlElement(Order = 4)]
public string address2
{
get;
set;
}
[System.Xml.Serialization.XmlElement(Order = 5)]
public string city
{
get;
set;
}
[System.Xml.Serialization.XmlElement(Order = 6)]
public int postalCode
{
get;
set;
}
[System.Xml.Serialization.XmlElement(Order = 7)]
public string countryCode
{
get;
set;
}
}
And testing the above via a serialize / deserialize cycle like so:
XmlSerializer serializer = new XmlSerializer(typeof(HotelListResponse));
HotelListResponse X = new HotelListResponse();
X.Items = new hoteltype[2];
X.Items[0] = new hoteltype();
X.Items[0].address1 = "address1";
X.Items[1] = new hoteltype();
X.Items[1].address1 = "address2";
using (Stream writer = new FileStream(#"C:\temp\myfile.xml", FileMode.Create))
{
serializer.Serialize(writer, X);
writer.Flush();
}
Stream reader = new FileStream(#"C:\temp\myfile.xml", FileMode.Open);
HotelListResponse htype = (HotelListResponse)serializer.Deserialize(reader);
The following file Deserializes:
<?xml version="1.0"?>
<HotelListResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<hotelId>0</hotelId>
<address1>address1</address1>
<postalCode>0</postalCode>
</Items>
<Items>
<hotelId>0</hotelId>
<address1>address2</address1>
<postalCode>0</postalCode>
</Items>
</HotelListResponse>

ASP.NET MVC 3 Data Annotation: Add validation dynamically

I'm new with data annotation. I'd like to know if it possible (and how) to add some validation dynamically. It is very extensive to explain why, but I've a ViewModel that receives and object when created. In that object I must check for some property and depending its value I should have or not some validations.
An example:
public class ProfileViewModel
{
[Required(ErrorMessage = "The field {0} is required")]
[Display(Name = "Client Code")]
public int ClientCode { get; set; }
[Required(ErrorMessage = "The field {0} is required")]
[StringLength(100, ErrorMessage = "The field {0} must have up to 100 characters.")]
[Display(Name = "Company")]
public string Company { get; set; }
[StringLength(50, ErrorMessage = "The field {0} must have up to 50 characters.")]
[Display(Name = "Name")]
public string Name { get; set; }
[StringLength(50, ErrorMessage = "The field {0} must have up to 50 characters.")]
[Display(Name = "LastName")]
public string LastName { get; set; }
public ProfileViewModel(User usr)
{
if (usuario.ClientCode != null)
{
ClientCode = Convert.ToInt32(usr.ClientCode);
}
else
{
//ClientCode and Company are not yet required.
//Name and LastName are now required.
}
Company = usr.Company;
Name = usr.Name;
LastName = usr.LastName;
}
}
I think that the simplest way of doing what I wanted is implementing IValidatableObject:
public class Product : IValidatableObject
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Prop1 < Prop2)
yield return new ValidationResult("Property 1 can't be less than Property 2");
}
}
See also: Class-Level Model Validation with ... ASP.NET MVC 3
Custom Attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class CustomRequiredIfAttribute : CustomAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public CustomRequiredIfAttribute()
{
}
public CustomRequiredIfAttribute(string dependentProperty, object targetValue)
: base()
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
Custom RequiredIfValidator
using System;
using System.Collections.Generic;
using System.Web.Mvc;
namespace Custom.Web.Validation
{
public class RequiredIfValidator : DataAnnotationsModelValidator<CustomRequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, CustomRequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return base.GetClientValidationRules();
}
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
object value = field.GetValue(container, null);
// compare the value against the target value
if (this.IsEqual(value) || (value == null && Attribute.TargetValue == null))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
{
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
private bool IsEqual(object dependentPropertyValue)
{
bool isEqual = false;
if (Attribute.TargetValue != null && Attribute.TargetValue.GetType().IsArray)
{
foreach (object o in (Array)Attribute.TargetValue)
{
isEqual = o.Equals(dependentPropertyValue);
if (isEqual)
{
break;
}
}
}
else
{
isEqual = Attribute.TargetValue.Equals(dependentPropertyValue);
}
return isEqual;
}
}
}
Register custom DataAnnotationsModelValidatorProvider
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CustomRequiredIfAttribute), typeof(RequiredIfValidator));
Use this CustomRequiredIf in the ViewModel
[CustomRequiredIf("CategoryId", 3, ErrorMessageResourceName = GlobalResourceLiterals.AccountGroup_Required)]
public string AccountGroup { get; set; }
Heres the updated MVC 3 version of that blog post http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx

Resources