Hello everyone I have create custom validation attribute and assign it to class level validation. Unfortunately, it is not called. I try every way that it think it could be solve the problem. However, it take me for hours and I can't find the attribute is not called by validation mechanism.
For illustrate you I put the following code.
Attribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class BooleanDependencyAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "กรุณากรอก{0}";
private readonly object _typeId = new object();
public string DependencyPropertyName { get; private set; }
public string DependentPropertyName { get; private set; }
public BooleanDependencyAttribute(string dependencyPropertyName, string dependentPropertyName)
: base(_defaultErrorMessage)
{
DependencyPropertyName = dependencyPropertyName;
DependentPropertyName = dependentPropertyName;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,name);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
bool dependencyValue = (bool) properties.Find(DependencyPropertyName, true /* ignoreCase */).GetValue(value);
object dependentValue = properties.Find(DependentPropertyName, true /* ignoreCase */).GetValue(value);
if (dependencyValue)
{
return true;
}
else
{
if (dependentValue == null)
{
return false;
}
else
{
return true;
}
}
}
}
ViewModel
[BooleanDependency("ReleaseNow","ReleaseDate",ErrorMessage="Please enter release date")]
public class ContentCreate
{
public string Title { get; set; }
public DateTime? ReleaseDate { get; set; }
public string Details { get; set; }
public string Abstract { get; set; }
public string Tags { get; set; }
public bool ReleaseNow { get; set; }
}
Please could you help me to solve this problem.
I found the solution. In fact validation in class level is called after all property-level validations are valid. Therefore I need to complete other required property then BooleanDependencyAttribute will called and valid value.
Thanks for view, edit the title and tag.
Related
In the below code i am getting an exception because "isValid" is coming as null from the input request.
I want to set "isValid" to "False" when it was null from the input request.
Can anyone pls suggest me how i can do this ?
public class Details
{
public string status { get; set; }
public MessageInfo messageInfo { get; set; }
}
public class MessageInfo
{
public bool isValid { get; set; }
}
var inputMessage =
{
"Body":
{
"status":"success",
"MessageInfo":
{
"isValid":null
}
}
}
var messagebody = inputMessage.Body.ToObject<Details>();
this works for me
void Main()
{
var inputMessage = "{ \"Body\":{\"status\":\"success\", \"MessageInfo\":{\"isValid\":null} }}";
var inputMessageObj= JsonConvert.DeserializeObject<Root>(inputMessage);
}
classes
public class Details
{
public string status { get; set; }
public MessageInfo messageInfo { get; set; }
}
public class MessageInfo
{
private bool? _isValid = false;
public bool? IsValid
{
get { return _isValid; }
set { _isValid = value == null ? false : value; }
}
}
public class Body
{
public string Status { get; set; }
public MessageInfo MessageInfo { get; set; }
}
public class Root
{
public Body Body { get; set; }
}
Assuming you are using Newtonsoft.Json, use the NullValueHandling property of the JsonSerializer class, setting it to ignore. Then pass in this instance of JsonSerializer to an overload of the ToObject<T> function.
This tells serialization to ignore any properties that were null, leaving the property initialized to its default value. (You can control that default value separately via System.ComponentModel.DefaultValueAttribute if you want.)
Fully compiling example below. (The references to 'Body' were removed, to make it easier to focus on the main problem.)
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace SomeNamespace
{
public class Program
{
private static void Main()
{
var inputMessage = JToken.Parse(
#"{
""status"":""success"",
""MessageInfo"":
{
""isValid"":null
}
}");
// build a custom serializer with a setting to ignore null
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
};
var serializer = JsonSerializer.Create(jsonSettings);
// using the serializer with custom settings avoids the original exception
var messagebody = inputMessage.ToObject<Details>(serializer);
}
}
public class Details
{
public string status { get; set; }
public MessageInfo messageInfo { get; set; }
}
public class MessageInfo
{
public bool isValid { get; set; }
}
}
This is my logs table:
public class Logs
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public DateTime Date { get { return DateTime.Now; } }
public string Controller { get; set; }
public string Action { get; set; }
public string Text { get; set; }
public bool isError {
get {
return this._isError.HasValue ? this._isError.Value : false;
}
set {
this._isError = value;
this.isError = value;
}
}
private bool? _isError = null;
}
and I don't know why when I try to make an insert, like:
var log = new Logs();
log.Action = "Send";
log.Controller = "Home";
log.Text = msg;
_context.logs.Add(log);
I get this error:
System.InvalidOperationException: 'Nullable object must have a value.'
And this is the object:
Except that private _isError variable, all the database columns have a value.
Where is the error?
Why is your private property nullable and your public property is not?
I would remove your private property and then change your public property:
public bool IsError { get; set; }
Since you're never going to return 'null' anyway, why would you make it an option?
Edit:
I also think this will cause an endless loop of your IsError property to be set:
set {
this._isError = value;
this.isError = value;
}
You're setting the public property within the public property setter.
Only setting the private property here is enough since you return the value of the private property in your getter.
This is just a side note though, I would still remove the private property and just use
public bool IsError { get; set; }
I am invoking a method in my constructor like below.Is this the right way to do to set properties based on some validations.Please suggest.
public class Asset
{
public Asset(string id)
{
SetStorageId(id);
}
public string AssetId { get; set; }
public string UtilId { get; set; }
public string MappingId { get; set; }
public bool IsActive { get; set; }
private void SetStorageId(string id)
{
if (Regex.Match(id, "^[A-Z][a-zA-Z]*$").Success)
{
AssetId = id;
}
else
{
UtilId = id;
}
}
}
In my opinion your design should be like below,
You should abstract common items to base class and create specific class inheriting this,
and decide from client(consumer) which instance do you need and construct it
public class AssetBase
{
public string MappingId { get; set; }
public bool IsActive { get; set; }
}
public class Asset : AssetBase
{
public string AssetId { get; set; }
}
public class Util : AssetBase
{
public string UtilId { get; set; }
}
static void Main(string[] args)
{
string id = Console.ReadLine();
if (Regex.Match(id, "^[A-Z][a-zA-Z]*$").Success)
{
Asset asset = new Asset();
asset.AssetId = id;
}
else
{
Util util = new Util();
util.UtilId = id;
}
}
simply try this
public class Asset
{
private string id;
public string AssetId { get; set; }
public string UtilId { get; set; }
public string Id
{
set
{
if (Regex.Match(value, "^[A-Z][a-zA-Z]*$").Success)
{
this.id = value;
}
else
{
UtilId = value;
}
}
get
{
return id;
}
}
}
When you create a property in c#, a private variable is created for that property on compile time. When you try to set the Id property in the code above the Id you pass goes into the value keyword and you can perform your validations on the value keyword and set your property accordingly.
No need to complicate your code with set methods, constructors or deriving classes
or you can even use data annotations which is a more elegant way https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute.aspx#Properties
using System.ComponentModel.DataAnnotations;
public class Asset
{
[RegularExpression("^[A-Z][a-zA-Z]*$")]
public string Id { get; set; }
}
It's not wrong. It can possibly grow to be a little confusing. Maybe you can make it clearer by moving the bod of SetStorageId to the constructor. Perhaps there is no need to complicate with subclassing, relative to other code within the project.
json string
{
"success": true,
"challenge_ts": "2016-11-03T17:30:00Z",
"hostname": "mydomain.com"
}
class
internal class reCaptchaResponse
{
internal bool success { get; set; }
internal DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
internal string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
internal string[] error_codes { get; set; } // optional
}
attempt to serialize
reCaptchaResponse responseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<reCaptchaResponse>(jsonResult);
attempt fails like...
Newtonsoft.Json.JsonConvert.SerializeObject(responseObject) returns
{}
Json.Net, by default, only serializes/deserialzes public fileds and properties, but you can also do it without changing access modifiers from internal to public.
Just use JsonProperty attribute
internal class reCaptchaResponse
{
[JsonProperty]
internal bool success { get; set; }
[JsonProperty]
internal DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
[JsonProperty]
internal string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
[JsonProperty]
internal string[] error_codes { get; set; } // optional
}
(Without modifing the original class) You can even use ContractResolver to select which properties/fields should be used in serialization process
EDIT
Although this answer has already been accepted, I want to post a code where the original assembly can not be modified.
var settings = new JsonSerializerSettings() {
ContractResolver = new AllPropertiesContractResolver()
};
reCaptchaResponse responseObject =
JsonConvert.DeserializeObject<reCaptchaResponse>(jsonResult ,settings);
public class AllPropertiesContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.Select(x => new Newtonsoft.Json.Serialization.JsonProperty()
{
PropertyName = x.Name,
PropertyType = x.PropertyType,
Readable = true,
ValueProvider = new AllPropertiesValueProvider(x),
Writable = true
})
.ToList();
return props;
}
}
public class AllPropertiesValueProvider : Newtonsoft.Json.Serialization.IValueProvider
{
PropertyInfo _propertyInfo;
public AllPropertiesValueProvider(PropertyInfo p)
{
_propertyInfo = p;
}
public object GetValue(object target)
{
return _propertyInfo.GetValue(target); //Serialization
}
public void SetValue(object target, object value)
{
_propertyInfo.SetValue(target, value, null); //Deserialization
}
}
Change the properties to public. By default it does not deserialize non-public properties
internal class reCaptchaResponse
{
public bool success { get; set; }
public DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
public string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
public string[] error_codes { get; set; } // optional
}
the members must be public
internal class reCaptchaResponse
{
public bool success { get; set; }
public DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
public string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
public string[] error_codes { get; set; } // optional
}
You can try to change modificator from internal to public
I have a request like this:
filter[logic]:and
filter[filters][0][value]:a
filter[filters][0][operator]:startswith
filter[filters][0][field]:result
filter[filters][0][ignoreCase]:true
I need to receive it on the Controller but I don't know exactly how. I have tried this view model:
{
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filters { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
//public string operator { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
}
But the Controller receives it all null. operator property is commented because operator is a reserved keyword, I don't know how to make Asp.Net to use it. And I don't know if this is the cause of the problem.
Note that I can't change the request body pattern because it comes from this Kendo Widget.
This is my Controller(test version):
public ActionResult Text(SearchFilterViewModel filter)
{
return Json("", JsonRequestBehavior.AllowGet);
}
Here is working solution
Model:
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filter { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
public string oper { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
Then you can write custom IValueProvider where you can override usual parsing mechanism like this:
public class KendoValueProvider : NameValueCollectionValueProvider
{
public KendoValueProvider(NameValueCollection originalCollection)
: base(UpdateCollection(originalCollection), CultureInfo.InvariantCulture)
{
}
private static NameValueCollection UpdateCollection(NameValueCollection collection)
{
NameValueCollection result = new NameValueCollection();
foreach (string key in collection.Keys)
{
// ignore all other request
if (!key.StartsWith("filter"))
return null;
var newKey = key
.Replace("[filters]", string.Empty)
.Replace("filter[logic]", "logic")
.Replace("[value]", ".value")
.Replace("[operator]", ".oper")
.Replace("[field]", ".field")
.Replace("[ignoreCase]", ".ignoreCase");
var value = collection[key];
result.Add(newKey, value);
}
return result;
}
}
Then you need to write ValueProviderFactory that will register this ValueProvider like this:
public class KendoValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new KendoValueProvider(controllerContext.HttpContext.Request.QueryString);
}
}
And the last step is just register it in Global.asax file
ValueProviderFactories.Factories.Add(new KendoValueProviderFactory());
And sample Action
[HttpGet]
public ActionResult Index(SearchFilterViewModel model)
{
return Json(model, JsonRequestBehavior.AllowGet);
}