views:

61

answers:

4

What I would really like to be able to do is redefine the conventions to do the following:

For Controllers and their respective Actions:

If a URL request is made, and there is not a controller with the supplied action, then perform some "default" function, which I would supply myself in the application. I would think this could be achieved by using a Func<>, but I'm not sure where to plug this in.

For Views:

If a controller's action is requesting for a View, and there is no View that matches the one that the controller's action is requesting for, return this "default" view.

Is this something that is possible, and if so where should I be digging in order to learn more about how to do this? Or is it a really simple thing to do?

EDIT

Here's an example of what I'm trying to achieve.

I have a very simplistic view, something akin to this:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 

Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    <%: Html.LabelForModel() %>
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <%: Html.EditorForModel() %>

</asp:Content>

So let's say I have a Customer class, and the controller action does something with a Customer object, and then does

return View(someCustomer);

The issue here, is that I have not defined any View to handle Customer. In this case, I want my view engine (or whatever is responsible), to say, "ok there's no view that directly handles Customers, I'll use the default view instead".

A: 

You could do it like this. I'm not sure of any performance implications or any other issues that might arise from capturing all 404s (including images and other content files)

protected void Application_Error(object sender, EventArgs e) {
    HttpException httpException = Server.GetLastError() as HttpException;
    if (httpException != null) {
        switch (httpException.GetHttpCode()) {
            case 404:
                //we couldn't find the controller or action

                //setup a new route to your default controller assigned
                //to handle unknown routes
                RouteData routeData = new RouteData();
                routeData.Values.Add("controller", "YourDefault");

                //create an instance of your controller and execute it
                IController yourDefaultController = new YourDefaultController();
                yourDefaultController.Execute(new RequestContext(
                        new HttpContextWrapper(Context), routeData));
                break;
        }
    }

}
BuildStarted
A: 

Controllers & Actions

I think you might want to create a custom controller factory. I haven't done this myself, but it should be an easy google (touch wood).

Views

You should look into creating a custom view engine (or just extending the default view engine) to look for different the different default files.

HTHs,
Charles

Charlino
Yeah unfortunately googling hasn't proven very useful in this regard. I might not be using the right terminology or something. The problem with the default view engine is that it's great if I want to just add a new place (folder) to "look" for views, but it's not so great in telling it "if you can't find a view for a Person model, then just use this DefaultView I've created" The reason why DefaultView won't be seen I think, is because I made it a View<dynamic>, unless the viewengine would actually see that.
Joseph
+1  A: 

You are asking for a number of different components.

First, create a 'DefaultController' with a 'Default' action.

To capture a request for a non existant controller, you need to override the controller factory:

protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
    try
    {
        return base.GetControllerInstance(requestContext, controllerType);
    }

    catch (HttpException ex)
    {
        int httpCode = ex.GetHttpCode();
        if(httpCode == (int)HttpStatusCode.NotFound)
        {
            IController controller = new DefaultController();
            ((DefaultController)controller).DefaultAction();
            return controller;
        }
        else
        {
            throw ex;
        }
    }
}

Then register this controller factory in the Global.asax startup.

To capture the case when an existing controller is called, but the action doesn't exist, override the HandleUnknownAction method, preferably in a base class:

public class BaseController : Controller
{
    protected override void HandleUnknownAction(string actionName)
    {
        RouteData.Values["action"] = "DefaultAction";

        if ( this.ActionInvoker.InvokeAction(this.ControllerContext, "DefaultAction"))
               return;

        base.HandleUnknownAction(actionName);
    }
}

Finally, to get a 'default'' view, you need to override the default view engine:

public class MyWebFormViewEngine : WebFormViewEngine
{
    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        ViewEngineResult result = null;

        result = base.FindView(controllerContext, viewName, masterName, useCache);

        if (result == null || result.View == null)
           result = base.FindView(controllerContext, "Default", masterName, useCache);

        return result;
    }
}

and register this in your Global.asax:

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new MyWebFormViewEngine());

    RegisterRoutes(RouteTable.Routes);
// etc
}

Hope that helps...

Clicktricity
The controller factory is spot on, thanks for that. For the view piece, where would that be put? Does that belong at the controller level, or are you saying I need to override asp.net mvc's view engine possibly?
Joseph
Added a better way of finding a default view. Gotta love the way MVC allows everything to be overridden...
Clicktricity
That is EXACTLY what I wanted to know, thanks!
Joseph
+2  A: 

Why not solve controller/action problem with routing?

Routing can have constraints, right? So why not solve the problem with non existing controller/action using that?

routes.MapRoute(
    "Root",
    "",
    new { controller = "Home", action = "Index" }
);

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { action = "Index", id = UrlParameter.Optional },
    // constraints for certain controllers (add actions if needed as well)
    new { controller = "Home|ControllerOne|ControllerTwo|..."}
);

// catch any request route and handle by the same controller/action
routes.MapRoute(
    "NonExisting",
    "{path*}",
    new { controller = "Default", action = "Any" }
);

Regarding views

If your controllers will always be caught, you shouldn't worry about views anyway. So if your last route resolves to anything and always displays the same content, it should never happen for a controller to request a non-existing view.

Edit about views (based on your comment)

If I understand the point of views in your comment, you will have to create a separate view engine that could solve everything by just providing an additional view path to an exact aspx file.

Default engine defines view paths with variables in them like Views/{0}/{1}.aspx. You could then add an additional line like Views/General/Default.aspx that would do the trick. Whe it will be searching for a particular view, it will match one if it exists but if it doesn't it will graciously fall down to default view.

Robert Koritnik
I think I might not be stating it properly. I don't just want a "catch all" view. I want a view that will be used 95% of the time by all my models, and only if I choose to specify a view for a specific kind of model, should it be overridden. This way I'm not having to build a ton of views for different models all the time. I already know how I want my default view to be constructed, I just don't know how to tell asp.net mvc to use it if there isn't an explicitly defined view already.
Joseph
@Joseph: I guess the best way to explain your situation would be to edit your question and add an actual example so we'll have a better perspective of it.
Robert Koritnik