.NET MVC: How to implement different page appearance per user? - asp.net-mvc

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>.

Related

When using ASP.NET MVC, what is the best way to update multiple page sections with one HTML.Action() method?

I have a pretty big ASP.NET MVC site with 100 controllers and thousands of actions. Previously the header image that was defined on the Site.Master page was hardcoded and I want to make it dynamic.
To do so, I added this line to my Site.Master file:
<%= Html.Action("GetHeaderTitle", "Home")%>
which just returns some HTML for the header title such as:
<span style='font-size:15px;'>My Header Title</span>
The issue is that <title> also had this same hard coded value. I could obviously create another HTML.Action to have it show the dynamic valid in the title, but now I am going back to the server twice for essentially the same information (not the exact same HTML as I don't want the span information, but the same logic on the server to get the data).
Is there a way to have an Html.Action return multiple snippets of HTML that I can updates in different places on my master page?
I think you're looking at it wrong - if retrieving of the title is a long operation then just cache the results and write different actions anyway.
// Controller
public string GetTitle()
{
var title = (string)ControllerContext.HttpContext.Items["CachedTitle"];
if (string.IsNullOrEmpty(title))
{
title = "some lengthy retrieval";
ControllerContext.HttpContext.Items["CachedTitle"] = title;
}
return title;
}
public ActionResult GetTitleForTitle()
{
return Content(GetTitle());
}
public ActionResult GetHeaderTitle()
{
return Content("<span>"+ GetTitle() + "<span>");
}
Alternatively, you can cache it directly on the view, which is kind of evil (the simpler view the better):
<%
ViewBag.CachedTitle = Html.Action("GetHeaderTitle", "Home");
%>
...
<%= ViewBag.CachedTitle %>
...
<%= ViewBag.CachedTitle %>
Are you using <asp:ContentPlaceHolder ID="TitleContent" runat="server" /> ? in your site.master? Or have you considered using it? This would allow you to set the title from within a view based on your model.
Maybe you should also consider to introduce ViewModels, which allows you to combine view related data into a ViewModel and return it from a controller. This would allow you to batch queries and save round trips. And use a data repository which gets injected into your controller class (if you are not already doing). Sorry I'm guessing here because you do not show any controller code.
Or you should take advantage of client side code (JavaScript) and load parts of the UI via ajax? And have the UI update itself (could also be reactive).
Unfortunately I have the feeling that the thing your are facing today is not about the title but more about the hundreds of controllers with thousands of actions.
So researching how to organize controllers and building up an opinion based on your use cases would be my best bet based on what I can see and guess from your question.
Hope that helps...

Replacing content in Razor selectively

Is it possible to do something like this in Razor:
#using(Html.CMSContent("TermsOfService"))
{
<text>
<!-- Some default content goes here if the "TermsOfService"
item doesn't exist in my content table in the database -->
</text>
}
When the view is being processed, if the CMSContent helper doesn't find a content item named "TermsOfService" in some database table I've set up, it'll output the default content.
Otherwise, it'll replace the default content with what's in the database?
I'm trying to build a CMS/application hybrid, so that the Marketing department can override the text in my app if they want.
CLARIFY
The reason I'm doing this is to make the code super-easy to read and write. I'm going to end up with these little content blocks all over the place, so I'm hoping to minimize how much a developer has to type to get this functionality.
I'm also expecting that 99% of the time, the default text is what's going to show up. But I'd like to have the 1% cases handled where we need to quickly change some text without having to re-deploy the app. So, having the default text as close to its context as possible is important (ie, I don't want the developer to have to jump to a different file to see what the default text is - nor have to jump to the top of the page).
For example, when you do #using(Html.BeginForm()) it'll automatically emit the start and end <form> tags. I'm hoping for something clever like that.
What does Html.CMSContent("..") return? Could you store the return value of this helper into a variable and then check to see if the call returned a database value, if so display content otherwise display default text? Here is some quick code for what I describe not sure if it works 100% in your case since you did not specify what your helper returns
#{
var terms = Html.CMSContent("TermsOfService");
}
#if (terms != null && terms != "") {
#terms
} else {
#: Default Terms of Service
}
I think I figured out what I was looking for. It uses an if instead of using:
if(!Html.CMSContent("TermsOfService"))
{
<text>
Default content goes here
</text>
}
For the helper:
public static bool CMSContent(this HtmlHelper Html, string BlockName)
{
if (blockname in database)
{
var blockdata = (retrieve from database);
Html.ViewContext.HttpContext.Response.Write(blockdata);
return true;
}
else
{
return false;
}
}
Although I'm not sure that using Response.Write is the best way to do it.

ASP.NET MVC : does a partial know if it is bring requested from another page?

I have a partial view which can either be requested via an Action (Action2 in the image below), or rendered inside another page with "Html.Action()" (Action1 in the image below). From within the partial (or the partials controller) is there a way to determine which of these two methods were used to render the page?
You can use ControllerContext.IsChildAction or check DataTokens whether there is something with key "ParentActionViewContext" if you don't have access to ControllerContext.
You should be able to get it from
HttpContext.Current.Request.RawUrl
It should be noted that it is not particularly good practise to do this sort of thing in MVC. The partial should not be concerned about its "parent" ... but if you do need to do this, for whatever reason ...
You can use this code in the partial view's controller to determine if it was loaded directly or included in another page.
// this is the route which was originally used to route the request
string req_controller = Request.RequestContext.RouteData.Values["controller"].ToString();
string req_action = Request.RequestContext.RouteData.Values["action"].ToString();
// this is the route which was used to route to this action/view
string this_controller = RouteData.Values["controller"].ToString();
string this_action = RouteData.Values["action"].ToString();
if (req_controller == this_controller && req_action == this_action)
{
// this partial was loaded directly
}
else
{
// this partial was loaded indirectly
}
my reason for wanting to know this is that I wanted to be able to switch the Layout of a partial view based on whether or not it was rendered from a controller action or from within another page.
ie.
return PartialView("MyView.cshtml");
would result in a layout with the requisite menu bars and other site trimmings.
and
#Html.Partial("MyView")
would just embed the content without adding the rest of the page.
so, in my page's default Layout I have:
#if (this.IsPartial()) {
Layout = null;
} else {
Layout = "_SiteLayout";
}
#RenderBody()
here's what I found:
public static bool IsPartialResult(this WebPageBase #this)
{
return !#this.OutputStack.Any(writer => writer is HttpWriter);
}
it probably won't work in all situations. but it works for me. YMMV/HTH
No, there is no way and a partial shouldn't need to know it.

ASP.NET MVC Render View to a string for emailing

I want to use MVC views to create the body for an email and I have come across this (http://www.brightmix.com/blog/renderpartial-to-string-in-asp-net-mvc/) but it doesn't seem to work with strongly typed views (ViewContext is null). But I was after something the would render a full view including a masterpage.
I thought that if there was a way of just invoking a view with out redirecting and just writing to a different stream and sending the email within the controller that might do it, but I can't work out how to invoke a view.
Any suggestions would be great!
Thanks in advance.
Gifster
The question has been asked (and answered) already:
Render View as a String
This is the bit that I use:
protected string RenderViewToString<T>(string viewPath, T model, System.Web.Mvc.ControllerContext controllerContext) {
using (var writer = new StringWriter()) {
var view = new WebFormView(viewPath);
var vdd = new ViewDataDictionary<T>(model);
var viewCxt = new ViewContext(controllerContext, view, vdd, new TempDataDictionary(), writer);
viewCxt.View.Render(viewCxt, writer);
return writer.ToString();
}
}
Best place for this method is in a class library project that your mvc project has a reference to. Mainly because that way you can easily reuse it in all your apps. But also because it is neither Application logic (so doesn't belong in the controller) nor does it belong in the model. Some things are just utilities.
Note that to get this to work, the viewPath parameter HAS to be the PHYSICAL PATH to the file, complete with the .aspx extension. You can't use a route as WebFormView class requires a physical path in its constructor.
This will render the full view and take account of the master page.
HEALTH WARNING FOR HTML EMAILS:
HTML emails and the devices where you read them are even more difficult and restrictive to design for than websites and browsers. What works in one, will not work in another. So with html emails, you really have to Keep It Simple! Your lovely page with menus and relative images and whatever else, JUST WON'T WORK in all email devices. Just as an example, the images src attribute needs to be absolute and include the domain:
This won't work:
<img src="/Images/MyImage.gif" ... />
Bit this will:
<img src="http://www.mywebsite.com/Images/MyImage.gif" ... />
With those caveats, it works fine and I use it. Just don't try to send them the full gimmickry of your website, cos that won't work!
Even more important:
All CSS must be INLINE and just for basic styling: colours, borders, padding. But no floating and positioning. CSS layouts won't work consistently across devices!
You can use MvcView NuGet package to do this! Its simple, just install using install-package MvcMailer and you are done! Use full power of view engine to template, master page, view model and send html emails neatly!

asp.net mvc change master page & .css dynamically

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

Resources