12

Is it possible to validate query parameters on an action without using a model? A lot of the calls in my API are one-offs and I don't see a point in making models for them if they will only be used one time.

I saw the following article, which seemed like it was exactly what I needed, except I don't want it to return a 404 if the required parm doesn't exist, I want it to return an object of error messages similar to the baked in model validation - really, I just want the parameters to be treated like a model, without actually having to make a model.

https://www.strathweb.com/2016/09/required-query-string-parameters-in-asp-net-core-mvc/

[HttpPost]
public async Task<IActionResult> Post(
    [FromQueryRequired] int? Id,
    [FromQuery] string Company)

EDIT:
The [FromQueryRequired] is a custom ActionConstraint that throws a 404 if the ID parm is missing (this was taken directly from the article). However I don't want the 404, I want an object that has a message that says {MESSAGE: "ID is required"}. I think the issue is that i can't access the Response context from within an Action Constraint.

Austin Shaw
  • 324
  • 1
  • 2
  • 12

4 Answers4

10

From Asp.Net Core 2.1 there is a built-in parameter [BindRequired] which is doing this validation automatically.

public async Task<ActionResult<string>> CleanStatusesAsync([BindRequired, 
    FromQuery]string collection, [BindRequired, FromQuery]string repository,
       [BindRequired, FromQuery]int pullRequestId)
{
    // all parameters are bound and valid
}

If you are calling this method without parameters, a ModelState error is returned:

{
"collection": [
  "A value for the 'collection' parameter or property was not provided."
],
"repository": [
  "A value for the 'repository' parameter or property was not provided."
],
"pullRequestId": [
  "A value for the 'pullRequestId' parameter or property was not provided."
],
}

More details you can find in this excellent article.

Karel Kral
  • 5,297
  • 6
  • 40
  • 50
2

You can read from request and validate it

string id= HttpContext.Request.Query["Id"].ToString();


    if (id==nll)
    {
    //do any thing here
    }
Sadeq Hatami
  • 140
  • 1
  • 8
  • I dont want to do any manual validation though, I was hoping there something I could do that is similar to how ModelState validation works. At least I know I can access the httpcontext from within the action constraint I'll play with that and see if I can work with it. – Austin Shaw Oct 24 '18 at 22:39
  • @AustinShaw you can create middelware to check request , validate and return your response – Sadeq Hatami Oct 25 '18 at 20:11
2

Here is the solution I ended up using. Add an Attribute to the parms named [RequiredParm]. I loosely based it on someone else's answer for a different question, but I can't seem to find it at the moment, apologies to whoever you are, if I can find it I'll update this answer for credit.

EDIT: Found it, answered by @James Law - Web Api Required Parameter

Usage:

[HttpPost]
public async Task<IActionResult> Post(
    [FromQuery, RequiredParm] int? Id,
    [FromQuery] string Company)

ActionFilterAttribute:

[AttributeUsage(AttributeTargets.Method)]
public sealed class CheckRequiredParmAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var requiredParameters = context.ActionDescriptor.Parameters.Where(
            p => ((ControllerParameterDescriptor)p).ParameterInfo.GetCustomAttribute<RequiredParmAttribute>() != null).Select(p => p.Name);

        foreach (var parameter in requiredParameters)
        {
            if (!context.ActionArguments.ContainsKey(parameter))
            {
                context.ModelState.AddModelError(parameter, $"The required argument '{parameter}' was not found.");
            }
            else
            {
                foreach (var argument in context.ActionArguments.Where(a => parameter.Equals(a.Key)))
                {
                    if (argument.Value == null)
                    {
                        context.ModelState.AddModelError(argument.Key, $"The requried argument '{argument.Key}' cannot be null.");
                    }
                }
            }
        }

        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
            return;
        }

        base.OnActionExecuting(context);
    }
}

/// <summary>
/// Use this attribute to force a [FromQuery] parameter to be required. If it is missing, or has a null value, model state validation will be executed and returned throught the response. 
/// </summary>  
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RequiredParmAttribute : Attribute
{
}
Austin Shaw
  • 324
  • 1
  • 2
  • 12
0

i haven't tried yet may be like that;

public class MyActionFilterAttribute: IActionFilter
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var id = actionContext.ActionArguments["Id"];
        if(string.IsNullOrEmpty(id))
            actionContext.Response = actionContext.Request.CreateResponse(
            HttpStatusCode.OK, 
            new {MESSAGE = "ID is required"}, 
            actionContext.ControllerContext.Configuration.Formatters.JsonFormatter
        );
    }
}

[HttpPost]
[MyActionFilterAttribute]
public ActionResult Post([FromQueryRequired] int? Id,[FromQuery] string Company)
bashkan
  • 464
  • 1
  • 5
  • 14