My route is defined as http:/.../home/index/1 which is (controller/action/id).
ViewModel is as below..
public class TestVM
{
[CustomValidation]
public string name{get; set;}
}
public class CustomValidation : ValidationAttribute
{
protected override ValidationResult? IsValid (object? value, validationContext)
{
var vm = (TestVM)validationContext.ObjectInstance;
//how to get the route value here?
}
}
To validate Name property, I need to have value of Id. To access route value in the IsValid method, I defined one more property Id in the TestVM.
Is there a way to access the Id in the IsValid without defining in the TestVM?
Is there a way to access the Id in the IsValid without defining in the
TestVM?
Seems you are trying to access parameter which has been passed to controller route so that, you can get those values inside your CustomValidation class
Well, using IHttpContextAccessor we can get all route value from which are available within HttpContext.Request. Thus, you can access property name of member of TestVM class.
Access Route Value Inside ValidationAttribute:
You can implement in following way:
public class CustomValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var vm = context.ObjectInstance as TestVM;
var httpContextAccessor = context.GetRequiredService<IHttpContextAccessor>()
var getIdFromRouteValue = httpContextAccessor.HttpContext.Request.Query["Id"];
var getNameFromRouteValue = httpContextAccessor.HttpContext.Request.Query["name"];
vm.Id = getIdFromRouteValue;
vm.name = getNameFromRouteValue;
return ValidationResult.Success;
}
}
Program.cs:
IHttpContextAccessor requires to register AddHttpContextAccessor() service on your program.cs file. You can do as following:
builder.Services.AddHttpContextAccessor();
Note: Please make sure you have followed the correct order. You can get more details on our official document here.
Output:
Related
I'm trying to resolve a problem I found while trying to do a custom validation attribute on my daughter's dog store using blazor.
Basically I have a property as follow:
[Test]
public int? PetAge { get; set; }
As you can see, I'm going to create a custom attribute called "Test", so I created a new class as follow:
public class Test : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
return new ValidationResult("niet! niet!");
}
}
and my understanding is that I can return a new ValidationResult with a string message or if everything goes fine, I can return ValidationResult.Success. In my example, it should always return a problem, as we did not provide any other logic than always returning a validationresult.
When running this code, it simply does not return anything, no error displayed on the form. Now what I did notice is that if I use the other version of the IsValid method, it works, the only problem is that I would like to send a custom message back and not just a true/false result. The one working is as follow:
public override bool IsValid(object? value)
{
return false;
}
But in that case, I have to provide the error message in the parameter like this:
[Test(ErrorMessage("this is not ok!")]
public int? PetAge { get; set; }
As these parameters must be constants, it was an awesome idea not only to be able to display a custom-built message as an error and/or have access to the context, etc...
am I doing something wrong on my Blazor app? any help will be appreciated!
I noticed this too, try the following
public class Test : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
return new ValidationResult("niet! niet!", new[] { validationContext.MemberName });
}
}
I am using .NET Core and have the following code in a view model:
public bool IsLocalArrangement { get; set; }
[RequiredIfLocalArrangements]
public string LocalArrangementDetail { get; set; }
For this annotation to work, I created a new class, and have the following code:
using System.ComponentModel.DataAnnotations;
using MyProject.ViewModels;
namespace MyProject.Validation
{
public class RequiredIfLocalArrangements : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var vm = (ArrangementsViewModel)validationContext.ObjectInstance;
return string.IsNullOrEmpty((string)value) && vm.IsLocalArrangement ?
new ValidationResult("Please provide details for the local arrangement(s)")
: ValidationResult.Success;
}
}
}
However this scenario is coming up a lot, and I'm now replicating all this code to new classes (RequiredIfOwnsVehicle, etc.), where the only variables I need to change are the view model name, the boolean property within the view model that it depends on, and the validation result message.
How can I make this generic?
EDIT:
In trying to explore the foolproof project from github I run into this issue with System.Web.Mvc
I got the below compilation error when I added "System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"].ToString()" to the authorize role section header.
In the web.config I have:
add key="ADGroupReader" value="Readers DEV"
Compilation error: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
[AuthorizedRedirect]
[Authorize(Roles = System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"].ToString())]
public class HomeController : Controller
{
.....
}
I do not want to hard code the role (Roles="Readers DEV"); I would like to read it from the web.config. How can I do that?
This attributes tutorial explains attribute parameter restrictions:
Attribute parameters are restricted to constant values of the
following types:
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
One-dimensional arrays of any of the above types
From description above, this assignment is invalid due to existence of ToString method:
[Authorize(Roles = System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"].ToString())]
As a workaround, you can create a custom AuthorizeAttribute with predefined Roles parameter which contains default assignment to Roles with your AppSettings:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public CustomAuthorizeAttribute()
{
this.Roles = ConfigurationManager.AppSettings["ADGroupReader"].ToString();
}
// other stuff
}
Usage in controller class:
[AuthorizedRedirect]
[CustomAuthorize]
public class HomeController : Controller
{
.....
}
I solved it this way
Created a derived class ReaderAuthorizeAttribute
public class ReaderAuthorizeAttribute : AuthorizeAttribute
{
public ReaderAuthorizeAttribute()
{
this.Roles = System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"];
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return base.AuthorizeCore(httpContext);
}
}
Then added [LoteReaderAuthorizeAttribute]
[AuthorizedRedirect]
[ReaderAuthorizeAttribute]
public class HomeController : Controller
{
....
}
I solved this the following way, allowing you to define the setting name the roles value should come from:
public class AuthorizeBySettingAttribute : AuthorizeAttribute
{
public AuthorizeBySettingAttribute(string setting) : base()
{
if (setting != null && Settings.Default[setting] != null)
{
this.Roles = Settings.Default[setting].ToString();
}
else
{
throw new InvalidOperationException("AuthorizeBySetting initialized with invalid setting");
}
}
}
In my case I'm getting the value from a generated Settings class, but you can just change Settings.Default to ConfigurationManager.AppSettings.
I need to set RegularExpression Dynamically in Model.
I mean, I have stored RegularExpression in table and then I will store that value in one variable.
Now I want to supply that variable in Regular Express validation.
i.e
[RegularExpression(VariableValue, ErrorMessage = "Valid Phone is required")]
Something like
i.e
string CustomExpress = "#"^(\+|\d)(?:\+?1[-. ]?)?\(?([0-9]{2})\)?[-. ]?([0-9]{1})[-. ]?([0-9]{9})$" (from Database's table)
[RegularExpression(CustomExpress, ErrorMessage = "Valid Phone is required")]
public string Phone { get; set; }
You have two options, either you create your own validation attribute or you make your whole model "validatable".
Option 1
public class RegexFromDbValidatorAttribute : ValidationAttribute
{
private readonly IRepository _db;
//parameterless ctor that initializes _db
public override ValidationResult IsValid(object value, ValidationContext context)
{
string regexFromDb = _db.GetRegex();
Regex r = new Regex(regexFromDb);
if (value is string && r.IsMatch(value as string)){
return ValidationResult.Success;
}else{
return new ValidationResult(FormatMessage(context.DisplayName));
}
}
}
Then on your model:
[RegexFromDbValidator]
public string Telephone {get; set;}
Option 2
public SomeModel : IValidatableObject
{
private readonly IRepository _db;
//don't forget to initialize _db in ctor
public string Telephone {get; set;}
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
string regexFromDb = _db.GetRegex();
Regex r = new Regex(regexFromDb);
if (!r.IsMatch(Telephone))
yield return new ValidationResult("Invalid telephone number", new []{"Telephone"});
}
}
Here's a good resource that explains how to create validation attributes
Here's an example of using IValidatableObject
As Murali stated Data Annotation attributes values must be compile time constants.
If you want to perform dynamic validation based on other model values you can try with some kind of third party framework (e.g. Fluent Validation, it even can be integrated in ASP.NET's model validation too).
I believe there might be a way to implement this by inheriting the IValidatableObject interface. Upon doing so whenever the ModelState gets validated on the server side you could perform all the necessary checks that you wish.
It would look something like:
public class SomeClass: IValidatableObject {
private RegEx validation;
public SomeClass(RegEx val) {
this.validation = val;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
// perform validation logic - check regex etc...
// if an error occurs:
results.Add(new ValidationResult('error message'));
}
}
Is it possible to get action name of System.Web.Mvc.RemoteAttribute object. (Initialized at constructor stage)
GetUrl() method and RouteData property are protected. Are there any hints?
With reflection you can get protected properties.
The better solution I think is to create a new attribute class derived from RemoteAttribute and add some public methods/properties which return the Url and RouteData. For example:
public class MyRemoteAttribute: System.Web.Mvc.RemoteAttribute
{
public string GetUrlPublic()
{
return this.GetUrl();
}
public RouteValueDictionary GetRouteData()
{
return this.RouteData;
}
}