I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it.
I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.
At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.
Works with
- MVC 3 (I think)
- MVC 4 (tested)
Drawbacks of my solution
Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:
- static object
ControllerBuilder.Current
(very bad for unit testing)
- a lot of classes from MVC (high coupling is always bad)
- not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
- internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that
RouteData
object is not used to find the controller type, so I simply use stub RouteData objects)
- mocks of complex objects to pass data (I needed to mock
HttpContextWrapper
and HttpRequestWrapper
in order to set the http method
to be POST
or GET
... these pretty simple values comes from complex objects (oh god! = ))
The code
public static Attribute[] GetAttributes(
this Controller @this,
string action = null,
string controller = null,
string method = "GET")
{
var actionName = action
?? @this.RouteData.GetRequiredString("action");
var controllerName = controller
?? @this.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current
.GetControllerFactory();
var controllerContext = @this.ControllerContext;
var otherController = (ControllerBase)controllerFactory
.CreateController(
new RequestContext(controllerContext.HttpContext, new RouteData()),
controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(
otherController.GetType());
var controllerContext2 = new ControllerContext(
new MockHttpContextWrapper(
controllerContext.HttpContext.ApplicationInstance.Context,
method),
new RouteData(),
otherController);
var actionDescriptor = controllerDescriptor
.FindAction(controllerContext2, actionName);
var attributes = actionDescriptor.GetCustomAttributes(true)
.Cast<Attribute>()
.ToArray();
return attributes;
}
EDIT
Forgot the mocked classes
class MockHttpContextWrapper : HttpContextWrapper
{
public MockHttpContextWrapper(HttpContext httpContext, string method)
: base(httpContext)
{
this.request = new MockHttpRequestWrapper(httpContext.Request, method);
}
private readonly HttpRequestBase request;
public override HttpRequestBase Request
{
get { return request; }
}
class MockHttpRequestWrapper : HttpRequestWrapper
{
public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
: base(httpRequest)
{
this.httpMethod = httpMethod;
}
private readonly string httpMethod;
public override string HttpMethod
{
get { return httpMethod; }
}
}
}
Hope all of this helps someone...
Happy coding for everybody!
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…