I have two methods in two separate controllers that I want to access, but can't seem to figure out how the custom routes should look like and if it's possible to do it without manually writing all the routes.
What I'm trying to achieve:
baseUrl/businessOwner/identity
to map to BusinessOwnerController, Identity action
baseUrl/{employee}/identity
to map to EmployeeController, Identity action
where {employee} will be a string to which I'm matching the value in the database.
Also, I want other URLs to be able to map themselves to their default behavior, such that:
baseUrl/accountant/identity
will not match on the previous routes, but will go to the default behavior: accountant - AccountantController, Identity action.
The only way for this to work (as far as I've found) was to manually map the routes in RegisterRoutes.
I've tried doing a custom route:
routes.MapRoute(
"BusinessOwner",
"businessOwner/{action}/{id}",
new { controller = "BusinessOwner", action = "Identity", id = UrlParameter.Optional }
);
before the default route, but that doesn't really work out, as the "Multiple controller match" error still pops up.
I've also tried separating the controllers into different namespaces within the same Controllers folder and do the routing based on that, but still unsuccessful.
Any ideas?
Look at it this way: You are using the same part of the URL to hold either an employee identifier or a string constant "BusinessOwner," which acts as a magic string to invoke the BusinessOwnerController instead of the EmployeeController.
Set up your main routes to always go to the EmployeeController, and then add in a special check in the EmployeeController which will pass control to the BusinessOwnerController if the special employee ID of "BusinessOwner" is detected.
class EmployeeController : Controller
{
public ActionResult Identity(string employeeID)
{
if (employeeID == "BusinessOwner")
{
var businessOwnerController = new BusinessOwnerController();
businessOwnerController.ControllerContext = new ControllerContext(
this.ControllerContext.RequestContext,
businessOwnerController
);
return businessOwnerController.Identity();
}
else
{
//Do employee stuff
}
}
}
Related
For every web page within my ASP.NET Core 3.1 C# application, I want to automatically generate an canonical link for SEO purposes.
Currently (POC phase), I have the following snippet in every controller action function:
Uri actualUrl = new Uri($"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.Path}{HttpContext.Request.QueryString}");
RouteValueDictionary values = RouteData.Values;
values.Remove("controller");
values.Remove("action");
foreach (var q in HttpContext.Request.Query)
values.Add(q.Key, q.Value);
// Further route/query parameter "fixes" here.
Uri canonicalUrl = new Uri(Url.ActionLink(values: values));
if (!canonicalUrl.Equals(actualUrl))
return RedirectPermanentPreserveMethod(canonicalUrl.ToString());
This snippet first builds a Uri with the current actual URL. Then it may "fixes" some important route/query parameters (as shown below). Finally it compares the actual uri with the desired uri, and redirects to the desired uri, when the actual uri is different compared to the desired uri (case sensitive).
RouteData.Values["subpage"] = "Digital-Contents";
This process enables the web application to generate the correct canonical url ( http://example.com/MyController/MyAction/Digital-Contents ) for the following sample urls.
http://example.com/mycontroller/myaction/digital-contents
http://example.com/Mycontroller/Myaction/Digital-contents
http://example.com/myconTROLLer/myACTion/digital-Contents
However, the POC is a massive duplication of code, and thus not desirable itself.
My first thought was to use a middleware. However, with an middleware, the action controller cannot "fix" route/query parameters, which are out-of-scope of the regular routing construct (like the "id" route parameter which is shown in most ASP.NET examples). E.g. ActionLink is capable of producing the correct case sensitive url slugs for controller and action, but cannot process ("fix") other route/query parameters.
My second thought was to use a generic class, but there I lose the context.
The best solution would be a single (void) function call, which can be placed before the actual action heavy processing (inside the action controller before processing data and generating output).
How to deduplicate this "automatic canonical redirector" code?
This may not be the best solution, i just modified the case based on the code you provided:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CanonicalUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
Uri actualUrl = new Uri($"{httpContext.Request.Scheme}://{httpContext.Request.Host}{httpContext.Request.Path}{httpContext.Request.QueryString}");
RouteValueDictionary values = filterContext.RouteData.Values;
values.Remove("controller");
values.Remove("action");
foreach (var q in httpContext.Request.Query)
values.Add(q.Key, q.Value);
// Further route/query parameter "fixes" here.
Uri canonicalUrl = new Uri(new UrlHelper(filterContext).ActionLink(values));
if (!canonicalUrl.Equals(actualUrl))
filterContext.Result = new LocalRedirectResult(canonicalUrl.ToString());
}
}
Usage
[CanonicalUrl]
public class HomeController : Controller {
}
If you're using names from view models to generate urls like example.com/some-category/some-product then i would use the helper in this Link to generate a slug in kebab case based on the model name (in my case its saved to db on model creation)
then with a custom route :
endpoints.MapControllerRoute(
name: "category",
pattern: "{Category}/{Product}",
defaults: new { controller = "Product", action = "Index" });
This pattern omits the action and controller names from route (which i prefer)
and gives you something like this example.com/some-category/some-product and in your action you just compare the model's slug with the route segment that is provided by the user (using the route contraint) like this:
public async Task<IActionResult> Index([FromRoute,Required] Category,[FromRoute,Required] Product)
and you do a route redirect on mismatch like this:
return RedirectToRoutePermanent("category", new { Product = Product.Slug, Category = Product.Category.Slug });
Hope this helps.
I am using this route to map all routes which were not found:
routes.MapRoute(
name: "NotFound",
template: "{*url}",
defaults: new { controller = "Home", action = "NotFound" }
);
The problem I encountered is that #Url.Action() always returns null on this route.
Could someone explain why is this happening and what could be alternatives to this?
you have to add the below code before the app.UseMvc(...[Routing] ...), make sure you use it in the right order because the order really matters, the pipeline in asp.net core is in reverse order meaning that if you add A before B then B will be called before A, you can read more here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1#order
app.UseStatusCodePagesWithReExecute("/error/{0}");
and in the controllers consider an ErrorController which contains different error codes here we just consider 404 and 500 errors in ErrorController, we must have the corresponding views for each error code (404, 500, Unknown)
public class ErrorController : ControllerBase
{
[Route("error/{code:int}")]
public ActionResult Error(int code)
{
switch (code)
{
case 404: return View("404");
case 500: return View("500");
default: return View("Unknown");
}
}
}
for more concise description please check Microsoft documentation here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-2.1
I suppose you call #Url.Action() either with route parameters (e.g. controller = "Home", action = "NotFound") or with route name - that makes no difference - to generate a URL to your not found page. Well, you said the URL can be any thing which effects in in-bound routing and it's just fine. But, when you are trying to generate URLs from a route (out-bound routing), the route hasn't any template to generate a URL. In route's perspective, it can be any URL. So, null is any URL too. So, the null will be returned.
After playing around, I found a "cheat" way to do it and it kinda works. If I get NotFound page then I redirect back to the same page if Url.Action() == null
if(this._urlService.Action() == null) //same urlHelper action, only has default Home page values passed into method
{
var query = string.Empty;
if(Request.QueryString.HasValue)
{
query = Request.QueryString.Value;
}
var path = "/Home/NotFound" + query;
path = string.Concat("/", this._userIdentity.CurrentLanguage.ToString().ToLower(), path);
return base.Redirect(path);
}
It could be because I use /{culture}/{Controller}/{Action} as my main route. Creating other test project, where my main route is default /{Controller}/{Action} has no problem at all finding the Url.Action() on NotFound page
I have a requirement to add specific functionality to an asp.net mvc2 web site to provide addtional SEO capability, as follows:
The incoming URL is plain text, perhaps a containing a sentence as follows
"http://somesite.com/welcome-to-our-web-site" or
"http://somesite.com/cool things/check-out-this-awesome-video"
In the MVC pipeline, I would like to take this URL, strip off the website name, look up the remaining portion in a database table and call an appropriate controller/view based on the content of the data in the table. All controllers will simply take a single parameter bieng the unique id from the lookup table. A different controller may be used depnding on different urls, but this must be derieved from the database.
If the url cannot be resolved a 404 error needs to be provided, if the url is found but obsolete then a 302 redirect needs to be provided.
Where the url is resolved it must be retained in the browser address bar.
I have had a look at the routing model, and custom routing and can't quite work out how to do it using these, as the controller would not be predefined, based on a simple route. I am also unsure of what to do to provide 404, 302 back to the headers also. Perhpas I need a custom httpmodule or similar but going there went beyond my understanding.
This must be possible somehow... we did it years ago in Classic ASP. Can anyone help with some details on how to achieve this?
Well, the simplest way would be to have an id somewhere in the url (usually the first option)
routes.MapRoute(
"SEORoute", // Route name
"{id}/{*seostuff}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, seostuff = UrlParameter.Optional } // Parameter defaults
);
In your controller you'd have something like
public class HomeController {
public ActionResult Index(int id) {
//check database for id
if(id_exists) {
return new RedirectResult("whereever you want to redirect", true);
} else {
return new HttpNotFoundResult();
}
}
}
If you don't want to use the id method you could do something else like...
routes.MapRoute(
"SEORoute", // Route name
"{category}/{page_name}", // URL with parameters
new { controller = "Home", action = "Index", category = UrlParameter.Optional, pagename = UrlParameter.Optional } // Parameter defaults
);
public ActionResult Index(string category, string page_name) {
//same as before but instead of looking for id look for pagename
}
The problem with the latter is that you would need to account for all types of routes and it can get really difficult if you have a lot of parameters that match various types.
This should get you in the right direction. If you neeed some clarification let me know and I'll see if I can write a specific route to help you
Additional
You could probably do what you're looking for like
public ActionResult Index() {
//Create and instance of the new controlle ryou want to handle this request
SomeController controller = new SomeController();
controller.ControllerContext = this.ControllerContext;
return controller.YourControllerAction();
}
but I don't know any of the side effects by doing that...so it's probably not a good idea - but it seems to work.
How do i map multiple url's to the same action in asp.net mvc
I have:
string url1 = "Help/Me";
string url2 = "Help/Me/Now";
string url3 = "Help/Polemus";
string url1 = "Help/Polemus/Tomorow";
In my global.asax.cs file i want to map all those url to the following action:
public class PageController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
}
Now in MVC 5 this can be achieved by using Route Attribute.
[Route("Help/Me")]
[Route("Help/Me/Now")]
[Route("Help/Polemus")]
[Route("Help/Polemus/Tomorow")]
public ActionResult Index()
{
return View();
}
Add the following line to your routing table:
routes.MapRoute("RouteName", "Help/{Thing}/{OtherThing}", new { controller = "Page" });
EDIT:
foreach(string url in urls)
routes.MapRoute("RouteName-" + url, url, new { controller = "Page", action = "Index" });
In my case I was looking to simply combine two 'hardcoded' routes into one and stumbled upon this post. I wanted to clean out my RouteConfig.cs a little - because it had so many similar routes.
I ended up using some simple 'or' logic in a regular expression and basically changed:
routes.MapRoute(
"UniqueHomePage",
"Default",
new { controller = "Redirector", action = "RedirectToRoot" }
);
routes.MapRoute(
"UniqueHomePage2",
"Home",
new { controller = "Redirector", action = "RedirectToRoot" }
);
Into a single route:
routes.MapRoute(
"UniqueHomePageGeneric",
"{url}",
new { controller = "Redirector", action = "RedirectToRoot" },
new { url = "Home|Default" }
);
Note for the SEO-savy or -interested: The reason for pointing multiple URL's to one and the same action, is to then redirect them to one and the same page again. In this case the homepage. So the idea is to prevent duplicate content issues. When you use this method for pointing for NON redirecting actions, but actions that show their own views, then you might be CAUSING duplicate content issues :P.
You can just add the routes into your route table as you need them, same as any other route. Just give them unique names, hard coded URL and point them to the same controller / action. It will work fine.
If you use pattern matching instead of hard coded URLs just make sure you get all your routes in the right order so the correct one is selected from the list. So /Help/Me should appear before /Help/{Page} if the hard coded route goes to a different page to the pattern matched one. If you put /help/{page} in the route tabel 1st this will match to /help/me and your hard coded named action for that route would never fire.
On a side note, if this is a public facing site and SEO is important please be careful if you have multiple URLs returning the same data, it will be flagged as duplicate. If this is the case, then use the Canonical tag, this gives all the page rank from all the URLS that go to that single page to the one you name and removes the duplicate content issue for you.
I'm having some trouble with ASP.NET MVC Beta, and the idea of making routes, controller actions, parameters on those controller actions and Html.ActionLinks all work together. I have an application that I'm working on where I have a model object called a Plot, and a corresponding PlotController. When a user creates a new Plot object, a URL friendly name gets generated (i.e.). I would then like to generate a "List" of the Plots that belong to the user, each of which would be a link that would navigate the user to a view of the details of that Plot. I want the URL for that link to look something like this: http://myapp.com/plot/my-plot-name. I've attempted to make that happen with the code below, but it doesn't seem to be working, and I can't seem to find any good samples that show how to make all of this work together.
My Route definition:
routes.MapRoute( "PlotByName", "plot/{name}", new { controller = "Plot", action = "ViewDetails" } );
My ControllerAction:
[Authorize]
public ActionResult ViewDetails( string plotName )
{
ViewData["SelectedPlot"] = from p in CurrentUser.Plots where p.UrlFriendlyName == plotName select p;
return View();
}
As for the ActionLink, I'm not really sure what that would look like to generate the appropriate URL.
Any assistance would be greatly appreciated.
The answer is pretty simple: You have to supply enough values in your "ActionLink" that will fulfill your Route. Example:
<%= Html.ActionLink("Click Here", "ViewDetails", "Plot", new { name="my-plot-name" }, null)%>
If you leave out the "name=" part of the ActionLink method, then the RouteEngine won't see this link as being good enough to "match"... so then it would go to the default route.
This code above will make the URL look the way you want it.
How about this code-fix? (Note the name = null, appened to the end of the 4th line....)
routes.MapRoute(
"PlotByName",
"plot/{name}",
new { controller = "Plot", action = "ViewDetails", name = null }
);
and this should be renamed.. (notice plotName is renamed to name)
public ActionResult ViewDetails(string name ) { ... }
does that help?