Using MVC 4 areas at custom path - asp.net-mvc

I'm trying to use areas at a custom path, and I'm having issues. I've been googeling a bunch, but havent found a solution.
My project is a EPiServer CMS project (which shouldn't have any effect I think, just wanna mention it, in case it does)
My structure is
Root
CompanyName
Areas
Commerce
Controllers
Models
Views
Cms
Controllers
HomePageController
Models
Views
HomePage
Index.cshtml
So I have a layer more to the tree then 'normal' which is the 'CompanyName'
I have this in global.asax.cs
protected void Application_Start()
{
ViewEngines.Engines.Add(new AreaTemplateViewEngineDynamic());
AreaRegistration.RegisterAllAreas();
...
}
I have a Custom RazorEngine (Could have just added more paths to the default, but have this solution as of now)
public class AreaTemplateViewEngineDynamic : RazorViewEngine
{
public AreaTemplateViewEngineDynamic()
{
this.PartialViewLocationFormats = this.ViewLocationFormats = this.MasterLocationFormats =
new string[]
{
"~/CompanyName/Views/{1}/{0}.cshtml", "~/CompanyName/Views/Shared/{0}.cshtml"
};
this.AreaMasterLocationFormats = this.AreaPartialViewLocationFormats = this.AreaViewLocationFormats =
new string[]
{
"~/CompanyName/Areas/{2}/Views/{1}/{0}.cshtml", "~/CompanyName/Areas/{2}/Views/Shared/{0}.cshtml"
};
}
}
Adding this area registration
public class CmsAreaRegistration: AreaRegistration
{
public override string AreaName
{
get { return "Commerce"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Cms_default",
"Cms/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "Root.CompanyName.Areas.Cms.Controllers" }
);
}
}
When I try to load the page, it seems it doesnt look at the Area paths, only the non-area paths.
The view 'index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/HomePage/index.aspx
~/Views/HomePage/index.ascx
~/Views/Shared/index.aspx
~/Views/Shared/index.ascx
~/Views/HomePage/index.cshtml
~/Views/HomePage/index.vbhtml
~/Views/Shared/index.cshtml
~/Views/Shared/index.vbhtml
~/CompanyName/Views/HomePage/index.cshtml
~/CompanyName/Views/Shared/index.cshtml
The path I want it to find is
~/CompanyName/Areas/Cms/Views/HomePage/index.cshtml
Also if I had to use
#{Html.RenderAction("MiniCart", "Cart", new { area = "Commerce"} );}
I would expect it to finde
~/CompanyName/Areas/Commerce/Views/Cart/MiniCart.cshtml

You are only setting the location for the AreaMasterLocation when you should also set the following locations:
AreaPartialViewLocationFormats
AreaViewLocationFormats
Find the following class in the object browser: VirtualPathProviderViewEngine for more properties and methods.

I wrote my own RazorViewEngine, where I added some custom codes to finding paths.
Could just use the URL, because the URL was controlled by the CMS, so the URL didnt represent the MVC path.

Related

Renaming "Areas" folder in ASP.NET MVC

Is it possible to rename Areas folder? Will visual studio and razor find correct path to views in this case ?
Yes it's possible! Let's make things easy focusing on the task of renaming an area from Foo to Bar.
Rename the area folder Foo to Bar
The area folder must contain a file which name ends in AreaRegistration.cs. Rename it from FooAreaRegistration.cs to BarAreaRegistration.cs. (current step is not mandatory but helps to keep your code clean)
Edit the file BarAreaRegistration.cs from:
public override string AreaName
{
get
{
return "Foo";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Foo_default",
"Foo/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
to:
public override string AreaName
{
get
{
return "Bar";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Bar_default",
"Bar/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
Rename also the namespace of the class from namespace xxxxx.Foo to xxxxx.Bar
Replace the namespace for every controller inside the Bar folder
Replace Foo with Bar in Views when needed (usually you get an absolute path when using specific area layout
Tested MVC > 4.0
Hope this helps
Yes it is possible to rename Areas folder in MVC. This is the location where Razor View Engine looks for Area Views. Add the following code in the Global.asax file. Now View Engine will search Area Views in the location ~/Modules/HelloArea/Views/Hello/Index.cshtml instead of ~/Areas/HelloArea/Views/Hello/Index.cshtml
RazorViewEngine viewEngine = new RazorViewEngine();
viewEngine.AreaMasterLocationFormats =
viewEngine.AreaPartialViewLocationFormats =
viewEngine.AreaViewLocationFormats =
new string[] {
"~/Modules/{2}/Views/{1}/{0}.cshtml",
"~/Modules/{2}/Views/Shared/{1}/{0}.cshtml"};
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(viewEngine);

ASP.NET MVC - Attribute Routing not finding area view

I've been searching for answers for this everywhere, but I can't seem to find any. I basically have an MVC application setup and I am using the built in AttributeRouting for my routes.
The folder structure looks like this;
Models
Views
Controllers
Areas
Member
MemberAreaRegistration.cs
Controllers
HomeController.cs
Views
Home
Account.cshtml
And then I wire up my routes in the global.asax like this;
public class Application : System.Web.HttpApplication {
protected void Application_Start(){
AreaRegistration.RegisterAllAreas();
// other web optimization stuff
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
So then, MemberAreaRegistration.cs is simple.
namespace App.Web.Areas.Member {
public class MemberAreaRegistration: AreaRegistration {
public override string AreaName { get { return "Member"; } }
}
public override void RegisterArea( AreaRegistrationContext context){ }
}
And I try to wire it using the attributes...
/areas/member/controllers/homecontroller.cs
// ...
[Route("member/account")]
public ActionResult Account() { return View(); }
// ...
The problem is that this finds the route, but it cannot find the view. I get the following error;
The view 'Account' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
~/Views/Home/Account.aspx
~/Views/Home/Account.ascx
~/Views/Shared/Account.aspx
~/Views/Shared/Account.ascx
~/Views/Home/Account.cshtml
~/Views/Home/Account.vbhtml
~/Views/Shared/Account.cshtml
~/Views/Shared/Account.vbhtml
By all accounts, this should work fine - and if not, I expect the ~/area to at least be in the path it is trying to search. Do I have to wire something additional up to make this function?
I am using ASP.NET MVC 5.0
If I hardcode the absolute path of the view, it works. Obviously this is not a good situation though. I'd prefer it to find the view out of convention. But if I type return View("~/areas/member/views/home/account.cshtml"); I do get the view back - so I know it can access to file and that it is correct.
Here is my RouteConfig.cs per request
RouteConfig.cs
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
// mvc attribute routing allows us to supersede normal routing mechanisms and
// declare our routes a bit more verbosely
routes.MapMvcAttributeRoutes();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "App.Web.Controllers" }
);
}
}
That's because, once you are defining your route as an action's attribute, ASP.NET MVC doesn't know which area it is in, hence it doesn't know where to look for Views.
In the Controller the Account action is in, try to explicitly specify a RouteArea attribute.
I'm writing this off the top of my head, but it should look like:
[RouteArea("Member")]
[RoutePrefix("member")]
public class HomeController: Controller {
[Route("account")]
public ActionResult Account() { return View(); }
}
or, alternatively:
[RouteArea("Member")]
public class HomeController: Controller {
[Route("member/account")]
public ActionResult Account() { return View(); }
}

ASP.NET MVC] Route match but controller never called. -> 404

Hi everyone I have a problem with MVC3 routing and areas:
My route matches but the controller is never called, instead i get a 404 error.
I have set an MVC3 solution with 2 project:
One is the main MVC Project: CityServices
One for an area: CityServices.Demo
In my area I register the following route:
public class TestAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Test";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Test_default",
"Test/{controller}/{action}",
new { controller = "Stuff", action = "ListAll" }
);
}
}
Content of StuffController.cs:
public class StuffController : Controller
{
public ActionResult ListAll()
{
List<Stuff> lstStuff = new List<Stuff>
{
new Stuff()
{
Id = 0,
Name = "HEeey",
Value = 10.456f
},
new Stuff()
{
Id = 1,
Name = "Beeee",
Value = 456789.47879999f
},
new Stuff()
{
Id = 2,
Name = "HooAAaoo",
Value = 0f
}
};
return Json(lstStuff);
}
}
I register area in the main project:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
//RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}
My route is matched: http://host/Test
But i always get a 404 error.
Any ideas?
Thanks,
- Jeremy
Edit: Here is a proof that the route matches:
Found Solution
So to sum it up.
My AreaRegistration inherited class was not in the same namespace as my controllers.
Since i did not have any views (it's just a restful websvc) i did not have to use MvcContribs which seems to be useful when you have views&controllers in a differents project than the main site.
Hope this helps if you arrive on this question ;)
You have your area in another project which won't work per default unless you are using MvcContribs portable areas or another custom implementation
I think you need to set the allow get for json:
return Json(data, JsonRequestBehavior.AllowGet)

asp.net mvc 2 preview 2 and Spark

Some body tried Spark View Engine with asp.net mvc 2 preview 2?
I have a problem with AREAS.
It looks likes spark engine looks **.spark* files inside of Views folders only instead of Areas folder in additionally.
My question is:
Somebody has information how to add it?
Spark will not automatically check the area views location in the current version. If you're willing to change the source (which i assume you are if you're doing mvc 2 stuff), here's the fix:
You have to modify the file src\Spark.Web.Mvc2\Descriptors\AreaDescriptorFilter.cs so that it reads as below (changes highlighted by **):
Note: I don't have the machine i did this on with me, so the slashes in the format string MIGHT need to be forward slashes.
Also, it is possible to create this class in your own code and pass it in when you register the view engine, but I don't remember the configuraiton code off the top of my head.
That's the approach I did since I wanted to modify the spark source as little as possible.
public class AreaDescriptorFilter : DescriptorFilterBase
{
**private const string areaPathFormatString = "~\\Areas\\{0}\\Views";**
public override void ExtraParameters(ControllerContext context, IDictionary<string, object> extra)
{
object value;
if (context.RouteData.Values.TryGetValue("area", out value))
extra["area"] = value;
}
public override IEnumerable<string> PotentialLocations(IEnumerable<string> locations, IDictionary<string, object> extra)
{
string areaName;
return TryGetString(extra, "area", out areaName)
**? locations.Select(x => Path.Combine(string.Format(areaPathFormatString,areaName), x)).Concat(locations)**
: locations;
}
}
Spark looks for a constraint or default value key "area" in a route to determine the view location. MVC 2 area support does not add this by default, you have to do it when declaring your area:
public class AdminRoutes : AreaRegistration
{
public override string AreaName
{
get { return "admin"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Dashboard", action = "Index", id = "", area = "admin" },
new [] { "MyProject.Areas.Admin.Controllers" });
}
}
Note the area = "admin" inside the defaults object.

MVC 2 AreaRegistration Routes Order

I noticed that in MVC 2 Preview 2, AreaRegistration is loading the routes for each area in an arbitrary order. Is there a good way to get one before the other?
For example, I have two areas - "Site" and "Admin". Both have a "Blog" controller.
I would like the following:
/admin/ --> go to Admin's Blog controller
/ --> go to Site's Blog controller.
The problem is that it is loading the site's route first, so it is matching {controller}/{action}/{id} instead of admin/{controller}/{action}/{id} when I go to the url "/admin/". I then get a 404, because there is no Admin controller in the "Site" area.
Both areas default to the "Blog" controller. I realize I could simply put site/{controller}/... as the url, but I would rather have it at the root if possible. I also tried keeping the default route in the global RegisterRoutes function, however, it is then not sent to the "Sites" area.
Thanks in advance!
Aside from what Haacked said, it is very much possible to order area registrations (and thus their routes). All you have to do is register each area manually, in whatever order you want. It's not as sleek as calling RegisterAllAreas() but it's definitely doable.
protected void Application_Start() {
var area1reg = new Area1AreaRegistration();
var area1context = new AreaRegistrationContext(area1reg.AreaName, RouteTable.Routes);
area1reg.RegisterArea(area1context);
var area2reg = new Area2AreaRegistration();
var area2context = new AreaRegistrationContext(area2reg.AreaName, RouteTable.Routes);
area2reg.RegisterArea(area2context);
var area3reg = new Area3AreaRegistration();
var area3context = new AreaRegistrationContext(area3reg.AreaName, RouteTable.Routes);
area3reg.RegisterArea(area3context);
}
Another option is to take the code for RegisterAllAreas(), copy it into your own app, and build your own mechanism for determining the order. It is quite a bit of code to copy if you want all the fancy caching logic that the built-in method does, but your app might not even need that.
Currently it's not possible to order areas. However, I think it makes sense to try and make each area as independent from other areas as possible so the order doesn't matter.
For example, instead of having the default {controller}/{action}/{id} route, maybe replace that with specific routes for each controller. Or add a constraint to that default route.
We are mulling over options to allow ordering, but we don't want to overcomplicate the feature.
I make this solution:
AreaUtils.cs
using System;
using System.Web.Mvc;
using System.Web.Routing;
namespace SledgeHammer.Mvc.Site
{
public static class Utils
{
public static void RegisterArea<T>(RouteCollection routes,
object state) where T : AreaRegistration
{
AreaRegistration registration =
(AreaRegistration)Activator.CreateInstance(typeof(T));
AreaRegistrationContext context =
new AreaRegistrationContext(registration.AreaName, routes, state);
string tNamespace = registration.GetType().Namespace;
if (tNamespace != null)
{
context.Namespaces.Add(tNamespace + ".*");
}
registration.RegisterArea(context);
}
}
}
In global.asax:
Utils.RegisterArea<SystemAreaRegistration>(RouteTable.Routes, null);
Utils.RegisterArea<ClientSitesAreaRegistration>(RouteTable.Routes, null);
//AreaRegistration.RegisterAllAreas(); do not dublicate register areas
No requred changes to generated area registration code.
I also use custom constrant in routes to filter routes by type of domain in request (system domain or user site).
This is my area registrations as example:
namespace SledgeHammer.MVC.Site.Areas.System
{
public class SystemAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "System"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"System_Feedback",
"Feedback",
new { controller = "Feedback", action = "Index" }
);
context.MapRoute(
"System_Information",
"Information/{action}/{id}",
new { controller = "Information", action = "Index", id = UrlParameter.Optional }
);
}
}
}
namespace SledgeHammer.MVC.Site.Areas.ClientSites
{
public class ClientSitesAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "ClientSites"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"ClientSites_default",
"{controller}/{action}/{id}",
new { controller = "Site", action = "Index", id = UrlParameter.Optional },
new { Host = new SiteInGroups("clients") }
);
}
}
}
For reference,
In MVC3 (don't know about MVC2) when you just want to map root to a specific area/controller you could simply use a global route.
Just remember to specify the namespace/area.
routes.MapRoute(
"CatchRoot", "",
new { controller = "SITEBLOG-CONTROLLER-NAME", action = "Index"}
).DataTokens.Add("area", "SITE-AREA-NAME");

Resources