C# Unable to find other properties in custom valid

2019-07-17 07:59发布

问题:

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.

回答1:

By the doc. getProperties() only return publics.

https://docs.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:

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.