【问题标题】:How to make sure controller and action exists before doing redirect, asp.net mvc3如何在进行重定向之前确保控制器和操作存在,asp.net mvc3
【发布时间】:2011-08-11 21:59:25
【问题描述】:

在我的控制器+动作对中,我从某处获取另一个控制器和动作的值作为字符串,我想重定向我当前的动作。在进行重定向之前,我想确保我的应用程序中存在控制器+操作,如果没有,则重定向到 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
}

【问题讨论】:

    标签: c# asp.net-mvc asp.net-mvc-3 reflection


    【解决方案1】:

    您可以实现IRouteConstraint 并在您的路由表中使用它。

    此路由约束的实现可以使用反射来检查控制器/动作是否存在。如果它不存在,则将跳过该路线。作为路由表中的最后一条路由,您可以设置一个捕获所有路由并将其映射到呈现 404 视图的操作。

    这里有一些代码 sn-p 可以帮助您入门:

    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
                );
    

    【讨论】:

      【解决方案2】:

      如果您无法获取控制器的完全限定名称以传递给 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 )
      

      【讨论】:

        【解决方案3】:

        我们通过将这一行添加到我们的 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);
                }
            }
        }
        

        【讨论】:

          【解决方案4】:

          反思是一项昂贵的操作。

          您真的应该对这些方法进行单元测试,以确保它们重定向到适当的操作和控制器。

          例如(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");
          }
          

          【讨论】:

          • 好吧,既然 RouteContraint 对象只创建一次,您可以一次反射所有控制器和操作并保存结果。
          猜你喜欢
          • 1970-01-01
          • 2013-02-13
          • 2011-08-06
          • 2015-05-18
          • 1970-01-01
          • 2013-08-16
          • 1970-01-01
          • 2015-04-11
          • 1970-01-01
          相关资源
          最近更新 更多