Asp.NET MVC Route Performance - asp.net

I have an application with a couple thousand routes.
This is because for each product we use a custom URL, as opposed to the textbook /product/id.
With this many urls, the performance is unacceptable in the router.
I am trying to find ways to improve it, but I am drawing a blank.
I have about 20 regex routes and about 3 thousand unique url routes.
Any Ideas?
Sorry for being so open ended, but I am not sure where to start.

If your urls are all on the format yoursite.com/{url}, you can still store all the three thousand urls in the database, and create a custom controller factory which uses the {url} parameter to look the correct information up in the database and assign correct controller, action and any parameters you're using.
There are lots of posts on google on how to implement the controller factory.
I imagine you'll also want some parsing of the existing routes to put them all in the database - this can probably be done by iterating over the RouteTable after you instantiate your application (i.e. after RegisterRoutes() is called).

I would get rid of the 3 thousand unique url routes and replace them with a generic route that is the last catch-all route. Something like:
routes.MapRoute("productRoute", "{category}/{manufacturer}/{productTitle}", new { controller = "Products", action = "Index", category = UrlParameter.Optional, manufacturer = UrlParameter.Optional, productTitle = UrlParameter.Optional });
You could also then add a custom IRouteConstraint to validate that the category exists (but just make sure it doesn't hit your database every time or performance will degrade).

Related

What's the right way to call an action from a different controller in ASP.Net MVC 6

I'm getting a
No route matches the supplied values
while trying to return a RedirectToAction("Action", "Controller"). The method signature says "actionName" and "ControllerName". I'm assuming actionName is the method name in the Controller, Am I correct? For ControllerName I'm using the Controller File Name without the Controller Sufix. Ex.:
return RedirectToAction("Index", "WebApp")
where Index is a method of WebAppController and the command is being issued from a method of AnotherController
Both the caller controller and the called one are on the same Controllers directory on the same application.
I'm cofused because in this ASP.net MVC application there is also Route attributes and Action attributes where you can put names on methods, different than the real method name. In my case I have no Route["Name"] nor [httpXXX("route", Name="dasdasdas")] configured for the methods involved in my attempt.
I have been reading MS docs and some examples but It appears I'm doing the thing right but for strange reasons it's not working. I even tried using Redirect("Controller/Action") and with it the problem vanishes but the new problem is this way of redirect doesn't support passing data parameters to the target route.
At this point I'm not working with Action links in Views, different from Form related ones.
I would really appreciate if at least anyone can give me a hint about where can I find info.
The right way to call an action from a different controller is the one I was using:
return RedirectToAction("AnActionMethodName", "AControllerWithoutControllerSufix"[, object values]);
My problem, after several hour spent was that I added two useMvc calls in the Startup.Configure(...)method:
app.UseMvc();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=MyApp}/{action=Index}/{id?}");
});
This was due to copy + paste of code. It was obviously confusing the MVC component in charge of attending the redirection part, as the right way I supose is tho use only one. (I'm newbie on .Net Platform and its frameworks)
Anyway I leave this as a reminder about the risks and consequences of copying and pasting code. At some point something weird can happen to your app and the cause can be a simple copy + paste error.

How to approach making a controller with (unlimited) db look-up routes

I'm digging into ASP.NET MVC from classic asp, and have completed some tutorials. I understand the concept now, but I have a main question about the controller. How are you able to control the url structure if you are getting your url's (with params) from a sql database?
Example: /custom-url-1 or /custom-url-23423411
(Returns params accordingly to feed the code)
I'm guessing it would have to do with ActionResult Index() , but not sure where to go after that. Any idea's where to look or is this even possible? Does MVC even allow this?
One Way you can approach this is to have everything go to one action in one controller and resolve the content in the view.
This is useful only if you have one type of view.
Second way is to have route constraint or a custom route constraint for each type of content you have
say : Galleries, Blog, Pages
and in each constraint check if the given url is of this type ( by db call), if the constraint returns true, it will point the request to the given controller and action.
The third way is to have a custom route handler that does the checking and routing (mind you this is probably the hardest task but works best if you have complex system, if your is simple try using method 1 or 2
P.S. if you want your urls separataed by "-" instead of "/" you can do just this
routes.MapRoute(
"Default", // Route name
"{controller}-{action}-{id}",// URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);

What is Ambient Route Values in ASP.NET MVC and how it works?

I am new to ASP.NET MVC. I read Professional ASP.NET MVC 3 and it has two pages talk about Ambient Route Values but I don't understand how it works. I search "asp.net mvc ambient route values" on google and still not find any articles or websites that explain what is it or how it works.
I want to know what is "Ambient Route Values" in ASP.NET MVC? How it works?
Ambient route values are related to all those values that are not needed for the current route outbound processing.
Let's explain through an example
Take for instance this route definition:
routes.MapRoute(
"Complex",
"{securityArea}/{permission}/{action}/{id}",
new { controller = "Administration", action = "List", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The scenario of ambient route values in this case would be:
User does some administration, so he's currently on URL served by the first route definition:
/users/change/apply/45
He edits some form on this URL and posts data back.
When he hits the server (executes some controller action), all of those route values get populated and are part of the context's route values now.
Controller does what it has to do and in the end we want it to redirect to non-admin part of the application, so hitting the second Default route.
Now if we take a look of the URL generation in #4. What happens?
route values that are defined during request are:
controller = "Administration"
action = "Apply"
securityArea = "Users"
permission = "Change"
id = 45
Only the first two are needed to generate the URL of the second Default route
What happens with the rest ambient route values?
They get added to the URL as well:
/Home/Index/?securityArea=Users&permission=Change
And we don't want that.
That's why they're called ambient because they are just *hanging in there orphaned in the request. This is my explanation of ambient route values. Hopefully explained in an understandable way.
I've also written about removing these ambient values in one of my blog posts where I've provided a custom route class that does this removal.
As referred on page 232
Ambient route values in the book that you linked also refer to outbound route processing but it talks about ambient values as those that we don't need to supply for outbound route processing because they will be taken from current values (namely controller and action may as well be ambient values).
Book though doesn't talk about the problem with ambient route values that I outlined in my upper answer. All defined route values can be ambient and they may cause problems when we don't realise how routing works.
The url generated does not need to contain the controller or action params of the current request. The page param indicated in the example is matched as one of the params in the RouteData, and the controller and action, since they weren't supplied, remain as they are on the current request.

ASP.NET MVC url construction

Before I ask my question: I just started ASP.NET MVC, so an advanced answer will maybe hard to understand ;)
I'm having 3 tables (I'll add just a few of the fields below)
Shops (ShopID, Name)
Products (ProductID, Name, ShopID)
Comments (CommentID, ProductID, UserID, CommentText)
Now what I want to do is this:
www.site.com/Shops
//Gives me a list of shops
www.site.com/Shops/ShopName
//Gives me some details about the shop called ShopName and lists all the products that the shop has
www.site.com/Shops/ShopName/ProductName/
//Gives me all the comments of that product called ProductName from that specific shop called ShopName
www.site.com/Shops/ShopName/ProductName/ASpecificCommentHere
//Gives me a specific comment on that product of that shop
and other things like
www.site.com/Shops/ShopName/ProductName/AddComment
//Adds a new comment to that product from that shop
Now I only have a ShopsController which makes it possible for me to do something like this now:
www.site.com/Shops/
// Gives me a list of all shops
and
www.site.com/Shops/Details/123
// Gives me some details about the shop with ID 123 and should list its products.
But here is also the first problem: I don't want the ID number in the url like /Shops/123 but rather the name like /Shops/ShopName and I don't know if it's good to have /Details/ there in the url. Maybe /Shops/ShopName would be better without the Details part in between?
How should I do this?
Should I create also a Controller for my Products and for my Comments that I have 3 controllers in total?
Or should I just keep one ShopController to always get the first part of the url (so that it always starts with) site.com/Shops/ShopName/...
Thanks in advance
(and a short little question, should ViewModels be placed in the controllers directory?)
Kind a doing your work, but - whatever.
I would go with something like this (be warned - it's untested)...
As you already likely know - order is important.
www.site.com/Shops/ShopName/ProductName/AddComment =>
routes.MapRoute(
"Product",
"Shops/{shopName}/{productName}/{action}",
new { controller="comment"}
);
www.site.com/Shops/ShopName/ProductName/ASpecificCommentHere =>
routes.MapRoute(
"Product",
"Shops/{shopName}/{productName}/{commentName}",
new { controller="comment", action="details"}
);
www.site.com/Shops/ShopName/ProductName/AddProduct =>
routes.MapRoute(
"Product",
"Shops/{shopName}/{productName}/{action}",
new { controller="product"}
);
www.site.com/Shops/ShopName/ProductName/ =>
routes.MapRoute(
"Product",
"Shops/{shopName}/{productName}",
new { controller="product", action = "details"}
);
www.site.com/Shops/ShopName =>
routes.MapRoute(
"ShopDetails",
"Shops/{shopName}",
new { controller="shop", action = "details"}
);
www.site.com/Shops (NB: this route is 'global', for every controller) =>
routes.MapRoute(
"List",
"{controller}",
new { action = "list"}
);
Maybe this can be optimized or worse - it's wrong but it should give you some better understanding for sure.
Remember not to name shops/controllers/comments as according controller actions. That's an issue with this routing approach.
And i hope you noticed that i would create 3 different controllers (what might and might not be a good decision - that's based on expectable weight and complexity of controller logic).
Another tip - read about model binding. Using custom binders you could replace string parameters (entity names) with actual, already assembled objects. Maybe that wouldn't be worth for this particular case, but model binding surely is 2nd important and hardest thing to grasp when facing asp.net mvc.
And i lied, i wouldn't go with this because i prefer specifying controller specific routes using attributes directly on actions (omitted that because it seems more advanced).
Routing is like regular expressions. There's a moment when it just 'clicks' and seems natural and easy.
Already answered second question as comment to your question. :)
I would spend a little time researching the routing mechanism in MVC. This is where you set the formats for the URLs your application should receive. All the things you mentioned are possible...you just need to construct your route definitions accordingly. As far as being able to accept the name of the entity, rather than the ID, you can do that very easily by constructing your route so that it accepts names and changing the signatures on your action methods to accept strings instead of ints.
I don't know what the official stance on where ViewModels should be located. It's really up to you, and it depends on the ORM method you are implementing. I'm using LINQ-to-SQL, and having a very good time with it. I keep my LINQ-to-SQL model in my Models folder. I prefer to keep my ViewModels in a View subfolder under my Models folder. I've also got a Domain subfolder to hold the partial models for my entities, in case I want to implement calculated fields or set up the initial values for the record.

Translate a url to a route

I feel like I'm re-inventing the wheel here, but I need to find a way of taking a URL from a catchall,and redirecting that to another route.
The reason for this is because I need to do some things like adding session cookies for certain urls, and then pass them on to their relevant action.
What's the best way of implementing this?
Thanks in advance for any help!
You can try changing the master page at runtime. It's not the most beautiful solution though.
Theming Support
If I understand question...
Looking at your previous question (redirect-to-controller-but-with-a-different-master-using-a-catchall-wildcard) you want to take the wildcard from:
routes.MapRoute(
"Partner",
"partners/{partner}/{*wildcard}",
new { controller = "Partners", action = "PartnerRedirect" }
);
And in the Partners/PartnerRedirect action, send it to the right controller/action specified in the wildcard?
I have no idea the best way to do this, but looking at ways to unit test url routing brought up this:
http://weblogs.asp.net/stephenwalther/archive/2008/07/02/asp-net-mvc-tip-13-unit-test-your-custom-routes.aspx
I'm not sure if it is an exact fit (ie. might not have to mock stuff), but it looks like all the data is returned from GetRouteData to pass to RedirectToAction method.
Oh, so you don't want a redirect? You want the URL to stay:
mysite.com/partners/pepsi/products/cola.htm
but serve up:
mysite.com/products/cola.htm
I don't have MVC at home, but couldn't you instead define your route differently?
Instead of:
routes.MapRoute(
"Partner",
"partners/{partner}/{*wildcard}",
new { controller = "Partners", action = "PartnerRedirect" }
);
do:
routes.MapRoute(
"Partner",
"partners/{partner}/{controller}/{action}/{other stuff you need}",
new { /* whatever defaults you want */ }
);
The action now has the partner variable to do whatever with, and you could add route constraints to partner so only valid ones match.
(I think) you could then use an action filter on your actions/controllers to do appropriate action based on the partner paramter (it should be in httpcontext of the filter, again I think!) so that you don't have to repeat code in every action if you want to do some basic checks/actions on partner.
Here is a decent write up on a lot of ways to skin that cat:
http://weblogs.asp.net/stephenwalther/archive/2008/08/12/asp-net-mvc-tip-31-passing-data-to-master-pages-and-user-controls.aspx
No, it is effectively a redirect, but the redirect is not visible to the user (in much the same way as a Server.Transfer() is done in Web Forms.
The link you pointed at only uses one master page, whereas I have many master pages (close to 200).
I have thought of having separate route handlers for these partners, but the master page choice came into the equation.
I think I'm going to have to create a ViewExtension to overcome these problems...

Resources