ASP.NET MVC - используйте Reflection, чтобы найти, существует ли контроллер - PullRequest
5 голосов
/ 08 августа 2010

У меня чертовски много времени выясняется, как правильно реализовать перенаправление 404.

Если я использую следующее

<HandleError()> _
Public Class BaseController : Inherits System.Web.Mvc.Controller
''# do stuff
End Class

Тогда любая необработанная ошибка на странице загрузит представление «Ошибка», которое прекрасно работает. http://example.com/user/999 (где 999 - недопустимый идентификатор пользователя) выдаст ошибку при сохранении исходного URL (это то, что я хочу)

Тем не менее. Если кто-то вводит http://example.com/asdfjkl в URL (где asdfjkl - недопустимый контроллер), то IIS создает общую страницу 404. (это не , что я хочу). То, что мне нужно, это то же самое, что и выше. Исходный URL остается, а контроллер NotFound загружается.

Я регистрирую свои маршруты следующим образом

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    routes.RouteExistingFiles = False
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
    routes.IgnoreRoute("Assets/{*pathInfo}")
    routes.IgnoreRoute("{*robotstxt}", New With {.robotstxt = "(.*/)?robots.txt(/.*)?"})

    routes.AddCombresRoute("Combres")

    routes.MapRoute("Start", "", New With {.controller = "Events", .action = "Index"})

    ''# MapRoute allows for a dynamic UserDetails ID
    routes.MapRouteLowercase("UserProfile", "Users/{id}/{slug}", _
                             New With {.controller = "Users", .action = "Details", .slug = UrlParameter.Optional}, _
                             New With {.id = "\d+"} _
    )


    ''# Default Catch All MapRoute
    routes.MapRouteLowercase("Default", "{controller}/{action}/{id}/{slug}", _
                             New With {.controller = "Events", .action = "Index", .id = UrlParameter.Optional, .slug = UrlParameter.Optional}, _
                             New With {.controller = New ControllerExistsConstraint})

    ''# Catch everything else cuz they're 404 errors
    routes.MapRoute("CatchAll", "{*catchall}", _
                    New With {.Controller = "Error", .Action = "NotFound"})

End Sub

Обратите внимание на ControllerExistsConstraint? Что мне нужно сделать, это использовать Reflection, чтобы узнать, существует ли контроллер.

Кто-нибудь может мне помочь заполнить пробелы?

Public Class ControllerExistsConstraint : Implements IRouteConstraint

    Public Sub New()
    End Sub

    Public Function Match(ByVal httpContext As System.Web.HttpContextBase, ByVal route As System.Web.Routing.Route, ByVal parameterName As String, ByVal values As System.Web.Routing.RouteValueDictionary, ByVal routeDirection As System.Web.Routing.RouteDirection) As Boolean Implements System.Web.Routing.IRouteConstraint.Match


        ''# Bah, I can't figure out how to find if the controller exists


End Class

Я также хотел бы знать, как это влияет на производительность ... насколько тяжелым является Reflection? Если это слишком много, есть ли лучший способ?

Ответы [ 4 ]

10 голосов
/ 18 августа 2010

У меня есть решение на C #, надеюсь, оно поможет. Я плагиат часть этого кода, хотя на всю жизнь я не могу найти, откуда я его взял. Если кто-нибудь знает, пожалуйста, дайте мне знать, чтобы я мог добавить его в свои комментарии.

Это решение не использует отражение, но оно просматривает все ошибки приложения (исключения) и проверяет, является ли это ошибкой 404. Если это так, то он просто направляет текущий запрос на другой контроллер. Хотя я не эксперт в любом случае, я думаю, что это решение может быть быстрее, чем отражение. В любом случае, вот решение, и оно входит в ваш Global.asax.cs,

    protected void Application_Error(object sender, EventArgs e)
    {
        Exception exception = Server.GetLastError();

        // A good location for any error logging, otherwise, do it inside of the error controller.

        Response.Clear();
        HttpException httpException = exception as HttpException;
        RouteData routeData = new RouteData();
        routeData.Values.Add("controller", "YourErrorController");

        if (httpException != null)
        {
            if (httpException.GetHttpCode() == 404)
            {
                routeData.Values.Add("action", "YourErrorAction");

                // We can pass the exception to the Action as well, something like
                // routeData.Values.Add("error", exception);

                // Clear the error, otherwise, we will always get the default error page.
                Server.ClearError();

                // Call the controller with the route
                IController errorController = new ApplicationName.Controllers.YourErrorController();
                errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
            }
        }
    }

Таким образом, контроллер будет,

public class YourErrorController : Controller
{
    public ActionResult YourErrorAction()
    {
        return View();
    }
}
2 голосов
/ 18 апреля 2011

Это очень похожая проблема на мою , но мне нравится ваш альтернативный подход.

Я думаю, что отражение в виде динамического фильтра может быть слишком тяжелым, но я думаю, что у меня естьлучший способ - вы можете фильтровать разрешенные действия по регулярному выражению:

// build up a list of known controllers, so that we don't let users hit ones that don't exist
var allMvcControllers = 
    from t in typeof(Global).Assembly.GetTypes()
    where t != null &&
        t.IsPublic &&
        !t.IsAbstract &&
        t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
        typeof(IController).IsAssignableFrom(t)
    select t.Name.Substring(0, t.Name.Length - 10);

// create a route constraint that requires the controller to be one of the reflected class names
var controllerConstraint = new
{
    controller = "(" + string.Join("|", allMvcControllers.ToArray()) + ")"
};

// default MVC route
routes.MapRoute(
    "MVC",
    "{controller}/{action}/{id}",
    new { action = "Index", id = UrlParameter.Optional },
    controllerConstraint);

// fall back route for unmatched patterns or invalid controller names
routes.MapRoute(
    "Catch All", 
    "{*url}",
    new { controller = "System", action = "NotFound" });

Затем я добавляю к этому дополнительный метод на моей базе Controller:

protected override void HandleUnknownAction(string actionName)
{
    this.NotFound(actionName).ExecuteResult(this.ControllerContext);
}

В этом случае BaseController.NotFound обрабатывает пропущенное действие на действительном контроллере.

Итак, наконец:

  • {site}/invalid - найдено с помощью нового фильтра на основе отражений
  • {site}/valid/notAnAction - найдено с помощьюHandleUnknownAction
  • {site}/valid/action/id - найдено проверками в коде для идентификатора (как и прежде)
  • {site}/valid/action/id/extraPath - найдено путем не совпадения ни с одним маршрутом, но с перехватом всех

Я думаю, что это все 404 сценария: -)

1 голос
/ 14 августа 2010

Возможно, эта статья может указать вам правильное направление: ASP.NET MVC: получить все контроллеры

0 голосов
/ 17 августа 2010

Почему бы вам просто не записать их с пользовательскими ошибками в файле web.config и избежать кучу размышлений вместе?

<customErrors mode="On">   
    <error statusCode="404" redirect="/Error/NotFound" />
</customErrors>
...