SDammann.WebApi.Versioning
SDammann.WebApi.Versioning copied to clipboard
Attribute routing
See issue: https://github.com/Sebazzz/SDammann.WebApi.Versioning/issues/25
I'm unable to get the versioning working with attribute routing. The code ends with following exception:
The given key was not present in the dictionary.
System.Collections.Generic.KeyNotFoundException
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.FindActionMatchRequiredRouteAndQueryParameters(IEnumerable`1 candidatesFound)
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.FindMatchingActions(HttpControllerContext controllerContext, Boolean ignoreVerbs)
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)
at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)
at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
Please find a sample project here: WebApiVersioning.zip
Thanks for the clear repro. I will take a look.
Quick dump of whát is happening here:
We're crashing in Web API itself. In the dictionary lookup:
private List<CandidateActionWithParams> FindActionMatchRequiredRouteAndQueryParameters(IEnumerable<CandidateActionWithParams> candidatesFound)
{
List<CandidateActionWithParams> matches = new List<CandidateActionWithParams>();
foreach (var candidate in candidatesFound)
{
HttpActionDescriptor descriptor = candidate.ActionDescriptor;
if (IsSubset(_actionParameterNames[descriptor], candidate.CombinedParameterNames))
{
matches.Add(candidate);
}
}
return matches;
}
Dictionary contents (1) opposed to requested key(2):
System.Linq.Enumerable.First(this.Keys)
{System.Web.Http.Controllers.ReflectedHttpActionDescriptor}
ActionBinding: {System.Web.Http.Controllers.HttpActionBinding}
ActionName: "Test"
Configuration: {System.Web.Http.HttpConfiguration}
ControllerDescriptor: {System.Web.Http.Controllers.HttpControllerDescriptor}
MethodInfo: {System.String Test()}
ParameterInfos: {System.Reflection.ParameterInfo[0]}
Properties: Count = 1
ResultConverter: {System.Web.Http.Controllers.ValueResultConverter<string>}
ReturnType: {Name = "String" FullName = "System.String"}
SupportedHttpMethods: Count = 1
_actionBinding: {System.Web.Http.Controllers.HttpActionBinding}
_actionExecutor: ThreadSafetyMode=ExecutionAndPublication, IsValueCreated=false, IsValueFaulted=false, Value=null
_actionName: "Test"
_attributeCache: {object[2]}
_configuration: {System.Web.Http.HttpConfiguration}
_controllerDescriptor: {System.Web.Http.Controllers.HttpControllerDescriptor}
_converter: {System.Web.Http.Controllers.ValueResultConverter<string>}
_declaredOnlyAttributeCache: {object[2]}
_filterGrouping: null
_filterPipeline: ThreadSafetyMode=ExecutionAndPublication, IsValueCreated=false, IsValueFaulted=false, Value=null
_filterPipelineForGrouping: null
_methodInfo: {System.String Test()}
_parameterInfos: {System.Reflection.ParameterInfo[0]}
_parameters: ThreadSafetyMode=ExecutionAndPublication, IsValueCreated=true, IsValueFaulted=false, Value={System.Collections.ObjectModel.Collection<System.Web.Http.Controllers.HttpParameterDescriptor>}
_properties: Count = 1
_returnType: {Name = "String" FullName = "System.String"}
_supportedHttpMethods (System.Web.Http.Controllers.HttpActionDescriptor): Count = 0
_supportedHttpMethods: Count = 1
key
{System.Web.Http.Controllers.ReflectedHttpActionDescriptor}
ActionBinding: {System.Web.Http.Controllers.HttpActionBinding}
ActionName: "Test"
Configuration: {System.Web.Http.HttpConfiguration}
ControllerDescriptor: {System.Web.Http.Controllers.HttpControllerDescriptor}
MethodInfo: {System.String Test()}
ParameterInfos: {System.Reflection.ParameterInfo[0]}
Properties: Count = 1
ResultConverter: {System.Web.Http.Controllers.ValueResultConverter<string>}
ReturnType: {Name = "String" FullName = "System.String"}
SupportedHttpMethods: Count = 1
_actionBinding: {System.Web.Http.Controllers.HttpActionBinding}
_actionExecutor: ThreadSafetyMode=ExecutionAndPublication, IsValueCreated=false, IsValueFaulted=false, Value=null
_actionName: "Test"
_attributeCache: {object[2]}
_configuration: {System.Web.Http.HttpConfiguration}
_controllerDescriptor: {System.Web.Http.Controllers.HttpControllerDescriptor}
_converter: {System.Web.Http.Controllers.ValueResultConverter<string>}
_declaredOnlyAttributeCache: {object[2]}
_filterGrouping: null
_filterPipeline: ThreadSafetyMode=ExecutionAndPublication, IsValueCreated=false, IsValueFaulted=false, Value=null
_filterPipelineForGrouping: null
_methodInfo: {System.String Test()}
_parameterInfos: {System.Reflection.ParameterInfo[0]}
_parameters: ThreadSafetyMode=ExecutionAndPublication, IsValueCreated=true, IsValueFaulted=false, Value={System.Collections.ObjectModel.Collection<System.Web.Http.Controllers.HttpParameterDescriptor>}
_properties: Count = 1
_returnType: {Name = "String" FullName = "System.String"}
_supportedHttpMethods (System.Web.Http.Controllers.HttpActionDescriptor): Count = 0
_supportedHttpMethods: Count = 1
Equality is determined based on the method info.
Input (1) vs existing in dictionary (2):
((System.Web.Http.Controllers.ReflectedHttpActionDescriptor)(object)(key))._methodInfo
{System.String Test()}
Attributes: Public | HideBySig
BindingFlags: Instance | Public
CallingConvention: Standard | HasThis
ContainsGenericParameters: false
CustomAttributes: Count = 2
DeclaringType: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
FullName: "WebApiVersioning.Controllers.V1.TestController.Test()"
InvocationFlags: INVOCATION_FLAGS_INITIALIZED | INVOCATION_FLAGS_NEED_SECURITY
IsAbstract: false
IsAssembly: false
IsConstructor: false
IsDynamicallyInvokable: true
IsFamily: false
IsFamilyAndAssembly: false
IsFamilyOrAssembly: false
IsFinal: false
IsGenericMethod: false
IsGenericMethodDefinition: false
IsHideBySig: true
IsOverloaded: false
IsPrivate: false
IsPublic: true
IsSecurityCritical: true
IsSecuritySafeCritical: false
IsSecurityTransparent: false
IsSpecialName: false
IsStatic: false
IsVirtual: false
MemberType: Method
MetadataToken: 100663442
MethodHandle: {System.RuntimeMethodHandle}
MethodImplementationFlags: IL
Module: {WebApiVersioning.dll}
Name: "Test"
ReflectedType: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
ReflectedTypeInternal: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
RemotingCache: {System.Runtime.Remoting.Metadata.RemotingMethodCachedData}
ReturnParameter: {System.String }
ReturnType: {Name = "String" FullName = "System.String"}
ReturnTypeCustomAttributes: {System.String }
Signature: {System.Signature}
System.IRuntimeMethodInfo.Value: {System.RuntimeMethodHandleInternal}
System.Runtime.InteropServices._MethodBase.IsAbstract: false
System.Runtime.InteropServices._MethodBase.IsAssembly: false
System.Runtime.InteropServices._MethodBase.IsConstructor: false
System.Runtime.InteropServices._MethodBase.IsFamily: false
System.Runtime.InteropServices._MethodBase.IsFamilyAndAssembly: false
System.Runtime.InteropServices._MethodBase.IsFamilyOrAssembly: false
System.Runtime.InteropServices._MethodBase.IsFinal: false
System.Runtime.InteropServices._MethodBase.IsHideBySig: true
System.Runtime.InteropServices._MethodBase.IsPrivate: false
System.Runtime.InteropServices._MethodBase.IsPublic: true
System.Runtime.InteropServices._MethodBase.IsSpecialName: false
System.Runtime.InteropServices._MethodBase.IsStatic: false
System.Runtime.InteropServices._MethodBase.IsVirtual: false
m_bindingFlags: Instance | Public
m_cachedData: {System.Runtime.Remoting.Metadata.RemotingMethodCachedData}
m_declaringType: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
m_handle: {140714995937072}
m_invocationFlags: INVOCATION_FLAGS_INITIALIZED | INVOCATION_FLAGS_NEED_SECURITY
m_keepalive: null
m_methodAttributes: Public | HideBySig
m_name: "Test"
m_parameters: {System.Reflection.ParameterInfo[0]}
m_reflectedTypeCache: {System.RuntimeType.RuntimeTypeCache}
m_returnParameter: {System.String }
m_signature: {System.Signature}
m_toString: "System.String Test()"
((System.Web.Http.Controllers.ReflectedHttpActionDescriptor)(object)(System.Linq.Enumerable.First(this.Keys)))._methodInfo
{System.String Test()}
Attributes: Public | HideBySig
BindingFlags: Instance | Public
CallingConvention: Standard | HasThis
ContainsGenericParameters: false
CustomAttributes: Count = 2
DeclaringType: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
FullName: "WebApiVersioning.Controllers.V1.TestController.Test()"
InvocationFlags: INVOCATION_FLAGS_INITIALIZED | INVOCATION_FLAGS_NEED_SECURITY
IsAbstract: false
IsAssembly: false
IsConstructor: false
IsDynamicallyInvokable: true
IsFamily: false
IsFamilyAndAssembly: false
IsFamilyOrAssembly: false
IsFinal: false
IsGenericMethod: false
IsGenericMethodDefinition: false
IsHideBySig: true
IsOverloaded: false
IsPrivate: false
IsPublic: true
IsSecurityCritical: true
IsSecuritySafeCritical: false
IsSecurityTransparent: false
IsSpecialName: false
IsStatic: false
IsVirtual: false
MemberType: Method
MetadataToken: 100663442
MethodHandle: {System.RuntimeMethodHandle}
MethodImplementationFlags: IL
Module: {WebApiVersioning.dll}
Name: "Test"
ReflectedType: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
ReflectedTypeInternal: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
RemotingCache: {System.Runtime.Remoting.Metadata.RemotingMethodCachedData}
ReturnParameter: {System.String }
ReturnType: {Name = "String" FullName = "System.String"}
ReturnTypeCustomAttributes: {System.String }
Signature: {System.Signature}
System.IRuntimeMethodInfo.Value: {System.RuntimeMethodHandleInternal}
System.Runtime.InteropServices._MethodBase.IsAbstract: false
System.Runtime.InteropServices._MethodBase.IsAssembly: false
System.Runtime.InteropServices._MethodBase.IsConstructor: false
System.Runtime.InteropServices._MethodBase.IsFamily: false
System.Runtime.InteropServices._MethodBase.IsFamilyAndAssembly: false
System.Runtime.InteropServices._MethodBase.IsFamilyOrAssembly: false
System.Runtime.InteropServices._MethodBase.IsFinal: false
System.Runtime.InteropServices._MethodBase.IsHideBySig: true
System.Runtime.InteropServices._MethodBase.IsPrivate: false
System.Runtime.InteropServices._MethodBase.IsPublic: true
System.Runtime.InteropServices._MethodBase.IsSpecialName: false
System.Runtime.InteropServices._MethodBase.IsStatic: false
System.Runtime.InteropServices._MethodBase.IsVirtual: false
m_bindingFlags: Instance | Public
m_cachedData: {System.Runtime.Remoting.Metadata.RemotingMethodCachedData}
m_declaringType: {Name = "TestController" FullName = "WebApiVersioning.Controllers.V1.TestController"}
m_handle: {140714995937072}
m_invocationFlags: INVOCATION_FLAGS_INITIALIZED | INVOCATION_FLAGS_NEED_SECURITY
m_keepalive: null
m_methodAttributes: Public | HideBySig
m_name: "Test"
m_parameters: {System.Reflection.ParameterInfo[0]}
m_reflectedTypeCache: {System.RuntimeType.RuntimeTypeCache}
m_returnParameter: {System.String }
m_signature: {System.Signature}
m_toString: "System.String Test()"
Since both are of type RuntimeMethodInfo, we just follow to reference source and find that apparently there is checked for reference equality.
The question is now: Why does reference equality not match?
Never mind -- previous post was based on a wrong assumption.
Because both routes match, Web API tries to match both actions in the dictionary.
I suggest you either:
- Don't use the {version} token at all in your direct route, so bypass this lib and route directly, e.g:
api/v1/testandapi/v2/test. - Try to use the 3.x beta version of this library
Thank you for the suggestion. However I'm not able to make it run. If I don't use your library at all, the standard WebAPI is not able to deal with two controllers of the same class name in different namespaces. If I use the library (just without the {version} token) I get 404 - The API 'Test' doesn't exist".
As for the 3.x beta - I have already tried, but I was not able to make it running within the project I want to use it in. I can do a feew tests in the testing app and then try to integrate it again.
That's correct. ASP.NET Web API is unable to handle controllers with the same name, resulting in 404. You need to prefix your controllers :(