Skip to content

OData API Explorer Should Support All Routing Conventions #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
commonsensesoftware opened this issue May 1, 2019 · 6 comments
Closed
Assignees
Milestone

Comments

@commonsensesoftware
Copy link
Collaborator

Overview

Today, the full set of OData routing conventions is not supported by the API Explorer. Generating proper OData route templates by inspecting compiled code is non-trivial. The common scenarios for entity sets, functions, actions, and navigation properties are currently supported. More complex scenarios such linking entities with $ref are not support - yet.

Workaround

Most, if not all, routes should be property discovered using attribute-based routing. The API Explorer does little to nothing in the way of modification. Since the route templates are user-defined, the correct route templates should be correctly generated.

References

Useful references that describe the expected conventions code must match can be found at:

@ben5Kuda
Copy link

Hello, I am trying to implement your samples AspNetCore + OData + Swagger + Api Versioning. The problem i have is with routing. I would like to be able to have routes like these v1/security/Users or v1/tracking/Areas the problem is that when i get the EDM models it brings back all EDM Models. This means i can not build a separate route based on the EDM model. For example

var models = modelBuilder.GetEdmModels();

routes.MapVersionedODataRoutes("security", "v{version:apiVersion}", models);

I would like to be able to get EDM models for a set of controllers(not all of them at once) and be able to add them to a specified route. I have tried atrribute routing and it only allows the RoutePrefix to match the controller name e.g. [ODataRoutePrefix("Users")]. if i try [ODataRoutePrefix("/security/Roles")] it will return a 404. Please assist

@commonsensesoftware
Copy link
Collaborator Author

@ben5Kuda your question isn't exactly specific to this issue, but let me answer your question. There are two ways this can be achieved:

  1. Build the EDM yourself and use MapVersionedODataRoute(name, prefix, model, apiVersion)
  2. Enumerate the EDMs by API version after they are built

The VersionedODataModelBuilder creates an EDM for every discovered API version. Each EDM is tagged with the ApiVersionAnnotation. To divide them up, you can match them with:

var models = modelBuilder.GetEdmModels();

foreach ( var model in models )
{
  var version = model.GetAnnotationValue<ApiVersionAnnotation>( model ).ApiVersion;

   if ( version == new ApiVersion( 1, 0 ) )
   {
        routes.MapVersionedODataRoute( "security", "v{version:apiVersion}", model, version );
   }

   // ... and so on
}

Of course, if you feel you know better, you can always build the EDM as normal and register the route with Option 1. The annotation is automatically applied during registration so you don't have to worry about matching it up.

I hope that helps.

@ben5Kuda
Copy link

ben5Kuda commented Jun 11, 2019

Thank you for your response, it has definitely helped a lot and my apologies for raising this issue on the the wrong thread. I have to admit i only saw your response today, however i have tried the 2 suggested approaches and it does appear that the routing only works per version. please see below

routes.MapVersionedODataRoute("tracking", "v{version:apiVersion}/tracking", trackingEdm, new ApiVersion(2, 0));

  routes.MapVersionedODataRoute("security", "v{version:apiVersion}/security", securityEdm, new ApiVersion(1, 0));

Here as you suggested, i am building two different routes and it works fine (building my own EdmModels). The issue i have is that the routing i need is on functional lines (I want to group a set of resources based on functionality such security or tracking - regardless of their version). That means Users for example falls under security module and subsequent versions will still be under security. From what i can tell the suggested routing would create a different route for potentially every version. So simply put is it possible to have different routes e.g v1/tracking and v1/security all with same version? I can only get it to work if it is v1/tracking and v2/security. please see some of my futile attempts below.

private IEdmModel BuildTracking(ODataModelBuilder builder)
{
  var xyz = builder.EntitySet<XYZ>("XYZ").EntityType;
  xyz.HasKey(k => k.Id);

  //var abc = builder.EntitySet<ABC>("ABC").EntityType;
  //abc.HasKey(k => k.Id);

  return builder.GetEdmModel();
}

private IEdmModel BuildSecurity(ODataModelBuilder builder)
{
var efg = builder.EntitySet("EFG").EntityType;
efg.HasKey(k => k.Id);

  var opq = builder.EntitySet<OPQ>("OPQ").EntityType;
  opq.HasKey(k => k.Id);

  return builder.GetEdmModel();
}

if controller XYZ is tagged with version 2.0 then it works (assuming all others are tagged v1). If i change XYZ to v1 along with everything else then i get this error: An unhandled exception occurred while processing the request.
ArgumentException: Duplicate type name within an assembly.
System.Reflection.Emit.ModuleBuilder.CheckTypeNameConflict(string strTypeName, Type enclosingType)

if i uncomment ABC (everything else remaining on v1) i get this error: KeyNotFoundException: The given key 'Microsoft.AspNet.OData.EdmTypeKey' was not present in the dictionary.
System.Collections.Generic.Dictionary<TKey, TValue>.get_Item(TKey key)
Microsoft.AspNet.OData.DefaultModelTypeBuilder.NewStructuredType(IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion, IEdmModel edmModel) in DefaultModelTypeBuilder.cs

I cant seem to find a way to separate routes except via a different version. I would really appreciate if is possible to separate the routes in a way that is version agnostic. Thank you.

@commonsensesoftware
Copy link
Collaborator Author

This may be because of your prefix. OData uses convention-based routing under the hood, which doesn't have a resolver for inline route constraints by key. This means that your prefix should be "v{apiVersion}/security" instead. This is different from attribute routing in non-OData controllers.

I should point out that you'll probably still have issues with the $metadata endpoint as described in #503 because you can only have one MetadataController per application. Defining an OData prefix was never meant to serve as two separate applications. It may have worked without versioning because there is 1:1 mapping between route and EDM, which is not the case with versioning.

@commonsensesoftware
Copy link
Collaborator Author

@Discjoggy, saw your message, but not sure why it doesn't show up in the issue. I didn't think there was such a thing as a private comment.

To restate the issue, the problem isn't OData or it's implementation supporting entity linking with $ref. This has everything to do with the API Explorer generating the URL.

OData provides zero functionality to build URLs. When you build the URLs yourself with attribute routing, things are pretty straight forward for the API Explorer. When you rely on the default OData conventions, the API Explorer has to build templates based on the EDM and method signature. As you can imagine, this is quite complicated and not all of the URL generation scenarios are covered today.

A few convention scenarios that are not covered:

  • Entity linking
  • Custom conventions
  • Raw value
  • Scalar value

I hope that helps clear things up.

@ben5Kuda
Copy link

Thank you, in the end i resolved to drop the route prefix and have everything under v{version:apiVersion} depending on the version and thank you for clearing things up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants