ASP.NET MVC - Creating a custom Uri to Route mapping handler - asp.net-mvc

I'm looking to heavily customize the way my application handles Routes. Essentially, I want to achieve a process something like this:
User requests URL: /custom-url-part-1/custom-url-part-2/gallery/1/date
Application looks for a match in a database table of Routes
Application returns matching Route record, consisting of:
RouteUrl
Controller
Action
Parameters
Defaults
Application looks at RouteUrl and parses any optional parameters {pagenum}, {orderby}
Application checks if these have been supplied in the requested Url
If they have not been supplied, it checks the "Defaults" of the Route record to get them
These are then passed in the RouteData defaults
Parameters are passed in the RouteData defaults
Route data is returned
The core reason for this is I want to have a dynamic table of Routes, that is modified by a CMS in order to store certain data against routes.
The questions I have are:
How can I replicate the way that MVC matches a Uri to a Route? I need to essentially look through the database table and bring back the appropriate Route based on the Uri. Very similar to how the standard ASP.NET MVC functionality looks through the RouteCollections and uses the correct route.
I realise I'm essentially rewriting some functionality that MVC supplies built-in, and replacing the RoutesCollection with a database... but I need to do that in order to achieve two things:
The routes being entirely dynamic, fluid and controlled by the CMS.
Data being stored against those routes which can't be inferred from the Uri itself (so for example, I might want to send a PageId through when a user hits a particular route, but I don't want that PageId in the Uri).

Related

Declarative routing in Asp.NET MVC

We are converting an existing Webforms site to MVC. The current URL structure is like http://site/City-Category-State, where spaces in city and category are also hyphens, so the URL would be /New-York-Small-Business-NY. A company URL structure is http://site/Company-City-Category-State so that would be /Company-ABC-New-York-Small-Business-NY. As you can see there is really no way to map it back to Category or Company Controllers directly. This is why we are thinking of using a declarative routing where an XML document will map URLs to controllers. Where can I put the code to read XML and assign controller/actions to the route?
if you have a siteController with an index method and route everything to that, then you can have your index method take the whole "City-Category-State" as a parameter and parse it, then redirect to the appropriate controller with the right values, or just return the view right there.

ASP.Net MVC redirecttoaction not passing action name in url

I have a simple create action to receive post form data, save to db and redirect to list view.
The problem is, after redirecttoaction result excutes, the url on my browser lost the action section. Which it should be "http://{hotsname}/Product/List" but comes out as "http://{hotsname}/Product/".
Below is my code:
[HttpPost]
public ActionResult Create(VEmployee model, FormCollection fc)
{
var facility = FacilityFactory.GetEmployeeFacility();
var avatar = Request.Files["Avatar"].InputStream;
var newModel = facility.Save(model, avatar);
return RedirectToAction("List");
}
The page can correctly render list view content, but since some links in this view page use relative url, the functions are interrupted. I am now using return Redirect("/Employee/List") to force the url. But I just wonder why the action name is missing. I use MVC3 and .Net framwork 4.
I am new to ASP.Net MVC, thanks for help.
Your route table definitely says that "List" action is default, so when you redirect to it as RedirectToAction("List") - routing ommits the action because it is default.
Now if you remove the default value from your routes - RedirectToAction will produce a correct (for your case) Url, but you'll have to double check elsewhere that you are not relying on List being a default action.
Well, Chris,
If you get the right content on http://{hotsname}/Product/ then it seems that routing make that URL point to List either indirectly (using pattern like {controller}/{action}) and something wrong happens when resolving URL from route or {action} parameter is just set wth default value List. Both URLs can point to the same action but the routing engine somehow takes the route without explicit action name.
You should check:
Order in which you define your routes
How many routes can possibly lead to EmployeeController.List()
Which one of those routes has the most priority
Default values for your routes
Just make the route with explicit values: employee/list to point to your List action and make sure that is the route to select when generating links (it should be most specific route if possible).
It would be nice if you provide your routes mappings here.
but since some links in this view
page use relative url, the functions
are interrupted.
Why do you make it that way? Why not generate all the links through routing engine?
When using the overload RedirectToAction("Action") you need to be specifying an action that is in the same controller. Since you are calling an action in a different controller, you need to specify the action with the alternate overload e.g. RedirectToAction("List", "Employee").

How Can I Stop ASP.Net MVC Html.ActionLink From Using Existing Route Values?

The website I'm working on has some fairly complicated routing structures and we're experiencing some difficulties working with the routing engine to build URLs the way we need them to be built.
We have a search results page that uses RegEx based pattern matching to group several variables into a single route segment (i.e. "www.host.com/{structuralParameters}" can be the following: "www.host.com/variableA-variableB-variableC" - where variables A through C are all optional). This is working for us fine after a bit of work.
The problem we are experiencing resolves around an annoying feature of the ActionLink method: if you point to the same controller/action it will retain the existing route values whether you want them or not. We prefer to have control over what our links look like and, in some cases, cannot have the existing parameters retained. An example would be where our site's main navigation leads to a search results page with no parameters set - a default search page, if you like. I say this is an annoying feature because it is a rare instance of the ASP.Net MVC Framework seemingly dictating implementation without an obvious extension point - we would prefer not to create custom ActionLink code to write a simple navigation link in our master page!
I've seen some say that you need to explicitly set such parameters to be empty strings but when we try this it just changes the parameters from route values into query string parameters. It doesn't seem right to me that we should be required to explicitly exclude values we aren't explicitly passing as parameters to the ActionLink method but if this is our only option we will use it. However at present if it is displaying in the query string then it is as useless to us as putting the parameters directly into the route.
I'm aware that our routing structure exasperates this problem - we probably wouldn't have any issue if we used a simpler approach (i.e. www.host.com/variableA/variableB/variableC) but our URL structure is not negotiable - it was designed to meet very specific needs relating to usability, SEO, and link/content sharing.
How can we use Html.ActionLink to generate links to pages without falling back on the current route data (or, if possible, needing to explicitly excluding route segments) even if those links lead to the same action methods?
If we do need to explicitly exclude route segments, how can we prevent the method from rendering the routes as query string parameters?
This seemingly small problem is causing us a surprising amount of grief and I will be thankful for any help in resolving it.
EDIT: As requested by LukLed, here's a sample ActionLink call:
// I've made it generic, but this should call the Search action of the
// ItemController, the text and title attribute should say "Link Text" but there
// should be no parameters - or maybe just the defaults, depending on the route.
//
// Assume that this can be called from *any* page but should not be influenced by
// the current route - some routes will be called from other sections with the same
// structure/parameters.
Html.ActionLink(
"Link Text",
"Search",
"Item",
new { },
new { title = "Link Text" }
);
Setting route values to be null or empty string when calling Html.ActionLink or Html.RouteLink (or any URL generation method) will clear out the "ambient" route values.
For example, with the standard MVC controller/action/id route suppose you're on "Home/Index/123". If you call Html.RouteLink(new { id = 456 }) then MVC will notice the "ambient" route values of controller="Home" and action="Index". It will also notice the ambient route value of id="123" but that will get overwritten by the explicit "456". This will cause the generated URL to be "Home/Index/456".
The ordering of the parameters matters as well. For example, say you called Html.RouteLink(new { action = "About" }). The "About" action would overwrite the current "Index" action, and the "id" parameter would get cleared out entirely! But why, you ask? Because once you invalidate a parameter segment then all parameter segments after it will get invalidated. In this case, "action" was invalidated by a new explicit value so the "id", which comes after it, and has no explicit value, also gets invalidated. Thus, the generated URL would be just "Home/About" (without an ID).
In this same scenario if you called Html.RouteLink(new { action = "" }) then the generated URL would be just "Home" because you invalidated the "action" with an empty string, and then that caused the "id" to be invalidated as well because it came after the invalidated "action".
Solution at the root of the problem
It seems that the optimal solution (that doesn't smell like a workaround) is the one that solves the problem where it has roots and that's in routing.
I've written a custom Route class called RouteWithExclusions that is able to define route value names that should be excluded/removed when generating URLs. The problem is when routing falls through routes table and subsequent routes don't have the same route value names...
The whole problem is detailed and explained in my blog post and all the code is provided there as well. Check it out, it may help you solve this routing problem. I've also written two additional MapRoute extension methods that take an additional parameter.
If you want total control of the link, just build the link yourself:
Click Here
Substitute whatever you need inside the href attribute.

ASP.NET MVC One Way Route

Is it possible to define a route in the RouteCollection of Asp.net MVC, so that it just does the "URL rewriting" part and ignore the URL generation with Html.Actionlink(...)?
In fact I want to add a keyword between controller and action (controller/..keyword.../action) for certain very special requests. The generated URLs on the pages, however, should remain using the default routes. (controller/action)
Yes you can do what you are asking. You would just create your own route that inherited from the current one and override GetVirtualPath to always return null. This way no Action lookup would be done, but the URL would still function as a routing mapping to your action/controller.
Also by the way, what is happening isn't URL Rewriting, because you using the Routes to define endpoints in to your application. Or in other words an API. So no rewriting is taking place. Think of the relationship between routes and your action/controller as more of a publically defined namespace for the web. Just like a namespace you are defining a specific spot in your application where your action/controller can be found.

Url routing with database lookup?

I want to build a ASP.NET MVC site so that the controller for a specific url is stored in the database instead of the URL.
The reason for that is that i'm building a CMS system and the users should be able to change the template (controller) without changing the URL. I also think that the name of the controller is not relevant for the end users and i want clean URL:s.
I realise that i could just add all routes at application start, but for a system with like 100 000 pages it feels like a bad idea.
Is it possible to store the url:s in the database and make a lookup for each request and then map that request to a specific controller?
Basically you'll have to implement your own IRouteHandler.
Part of the answer and some example code is in Option 3 of this question's answer:
ASP.NET MVC custom routing for search
More information:
http://weblogs.asp.net/fredriknormen/archive/2007/11/18/asp-net-mvc-framework-create-your-own-iroutehandler.aspx
Why couldn't you just do something like this:
-- Global.asax.cs --
routes.MapRoute(null, // Route name
"content/{id}", // URL with parameters
new { Controller = "Content", Action = "Show", Id = (string) null }); // Parameter defaults
-- /Controllers/ContentController.cs --
public class ContentController : Controller
{
public ActionResult Show(string id)
{
// Lookup the 'content' (article, page, blog post, etc) in the repository (database, xml file, etc)
ContentRepository repository = new ContentRepository();
Content content = repository.FindContent(id);
return View(content);
}
}
Such that a request to your site www.yoursite.com/content/welcome-to-my-first-blog-post would call ContentController.Show("welcome-to-my-first-blog-post").
I suppose ASP.NET can do many of the same things as PHP. If so there is a simple approach.
With rewrite rules you can easily send any traffic to any URL of the 100K to the same place. On that destination you could simply use the server variables containing the URL requested by the client and extract the location. Look it up in the DB and send the corresponding data for that URL back to the client on-the-fly.
"for a system with like 100,000 pages it feels like a bad idea."
It is a bad idea if you are creating a routing system that cannot be reused. The basic {controller}/{action}/{id} schema points you in the direction of reuse. This schema can be extended/revamped/recreated according to your needs.
Instead of thinking about how many pages you have think about how your resources can be grouped.
Instead of creating a heavy routing system why not create an anchor link control (ascx) which allows user to only add valid internal links. Keep a table in the db of your templates and their controllers to populate the control with it.

Resources