15

I am using Data Annotations to validate my Model in ASP.NET MVC. This works well for action methods that has complex parameters e.g,

public class Params  
{  
    [Required] string Param1 {get; set;}   
    [StringLength(50)] string Param2 {get; set;}  
}


ActionResult MyAction(Params params)  
{  
   If(ModeState.IsValid)  
   {  
      // Do Something  
   }  
}

What if I want to pass a single string to an Action Method (like below). Is there a way to use Data Annotations or will I have to wrap the string into a class?

ActionResult MyAction(string param1, string param2)  
{  
   If(ModeState.IsValid)  
   {  
     // Do Something  
   }  
}  
dannie.f
  • 2,395
  • 2
  • 22
  • 20

6 Answers6

4

Create your own model...

public class Params  
{  
    [Required] string param1 {get; set;}   
    [StringLength(50)] string param2 {get; set;}  
}

And change your signature of your server side controller:

[HttpGet]
ActionResult MyAction([FromUri] Params params)  
{  
    If(ModeState.IsValid)  
    {  
        // Do Something  
    }  
}  

If your client side code already worked you don't have to change it... Please, note that the properties of your Model are the same of the parameter you are passing now (string param1, string param2)... and they are case sensitive...

EDIT: I mean that you can call your controller in this way:

http://localhost/api/values/?param1=xxxx&param2=yyyy

Simone
  • 2,304
  • 6
  • 30
  • 79
  • Yes, but... if they are missing a query string, the action can still execute and the model can be null and still complete the action. So, null check the model. – Patrick Knott May 28 '20 at 16:45
  • [FromUri] Params params doesn't work for me, now I would have to rewrite all my tests to provide this Params class instead. Sadly this is not a viable solution. – Essej Jun 05 '20 at 07:41
3

I don't believe there is a Data Annotations method to what you are proposing. However, if you want your validation to happen before the action method is invoked, consider adding a custom model binder attribute to the parameter and specify a specific model binder you want to use.

Example:

public ActionResult MyAction [ModelBinder(typeof(StringBinder)] string param1, [ModelBinder(typeof(StringBinder2)] string param2)
{
  .........
}
Tejs
  • 40,736
  • 10
  • 68
  • 86
  • 3
    And make a horror out of simple solution with view model... I believe that there is no need to look for other one. – LukLed Apr 26 '10 at 22:11
3

With ActionFilterAttribute it is possible to use DataAnnotation on action parameters. This enables you to do things like this:

ActionResult MyAction([Required] string param1, [StringLength(50)] string param2)  
{  
   If(ModeState.IsValid)  
   {  
     // Do Something  
   }  
}

See the solution here: https://blog.markvincze.com/how-to-validate-action-parameters-with-dataannotation-attributes/

It uses an action filter to pass through all the action parameters of the query and execute the data annotations on them (if there is any).


EDIT: The solution above only works in .NET Core. I made a slightly modified version that works on .NET Framework 4.5 (might work on older versions)

public class ValidateActionParametersAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext context)
    {
        var parameters = context.ActionDescriptor.GetParameters();

        foreach (var parameter in parameters)
        {
            var argument = context.ActionArguments[parameter.ParameterName];

            EvaluateValidationAttributes(parameter, argument, context.ModelState);
        }

        base.OnActionExecuting(context);
    }

    private void EvaluateValidationAttributes(HttpParameterDescriptor parameter, object argument, ModelStateDictionary modelState)
    {
        var validationAttributes = parameter.GetCustomAttributes<ValidationAttribute>();

        foreach (var validationAttribute in validationAttributes)
        {
            if (validationAttribute != null)
            {
                var isValid = validationAttribute.IsValid(argument);
                if (!isValid)
                {
                    modelState.AddModelError(parameter.ParameterName, validationAttribute.FormatErrorMessage(parameter.ParameterName));
                }
            }
        }
    }
}
Maxime
  • 2,192
  • 1
  • 18
  • 23
3

UPDATE: ASP.NET Core 3

In ASP.net Core 3 it's working as supposed to be: Just decorate the parameters like any other property of your DTOs.

[HttpPut]
[Route("{id}/user-authorizations")]
public async Task<IActionResult> AuthorizeUsersOnAppsAsync(
   [Range(1, int.MaxValue, ErrorMessage = "Enter the company identifier")] int id,
   [FromBody] List<AuthorizeCompanyUserDto> authorizations)
{
    ...
}

Response

enter image description here

Tip

You don't even need to check the model validity manually. Just decorate your controller with the [ApiController] and ASP.NET Core will automatic validate them.

Maicon Heck
  • 2,017
  • 1
  • 20
  • 27
1

Commenting here because the solutions on here weren't right for my scenario, so just came up with my own solution instead.

This works for ASP.NET Framework 4.5.2, in an MVC5 web application. This code is by no means perfect as I threw it together very quickly for a proof of concept, but it does compile. You can remove the part where I return a BadRequest status code result and add the validation errors to the model state - or do whatever your requirements need.

First create a custom attribute class:

public class DataAnnotationFilterAttribute : ActionFilterAttribute, IResultFilter
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {

        var actionParams = filterContext.ActionDescriptor.GetParameters();

        if (actionParams != null)
        {
            foreach (var param in actionParams)
            {
                ValidationAttribute[] atts = (ValidationAttribute[])param.GetCustomAttributes(typeof(ValidationAttribute), false);

                if (atts != null && filterContext.ActionParameters.TryGetValue(param.ParameterName, out object value))
                {
                    foreach(ValidationAttribute att in atts)
                    {
                        if (!att.IsValid(value))
                            filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest);
                    }
                }
            }
        }
        
        base.OnActionExecuting(filterContext);
    }
}

Then register your attribute in FilterConfig.cs, which should already be part of your project:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute()); // this was existing
        filters.Add(new DataAnnotationFilterAttribute());
    }
}

Now you can use attributes that inherit from the "ValidationAttribute" on your MVC action parameters.

0
  1. Create your own filter attribute:

    public class ActionParameterValidationAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            actionContext
                .ActionDescriptor
                .GetParameters()
                .SelectMany(p => p.GetCustomAttributes<ValidationAttribute>().Select(a => new
                {
                    IsValid = a.IsValid(actionContext.ActionArguments[p.ParameterName]),
                    p.ParameterName,
                    ErrorMessage = a.FormatErrorMessage(p.ParameterName)
                }))
                .Where(_ => !_.IsValid)
                .ToList()
                .ForEach(_ => actionContext.ModelState.AddModelError(_.ParameterName, _.ErrorMessage));
    
            if (!actionContext.ModelState.IsValid)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
    
  2. Use it globally:

    new HttpConfiguration { Filters = { new ModelValidationAttribute() } };
    
Andriy Tolstoy
  • 5,690
  • 2
  • 31
  • 30