3

Trying to make my own Validation Attribute that uses a property name to find another property.

Currently, I am having problems finding the other property. It seems I am unable to find this property (or in fact any properties).

The check for property == null is always coming up as true.

Any ideas why I wouldn't be able to find properties?

This is the custom filter I have made

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectInstance.GetType().GetProperty(PropertyName);

        if (property == null)
        {
            return new ValidationResult(string.Format(
                "Unknown property {0}",
                new[] { PropertyName }
            ));
        }

        var propertyValue = property.GetValue(validationContext.ObjectInstance);

        // Just for testing purposes.
        return new ValidationResult(ErrorMessage);

    }

This is the model I am using behind my razor view.

public class OrganisationDetailsModel : PageModel
{

    private readonly FormStateContext _context;

    public OrganisationDetailsModel(FormStateContext context)
    {
        _context = context;
    }

    [BindProperty]
    [RegularExpression(pattern: "(yes|no)")]
    [Required(ErrorMessage = "Please select if you are registered on companies house")]
    public string CompanyHouseToggle { get; set; }

    [BindProperty]
    [StringLength(60, MinimumLength = 3)]
    [RequiredIf("CompanyHouseToggle")]
    public string CompanyNumber { get; set; }

    [BindProperty]
    [StringLength(60, MinimumLength = 3)]
    [Required(ErrorMessage = "Enter your organisation name")]
    public string OrganisationName { get; set; }

    [BindProperty]
    [RegularExpression(pattern: "(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})", ErrorMessage = "This VAT number is not recognised")]
    [Required(ErrorMessage = "Enter your vat number")]
    public string VatNumber { get; set; }

    public void OnGet()
    {
    }

    public IActionResult OnPost()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
        return RedirectToPage("ApplicantDetails");
    }

I appreciate the fact that the custom validation attribute doesn't really do anything at the moment but that is becuase I have become stuck on this issue.

Thanks for any help.

0

2 Answers 2

3

Let's use the following code snippet to help explain what is going on here:

protected override ValidationResult IsValid(object value, ValidationContext ctx)
{
    var typeFullName = ctx.ObjectInstance.GetType().FullName;

    ...
}

In this example, you might expect typeFullName to be XXX.OrganisationDetailsModel, but it isn't: it's actually System.String (the type of the property you’re trying to validate). System.String clearly does not have a property named e.g. CompanyHouseToggle and so GetProperty rightly returns null.

I've not seen many cases where [BindProperty] has been used more than once on a PageModel. It's certainly possible, but it appears that each property is treated as being individual and that the PageModel itself is not being validated.

In order to work around this, you can just turn your individual properties into a complex type and use that instead. The docs and examples use an inline class for this, inside of the PageModel class. Here's an example of an updated OrganisationDetailsModel class:

public class OrganisationDetailsModel : PageModel
{
    ...

    [BindProperty]
    public InputModel Input { get; set; }

    public void OnGet() { }

    public IActionResult OnPost()
    {
        if (!ModelState.IsValid)
            return Page();

        return RedirectToPage("ApplicantDetails");
    }

    public class InputModel
    {
        [RegularExpression(pattern: "(yes|no)")]
        [Required(ErrorMessage = "Please select if you are registered on companies house")]
        public string CompanyHouseToggle { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [RequiredIf("CompanyHouseToggle")]
        public string CompanyNumber { get; set; }

        ...
    }
}

This includes the following changes:

  • Creation of an InputModel class to hold all properties.
  • Removal of all other properties that have now moved into InputModel.
  • Addition of an Input property of type InputModel, that gets bound using [BindProperty].
  • Removed [BindProperty] from the original properties that have now been moved.

The last step is to replace any usage of e.g. CompanyNumber with Input.CompanyNumber in the PageModel's corresponding .cshtml and to ensure you use the Input. prefix when accessing properties within the PageModel class itself.

Sign up to request clarification or add additional context in comments.

Comments

0

By the doc. getProperties() only return publics.

https://learn.microsoft.com/en-us/dotnet/api/system.type.getproperties?view=netframework-4.7.2

So if want to get non-public properties. Find on belows.

Get protected property value of base class using reflections


protected override ValidationResult IsValid(object value, ValidationContext context) {
    var property = context.ObjectType.getProperty(context.MemberName);

    // TODO

    return ValidationResult.Success;
}

2 Comments

But the fields I am trying to get are publics? For example, CompanyHouseToggle is one I am trying to retrieve and this is public.
Ok. I found solution. GetType() method from validationContext is from Object class. So, you was wrong. You should use member "ObjectType" instead of GetType().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.