Search code examples
c#validationattribute

Is it possible to bind A RequiredAttribute to a function?


public partial class TaskScheduling
{
    public Guid EvaluationTemplateId { get; set; }

    //[Required(ErrorMessage = "TaskScheduling.OwnerUser")]
    [Required(ErrorMessage = ResolveErrorMessage())]
    public string SenderName { get; set; }

    [Required]
    public string SenderMail { get; set; }

    [Required]
    public string Subject { get; set; }

    [Required]
    public string BodyMessage { get; set; }

    private string ResolveErrorMessage()
    {
        //Code to customize the error message here.
        return "test";
    }
}

I'm trying to bind The error message to a function, so I can customize the type of message that I will show. But when I try to set the error message to the function ResolveErrorMessage, the compiler says that : an object reference is required to access non-static field, method, or property 'TaskScheduling.ResolveErrorMessage()'.

Therefore I try to set the ResolveErrorMessage as a static function.

    private static string ResolveErrorMessage()
    {
        //Code to customize the error message here.
        return "test";
    }

And now the compilers complains with the following : an attribute argument must be a constant expression typeof expression or array creation expression.

So, Is what I'm trying to do even possible?


Solution

  • As stated in the comments, this is not possible. The value needs to be known at compilation time. Besides, how would the runtime know when to call this method, and how would you customize it without having any data available, e.g. the invalid value.

    There are other ways around this, but it depends to what extent you want to customize the error message.

    1. Simple case - built in message formatting

    In the simplest case, you can put simply add a {0} placeholder into the message text for where to put the property name:

    [Required(ErrorMessage = "Please enter {0}!")]
    public string SenderName { get; set; }
    

    Alternatively,you can reference a resource string instead of a hard-coded message text if you want to update the error message later without recompiling your code, using the ErrorMessageResourceType and ErrorMessageResourceName properties:

    [Required(ErrorMessageResourceType = typeof(MyMessageResources), ErrorMessageResourceName = "SenderNameRequiredMessage")]
    public string SenderName { get; set; }
    

    2. Custom Logic - Not calling validated instance

    If you do need logic to customize the error message, you are best off creating a sub-class of RequiredAttribute and overriding properties or methods as required to add the logic there.

    For example, if for some obscure reason you wanted to add the day of the week to the error message, you can override FormatErrorMessage() to do this:

    public class DayOfWeekRequiredAttribute : RequiredAttribute
    {
        public override string FormatErrorMessage(string propertyName)
        {
            string day = DateTime.Today.DayOfWeek.ToString();
            return string.Format(this.ErrorMessage, propertyName, day);
        }
    }
    
    public partial class TaskScheduling
    {
        // {0} is property name, {1} is day of week
        [DayOfWeekRequired(ErrorMessage = "Hey, it's {1} and {0} is a required field!")]
        public string SenderName { get; set; }
    }
    

    You can make this a little more general and pass a type which is responsible for resolving the error message, so you don't need to lots of XXXXRequireAttribute classes:

    // interface representing a class for resolving the error message
    public interface IErrorMessageResolver
    {
        // The attribute will call this method
        string ResolveErrorMessage(string propertyName, object value);
    }
    
    public class MessageResolverRequiredAttribute : RequiredAttribute
    {
        // Stores the type responsible for resolving the error message
        public Type MessageResolverType { get; set; }
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // call the built-in RequiredAttribute validator
            ValidationResult result = base.IsValid(value, validationContext);
    
            // if not valid, attempt to get the custom error message
            if (!result.Equals(ValidationResult.Success))
            {
                if (this.MessageResolverType != null)
                {
                    // create the resolver and check it actually is a resolver
                    IErrorMessageResolver handler = Activator.CreateInstance(this.MessageResolverType) as IErrorMessageResolver;
                    if (handler == null)
                    {
                        throw new Exception("MessageResolverType is not an IErrorMessageResolver");
                    }
    
                    // get the custom error message, if returns null, don't override the built-in error message
                    string customError = handler.ResolveErrorMessage(validationContext.MemberName, value);
                    if (customError != null)
                    {
                        result.ErrorMessage = customError;
                    }
                }
            }
    
            return result;
        }
    }
    
    public partial class TaskScheduling
    {
        [MessageResolverRequired(MessageResolverType = typeof(MyMessageResolver))]
        public string SenderName { get; set; }
    }
    

    3. Custom Logic - Using validated instance

    To ask the instance which is being validated itself for the error message, you can use the same principal as the general custom logic above, but it will be the insatnce which would need to implement IErrorMessageResolver. You can use the validationContext.ObjectInstance property to get hold of this. I would say this is really only useful if you need to use private data within the instance to create the error message. Otherwise it is a bit of an odd, un-SOLID pattern.

    // interface representing a class for resolving the error message
    public interface IErrorMessageResolver
    {
        // The attribute will call this method
        string ResolveErrorMessage(string propertyName, object value);
    }
    
    public class MessageResolverRequiredAttribute : RequiredAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            ValidationResult result = base.IsValid(value, validationContext);
    
            if (!result.Equals(ValidationResult.Success))
            {
                IErrorMessageResolver handler = validationContext.ObjectInstance as IErrorMessageResolver;
                if (handler == null)
                {
                    throw new Exception("Not validating an IErrorMessageResolver");
                }
    
                string customError = handler.ResolveErrorMessage(validationContext.MemberName, value);
                if (customError != null)
                {
                    result.ErrorMessage = customError;
                }
            }
    
            return result;
        }
    }
    
    public partial class TaskScheduling
        : IErrorMessageResolver
    {
        [MessageResolverRequired(MessageResolverType = typeof(MyMessageResolver))]
        public string SenderName { get; set; }
    
        public string ResolveErrorMessage(string propertyName, object value)
        {
            if (propertyName == "SenderName")
            {
                return "You need to set a Sender!";
            }
    
            return null;
        }
    }