Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC4 Searching for controller in wrong area

I'm using default MVC routing setup:

        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

I have area defined as:

public class AdministrationAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "Administration";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "Administration_default",
                "Administration/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }
    }

And I have a controller in that area:

namespace XXX.Areas.Administration.Controllers
{
    public class CountryController : Controller
    {
        public ActionResult Index()
        {
///
        }
    }
}

When I type

/Administration/Country

it works good as it is desired.

When I type

/Country

action still gets invoked, though view is not found so I get an error.

Why is MVC accepting

/Country

as valid route? I don't have another CountryController in non-area zone.

like image 541
Admir Tuzović Avatar asked Jun 20 '13 21:06

Admir Tuzović


2 Answers

Add NameSpace in the Global.asax file for the default Route.

var route = routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}", // URL with parameters
    new { controller = "Home", action = "Index" }, // Parameter defaults,
    new[] { "YourNameSpace.Controllers" }
);

Add NameSpace in the AreaRegistration class present in your Area

public class MyArea : AreaRegistration
{
    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            "test",
            "Test/{controller}/{action}/{id}",
            new { action = "Index", id = UrlParameter.Optional },
            new[] { "MyProjectNameSpace.Controllers" }
        );
    }
}

Explanation

I have a following area in my application. So the below highlighted section is out concerned Controller. ok.

Figure -1

enter image description here

I typed Url : http://localhost:2474/ActionFilterAttribute/index

Before moving toward the destination. I will show you some how I initialized my test. I added a Reference of RoureDebugger. You can get the Dll from this location. Then I added a line of code in my Global.asax file under Application_Start Handler.

RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

So, finally using the above mentioned Url, I started debugging the application. Finally I saw the below picture.



Figure -2

enter image description here

Question

action still gets invoked, though view is not found so I get an error.

Answer

So if you pay attention to the above highlighted Route, well, this is the Default Route. This pattern is matched with Url as mentioned above. But View will not be found in this case and that's the reason your Controller Action Method is Invoked.


Before moving to the next part that why did I get 404. I will show you some test I did in my sample application.

I created a class which derives from ActionFilterAttribute like below. This contains only one Override that's called OnResultExecuting. This Handler executes before executing the View corresponding to particular Action

The purpose of Creating this class is just to verify what is happening with RouteData and DataTokens.

public class MyActionFilter : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var viewResult = filterContext.Result as ViewResult;

        if (viewResult != null)
        {
            var razorEngine = viewResult
                                        .ViewEngineCollection
                                        .OfType<RazorViewEngine>()
                                        .Single();

            var viewName = !String.IsNullOrEmpty(viewResult.ViewName) ? 
                                                  viewResult.ViewName :         
                  filterContext.RouteData.Values["action"].ToString();

           var razorview = razorengine
                           .FindView
                           (
                               filtercontext.Controller.ControllerContext, 
                               viewname, 
                               viewResult.MasterName, 
                               false
                           ).View as RazorView;
        }

        base.OnResultExecuting(filterContext);
    }
}

Ok. Let's come back to the original question. Why did I get 404?

Your Controller will be picked up by the default base route {controller}/{action} before it checks the Area Route and therefore will look for the View in the Root/Views instead of in the Area/views.

To examine this, I set the debugger in the Action Method of Controller inside the Area and found that that there is no DataToken Information when the Requested url is without Area Name. let's see more details about DataToken in Debug Mode


Figure -3

enter image description here

If you pay attention to the ControllerContext, I enumerated the DataTokens, which is showing no key/Value. It's because no view is found pertaining to that controller under the Root Directory

How can you say that the currently located Directory is Root Directory? Proof is below




Figure -4

enter image description here

There is no Namespace or any Area mentioned in the RouteData values. right?

Now let's move to the RouteData that matched the pattern which is containing the Area Name. So, this time my Url is : http://localhost:2474/mypractise/ActionFilterAttribute/index and below is the RouteData matched by URLRoutingModule




Figure -5

enter image description here

Please pay attention to the highlighted section, this time the Route matched belongs to AreaName pattern and matched value is false for the Default Route which belongs to some RouteData at Root Directory. Right?

My final details for the DataTokens in case of above mentioned requested Url. You can see the Namespace details and Area details this time.


Figure -6

enter image description here



Conclusion :

When the Controller is inside the Area and your DataTokens are not showing the information for Area , NameSpace and UseNameSpaceFallback informations. That means you will get 404. As mentioned in Figure-4, your requested Url was correct, so you got the DataTokens and As mentioned in Figure 3, DataTokens were not shown because the requested Url does not contains the Area Name and despite of the fact that you have got the RouteData as mentioned in Figure 2 because it's a default Url Pattern. Finally try to execute the third line of code in OnResultExecuting. It will show null because View is not found.

Hope this explanation will help you.

like image 160
Imad Alazani Avatar answered Oct 26 '22 04:10

Imad Alazani


Check it. Modify the default route in your Global.ascx.cs file like so.

var route = routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    namespaces: new string[] { "APPLICATION_NAMESPACE.Controllers.*" }
);

route.DataTokens["UseNamespaceFallback"] = false;

EDIT:

My apologies. It seemed like you didn't want it to do this as well as know why.

You are running into the fact that the default routing will look for anything that is a controller. Even if it's in an Area. You can overcome this default behavior by simply adding the namespaces parameter to the route and specify what the default routing should be looking for with controllers.

The solution that I provided above is merely a fix if you wanted to not serve the view of an area outside the area itself.

There is a great article on why this is occurring here.

like image 22
technicallyjosh Avatar answered Oct 26 '22 05:10

technicallyjosh