1

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 Answers 3

8

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

Sign up to request clarification or add additional context in comments.

2 Comments

Does my answer help you or it don't solves your problem? Otherwise, I appreciate if you accept my answer.
This is a well described solution, my response would have been very similar, this is a common issue that many devs new to OData v4 encounter
1

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!

2 Comments

This is done my job, I should add an extra info, if your custom parameter starts with $ sign, somehow odata interperet that it is own parameter and exception occured. So, not start with $ sign has any problem. And I don't have List<T> Get() method parameter. Directly using System.Web.Http.Request had enough. string value = Request.RequestUri.ParseQueryString().Get(<paramname>); ParseQueryString() method is an extension method in System.Net.Http namespace.
Good note Hamit, that definitely seems like a good rule - $ prefix is for oData only. For getting the parameter, I see you're correct, you can use Request directly not needing to go through the oData options. I've edited my post for that, thanks.
0

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)
    {
    }
    }
    

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.