MVC5 Url.Action returns null inside area - c#

We have a MVC application with different areas. At moment just one area is registered (defined in config) and no route registrations is done in area registration. I have run into issues with routing as Url.Action method stopped to work. My simplified RouteConfig looks like this:
routes.MapRoute(
name: "Second",
url: "second/{action}/{id}",
defaults: new { controller = "Second", action = "Action", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Areas.MyArea.Controllers" }
).DataTokens["area"] = "MyArea";
routes.MapRoute(
name: "Home",
url: "home/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Controllers" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Controllers" }
);
The problem is that when I try to call #Url.Action("Index", "Home") inside Action.cshtml in Second controller in MyArea it returns null. When I call the same in Index.cshtml in Home controller it works well. I have found out that when I use #Url.Action("Index", "Home", new {Area = ""}) in Action.cshtml it works well too, but why this is happening? How can I avoid that to be able to call just #Url.Action("Index", "Home") without route values?

This is not a problem but it's actually the proper behavior.
When you use #Url.Action() or #Html.ActionLink it will by default use the area from where it's being called. If you want to link to a different area, as in this case, you need to specify the parameter area as you already found out.
In this particular case when you're trying to link to root area you need to specify the parameter as an empty string as you did. ({Area=""})

Related

C# MVC Route detection

I need to call customer profile page like "(www.mysite.com/John) or (www.mysite.com/customer name)"
so i had add route to be like that
routes.MapRoute(
name: "Profile",
url: "{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
but it always go to the first route as if i need to open any controller it does n`t work
any advice?
Thanks.
the route wont work like that so you have two options
1) When going to any action your URL should be www.mysite.com/controller/action or else it will give HTTP 404 error
2) You can make route unique by adding prefix www.mysite.com/Customer/John like this
routes.MapRoute(
name: "Profile",
url: "Customer/{id}",
defaults: new { controller = "Home", action = "Index"}
);
and your action should be
public ActionResult Index(string id)
{
string SurveyName = "";
if (id != null)
SurveyName = id;
if (!string.IsNullOrEmpty(SurveyName))
{
ViewBag.Survey = SurveyName;
}
return View();
}
You need to change your controller and action according to your requirement, you are still providing default values to controller and action.
You can also use Html.ActionLinkat to refer to your desired view
ex. Html.ActionLink("action_name","Controller_name")
Your route has a single optional parameter and is working like a catch all route.
[EDIT]
You can't use the constraint as I said on my previous answer if you don't have an pattern to restrict the route. Based on this you can remove the id = UrlParameter.Optional from defaults (by this change your route will be reached only when the url contains the id parameter).
routes.MapRoute(
name: "Profile",
url: "{id}",
defaults: new { controller = "Employee", action = "Index"}
);
Another option is to create a custom route constraint to check if your profile id is valid but take care since it can slow down your application since it'll be executed on every request (maybe caching your profile list could help on this).
[OLD ANSWER]
An easy way to restrict a route scope is using constraint.
The route below is reached just if the id parameter is a value with 4 digits (you can use your own regex to fit your requirements).
routes.MapRoute(
name: "Profile",
url: "{id}",
defaults: new { controller = "Employee", action = "Index"},
constraints: new { id = #"\d{4}" }
);
Make these changes. It worked for me.
routes.MapRoute(
name: "Profile",
url: "{id}",
defaults: new { controller = "NewCon", action = "Demo", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

MVC RouteConfig affected the Html.ActionLink() helper method

In my MVC project, I've Item controller and some actions like Index.
The RouteConfig includes:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
In some views, I'm using the helper method Html.ActionLink("Items","Index","Item") to create anchors for the index action. So the href of the anchor result will be (/Item/Index)
Now, I need to map the following static URL:
/IndirectItem/Index
to the Index action of the Item controller with default parameter (indirect = true), so the RouteConfig will be:
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index",
defaults: new { controller = "Item", action = "Index", indirect = true }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
It seems OK and the client requests were mapped correctly , but all anchors resulted from the Html.ActionLink("Items","Index","Item") helper method were mapped to the URL (/IndirectItem/Index) instead of (/Item/Index).
How can I fix this problem without changing all Html.ActionLink() to Html.RouteLink() or adding another route for the original url ?
Using constraints will be a convenient solution for your problem.
Use the following IndirectItem route instead of yours.
routes.MapRoute(
name: "IndirectItem",
url: "{staticVar}/{action}",
defaults: new { controller = "Item", action = "Index", indirect = true},
constraints: new { staticVar = "IndirectItem" }
);
and you don't need any change in the Default route.
It works fine with me.
You are experiencing this issue because Html.ActionLink uses Routing table for generating URLs and since IndirectItem route is match to Html.ActionLink("Items","Index","Item") (because it has Index action and Item controller specified in both route and action link). The resolving done by a first match so the order of routes registration matters
By adding DefaultItem route:
routes.MapRoute(
name: "DefaultItem",
url: "Item/Index/{id}",
defaults: new { controller = "Item", action = "Index", id = UrlParameter.Optional }
);
prior to your current routes:
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index/,
defaults: new { controller = "Item", action = "Index", indirect = true}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Should fix the issue
Another option could be creating empty IndirectItem controller that inherits from Item controller:
public IndirectItemController : ItemController
{
}
and then changing the route to
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index/,
defaults: new { controller = "IndirectItem", action = "Index", indirect = true}
);
The answers given by Omar Gohar and Alex Art are misleading.
The problem you are running into is that your route does not match when generating the URL. This is simply because you have not provided all of the route values to create a match in your ActionLink.
#Html.ActionLink("Items", "Index", "Item", new { indirect = true }, null)
If changing your ActionLink declaration is not an option, you can attach your "indirect" metadata to the route using the DataTokens parameter.
You use the DataTokens property to retrieve or assign values associated with the route that are not used to determine whether a route matches a URL pattern. These values are passed to the route handler, where they can be used for processing the request.
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index",
defaults: new { controller = "Item", action = "Index" }
).DataTokens = new RouteValueDictionary(new { indirect = true });
The bottom line is that RouteValues (which are populated by the defaults if not provided by the URL pattern) are not meant to be used for metadata. They are meant to be real data to match to make the URL unique.
Of course, if you are not actually using the indirect route value for anything, you can simply omit it from the route.
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index",
defaults: new { controller = "Item", action = "Index" }
);

How to call MVC routes from the View

I have the problem where URL's are being added on the current URL for example:
Localhost/Config -> Localhost/Config/Profile
By making the call #Html.ActionLink(Resources.General.Achievements, "Index", "Profile")
When what I really want is: Localhost/Profile
I know this is possible using MVC Routes, I just can't figure out how to call them from the View. I have used Html.BeginRouteForm, but this route won't be triggered on submit.
This is my route config
routes.MapRoute(
name: "Profile",
url: "Profile/{id}",
defaults: new { controller = "Profile", action = "Index" },
namespaces: new[] { "Cobalt.Controllers" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "Cobalt.Controllers" }
);
I am trying to access the Profile controller and Index action in the root directory. I have both Config and Manage Areas, which I am trying to escape from when making calls back to controllers in the root directory.
Thanks in advance for any help
When dealing with areas, if you want to move from one area to another area or out of an area into the root, you can use the overload of ActionLink which allows you to specify route data and pass an empty string (or the appropriate area name) into area:
#Html.ActionLink(Resources.General.Achievements, "Index", "Profile", new { area = string.Empty })
Use RouteLink:
#Html.RouteLink(Resources.General.Achievements, "Profile", new { id = "1234" })
Do note that since your "id" parameter is not declared optional on your Profile route it won't match unless you supply it.

How can i rewrite URL IN ASP.NET MVC?

I have some files under the views of the area "admin", and i want to rewrite the url of some action like "agence" and "agences" without affecting "login", so how can i do it ?
This is a link to a print screen of my project.
http://hpics.li/764e0ea
If I understand your question correctly, you can solve this issue by creating a new route in your "RouteConfig" file (App_Start -> RouteConfig).
The default route for MVC is:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
You can use this as a template for creating a new route as well. Simply place the new route above the "Default" route. Something like the following:
routes.MapRoute(
name: "Any distinct name",
url: "{controllerName (judging by your question, probably }/{variable1}/{variable2}/{etc... if needed}",
defaults: new
{
variable1 = UrlParameter.Optional,
variable2 = UrlParameter.Optional
}
);
You can then pass your chosen variables as RouteValues in ActionLinks.
#Html.ActionLink("LinkText", "Action", new { variable1 = whatever, variable2 = whateverElse})

MVC 4 custom routing str and int

I need to have a custom route, like the default one, but this one should accept numeric values as strings. Like 0015. If I leave the parameter type as int, the value passed to the controller method get truncated to 15. And I need 0015.
So what I did, I created the following:
routes.MapRoute(
name: "AccRef",
url: "{controller}/{action}/{acc_ref}",
defaults: new { controller = "Company", action = "Index", acc_ref = "" },
constraints: new { acc_ref = #"^\d{1,4}$" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And the problem is, as I understand, that when I now pass in an integer as "id"
#Url.Action("Method", "Controller", new { id = item.ref})
from the view, the routing still applies the first route to it and the call fails.
How would you go about solving this problem with routing?
Is it possible to have two same routing configurations where one accepts int and another string?
Your AccRef is too greedy.
If you look at the url generated from the Url helper it is:
Controller/Method/id
This matches your first AccRef route as well as the default route.
You have to be more specific with your routes. Also the order you define your routes are important. So you normally want to define greedier routes last.
Phil Haack has a route debugger on nuget (blog post here) which can help you identify route issues.
If you reverse the order like so:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "AccRef",
url: "{controller}/{action}/{acc_ref}",
defaults: new { controller = "Company", action = "Index", acc_ref = "" },
constraints: new { acc_ref = #"^\d{1,4}$" }
);
Your current scenario will work with above change but the url "/Home/Index/5" or "/Company/Index/0015" still matches both the routes. This is because the routes are generic (as correctly pointed out by Bigfellahull).
In your case since both the parameter is of type int, both the routes are matched.
Option 1:
You can add a extra string say "Acc" in the route url to make it more specific.
routes.MapRoute(
name: "AccRef",
url: "{controller}/{action}/{acc}/{acc_ref}",
defaults: new { controller = "Company", action = "Index", acc_ref = "" },
constraints: new { acc_ref = #"^\d{1,4}$" }
);
In this case the url will change to ".Company/Index/acc/0015".
Option 2:
If you can change the parameter type in action method like so:
public class HomeController : Controller
{
public ActionResult Index(string id)
{
}
}
The url will match only one route.
Option 1 and 2 are for example only to explain how you can make routes more specific.

Categories