I've got StructureMap working fine on my machine. Everything works great ... until I request a resource that doesn't exist. Instead of a 404, i get a 500 error.
eg. http://localhost:6969/lkfhklsfhskdfksdf
Checking the net, i was told to fix my structuremap controller class. Did that and joy! i now get the -original default 404 yellow screen page-. Ok, that's better than my 500 error page.
BUT, i want it to go to my custom 404 page :( If i call a bad action on a legit controller, i get my custom 404 page.
In my global.asax i have my custom routes, then the default, then finally the 404 route:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Post", action = "Index", id = "" } // Parameter defaults
);
// Invalid/Unknown route.
routes.MapRoute(
"404-ResourceNotFound",
"{*url}",
new { controller = "StaticContent", action = "ResourceNotFound" }
);
Here's my structuremap controller code:
public class StructureMapControllerFactory: DefaultControllerFactory {
protected override IController GetControllerInstance(Type controllerType) {
if (controllerType != null) {
return ObjectFactory.GetInstance(controllerType) as Controller;
return base.GetControllerInstance(controllerType);
}
}
Any ideas? is there some way i could somehow get the structure map controller factory to bubble back into the global.asax routes list? or have i done something really bad and need to fix up some other stuff.
cheers!
mmm... seems like it might be an exception thing. like the way MVC is designed to handle the 404 errors via exceptions.
here is my code:
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(Type controllerType)
{
IController result = null;
try
{
result = ObjectFactory.GetInstance(controllerType) as Controller;
}
catch (StructureMapException)
{
System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave());
throw;
}
return result;
}
}
.. you may have even had this and changed it. If not, try it and see if there is a difference. I dont suspect there will be.
Maybe just try breaking into that override and seeing what exceptions are being thrown.
(side note: its weird how i keep stumbling on your questions Krome. What r u working on?)
EDIT: I tried the garb request and got the same exception. So i updated my class like you did.
My new class:
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(Type controllerType)
{
if (controllerType == null)
return base.GetControllerInstance(controllerType);
IController result = null;
try
{
result = ObjectFactory.GetInstance(controllerType) as Controller;
}
catch (StructureMapException)
{
System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave());
throw;
}
return result;
}
}
..this seems to give me back the 404 like it should.. but i never get the custom error pages in development (locally).. i have to wait till i publish before i get those. Are you used to seeing the custom error pages in dev?
Related
I've some "bad boys" users which call my website using controller that doesn't exist.
Such as www.myapp.com/.svn/, or www.myapp.com/wp-admin/.
The app basically throw a System.Web.HttpException, which this ExceptionMessage: The controller for path '...' was not found or does not implement IController.
Is there a way to exactly catch this "sub" System.Web.HttpException in a robust way? (not comparing the string of ExceptionMessage, of course).
Any sub InnerException type and/or some "code" which indicate that exact exception?
You might create a custom IControllerFactory, e.g. by deriving from DefaultControllerFactory.
Its GetControllerInstance method will be called with a null value for the controllerType argument, when no matching controller could be resolved.
At this point, you are ahead of the Exception.
IController GetControllerInstance(RequestContext requestContext, Type controllerType)
Here you decide how to handle the request, like e.g. logging and/or handling the request by a specific controller.
The example below shows how the ErrorController is being used as fallback, executing its Index action method.
class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
var routeData = requestContext.RouteData;
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Index";
controllerType = typeof(ErrorController);
}
return base.GetControllerInstance(requestContext, controllerType);
}
}
A custom controller factory gets installed from within e.g. Application_Start in Global.asax.cs using:
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
Note that this doesn't guard you from a related exception in case the contoller does exist, but the action doesn't.
E.g. given a HomeController without an Ufo action method, the url www.myapp.com/home/ufo results in an Exception "A public action method 'ufo' was not found on controller '...HomeController".
A simple solution to handle such scenario is by overriding HandleUnknownAction in a custom base controller of which all your controllers inherit.
The example below shows how a shared Error view (Shared\Error.cshtml) gets executed.
public abstract class BaseController : Controller
{
protected override void HandleUnknownAction(string actionName)
{
View("Error").ExecuteResult(ControllerContext);
}
}
The problem as you have stated seems like a routing problem.
Have you tried a catch all route?... That is a route that is executed when no other route matches the request's path. The catch all route is defined last and seems like:
// Specific routes here...
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Catch-All",
"{*controller}",
new { controller = "Home", action = "Index" }
);
I tested with .NET Core MVC
// This is how it is configured in .NET Core MMVC
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "catch-all",
pattern: "{*controller}",
defaults: new { controller="Home", action="Index" });
});
I think that managing the excepion, as sugested in previous answers, is a good practice, but does not solve the routing problem you are facing.
You might want to take a look to this discussion regarding similiar problem with ASP.NET MVC:
ASP.NET MVC - Catch All Route And Default Route
Like #Amirhossein mentioned, since you are using .net 4.6 framework you can have a global exception catch inside the
Global.asax file something like:
protected void Application_Error(object sender, EventArgs e)
{
var ex = Server.GetLastError();
Debug.WriteLine(ex);
var httpEx = ex as HttpException;
var statusCode = httpEx?.GetHttpCode() ?? 500;
Server.ClearError();
...
}
or you can address this issue via IIS (since I presume you host your application on IIS) by adding a mapping rule (which in turn can be done in the web.config) or by adding a filter, or even a firewall rule (since by my account those kind of probing scans are not worth to be logged in your application - I believe they should be handled on another level)
I've been reading about the different ways to perform error handling in ASP.MVC. I know about try/catch within a controller, and also about [HandleError] at controller-level.
However, I am trying to perform global error handling for unhandled exceptions that could occur anywhere in the application (the wow - we never expected a user to do that! type of thing). The result of this is that an email is sent to dev's:
This is working fine:
protected void Application_Error()
{
Exception last_ex = Server.GetLastError();
Server.ClearError();
// send email here...
Response.Redirect("/Login/Login");
}
Elsewhere in the application, should an error occur in a controller, we have this logic which provides a friendly error in our error view:
Error Model
namespace My.Models
{
public class ErrorViewModel
{
public string Summary { get; set; }
public string Description { get; set; }
}
}
Controller Code
if(somethingBad){
return View("Error", new ErrorViewModel { Summary = "Summary here", Description = "Detail here" });
}
My question is, is it possible to redirect to the error view passing the ErrorViewModel from within Global.asax, e.g.
// send email here...
Response.MethodToGetToView(new ErrorViewModel { Summary = "Error", Description = ex.Message });
From the Application_Error method, you can do something like:
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action","Error500");
routeData.Values.Add("Summary","Error");
routeData.Values.Add("Description", ex.Message);
IController controller = new ErrorController()
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
This will return the view from the action result you specify here to the user's browser, so you will need a controller & action result that returns your desired error view (Error/Error500 in this example). It is not a redirect so is technically more correct (if an error occurs, you should return an HTTP 500 status immediately, not a 302 then a 500 as is often done).
Also, if you want to capture all 404's from all URLs (not just those that match a route), you can add the following route to the end of your route config
routes.MapRoute("CatchAllUrls", "{*url}", new { controller = "Error", action = "Error404" }, new string[] { "MyApp.Controllers" });
We have built an MVC app that publishes a complete website with hierarchal Folders, SubFolders and Pages. The resulting pages, are strictly HTML and are not published in our MVC app. Our customers are able to name their Folders and Pages with any compliant string they choose. So conceivably, once the site is hosted, they could end up with a URL such as:
someDomain.com/folder/subfolder1/subfolder2/page-slug. There is no limit to the number of nested subfolders.
We would like to replicate their sites in our MVC app, so that they are able to test them before they publish and perhaps so we can provide hosting ourselves if required.
The obvious problem, is how can we handle,
ourMVCApp.com/folder/subfolder1/subfolder2/page-slug in an MVC app?
If there was a way that we could set routing to handle such a thing, then we could easily get the content required for the request by splitting the url into an array by "/".
The last segment would be a page contained in the previous segment's folder. We could then search our DB using these strings to get the required content.
Your help is greatly appreciated.
FURTHER QUESTION:
In response to the answer provided by Tomi.
I added the code to my controller's class but I am receiving the following warning:
I am not sure what I am missing? Did I put the code in the place? Thanks again.
UPDATE 2. I realized I had not actually created the controller factory, so I followed a partial example I found here: http://develoq.net/2010/custom-controller-factory-in-asp-net-mvc/. And since implementing it, I no longer receive any build-errors, but when I run the the debug, it crashes the built-in IISEXPRESS without any error message.
Here is my controller factory code:
public class FolderControllerFactory : IControllerFactory
{
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
try
{
// Get the path
string path = requestContext.RouteData.Values["pathInfo"].ToString();
IController controller = new FolderController(path);
return controller;
}
catch
{
// Log routing error here and move on
return CreateController(requestContext, controllerName);
}
}
public void ReleaseController(IController controller)
{
var disposable = controller as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
}
Here is my global:
ControllerBuilder.Current.SetControllerFactory(typeof(ProofPixApp.Controllers.FolderControllerFactory));
And finally my controller:
public class FolderController : Controller
{
private string _path;
public FolderController(string path)
{
_path = path;
}
public ActionResult Index(string name)
{
ViewBag.Message = "Hello " + name;
return View("/Views/" + _path);
}
}
A couple of notes:
1. I removed the 'override' from public IController CreateController
because I kept receiving the initial error I posted.
2. I added public void ReleaseController and the public
SessionStateBehavior GetControllerSessionBehavior methods to the
CreateController class to avoid other build errors.
3. I removed 'base.' from the catch clause because it too was causing a
build error.
SOLUTION:
I was able to avoid the error by checking to see pathValue was not null in the createController method, like so:
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
// Get the path
string path = "";
if (requestContext.RouteData.Values["pathInfo"] != null)
{
path = requestContext.RouteData.Values["pathInfo"].ToString();
}
IController controller = new FolderController(path);
return controller;
}
I have no idea what page slug is but here's my solution on how to achieve the routing you requested.
I made a custom ControllerFactory which handles the url and passes it to controller. This ControllerFactory constructs the controller we use to handle folder-route requests. We get the path from routevalues and then pass it to the FolderController.
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
try
{
// Get the path
string path = requestContext.RouteData.Values["pathInfo"].ToString();
IController controller = new FolderController(path);
return controller;
}
catch
{
// Log routing error here and move on
return base.CreateController(requestContext, controllerName);
}
}
Here's the controller. The actionmethod, which redirects to given path is called Index for now. The actionmethod returns view it finds from the url.
public class FolderController : Controller
{
private string _path;
public FolderController(string path)
{
_path = path;
}
public FolderController()
{
}
public ActionResult Index(string name)
{
ViewBag.Message = "Hello " + name;
return View("/Views/"+_path);
}
}
Last step is to write our own route and register the factory. Open up RouteConfig.cs. My new RegisterRoutes method looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Dynamic",
url: "{*pathInfo}",
defaults: new { controller = "Folder", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
In global.asax we need to register our FolderControllerFactory by adding this line into Application_Start method
ControllerBuilder.Current.SetControllerFactory(typeof(FolderControllerFactory));
And that's it! There's still much to be done, like handling improper urls and such. Also I don't think this supports plain html files, the files must be in .cshtml or asp format.
Here's the test:
My folder structure:
Url I request:
localhost:port/Mainfolder/Subfolder/Subfolder2/view.cshtml?name=Tomi
The result with Route Debugger plugin:
I've recently added Microsoft Unity to my MVC3 project and now I'm getting this error:
The controller for path '/favicon.ico' could not be found or it does not implement IController.
I do not really have a favicon.ico so I have no idea where that's coming from. And the weirdest thing is that the view is actually being rendered and THEN this error is being thrown... I am not sure if it's something wrong with my controller factory class because I got the code from some tutorial (I'm not to IoC - this is the first time I do that). Here's the code:
public class UnityControllerFactory : DefaultControllerFactory
{
IUnityContainer container;
public UnityControllerFactory(IUnityContainer _container)
{
container = _container;
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
IController controller;
if(controllerType == null)
throw new HttpException(404, string.Format("The controller for path '{0}' could not be found or it does not implement IController.",
requestContext.HttpContext.Request.Path));
if(!typeof(IController).IsAssignableFrom(controllerType))
throw new ArgumentException(string.Format("Type requested is not a controller: {0}",
controllerType.Name),
"controllerType");
try
{
controller = container.Resolve(controllerType) as IController;
}
catch (Exception ex)
{
throw new InvalidOperationException(String.Format(
"Error resolving controller {0}",
controllerType.Name), ex);
}
return controller;
}
}
Any suggestions?
Thanks in advance!
This has nothing to do with your controller factory specifically, but it is something you can easily address.
If you are using a Webkit browser (Chrome specifically, Safari too- I think), a request to any website will automatically be accompanied by a request to '/favicon.ico'. The browser is attempting to find a shortcut icon to accompany your website and (for whatever reason) the default path for shortcut icons has been standardized to be '/favicon.ico'.
To avoid the error you're getting, simply define an IgnoreRoute() within the routing table of your MVC web application:
RouteTable.Routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.([iI][cC][oO]|[gG][iI][fF])(/.*)?" });
This will ensure that any request to '/favicon.ico' (or '/favicon.gif') will not be handled by MVC.
I have seen this done as well:
catch (Exception ex)
{
/*throw new InvalidOperationException(String.Format(
"Error resolving controller {0}",
controllerType.Name), ex);*/
base.GetControllerInstance(requestContext,controllerType);
}
I need to globally redirect my users if a custom error is thrown in my application. I have tried putting some logic into my global.asax file to search for my custom error and if it's thrown, perform a redirect, but my application never hits my global.asax method. It keeps giving me an error that says my exception was unhandled by user code.
here's what I have in my global.
protected void Application_Error(object sender, EventArgs e)
{
if (HttpContext.Current != null)
{
Exception ex = HttpContext.Current.Server.GetLastError();
if (ex is MyCustomException)
{
// do stuff
}
}
}
and my exception is thrown as follows:
if(false)
throw new MyCustomException("Test from here");
when I put that into a try catch from within the file throwing the exception, my Application_Error method never gets reached. Anyone have some suggestions as to how I can handle this globally (deal with my custom exception)?
thanks.
1/15/2010 edit:
Here is what is in // do stuff.
RequestContext rc = new RequestContext(filterContext.HttpContext, filterContext.RouteData);
string url = RouteTable.Routes.GetVirtualPath(rc, new RouteValueDictionary(new { Controller = "Home", action = "Index" })).VirtualPath;
filterContext.HttpContext.Response.Redirect(url, true);
You want to create a customer filter for your controllers / actions. You'll need to inherit from FilterAttribute and IExceptionFilter.
Something like this:
public class CustomExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == typeof(MyCustomException))
{
//Do stuff
//You'll probably want to change the
//value of 'filterContext.Result'
filterContext.ExceptionHandled = true;
}
}
}
Once you've created it, you can then apply the attribute to a BaseController that all your other controllers inherit from to make it site wide functionality.
These two articles could help:
Filters in ASP.NET MVC - Phil Haack
Understanding Action Filters
I found this answer (and question) to be helpful Asp.net mvc override OnException in base controller keeps propogating to Application_Error
Im your case the thing that you're missing is that you need to add your custom filter to the FilterConfig.cs in your App_Start folder:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomExceptionFilter());
filters.Add(new HandleErrorAttribute());
}