Git Product home page Git Product logo

Comments (9)

commonsensesoftware avatar commonsensesoftware commented on May 24, 2024

Odd. My gut reaction is to make sure that this current routing works without API versioning. The API versioning does not alter the routing. Instead it resolves what would normal be duplicate, ambiguous routes by API version. If routes can't be disambiguated by API version, then the behavior is nascent of what happens out-of-the-box for a duplicate route.

Something that sticks out to me is that the example URL you provided doesn't appear to match your route template. Your example shows:

http://localhost:52900/v1.0-beta/candidates/10000/resumes

but I would expect it to be:

http://localhost:52900/v1.0-beta/candidates(10000)/resumes

based on your route template. I know you can change the entity key URL segment convention, but I thought the route template had to match when you do that.

It's also a long-shot, but to remove the obvious, I would consider having your routes and controller names match the standard OData convention, which is uppercase. To make things support lowercase, just enable that in the start up a la: configuration.EnableCaseInsensitive(true);.

If none of that helps or gets you in the right direction, I'll look into creating a repro. If you have a bare bones repro you can share with just the simplest Candidate model, Resume model, OData model builder setup, and relevant Web API configuration, I should be able to take it from there. I won't rule out a bug because I found making some of the OData stuff work tricky, but this feels like it could be a routing problem.

from aspnet-api-versioning.

silkroadscott avatar silkroadscott commented on May 24, 2024

Thanks for the reply. In troubleshooting this routing issue I may have stumbled on another. I'll build a bare bones repro and see how it behaves. I'll let you know what I find either way. Thanks again.

from aspnet-api-versioning.

silkroadscott avatar silkroadscott commented on May 24, 2024

I built as minimal of a repro as I could, including using all of the conventions you noted earlier. I still see both of the issues that I was seeing before. I'll attach the zip file with the repro to this comment. The repro contains two nearly identical projects: One is a versioned API and the other one is unversioned. I've tried to limit the differences between the two projects to just those absolutely necessary for versioning. Everything behaves as expected in the unversioned project, but the versioned project shows two issues:

The first is the one I described yesterday, in which the path /Candidates(id)/Resumes is not recognized as belonging to the version.

The second is one I did not report yesterday as I only noticed it once I started doing more in-depth troubleshooting. That issue is that the /Resource(id) path is not routing correctly. If you make a request to /Resource(id) it actually just hits the /Resource path. I first noticed it in my own project, then I loaded up the sample solution associated with this repository and found the same thing. Now I've confirmed it a third time in the repro.

For a specific example, if you run the AdvancedODataWebApiSample sample project and throw some breakpoints on the Get functions in the PeopleController, you'll find that whether you request /api/People or /api/People(2) it's hitting the same Get function (the one without an Id parameter) in both cases.

You can see the same thing in my repro if you spin up the VersionedApi project. Requests to /v1.0/Candidates or /v1.0/Candidates(2) both hit the same Get function, the one that does not include the Id parameter.

So, to summarize what you'll see in the repro, in the unversioned project:
/Candidates => routes successfully and correctly to Get()
/Candidates(2) => routes successfully and correctly to Get(int id)
/Candidates(2)/Resumes => routes successfully and correctly to GetResumes(int id)

But in the versioned project:
/Candidates => routes successfully and correctly to Get()
/Candidates(2) => routes successfully but INcorrectly to Get()
/Candidates(2)/Resumes => is not recognized as belonging to the version; fails to route

Hopefully that all makes sense, but let me know if I can clarify any points or provide anything else. I've done a little bit of stepping through the source code, but haven't found anything of interest yet. I'm sure you'll have a much better idea of where to look than I do, but I'll dig some more as time permits.

Thanks again.
ApiVersioningRepro.zip

from aspnet-api-versioning.

commonsensesoftware avatar commonsensesoftware commented on May 24, 2024

Thanks for the repro; it's immensely useful. I can confirm the behavior that you're seeing. I've seen something like this before. I thought maybe it was some quirkiness in the OData APIs and I just had my setup incorrect. Your repro seems to prove otherwise.

Workaround

The following should function as a workaround:

[ApiVersion( "1.0" )]
[ControllerName( "Candidates" )]
[ODataRoutePrefix( "Candidates" )]
public class CandidatesController : ODataController
{
    public IHttpActionResult Get() => Ok( new[] { new Candidate() { Id = 1, Name = "John Doe" } } );

    [ODataRoute( "({key})" )]
    public IHttpActionResult Get( int key ) => Ok( new Candidate() { Id = key, Name = "Jane Doe" } );

    [ODataRoute( "({key})/Resumes" )]
    public IHttpActionResult GetResumes( int key ) => Ok( new[] { new Resume() { Id = 100, Name = "Resume ABC" } } );
}

Analysis

The issue occurs during action selection. The routing is correct, but the action selection is not. I can't explain why this works - yet, but I was able to get the expected results by making this change. The code for action selection is forked from the original ApiControllerActionSelector. Unfortunately, the class was not very extensible so my only option was to fork the code. The differences between the two are quite minimal. The main difference is rationalizing multiple candidate actions, potentially from different controllers so that a 400 is properly returned when an action could match, but doesn't for a particular API version. I've painfully re-examined the original and forked versions of the code line-by-line, but I didn't uncover any differences that would result in the behavior we're seeing.

It appears that the reason the action isn't matching as a candidate is that the route parameters do not match up. OData seems to always assume that the route parameter name is key for the entity key value segment by default. If you use a different route parameter name - say id, it magically gets transformed - somewhere. I haven't been able to figure out where that is happening yet and why it happens when a controller is unversioned, but not when API versioning is applied. Since all of the route selection, controller selection, and action selection happen in the Web API layer, I'm assuming that any transformation of route parameter names and/or values happen in the OData layer before it's handed down. It's unclear to me at this point where I would have broken this behavior, but clearly this process is not happening.

I will continue investigating as time allows. As soon as I can discover the cause, I'll escalate this issue to a bug with a proper description. In the meantime, the workaround should unblock you. Thanks for helping localize the issue. If you uncover anything else, please share.

from aspnet-api-versioning.

silkroadscott avatar silkroadscott commented on May 24, 2024

Thanks for the update and for the suggested workaround. I'll give that a try this afternoon and let you how it goes or if I uncover anything else.

from aspnet-api-versioning.

commonsensesoftware avatar commonsensesoftware commented on May 24, 2024

I'm 99% sure I found the problem. It is a bug and I'm tracking it in issue #40. The good news is that as far as I can tell the fix is really simple. I'm going to run a full regression pass and if that works, I should be able to get a release out soon.

from aspnet-api-versioning.

commonsensesoftware avatar commonsensesoftware commented on May 24, 2024

I've tracked down the issue, resolved it, and published out a new version of the package. The changes can be seen in PR #41. I've published version 2.0.1 of the package. In addition to the test suites, I also vetted it against your repro. Everything seems to be good. Give it a spin and let me know things work as expected for you. I'll leave this issue open until you confirm things are working on your side.

from aspnet-api-versioning.

commonsensesoftware avatar commonsensesoftware commented on May 24, 2024

As a side follow-up, I thought I would mention that you can enable lowercase support and roll your EDM model extensions into the VersionedODataModelBuilder like so:

configuration.EnableCaseInsensitive( true );
configuration.EnableUnqualifiedNameCall( true );

var modelBuilder = new VersionedODataModelBuilder( configuration )
{
    ModelBuilderFactory = () => new ODataConventionModelBuilder().EnableLowerCamelCase(),
    ModelConfigurations =
    {
        new CandidateModelConfiguration(),
        new ResumeModelConfiguration()
    },
    OnModelCreated = ( builder, model ) =>
    {
        var container = model.EntityContainer;
        var candidatesEntitySet = (EdmEntitySet) container.FindEntitySet( "Candidates" );
        var candidateEntityType = (EdmEntityType) model.FindDeclaredType( typeof( Candidate ).FullName );
        var resumesEntitySet = (EdmEntitySet) container.FindEntitySet( "Resumes" );
        var resumeEntityType = (EdmEntityType) model.FindDeclaredType( typeof( Resume ).FullName );
        var resumeProperty = new EdmNavigationPropertyInfo();

        resumeProperty.TargetMultiplicity = EdmMultiplicity.Many;
        resumeProperty.Target = resumeEntityType;
        resumeProperty.ContainsTarget = false;
        resumeProperty.OnDelete = EdmOnDeleteAction.None;
        resumeProperty.Name = "Resumes";
        candidatesEntitySet.AddNavigationTarget( candidateEntityType.AddUnidirectionalNavigation( resumeProperty ), resumesEntitySet );
    }

};
var models = modelBuilder.GetEdmModels();

The OnModelCreated callback is invoked for each EDM model generated by GetEdmModels. Your approach isn't wrong, but this will allow you to wrap all of the model building activities in the builder if you want. I use this hook point to support media resources and instance annotations extensions combined with API versioning from my More.OData library.

Cheers!

from aspnet-api-versioning.

silkroadscott avatar silkroadscott commented on May 24, 2024

Phenomenal! I just gave my original project a whirl with the new package and it appears to be working like a charm! Thanks a lot for your help and the quick turnaround on the new version of the package.

from aspnet-api-versioning.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.