This is a 2 part question.
- 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]}
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.
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 ....
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.
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:
Some of the lines were split in order to be displayed semi decently in stackoverflow and clearly will not work in .NET studio
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.