FluentValidation rule for null object - c#

I've been trying to work out how to create a FluentValidation rule that checks if the instance of an object it's validating is not null, prior to validating it's properties.
I'd rather encapsulate this null validation in the Validator rather then doing it in the calling code.
See example code below with comments where the required logic is needed:
namespace MyNamespace
{
using FluentValidation;
public class Customer
{
public string Surname { get; set; }
}
public class CustomerValidator: AbstractValidator<Customer>
{
public CustomerValidator()
{
// Rule to check the customer instance is not null.
// Don't continue validating.
RuleFor(c => c.Surname).NotEmpty();
}
}
public class MyClass
{
public void DoCustomerWork(int id)
{
var customer = GetCustomer(id);
var validator = new CustomerValidator();
var results = validator.Validate(customer);
var validationSucceeded = results.IsValid;
}
public Customer GetCustomer(int id)
{
return null;
}
}
}
So my question is how do I check in the CustomerValidator() constructor that the current instance of customer is not null and abort further rule processing if it is null?
Thanks in advance.

EDIT 2022-07-19
As some commenters have pointed out, please check out answer https://stackoverflow.com/a/52784357/1943 for a newer implementation. I haven't personally vetted, but it's worth a try to give that a go first.
If you're using an older version, or you enjoy nostalgia, my original answer below is from 2013.
You should be able to override the Validate method in your CustomerValidator class.
public class CustomerValidator: AbstractValidator<Customer>
{
// constructor...
public override ValidationResult Validate(Customer instance)
{
return instance == null
? new ValidationResult(new [] { new ValidationFailure("Customer", "Customer cannot be null") })
: base.Validate(instance);
}
}

I can't really test that right now, but you can either try to override Validate, or include the rules in the When block:
public CustomerValidator()
{
When(x => x != null, () => {
RuleFor(x => x.Surname).NotEmpty();
//etc.
});
}

For those using version >6.2.1 you need to override this signature instead, in order to achieve the same as #chrispr:
public override ValidationResult Validate(ValidationContext<T> context)
{
return (context.InstanceToValidate == null)
? new ValidationResult(new[] { new ValidationFailure("Property", "Error Message") })
: base.Validate(context);
}
/// EXAMPLE FOR NETCORE-3.1
/// fluentvalidator-9.5.0
public class Organisation
{
public string Name { get; set; }
}
public class OrganisationValidator : AbstractValidator<Organisation>
{
public OrganisationValidator()
{
RuleFor(x => x.Name).NotNull().MaximumLength(50);
}
protected override bool PreValidate(ValidationContext<Organisation> context, ValidationResult result)
{
if (context.InstanceToValidate == null) {
result.Errors.Add(new ValidationFailure("", "org is null"));
return false;
}
return base.PreValidate(context, result);
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void ValidateWithNull()
{
var validator = new OrganisationValidator();
Organisation organisation = null;
var result = validator.Validate(organisation);
// result.Errors[0].ErrorMessage == "org is null";
}
}

This is an older post, but want to update the answers to include the following from the FluentValidation documentation:
Using PreValidate
If you need to run specific code every time a validator is invoked, you can do this by overriding the PreValidate method. This method takes a ValidationContext as well as a ValidationResult, which you can use to customise the validation process.
public class MyValidator : AbstractValidator<Person> {
public MyValidator() {
RuleFor(x => x.Name).NotNull();
}
protected override bool PreValidate(ValidationContext<Person> context, ValidationResult result) {
if (context.InstanceToValidate == null) {
result.Errors.Add(new ValidationFailure("", "Please ensure a model was supplied."));
return false;
}
return true;
}
}
https://docs.fluentvalidation.net/en/latest/advanced.html?#prevalidate

I inherited from the fluent AbstractValidator and created a NullReferenceAbstractValidator class instead:
public class NullReferenceAbstractValidator<T> : AbstractValidator<T>
{
public override ValidationResult Validate(T instance)
{
return instance == null
? new ValidationResult(new[] { new ValidationFailure(instance.ToString(), "response cannot be null","Error") })
: base.Validate(instance);
}
}
and then inherited from that class with each validator that needed a null reference check:
public class UserValidator : NullReferenceAbstractValidator<User>

As the above solutions didn't work for me (FluentValidation, Version=6.2.1.0 for Net45), I am posting what I did.
This is just a simple replacement/wrapper for ValidateAndThrow extension method.
public static class ValidatorExtensions
{
public static void ValidateAndThrowNotNull<T>(this IValidator<T> validator, T instance)
{
if (instance == null)
{
var validationResult = new ValidationResult(new[] { new ValidationFailure("", "Instance cannot be null") });
throw new ValidationException(validationResult.Errors);
}
validator.ValidateAndThrow(instance);
}
}

By means of Custom(). It can be also very helpful when validation of another field is based on validation of your current field.
ruleBuilder.Custom((obj, context) =>
{
if (obj != null)
{
var propertyName = <field where should be validation>;
context.AddFailure(propertyName, "'Your field name' Your validation message.");
}
});

Use the Cascade mode.
Here is the example from the documentation.
RuleFor(x => x.Surname).Cascade(CascadeMode.StopOnFirstFailure).NotNull().NotEqual("foo");
Also from the documentation:
If the NotNull validator fails then the NotEqual validator will not be
executed. This is particularly useful if you have a complex chain
where each validator depends on the previous validator to succeed.

Override EnsureInstanceNotNull as below
protected override void EnsureInstanceNotNull(object instanceToValidate)
{
if(instanceToValidate==null)
throw new ValidationException("Customer can not be null");
}

You can override a virtual method called EnsureInstanceNotNull as the author recommends here
public class CustomerValidator: AbstractValidator<Customer>
{
public CustomerValidator()
{
// Rule to check the customer instance is not null.
RuleFor(c => c).NotNull();
// Don't continue validating.
RuleFor(c => c.Surname).NotEmpty();
}
protected override void EnsureInstanceNotNull(object instance) { }
}

The commonly accepted PreValidate answer will not work in this instance, as per here:
If you use SetValidator with an AbstractValidator derivative, then it
will not be run if the property value is null. This is intentional
behaviour as AbstractValidator derivatives are designed to validate
the properties of a complex type, which cannot be done if the instance
is null. AbstractValidator is not designed for use with
simple/primative types or object. Now, if you want to check for null
here, then you can precede the SetValidator call with a NotNull rule
(although it doesn't seem like this is what you want in this case).
For me the only thing that worked is (my example):
RuleForEach(command => command.ProductDto.ProductInfos).NotNull().WithMessage("Custom Message").SetValidator(new ProductInfoValidator());
Without the "NotNull()", null values will be skipped.

Related

How to validate an item when it is added to the list in C#

I need to validate an item added to a list in C#. I've looked at this article: Validating lists in C#, but it isn't working.
returnModel.FileSystemItems.Add(new FileManagerFileSystemItem
{
Name = item.FolderName,
Key = item.FolderId.ToString(),
Key2 = "folder",
Key3 = key3,
IsDirectory = true,
HasSubDirectories = list.Any(a => a.ParentId == item.FolderId),
DateModified = DateTime.Now,
Size = 0,
Metadata = metadata,
Permissions = key3 == FileManagerFolderType.Private.ToString() ? GetPrivateFolderPermissions() : GetPublicFolderPermissions(hasFileManager)
});
So I want to make sure they set Key to an id or make sure Key2 is set to "Files" or "Folders"
I'm not trying to validate really the end-user, but validate to make sure the developer has set all the necessary properties when using the list.
Of course, I could add a Validate() method within the class to validate it after they are done adding to it, but then the developer would have to know to add that code at the end, which kind of defeats the purpose.
I tried to do the example here: https://stackoverflow.com/a/51523347/13698253, but it will never hit the breakpoint in the code.
Any ideas on how to solve this OR do I just need to add my own validation method at the end of the list that will make sure that the developer added everything properly.
This is not in a model validation for the model state of the controller. This is just in a normal class that might not be tied to MVC etc.
Thanks.
You can create your own custom List class which will throw an exception if the item being added is invalid:
public FileManagerFileSystemItemList : List<FileManagerFileSystemItem>
{
// edit: added TryAdd as per other's suggestions
// all this does is wrap the Add call in a try/catch block
// which would be enough to prevent unwanted exceptions
// but using Add alone would throw potentially useful exceptions
// for the developer to then deal with.
public bool TryAdd(FileManagerFileSystemItem item)
{
try
{
Add(item);
return true;
}
catch
{
return false;
}
}
new public void Add(FileManagerFileSystemItem item)
{
Validate(item);
base.Add(item);
}
private void Validate(FileManagerFileSystemItem item)
{
if (!Guid.TryParse(item.Key, out _))
throw new Exception("Key is not an id");
// ... and other validation
}
}
If however you wanted something more generic, you could put the validation into the class to be validated and have it implement an interface that can then be used as a constraint in a ValidatableList:
public interface IValidatable
{
bool IsValid { get; }
void Validate();
}
public class ValidatableList<T> : List<T> where T : class, IValidatable
{
public bool TryAdd(T item)
{
if (!item.IsValid)
return false;
base.Add(item);
return true;
}
new public void void Add(T item)
{
item.Validate();
base.Add(item);
}
}
public class FileManagerFileSystemItem : IValidatable
{
...
// rest of your class
...
public bool IsValid
{
get
{
try
{
Validate();
return true;
}
catch
{
return false;
}
}
}
public void Validate()
{
if (!Guid.TryParse(Key, out _))
throw new Exception("Key is not an id");
// ... and other validation
}
}
// Now your FileSystemItems property would be an instance of the new List class
...
ValidatableList<FileManagerFileSystemItem> FileSystemItems ...
...
The only potential issue I can see with the above is if the class you want a list of is not one you can modify to add in your validation. In this case you would need a separate validator class that handles this for your object which would look more like:
public interface IValidator<T> where T : class
{
bool IsValid(T item);
void Validate(T item);
}
public class FileSystemItemValidator : IValidator<FileManagerFileSystemItem>
{
public bool IsValid(T item)
{
try
{
Validate(item);
return true;
}
catch
{
return false;
}
}
public void Validate(T item)
{
if (!Guid.TryParse(item.Key, out _))
throw new Exception("Key is not an id");
// ... and other validation
}
}
// and the list class needs to know which class can validate its items
public class ValidatableList<T, TValidator> : List<T> where TValidator : class, IValidator<T>, new()
{
private readonly TValidator _validator;
public ValidatableList()
: base()
{
_validator = new TValidator();
}
public bool TryAdd(T item)
{
if (!_validator.IsValid(item))
return false;
base.Add(item);
return true;
}
new public void void Add(T item)
{
_validator.Validate(item);
base.Add(item);
}
}
// Now your FileSystemItems property would be an instance of the new List class
...
ValidatableList<FileManagerFileSystemItem, FileSystemItemValidator> FileSystemItems ...
...

FluentValidation - pre-validation / conditional validation with no code duplication

I'm trying to create Validation which is able to have two groups and block second validation if first fail (it contains many rules).
For now I did create a private 'BasicValidation' class inside and in 'main validator' do sth like this:
RuleFor(m => m).SetValidator(new BasicValidation()).DependentRules(() => {
//Complex validation
RuleFor(m => m.IdOfSthInDb)
.MustAsync(ItemMustExists)
.WithMessage("Item does not exist.");
});
It does the trick but I would like to avoid creating that 'BasicValidation' for each model.
In my previous answer I misunderstood the question. The main goal is to avoid code duplication in different validators. After some investigation I found solution that matches your requirements. Suppose you have models:
public abstract class BaseModel
{
public string BaseProperty1 { get; set; }
public string BaseProperty2 { get; set; }
}
public class ChildModel : BaseModel
{
public int IdOfSthInDb { get; set; }
}
You have to create validator for base model (it would be used further):
class InternalBaseModelValidator : AbstractValidator<BaseModel>
{
public InternalBaseModelValidator()
{
RuleFor(x => x.BaseProperty1).NotEmpty().WithMessage("Property 1 is empty");
RuleFor(x => x.BaseProperty2).NotEmpty().WithMessage("Property 2 is empty");
}
}
Then you can use new feature of FluentValidation, called PreValidate:
public class BaseModelValidator<T>: AbstractValidator<T> where T : BaseModel
{
// necessary for reusing base rules
private readonly InternalBaseModelValidator preValidator;
protected BaseModelValidator()
{
preValidator = new InternalBaseModelValidator();
}
protected override bool PreValidate(ValidationContext<T> context, ValidationResult result)
{
var preValidationResult = preValidator.Validate(context.InstanceToValidate);
if (preValidationResult.IsValid)
{
return true;
}
foreach(var error in preValidationResult.Errors)
{
result.Errors.Add(new ValidationFailure(error.PropertyName, error.ErrorMessage, error.AttemptedValue));
}
return false;
}
}
After creating validator for all base models you can inherit from it for ChildModel validation:
public class ChildModelValidator : BaseModelValidator<ChildModel>
{
public ChildModelValidator()
: base()
{
RuleFor(x => x.IdOfSthInDb)
.MustAsync(ItemMustExists)
.WithMessage("Item does not exist.");
}
private Task<bool> ItemMustExists(int arg1, CancellationToken arg2)
{
return Task.FromResult(false); // some logic here
}
}
That's it!
I think hext code would solve your problem:
var basicValidator = new BasicValidation();
RuleFor(m => m).SetValidator(basicValidator));
When(m => basicValidator.Validate(m).IsValid, () =>
{
RuleFor(m => m.IdOfSthInDb)
.MustAsync(ItemMustExists)
.WithMessage("Item does not exist.");
});

How to ignore a test in C# when CurrentEnv is Prod

have created a ProdIgnoreAttribute which extends from IgnoreAttribute. And I have assigned this attribute to certain tests which I want to run in DEV/QA but not in PROD.
ApplyToTest(Test test) method is not being called in this case. How to resolve this?
public class ProdIgnoreAttribute : IgnoreAttribute
{
private string IgnoreReason { get; }
public ProdIgnoreAttribute(string reason) : base(reason)
{
IgnoreReason = reason;
}
public new void ApplyToTest(Test test)
{
if (test.RunState == RunState.NotRunnable)
return;
if (StaticInfoHelper.VrCurrentEnv == (int)RunEnv.PROD)
{
test.RunState = RunState.Ignored;
test.Properties.Set("_SKIPREASON", (object)IgnoreReason);
}
else
{
base.ApplyToTest(test);
}
}
}
How about extending Attribute rather than IgnoreAttribute?
public class ProdIgnoreAttribute : Attribute, ITestAction
{
public void BeforeTest(TestDetails details)
{
bool ignore = StaticInfoHelper.VrCurrentEnv == (int)RunEnv.PROD;
if (ignore)
Assert.Ignore("Test ignored during Prod runs");
}
//stub out rest of interface
}
If you want a custom ignore message you could make a ProdIgnoreAttribute constructor that accepts a string. You'd then use the attribute on tests like: [ProdIgnore("ignored because xyz")]

ModelState.IsValid even when it should not be?

I have API where I need to validate my user model. I choose an approach where I create different classes for Create/Edit actions to avoid mass-assignment and divide validation and actual model apart.
I don't know why but ModelState.IsValid returns true even when it should not. Am I doing something wrong?
Controller
public HttpResponseMessage Post(UserCreate user)
{
if (ModelState.IsValid) // It's valid even when user = null
{
var newUser = new User
{
Username = user.Username,
Password = user.Password,
Name = user.Name
};
_db.Users.Add(newUser);
_db.SaveChanges();
return Request.CreateResponse(HttpStatusCode.Created, new { newUser.Id, newUser.Username, newUser.Name });
}
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
Model
public class UserCreate
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string Name { get; set; }
}
Debug proof
The ModelState.IsValid internally checks the Values.All(modelState => modelState.Errors.Count == 0) expression.
Because there was no input the Values collection will be empty so ModelState.IsValid will be true.
So you need to explicitly handle this case with:
if (user != null && ModelState.IsValid)
{
}
Whether this is a good or bad design decision that if you validate nothing it will true is a different question...
Here is an action filter to check for null models or invalid models. (so you dont have to write the check on every action)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace Studio.Lms.TrackingServices.Filters
{
public class ValidateViewModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.Any(kv => kv.Value == null)) {
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Arguments cannot be null");
}
if (actionContext.ModelState.IsValid == false) {
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
}
You can register it globally:
config.Filters.Add(new ValidateViewModelAttribute());
Or use it on demand on classes/actions
[ValidateViewModel]
public class UsersController : ApiController
{ ...
I wrote a custom filter which not only ensures that all non optional object properties are passed, but also checks if model state is valid:
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class ValidateModelAttribute : ActionFilterAttribute
{
private static readonly ConcurrentDictionary<HttpActionDescriptor, IList<string>> NotNullParameterNames =
new ConcurrentDictionary<HttpActionDescriptor, IList<string>> ();
/// <summary>
/// Occurs before the action method is invoked.
/// </summary>
/// <param name="actionContext">The action context.</param>
public override void OnActionExecuting (HttpActionContext actionContext)
{
var not_null_parameter_names = GetNotNullParameterNames (actionContext);
foreach (var not_null_parameter_name in not_null_parameter_names)
{
object value;
if (!actionContext.ActionArguments.TryGetValue (not_null_parameter_name, out value) || value == null)
actionContext.ModelState.AddModelError (not_null_parameter_name, "Parameter \"" + not_null_parameter_name + "\" was not specified.");
}
if (actionContext.ModelState.IsValid == false)
actionContext.Response = actionContext.Request.CreateErrorResponse (HttpStatusCode.BadRequest, actionContext.ModelState);
}
private static IList<string> GetNotNullParameterNames (HttpActionContext actionContext)
{
var result = NotNullParameterNames.GetOrAdd (actionContext.ActionDescriptor,
descriptor => descriptor.GetParameters ()
.Where (p => !p.IsOptional && p.DefaultValue == null &&
!p.ParameterType.IsValueType &&
p.ParameterType != typeof (string))
.Select (p => p.ParameterName)
.ToList ());
return result;
}
}
And I put it in global filter for all Web API actions:
config.Filters.Add (new ValidateModelAttribute ());
Updated slightly for asp.net core...
[AttributeUsage(AttributeTargets.Method)]
public sealed class CheckRequiredModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var requiredParameters = context.ActionDescriptor.Parameters.Where(
p => ((ControllerParameterDescriptor)p).ParameterInfo.GetCustomAttribute<RequiredModelAttribute>() != null).Select(p => p.Name);
foreach (var argument in context.ActionArguments.Where(a => requiredParameters.Contains(a.Key, StringComparer.Ordinal)))
{
if (argument.Value == null)
{
context.ModelState.AddModelError(argument.Key, $"The argument '{argument.Key}' cannot be null.");
}
}
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
context.Result = new BadRequestObjectResult(errors);
return;
}
base.OnActionExecuting(context);
}
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RequiredModelAttribute : Attribute
{
}
services.AddMvc(options =>
{
options.Filters.Add(typeof(CheckRequiredModelAttribute));
});
public async Task<IActionResult> CreateAsync([FromBody][RequiredModel]RequestModel request, CancellationToken cancellationToken)
{
//...
}
This happened to me, and in my case, I had to change using Microsoft.Build.Framework; to using System.ComponentModel.DataAnnotations; (and add the reference).
I was looking for a solution to this problem and came out here first. After some further research I have realized the following solution:
How do you use my solution?
You can register it globally:
config.Filters.Add(new ValidateModelStateAttribute());
Or use it on demand for a class
[ValidateModelState]
public class UsersController : ApiController
{...
or for a methode
[ValidateModelState]
public IHttpActionResult Create([Required] UserModel data)
{...
As you can see, a [System.ComponentModel.DataAnnotations.Required] atribute has been placed in the method parameter.
This indicates that the model is required and can not be null.
You can also use with a custom message:
[ValidateModelState]
public IHttpActionResult Create([Required(ErrorMessage = "Custom message")] UserModel data)
{...
Here is my code:
using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace your_base_namespace.Web.Http.Filters
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class ValidateModelStateAttribute : ActionFilterAttribute
{
private delegate void ValidateHandler(HttpActionContext actionContext);
private static readonly ConcurrentDictionary<HttpActionBinding, ValidateHandler> _validateActionByActionBinding;
static ValidateModelStateAttribute()
{
_validateActionByActionBinding = new ConcurrentDictionary<HttpActionBinding, ValidateHandler>();
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
GetValidateHandler(actionContext.ActionDescriptor.ActionBinding)(actionContext);
if (actionContext.ModelState.IsValid)
return;
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
private ValidateHandler GetValidateHandler(HttpActionBinding actionBinding)
{
ValidateHandler validateAction;
if (!_validateActionByActionBinding.TryGetValue(actionBinding, out validateAction))
_validateActionByActionBinding.TryAdd(actionBinding, validateAction = CreateValidateHandler(actionBinding));
return validateAction;
}
private ValidateHandler CreateValidateHandler(HttpActionBinding actionBinding)
{
ValidateHandler handler = new ValidateHandler(c => { });
var parameters = actionBinding.ParameterBindings;
for (int i = 0; i < parameters.Length; i++)
{
var parameterDescriptor = (ReflectedHttpParameterDescriptor)parameters[i].Descriptor;
var attribute = parameterDescriptor.ParameterInfo.GetCustomAttribute<RequiredAttribute>(true);
if (attribute != null)
handler += CreateValidateHandler(attribute, parameterDescriptor.ParameterName);
}
return handler;
}
private static ValidateHandler CreateValidateHandler(ValidationAttribute attribute, string name)
{
return CreateValidateHandler(attribute, new ValidationContext(new object()) { MemberName = name });
}
private static ValidateHandler CreateValidateHandler(ValidationAttribute attribute, ValidationContext context)
{
return new ValidateHandler(actionContext =>
{
object value;
actionContext.ActionArguments.TryGetValue(context.MemberName, out value);
var validationResult = attribute.GetValidationResult(value, context);
if (validationResult != null)
actionContext.ModelState.AddModelError(context.MemberName, validationResult.ErrorMessage);
});
}
}
}
There is a simple Solution for your problem
public class UserCreate
{
[Required(AllowEmptyStrings = false)]
public string Username { get; set; }
}
Here AllowEmptyStrings = false can be used for your validation
Try
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
in the startup.cs file's ConfigureServices()
What I did was to create an Attribute along with an ActionFilter and a Extension Method to avoid null models.
The extension method looks for parameters with the NotNull attribute and check if they are null, if true, they are instantiated and set in the ActionArguments property.
This solution can be found here: https://gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9
This "ModelState.IsValid returns true even when it should not" problem can also appear if you forgot to add getters and setters on your model (OP didn't forget, but I did which led me to this question). I hope it's ok to provide solutions that have the same symptoms but are slightly different than OP's code:
Wrong:
public class UserRegisterModel
{
[Required]
public string Login; // WRONG
[Required]
public string Password; // WRONG
}
Good:
public class UserRegisterModel
{
[Required]
public string Login { get; set; }
[Required]
public string Password { get; set; }
}
this issue happened to me .i do not know why but take it easy just change your action Object name(UserCreate User) by some other like (UserCreate User_create)

Reflecting Over Nested Instances without Creating New Instance

I have been struggling a bit with some reflection code that I though would be simple. Essentially, I have an interface that defines a method. Then, I have an abstract class that provides a base implementation of that method.
The concrete classes can contain nested instances of other classes that can also derive from the same base class. It can be illustrated by the following sample:
using System.Linq;
public interface ISampleObject
{
bool IsValid();
}
public abstract class SampleObjectBase : ISampleObject
{
public bool IsValid()
{
var returnValue = true;
// Self-validation sets the return value.
var childProperties = this.GetType().GetProperties().Where(pi => typeof(ISampleObject).IsAssignableFrom(pi.PropertyType));
foreach (var childProperty in childProperties)
{
// var childInstance = ????; // Need the actual *existing* instance property, cast to ISampleObject.
// if (childInstance.IsValid() != true)
// {
// returnValue = false;
// }
}
return returnValue;
}
}
public sealed class InnerSampleObject : SampleObjectBase
{
}
public sealed class OuterSampleObject : SampleObjectBase
{
public InnerSampleObject DerivedSampleObject { get; set; }
}
My problem is that in the commented code for SampleObjectBase, I cannot get the concrete instance of the matching PropertyInfo value. If I look at the PropertyInfo object in the loop, I see that the type is correct, but I cannot find a way to directly access the instance that already exists in the implementation. So, when executing, for example, OuterSampleObject.IsValid(), the code finds the PropertyInfo for InnerSampleObject, as expected. I want to execute InnerSampleObject.IsValid().
I have tried (multiple variations of):
var childIsValid = (bool)contractProperty.PropertyType.InvokeMember("IsValid", BindingFlags.InvokeMethod, null, null, null);
And:
var childInstance = (ISampleContract)contractProperty;
The problem with the first one is that I can't pass null in as the target for InvokeMember, as IsValid() is not static (nor can it be, since I am focused on the actual instance). The second on is just a lame cast, but is the gist of what I want to accomplish.
The sample code above is just a minimalist example of what I want to achieve. The full code is part of a self-validating DTO that recursively checks the entire hierarchy and returns what children have validation issues and what they are.
Any help would be greatly appreciated.
How about:
var instance = childProperty.GetValue(this, null) as ISampleObject;
if (instance != null)
{
if (!instance.IsValid())
return false;
}
Please see if the code below is what you are looking for. My changes are marked with a comment starting with //VH:
public interface ISampleObject
{
bool IsValid();
}
public abstract class SampleObjectBase : ISampleObject
{
public virtual bool IsValid()
{
var returnValue = true; //VH: Changed value from false to true
// Self-validation sets the return value.
var childProperties = this.GetType().GetProperties().Where(pi => typeof(ISampleObject).IsAssignableFrom(pi.PropertyType));
foreach (var childProperty in childProperties)
{
//VH: Here is how you get the value of the property
var childInstance = (ISampleObject)childProperty.GetValue(this, null);
if (childInstance.IsValid() != true)
{
returnValue = false;
}
}
return returnValue;
}
}
public sealed class InnerSampleObject : SampleObjectBase
{
}
public sealed class OuterSampleObject : SampleObjectBase
{
//VH: Added this constructor
public OuterSampleObject()
{
DerivedSampleObject = new InnerSampleObject();
}
public InnerSampleObject DerivedSampleObject { get; set; }
}
class Program
{
static void Main(string[] args)
{
OuterSampleObject c = new OuterSampleObject();
c.IsValid();
}
}
Just use
var childInstance = (ISampleObject)childProperty.GetValue(this, null);

Categories