13

I have a Web Api 2.2 project working with OData v4. The normal EntitySet configuration is working as desired with all http verbs. Where I am having a problem is trying to expose a custom function. I started off trying to do something different than the standard examples, but I have backed all the way up to just trying to getting a basic example function working.

Here is my startup config (straight from the MS examples):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;

namespace Test.Service
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {

            // other entitysets that don't have functions

            builder.EntitySet<Product>("Products");
            builder.Namespace = "ProductService";
            builder.EntityType<Product>().Collection
                .Function("MostExpensive")
                .Returns<double>();

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

And here is my controller:

using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;

namespace Test.Service.Controllers
{
    public class ProductsController : ODataController
    {
        private EntityContext db = new EntityContext();

        [EnableQuery]
        public IQueryable<Product> GetProducts()
        {
            return db.Products;
        }

        [HttpGet]
        public IHttpActionResult MostExpensive()
        {
            double test = 10.3;
            return Ok(test);
        }
    }
}

The regular GET, works fine:

http://something/odata/Products

However, the following always gives me a 404:

http://something/odata/Products/ProductService.MostExpensive()

I have tried any number of different things with the namespace, etc... So, it doesn't work like all of the examples, but I'm at a loss at how to dig in deeper to figure out what is going wrong. The metadata exposed by http://something/odata doesn't provide any clues. Is there any other way to discover where (and how) this function should be exposed?

EDIT: Here is the link to the Microsoft Example I am following: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/odata-actions-and-functions

snow_FFFFFF
  • 3,235
  • 17
  • 29
  • Just curious,have you tried: http://something/odata/Products/MostExpensive? – Andrea Scarcella Aug 14 '14 at 15:35
  • Yes, I've tried any number of different combinations: like odata/Products/MostExpensive, odata/Products/MostExpensive(), odata/Products/Default.MostExpensive() (the last one when I didn't explicitly set the namespace) – snow_FFFFFF Aug 14 '14 at 15:38
  • Thank you, I also noticed there is no response type attribute [ResponseType(typeof(decimal))]. – Andrea Scarcella Aug 14 '14 at 15:45
  • 1
    Thanks for the suggestion, but the ResponseType attribute doesn't seem to be available in this version of WebApi (or I am missing a using...). And, in all of the examples I have seen, this isn't necessary (I added a link to the MS example above). What is weird is that I can add an unbound function without issue, I just can't bind a function to an entity set. – snow_FFFFFF Aug 14 '14 at 16:23

3 Answers3

17

Please change the element as below, which is the recommended way if there is dot in the request URL:

 <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
 </system.webServer>

and if

http://something/odata/Products/ProductService.MostExpensive()

is requested, I can get the data:

{
@odata.context: "http://localhost:14853/odata/$metadata#Edm.Double",
value: 3
}
Tan Jinfu
  • 3,327
  • 1
  • 19
  • 20
  • 1
    I have a very similar looking line in my web.config: - the only different I see with yours if the path attribute. If I change it to yours, everything comes up with a 500 (internal server error) – snow_FFFFFF Aug 15 '14 at 20:41
  • Both path attribute and verb look suspicious. For verb, please use "*" and for path, if "/*" doesn't work for you, please try "*", and "*/odata/*". – Tan Jinfu Aug 16 '14 at 01:38
  • Sorry it took me so long to accept this answer, but I wasn't able to put the time into trying it out. The seems to have solved all of my problems....though I'm not sure why it wouldn't be there by default. Thank you! – snow_FFFFFF Sep 03 '14 at 21:39
  • 2
    This can be done without running all managed modules for every request (see link). Running all managed modules may be expensive and might cause errors: http://www.britishdeveloper.co.uk/2010/06/dont-use-modules-runallmanagedmodulesfo.html – Bvrce Jun 15 '15 at 08:38
7

I know this question is not recent, but I found another answer that works for me. If you're willing to remove the namespace from the URL, you can use

config.EnableUnqualifiedNameCall(true);

Your URL would then look like this:

http://something/odata/Products/MostExpensive

See http://odata.github.io/WebApi/#06-01-custom-url-parsing. That's available in the Microsoft.AspNet.OData NuGet package.

Becca Dee
  • 1,530
  • 1
  • 24
  • 51
  • You're quite welcome! The function isn't available in version 5.3.0 of that package (which is the version used by a sample solution I downloaded), but it is available in 5.7.0, so I'm guessing it wasn't around all those months ago. – Becca Dee Feb 02 '16 at 15:23
  • 1
    I could not get any `web.config` based solution working. This was preferable anyway since the namespace seems completely superfluous. – Luke Puplett Jun 21 '16 at 18:20
  • This extension method is not present in Microsoft.AspNet.OData nuget 6.1.0. A workaround can be found here https://github.com/OData/WebApi/blob/OData60/OData/test/E2ETest/WebStack.QA.Test.OData/UriParserExtension/UnqualifiedCallTest.cs – Victor Wilson Jan 30 '18 at 05:28
2

Then you may try adding the part not replacing them. Mine looks like below and it can work.

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  <clear/>
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*"
      verb="*" type="System.Web.Handlers.TransferRequestHandler"
      preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
QianLi
  • 1,088
  • 12
  • 22