Skip to content
This repository has been archived by the owner on Nov 27, 2018. It is now read-only.

"Multiple actions matched" for controller in area on Mono #391

Closed
Daniel15 opened this issue Mar 19, 2017 · 8 comments
Closed

"Multiple actions matched" for controller in area on Mono #391

Daniel15 opened this issue Mar 19, 2017 · 8 comments

Comments

@Daniel15
Copy link

After upgrading from .NET Core 1.0 to 1.1.1, I'm hitting this error on Mono:

Multiple actions matched. The following actions matched route data and had all constraints satisfied: 
Daniel15.Web.Areas.Admin.Controllers.BlogController.Index (Daniel15.Web)
Daniel15.Web.Areas.Admin.Controllers.BlogController.Dispose (Daniel15.Web)

However, the exact same controller works fine on .NET Framework 4.5

The controller does not even have a Dispose method so I have no idea why the routing is picking it up :/

Controller source code: https://github.com/Daniel15/Website/blob/4aeb709b11db77543f16ef41b9018a3fb48de066/Daniel15.Web/Areas/Admin/Controllers/BlogController.cs

@rynowak
Copy link
Member

rynowak commented Mar 19, 2017

Thanks for reporting this. Controller has a dispose method, mostly for back-compat. We have some code in particular to avoid considering Dispose and action, but it looks like there are some subtle reflection differences on mono.

@Daniel15
Copy link
Author

Daniel15 commented Mar 20, 2017

@rynowak Where is that code located? I could try to debug it and see why it's not working properly.

For what it's worth, I'm only seeing this for one controller in the app. Other controllers are working fine 😕

@rynowak
Copy link
Member

rynowak commented Mar 20, 2017

For what it's worth, I'm only seeing this for one controller in the app.

Now that's really strange..

Code is here, note that we fixed a bug in this code in 1.1.0 aspnet/Mvc#5352

@Daniel15
Copy link
Author

Daniel15 commented Mar 22, 2017

So this does look like a Mono bug with GetRuntimeInterfaceMap combined with inheritance

However, even considering that... Why would the route even be matching Dispose? I'm using attribute routing, the Dispose method is not decorated with any route attribute, and my URL doesn't have Dispose in it anywhere (the URL being hit is /blog/admin). This is the full controller: https://github.com/Daniel15/Website/blob/4aeb709b11db77543f16ef41b9018a3fb48de066/Daniel15.Web/Areas/Admin/Controllers/BlogController.cs. I don't understand why ASP.NET MVC is even considering Dispose to match that route.

As for the bug, I made a small console app to test:

class Foo : IDisposable
{
	public void Dispose() => Dispose(disposing: true);
	protected virtual void Dispose(bool disposing) {}
}
class Bar : Foo { }
class Foo2 : Microsoft.AspNetCore.Mvc.Controller { }

class Program
{
	static void Main(string[] args)
	{
		Console.WriteLine("Regular class:");
		Test(typeof(Foo));
		Console.WriteLine();
		Console.WriteLine("Inheritance:");
		Test(typeof(Bar));
		Console.WriteLine();
		Console.WriteLine("Controller:");
		Test(typeof(Foo2));
	}

	static void Test(Type type)
	{
		var methodInfo = type.GetMethod("Dispose");
		var baseMethodInfo = methodInfo.GetBaseDefinition();
		var declaringTypeInfo = baseMethodInfo.DeclaringType.GetTypeInfo();
		var firstTargetMethod = declaringTypeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0];

		Console.WriteLine("IsAssignableFrom: {0}", typeof(IDisposable).GetTypeInfo().IsAssignableFrom(declaringTypeInfo));
		Console.WriteLine("TargetMethod is equal (==): {0}", firstTargetMethod == baseMethodInfo);
		Console.WriteLine("TargetMethod is equal (Equals): {0}", firstTargetMethod.Equals(baseMethodInfo));
		Console.WriteLine("baseMethodInfo: {0} attribs={1}", baseMethodInfo, baseMethodInfo.Attributes);
		Console.WriteLine("targetMethod:   {0} attribs={1}", firstTargetMethod, firstTargetMethod.Attributes);
	}
}

The output when running it on Windows is what you'd expect:

Regular class:
IsAssignableFrom: True
TargetMethod is equal (==): True
TargetMethod is equal (Equals): True
baseMethodInfo: Void Dispose() attribs=PrivateScope, Public, Final, Virtual, HideBySig, VtableLayoutMask
targetMethod:   Void Dispose() attribs=PrivateScope, Public, Final, Virtual, HideBySig, VtableLayoutMask

Inheritance:
IsAssignableFrom: True
TargetMethod is equal (==): True
TargetMethod is equal (Equals): True
baseMethodInfo: Void Dispose() attribs=PrivateScope, Public, Final, Virtual, HideBySig, VtableLayoutMask
targetMethod:   Void Dispose() attribs=PrivateScope, Public, Final, Virtual, HideBySig, VtableLayoutMask

Controller:
IsAssignableFrom: True
TargetMethod is equal (==): True
TargetMethod is equal (Equals): True
baseMethodInfo: Void Dispose() attribs=PrivateScope, Public, Final, Virtual, HideBySig, VtableLayoutMask
targetMethod:   Void Dispose() attribs=PrivateScope, Public, Final, Virtual, HideBySig, VtableLayoutMask

However, the output on Mono is odd and baseMethodInfo is not equal to TargetMethods[0] when looking at a child class:

Regular class:
IsAssignableFrom: True
TargetMethod is equal (==): True
TargetMethod is equal (Equals): True
baseMethodInfo: Void Dispose() attribs=ReuseSlot, Public, Final, Virtual, HideBySig, NewSlot
targetMethod:   Void Dispose() attribs=ReuseSlot, Public, Final, Virtual, HideBySig, NewSlot

Inheritance:
IsAssignableFrom: True
TargetMethod is equal (==): False
TargetMethod is equal (Equals): False
baseMethodInfo: Void Dispose() attribs=ReuseSlot, Public, Final, Virtual, HideBySig, NewSlot
targetMethod:   Void Dispose() attribs=ReuseSlot, Public, Final, Virtual, HideBySig, NewSlot

Controller:
IsAssignableFrom: True
TargetMethod is equal (==): False
TargetMethod is equal (Equals): False
baseMethodInfo: Void Dispose() attribs=ReuseSlot, Public, Final, Virtual, HideBySig, NewSlot
targetMethod:   Void Dispose() attribs=ReuseSlot, Public, Final, Virtual, HideBySig, NewSlot

Mono bug reported: https://bugzilla.xamarin.com/show_bug.cgi?id=53690

@Daniel15
Copy link
Author

As a temporary workaround, I'm overriding IsAction to ignore all methods named "Dispose":

public class BugfixApplicationModelProvider : DefaultApplicationModelProvider
{
	public BugfixApplicationModelProvider(IOptions<MvcOptions> mvcOptionsAccessor) : base(mvcOptionsAccessor) {}

	protected override bool IsAction(TypeInfo typeInfo, MethodInfo methodInfo)
	{
		return methodInfo.Name != "Dispose" && base.IsAction(typeInfo, methodInfo);
	}
}

And replacing the standard implementation with mine:

services.Replace(ServiceDescriptor.Transient<IApplicationModelProvider, BugfixApplicationModelProvider>());

I don't have any actions named "Dispose", so this is a fine workaround. 😃

@Eilon
Copy link
Member

Eilon commented May 11, 2017

Hmm so Mono appears to have unusual behavior here, but perhaps MVC is doing something weird as well, which causes the Mono behavior difference to be problematic?

@rynowak - do you feel it's worth looking into why MVC is doing why it's doing, or consider this a Mono bug and close this issue?

@Daniel15
Copy link
Author

This has been fixed in Mono: https://bugzilla.xamarin.com/show_bug.cgi?id=53690. Let's just close this out.

@rynowak
Copy link
Member

rynowak commented May 12, 2017

Awesome. thanks

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

No branches or pull requests

3 participants