ASP.NET MVC 4 Custom View Routing in Sitefinity 7 - asp.net-mvc

Our solution hierarchy is as follows:
language/doctor-cv/doctorMcr/doctorFullName
Ex: en\doctor-cv\12345\David
Now I'd like to map the routing so that when the user just types the name of the view in the url, it automatically maps the url to the corresponding controller
I.E: localhost:1234\en\doctor-cv\12345\David
Should map to
View\DoctorCVPage\Index.cshtml
Currently, we're using the default routing
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DoctorCVPage",
routeTemplate: "{language}/doctor-cv/{doctorMcr}/{doctorFullName}",
defaults: new
{
controller = "DoctorCVPage",
action = "Index",
doctorMcr = UrlParameter.Optional,
doctorFullName = UrlParameter.Optional,
language = UrlParameter.Optional
});
Here is MyController
public class DoctorCVPageController : BaseController
{
/// <summary>
/// Gets or sets the message.
/// </summary>
[Category("String Properties")]
public string Message { get; set; }
/// <summary>
/// This is the default Action.
/// </summary>
public ActionResult Index(string doctorMcr)
{
var id = "";
ViewBag.PageTitleLink = Request.UrlReferrer != null ? Request.UrlReferrer.ToString() : string.Empty;
}
}
And then in my view I have a tag.
<a href="/en/doctor-cv/${DoctorMcr}/${DoctorName}"/>
After user click on this tag, system should to redirect to DoctorCVPage/Index (controller: DoctorCVPage, Action = Index), but it can't do this.
Please helps me know why, thank for all helps.

Please follow this way, I think it can help you handle that thing.
protected override void HandleUnknownAction(string actionName)
{
UrlDetail = Request.Url != null ? Request.Url.ToString() : string.Empty;
Response.StatusCode = (int)HttpStatusCode.OK;
this.ActionInvoker.InvokeAction(this.ControllerContext, "Index");
return;
}
public ActionResult Index()
{
return;
}
Hope this way can help you.

It's not working because any URL in Sitefinity will be routed to a page. Your widget is not a page, but on the page. What you have to do is override the HandleUnknownAction method and parse the URL there, then take the appropriate action.
For example
protected override void HandleUnknownAction( string actionName )
{
if (!RouteHelper.GetUrlParametersResolved())
{
this.Response.StatusCode = (int)HttpStatusCode.NotFound;
}
else
{
this.Response.StatusCode = (int)HttpStatusCode.OK;
List<string> segments = RouteHelper.SplitUrlToPathSegmentStrings( Request.Url.LocalPath, true )
.Skip(1)
.ToList();
ViewBag.PageTitleLink = String.IsNullOrEmpty(segments.ElementAt(2)):String.Empty:segments.ElementAt(2);
var lang = segments.ElementAt(0);
// etc
View( "Index", model ).ExecuteResult( this.ControllerContext );
}
}
This relies on the position of the url segments being consistent, but it works well otherwise. This is the only way I have found to deal with the requirements you have. Also notice that this is a Void method, so you have to execute the view, as shown.

Related

mvc custom route configuration

Suppose that we want to transform an old version of asp.net web forms into mvc architecture.
But some users have bookmarked our urls or they have link it on other sites.
Now we don't want to loose that urls.instead we want to create a custom route that manages the old-typed urls.
This class gets the old url from user and creates a new route and redirects the user to the new page.
This is my custom route class:
public class LegacyRoute:RouteBase
{
private string[] urls;
public LegacyRoute(params string[] targetUrls)
{
urls = targetUrls;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL =
httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (urls.Contains(requestedURL,StringComparer.OrdinalIgnoreCase))
{
result = new RouteData(this, new MvcRouteHandler());
result.Values.Add("controller", "Legacy");
result.Values.Add("action", "GetLegacyUrl");
result.Values.Add("legacyUrl", requestedURL);
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
and this is my RegisterRoutes function:
public static void RegisterRoutes(RouteCollection routes)
{
// routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new LegacyRoute("~/articles/windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library"));
routes.MapRoute(null, "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
I have also created "Legacy" controller and "GetLegacyUrl" action.
But when I run the website and type
"localhost:14786/articles/windows_3.1_Overview.html"
in my browser I get the 404 Not Found error.
Where is the problem? Why I get this error?
I solved this problem (in Restful Routing) by creating a global attribute that looks at the route and then executes a redirect if it realizes the route is a "redirect route". In your case it would be a LegacyRoute.
public class RedirectFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.IsChildAction)
return;
var redirect = filterContext.RouteData.Route as RedirectRoute;
if (redirect != null)
{
var helper = new UrlHelper(filterContext.RequestContext);
var values = new RouteValueDictionary(filterContext.RequestContext.RouteData.Values);
var merged = new RouteValueDictionary(redirect.DataTokens["new_path"] as RouteValueDictionary);
// keep the values we specified, and add the other routeValues
// that we we didn't have overrides for.
foreach (var key in values.Keys.Where(key => !merged.ContainsKey(key)))
merged.Add(key, filterContext.RouteData.Values[key]);
var url = helper.RouteUrl(filterContext.RouteData.Values);
filterContext.Result = new RedirectResult(url, redirect.IsPermanent);
}
}
}
I know this is like a month old, but in your post, you did not mention the view. What view do you expect the user to arrive at? What is the view that the controller is returning as a result of GetLegacyUrl? If there is view being retured by GetLegacyUrl, then you will not get a 404. The original code you posted is fine.

Multi Tenant application using MapRoute

I have found a solution for implement multi-tenant in my asp.net mvc project and
I want know if it's correct or exist a better way.
I want organize more customers using the same application handling the web request, for example:
http://mysite/<customer>/home/index //home is controller and index the action
For this reason i changed the default maproute:
routes.MapRoute(
name: "Default",
url: "{customername}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
and I implemented a custom ActionFilterAttribute:
public class CheckCustomerNameFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext filterContext )
{
var customerName = filterContext.RouteData.Values["customername"];
var customerRepository = new CustomerRepository();
var customer = customerRepository.GetByName( customerName );
if( customer == null )
{
filterContext.Result = new ViewResult { ViewName = "Error" };
}
base.OnActionExecuting( filterContext );
}
}
and using it:
public class HomeController : Controller
{
[CheckCustomerNameFilterAttribute]
public ActionResult Index()
{
var customerName = RouteData.Values["customername"];
// show home page of customer with name == customerName
return View();
}
}
With this solution i can switch customer using customer name and correctly accept requests like this:
http://mysite/customer1
http://mysite/customer2/product/detail/2
...................................
This solution works well but I don't know if the best approach.
Does anyone know a better way?
You can model bind the customer name, and not have to pull it from route values:
public ActionResult Index(string customerName)
{
}

How do I route a URL with a querystring in ASP.NET MVC?

I'm trying to setup a custom route in MVC to take a URL from another system in the following format:
../ABC/ABC01?Key=123&Group=456
The 01 after the second ABC is a step number this will change and the Key and Group parameters will change. I need to route this to one action in a controller with the step number key and group as paramters. I've attempted the following code however it throws an exception:
Code:
routes.MapRoute(
"OpenCase",
"ABC/ABC{stepNo}?Key={key}&Group={group}",
new {controller = "ABC1", action = "OpenCase"}
);
Exception:
`The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.`
You cannot include the query string in the route. Try with a route like this:
routes.MapRoute("OpenCase", "ABC/ABC{stepNo}",
new { controller = "ABC1", action = "OpenCase" });
Then, on your controller add a method like this:
public class ABC1 : Controller
{
public ActionResult OpenCase(string stepno, string key, string group)
{
// do stuff here
return View();
}
}
ASP.NET MVC will automatically map the query string parameters to the parameters in the method in the controller.
When defining routes, you cannot use a / at the beginning of the route:
routes.MapRoute("OpenCase",
"/ABC/{controller}/{key}/{group}", // Bad. Uses a / at the beginning
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
routes.MapRoute("OpenCase",
"ABC/{controller}/{key}/{group}", // Good. No / at the beginning
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
Try this:
routes.MapRoute("OpenCase",
"ABC/{controller}/{key}/{group}",
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
Then your action should look as follows:
public ActionResult OpenCase(int key, int group)
{
//do stuff here
}
It looks like you're putting together the stepNo and the "ABC" to get a controller that is ABC1. That's why I replaced that section of the URL with {controller}.
Since you also have a route that defines the 'key', and 'group', the above route will also catch your initial URL and send it to the action.
There is no reason to use routing based in querystring in new ASP.NET MVC project. It can be useful for old project that has been converted from classic ASP.NET project and you want to preserve URLs.
One solution can be attribute routing.
Another solution can be in writting custom routing by deriving from RouteBase:
public class MyOldClassicAspRouting : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext.Request.Headers == null) //for unittest
return null;
var queryString = httpContext.Request.QueryString;
//add your logic here based on querystring
RouteData routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", "...");
routeData.Values.Add("action", "...");
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Implement your formating Url formating here
return null;
}
}
And register your custom routing class
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.Add(new MyOldClassicAspRouting ());
}
The query string arguments generally are specific of that controller and of that specific application logic.
So it will better if this isn't written in route rules, that are general.
You can embed detection of query string on action argument in the following way.
I think that is better to have one Controller for handling StepNo.
public class ABC : Controller
{
public ActionResult OpenCase(OpenCaseArguments arg)
{
// do stuff here
// use arg.StepNo, arg.Key and arg.Group as You need
return View();
}
}
public class OpenCaseArguments
{
private string _id;
public string id
{
get
{
return _id;
}
set
{
_id = value; // keep original value;
ParseQueryString(value);
}
}
public string StepNo { get; set; }
public string Key { get; set; }
public string Group { get; set; }
private void ParseQueryString(string qs)
{
var n = qs.IndexOf('?');
if (n < 0) return;
StepNo = qs.Substring(0, n); // extract the first part eg. {stepNo}
NameValueCollection parms = HttpUtility.ParseQueryString(qs.Substring(n + 1));
if (parms.Get("Key") != null) Key = parms.Get("Key");
if (parms.Get("Group") != null) Group = parms.Get("Group");
}
}
ModelBinder assign {id} value to the id field of OpenCaseArguments. The set method handle querystring split logic.
And keep routing this way. Note routing get your querystring in id argument.
routes.MapRoute(
"OpenCase",
"ABC/OpenCase/{id}",
new {controller = "ABC", action = "OpenCase"}
);
I have used this method for getting multiple fields key value on controller action.

How to set a Default Route (To an Area) in MVC

Ok this has been asked before but there is no solid solution out there. So for purpose of myself and others who may find this useful.
In MVC2 (ASP.NET) I want it so when someone navigates to the website, there is a default area specified. So navigating to my site should send you to ControllerX ActionY in AreaZ.
Using the following route in the Global.asax
routes.MapRoute(
"Area",
"",
new { area = "AreaZ", controller = "ControllerX ", action = "ActionY " }
);
Now this works as in it does try to serve the correct page. However MVC proceeds to look for the View in the root of the site and not in the Area folder.
Is there a way to resolve this?
EDIT
There is a 'Solution' and that is in ControllerX, ActionY return the full path of the view. Bit of a hack but it does work. However I'm hoping there is a better solution.
public ActionResult ActionY()
{
return View("~/Areas/AreaZ/views/ActionY.aspx");
}
Edit:
This also becomes an issue when having a HTML ActionLink of the page. If the area is not set the Action Link is output blank.
Is all of this by design or a flaw?
This one interested me, and I finally had a chance to look into it. Other folks apparently haven't understood that this is an issue with finding the view, not an issue with the routing itself - and that's probably because your question title indicates that it's about routing.
In any case, because this is a View-related issue, the only way to get what you want is to override the default view engine. Normally, when you do this, it's for the simple purpose of switching your view engine (i.e. to Spark, NHaml, etc.). In this case, it's not the View-creation logic we need to override, but the FindPartialView and FindView methods in the VirtualPathProviderViewEngine class.
You can thank your lucky stars that these methods are in fact virtual, because everything else in the VirtualPathProviderViewEngine is not even accessible - it's private, and that makes it very annoying to override the find logic because you have to basically rewrite half of the code that's already been written if you want it to play nice with the location cache and the location formats. After some digging in Reflector I finally managed to come up with a working solution.
What I've done here is to first create an abstract AreaAwareViewEngine that derives directly from VirtualPathProviderViewEngine instead of WebFormViewEngine. I did this so that if you want to create Spark views instead (or whatever), you can still use this class as the base type.
The code below is pretty long-winded, so to give you a quick summary of what it actually does: It lets you put a {2} into the location format, which corresponds to the area name, the same way {1} corresponds to the controller name. That's it! That's what we had to write all this code for:
BaseAreaAwareViewEngine.cs
public abstract class BaseAreaAwareViewEngine : VirtualPathProviderViewEngine
{
private static readonly string[] EmptyLocations = { };
public override ViewEngineResult FindView(
ControllerContext controllerContext, string viewName,
string masterName, bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(viewName))
{
throw new ArgumentNullException(viewName,
"Value cannot be null or empty.");
}
string area = getArea(controllerContext);
return FindAreaView(controllerContext, area, viewName,
masterName, useCache);
}
public override ViewEngineResult FindPartialView(
ControllerContext controllerContext, string partialViewName,
bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(partialViewName))
{
throw new ArgumentNullException(partialViewName,
"Value cannot be null or empty.");
}
string area = getArea(controllerContext);
return FindAreaPartialView(controllerContext, area,
partialViewName, useCache);
}
protected virtual ViewEngineResult FindAreaView(
ControllerContext controllerContext, string areaName, string viewName,
string masterName, bool useCache)
{
string controllerName =
controllerContext.RouteData.GetRequiredString("controller");
string[] searchedViewPaths;
string viewPath = GetPath(controllerContext, ViewLocationFormats,
"ViewLocationFormats", viewName, controllerName, areaName, "View",
useCache, out searchedViewPaths);
string[] searchedMasterPaths;
string masterPath = GetPath(controllerContext, MasterLocationFormats,
"MasterLocationFormats", masterName, controllerName, areaName,
"Master", useCache, out searchedMasterPaths);
if (!string.IsNullOrEmpty(viewPath) &&
(!string.IsNullOrEmpty(masterPath) ||
string.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(CreateView(controllerContext, viewPath,
masterPath), this);
}
return new ViewEngineResult(
searchedViewPaths.Union<string>(searchedMasterPaths));
}
protected virtual ViewEngineResult FindAreaPartialView(
ControllerContext controllerContext, string areaName,
string viewName, bool useCache)
{
string controllerName =
controllerContext.RouteData.GetRequiredString("controller");
string[] searchedViewPaths;
string partialViewPath = GetPath(controllerContext,
ViewLocationFormats, "PartialViewLocationFormats", viewName,
controllerName, areaName, "Partial", useCache,
out searchedViewPaths);
if (!string.IsNullOrEmpty(partialViewPath))
{
return new ViewEngineResult(CreatePartialView(controllerContext,
partialViewPath), this);
}
return new ViewEngineResult(searchedViewPaths);
}
protected string CreateCacheKey(string prefix, string name,
string controller, string area)
{
return string.Format(CultureInfo.InvariantCulture,
":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:",
base.GetType().AssemblyQualifiedName,
prefix, name, controller, area);
}
protected string GetPath(ControllerContext controllerContext,
string[] locations, string locationsPropertyName, string name,
string controllerName, string areaName, string cacheKeyPrefix,
bool useCache, out string[] searchedLocations)
{
searchedLocations = EmptyLocations;
if (string.IsNullOrEmpty(name))
{
return string.Empty;
}
if ((locations == null) || (locations.Length == 0))
{
throw new InvalidOperationException(string.Format("The property " +
"'{0}' cannot be null or empty.", locationsPropertyName));
}
bool isSpecificPath = IsSpecificPath(name);
string key = CreateCacheKey(cacheKeyPrefix, name,
isSpecificPath ? string.Empty : controllerName,
isSpecificPath ? string.Empty : areaName);
if (useCache)
{
string viewLocation = ViewLocationCache.GetViewLocation(
controllerContext.HttpContext, key);
if (viewLocation != null)
{
return viewLocation;
}
}
if (!isSpecificPath)
{
return GetPathFromGeneralName(controllerContext, locations, name,
controllerName, areaName, key, ref searchedLocations);
}
return GetPathFromSpecificName(controllerContext, name, key,
ref searchedLocations);
}
protected string GetPathFromGeneralName(ControllerContext controllerContext,
string[] locations, string name, string controllerName,
string areaName, string cacheKey, ref string[] searchedLocations)
{
string virtualPath = string.Empty;
searchedLocations = new string[locations.Length];
for (int i = 0; i < locations.Length; i++)
{
if (string.IsNullOrEmpty(areaName) && locations[i].Contains("{2}"))
{
continue;
}
string testPath = string.Format(CultureInfo.InvariantCulture,
locations[i], name, controllerName, areaName);
if (FileExists(controllerContext, testPath))
{
searchedLocations = EmptyLocations;
virtualPath = testPath;
ViewLocationCache.InsertViewLocation(
controllerContext.HttpContext, cacheKey, virtualPath);
return virtualPath;
}
searchedLocations[i] = testPath;
}
return virtualPath;
}
protected string GetPathFromSpecificName(
ControllerContext controllerContext, string name, string cacheKey,
ref string[] searchedLocations)
{
string virtualPath = name;
if (!FileExists(controllerContext, name))
{
virtualPath = string.Empty;
searchedLocations = new string[] { name };
}
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext,
cacheKey, virtualPath);
return virtualPath;
}
protected string getArea(ControllerContext controllerContext)
{
// First try to get area from a RouteValue override, like one specified in the Defaults arg to a Route.
object areaO;
controllerContext.RouteData.Values.TryGetValue("area", out areaO);
// If not specified, try to get it from the Controller's namespace
if (areaO != null)
return (string)areaO;
string namespa = controllerContext.Controller.GetType().Namespace;
int areaStart = namespa.IndexOf("Areas.");
if (areaStart == -1)
return null;
areaStart += 6;
int areaEnd = namespa.IndexOf('.', areaStart + 1);
string area = namespa.Substring(areaStart, areaEnd - areaStart);
return area;
}
protected static bool IsSpecificPath(string name)
{
char ch = name[0];
if (ch != '~')
{
return (ch == '/');
}
return true;
}
}
Now as stated, this isn't a concrete engine, so you have to create that as well. This part, fortunately, is much easier, all we need to do is set the default formats and actually create the views:
AreaAwareViewEngine.cs
public class AreaAwareViewEngine : BaseAreaAwareViewEngine
{
public AreaAwareViewEngine()
{
MasterLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.master",
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.master",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Views/{1}/{0}.master",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.master"
"~/Views/Shared/{0}.cshtml"
};
ViewLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.aspx",
"~/Areas/{2}/Views/{1}/{0}.ascx",
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.aspx",
"~/Areas/{2}/Views/Shared/{0}.ascx",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.aspx"
"~/Views/Shared/{0}.ascx"
"~/Views/Shared/{0}.cshtml"
};
PartialViewLocationFormats = ViewLocationFormats;
}
protected override IView CreatePartialView(
ControllerContext controllerContext, string partialPath)
{
if (partialPath.EndsWith(".cshtml"))
return new System.Web.Mvc.RazorView(controllerContext, partialPath, null, false, null);
else
return new WebFormView(controllerContext, partialPath);
}
protected override IView CreateView(ControllerContext controllerContext,
string viewPath, string masterPath)
{
if (viewPath.EndsWith(".cshtml"))
return new RazorView(controllerContext, viewPath, masterPath, false, null);
else
return new WebFormView(controllerContext, viewPath, masterPath);
}
}
Note that we've added few entries to the standard ViewLocationFormats. These are the new {2} entries, where the {2} will be mapped to the area we put in the RouteData. I've left the MasterLocationFormats alone, but obviously you can change that if you want.
Now modify your global.asax to register this view engine:
Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaAwareViewEngine());
}
...and register the default route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Area",
"",
new { area = "AreaZ", controller = "Default", action = "ActionY" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
Now Create the AreaController we just referenced:
DefaultController.cs (in ~/Controllers/)
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
Obviously we need the directory structure and view to go with it - we'll keep this super simple:
TestView.aspx (in ~/Areas/AreaZ/Views/Default/ or ~/Areas/AreaZ/Views/Shared/)
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.
And that's it. Finally, we're done.
For the most part, you should be able to just take the BaseAreaAwareViewEngine and AreaAwareViewEngine and drop it into any MVC project, so even though it took a lot of code to get this done, you only have to write it once. After that, it's just a matter of editing a few lines in global.asax.cs and creating your site structure.
This is how I did it. I don't know why MapRoute() doesn't allow you to set the area, but it does return the route object so you can continue to make any additional changes you would like. I use this because I have a modular MVC site that is sold to enterprise customers and they need to be able to drop dlls into the bin folder to add new modules. I allow them to change the "HomeArea" in the AppSettings config.
var route = routes.MapRoute(
"Home_Default",
"",
new {controller = "Home", action = "index" },
new[] { "IPC.Web.Core.Controllers" }
);
route.DataTokens["area"] = area;
Edit: You can try this as well in your AreaRegistration.RegisterArea for the area you want the user going to by default. I haven't tested it but AreaRegistrationContext.MapRoute does sets route.DataTokens["area"] = this.AreaName; for you.
context.MapRoute(
"Home_Default",
"",
new {controller = "Home", action = "index" },
new[] { "IPC.Web.Core.Controllers" }
);
even it was answered already - this is the short syntax (ASP.net 3, 4, 5):
routes.MapRoute("redirect all other requests", "{*url}",
new {
controller = "UnderConstruction",
action = "Index"
}).DataTokens = new RouteValueDictionary(new { area = "Shop" });
Thanks to Aaron for pointing out that it's about locating the views, I misunderstood that.
[UPDATE] I just created a project that sends the user to an Area per default without messing with any of the code or lookup paths:
In global.asax, register as usual:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = ""} // Parameter defaults,
);
}
in Application_Start(), make sure to use the following order;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
in you area registration, use
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"ShopArea_default",
"{controller}/{action}/{id}",
new { action = "Index", id = "", controller = "MyRoute" },
new { controller = "MyRoute" }
);
}
An example can be found at
http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/
I really hope that this is what you were asking for...
////
I don't think that writing a pseudo ViewEngine is the best solution in this case. (Lacking reputation, I can't comment). The WebFormsViewEngine is Area aware and contains AreaViewLocationFormats which is defined per default as
AreaViewLocationFormats = new[] {
"~/Areas/{2}/Views/{1}/{0}.aspx",
"~/Areas/{2}/Views/{1}/{0}.ascx",
"~/Areas/{2}/Views/Shared/{0}.aspx",
"~/Areas/{2}/Views/Shared/{0}.ascx",
};
I believe you don't adhere to this convention. You posted
public ActionResult ActionY()
{
return View("~/Areas/AreaZ/views/ActionY.aspx");
}
as a working hack, but that should be
return View("~/Areas/AreaZ/views/ControllerX/ActionY.aspx");
IF you don't want to follow the convention, however, you might want to take a short path by either deriving from the WebFormViewEngine (that is done in MvcContrib, for example) where you can set the lookup paths in the constructor, or -a little hacky- by specifying your convention like this on Application_Start:
((VirtualPathProviderViewEngine)ViewEngines.Engines[0]).AreaViewLocationFormats = ...;
This should be performed with a little more care, of course, but I think it shows the idea. These fields are public in VirtualPathProviderViewEngine in MVC 2 RC.
I guess you want user to be redirected to ~/AreaZ URL once (s)he has visited ~/ URL.
I'd achieve by means of the following code within your root HomeController.
public class HomeController
{
public ActionResult Index()
{
return RedirectToAction("ActionY", "ControllerX", new { Area = "AreaZ" });
}
}
And the following route in Global.asax.
routes.MapRoute(
"Redirection to AreaZ",
String.Empty,
new { controller = "Home ", action = "Index" }
);
First, what version of MVC2 are you using? There have been significant changes from preview2 to RC.
Assuming you use the RC, I think you route-mapping should look differently. In the AreaRegistration.cs in your area, you can register some kind of default route, e.g.
context.MapRoute(
"ShopArea_default",
"{controller}/{action}/{id}",
new { action = "Index", id = "", controller="MyRoute" }
);
The code above will send the user to the MyRouteController in our ShopArea per default.
Using an empty string as a second parameter should throw an exception, because a controller must be specified.
Of course you will have to change the default route in Global.asax so it doesn't interfere with this default route, e.g. by using a prefix for the main site.
Also see this thread and Haack's answer: MVC 2 AreaRegistration Routes Order
Hope this helps.
Adding the following to my Application_Start works for me, although I'm not sure if you have this setting in RC:
var engine = (WebFormViewEngine)ViewEngines.Engines.First();
// These additions allow me to route default requests for "/" to the home area
engine.ViewLocationFormats = new string[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Areas/{1}/Views/{1}/{0}.aspx", // new
"~/Areas/{1}/Views/{1}/{0}.ascx", // new
"~/Areas/{1}/Views/{0}.aspx", // new
"~/Areas/{1}/Views/{0}.ascx", // new
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
What I did to get this to work is the following:
I created a default controller in the root/Controllers folder. I named my controller DefaultController.
In the controller I added the following code:
namespace MyNameSpace.Controllers {
public class DefaultController : Controller {
// GET: Default
public ActionResult Index() {
return RedirectToAction("Index", "ControllerName", new {area = "FolderName"});
}
} }
In my RouterConfig.cs I added the following:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {controller = "Default", action = "Index", id = UrlParameter.Optional});
The trick behind all this is that I made a default constructor which will always be the startup controller every time my app starts. When it hits that default controller it will redirect to any controller I specify in the default Index Action. Which in my case is
www.myurl.com/FolderName/ControllerName
.
routes.MapRoute(
"Area",
"{area}/",
new { area = "AreaZ", controller = "ControlerX ", action = "ActionY " }
);
Have you tried that ?
Locating the different building blocks is done in the request life cycle. One of the first steps in the ASP.NET MVC request life cycle is mapping the requested URL to the correct controller action method. This process is referred to as routing. A default route is initialized in the Global.asax file and describes to the ASP.NET MVC framework how to handle a request. Double-clicking on the Global.asax file in the MvcApplication1 project will display the following code:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing;
namespace MvcApplication1 {
public class GlobalApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index",
id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}
In the Application_Start() event handler, which is fired whenever the application is compiled or the web server is restarted, a route table is registered. The default route is named Default, and responds to a URL in the form of http://www.example.com/{controller}/{action}/{id}. The variables between { and } are populated with actual values from the request URL or with the default values if no override is present in the URL. This default route will map to the Home controller and to the Index action method, according to the default routing parameters. We won't have any other action with this routing map.
By default, all the possible URLs can be mapped through this default route. It is also possible to create our own routes. For example, let's map the URL http://www.example.com/Employee/Maarten to the Employee controller, the Show action, and the firstname parameter. The following code snippet can be inserted in the Global.asax file we've just opened. Because the ASP.NET MVC framework uses the first matching route, this code snippet should be inserted above the default route; otherwise the route will never be used.
routes.MapRoute(
"EmployeeShow", // Route name
"Employee/{firstname}", // URL with parameters
new { // Parameter defaults
controller = "Employee",
action = "Show",
firstname = ""
}
);
Now, let's add the necessary components for this route. First of all, create a class named EmployeeController in the Controllers folder. You can do this by adding a new item to the project and selecting the MVC Controller Class template located under the Web | MVC category. Remove the Index action method, and replace it with a method or action named Show. This method accepts a firstname parameter and passes the data into the ViewData dictionary. This dictionary will be used by the view to display data.
The EmployeeController class will pass an Employee object to the view. This Employee class should be added in the Models folder (right-click on this folder and then select Add | Class from the context menu). Here's the code for the Employee class:
namespace MvcApplication1.Models {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
Well, while creating a custom view engine can work for this, still you can have an alternative:
Decide what you need to show by default.
That something has controller and action (and Area), right?
Open that Area registration and add something like this:
public override void RegisterArea(AreaRegistrationContext context)
{
//this makes it work for the empty url (just domain) to act as current Area.
context.MapRoute(
"Area_empty",
"",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "Area controller namespace" }
);
//other routes of the area
}
Cheers!
Accepted solution to this question is, while correct in summing up how to create a custom view engine, does not answer the question correctly. Issue here is that Pino is incorrectly specifying his default route. Particularly his "area" definition is incorrect. "Area" is checked via DataTokens collection and should be added as such:
var defaultRoute = new Route("",new RouteValueDictionary(){{"controller","Default"},{"action","Index"}},null/*constraints*/,new RouteValueDictionary(){{"area","Admin"}},new MvcRouteHandler());
defaultRoute.DataTokens.Add("Namespaces","MyProject.Web.Admin.Controller");
routes.Add(defaultRoute);
Specified "area" in defaults object will be ignored. Code above creates a default route, which catches on requests to your site's root and then calls Default controller, Index action in Admin area. Please also note "Namespaces" key being added to DataTokens, this is only required if you have multiple controllers with same name. This solution is verified with Mvc2 and Mvc3 .NET 3.5/4.0
ummm, I don't know why all this programming, I think the original problem is solved easily by specifying this default route ...
routes.MapRoute("Default", "{*id}",
new { controller = "Home"
, action = "Index"
, id = UrlParameter.Optional
}
);

Passing a {sitename} parameter to MVC controller actions

How can I retrieve a site-wide URL parameter in a route without cluttering each controller action with a parameter? My question is similar to this question, but I want to avoid the ModelBinder clutter. Ie. in Global.asax.cs:
routes.MapRoute(
"Default", // Route name
"{sitename}/{controller}/{action}/{id}",
new { sitename = "", controller = "SomeController", action = "Index", id = "" } );
So, instead of the following in SomeController class:
public ActionResult Index(string sitename)
{
SiteClass site = GetSite(sitename);
...
return View(site.GetViewModel());
}
I would rather have the following:
public ActionResult Index()
{
SiteClass site = CurrentSite; // where CurrentSite has already retrieved data based on unique URL sitename parameter.
...
return View(site.GetViewModel());
}
Perhaps this can be achieved with controller-wide action filter? OnActionExecuting?
First add a route to Global.aspx.cs to pass a {sitename} parameter:
routes.MapRoute(
"Sites", // Route name
"{sitename}/{controller}/{action}/{id}", // URL with parameters
new { sitename = "", controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Then add the following simple code inside a base controller:
public class BaseController: Controller
{
public string SiteName = "";
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase req = filterContext.HttpContext.Request;
SiteName = filterContext.RouteData.Values["sitename"] as string;
base.OnActionExecuting(filterContext);
}
}
And use in your derived controller:
public class HomeController: BaseController
{
public ActionResult Index()
{
ViewData["SiteName"] = SiteName;
return View();
}
}
Perhaps I misunderstand the question, but why not simply do the following inside your controller action:
var sitename = RouteData.Values["sitename"] as string;
I don't understand why you would need to override OnActionExecuting (per #pate's answer) when you can retrieve the value when you need it.
There's also no need to create a base class and have everything derive from it. If you object to copying that line into every action method of every controller, why not create an extension method?

Resources