简体   繁体   English

如何在进行重定向之前确保 controller 和操作存在,asp.net mvc3

[英]How to make sure controller and action exists before doing redirect, asp.net mvc3

In one of my controller+action pair, I am getting the values of another controller and action as strings from somewhere and I want to redirect my current action.在我的一个控制器+动作对中,我从某处获取另一个 controller 和动作作为字符串的值,我想重定向我当前的动作。 Before making a redirect I want to make sure that controller+action exists in my app, if not then redirect to 404. I am looking for a way to do this.在进行重定向之前,我想确保我的应用程序中存在控制器+操作,如果没有,则重定向到 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 });
}

All I have done is this, but it doesn't work.我所做的就是这个,但它不起作用。

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
}

You can implement IRouteConstraint and use it in your route table.您可以实现IRouteConstraint并在路由表中使用它。

The implementation of this route constraint can than use reflection to check if controller/action exists.此路由约束的实现可以使用反射来检查控制器/动作是否存在。 If it doesn't exist the route will be skipped.如果它不存在,则将跳过该路线。 As a last route in your route table, you can set one that catches all and map it to action that renders 404 view.作为路由表中的最后一条路由,您可以设置一个捕获所有路由并将 map 设置为呈现 404 视图的操作。

Here's some code snippet to help you started:这里有一些代码片段可以帮助您入门:

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

Note that you need to use fully-qualified name of the controller.请注意,您需要使用 controller 的完全限定名称。

RouteConfig.cs路由配置.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
            );

If you can't obtain the fully-qualified name of the controller to pass in to GetType() you'll need to use GetTypes() and then do a string comparison over the results.如果您无法获得 controller 的完全限定名称以传递给 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 )

We solved this by adding this line to our WebApiConfig.cs file我们通过将此行添加到我们的 WebApiConfig.cs 文件来解决这个问题

config.Services.Replace(typeof(IHttpControllerSelector), new AcceptHeaderControllerSelector(config));

The core method that I have used is as follows This method was within the AcceptHeaderControllerSelector class that extended the IHttpControllerSelector interface.我使用的核心方法如下这个方法在扩展 IHttpControllerSelector 接口的 AcceptHeaderControllerSelector class 中。

The reason I have done it like this is that we have to version our API and this was a way to create a new controller eg V2 with just the methods that we were versioning and just drop back to V1 if a V2 didn't exists我这样做的原因是我们必须对 API 进行版本化,这是一种创建新 controller 的方法,例如 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;
}

The full file can be seen below:完整的文件可以在下面看到:

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

Reflection is a costly operation.反射是一项昂贵的操作。

You should really be unit testing these methods to ensure they are redirecting to the appropriate action and controller.你真的应该对这些方法进行单元测试,以确保它们重定向到适当的操作和 controller。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM