goroth
goroth

Reputation: 2610

OData Unbound function send and receive primitive collection

Need an example on how to pass a collection of primitive type to an unbound function /or action and also how to return a collection of primitive type.
Such as a list or array of integers.
Here is a simple example.

List<int> GetEvenNumbers(List<int> numbers)
{
// loop through numbers collection and return a list of the even numbers
}

The following sites talk about using function / actions but does not explain passing / receiving collections.

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/odata-actions-and-functions

https://aspnet.codeplex.com/sourcecontrol/latest#Samples/WebApi/OData/v4/

I even posted a suggestion on the "Show Me How With Code" but have not received a response.
http://aspnet.uservoice.com/forums/228522-show-me-how-with-code/suggestions/6264729-odata-v4-passing-collection-as-parameter-to-unbou

Here is some code that I have but it does not seem to work.

// in the controller
[HttpGet]
[ODataRoute("GetEvenNumbers(numbers={numbers})")]
public IHttpActionResult GetEvenNumbers(List<int> numbers)
{
    List<int> evenNumbers = new List<int>();
    foreach (var number in numbers)
    {
        if (number % 2 == 0)
        {
            evenNumbers.Add(number);
        }
    }
    return Ok(evenNumbers);
}

// in the WebApiConfig
var testCollectionFunction = builder.Function("GetEvenNumbers");
testCollectionFunction.CollectionParameter<int>("numbers");
testCollectionFunction.ReturnsCollection<int>();

In WCF this was very simple but in OData it is not so simple.

Upvotes: 7

Views: 8553

Answers (3)

9Rune5
9Rune5

Reputation: 463

As Cyrus' answer says, an OData function can both return collections as well as take them as parameters.

In my case I have a function called "MostPopular", defined as:

        var mostPopFunc = svcHist.Collection.Function("MostPopular");
        mostPopFunc.Parameter<DateTime>("From");
        mostPopFunc.CollectionParameter<ServiceCodes>("ServiceCodes");
        mostPopFunc.CollectionParameter<int>("ServiceUnits");
        mostPopFunc.Parameter<string>("SearchCCL");
        mostPopFunc.Parameter<int>("ListLength").OptionalParameter = true;
        mostPopFunc.ReturnsCollection<ciExternalPartnerPopularResult.MarcIdPopularity>();

Since this function is bound to an entity set, I do not need to provide a route mapping and the function declaration is whittled down to:

[HttpGet]
public async Task<IHttpActionResult> MostPopular([FromODataUri] DateTimeOffset from, [FromODataUri] IEnumerable<ServiceCodes> serviceCodes, 
                                                 [FromODataUri] IEnumerable<int> serviceUnits,
                                                 [FromODataUri] string searchCcl, [FromODataUri] int listLength = ListLengthDefault)
{ // ...
}

int and other simple types do not require the [FromODataUri] decoration, but I did not mind them that much. This function is a bit heavy considering the number of parameters, but I ran out of time to figure out a better interface for this one.

Finally, to invoke this from PostMan (or a browser), the GET request looks something like this:

{{url}}/ServiceHistories/Default.MostPopular(From=2016-10-07T12:41:59Z,ServiceCodes=['OnLoan'],ServiceUnits=[6471,6473],SearchCCL='TI+SE=Harry',ListLength=10)

Which forced me to modify my web.config because of the timestamp:

<system.web>
  <!-- This web service accepts datetimeoffset as a parameter and requires the ":" character to be supplied as part of the URL -->
  <httpRuntime targetFramework="4.5" requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,\,?" />
</system.web>

Upvotes: 0

Cyrus Downey
Cyrus Downey

Reputation: 223

So I wanted to provide and update to this post. I'm playing around with OData v4 Using Web API 2.2. I was able to get parameters passed to an unbound function.

Here is the builder code in my Register method.

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");

//other stuff here
builder.Function("EmployeesWithParameters")
       .ReturnsCollectionFromEntitySet<Employee>("Employees")
       .CollectionParameter<string>("Ids");

Here is the actual method call on my controller.

    [HttpGet]
    [ODataRoute("EmployeesWithParameters(Ids={Ids})")]
    public IQueryable<Employee> EmployeesWithParameters([FromODataUri]string[] Ids)
    {
        IQueryable<Employee> result = db.Employees.Where(p => Ids.Contains(p.EmployeeId) );

        return result; ;
    }

Upvotes: 4

Tan Jinfu
Tan Jinfu

Reputation: 3345

As far as I know, webapi odata does not allow collections in Functions parameters. But you can use Actions as a workaround.

Model builder:

        var testCollectionFunction = modelBuilder.Action("GetEvenNumbers");
        testCollectionFunction.CollectionParameter<int>("numbers");
        testCollectionFunction.ReturnsCollection<int>();

Controller:

    [HttpPost]
    [ODataRoute("GetEvenNumbers")]
    public IHttpActionResult GetEvenNumbers(ODataActionParameters parameter)
    {
        IEnumerable<int> numbers = parameter["numbers"] as IEnumerable<int>;
        List<int> evenNumbers = new List<int>();
        foreach (var number in numbers)
        {
            if (number % 2 == 0)
            {
                evenNumbers.Add(number);
            }
        }
        return Ok(evenNumbers);
    }

Request:

POST http://localhost:44221/odata/GetEvenNumbers HTTP/1.1
Host: localhost:44221
Connection: keep-alive
Content-Length: 17

{"numbers":[1,2]}

Response:

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=full
OData-Version: 4.0
Content-Length: 109

{
  "@odata.context":"http://localhost:44221/odata/$metadata#Collection(Edm.Int32)","value":[
    2
  ]
}

Upvotes: 5

Related Questions