ASP.NET Odata Web API Composable function issue

1.2k views Asked by At

I am building a Service Layer in Web API OData that exposes a file management API. I have a problem with composable functions. Consider the following scenario. Particular files can be accessed in two ways: through an ID or through a complex Path. My original design concept was to have two URLS:

  • /File({IdAsGuid})
  • /Repositories({RepositoryName})/Libraries({libName})/Path({path})/api.getFileByName(name={fileName})

This worked pretty well using the ODataRoute attributes. The next step was to support versions, which would use URL's like:

  • /File({IdAsGuid})/Versions({versionNumber})
  • /Repositories({RepositoryName})/Libraries({libName})/Path({path})/api.getFileByName(name={fileName})/Versions({versionNumber})

Using an EntitySet "Versions" as a path segment was no problem or the first URL. However, OData refused to validate the EntitySet used after the function call. The error:

The segment 'eBesNg.getContentByName' must be the last segment in the URI because it is one of the following: $ref, $batch, $count, $value, $metadata, a named media resource, an action, a noncomposable function, an action import, a noncomposable function import, an operation with void return type, or an operation import with void return type.

After some research, I realized that the function is defined as follows:

builder.Namespace = "api";
var function = builder.EntityType<Path>().Function("getFileByName");
function.Parameter<string>("name");
function.ReturnsFromEntitySet<File>("Files");

And may additionally require:

function.IsComposable = true;

However, this created a different issue. Now, during the OData validation, I receive a NullReferenceException:

[NullReferenceException: Object reference not set to an instance of an object.]
Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreatePropertySegment(ODataPathSegment previous, IEdmProperty property, String queryPortion) +205
Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreateNextSegment(String text) +405
Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.ParsePath(ICollection'1 segments) +244
Microsoft.OData.Core.UriParser.Parsers.ODataPathFactory.BindPath(ICollection'1 segments, ODataUriParserConfiguration configuration) +96
Microsoft.OData.Core.UriParser.ODataUriParser.ParsePathImplementation() +205

What am I missing? Is it not possible to use functions for navigation and continue to navigate on results in OData?

1

There are 1 answers

0
Karata On

You should properly set the EntitySetPath of your function. That is, replace:

function.ReturnsFromEntitySet<File>("Files");

With

function.ReturnsEntityViaEntitySetPath<File>("bindingParameter/xxx");

Here is a complete sample:

class Path
{
    public string Id { get; set; }
    public File File { get; set; }
}

class File
{
    public Guid Id { get; set; }
    public ICollection<Version> Versions { get; set; }
}

class Version
{
    public string Id { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var builder = new ODataConventionModelBuilder();
        builder.Namespace = "api";
        builder.EntityType<File>();
        var function = builder.EntityType<Path>().Function("getFileByName");
        function.Parameter<string>("name");
        //function.ReturnsFromEntitySet<File>("Files");
        function.ReturnsEntityViaEntitySetPath<File>("bindingParameter/File");
        function.IsComposable = true;
        builder.EntitySet<Path>("Paths");
        builder.EntitySet<Version>("Versions");
        var model = builder.GetEdmModel();

        string path = "Paths('1')/api.getFileByName(name='sd')/Versions('s')";
        var parser = new ODataUriParser(model, new Uri(path, UriKind.Relative));
        var pa = parser.ParsePath();
        Console.WriteLine(pa);
    }
}