routing image paths through mvc - asp.net-mvc

I am currently creating an MVC4 web application. This application shows products including images. I have got a company to review the SEO aspects of the site, and they have come back with a recommendation regarding product images.
Currently my image path is: folder/images/productimage/PROD_98713204_LARGE.gif
They have recommended the following: /folder/images/productimage/98713204-160x260-0-0_My+Product+Name.gif
The problem I have is that I have a large number of images on the site so it is difficult to go rename all to include product names etc. So I have thought about using the routing features within MVC, outputting the recommended in the html markup but picking the current image path shown above from the filesystem.
2 questions I have are:
Is there performance implications of using such routing to manage image paths? My site will have large traffic loads and a number of images so it is a concern.
Could someone give me an example of a route I would need to configure to achieve the above?

In order to do the routing option, you'll have to come up with some specification for how the urls map to the actual images. How many images are we talking about? 1000? 10,000? A million? If you have less than 100,000 then I'd probably go ahead and use the specification you already wrote to just go ahead and rename all the files and then use the specification to name files on the way in to the file system from now on.
The advantages of this system are that it limits the scope of the changes to the data, and you only have to affect one point of the system (when the files are on the way in). When it comes to performance, the overhead of mapping a string to another string is probably negligible, even for a large number of requests. String manipulation for short strings is very fast, but in any case you should profile the entire request if requests start taking too long and focus on the major pain points. Of course if you just rename the files, you can be sure you won't have to worry about any of this profiling.
As for creating a route to do the mapping of urls, you first have to get ASP.NET to hand the request to your code. By default ASP.NET first checks if the file exists at the location specified by the url, and if it does, it just processes the file based on the registered handler in IIS. My suggestion is to leave these settings as they are because they make very large changes to the system when you change them. For images, the handler just attempts to send the file to the client.
If the file does not exist on the disk, it then looks for a handler for the request by iterating through the route collection, which is the thing you register your routes into typically in Global.asax. Since you didn't tell us what you tried, I'm going to assume that you know how routes work. You can get pretty crafty with routes, but I'll stick to something simple:
routes.MapRoute(
name: "images",
url: "{folder}/images/productimage/{unmappedFileName}",
defaults: new { controller = "Home", action = "Images" }
);
This route will match the example url you gave. In the case that they use the actual file name however, this route will never be hit, as I have explained above. Since the SEOed file name does NOT exist however, this route will be hit and it will try to run the Images action on the Home controller (I list my entire Home controller here to remove any confusion about where these parts go):
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Images(string unmappedFileName)
{
if (String.IsNullOrWhiteSpace(unmappedFileName))
{
return HttpNotFound();
}
var fileName = MapFileName(unmappedFileName);
var diskLocation = GetDiskLocation(fileName);
return File(diskLocation, "image/png");
}
private string MapFileName(string unmappedFileName)
{
return unmappedFileName + ".png";
}
private string GetDiskLocation(string fileName)
{
var fullPath = String.Format("~/Content/themes/base/images/{0}", fileName);
var diskLocation = Server.MapPath(fullPath);
return diskLocation;
}
}
Obviously you'll need to update the file name mapping to whatever spec you decided upon. I am using what's here because there are a bunch of example files in that folder when you create a new MVC4 project.
A simple way to show that it works is to implement your Index view in the Home folder like this:
{
ViewBag.Title = "Index";
var imagesDirectory = Server.MapPath("~/Content/themes/base/images/");
var imageFileNames = Directory.GetFiles(imagesDirectory).Select(m => m.Replace(imagesDirectory, "").Replace(".png", ""));
}
<h2>Index</h2>
#foreach (var imageFileName in imageFileNames)
{
<div>#Html.ActionLink(imageFileName, "Images", new { unmappedFileName = imageFileName })</div>
}
In the future when you run into a problem like this, you should just try to figure it out first. When you ask your question, be sure to tell us what you have tried so we can get you over the next hump and point you in the right direction instead of just asking us for the code to solve your problem. In the case where you don't know where to get started, try searching for or asking a more abstract question. Who knows, they might even answer your other questions at the same time. :)
And lastly, this solution is really complicated. I don't even know how your mapping function is going to work, but I know this is complicated. It also adds a layer of complexity when debugging because now the urls you have don't directly relate to the file name on disk, and that time will add up later on. Of course there are reasons why I might favor this mapping, most notably if you intend to change the url structure in the future for further SEO changes, but then you're breaking urls on the internet and damn you for that. So really, I suggest just changing all of your file names if that is feasible.

Related

Organizing (splitting?) controller while maintaining URL

We have a large web application that is a mix of WebForms and MVC. The URLs we work with look as such www.something.com/Secure/Module/SomeAction. When we used to work with Webforms Module was a folder, as we had as many ASPXs as we needed under that folder. With MVC we're taking a similar approach, where Module translates to a controller, containing all the action. The problem that we're running into is that if the Module has something like 20 - 30 actions it's getting really messy. For example if we have a PersonReport, that usually translates to several actions dedicated to serving that report alone (to facilitate ajax calls etc). Our actions are fairly thin, they populate the model (usually calling WCF services) and that's about it. Nevertheless it can easily creep up to 1500 lines of codes, and we start utilizing regions to organize sections of the controller. Creating a new controller (by default) will obviously stray away from our URL pattern. Any suggestions on how to better organize this mess?
You can use attribute routing if you plan to use MVC 5. This will allow you to specify custom routes for each Action method with attribute
The previous versions allow you to override the default routes through RouteConfig.cs file. Example would be
routes.MapRoute(
name: “ProductPage”,
url: “{productId}/{productTitle}”,
defaults: new { controller = “Products”, action = “Show” },
constraints: new { productId = “\\d+” }
);
See this link for more info.
Hope this helps.

Newbie MVC Routing

I'm clearly missing the concept of routing - for an experiment I've set the route as
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Standard",
"{devicetype}/{devicesub}/{language}/{culture}/{controller}/{action}/{id}",
new
{
devicetype = "pc",
devicesub = "def",
language = "en",
culture = "int",
controller = "Home",
action = "Index",
id = ""
}
);
My index page is in Views/pc/def/en/int/Home
When I run it I get an error searching for /Home/Index.aspx
It seems to still use the default structure and not my more complex one - what am I not understanding?
The way the content of the site is stored does not reflect the route but is defined by the Controllers and the Views so although your route is complex you're still ending up at the home controller so MVC is going to be looking in /views/home for the appropriate view which in this case is index.
One of the hardest things I've found to get my head around is the separation of URL from the processing and more importantly content - its right and its clever but the fact that routing and result can be radically different (in terms of finding things in your directory structure) is, erm, interesting (-:
As a practical experiment, do nothing other than relocate your index page to /views/home/index.aspx and see if that resolves the problem...
I'm a bit of a newbie myself so this might not be correct, but as far as I know, the path of your views are always located in the "Controller/Action" path. The additional properties you have specified are simply just querystring values being submitted additionally with the request.
Hope it helps...
G
So, the relevant lines if your controller simply ends in return View() or return View(modelData) are:
controller = "Home",
action = "Index",
All URLs matching your above route will land there, unless your URL is for something like /pc/def/en/int/Widgets, in which case you will route to WidgetsController/index.
It sounds like you want to have different views for the same action. If you want to have different views depending on the parameters passed to your controller, you can do that. You need to be explicit about it when you return your ViewResult. You can return View("SpecialView",model) and the view engine will look for SpecialView.aspx in your controller's view directory. Of course, "SpecialView" could be replaced with an appropriate string for your app, and could be generated programmatically if it makes sense.
Many thanks for the input folks - i think i'm begining to understand
It does indeed work if i place the form in views/home - however most commercial sites are much more complex than 2 levels
As you have probably gathered from the structure what i was trying to experiment with was different forms for device type (pc, phone, mobile) and culture but using a single controller as the business logic is the same regardless of the style and language of the presentation
Just for further info
I've changed the directory structure to Views/Home/pc/def/int and auto generated the path as Jason suggests and this works fine - I had to change the structure as Views/Home seems to get added to the front of the search regardless of the string you supply in the View command

Serving HTML or ASPX files with ASP.NET MVC

I want to implemented URL like :
www.domain.com/Business/Manufacturing/Category/Product1
This will give the details of specific product(such as specifications).
Since the list of Categories & Products in each Category are limited, I thought it is not worth to use database for products.
So I want to implement it using the HTML or ASPX files. Also I want the URL to be without the files extention(i.e. URL's should not have .html or .aspx extension)
How can I implement it with ASP.NET MVC? Should I use nested folder structure with HTML/ASPX files in it, so that it corresponds to URL? Then how to avoid extensions in URL?
I am confused with these thoughts
Asp.net Mvc uses the routing library from Microsoft. So it is very easy to get this kind of structure without thinking about the folder structure or the file extensions. With asp.new mvc you do not point a request at a specific file. Instead you point at a action that handles the request and use the parameters to determine what to render and send to the client. To implement your example you can do something like this:
_routes.MapRoute(
"Product",
"Business/Manufacturing/Category/Product{id}",
new {controller = "Product", action = "Details", id = ""}
);
This route will match the url you described and execute the action named "Details" on a controller named "ProductController" (if you are using the default settings). That action can look something like this:
public ActionResult Details(int id) {
return View(string.Format("Product{0}", id);
}
This action will then render views depending on what id the product have (the number after "Product" in the end of your example url). This view should be located in the Views/Product folder if you use the default settings. Then if you add a view named "Product1.aspx" in that folder, that is the view that will be rendered when you visit the url in your example.
All tough it is very possible to do it that way I would strongly recommend against it. You will have to do a lot of duplicated work even if you only have a few products and use partial views in a good way to minimize the ui duplications. I would recommend you use a database or some other kind of storage for you products and use a single view as template to render the product. You can use the same route for that. You just edit your action a little bit. It can look something like this:
public ActionResult Details(int id) {
var product = //get product by id from database or something else
return View(product);
}
This way you can strongly type your view to a product object and you will not have that much duplication.
The routing engine is very flexible, and when you have played around with it and learned how it works you will be able to change your url in just about any way you want without changing any other code or moving any files.
If you're not ready to dive into ASP.Net MVC, you can still get the nice URLs by using URL Rewriting for ASP.Net. That'd be simpler if you're already familiar with ASP.Net WebForms. The MSDN article on URL Rewriting should be a good start:
http://msdn.microsoft.com/en-us/library/ms972974.aspx
I'd be really sure you won't eventually have more products before deciding not to use a database. Both MVC and WebForms would allow you to make one page that dyamically shows a product and still have a nice URL- plus you might save yourself development time down the road. Something to think about.

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.

Where should "Master Page" logic go in MVC?

I'm experimenting with MVC, and my question is - where I had Page_Load logic in Master Pages with WebForms, where should it go in MVC? Here's the business case:
Different Host Headers should cause different Page Titles to be displayed on the site's (one) Master Page, therefore all pages. For example, if the host header is hello.mydomain.com, the page title should be "Hello World" for all pages/views, while goodbye.mydomain.com should be "Goodbye World" for all pages/views.
If the host header is different than anything I have in the list, regardless of where in the application, it should redirect to /Error/NoHostHeader.
Previously, I'd put this in the MasterPage Load() event, and it looks like in MVC, I could do this either in every controller (doesn't feel right to have to call this functionality in every controller), or somewhere in Global.asax (seems too... global?).
Edit: I have gotten this to work successfully using the Global.asax method combined with a Controller for actually processing the data. Only problem at this point is, all of the host header information is in a database. I would normally store the "tenant" information if you will in a Session variable and only do a DB call when it's not there; is there a better way to do this?
There is no 1:1 equivalent in MVC for a reason, let's just recapitulate how to think about it the MVC way:
Model: "Pages of this site are always requested in a certain context, let's call it the tenant (or user, topic or whatever your sub domains represent). The domain model has a property representing the tenant of the current request."
View: "Render the page title depending on the tenant set in the model."
Controller: "Set the tenant in the model depending on the host header".
Keep in mind that what we want to avoid is mixing controller, view and business logic. Having controller logic in more then one place or a place, that is not called "controller" is not a problem, as long as it remains separated.
And now the good thing: You could do this "MVC style" even with Web Forms, and the solution still works with ASP.NET MVC!
You still have the request lifecycle (not the page lifecycle), so you could implement a custom HttpModule that contains this part of the controller logic for all requests. It handles the BeginRequest event, checks for the host header, and stores the tenant to something like HttpContext.Current.Items["tenant"]. (Of course you could have a static, typed wrapper for this dictionary entry.)
Then all your model objects (or a model base class, or whatever is appropriate for your solution) can access the HttpContext to provide access to this information like this:
public string Tenant
{
get { return HttpContext.Current.Items["tenant"]; }
}
Advantages:
You have separated cause (host header) and effect (rendering page title), improving maintainability and testability
Therefore you could easily add additional behavior to your domain model based on this state, like loading content from the database depending on the current tenant.
You could easily make more parts of the view depend on the tenant, like CSS file you include, a logo image etc.
You could later change the controller logic to set the tenant in the model not only based on the sub domain, but maybe based on a cookie, a referrer, search term, user agent's language, or whatever you can think about, without modifying any of your code depending on the model.
Update re your edit: I don't like the idea of holding the state in the session, especially if your session cookie might apply not only to each sub domain, but to all domains. In this case you might serve inconsistent content if users visted another sub domain before. Probably the information in the database that is mapping host headers to tenants won't change very often, so you can cache it and don't need a database lookup for every request.
You could create a base controller that supplied the correct ViewData to your MVC Master Page View, then derive each of your actual controllers from that one. If you put the logic into the ActionExecuting method, you should be able to generate an exception or redirect to an error page if necessary.
You are thinking too "WebForms" and not enough MVC. A master page is just a wrapper of your view, and it should only contain layout html. You can send stuff to your master, but it's a one way road and you should strive for agnostic views. Bottom line: forget about the events that WebForms had as they aren't going to be used here.
Since you are dealing with Host headers I suppose you could put it in the Global.asax...great now I'm confused :P
Stolen code from http://forums.asp.net/t/1226272.aspx
protected void Application_BeginRequest(object sender, EventArgs e)
{
string host = string.Empty;
if (this.Request.ServerVariables["HTTP_HOST"] == this.Request.Url.DnsSafeHost)
{
host = this.Request.Url.DnsSafeHost;
}
else
{
Regex regex = new Regex("http://[^/]*.host/([^/]*)(/.*)");
Match match = regex.Match(this.Request.Url.AbsoluteUri);
if (match.Success)
{
host = match.Groups[1].Value;
Context.RewritePath(match.Groups[2].Value);
}
}
// Match the host with the portal in the database
...
}
What you need is here http://www.asp.net/mvc/tutorials/passing-data-to-view-master-pages-cs

Resources