Home > Net >  Passing a json array to a REST Web API
Passing a json array to a REST Web API

Time:11-23

This is a 2 part question.

  1. I would like to pass an array of int values to my REST url but I'm getting an inline constraint exception. The route I tried to defined is as follows

[Route("categories/{categoryId:int}/documenttypes/{documentTypes:int[]}")]

...
...

and when called, it would look like this:

   api/categories/2/documenttypes/{[2,3,4,5]}

Can anyone point me in the right direction on how to handle this in-line constraint exception. I have found a few articles but none seem to apply on how to handle an array of int values.

Maybe it is not do-able and I should just change this to a POST request but I thought I'd try it with a GET first or just use the string parameter and just add another resource definition to my REST url such as

   api/config/categories/2/documenttypes/multi?doctype={[1,3,4]}
  1. Is this event acceptable in terms of REST standards?

    api/categories/2/documenttypes/{[1,3,4]}
    

I know I could just pass it as a query string but the problem I have with that is that I ended up with 2 endpoints conflicting if I do this:

    api/categories/2/documenttypes

no parameters and returns all document types for the given category

    api/categories/2/documenttypes?doctypes={[1,2,3]}

When defined and then called, I get an error that there are multiple end point with the same definition, thus the request to see if I could somehow pass it as an inline contraint.

Hope the above makes sense. If not, ask away. Thanks

CodePudding user response:

assuming the action is

[HttpGet("~/api/categories/{id}/documenttypes")]
public ActionResult GetDocTypes(int id, int[] doctype)

use this url

api/categories/2/documenttypes?doctype=2&doctype=3&doctype=4&doctype=5

CodePudding user response:

I figured out the various parts that needed to be changed.

  1. First, I had to add a new route constraint (IHttpRouteConstraint) to handle the new data type passed as part of the Web API REST resource:

     public class HttpRouteConstraintIntArray : IHttpRouteConstraint
     {
          public bool Match(HttpRequestMessage request, IHttpRoute route,
            string parameterName, IDictionary<string, object> values,
            HttpRouteDirection routeDirection)
          {
              object value;
              if (values.TryGetValue(parameterName, out value) && value != null)
              {
                  var parameters = value.ToString()
                      .Replace("{", string.Empty)
                      .Replace("}", string.Empty)
                      .Replace("[", string.Empty)
                      .Replace("]", string.Empty);
                  var type = typeof(int);
                  var newvalues = parameters.Split(new[] { "," },
                   StringSplitOptions.RemoveEmptyEntries)                 
                  .Select(TypeDescriptor.GetConverter(type)
                  .ConvertFromString).ToArray();
    
                  var typedValues = Array.CreateInstance(type, newvalues.Length);
    
                  newvalues.CopyTo(typedValues, 0);
    
                  return true;    
              }
    
              return false;
          }
     }
    

Once I declared this new constraint, I also had to register it in the WebApiConfig.cs:

        var constraintResolver = new DefaultInlineConstraintResolver();
        constraintResolver.ConstraintMap.Add("intarray", 
           typeof(HttpRouteConstraintIntArray));

            config.MapHttpAttributeRoutes(constraintResolver);

But once I made these stanges, I started to get another error:

    the request contains an entity body but no Content-Type header
    get request ....
  1. In order to resolve this error, I had to add [FromUri] in the endpoint definition:

    [HttpGet]  [Route("categories/{categoryId:int}/documenttypes/
               {documentTypeIds:intarray}")]
     public async Task<IHttpActionResult> GetDataByDocumentTypeIds(
     int categoryId,[FromUri] int[] documentTypeIds)
    

Now the above is getting passed these 2 errors but some reason, the documentTypeIds is coming back as an array, but it only contains one value instead of 3 for example and its value is 0 instead of 1, so clearly need to add something else.

  1. To handle the last problem, I had to create an action filter attribute:

     public class ArrayInputAttribute : ActionFilterAttribute
     {
     private readonly string[] _parameternames;
    
     public string Separator { get; set; }
    
     public ArrayInputAttribute(params string[] parameternames)
     {
         this._parameternames = parameternames;
         Separator = "-";
     }
    
     public void ProcessArrayInput(HttpActionContext actionContext, string parametername)
     {
         if (actionContext.ActionArguments
               .ContainsKey(parametername))
         {
             var parameterdescriptor = actionContext.ActionDescriptor.GetParameters()
                 .FirstOrDefault(p => p.ParameterName == parametername);
    
             if (parameterdescriptor != null && parameterdescriptor.ParameterType.IsArray)
             {
                 var type = parameterdescriptor.ParameterType.GetElementType();
                 var parameters = string.Empty;
                 if (actionContext.ControllerContext.RouteData.Values
                       .ContainsKey(parametername))
                 {
                     parameters = (string)actionContext.ControllerContext
                       .RouteData.Values[parametername];
                 }
                 else
                 {
                     var queryString = actionContext.ControllerContext
                        .Request.RequestUri.ParseQueryString();
                     if (queryString[parametername] != null)
                     {
                         parameters = queryString[parametername];
                     }
                 }
    
                 parameters = parameters.ToString()
                     .Replace("{", string.Empty)
                     .Replace("}", string.Empty)
                     .Replace("[", string.Empty)
                     .Replace("]", string.Empty);
    
                 var values = parameters.Split(new[] { Separator },
                    StringSplitOptions.RemoveEmptyEntries)
                    Select(TypeDescriptor.GetConverter(type).
                    ConvertFromString).ToArray();
    
                 var typedValues = Array.CreateInstance(type, values.Length);
    
                 values.CopyTo(typedValues, 0);
    
                 actionContext.ActionArguments[parametername] = typedValues;
             }
         }
     }
    
     public override void OnActionExecuting(HttpActionContext actionContext)
     {
         foreach (var parameterName in _parameternames)
         {
             ProcessArrayInput(actionContext, parameterName);
         }
     }
    }
    

and lastly, I had to add the attribute to the endpoint definition:

    [HttpGet]
    [Route("categories/{categoryId:int}/documenttypes/
            {documentTypeIds:intarray}")]
    [ArrayInputAttribute("documentTypeIds", Separator = ",")]
    public async Task<IHttpActionResult> GetDataByDocumentTypeIds(int categoryId,
    [FromUri] int[] documentTypeIds)
    {
    }

A few notes:

  1. Some of the lines were split in order to be displayed semi decently in stackoverflow and clearly will not work in .NET studio

  2. The above code was a combination of a couple of articles I eventually found after more googling. These are:

-Pass an array of integers to ASP.NET Web API?

By the looks of things, I may not even need to Constraint, thought right now I don't have time to investigate as I need to move on, I will post an update when I do try it later.

Web API Attribute Routing Route Constraints

Web API Attribute Routing Route Constrains

Hope the above helps others.

Thanks.

  • Related