Marc
Marc

Reputation: 16522

Error 500 instead of 404

I have an asp.net MVC 5 project and I'm trying to throw a 404 error instead of 500.

The errors are

A public action method 'something' was not found on controller 'ProjetX.Controllers.HomeController'

and

The controller for path '/something' was not found or does not implement IController

I understand why it's an error 500 but I would like to throw a 404. It would be better for SEO.

I can't figure out how to

Here's my code

My ExceptionHandler class for elmah

public class HandleCustomError : HandleErrorAttribute
    {

        public override void OnException(ExceptionContext filterContext)
        {
            //If the exeption is already handled we do nothing
            if (filterContext.ExceptionHandled)
            {
                return;
            }
            else
            {
                //Log the exception with Elmah
                Log(filterContext);

                Type exceptionType = filterContext.Exception.GetType();

                //If the exception is while an ajax call
                if (exceptionType == typeof(ExceptionForAjax))
                {
                    filterContext.HttpContext.Response.Clear();
                    filterContext.HttpContext.Response.ContentEncoding = Encoding.UTF8;
                    filterContext.HttpContext.Response.HeaderEncoding = Encoding.UTF8;
                    filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
                    filterContext.HttpContext.Response.StatusCode = 500;
                    filterContext.HttpContext.Response.StatusDescription = filterContext.Exception.Message;
                }
                else
                {
                    base.OnException(filterContext);
                }

            }

            //Make sure that we mark the exception as handled
            filterContext.ExceptionHandled = true;
        }

        private void Log(ExceptionContext context)
        {
            // Retrieve the current HttpContext instance for this request.
            HttpContext httpContext = context.HttpContext.ApplicationInstance.Context;

            if (httpContext == null)
            {
                return;
            }

            // Wrap the exception in an HttpUnhandledException so that ELMAH can capture the original error page.
            Exception exceptionToRaise = new HttpUnhandledException(message: null, innerException: context.Exception);

            // Send the exception to ELMAH (for logging, mailing, filtering, etc.).
            ErrorSignal signal = ErrorSignal.FromContext(httpContext);
            signal.Raise(exceptionToRaise, httpContext);
        }

    }

How I add the custom errors

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleCustomError());
        }

The routes config

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute("Robots.txt",
                "robots.txt",
                new { controller = "robot", action = "index" });

            routes.MapRoute(
                name: "Localization",
                url: "{lang}/{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                constraints: new { lang = @"^[a-zA-Z]{2}$" }

            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

       routes.MapRoute(
        "NotFound",
        "{*url}",
        new { controller = "Error", action = "Error404" }
        );


            routes.MapMvcAttributeRoutes();
        }

My webconfig

<system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <!-- 2MB-->
    <httpRuntime targetFramework="4.5" maxRequestLength="2097152" />
    <httpModules>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>
    <!-- Set mode to RemoteOnly in production -->
    <customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="/Error/Error500">
      <error statusCode="400" redirect="/Error/Error400" />
      <error statusCode="404" redirect="/Error/Error404" />
      <error statusCode="500" redirect="/Error/Error500" />
    </customErrors>
  </system.web>
  <system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="400" subStatusCode="-1" />
      <error statusCode="400" path="/Error/Error400" responseMode="ExecuteURL" />
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/Error/Error404" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/Error/Error500" responseMode="ExecuteURL" />
    </httpErrors>
    <modules>
      <remove name="FormsAuthenticationModule" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="Robots-ISAPI-Integrated-4.0" path="/robots.txt" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    <staticContent>
    </staticContent>
  </system.webServer>

I wanted to handle the error in the HandleCustomError class but the problem is that it goes straight to my Error500 action in the error controller.

What's weird is that the error is still logged in elmah.

It doesn't hit any breakpoint inside the HandleCustomError class, how can the error be logged?

Thank you

Upvotes: 1

Views: 2729

Answers (1)

Marc
Marc

Reputation: 16522

Here's the final code that works

I had to add 2 functions to generate a regex with all the name of my controllers and actions

public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");


        routes.MapRoute(
            name: "Localization",
            url: "{lang}/{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            constraints: new { lang = @"^[a-zA-Z]{2}$", controller = GetAllControllersAsRegex(), action = GetAllActionsAsRegex }

        );

        routes.MapRoute(
            "Default",
            "{controller}/{action}/{id}",
            new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            new { controller = GetAllControllersAsRegex(), action = GetAllActionsAsRegex() }
        );

        routes.MapRoute(
        "NotFound",
        "{*url}",
        new { controller = "Error", action = "Error404" }
        );

        routes.MapMvcAttributeRoutes();
    }
    private static string GetAllControllersAsRegex() 
    { 
        var controllers = typeof(MvcApplication).Assembly.GetTypes()
            .Where(t => t.IsSubclassOf(typeof(Controller))); 

        var controllerNames = controllers
            .Select(c => c.Name.Replace("Controller", "")); 

        return string.Format("({0})", string.Join("|", controllerNames)); 
    }
    private static string GetAllActionsAsRegex()
    {
        Assembly asm = Assembly.GetExecutingAssembly();

        var actions = asm.GetTypes()
                        .Where(type => typeof(Controller).IsAssignableFrom(type)) //filter controllers
                        .SelectMany(type => type.GetMethods())
                        .Where(method => method.IsPublic && !method.IsDefined(typeof(NonActionAttribute)))
                        .Select(x=>x.Name);

        return string.Format("({0})", string.Join("|", actions)); 
    }
}

See Also
https://stackoverflow.com/a/4668252

Upvotes: 1

Related Questions