在我的一个控制器+动作对中,我从另一个地方获取另一个控制器和动作的值作为字符串,我想重定向我当前的动作。在进行重定向之前,我想确保我的应用程序中存在控制器+操作,如果没有,则重定向到404.我正在寻找一种方法来执行此操作。
public ActionResult MyTestAction()
{
string controller = getFromSomewhere();
string action = getFromSomewhereToo();
/*
At this point use reflection and make sure action and controller exists
else redirect to error 404
*/
return RedirectToRoute(new { action = action, controller = controller });
}
我所做的就是这个,但它不起作用。
var cont = Assembly.GetExecutingAssembly().GetType(controller);
if (cont != null && cont.GetMethod(action) != null)
{
// controller and action pair is valid
}
else
{
// controller and action pair is invalid
}
答案 0 :(得分:7)
您可以实施IRouteConstraint
并在路线表中使用它。
此路由约束的实现可以使用反射来检查控制器/操作是否存在。如果它不存在,将跳过该路线。作为路由表中的最后一个路由,您可以设置一个捕获所有路由并将其映射到呈现404视图的操作。
以下是一些帮助您入门的代码段:
public class MyRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var action = values["action"] as string;
var controller = values["controller"] as string;
var controllerFullName = string.Format("MvcApplication1.Controllers.{0}Controller", controller);
var cont = Assembly.GetExecutingAssembly().GetType(controllerFullName);
return cont != null && cont.GetMethod(action) != null;
}
}
请注意,您需要使用控制器的完全限定名称。
RouteConfig.cs
routes.MapRoute(
"Home", // Route name
"{controller}/{action}", // URL with parameters
new { controller = "Home", action = "Index" }, // Parameter defaults
new { action = new MyRouteConstraint() } //Route constraints
);
routes.MapRoute(
"PageNotFound", // Route name
"{*catchall}", // URL with parameters
new { controller = "Home", action = "PageNotFound" } // Parameter defaults
);
答案 1 :(得分:4)
如果您无法获取要传递给GetType()的控制器的完全限定名称,则需要使用GetTypes(),然后对结果进行字符串比较。
Type[] types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes();
Type type = types.Where( t => t.Name == controller ).SingleOrDefault();
if( type != null && type.GetMethod( action ) != null )
答案 2 :(得分:0)
我们通过将这一行添加到WebApiConfig.cs文件中解决了此问题
config.Services.Replace(typeof(IHttpControllerSelector), new AcceptHeaderControllerSelector(config));
我使用的核心方法如下所示:该方法在扩展IHttpControllerSelector接口的AcceptHeaderControllerSelector类内。
之所以这样做,是因为我们必须对API进行版本控制,这是创建新控制器的一种方式,例如V2仅包含我们正在版本控制的方法,如果V2不存在,则退回到V1
private HttpControllerDescriptor TryGetControllerWithMatchingMethod(string version, string controllerName, string actionName)
{
var versionNumber = Convert.ToInt32(version.Substring(1, version.Length - 1));
while(versionNumber >= 1) {
var controllerFullName = string.Format("Namespace.Controller.V{0}.{1}Controller, Namespace.Controller.V{0}", versionNumber, controllerName);
Type type = Type.GetType(controllerFullName, false, true);
var matchFound = type != null && type.GetMethod(actionName) != null;
if (matchFound)
{
var key = string.Format(CultureInfo.InvariantCulture, "V{0}{1}", versionNumber, controllerName);
HttpControllerDescriptor controllerDescriptor;
if (_controllers.TryGetValue(key, out controllerDescriptor))
{
return controllerDescriptor;
}
}
versionNumber--;
}
return null;
}
完整文件如下:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Routing;
namespace WebApi
{
public class AcceptHeaderControllerSelector : IHttpControllerSelector
{
private const string ControllerKey = "controller";
private const string ActionKey = "action";
private const string VersionHeaderValueNotFoundExceptionMessage = "Version not found in headers";
private const string VersionFoundInUrlAndHeaderErrorMessage = "Version can not be in Header and Url";
private const string CouldNotFindEndPoint = "Could not find endpoint {0} for api version number {1}";
private readonly HttpConfiguration _configuration;
private readonly Dictionary<string, HttpControllerDescriptor> _controllers;
public AcceptHeaderControllerSelector(HttpConfiguration config)
{
_configuration = config;
_controllers = InitializeControllerDictionary();
}
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
var assembliesResolver = _configuration.Services.GetAssembliesResolver();
// This would seem to look at all references in the web api project and find any controller, so I had to add Controller.V2 reference in order for it to find them
var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (var t in controllerTypes)
{
var segments = t.Namespace.Split(Type.Delimiter);
// For the dictionary key, strip "Controller" from the end of the type name.
// This matches the behavior of DefaultHttpControllerSelector.
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var key = string.Format(CultureInfo.InvariantCulture, "{0}{1}", segments[segments.Length - 1], controllerName);
dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
return dictionary;
}
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var controllerName = GetRouteVariable<string>(routeData, ControllerKey);
var actionName = GetRouteVariable<string>(routeData, ActionKey);
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var version = GetVersion(request);
HttpControllerDescriptor controllerDescriptor;
if (_controllers.TryGetValue(controllerName, out controllerDescriptor))
{
if (!string.IsNullOrWhiteSpace(version))
{
throw new HttpResponseException(request.CreateResponse(HttpStatusCode.Forbidden, VersionFoundInUrlAndHeaderErrorMessage));
}
return controllerDescriptor;
}
controllerDescriptor = TryGetControllerWithMatchingMethod(version, controllerName, actionName);
if (controllerDescriptor != null)
{
return controllerDescriptor;
}
var message = string.Format(CouldNotFindEndPoint, controllerName, version);
throw new HttpResponseException(request.CreateResponse(HttpStatusCode.NotFound, message));
}
private HttpControllerDescriptor TryGetControllerWithMatchingMethod(string version, string controllerName, string actionName)
{
var versionNumber = Convert.ToInt32(version.Substring(1, version.Length - 1));
while(versionNumber >= 1) {
var controllerFullName = string.Format("Namespace.Controller.V{0}.{1}Controller, Namespace.Controller.V{0}", versionNumber, controllerName);
Type type = Type.GetType(controllerFullName, false, true);
var matchFound = type != null && type.GetMethod(actionName) != null;
if (matchFound)
{
var key = string.Format(CultureInfo.InvariantCulture, "V{0}{1}", versionNumber, controllerName);
HttpControllerDescriptor controllerDescriptor;
if (_controllers.TryGetValue(key, out controllerDescriptor))
{
return controllerDescriptor;
}
}
versionNumber--;
}
return null;
}
public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
{
return _controllers;
}
private string GetVersion(HttpRequestMessage request)
{
IEnumerable<string> values;
string apiVersion = null;
if (request.Headers.TryGetValues(Common.Classes.Constants.ApiVersion, out values))
{
apiVersion = values.FirstOrDefault();
}
return apiVersion;
}
private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
{
object result = null;
if (routeData.Values.TryGetValue(name, out result))
{
return (T)result;
}
return default(T);
}
}
}
答案 3 :(得分:-1)
反思是一项代价高昂的行动。
您应该对这些方法进行单元测试,以确保它们重定向到适当的操作和控制器。
E.g。 (NUnit的)
[Test]
public void MyTestAction_Redirects_To_MyOtherAction()
{
var controller = new MyController();
var result = (RedirectToRouteResult)controller.MyTestAction();
Assert.That(result.RouteValues["action"], Is.EqualTo("MyOtherAction");
Assert.That(result.RouteValues["controller"], Is.EqualTo("MyOtherController");
}