1
votes

I am trying to create a odata api endpoint using odata-4(inside web api 2.2 project), where I need to have one of my GET action in odata controller to accept multiple string parameters(they are custom search properties apart from properties in my entity for which odata controller is created).

But with all the trials i have done so far, i always get into one or other error while accessing the specific action in browser. and so far I haven't been able to get a working combination of flow/syntax hence sharing query here to get suggestion on either how to achieve passing multiple param to odata action in odata-4 OR how to fix error coming for me.

Code as follow:

package.config:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.AspNet.Mvc" version="5.1.2" targetFramework="net45" />
  <package id="Microsoft.AspNet.OData" version="5.3.1" targetFramework="net45" />
  <package id="Microsoft.AspNet.Razor" version="3.1.2" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.OData" version="5.1.2" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebPages" version="3.1.2" targetFramework="net45" />
  <package id="Microsoft.Data.Edm" version="5.6.0" targetFramework="net45" />
  <package id="Microsoft.Data.OData" version="5.6.0" targetFramework="net45" />
  <package id="Microsoft.OData.Core" version="6.5.0" targetFramework="net45" />
  <package id="Microsoft.OData.Edm" version="6.5.0" targetFramework="net45" />
</packages>

WebApiConfig:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();

            ODataModelBuilder builder = new ODataConventionModelBuilder();

            builder.EntitySet<DocumentsModel>("SampleData");

            var function = builder.Function("SampleFunction");
            function.Parameter<string>("catGUIDOrText");
            function.Parameter<string>("type");
            function.Parameter<string>("isAutoCompSearch");
            function.ReturnsCollectionFromEntitySet<DocumentsModel>("SampleData");

            config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

Odata Controller:

[ODataRoutePrefix("SampleData")]
    public class SampleDataController : ODataController
    {
        [EnableQuery]
        [HttpGet]
        [ODataRoute("SampleFunction(catGUIDOrText={catGUIDOrText},type={type},isAutoCompSearch={isAutoCompSearch})")]
        public IEnumerable<DocumentsModel> GetSampleData([FromODataUri] string catGUIDOrText, [FromODataUri] string type, [FromODataUri] string isAutoCompSearch)
        {
            return new List<DocumentsModel>().AsQueryable<DocumentsModel>();
        }
    }

Note: - DocumentsModel is a class with all string properties.

Error details

Now while accessing this action in browser via below URL, i am getting error:

URL: http://localhost/VirtualDirectoryNameInIIS/odata/SampleData/SampleFunction(catGUIDOrText='752',type='230',isAutoCompSearch='false')

Error which i am getting:

The path template 'SampleData/SampleFunction(catGUIDOrText={catGUIDOrText},type={type},isAutoCompSearch={isAutoCompSearch})' on the action 'GetSampleData' in controller 'SampleData' is not a valid OData path template. The request URI is not valid. Since the segment 'SampleData' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource.

Please help me with any inputs you may have around this, either in code or in url i am using to access the given method. Thanks.

Some references i followed for resolving this or to ensure i am following right direction/syntax:

  1. Web API and OData- Pass Multiple Parameters

  2. https://damienbod.com/2014/06/13/web-api-and-odata-v4-queries-functions-and-attribute-routing-part-2/

3

3 Answers

6
votes

Edit the WebApiConfig.cs adding an explicit namespace

        builder.Namespace = "MyNamespace";
  • if you don't explicit it, the default namespace is "Default" :)

Edit the WebApiConfig.cs changing the function declaration

        FunctionConfiguration function = builder.EntityType<DocumentsModel>().Collection.Function("SampleFunction");
        function.Parameter<string>("catGUIDOrText");
        function.Parameter<string>("type");
        function.Parameter<string>("isAutoCompSearch");
        function.ReturnsCollectionFromEntitySet<DocumentsModel>("SampleData");
  • with ".EntityType()" you target the controller of entity DocumentsModel, that you have named "SampleData"
  • with ".Collection" you target the entity's collection; otherwise if you omit, you target a single entity

Change the Odata Controller

//[ODataRoutePrefix("SampleData")]
public class SampleDataController : ODataController
{
    //[ODataRoute("SampleData/MyNamespace.SampleFunction(catGUIDOrText={catGUIDOrText},type={type},isAutoCompSearch={isAutoCompSearch})")]
    [EnableQuery]
    [HttpGet]
    public IHttpActionResult SampleFunction(string catGUIDOrText, string type, string isAutoCompSearch)
    {
        return new List<DocumentsModel>().AsQueryable<DocumentsModel>();
    }
}
  • You can comment [ODataRoutePrefix("SampleData")]: the naming convention's routing is entitySetNameController, and you set it on WebApiConfig with "builder.EntitySet("SampleData")"
  • The return type can't be a IEnumerable; if you use IHttpActionResult you'll never have problems.
  • You can comment ODataRoute, if you use the naming convention

Change the url adding the odata namespace

http://localhost/VirtualDirectoryNameInIIS/odata/SampleData/MyNamespace.SampleFunction(catGUIDOrText='752',type='230',isAutoCompSearch='false')


In your code you had declared an Unbound Function, but you was calling like a Bound Function

You can find the info you needed in this tutorial

1
votes

I'm trying to achieve something similar but I use ODataQueryOptions directly so my controller is like this:

[HttpGet]
public ODataList<DTO> Get(ODataQueryOptions<POCO> options) {
    using (var db = new Entities()) {
        var param = Request.RequestUri.ParseQueryString().Get("CompanyCode");
        return _oData.Query<POCO, DTO>(options, db.POCO.AsQueryable());
    }
}

If I try to add other params in the signature I get errors so was trying to find a way to pass in a CompanyCode along with all the OData params and still have all the paging/filtering/sorting work.

This is working like a charm for me - I just add extra params to the request like:

api/DTO?CompanyCode=blah&$skip=50&$top=50

The .NET OData stuff seems to ignore my extra param and doesn't have a problem - and I just parse the param out manually instead of putting it in the function signature. Good enough for me!

0
votes

I am able to solve this by:-

  1. Add below code to startup.cs

    public void ConfigureServices(IServiceCollection services)
     {
        // your code
       services.AddMvc();
       services.AddOData();
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
     {
      // your code
      app.UseRouting();
       app.UseEndpoints(endpoints =>
        {
    
    endpoints.Select().Filter().OrderBy().Expand().Count().MaxTop(50);
            endpoints.MapODataRoute("api", "api", GetEdmModel());
        });
    }
    
    private IEdmModel GetEdmModel()
    {
        var edmBuilder = new ODataConventionModelBuilder();
        edmBuilder.EntitySet<Student>("Students");  
    
        var pullRequestsByProjectByContributor = edmBuilder.EntityType<Student>().Collection
    .Function("GetStudents")
    .ReturnsCollectionFromEntitySet<Student>("Students");
        pullRequestsByProjectByContributor.Parameter<int>("id").Required();
        pullRequestsByProjectByContributor.Parameter<int>("classId").Required();
    
    
        return edmBuilder.GetEdmModel();
    }
    
  2. In Controller

    [ODataRouting]
    [ODataRoutePrefix("students")] 
    public class StudentsController : ODataController
    {
    //http://localhost:5112/api/students/GetStudents(id=3,classId=6)?$orderby=id%20desc&$skip=1&$top=2 
    [HttpGet]
    [EnableQuery]        
    [ODataRoute("GetStudents(id={id},classId={classId})")]
    public async Task<IActionResult> GetStudents(int id, int classId, ODataQueryOptions<Student> options)
    {
    }
    }