Routing / URL generation issues - asp.net-core

I'm currently building an FAQ system which has the following URL structure:
Parent category: /faqs/parent-category/ (eg /faqs/products/)
Child article within a parent category: /faqs/parent-category/child-article/ (eg /faqs/products/how-to-build-my-product/)
I have the following routes set up:
routes.MapRoute(
name: "Faq.Main",
template: "{controller}/{parentSlug}/",
defaults: new { controller = "Faqs", action = "Index" }
);
routes.MapRoute(
name: "Faq.Article",
template: "{controller}/{parentSlug}/{slug}/",
defaults: new { controller = "Faqs", action = "View" }
);
The URL's generate fine for the parent, however I'm having an issue with the child articles. An example generated URL is as follows: /faqs/how-to-build-my-product/view/?parentSlug=products
The code in the View to create these links is as follows:
<a asp-controller="Faqs" asp-action="View" asp-route-parentSlug="#faq.ParentSlug" asp-route-slug="#faq.Slug">#faq.Title</a>
As you can see the URL generated for the child article isn't formatted correctly. It needs to be /faqs/products/how-to-build-my-product/
Any ideas how I can rectify this?
Thanks in advance.

Related

Unable to target different area in asp.net core mvc

When i am using the asp-route-area tag helper to target a different area. The URL does not target it.
I have put the below code in the VIew of my Index method of 1 area:
<a asp-route-area="Employee" asp-action="List" asp-controller="Home">Link3</a>
The link formed is not targetting the Employee area. The link formed is :
Link3
It should be:
<a href="/Employee/home/list/>Link3</a>
My route is:
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}");
//Default Route
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
what's wrong?
The correct syntax is asp-area="Employee".
asp-route-* tag helper is used to populate route parameters if they are defined on the target action e.g. [Get("path/{id}")] can be populated by asp-route-id="1".
If no matching route parameter can be found it is added to the query string, hence ?area=Employee is added to your URL because you don't have a matching route parameter named area.
According to this link
Once you've defined the folder hierarchy, you need to tell MVC that each controller is associated with an area. You do that by decorating the controller name with the [Area] attribute.
Further below, its says that in the route configuration you should use areaRoute instead of areas
In your case I think it should be:
<a asp-route-area="areas" asp-area="Employee" asp-action="List" asp-controller="Home">Link3</a>
I found that I did not added the attribute [Area("Employee")] on my Home Controller of Employee area.
Adding that attribute solved my problem.
using Microsoft.AspNetCore.Mvc;
namespace URLRouting.Areas.Employee.Controllers
{
[Area("Employee")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}

Optional middle parameter in MVC4 Route

I am trying to create a controller action that receives several parameters for filtering a scope and then the final parameter is a required paging parameter for paging the results.
//Example Action
public ActionResult Details(string time, string regionscope, string localscope = null, int page = 1) {
}
//RouteConfig.cs
routes.MapRoute(
name: "LocationWithLocalScopeRoute",
url: "/Details/{time}/{regionscope}/{localscope}/{page}",
defaults: new
{
controller = "Location",
action = "Details",
localscope = UrlParameter.Optional
}
);
Is there anyway I can exclude the localscope parameter but still have the page parameter be interpreted as the page number by the controller (while still maintaining pretty URLs)?
// Test URLs
/Details/May-17/Mid/1
/Details/May-17/Mid/Bumville/1
I know I could add a route before the existing route:
//RouteConfig.cs
routes.MapRoute(
name: "LocationWithNoLocalScopeRoute",
url: "/Details/{time}/{regionscope}/{page}",
defaults: new
{
controller = "Location",
action = "Details"
}
);
I also realize I could put page before any of the other parameters.
routes.MapRoute(
name: "LocationWithLocalScopeRoute",
url: "/Details/{page}/{time}/{regionscope}/{localscope}",
defaults: new
{
controller = "Location",
action = "Details",
localscope = UrlParameter.Optional
}
);
Was just wondering if there was a way to make it work all in one route definition while still having the Test URLs work.
No, not without creating another specific route or making localscope the last parameter.
When using routing, only the last parameter can be marked as UrlParameter.Optional. If more that one is optional, then the routing engine has no way to know which route segment applies to which method parameter.
In your case when the url is ../Details/May-17/Mid/1, is the last segment with the value of 1 the value for localscope or the value for page? There is no way to determine that, so if you were to generate the url using #Url.Action() (or one of the other methods that generate url's), then the url will be generated with query string values, not route values.

Create Link to level up controller using Razor in ASP.NET MVC4

This is my code that i'm using to create a link to top level link:
#Html.ActionLink("Edit",Url.Action("Edit","Home", new{row.Application_ID}))
// supposed to return /Home/Edit/x (application_id)
Instead, it returns:
/Home/Home/Edit/x // Current View is Home/Edit
Please help.
Edit 1:
Route Table information:
//This is for Edit
routes.MapRoute(
"Edit", // Route name
"Home/Edit/{application_id}", // URL with parameters
new { controller = "Home", action = "Edit", application_id = UrlParameter.Optional } // Parameter defaults
);
// This is the default route, I've modified it to LogOn.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Account", action = "LogOn", id = UrlParameter.Optional } // Parameter defaults
);
As a small edition to haim770 answer. if you want for some reason use Url.Action instead Html.ActionLink you are always free to write it like that:
Edit
Some developers prefere this way because it more similar to plain Html, but result is the same as Html.ActionLink
You don't need the inner #Url.Action, try this instead:
#Html.ActionLink("Edit", "Edit", "Home", new { application_id = row.Application_ID }, null);
tried this work around and it worked:
Edit
now it returns the link as Home/Edit/x :)

Generate wrong outgoing url because of segment variable reuse in MVC 4

Here is my RouteConfig.cs
routes.MapRoute(null,
"{controller}/Page{page}",
new {controller = "Product", action = "Index", category = (string) null},
new {page = #"\d+"}
);
routes.MapRoute(null,
"{controller}/{category}",
new {controller = "Product", action = "Index", page = 1}
);
routes.MapRoute(null,
"{controller}/{category}/Page{page}",
new {controller = "Product", action = "Index"},
new {page = #"\d+"}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And here is the code for generating url:
#Html.ActionLink("View Cart", "Index", "ShoppingCart", null, new { #class = "btn btn-orange" })
It works well when I navigate to, for example, Product/Page2, Product/Laptop, Product/Laptop/Page2. The problem is, whenever my current URL contains Page segment, it will try to reuse that segment for generating outgoing URL. So, if I'm at Product/Page2 the above generated URL would be ShoppingCart/Page2. I don't know how to avoid this.
Please help me. Thank you so much.
EDIT!!!
I've found a workaround way. Instead of using ActionLink, I use RouteLink like this:
#Html.RouteLink("View Cart", "Default", new { controller = "ShoppingCart", action = "Index" }, new { #class = "btn btn-orange" })
But I still want to use ActionLink, so please help me.
EDIT!!!
The workaround way doesn't work when I generate a link to ShoppingCart/Checkout. It still take me to Index action in ShoppingCart controller.
Create a new route pattern specific to ShoppingCart and make it the first route by placing it at the TOP.
routes.MapRoute(null,
"ShoppingCart/{action}",
new {controller = "Product"});
);
As a rule all the specific routes should come first.
This is because of the way the routing system tries to evaluate the value of segment variables when trying to match against a route.
So when the call to render the link occurs with the following arguments:
#Html.ActionLink("View Cart", "Index", "ShoppingCart", null, new { #class = "btn btn-orange" })
the framework when evaluating the route with template
{controller}/Page{page}
will resolve the controller segment variable to be ShoppingCart however when it cannot find a value for the page segment variable (via any of the arguments in the method call), it will then try and resolve that value from the RouteData object in the ViewContext. Since you have navigated to Product/Page2, the current value of page within the routes value dictionary is 2.
You can inspect this by looking at the value of ViewContext.RouteData.Values["page"] when rendering that view.

What Causes the browser URL line to get filled with Route in MVC4 asp.net

When I go to my home page
http://localhost:5119/
it redirects to
http://localhost:5119/Home
(that is what shows in my browser url bar, I want it to not show /Home)
I understand that is where my default route goes but what I can't figure out what is causing my URL line to be rewritten in the browser. The default example in Visual Studio 2012 does not have this issue when you go to the base URL. I'm attaching a picture of my route debug (that does not seem to help me, but may have some value).
Thanks, -Peter
Adding This After. It is relevant parts of route code
// HOME
int currentYearInt;
Int32.TryParse(currentYear, out currentYearInt);
routes.MapRoute("HomeRouteAll", "Home/{yearInt}",
new
{
/* Your default route */
controller = "Home",
action = "Index"
});
// DEFAULT ROUTE
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
You are seeing this because of the route Home/{year} above your default route that has a default value for the {year} parameter.
The route engine makes the following decisions:
Start at top of route list
Look for something that matches what your route values (controller = "Home", Action="Index").
At the first match, return and that is your URL.
Since you have a matching controller (Home) and action (Index) as well as a default value for the year parameter, the route engine is matching to the route Home/{year}, thus giving the url http://domain.com/Home.
The quick fix would be either a) make year not have a default value (Home/2013), b) move whatever that is over to a different controller (NewName/{year}), c) move that to a different action (NewIndex/{year}) or d) update your default route to use the year parameter instead of id
routes.MapRoute(
"Default",
"{controller}/{year}/{action}",
new {controller = "Home", action = "Index", year = 2013});
EDIT
I am not really sure what you have as far as the tryParse stuff in your route definitions, but in my testing, this seemed to accomplish what you are wanting to do:
routes.MapRoute(
name: "Test",
url: "Home/{year}",
defaults: new { controller = "Home", action = "Index"},
//this line causes the year to be an integer
constraints: new { year = #"\d+" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Behaviors:
http://domain.com/ -> calls Home controller, Index action with a null value for the year parameter (no redirect to Home/2013)
http://domain.com/Home/2010 -> calls Home Controller, Index action with 2010 for the year parameter
http://domain.com/Home/ -> call Home Controller, Index action with null value for the year.
If you define a default year in the first of the two routes, then going to http://domain.com/Home will call Home controller, Index action with 2013 for the year and the redirect will still not occur.
Lastly, I suspect your Home/Index action looks something like this:
public ActionResult Index(int year){...}
If when you hit the Home/Index action, you want to automatically have the 2013 populated, then change your int to a nullable parameter and do it there rather than your routes.
public ActionResult Index(int? year){
if(!year.hasValue){
year = 2013;
}
By performing this logic here instead of the route, you should prevent the redirect to Home/ as you have no matching {year} parameter.