I have only tried this in single project areas. So if anyone tries this in a multi-project areas solution please let us know.
Area support was added to MVC2. However the views for your controllers have to be in your main Views folder. The solution I present here will allow you to keep your area specific views in each area. If your project is structured like below, with Blog being an area.
+ Areas <-- folder
+ Blog <-- folder
+ Views <-- folder
+ Shared <-- folder
Index.aspx
Create.aspx
Edit.aspx
+ Content
+ Controllers
...
ViewEngine.cs
Add this code to the Application_Start method in Global.asax.cs. It will clear your current view engines and use our new ViewEngine instead.
// Area Aware View Engine
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaViewEngine());
Then create a file named ViewEngine.cs and add the code below.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
namespace MyNamespace
{
public class AreaViewEngine : WebFormViewEngine
{
public AreaViewEngine()
{
// {0} = View name
// {1} = Controller name
// Master Page locations
MasterLocationFormats = new[] { "~/Views/{1}/{0}.master"
, "~/Views/Shared/{0}.master"
};
// View locations
ViewLocationFormats = new[] { "~/Views/{1}/{0}.aspx"
, "~/Views/{1}/{0}.ascx"
, "~/Views/Shared/{0}.aspx"
, "~/Views/Shared/{0}.ascx"
, "~/Areas/{1}/Views/{0}.aspx"
, "~/Areas/{1}/Views/{0}.ascx"
, "~/Areas/{1}/Views/Shared/{0}.aspx"
, "~/Areas/{1}/Views/Shared/{0}.ascx"
};
// Partial view locations
PartialViewLocationFormats = ViewLocationFormats;
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new WebFormView(partialPath, null);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
return new WebFormView(viewPath, masterPath);
}
} // End Class AreaViewEngine
} // End Namespace
This will find and use the views you have created in your areas.
This is one possible solution that allows me to keep views in the specified area. Does anyone else have a different, better, enhanced solution?
Thanks
I'm sorry to be the one to tell you this, but you must be missing something. I currently have your scenario working out of the box with ASP.NET MVC 2 RC.
I assume you have all the register routes and have the correct web.config files inside your area's view folder?
Maybe have a look at this walk through, especially the part about creating the areas.
HTHs,
Charles
EDIT:
Ok, so you're not happy about putting in the extra new { area = "blog' }, null - fair enough, I'll admit its niggly... but what else are you going to do?
What happens when you have two controllers with the same name? One in your root project and one in an area or two controllers with the same name in two different areas? How is it going to find the correct view?
Also, I do see a problem with your ViewLocationFormats. All of the area view locations have no reference to their area... e.g. ~/Areas/{1}/Views/{0}.ascx - how does it know what area?
If you are suggesting that all the different area's views and all thrown into the Areas folder under their controller name and then found under Views and Views/Shared - I would highly recommend against that... It'll become a mess very quickly.
So where does that leave you? It really leaves you needing to specify the area when creating the route. It really boils down to the fact that although it's niggly having to specify the area, there really is no other way.
This solution works well in Mvc2. It is not necessary in Mvc3.
Related
I have read a nice article about how I can use feature folder structure in my ASP.NET Core MVC application. My plan is to use then a feature folder structure to organize my web application in a better way.
First of all lets see my folder structure:
...
wwwroot
Claims
Controllers
Services
Views
Shared
Map
...
I have followed the article and I implemented the IViewLocationExpander like the following:
public class MyViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context)
{
context.Values["customviewlocation"] = nameof(MyViewLocationExpander);
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
var viewLocationFormats = new[]
{
"/Claims/Views/{0}.cshtml",
"/Claims/Views/Shared/{0}.cshtml"
};
return viewLocationFormats;
}
}
I placed my main Claims.cshtml view in the "/Claims/Views/" folder. At the beginning of my Claims.cshtml I have to following line to render my partial view:
#Html.Partial("_NewClaimPopup");
As for the _NewClaimPopup.cshtml, it placed it into the path "/Claims/Views/Shared". But unfortunately I got the following exception when trying to GET the following url: http://localhost:13078/Claims/Claims
InvalidOperationException: The partial view '_NewClaimPopup' was not found. The following locations were searched:
/Views/Claims/_NewClaimPopup.cshtml
/Views/Shared/_NewClaimPopup.cshtml
It seems that the custom paths are successfully added by the implementation of IViewLocationExpander.
Additional infos:
What I also tried is to use "~" sign in paths of the implementation of IViewLocationExpander, so: "~/Claims/Views/{0}.cshtml" and "~/Claims/Views/Shared/{0}.cshtml" but it does not help.
I tried to use absolute path for rendering my partial view, but still nothing
#Html.Partial(""~/Claims/Views/_NewClaimPopup.cshtml");
And of course, I registered my expander in the Startup.cs:
services.Configure<RazorViewEngineOptions>(options => options.ViewLocationExpanders.Add(new MyViewLocationExpander()));
Last but not least I attach a picture about my project structure:
Any other idea? Thanks in advance for any help!
I have made sample application. You can download from below link
Custom View Location
I have a C# MVC Razor site. Typically, Controllers load views from the Views folder. However, I have a special circumstance where I need to render a view outside of the Views folder. How do I do that?
Controller will load /Views/Random/Index.cshtml
Can't load /Random/Index.cshtml
/Random/test.aspx loads with no issues, but can't change cshtml files to aspx files, they need to be built regularly.
I have tried return Redirect("/Random/Index.cshtml") in the Controller, and currently have no controller at all.
The weird thing is it works on my Production environment, but not in localhost. In localhost I get:
The type of page you have requested is not served because it has been explicitly forbidden. The extension '.cshtml' may be incorrect. Please review the URL below and make sure that it is spelled correctly.
Requested URL: /Random/Index.cshtml
You can definitely do this. For doing this you need to create one new custom view engine like
public class MyViewEngine : RazorViewEngine
{
private static string[] AdditionalViewLocations = new[]{
"~/Random/{0}.cshtml"
};
public MyViewEngine()
{
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(AdditionalViewLocations).ToArray();
base.ViewLocationFormats = base.ViewLocationFormats.Union(AdditionalViewLocations).ToArray();
base.MasterLocationFormats = base.MasterLocationFormats.Union(AdditionalViewLocations).ToArray();
}
}
Then in you global.asax's Application_Start method register this view engine like this-
ViewEngines.Engines.Add(new MyViewEngine ());
If you want your viewengine to take precedence then insert this at 0th position. like this -
ViewEngines.Engines.Insert(0, new MyViewEngine());
return View("~/AnotherFolder/Index.cshtml")` should work for you.
Do not forget to indicate the Layout in your index view:
#{
Layout="~/Views/Shared/Layout.cshtml";
}
In MVC 4, you can just append .Mobile to any view and mobile devices will automatically get served that view from the same controller. Is there a way to store the .Mobile files in a different folder? I really want to store the desktop files in one "Area" and the mobile in another "Area". Anyone know of something like this?
This can easily be accomplished by creating a custom implementation of RazorViewEngine and adding the custom mappings to the ViewLocationFormats. It is important to remember to add the custom mappings to the beginning of the ViewLocationFormats array as they are more specific than the existing mappings.
namespace MobileViewsInMobileFolder.Utility {
public class MyCustomViewEngine : RazorViewEngine {
public MyCustomViewEngine() {
List<string> existingViewLocationFormats = ViewLocationFormats.ToList();
//Folder Structure: Views\Home\Desktop and Views\Home\Mobile
existingViewLocationFormats.Insert(0, "~/Views/{1}/Desktop/{0}.cshtml");
existingViewLocationFormats.Insert(0, "~/Views/{1}/Mobile/{0}.cshtml");
//Folder Structure: Views\Desktop\Home and Views\Mobile\Home
existingViewLocationFormats.Insert(0, "~/Views/Desktop/{1}/{0}.cshtml");
existingViewLocationFormats.Insert(0, "~/Views/Mobile/{1}/{0}.cshtml");
ViewLocationFormats = existingViewLocationFormats.ToArray();
}
}
}
And then make sure to add the custom view engine in the Application_Start
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new MyCustomViewEngine());
We want to use Orchard for a website. We are creating a custom module/widget for that cms and in that module we want to use a GridView from DevExpress to show data. We got most of it working, but we can't get callbacks to work. With that i mean things like navigating through pages, sorting rows and moving columns.
If we look in the console we can see that the javascript and ajax callbacks are never executed, we can't figure out why that is so. I have found some topics on the DevExpress site and this site about using DevExpress with Orchard, but i couldn't find anything usefull (for my case) in those. We also noticed that the methods of our controller are never called, but cannot figure out why not.
I found that sometimes jQuery can cause problems for DevExpress controls, so i tried removing all jQuery scripts, but that didn't make a difference. Someone also suggested to put a callbackpanel around te gridview, but that didn't work either. I have tried many more things (which i mostly forgot already) but nothing worked so far.
I have also asked the same question on the DevExpress website end the Orchard forums but i'm not getting any answers there, so i thought i'd try my luck here.
I have made an example project in case you want to see what i'm trying to do. The file is 40MB because i added the entire cms to itwith example daabase, including our module. The module is called GridViewTest You can find the source here:http://www.obec.nl/download/Orchard-DevExpress.zip.
I have finally found a solution. It turned out to be a pretty simple one (like usual) and i want to share it here, in case other people want to use DevExpress with Orchard:
In your Orchard module, you have to create a Routes.cs file (in the root of the module). There you have to add this:
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Mvc.Routes;
namespace CentralStationDataView
{
public class Routes : IRouteProvider
{
public void GetRoutes(ICollection<RouteDescriptor> routes)
{
foreach (var routeDescriptor in this.GetRoutes())
{
routes.Add(routeDescriptor);
}
}
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[]
{
new RouteDescriptor
{
Priority = 5,
Route = new Route(
"AreaName",
new RouteValueDictionary
{
{ "area", "AreaName" },
{ "controller", "ControllerName" },
{ "action", "ActionName" }
},
new RouteValueDictionary(),
new RouteValueDictionary
{
{ "area", "AreaName" }
},
new MvcRouteHandler())
}
};
}
}
}
You can make the AreaName up as you like, it doesn't matter (as far as i know) what you call it. Make sure that you don't add the "Controller" suffix to the ControllerName.
Then, in your GridView settings you have to add this:
settings.CallbackRouteValues = new { area = "AreaName", Controller = "ControllerName", Action = "ViewDataPartial" };
These values have to all be exactly the same as the values in the Routes.cs file. The "area" property was critical for me, i already had the Routes.cs file and everything, but i didn;t add the area property to the CallbackRouteValues.
The second part of the solution is that you have to make a partial view with only and i stress, only, the GridView inside it. So no scripts, no extra html elements, no text, nothing.
I'm looking to create a common ASP.MVC (c# and razor) base project containing common controllers, _Layout.cshtml, css and js for many of our webapps to extend from.
I thought that using MvcContrib and creating Portable Areas is my best bet
So the folder setup is roughly like this
BaseProj
Content
js
plugins
misc
images
foo
css
Controllers
Views
MainProj
Content
Controllers
Views
I am registering the BaseProj area by extending PortableAreaRegistration class (as per MvcContrib docs) so this url works...
htttp://localhost/MainProj/BaseProj/
Looking in the MvcContrib code for PortableAreaRegistration it also registers 3 static routes too (images, styles, scripts) under Content.
Therefore this url works fine too...
htttp://localhost/MainProj/BaseProj/images/bar.jpg
also subfolders work fine only if i use a dot instead of a slash...
htttp://localhost/MainProj/BaseProj/images/foo.bar.jpg
However its really useful to have subfolders under images, css, js etc for organisation purposes. These subfolders will now 404 though
ie this will not work....
htttp://localhost/MainProj/BaseProj/images/foo/bar.jpg
So the question is how do i map subfolders under images (without the dot notation)?
ie so this works....
htttp://localhost/MainProj/BaseProj/images/foo/bar.jpg
Thanks
EDIT:
Well a bit of thinking and I found a solution. I wonder if this kind of thing might be included in MvcContrib.Portable Areas by default.
First create a catch all route in your BaseProjRegistration.cs to catch all subfolders of Content
public override void RegisterArea(AreaRegistrationContext context, IApplicationBus bus)
{
...
//static controller with catch all for all subfolders of content
context.MapRoute(
"BaseProjContent",
"BaseProj/Content/{*resourceName}",
new { controller = "Content", action = "LoadContent", resourcePath = "Content" }
);
...
}
Then Create a ContentController class in your BaseProj which will extend MvcContrib.PortableAreas.EmbeddedResourceController.
This will convert slashes to dots to load the BaseProj content from the DLL
ie /BaseProj/Content/images/foo/bar.jpg => /BaseProj/Content/images.foo.bar.jpg
public class ContentController : MvcContrib.PortableAreas.EmbeddedResourceController
{
public ActionResult LoadContent(string resourceName, string resourcePath)
{
string actualResourceName = resourceName.Replace("/", ".");
return base.Index(actualResourceName, resourcePath);
}
}
This worked for me, but any other recommnedations of how to setup a common base project for lots of webapps to extend are welcome