The specification of OData V4 states that it MUST be possible: https://issues.oasis-open.org/browse/ODATA-636.
"Complex types and arrays can only be passed to functions through parameter aliases"
When I'm trying to pass an array with OData parameter aliases an exception occurs.
/TestEntities/NS.TestFunction(ArrayHere=@p)?@p=[1,2,3]
Results in:
Unable to cast object of type 'EdmValidCoreModelPrimitiveType' to type 'Microsoft.OData.Edm.IEdmStructuredType
The interesting thing is that the metadata document is correctly composed for such cases:
<Function Name="TestFunction" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(NS.TestEntity)"/>
<Parameter Name="ArrayHere" Type="System.Int32[]"/>
<ReturnType Type="Collection(NS.TestEntity)"/>
</Function>
Is it possible with ASP.NET MVC Web API 2 OData to pass an array to OData function in query string?
UPDATE:
Here is the code to build the EDM model and the controller.
var builder = new ODataConventionModelBuilder();
builder.Namespace = "NS";
builder.EntitySet<TestEntity>("TestEntities");
builder.EntityType<TestEntity>().Collection
.Function("TestFunction")
.ReturnsCollectionFromEntitySet<TestEntity>("TestEntities")
.Parameter<int[]>("ArrayHere");
Controller:
public class TestEntitiesController : ODataController
{
public IEnumerable<TestEntity> TestFunction(int[] arrayHere)
{
throw new NotImplementedException();
}
}
Marking parameter with [FromODataUri]
does not solve the problem.
UPDATE 2:
Here is the stack trace:
at Microsoft.OData.Core.UriParser.TypePromotionUtils.CanConvertTo(SingleValueNode sourceNodeOrNull, IEdmTypeReference sourceReference, IEdmTypeReference targetReference)
at Microsoft.OData.Core.UriParser.Parsers.MetadataBindingUtils.ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference)
at Microsoft.OData.Core.UriParser.Parsers.FunctionCallBinder.BindSegmentParameters(ODataUriParserConfiguration configuration, IEdmOperation functionOrOpertion, ICollection`1 segmentParameterTokens)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.TryBindingParametersAndMatchingOperation(String identifier, String parenthesisExpression, IEdmType bindingType, ODataUriParserConfiguration configuration, ICollection`1& boundParameters, IEdmOperation& matchingOperation)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.TryCreateSegmentForOperation(ODataPathSegment previousSegment, String identifier, String parenthesisExpression)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreateNextSegment(String text)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.ParsePath(ICollection`1 segments)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathFactory.BindPath(ICollection`1 segments, ODataUriParserConfiguration configuration)
at Microsoft.OData.Core.UriParser.ODataUriParser.ParsePathImplementation()
at Microsoft.OData.Core.UriParser.ODataUriParser.Initialize()
at System.Web.OData.Routing.DefaultODataPathHandler.Parse(IEdmModel model, String serviceRoot, String odataPath, Boolean enableUriTemplateParsing)
at System.Web.OData.Routing.DefaultODataPathHandler.Parse(IEdmModel model, String serviceRoot, String odataPath)
at System.Web.OData.Routing.ODataPathRouteConstraint.Match(HttpRequestMessage request, IHttpRoute route, String parameterName, IDictionary`2 values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraint(HttpRequestMessage request, Object constraint, String parameterName, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraints(HttpRequestMessage request, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request)
at System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext)
First step is to build the list and assign to a global (let's use $list). Probably would read the source (could be a file, db query, xml/json from an inbound api call), build a loop using While() and append values to $list (like $list = $list + ",'"+sourcelist[count]+"'").
In Solution Explorer, right click on Models folder > Add > Class > Name your class. Now, we are going to create a Controller. Right click on the Controllers folder > Add > Controller> selecting Web API 2 OData v3 Controller with actions, using Entity Framework > click Add.
OData expand functionality can be used to query related data. For example, to get the Course data for each Enrollment entity, include ?$ expand=course at the end of the request path: This tutorial uses Postman to test the web API.
Assuming you're using OData V4 you need to use CollectionParameter
when registering the function and you're missing the [FromODataUri]
on the arrayHere parameter. Also, try it with IEnumerable<int>
instead of an array.
var builder = new ODataConventionModelBuilder();
builder.Namespace = "NS";
builder.EntitySet<TestEntity>("TestEntities");
builder.EntityType<TestEntity>().Collection
.Function("TestFunction")
.ReturnsCollectionFromEntitySet<TestEntity>("TestEntities")
.CollectionParameter<int>("ArrayHere");
With the function in the controller like this...
[HttpGet]
public async Task<IHttpActionResult> TestFunction([FromODataUri] IEnumerable<int> ArrayHere)
{
// Do stuff
}
Now you can make a request such as...
http://yourRestService/API/TestEntities/NS.TestFunction(ArrayHere=[1,2,3])
As a note, you can also accept an array of complex types as well. You'll have to url encode the json for the array and use a parameter alias so you'll end up with something like this...
builder.EntityType<TestEntity>().Collection
.Function("TestFunction2")
.ReturnsCollectionFromEntitySet<TestEntity>("TestEntities")
.CollectionParameter<person>("ArrayHere");
and
[HttpGet]
public async Task<IHttpActionResult> TestFunction2([FromODataUri] IEnumerable<person> ArrayHere)
{
// Do stuff
}
http://yourRestService/API/TestEntities/NS.TestFunction2(ArrayHere=@ArrayData)?@ArrayData=%5B%7B%22FirstName%22%3A%22Bob%22%2C+%22LastName%22%3A%22Dole%22%7D%2C%7B%22FirstName%22%3A%22Bill%22%2C+%22LastName%22%3A%22Clinton%22%7D%5D
I don't know which version of OData you're currently using, but here's a solution I've implemented which works for v4 and passing in simple arrays to an OData Action
Method
In your WebConfig.cs
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<TestEntity>("TestEntities");
builder.Namespace = "NS";
builder.EntityType<TestEntity>()
.Action("TestAction")
.CollectionParameter<string>("ArrayHere");
And your controller method here
[HttpPost]
public async Task<IHttpActionResult> TestAction([FromODataUri] int key, ODataActionParameters parameters)
{
// Do your thing here..
var invites = parameters["ArrayHere"] as IEnumerable<string>;
}
The resulting API endpoint request should look something like the following:
POST http://localhost/odata/TestEntities(1)/NS.TestAction HTTP/1.1
Content-type: application/json
Host: localhost:49255
Content-Length: 37
{"ArrayHere":["hello", "world"]}
I went down this particular problem a few times and couldn't make it work for an OData function. I dug a little bit deeper and found the CollectionParameter<>()
method which sorted everything out. I didn't go back and try it with a Function
as I didn't really see the need. I hope this helps you out
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With