ASP .NET MVC 2: Dynamic themes - asp.net

I would like to change dynamically the page theme in a MVC 2 Application.
I found several solutions, but I want to use this method: in the Global.asax, change the current page theme:
protected void Application_PreRequestHandlerExecute(object sender, System.EventArgs e)
{
// cast the current handler to a page object
Page p = HttpContext.Current.Handler as Page;
if (p != null)
{
string strTheme = "Theme1";
if (Convert.ToString(HttpContext.Current.Session["THEME"]) != string.Empty)
strTheme = Convert.ToString(HttpContext.Current.Session["THEME"]);
p.StyleSheetTheme = strTheme;
}
}
But this code always returns null in "p"...
I've also tried a similar code using the event PreRequestHandlerExecute in a HttpModule and the PreInit event of a page, but the code
HttpContext.Current.Handler as Page
always returns null.
Can anyone help me?
Thank you in advance.

I don't use baked in themes, but I do use jQuery UI themes. The way I handle it is in my master page I have logic that gets the current theme from a common viewmodel. The master page is strongly typed to this view model. The common viewmodel properties are updated from user preferences and other sources in a common base controller that all my controllers inherit. I do this in OnActionExecuted. I check if the result is a ViewResult, then cast the result from ViewData on the ActionExecutedContext.Result property to my common view model and set the property. The master page uses the value of the property to build the url for the CSS file.
Model
public abstract class CommonViewModel
{
public string Theme { get; set; }
// ...
}
Controller
public abstract class BaseController : Controller
{
public override void OnActionExecuted( ActionExecutedContext context )
{
if (context.Result is ViewResult)
{
var model = ((ViewResult)context.Result).ViewData.Model as CommonViewModel;
if (model != null)
{
var preferences = ...get from database for current user...
model.Theme = preferences.Theme;
}
}
}
}
Master note it uses a custom HtmlHelper to generate the stylesheet link, you could
do it by hand.
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<...CommonViewModel>" >
<%: Html.Stylesheet( "themes/" + Model.Theme + ".css" ) %>

The technique you are talking about works for standard asp.net, not asp.net MVC. The reason is that (in general) asp.net MVC does not use the web control model that standard asp.net does, and as such there is nothing to interpret the theme setting.
#tvanfosson has some great advice. Just remember that with MVC, you have much more control over things.. but that also means you have to do more work to get some of the features that standard asp.net provides for free. MVC makes many things easier, but this is not one of them.

Related

Can I use ViewBag in an .aspx page?

I have to move my UI page from a .cshtml file to an .aspx file. Now I'm having a couple of compiling errors.
First is that 'ViewBag' does not exist in the current context. Can I not use it in .aspx? If not, what is a good substitute?
Second, the .cshtml page had a model declaration:
#model myProject.Models.Navigation
I changed it so that it would work in the .aspx page as follows:
<%# Import Namespace="myProject.Models" %>
I'm still not sure that's a correct substitute, because I could not include the word "Navigation" without getting an error. And now, in the code where I used to have:
#foreach (myProject.Models.Navigationitem item in Model.navigationItems){...
I've replaced it with:
<% foreach (myProject.Models.Navigationitem item in Model.navigationItems){...
And I get this error:
The name 'Model' does not exist in the current context
Apparently, I'm the only guy who has ever gone from razor to aspx, because there's exactly zilch about it online. Appreciate any help.
WebForms don't usually use a ViewBag, which is just a way to make data available to your View in ASP.Net MVC. With WebForms, a nice way to make data available to your "View" (the aspx page containing the HTML) is to expose a property containing that data.
The MVC way might be to set ViewBag.MyValue = "Some Value"; in your Controller, and reference it in your view with <h1>#ViewBag.MyValue</h1>. To do the equivalent in WebForms you would first define a property in your codebehind:
protected string MyValue { get; set; }
Then, set the value somewhere, perhaps in your Page_Load:
protected void Page_Load (object sender, EventArgs e)
{
this.MyValue = "Some Value";
}
And write the value on the page using WebForms syntax:
<h1><%= MyValue %></h1>
For your specific case, you don't seem to actually be using ViewBag. That's ok, you can make objects available as properties also:
protected MyProject.Models.Navigation Model { get; set; }
protected void Page_Load (object sender, EventArgs e)
{
this.Model = SomeMethodThatReturnsModel();
}
With the property defined and the value set, the code you have above for your ASPX should work just fine.
Your page should have
<%# Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of myProject.Models.Navigation)" %>
at the top to specify the model type.
Instead of ViewBag, you can use ViewState, as shown in the example below.
private List<TrimPackage> Packages
{
get
{
return (List<TrimPackage>)ViewState["Packages"];
}
set
{
ViewState["Packages"] = value;
}
}

Get output cache to work in ASP.net with webusercontrol

I have a webusercontrol with a public int property SelectedCatID. I use this control on other pages and in other controls as follows:
<NewStore:LeftMenuLinks runat="server" SelectedCatID="<%#CatIDToSelect%>" />
How do I output cache this control based on SelectedCatID? Everything I've tried fails.
The closest I've gotten is getting it to cache, but it doesn't vary on SelectedCatID leaving the same menu item selected until the cache expires. Without caching, the control works as expected.
I figured out why the VaryByControls approach you used initially does not work. Sadly you edited it out of your question, so my research for this will just have to go into a blog post. Update: the blog post in question: http://tabeokatech.blogspot.be/2014/09/outputcache-on-user-controls.html .
The long and short of it though is that VaryByControls is kinda shorthand for VaryByParams, and does nothing for properties: it only looks at POST values. The fact that it ever worked for properties with a static value appears to be a bug - any string whatsoever in the VaryByControls would have made that part work. The accepted answer to this question is wrong: Vary by control properties using PartialCaching in ASP.NET .
There is no built-in way to vary by control property values.
That wouldn't make sense anyway, because user controls need to be created to have property values, and you want to avoid creating them, instead caching their rendered markup - cached user controls fields are null in code-behind if cached markup is served for them.
This works by injecting a PartialCachingControl into the page instead of the actual user control. This PartialCachingControl checks the cache, and only creates the control if no cached version exists.
As for making it work, I see two options:
If you only have 1 usercontrol per page, you could use the VaryByCustom approach. To make things easy you could write an interface that returns your property value for that page, and implement it on every page that hosts the user control, e.g.:
interface INumberProvider
{
int GetNumber();
}
// and the page:
public partial class _Default : Page, INumberProvider
{
public int GetNumber()
{
return this.SomeNumberPropertyOrWhatever;
}
...
In your Global.asax you cast the current handler to INumberProvider and get the number:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom == "INumberProvider")
{
var page = context.CurrentHandler as INumberProvider;
if (page != null)
{
return page.GetNumber().ToString();
}
}
return base.GetVaryByCustomString(context, custom);
}
And in your control you obviously add:
OutputCache Duration="180" VaryByCustom="INumberProvider" VaryByParam="None" Shared="true"
That's if you only have one user control per page, and should be pretty straightforward. If you need more than one user control per page you're out of luck:
Build your own wrapper around your user control by writing a custom WebControl. Add the properties you need, capture the output of the rendered user control, and insert it into HttpContext.Current.Cache with a key that includes the SelectedCatID. Basically write your own custom PartialCachingControl.
There's also option 3:
Decide caching is not that important after all
<%# OutputCache Duration="60" VaryByParam="SelectedCatID" %>
Now store youre <%#CatIDToSelect%> as an a parameter ex ?SelectedCatID=12
Now you're Page or UserControl depending on what you want to cache will output the cache depending on what the Request.Param["SelectedCatID"] is equal to.
You can also do something like this (although not the easiest way)
This goes on the page/usercontrol you want cached:
<%# OutputCache duration="120" varybyparam="None" varybycustom="SelectedCatID" %>
This goes into the Gloabal.asax file:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if(custom == "SelectedCatID")
{
return CatIDToSelect;
}
return String.Empty;
}
I'm late to the party here what with an accepted answer and a 500 point bounty awarded. Still wanted to give my few cents on how this could be achieved.
It can be made to work in the control itself. You can have the control store it's own output in the cache and use the cached version in the Render method if found. I have made a really simple UserControl to test with. The markup looks like this:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="TestUC.ascx.cs"
Inherits="Webforms_Test.UserControls.TestUC" %>
<div>
<asp:Label ID="curTime" runat="server"></asp:Label>
</div>
It just contains a label that is set to DateTime.Now when it is initialized. The code behind looks like this:
public partial class TestUC : System.Web.UI.UserControl
{
private string cachedOutput = null;
public bool RenderFromCache = true; // set to false in containing page if this control needs to be re-rendered
protected void Page_Load(object sender, EventArgs e)
{
cachedOutput = HttpContext.Current.Cache["key"] as string;
if (cachedOutput == null)
{
// not found in cache, do the heavy lifting here to setup the control
curTime.Text = "UC:" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss");
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
if (cachedOutput == null || !RenderFromCache)
{
RenderFromCache = false;
StringBuilder b = new StringBuilder();
HtmlTextWriter h = new HtmlTextWriter(new StringWriter(b));
this.RenderControl(h);
cachedOutput = b.ToString();
HttpContext.Current.Cache.Insert("key", cachedOutput, null, DateTime.UtcNow.AddSeconds(10), TimeSpan.Zero);
RenderFromCache = true;
}
}
protected override void Render(HtmlTextWriter writer)
{
if (!RenderFromCache)
base.Render(writer);
else
writer.Write(cachedOutput);
}
}
In this sample, the control itself checks if its output is found in the cache, and if so the Render method will just write the cached output. If it is not found in the cache, the PreRender method will run the Render method normally and capture the output and store it in the cache.
In your case you would of course need a bit more logic which would check the relevant property on the control and use that to check if a cached version exists.
Disclaimer: This is an extremely simple test control. I have not tried to figure out how to make all of this work with controls that contain event handlers etc. So take it for what it's worth...

Using CsQuery in MasterPage code-behind to modify HTML output?

What's the "skeleton" code for using CsQuery in the code-behind of a MasterPage in order to modify the HTML output? I need to be able to modify everything in the <body> of the HTML?
I'm hoping to use CsQuery to "touch-up" the HTML output of a Dynamic Data website without rewriting / messing with the default code.
Just looking for sample code specific to MasterPage code-behind? Thanks.
There is an example in the CsQuery project that shows how to do this (which I just made sure was working right!) in the CsQuery.WebFormsApp project.
The core of the usage looks like this. You must override the Render method in a class that inherits Page, and use this instead of Page as the base class for the codebehind in an aspx page:
public class CsQueryPage: System.Web.UI.Page
{
public CQ Doc { get; protected set; }
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
// most of the work is done for you with the
// `CsQuery.WebForms.CreateFromRender` method
var csqContext = WebForms.CreateFromRender(this, base.Render, writer);
// if you are using update panels, this lets you also manipulate that
// HTML, otherwise you don't need the IsAsync part
if (csqContext.IsAsync)
{
foreach (var item in csqContext.AsyncPostbackData) {
Cq_RenderUpdatePanel(item.Dom,item.ID);
}
}
else
{
Doc = csqContext.Dom;
Cq_Render();
}
// writes the altered content to the base HtmlTextWriter
csqContext.Render();
}
protected virtual void Cq_Render()
{ }
protected virtual void Cq_RenderUpdatePanel(CQ doc, string updatePanelId)
{ }
}
The two virtual methods are where you can alter the dom, which is populated in the Doc property of the CsQueryPage object - the intent of leaving them unimplemented here is that each aspx page that inherits CsQueryPage can optionally override them and make changes to the DOM.
To see how this works in practice just pull down the CsQuery code from github and run the example.
The same technique can be used for a UserControl which is also shown in the example. I don't actually show how to do it with MasterPage but it's very much the same-- MasterPage derives from UserControl, you just override it's Render method same as the other situations.

Using ASP.NET Server Controls in MVC?

On my current project I need to add a functionality that allows the user to view a thumbnail of their uploaded PDF. I've found a handy component that achieves this (the basic version is free, but it's enough for my current needs). Anyways, the control is pretty outdated (2010), therefore there doesn't seem to be MVC support. On the demos they depict usage of the control as such:
The View's Markup:
<form method="post" runat="server" enctype="multipart/form-data">
<asp:Panel ID="thumbnailsPanel" runat="server" />
</form>
The thumbnail control is instantiated via code, the byte array which represents the thumbnail is passed to the control and the control is added to thumbnailsPanel
<script runat="server">
protected void DisplayThumbs_Click( object sender, System.EventArgs e )
{
Thumbnail thumbnail = new Thumbnail();
thumbnail.SessionKey = sessionID;
thumbnail.Index = i;
thumbnailsPanel.Controls.Add( thumbnail );
}
</script>
Given that I can't declare a Thumbnail control in my razor view, how would I used this control in MVC? I've spent a few hours trying to make this control MVC friendly to no avail, the best I've come up with is to include a .ASPX view (not.cshtml) in my project and render the Thumbnail control on that view. Obviously this is not desirable.
So how would you go about using a ASPX server controls in MVC? Is the idea a bad one altogether and should not be practised?
I worked around it in a project of mine by reimplementing the control as a HtmlHelper. Assuming the control isn't too complicated then it should work for you too. Do this:
Dump the Control's source using Reflector
Massage the source so it actually compiles (as source from Reflector doesn't usually compile straight away)
Identify what state the control has. Convert the state from member properties into members of its own new ViewModel class.
Find the Render method and convert it to a HtmlHelper that uses ViewContext.Writer
For example:
public class FooControl : Control {
public String Message { get; set; }
public override void Render(HtmlTextWriter wtr) {
wtr.WriteLine("<p>");
wtr.WriteLine( message );
wtr.WriteLine("</p>");
}
}
Becomes this:
public class FooViewModel {
public String Message { get; set; }
}
// This method should exist in a static Extensions class for HtmlHelper
public static void Foo(this HtmlHelper html, FooViewModel model) {
HtmlTextWriter wtr = html.ViewContext.Writer;
wtr.WriteLine("<p>");
wtr.WriteLine( model.Message );
wtr.WriteLine("</p>");
}

Accessing a Master Page using custom base class causes Page not to render

I have a really weird problem using ASP.NET/C# 4.0 that I haven't been able to find an answer to. I have a custom base master page for my child master pages, and a custom base page class for my pages. My problem is that if I access the Master member from any of my pages, the page content itself doesn't get rendered, only the master pages.
So, to recap in a different way:
"Master Page" inherits System.Web.UI.MasterPage
"Nested Master" using "Master Page" inherits System.Web.UI.MasterPage
"WebForm" using "Nested Master", WebForm is inheriting CustomPageBase which inherits System.Web.UI.Page
When CustomBaseMaster accesses this.Master it will not render the webform's content, only the "Nested Master" and "Master Page" content. I have tried it with only reading a member, writing a member, and both reading and writing, and not even accessing a member, just calling Debug.WriteLine(this.Master)
If it helps any, I am using a Page Decorator on "WebForm" that needs to update the controls on the "Master Page"
Unfortunately, my google searching queries show up with results on how to access public members of the Master Page. But nothing I could find would help explain why, when accessing the master page, it results in the page's content not displaying.
Has anybody seen anything like that and can offer any suggestions? Google hasn't been much help, been searching for a couple of hours now.
Ok, turns out when using the Page decorator at the top of the class it assigns the properties of the page class before PreInit completes. I used ILSpy to look at what happens when you access the Master property of the page, if it is accessed before the Master page has been created (internally null) and before all of the PreInit methods are called, it will create a new master page and uses that content. Kind of annoying. Anyways, my solution was to not make the properties bubble up the values to the parent master pages on getting and setting them, but overloaded the OnLoad event in my base page and base master classes, and set the properties on the parents there.
So, instead of doing this:
namespace MyWebsite
{
public abstract class MyPageBase : Page
{
public int PropertyName
{
get
{
return ((MyMaster)this.Master).RatePageId;
}
set
{
((MyMaster)this.Master).RatePageId;
}
}
}
}
I did this:
namespace MyWebsite
{
public abstract class MyPageBase : Page
{
public int? PropertyName
{
get;
set;
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (PropertyName != null)
{
((MyMaster)this.Master).PropertyName = PropertyName.Value;
}
}
}
}

Resources