Is there a good way to change the MasterPage and/or .css dynamically in asp.net mvc based on the user preferences?
I understand I can change the master name as follows:
return View("viewName", "master-name", oModel)
and the view using a different contentPlaceHolder perhaps but that requires changing each controller+action.
I'd have to assume there is to be a better way than this.
I have a somewhat simpler method:
return View("View", getMasterName());
and in my master controller, I have:
protected string getMasterName() {
return (Request.QueryString["tb"] == null) ? null : "Other_Master";
}
I use it to display a different template in the case of a thickbox popup vs if, eg, javascript isn't working and the controller is loaded without thickbox.
Here's interesting reading http://developmentalmadness.blogspot.com/2009/06/aspnet-mvc-discover-masterpagefile.html
Related
I'm new to Sitecore MVC and currently with web forms I have all the sites organized under:
\Website\Sites\Site1\css|js|Layouts|Sublayouts|etc.
\Website\Sites\Site{n}\css|js|Layouts|Sublayouts|etc.
I'm able to add an MVC site to my solution and works fine alongside the web forms sites; however, adding a second MVC site that happen to have the same controller/view names generates a conflict.
For example, if I create a controller for Site1
Controllers/Site1/FooController (has index and hello)
Then the views are:
Views/Foo/Index
Views/Foo/Hello
But if Site2 also has a controller with the same name then it's a conflict:
Controllers/Site2/FooController (has index and hello)
Then the views are:
Views/Foo/Index
Views/Foo/Hello
But they're used by Site1.
The question is how to setup two (or more) MVC sites that happen to have the same controller/view names. Is there a recommended way to structure the sites in the solution or do I have to override pipelines/processors?
Thanks
Update:
Thanks everyone. Areas solved my problem but introduced two new problems:
The conflict in the controller names which solved by putting the namespace, class and dll names in the controller name in Sitecore - reference: http://blog.xcentium.com/2014/03/sitecore-mvc-and-duplicate-controller-names/
When the controller returns a view, I have to put the full path of the view; otherwise, I get an error where the view is not found.
For example: return View("~/Areas/Site1/Views/Home/Index.cshtml");
I'm looking into a fix provided from a developer from Sitecore's forum:
http://www.chrisvandesteeg.nl/2014/06/13/sitecore-mvc-in-a-multisite-environment-areas/
I'll try it out and report back.
you need to use namespaces in routes.MapRoute, look at the below posts which have already discussed:
Is it possible, in MVC3, to have the same controller name in different areas?
Multiple MVC projects in a single solution
and below is the post by John west post which relates your situation:
http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2012/06/Using-Web-Forms-and-MVC-in-a-Single-Solution-with-the-Sitecore-ASPNET-CMS.aspx
We had the similar problem and answer was to separate out every site with MVC areas and they works perfectly. Though we ran into issue of controller name duplication but that can be resolved by adding the namespace during the area route registration.
But a clean way to implement this is to let Sitecore know about the MVC areas and initialize your controller/action with area and namespace. This process has been blogged by Kevin and he has a package as well. It expect you to define the area name in controller rendering.
http://webcmd.wordpress.com/2013/01/24/sitecore-mvc-area-controller-rendering-type/
To avoid the hard coded path of view(s) you can always extend controller rendering template to add view path and create an action filter to add the view path after action is executed. Add the below code in action filter and register the filter in sitecore action filter registration pipeline.
public void OnActionExecuted(ActionExecutedContext filterContext)
{
ViewResult result= filterContext.Result as ViewResult;
if(result == null) return;
Rendering redering = RenderingContext.CurrentOrNull.With(x=>x.Rendering).Return(x=>x,null);
string viewName= rendering.Return(r=> r.GetFieldValue(CustomMvcSettings.ViewPathField), string.Empty);
if(String.IsnullOrEmpty(viewName)) return;
result.ViewName = viewName;
}
The best thing you can do is split your websites up in different projects in the same solution.
Building two websites in the same project can become unstructured and messy.
After that you can route the controllers with the same name using the different namespaces.
Sitecore mvc duplicate controller
Just to keep this topic in sync with the SDN forum,
I recommend using a sitecore specific constraint, as described at
http://www.chrisvandesteeg.nl/2014/06/13/sitecore-mvc-in-a-multisite-environment-areas/
This solution allows you to set the attribute mvcArea on your configuration node
I have an ASP.Net MVC 4 application where the user can choose a theme or a design of for their hosted one-page site (within this application). At first, I thought to do this with the built-in Areas but due to some application restrictions I've decided not to use that method. The way I thought to do it (which so far works) is to send the user to the index action of the controller, there find out which theme they have chosen and then return the appropriate view. this way I don't have the action name on the url which is nice since the url needs to be simple like: abc.com/cb/websiteID. btw, every theme/design has one view in the folder.
For some reason this method does not sit well with me and I think there should be a better way of doing this. Is there a downfall to this? Is this method a bad practice? is there a better way?
If I've left out a detail, please let me know and I'll do my best to address it.
Do you have a limited set of themes, which your users can choose from?
If so, I would consider to have a layout-per-theme instead, have a single view and dynamically switch layout based on params...
//in your controller
public ActionResult(int id) {
string layoutForThemeName = SomeService.GetThemeForUser(id);
ViewBag.LayoutName = layoutForThemeName
}
// in your view Index.cshtml
#{
Layout = ViewBag.LayoutName;
}
Do not forget that Razor let you inherit one layout from another, so you could event create base layout with script references etc. and layout for every of your themes.
I am running out of ideas here. Maybe you can advice me what pattern or method(s) to use.
User should be able to log in and change the appearance only for his/her profile.
The difference (AFAIK) with personalization is that personalized layout are seen only for the editor (him-/herself).
The difference between skinning, I guess, is that Skins are predefined but users should be able to change the settings themselves.
I need to be able to display the customized layout to everyone who visit author`s page.
The good solution would be to keep the layout info in a DB table. Also it should be cached I guess to take load off the DB and used in CSS.
Thanks
Edit:
OK I have done some research now. Came up with this kind of idea.
In a View get a userId (Guid type) from a DB and set it to the ViewData:
ViewData["userId"] = profile.userId;
That View uses the following MasterPage called 'Profile.Master' and links to the dynamic CSS file:
<link href="<%= Url.Action("Style", "Profile",
ViewData["userId"]) %>" rel="stylesheet" type="text/css" />
</head>
In the ProfileController get the CSS data from DB and return it to the dynamic CSS View:
public ActionResult Style(Guid userId)
{
var styles = (from s in Db.UserStyleSet.OfType<UserStyle>()
where s.aspnet_Users.UserId == userId
select s);
return View("Style", styles);
}
The problem is that the UserId is never passed to the dynamic CSS link:
The parameters dictionary contains a null entry for parameter 'userId' of non-nullable type 'System.Guid' for method 'System.Web.Mvc.ActionResult Style(System.Guid)' in 'Project.Controllers.ProfileController'.
Any advice is welcome, thank you.
Very neat layout customization features you can find in Kona project developed by Rob Conery. When you run source code which you can find here, you will see layout management UI which allows you to change the position of each component on the screen.
The approach used there is as follows:
When page is rendered our customized view engine check which master page should present (this way we are able to switch themes based on current settings)
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
ViewEngineResult result = null;
var request = controllerContext.RequestContext;
if (controllerContext.Controller.GetType().BaseType == typeof(KonaController)) {
var orchardController = controllerContext.Controller as KonaController;
string template = orchardController.ThemeName;
View engine uses master page and renders view which was defined by specific controller action resolved using route tables. For instance, we typed main url of the site which pointed to Home Controller, Index method. This method returned Index.aspx view which was rendered by View engine.
While view engine is rendering the Index.aspx page it launches helper methods like
<%this.RenderWidgets("sidebar1"); %>.
This method is truely responsible for rendering specific widdgets per each div in the aspx page. This way, if your user changes the layout of the widgets they will be correctly presented on the screen.
public static void RenderWidgets(this ViewPage pg, Kona.Infrastructure.Page page, bool useEditor, string zone) {
if (page != null) {
foreach (IWidget widget in page.Widgets.Where(x => x.Zone.Equals(zone, StringComparison.InvariantCultureIgnoreCase))) {
string viewName = useEditor ? widget.EditorName : widget.ViewName;
if (widget.ViewName != null) {
if (widget.IsTyped) {
var typedWidget = widget as Widget<IList<Product>>;
pg.Html.RenderPartial(viewName, typedWidget);
} else {
pg.Html.RenderPartial(viewName, widget);
}
} else if (!string.IsNullOrEmpty(widget.Title)) {
pg.Html.RenderPartial("TitleAndText", widget);
} else {
pg.Html.RenderPartial("TextOnly", widget);
}
}
}
}
How user is able to change the layout? Kona has very neat javascript which is used together with Ajax and user simply drag&drop widgets from one panel to another to reorder the layout.
You could use a CMS framework. See this question for suggestions
You could dynamically build a CSS file and save the css name in the user's db entry.
How much customisation do you need? Storing an entire css in the database 1 style at a time seems a little overkill, are you sure your users really need / want that level of customisation?
Wouldn't it be simpler to present a list of themes, allow the user to select the one they want and then store that information with the user profile so that when you retrieve the profile details you also retrieve the theme. This information can then be used to select the appropriate master as well as passed to the view to render the correct stylesheet(s).
If you really want to allow extreme customisation down to the individual style level, I would use a default css and then when the user customises their layout, copy the default and alter as necessary, creating a custom css for the user. Each time the user updates their profile layout, simply update the css file with the changes. To get around css caching, record an incrementing version number for each change and append that to the end of the url for the css e.g. <link rel="stylesheet" href="user001.css?v=2>.
Take the example of wanting to have a "Latest news items" sidebar on every page of your ASP.NET MVC web site. I have a NewsItemController which is fine for pages dedicating their attention to NewsItems. What about having a news sidebar appear on the HomeController for the home page though? Or any other controller for that matter?
My first instinct is to put the logic for selecting top 5 NewsItems in a user control which is then called in the Master Page. That way every page gets a news sidebar without having to contaminate any of the other controllers with NewsItem logic. This then means putting logic in what I understood to be the presentation layer which would normally go in a Controller.
I can think of about half a dozen different ways to approach it but none of them seem 'right' in terms of separation of concerns and other related buzz-words.
I think you should consider putting it in your master page. Your controller can gather data (asynchronously, of course), store it in a nice ViewModel property for your view (or in TempData) and then you can call RenderPartial() in your master page to render the data.
The keeps everything "separate"
http://eduncan911.com/blog/html-renderaction-for-asp-net-mvc-1-0.aspx
This seems to address the question - even using the instance of a sidebar - but using a feature not included with MVC 1 by default.
http://blogs.intesoft.net/post/2009/02/renderaction-versus-renderpartial-aspnet-mvc.aspx
This also indicates the answer lies in RenderAction.
For anyone else interested, here's how I ended up doing it. Note you'll need to the MVC Futures assembly for RenderAction.
Basically you'd have something like this in your controller:
public class PostController
{
//...
public ActionResult SidebarBox()
{
// I use a repository pattern to get records
// Just replace it with whatever you use
return View(repoArticles.GetAllArticles().Take(5).ToList());
}
//...
}
Then create a partial view for SidebarBox with the content you want displayed, and in your Master Page (or wherever you want to display it) you'd use:
<% Html.RenderAction<PostController>(c => c.SidebarBox()); %>
Not so hard after all.
You can create a user control (.ascx) and then call RenderPartial().
Design a method in your controller with JsonResult as return type. Use it along with jQuery.
Use RenderAction() as suggested by elsewhere.
News section with ASP.NET MVC
I am building what will primarily be a mobile browser targeted ASP.NET MVC application. Although it will target the desktop as well in a smaller capacity. (I'm fairly new to both MVC and mobile apps.)
I'm wondering what is the best practice for segregating mobile vs. desktop users in an MVC application.
Should the controller be in charge of checking for browser type? Or, should this type of functionality be reserved for the View?
If checked in the view, can & should a masterpage do the checking? Do you know of any good examples online?
Update:
I just discovered an overload of the View method that accepts a string argument specifying the Masterpage to be used.
For example:
public ActionResult Index()
{
if (isMobile())
return View("Index", "Mobile", myObject);
else
return View("Index", myObject);
}
To me this suggests that at least a few people on the Microsoft team expect major distinctions (such as mobile vs. desktop) to be carried out in the controller. (There's a good chance I'm highly confused about this.)
I think the controller must know the plataform, because you can get a lot of views in distint languages, Some view for browsers (mobile) another view in Desktop App, another view can be a web service, and all views can have different needs.
If you have a few views, you can call views with parameters to mark the type of view:
Index(mobile) and Index(Desktop) as it:
Index(string typeOfApp){
//prepare data, do querys, etc
if (typeOfApp=='Mobile'){
redirectoAction('IndexMobile',ds);
//or
return view('IndexMobile',ds)
}
return View('IndexDesktop',ds);
}
IndexMobile(DataSet ds){}
IndexDesktop(DataSet ds){}
You can get a general method for your action() and another action for every type,
Index -> Index4Mobile & Index4Browser & Index4Desktop
And in all of this methods prepare or do something special for every plataform, or a Single Action with multiple views(1 for plataform).
Any code dealing with rendering of your code should exist on the page itself via CSS and javascript. Your controllers should know nothing about how your data will be rendered on screen. The views shouldn't really even know anything about it either - they only expose the data that your CSS will render.
The HTML your View spits out describes your data and how it is organized. Only the CSS should know how to make it look appropriate for whatever device is rendering it.
This link, chock full of javascript should help determine which mobile browser is running.