Skip to content

Updating to Microsoft.AspNetCore.Mvc.Versioning 4.2 causes Exception #694

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
EnthusedDragon opened this issue Nov 9, 2020 · 7 comments
Closed
Assignees

Comments

@EnthusedDragon
Copy link

EnthusedDragon commented Nov 9, 2020

Preconditions:
Microsoft.AspNetCore.Mvc.Versioning: 4.1.1
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer: 4.1.1
.NET Core 3.1

Extra Packages:
Microsoft.AspNetCore.Odata: 7.5.1
Microsoft.AspNetCore.OData.Versioning: 4.1.1
Microsoft.AspNetCore.OData.Versioning.ApiExplorer: 4.1.1

Description:
When updating to Microsoft.AspNetCore.Mvc.Versioning V4.2 my application stops working with the following: Exception thrown: 'System.AggregateException' in Microsoft.Extensions.DependencyInjection.dll

Am I missing something somewhere?

@commonsensesoftware commonsensesoftware self-assigned this Nov 9, 2020
@commonsensesoftware
Copy link
Collaborator

This is essentially the same configuration it was built with. What is the AggregateException.InnerException or better still AggregateException.BaseException()? It's not clear what the source is here. If you have a repro, that's would useful as well.

@bojanmisic
Copy link

bojanmisic commented Nov 14, 2020

I'm probably having the exact same issue. This is my stack trace:

System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Routing.LinkGenerator Lifetime: Singleton ImplementationType: Microsoft.AspNetCore.Mvc.Routing.ApiVersionLinkGenerator`1[Microsoft.AspNetCore.Routing.LinkGenerator]': A circular dependency was detected for the service of type 'Microsoft.AspNetCore.Routing.LinkGenerator'.
Microsoft.AspNetCore.Routing.LinkGenerator(Microsoft.AspNetCore.Mvc.Routing.ApiVersionLinkGenerator<Microsoft.AspNetCore.Routing.LinkGenerator>) -> Microsoft.AspNetCore.Routing.LinkGenerator)
 ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Routing.LinkGenerator Lifetime: Singleton ImplementationType: Microsoft.AspNetCore.Mvc.Routing.ApiVersionLinkGenerator`1[Microsoft.AspNetCore.Routing.LinkGenerator]': A circular dependency was detected for the service of type 'Microsoft.AspNetCore.Routing.LinkGenerator'.
Microsoft.AspNetCore.Routing.LinkGenerator(Microsoft.AspNetCore.Mvc.Routing.ApiVersionLinkGenerator<Microsoft.AspNetCore.Routing.LinkGenerator>) -> Microsoft.AspNetCore.Routing.LinkGenerator
 ---> System.InvalidOperationException: A circular dependency was detected for the service of type 'Microsoft.AspNetCore.Routing.LinkGenerator'.
Microsoft.AspNetCore.Routing.LinkGenerator(Microsoft.AspNetCore.Mvc.Routing.ApiVersionLinkGenerator<Microsoft.AspNetCore.Routing.LinkGenerator>) -> Microsoft.AspNetCore.Routing.LinkGenerator
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain.CheckCircularDependency(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, IServiceProviderEngine engine, ServiceProviderOptions options)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, IServiceProviderEngine engine, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
   at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()

I was migrating the project to net5.0 and as part of this I updated all other dependencies including this one to 4.2. Then I reverted all packages back, including project framework to netcoreapp3.1, and updated only this dependency from 4.1.1 to 4.2 and got the same exception.

Here is the list of installed packages for the solution:

   [netcoreapp3.1]:
   Top-level Package                                                 Requested   Resolved
   > AutoMapper.Extensions.Microsoft.DependencyInjection             8.0.1       8.0.1
   > HaemmerElectronics.SeppPenner.Serilog.Sinks.MicrosoftTeams      1.0.9       1.0.9
   > MediatR.Extensions.Microsoft.DependencyInjection                8.0.1       8.0.1
   > Microsoft.ApplicationInsights.AspNetCore                        2.12.0      2.12.0
   > Microsoft.AspNetCore.Authentication.JwtBearer                   3.1.5       3.1.5
   > Microsoft.AspNetCore.Mvc.NewtonsoftJson                         3.1.5       3.1.5
   > Microsoft.AspNetCore.Mvc.Versioning                             4.2.0       4.2.0
   > Microsoft.AspNetCore.OData                                      7.4.1       7.4.1
   > Microsoft.EntityFrameworkCore                                   3.1.5       3.1.5
   > Microsoft.EntityFrameworkCore.SqlServer                         3.1.5       3.1.5
   > Serilog.AspNetCore                                              3.4.0       3.4.0
   > Serilog.Enrichers.CorrelationId                                 3.0.1       3.0.1
   > Serilog.Exceptions                                              5.6.0       5.6.0
   > Serilog.Exceptions.EntityFrameworkCore                          5.6.0       5.6.0
   > Serilog.Sinks.ApplicationInsights                               3.1.0       3.1.0
   > Serilog.Sinks.MSSqlServer                                       5.5.1       5.5.1
   > Swashbuckle.AspNetCore                                          5.5.1       5.5.1
   > Swashbuckle.AspNetCore.Newtonsoft                               5.5.1       5.5.1

Project '***' has the following package references
   [netstandard2.1]: No packages were found for this framework.
Project '***' has the following package references
   [netstandard2.1]:
   Top-level Package                               Requested   Resolved
   > Microsoft.EntityFrameworkCore                 3.1.5       3.1.5
   > Microsoft.EntityFrameworkCore.Relational      3.1.5       3.1.5
   > Microsoft.EntityFrameworkCore.SqlServer       3.1.5       3.1.5

Project '***' has the following package references
   [netstandard2.1]:
   Top-level Package          Requested   Resolved
   > Azure.Storage.Blobs      12.4.4      12.4.4

Project '***' has the following package references
   [netstandard2.1]:
   Top-level Package                          Requested   Resolved
   > Microsoft.AspNetCore.Authorization       3.1.5       3.1.5
   > Microsoft.Extensions.Http                3.1.5       3.1.5
   > Microsoft.IdentityModel.Tokens           6.7.1       6.7.1
   > Newtonsoft.Json                          12.0.3      12.0.3
   > Serilog                                  2.10.0      2.10.0
   > Serilog.Sinks.ApplicationInsights        3.1.0       3.1.0
   > System.Diagnostics.DiagnosticSource      4.7.1       4.7.1
   > System.IdentityModel.Tokens.Jwt          6.7.1       6.7.1
   > System.Text.Json                         4.7.2       4.7.2

Project '***' has the following package references
   [netstandard2.1]:
   Top-level Package                                            Requested        Resolved
   > AutoMapper                                                 10.0.0           10.0.0
   > Microsoft.Extensions.Caching.Abstractions                  3.1.5            3.1.5
   > Microsoft.Extensions.DependencyInjection.Abstractions      3.1.5            3.1.5
   > Newtonsoft.Json                                            12.0.3           12.0.3
   > SixLabors.ImageSharp                                       1.0.0-beta0007   1.0.0-beta0007
   > System.IO.Packaging                                        4.7.0            4.7.0

Project '***' has the following package references
   [netcoreapp3.1]:
   Top-level Package                                            Requested   Resolved
   > AutoMapper.Extensions.Microsoft.DependencyInjection        8.0.1       8.0.1
   > coverlet.collector                                         1.3.0       1.3.0
   > MediatR.Extensions.Microsoft.DependencyInjection           8.0.1       8.0.1
   > Microsoft.EntityFrameworkCore.InMemory                     3.1.5       3.1.5
   > Microsoft.Extensions.Configuration.FileExtensions          3.1.5       3.1.5
   > Microsoft.Extensions.Configuration.Json                    3.1.5       3.1.5
   > Microsoft.Extensions.DependencyInjection.Abstractions      3.1.5       3.1.5
   > Microsoft.NET.Test.Sdk                                     16.6.1      16.6.1
   > MSTest.TestAdapter                                         2.1.2       2.1.2
   > MSTest.TestFramework                                       2.1.2       2.1.2
   > Serilog                                                    2.10.0      2.10.0
   > Serilog.Exceptions                                         5.6.0       5.6.0
   > Serilog.Exceptions.EntityFrameworkCore                     5.6.0       5.6.0
   > Serilog.Extensions.Logging                                 3.0.1       3.0.1
   > Serilog.Settings.Configuration                             3.1.0       3.1.0
   > Serilog.Sinks.Console                                      3.1.1       3.1.1
   > Serilog.Sinks.MSSqlServer                                  5.5.1       5.5.1

Project '***' has the following package references
   [netstandard2.1]:
   Top-level Package                                            Requested   Resolved
   > FluentValidation                                           9.0.1       9.0.1
   > MediatR                                                    8.0.2       8.0.2
   > Microsoft.AspNetCore.Cryptography.KeyDerivation            3.1.5       3.1.5
   > Microsoft.Extensions.DependencyInjection.Abstractions      3.1.5       3.1.5
   > Newtonsoft.Json                                            12.0.3      12.0.3

Project '***' has the following package references
   [netcoreapp3.1]:
   Top-level Package                              Requested   Resolved
   > Microsoft.EntityFrameworkCore                3.1.5       3.1.5
   > Microsoft.EntityFrameworkCore.Design         3.1.5       3.1.5
   > Microsoft.EntityFrameworkCore.SqlServer      3.1.5       3.1.5

Project '***' has the following package references
   [netcoreapp3.1]:
   Top-level Package                                          Requested   Resolved
   > AutoMapper.Extensions.Microsoft.DependencyInjection      8.0.1       8.0.1
   > MediatR.Extensions.Microsoft.DependencyInjection         8.0.1       8.0.1
   > Microsoft.ApplicationInsights.AspNetCore                 2.12.0      2.12.0
   > Microsoft.Azure.Functions.Extensions                     1.0.0       1.0.0
   > Microsoft.NET.Sdk.Functions                              3.0.9       3.0.9
   > Serilog                                                  2.10.0      2.10.0
   > Serilog.Exceptions                                       5.6.0       5.6.0
   > Serilog.Exceptions.EntityFrameworkCore                   5.6.0       5.6.0
   > Serilog.Extensions.Logging                               3.0.1       3.0.1
   > Serilog.Settings.Configuration                           3.1.0       3.1.0
   > Serilog.Sinks.Console                                    3.1.1       3.1.1
   > Serilog.Sinks.MSSqlServer                                5.5.1       5.5.1

Project '***' has the following package references
   [netstandard2.1]:
   Top-level Package                   Requested   Resolved
   > AutoMapper                        10.0.0      10.0.0
   > System.ServiceModel.Duplex        4.7.0       4.7.0
   > System.ServiceModel.Http          4.7.0       4.7.0
   > System.ServiceModel.NetTcp        4.7.0       4.7.0
   > System.ServiceModel.Security      4.7.0       4.7.0

Project '***' has the following package references
   [netstandard2.1]:
   Top-level Package                Requested   Resolved
   > Microsoft.Graph                3.8.0       3.8.0
   > Microsoft.Identity.Client      4.16.1      4.16.1

Let me know if you need more info! Thanks!

@jonasaebersold
Copy link

jonasaebersold commented Nov 16, 2020

I have the same issue in a asp.net core 3.1 project with the version 4.2.0. ApiVersionLinkGenerator is registered as LinkGenerator and it has a LinkGenerator constructor parameter - which makes it circular, or did i miss something?

@commonsensesoftware
Copy link
Collaborator

@bojanmisic unless you are using the preview version, net5.0 is not officially supported by 4.2.0.

@jonasaebersold - yes, you missed something 😉 . ASP.NET Core already registers a LinkGenerator implementation by way of DefaultLinkGenerator.cs. There are two systemic issues. First, this class is internal so it cannot be inherited or otherwise decorated. Two, the ApiVersionLinkGenerator provides very lightweight decoration to insert ambient route values. It should decorate versus completely replace the default LinkGenerator implementation because that will break things if someone has already replaced it with a custom implementation.

All,

Can anyone share their startup configuration for service registration? If you're seeing this behavior, then 2 things are likely true:

  1. You're versioning by URL segment (ApiVersionLinkGenerator is not required in any other scenario)
  2. You've invoked .AddApiVersioning() before MVC via .AddMvcCore(), .AddMvc(), or .AddControllers()

Order is a bit tricky and, in general, should not matter, but in this specific combination, the URL segment method requires that the core MVC services have been registered because that's where the default LinkGenerator is registered. I suspect this is the case. This was hit in the RC, but I thought I had fixed it for good. =/

The good news is, that if I'm correct, then this is something you can easily fix in your configuration. If confirmed, I should be able to repro the scenario. At that time, I'll promote this to bug to be fixed in the next patch, but at least you'll have a lasting workaround. At least in my mind, MVC should always logically be the first set of services registered.

@jonasaebersold
Copy link

@commonsensesoftware Thanks for your response.

I tried to create a small sample project - and it worked like a charm. So i added more stuff from our project and after all, in my case it's the "AddOData" extension method. If the method is called before "AddApiVersioning", it's not working. I add a sample. If AddOData is called before, the resolved LinkGenerator is of type ApiVersionLinkGenerator, and the other way the type is ODataEndpointLinkGenerator. We use OData in our project to use filter expressions.

With the version 4.1.1, the order doesn't matter. But I'm not sure, if the bug comes from the OData package or the ApiVersioning.

ApiVersioningIssue.zip

@commonsensesoftware
Copy link
Collaborator

@jonasaebersold thanks for the repro. As I suspected, this seems to be caused by service registration order. Things are even messier with OData because it registers it's own ODataEndpointLinkGenerator, which is yet again internal 😞 . Worse still, it puts itself in front of the default implementation so there is no way for the existing ApiVersionLinkGenerator to work its magic. This means the OData API Versioning extensions have to decorate this LinkGenerator again (and is also why .EnableApiVersioning() is required). To visualize that, the graph looks like:

ApiVersionLinkGenerator<ODataEndpointLinkGenerator>
→ ODataEndpointLinkGenerator
→ ApiVersionLinkGenerator<DefaultLinkGenerator>
→ DefaultLinkGenerator

ApiVersionLinkGenerator<T> is just ApiVersionLinkGenerator with a closed typed so that DI can register it with the concrete implementation of LinkGenerator (which should remove ambiguity or cicular dependencies). Unfortunately, this means that the order of registration may matter. I see that your repo did not specifically version by URL segment so I've clearly missed a setup configuration. This behavior is only supposed to be required for that scenario and with sensible defaults if you register things in an unexpected order (as can be seen here and here). While things might not behave as expected, it's not supposed to fail with an exception.

You may be wondering why this is even necessary. Numerous developers expect the API version route parameter to be magically filled in when they version by URL segment. I really don't know why, but they do. This ambient value is not known to the routing system and all possible route values are not considered in URL generation. Since the name of the API version route parameter is user-defined (but typically apiVersion), even API Versioning doesn't know the name until the ApiVersionRouteConstraint is evaluated. After a long time (years?) of developers getting hung up on this issue, I uncovered that I could probably handle it by providing the ambient values in a decorated IUrlHelperFactory / IUrlHelper combination and/or LinkGenerator. Unfortunately, solving that problem has led to this problem due to the required sequencing.

For clarity, the following should be your service registration as it relates to API versioning:

services.AddControllers(); // or services.AddMvcCore()
services.AddApiVersioning();
services.AddOData().EnableApiVersioning(); // if using OData
services.AddODataApiExplorer(); // if using OData
// services.AddVersionedApiExplorer(); // if NOT using OData
// TODO: remaining setup

Every API Versioning sample project is setup this way and all wiki documentation also show it that way.

I'll investigate further in to ensuring that an exception is not thrown; however, you should aware that if you do version by URL segment or intend to (but please don't), then you'll want to register things in the expected sequence. If you don't, you may see that URL generation is wrong if you do not explicitly provide the API version route value. As I recall, in the of case of OData, it does not currently consider other ambient route values so link generation will be wrong in responses.

@commonsensesoftware
Copy link
Collaborator

I know it's been a long time, but here's a quick update on this issue. Registering OData first causes a couple of bugs.

Issue 1

The OData implementation replaces the LinkGenerator with an implementation factory and not a concrete type. In order to decorate (and get in front of) the factory method, API Versioning needs to make a special concession if you register things in this order. Although unlikely, this could happen as a result of other replacements/updates to the LinkGenerator. While unexpected, it was pretty easy to solve.

Issue 2

This was much, much harder to track down. The short version is that despite OData supporting Endpoint Routing, it still uses IActionSelector behind the scenes in 7.x (yes - it's insane). The IActionSelector has been a bane in API Versioning until Endpoint Routing replaced it. The IActionSelector service is always added so TryAdd cannot be used; Replace is the only option. The normal, expected sequence is:

  • services.AddControllers()services.TryAdd<IActionSelector, ActionSelector>()
  • services.AddApiVersioning()services.Replace<IActionSelector, ApiVersionActionSelector>()
  • services.AddOData()services.Replace<IActionSelector, ODataActionSelector>()
  • services.AddOData().EnableApiVersioning()services.Replace<IActionSelector, ODataApiVersionActionSelector>()

The very reason that EnableApiVersioning() is chained to the IODataBuilder is to try and prevent this problem. By switching the order of services registration, things can break. Specifically, if you call services.AddOData().EnableApiVersioning() and then services.AddApiVersioning(), this will cause the ApiVersionActionSelector to be used instead of ODataApiVersionActionSelector.

Service order registration is not supposed to matter, but it can have significant side effects in this case. Registering things in a sensible order will help, but I believe I've devised a way for people to avoid this landmine in the future. This should be a non-issue in OData 8+, but that remains to be seen. Expect the fix in the next release. Until then, the recommendation is to register things the order provided by the examples.

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

4 participants