2010-08-07 7 views
5

Mam trochę czasu, zastanawiając się, jak poprawnie wdrożyć moje przekierowanie 404.ASP.NET MVC - Użyj refleksji, aby dowiedzieć się, czy istnieje sterownik

Jeśli używam następujący

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

wtedy każdy nieobsługiwany błąd na stronie załaduje się na „Error” widok, który działa świetnie. http://example.com/user/999 (gdzie 999 jest nieprawidłowym identyfikatorem użytkownika) spowoduje wyświetlenie błędu przy zachowaniu oryginalnego adresu URL (tego właśnie chcę).

Jednak. Jeśli ktoś wpisze http://example.com/asdfjkl w adres URL (gdzie asdfjkl jest nieprawidłowym kontrolerem), to IIS rzuca ogólną stronę 404. (to jest nie czego chcę). Potrzebuję tego samego, aby zastosować powyższe. Oryginalny adres URL pozostaje, a kontroler "NotFound" jest załadowany.

mam rejestrację moich trasy jak to

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 

obwieszczeniu ControllerExistsConstraint? Muszę użyć Odbicia, aby odkryć, czy kontroler istnieje, czy nie.

Czy ktoś może mi pomóc wypełnić puste miejsca?

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 

Chciałbym również poznać konsekwencje tego ... jak wysoka jest wydajność Reflection? Jeśli to za dużo, czy istnieje lepszy sposób?

Odpowiedz

-1

Dlaczego nie można po prostu uchwycić ich własnych błędów w pliku web.config i uniknąć kilka refleksji wszystko razem?

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

ponieważ w moim pytaniu powiedziałem: "Pierwotny URL zostaje, a kontroler" NotFound "jest załadowany.". ** NIE chcę przekierowywać do strony nie znalezionej ** –

10

Mam rozwiązanie C#, mam nadzieję, że pomaga. Plagiowałem niektóre z tych kodów, choć nie wiem, skąd je wziąłem. Jeśli ktoś wie, daj mi znać, żebym mógł dodać to do moich komentarzy.

To rozwiązanie nie używa refleksji, ale analizuje wszystkie błędy aplikacji (wyjątki) i sprawdza, czy jest to błąd 404. Jeśli tak, to po prostu kieruje bieżące żądanie do innego kontrolera. Chociaż nie jestem ekspertem w żaden sposób, myślę, że to rozwiązanie może być szybsze niż refleksja. Tak czy inaczej, oto rozwiązanie i idzie do swojego 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)); 
      } 
     } 
    } 

więc kontroler byłoby

public class YourErrorController : Controller 
{ 
    public ActionResult YourErrorAction() 
    { 
     return View(); 
    } 
} 
+0

Chociaż ** NIE ** odpowiedź na pytanie. To ** DOES ** rozwiązuje mój problem. Przyznam nagrodę, ale nie oznaczę jako odpowiedzi. –

+0

Prawdopodobnie masz rację co do "szybszego od odbicia". To jest miłe, ponieważ nie muszę przez cały czas wywoływać mojego 'ControllerExistsConstraint'. –

+0

Nie zauważyłem, że masz inny post otwarty na to pytanie. Powinienem był odpowiedzieć na to pytanie. Może możesz połączyć rozwiązanie tego drugiego tutaj. Jesteś zbyt miły z powodu punktów za bounty :). –

2

To jest bardzo podobny problem to mine, ale lubię swoją alternatywnego podejścia.

myślę odbicie jako dynamiczny filtr może być zbyt wydajność ciężki, ale myślę, że mam lepszy sposób - można filtrować dozwolonych działań przez Regex:

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

Potem dodać do tego dodatkowy Sposób na mojej bazy Controller:

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

W tym przypadku BaseController.NotFound obsługuje brakujący działanie na prawidłowy sterownik.

więc ostatecznie:

  • {site}/invalid - znaleźć nowego filtra opartego refleksji
  • {site}/valid/notAnAction - znalezione przez HandleUnknownAction
  • {site}/valid/action/id - znaleziono kontrolami w kodzie dla identyfikatora (jak poprzednio)
  • {site}/valid/action/id/extraPath - znalezione przez nie pasujące do żadnej trasy, ale wszystkie przechwycone wszystkie

Myślę, że to wszystkie 404 scenariusze :-)

Powiązane problemy