ASP.NET MVC Routes with "File Extensions" - asp.net-mvc

I want to make an MVC route for a list of news, which can be served in several formats.
news -> (X)HTML
news.rss -> RSS
news.atom -> ATOM
Is it possible to do this (the more general "optional extension" situation crops up in several places in my planned design) with one route? Or do I need to make two routes like this:
routes.MapRoute("News-ImplicitFormat",
"news",
new { controller = "News", action = "Browse", format = "" });
routes.MapRoute("News-ExplicitFormat",
"news.{format}"
new { controller = "News", action = "Browse" });
It seems like it would be useful to have the routing system support something like:
routes.MapRoute("News",
"news(.{format})?",
new { controller = "News", action = "Browse" });

I made a method to support adding pairs like this as follows:
public static void MapRouteWithOptionalFormat(this RouteCollection routes,
string name,
string url,
object defaults)
{
Route implicitRoute = routes.MapRoute(name + "-ImplicitFormat",
url,
defaults);
implicitRoute.Defaults.Add("format", string.Empty);
Route explicitRoute = routes.MapRoute(name + "-ExplicitFormat",
url + ".{format}",
defaults);
}

You can look into using constraints to make this work with normal routes.
UPDATE: actually, I misread the question. The other answer is the correct thing to do for now. Or create a custom route. We're looking at the idea of optional segments as a possible future feature.

Related

MVC routing with optional first parameter

I am trying to get a route like:
{lang:optional}/{controller}/{action}/{id:optional}
With "controller constraints" idea from this article: (MVC Routing Constraint on Controller Names), the above route works very well, when {lang} value is presented or not.
However I have a problem to match this route:
routeName: testRoute
url: {lang:optional}/list/{something:optional}
{controller = "product", action = "index"}
for the above route, the {lang} value must be presented, otherwise it does not work.
I have two workarounds to work it out.
The first way is to set two routes for the above:
The very standard one:
routeName: testRoute
url: /list/{something}
{controller = "product", action = "index"}
and another very standard one:
routeName: testRouteLang
url: {lang:not_optional}/list/{something:optional}
{controller = "product", action = "index", lang="de"}
I am wondering if there is a way to combine the two standard routes into one single route.
The second workaround is to use subdomain name, such as
http://example.com/list (default to English)
http://de.example.com/list (de)
But I really do not like the subdomain idea for the SEO reasons (maybe I am wrong on this point).
My goal is to remove the default "en-us" in the URL. I like this
http://www.example.com/list/something (default as English)
I do not want to force "en-us" in the url
http://www.example.com/en-us/list/something
The "lang" should only be presented in the Url if the current culture is not English:
http://www.example.com/de
http://www.example.com/fr/list/something
Thanks.
Finally I found an very easy and DRY solution. The core thing is to use the HttpContext.Current.RewritePath to inject the default "en", while this "en" will not be shown in the URL.
protected void Application_BeginRequest()
{
var rawUrl = HttpContext.Current.Request.RawUrl;
var segments = HttpContext.Current.Request.Url.Segments;
var segment1 = segments.Count() >= 2 ? segments[1] : string.Empty;
if (IsSomethingThatIWantToHandle("are,you,js,script,css,jpg,png,and,so,on?")
&& !LittleHelper.DoIHaveValidLangAlready(segment1))
{
HttpContext.Current.RewritePath("/en" + rawUrl);
}
}
When generate URL, if lang is null/empty, the URL will be have a double //. I just need a little helper to remove the extra "/".
When define a route, a trick is that the area name must be added to the DataTokens, otherwise the view cannot be correctly located, if areas are presented in the project.
routes.MapRoute(
"good name",
"{lang}/some-cool-stuff/{id}/{slug}",
defaults: new { area = "bigarea", controller = "bigcontroller", action = "tinyaction",
lang = UrlParameter.Optional, id = UrlParameter.Optional, slug = UrlParameter.Optional }
, constraints: new { lang = new CultureConstraint() }
).DataTokens.Add("area", "bigarea");
The CultureConstraint is very straightforward -- just verify whether it is a valid culture name. The namespace constraints is not necessary. However if the route table is big and complicated then the controller constraints, or even action constraints is very necessary otherwise duplicated routes will be an issue.
My default route in my project now is:
url: "{lang}/{area}/{controller}/{action}/{id}/{slug}",
and it works beautifully as I want.

MVC Routing Redirection

I have a webservice project that is being expanded to host multiple APIs instead of just one, so I wanted to cleanup the routes without breaking the old version. The main API used to sit off of a controller named API and accept parameters like this:
api/{language}/{action}/{*parameters}
Now, I have an Area named API that is going to house each of the APIs in their own controller and the route will look like this:
api/{controller}/{language}/{action}
I still need the old route to be usable for legacy apps already using the old route, I was hoping just to be able to create a 2nd "legacy" route that would catch the old pathing and use the new controller. I tried this but it only worked then with the new location and the ikd one returned a 404.
api/{language}/{action}/{*parameters}",
new { controller = "api1", action = "Index", language = "json" }
Any ideas on how to accomplish this? I tried RouteMagic but it didn't seem to work. Maybe I set the redirect up wrong though.
Ok, figured out after getting a sandwhich...just needed to add route constraints so that the old version would not catch on the new route format:
routes.MapRoute(
"API_default",
"api/{controller}/{language}/{action}",
new { controller = "api1", action = "Index", language = "json" },
new { language = "json|xml" }
);
routes.MapRoute(
"API_legacy",
"api/{language}/{action}/{*parameters}",
new { controller = "api1", action = "Index", language = "json", parameters = UrlParameter.Optional },
new { language = "json|xml" }
);

MVC3 Site Builder Application Design

It would be great if someone could help me on this problem as I am new to web development and ASP.NET MVC. My goal is to design an MVC application which could help users to create their own mini websites. Each user will get their own mini websites and an admin panel to change the pages and template of their sites. I think there are scripts out there to achieve something similar, but we need to create our own for our specific requirements
Towards that goal I created a main mvc 3 application and I created an area as Site under it. We want the user to have their own sub domain urls like www.site1.mainsite.com where site1 is the name of the mini site of the user. For that I added 2 routes one for the main site and another for the area like
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "MainSite.UI.Controllers" }
);
In Area Registration
context.MapRoute(
"Sites_default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new {controller = new SiteConstraint()},
new[] { "MainSite.UI.Areas.Site.Controllers" });
Both of them have the same url type, but area route have a constraint where I added like
var url = httpContext.Request.Headers["HOST"];
var storeName = url.Split('.')[1];
var match = storeName != "mainsite";
return match;
so far its working, But i dont think its a good design. Now apart from this I need to add 2 more areas one is siteadmin and another is blog.
What should be best way to achieve this?
sub domain urls should be directed to area "site"
subdomain url/Admin should be routed to area "siteadmin"
domain/blog should be routed to blog area.
domain other urls should be handled by main controllers
Thanks in advance
I didnt get even one reply :( But I tried playing with the routes and achieved some what near to what I was looking for.
Thanks to Route Debugger, It helped me to see what was going wrong with the routes
When I tried adding another area "Admin", and registered it with the route below
context.MapRoute(
"SiteAdmin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Dashboard", action = "Index", id = UrlParameter.Optional },
new { controller = new SiteConstraint() },
new[] { "MainSite.UI.Areas.SiteAdmin.Controllers" }
);
Things started going wrong. Route Debugger helped to me to figure out, what was happening.
For me the order of the Routes really mattered, so I had to make sure that my areas were registered in the correct order.
AreaRegistration.RegisterAllAreas()
was not registering my routes in the correct order, Googling gave me this helper method.
public static class RouteHelper
{
public static void RegisterArea<T>(RouteCollection routes, object state) where T : AreaRegistration
{
var registration = (AreaRegistration)Activator.CreateInstance(typeof(T));
var context = new AreaRegistrationContext(registration.AreaName, routes, state);
var tNamespace = registration.GetType().Namespace;
if (tNamespace != null)
{
context.Namespaces.Add(tNamespace + ".*");
}
registration.RegisterArea(context);
}
}
That method helped me to register the areas in the required order in the Global asax as
RouteHelper.RegisterArea<SiteAdminAreaRegistration>(RouteTable.Routes, null);
RouteHelper.RegisterArea<SiteAreaRegistration>(RouteTable.Routes, null);
I am writing it down, what I got so far as an answer so that someone else may be find it useful.

How do I create SEO-Friendly urls in ASP.Net-MVC

I'm getting myself acquainted with ASP.Net-MVC, and I was trying to accomplish some common tasks I've accomplished in the past with webforms and other functionality. On of the most common tasks I need to do is create SEO-friendly urls, which in the past has meant doing some url rewriting to build the querystring into the directory path.
for example:
www.somesite.com/productid/1234/widget
rather than:
www.somesite.com?productid=1234&name=widget
What method do I use to accomplish this in ASP.Net-MVC?
I've search around, and all I've found is this, which either I'm not understanding properly, or doesn't really answer my question:
SEO URLs with ASP.NET MVC
MVC stands for "Model View Controller" and while those concepts aren't what you're asking about, you generally can wire up URL's like you see above quite easily
So for example by default the URL's look like the following
http://www.somesite.com/controller/view/
where controller refers to the controller class within your project, and view refers to the page/method combination within the controller. So for example you could write the view to take in an input and look something like the following
http://www.somesite.com/widget/productid/1234/
Now as for SEO Friendly URL's, that's just useless sugar. You author your controller such that it adds harmless cruft to the end of the URL.
So for example, you'll notice that the following three ways to get to this question produce the same result:
How do I create SEO-Friendly urls in ASP.Net-MVC
How do I create SEO-Friendly urls in ASP.Net-MVC
How do I create SEO-Friendly urls in ASP.Net-MVC
Stack Overflow has authored their route values such that the bit that occurs after the question ID isn't really necessary to have.
So why have it there? To increase Google PageRank. Google PageRank relies on many things, the sum total of which are secret, but one of the things people have noticed is that, all other things being equal, descriptive text URL's rank higher. So that's why Stack Overflow uses that text after the question number.
Create a new route in the Global.asax to handle this:
routes.MapRoute(
"productId", // Route name
"productId/{id}/{name}", // URL with parameters
new { controller = "Home", action = "productId", id = 1234, name = widget } // Parameter defaults
);
Asp.Net MVC has routing built in, so no need for the Url Rewriter.
Be careful when implementing routes with names in them, you need to validate that the name coming in is correct or you can end up harming your SEO-Ranking on the page by having multiple URIs share the same content, either set up a proper canonical or have your controller issue 301s when visiting the 'wrong' URI.
A quick writeup I did on the latter solution can be found at:
http://mynameiscoffey.com/2010/12/19/seo-friendly-urls-in-asp-net-mvc/
Some info on canonicals:
http://googlewebmastercentral.blogspot.com/2009/02/specify-your-canonical.html
I think what you are after is MVC Routing
have a look at these
MVC Routing on www.asp.net
Book: ASP.NET MVC in Action - Routing
It is also important to handle legacy urls. I have a database of old and new urls and I redirect with the following code;
public class AspxCatchHandler : IHttpHandler, IRequiresSessionState
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (context.Request.Url.AbsolutePath.Contains("aspx") && !context.Request.Url.AbsolutePath.ToLower().Contains("default.aspx"))
{
string strurl = context.Request.Url.PathAndQuery.ToString();
string chrAction = "";
string chrDest = "";
try
{
DataTable dtRedirect = SqlFactory.Execute(
ConfigurationManager.ConnectionStrings["emptum"].ConnectionString,
"spGetRedirectAction",
new SqlParameter[] {
new SqlParameter("#chrURL", strurl)
},
true);
chrAction = dtRedirect.Rows[0]["chrAction"].ToString();
chrDest = dtRedirect.Rows[0]["chrDest"].ToString();
chrDest = context.Request.Url.Host.ToString() + "/" + chrDest;
chrDest = "http://" + chrDest;
if (string.IsNullOrEmpty(strurl))
context.Response.Redirect("~/");
}
catch
{
chrDest = "/";// context.Request.Url.Host.ToString();
}
context.Response.Clear();
context.Response.Status = "301 Moved Permanently";
context.Response.AddHeader("Location", chrDest);
context.Response.End();
}
else
{
string originalPath = context.Request.Path;
HttpContext.Current.RewritePath("/", false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(HttpContext.Current);
HttpContext.Current.RewritePath(originalPath, false);
}
}
#endregion
}
hope this is useful
i think stackoverflow is best practice.
because when you remove a word at end of url, it redirect with 301 status to current url.
domain.com/product/{id}/{slug}
it had some benefits:
when you change slug of page it will redirect to correct url
your url is short but have seo friendly words at end.
in backend you use id to find product but you have slug for search engine
you can have same slug for different page. I now its not good for seo but you can have same slug for 2 products
the code will be like #Martin
routes.MapRoute(
name: "Cafes",
url: "cafe/{id}/{FriendlyUrl}",// how send array in metod get
defaults: new { controller = "cafe", action = "index", id = UrlParameter.Optional, FriendlyUrl = UrlParameter.Optional }//, id = UrlParameter.Optional
);
in action controller you should check database slug of product with the parameter that you receive. if they are not same redirect with status 301 to correct url.
the code for redirect:
return RedirectToActionPermanent("index", new
{
id = CafeParent.CafeID,
FriendlyUrl = CafeParent.CafeSlug.Trim()
});
RedirectToActionPermanent redirect with status 301. when you do this, the search engine find that this url is changed to what you redirected.

ASP.Net MVC routing legacy URLs passing querystring Ids to controller actions

We're currently running on IIS6, but hoping to move to IIS 7 soon.
We're moving an existing web forms site over to ASP.Net MVC. We have quite a few legacy pages which we need to redirect to the new controllers. I came across this article which looked interesting:
http://blog.eworldui.net/post/2008/04/ASPNET-MVC---Legacy-Url-Routing.aspx
So I guess I could either write my own route handler, or do my redirect in the controller. The latter smells slightly.
However, I'm not quite sure how to handle the query string values from the legacy urls which ideally I need to pass to my controller's Show() method. For example:
Legacy URL:
/Artists/ViewArtist.aspx?Id=4589
I want this to map to:
ArtistsController Show action
Actually my Show action takes the artist name, so I do want the user to be redirected from the Legacy URL to /artists/Madonna
Thanks!
depending on the article you mentioned, these are the steps to accomplish this:
1-Your LegacyHandler must extract the routes values from the query string(in this case it is the artist's id)
here is the code to do that:
public class LegacyHandler:MvcHandler
{
private RequestContext requestContext;
public LegacyHandler(RequestContext requestContext) : base(requestContext)
{
this.requestContext = requestContext;
}
protected override void ProcessRequest(HttpContextBase httpContext)
{
string redirectActionName = ((LegacyRoute) RequestContext.RouteData.Route).RedirectActionName;
var queryString = requestContext.HttpContext.Request.QueryString;
foreach (var key in queryString.AllKeys)
{
requestContext.RouteData.Values.Add(key, queryString[key]);
}
VirtualPathData path = RouteTable.Routes.GetVirtualPath(requestContext, redirectActionName,
requestContext.RouteData.Values);
httpContext.Response.Status = "301 Moved Permanently";
httpContext.Response.AppendHeader("Location", path.VirtualPath);
}
}
2- you have to add these two routes to the RouteTable where you have an ArtistController with ViewArtist action that accept an id parameter of int type
routes.Add("Legacy", new LegacyRoute("Artists/ViewArtist.aspx", "Artist", new LegacyRouteHandler()));
routes.MapRoute("Artist", "Artist/ViewArtist/{id}", new
{
controller = "Artist",
action = "ViewArtist",
});
Now you can navigate to a url like : /Artists/ViewArtist.aspx?id=123
and you will be redirected to : /Artist/ViewArtist/123
I was struggling a bit with this until I got my head around it. It was a lot easier to do this in a Controller like Perhentian did then directly in the route config, at least in my situation since our new URLs don't have id in them. The reason is that in the Controller I had access to all my repositories and domain objects. To help others this is what I did:
routes.MapRoute(null,
"product_list.aspx", // Matches legacy product_list.aspx
new { controller = "Products", action = "Legacy" }
);
public ActionResult Legacy(int catid)
{
MenuItem menuItem = menu.GetMenuItem(catid);
return RedirectPermanent(menuItem.Path);
}
menu is an object where I've stored information related to menu entries, like the Path which is the URL for the menu entry.
This redirects from for instance
/product_list.aspx?catid=50
to
/pc-tillbehor/kylning-flaktar/flaktar/170-mm
Note that RedirectPermanent is MVC3+. If you're using an older version you need to create the 301 manually.

Resources