I am new to MVC and started with MVC 4. I want to make an online shop application.
-Same shopping logic will be used by two different web sites/domains.
-And the views must alter according to domain name and their mobile versions.
Logic file structe is like this:
Controllers/HomeController.cs
Controllers/ProductController.cs
Models/Home.cs
Models/Product.cs
View file structure for first domain:
Views/my_1st_Domain/Home/Index.cshtml
Views/my_1st_Domain/Home/Index.Mobile.cshtml
Views/my_1st_Domain/Home/Terms.cshtml
Views/my_1st_Domain/Home/Terms.Mobile.cshtml
Views/my_1st_Domain/Product/Index.cshtml
Views/my_1st_Domain/Product/Index.Mobile.cshtml
Views/my_1st_Domain/Product/Detail.cshtml
Views/my_1st_Domain/Product/Detail.Mobile.cshtml
View file structure for second domain:
Views/my_2nd_Domain/Home/Index.cshtml
Views/my_2nd_Domain/Home/Index.Mobile.cshtml
Views/my_2nd_Domain/Home/Terms.cshtml
Views/my_2nd_Domain/Home/Terms.Mobile.cshtml
Views/my_2nd_Domain/Product/Index.cshtml
Views/my_2nd_Domain/Product/Index.Mobile.cshtml
Views/my_2nd_Domain/Product/Detail.cshtml
Views/my_2nd_Domain/Product/Detail.Mobile.cshtml
The question is:
How do switch between domain specific view folders automatically?
It can be done manually by repeating this line everywhere:
return View("~/Views/" + getDomainSpecificFolder() + "/Home/Index" + getMobileSuffixIfNeeded() + ".cshtml");
Is there any easier way to change base view folder globally with one shot?
Thanks in advance,
You can make own ViewEngine, and return specific view depending on your current domain.
In controller code you just need to return usual
return View();
And all logic will be handled in custom ViewEngine.
Here is some details about it -
http://weblogs.asp.net/imranbaloch/archive/2011/06/27/view-engine-with-dynamic-view-location.aspx
http://www.headcrash.us/blog/2011/10/custom-view-engine-for-localized-views-with-asp-net-mvc-razor/
Basically you need to make CustomViewEngine class, that inherits RazorViewEngine and register it in global.asax. Then in that class you need to overwrite FindView method, that will use path for view based your domain.
Related
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 my ASP.NET MVC site, my set up allows users to have roles, and roles have permissions. Generally, these permissions are set for a controller. In my site's main navigational menu, an Authenticated user can see all items, even if they aren't authorized to access that page.
Currently I can only configure the menu based off if the user is authenticated:
#if (Request.IsAuthenticated){ }
I'm wondering, what's the best way to pass the user's permissions to a view, only for the sake of configuring the menu for that user? Is there some common way of doing it, or will I have to implement this myself? I haven't found much information on it, but maybe I'm using the wrong search terms.
Thanks for any advice.
EDIT
Sorry I may not have been clear enough. This is my main nav menu, in the _Layout page. Also, permissions assigned to a role are very configurable by an admin (they can also create and delete roles), so checking if the user is in a role won't meet my needs.
You could create an action in say, the CommonController, which returns a partial view containing your navigation. This partial view can have its own model which can be populated from the controller. This allows you to use dependency injection for instance.
The action could look like this:
[ChildActionOnly]
public ActionResult Navigation()
{
var model = new NavigationModel();
// populate the model..
return PartialView("_Navigation", model);
}
You can render this partial in your view (_Layout.cshtml in your case) like this:
#Html.Action("Navigation", "Common")
For most cases, Request.IsAuthenticated is just fine. Only use this if you need something more advanced.
You can use Roles class static method IsUserInRole:
#if (Roles.IsUserInRole("Admin"))
{
// ...
}
The best way would be to have a property on the viewmodel that the view uses.
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());
I have a Controller called BaseController and Controller1 and Controller2 inherit from it.
All of the views for these controllers are under ~/Views/Base/ the reason for this is that Controller1 and 2 do the exact same thing but have custom attributes for certain things on some overriden actions.
I want to be able to point to ~/Views/Base as the location to look for views in both Controller1 and Controller2. Now can i do this without having to implement my own ViewLocator as per Dale's solution in this post Views in separate assemblies in ASP.NET MVC
I would prefer to not throw all these views into ~/Views/Shared as they aren't really shared except in between these two Controllers.
You could write a custom view engine in order to add this new location in the search list:
public class MyViewEngine : RazorViewEngine // WebFormViewEngine - if you are using WebForms
{
public MyViewEngine()
{
ViewLocationFormats = base.ViewLocationFormats.Union(new[]
{
"~/Views/Base/{0}.cshtml",
"~/Views/Base/{0}.vbhtml",
"~/Views/Base/{0}.aspx",
"~/Views/Base/{0}.ascx",
}).ToArray();
}
}
which will be registered in Application_Start:
ViewEngines.Engines.Add(new MyViewEngine());
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.