Integrating into your site's MasterPage and Enabling ASP.NET Themes

Topics: ASP.NET 2.0, Themes
Nov 27, 2007 at 6:06 PM
Edited Nov 27, 2007 at 6:08 PM

I wanted to use this engine because of its use of ASP.NET masterpages for themes and user controls for content. This allows me to easily integrate it into my site which will contain this blog plus other applications, each with the same look and feel.... theoretically :) I thought this would be easy given the existence of the BlogEngine.VirtualPath appSetting, but it doesn't look like this setting is used consistently and there were a number of other issues I had to deal with in order to get it working. I decided to document them here for others attempting the same. Once a couple bugs are resolved (noted below), it shouldn't be as difficult, but in the meantime...

I've installed the app into the /blog directory but didn't set it up as an IIS Application because I want to create a custom theme that uses nested master pages, the base of which is my own master page for the site. For example, my site masterpage is at /templates/base.master and the blog theme is located at /blog/themes/inherited/site.master. But I can't reference base.master if /blog is its own application, so I'm stucky trying to figure this out.


Subdirectory Installation Issues

Unfortunately, to install into a subdirectory of your site that is not marked as an application, it is not as easy as simply changing the BlogEngine.VirtualPath setting in the web.config file. Here are all the changes that I've made so far to get this to work:

  1. Change the BlogEngine.VirtualPath appSetting to use "~/blog/".
  2. Integrate the supplied web.config file with my own web.config file
  3. Copied the contents of /blog/AppGlobalResources to /AppGlobalResources.
  4. Copied the Global.asax file to the root directory
  5. Copied the robots.txt file to the root directory
  6. Made changes to use SQL Server for posts, roles, and membership (so I didn't copy App_Data)

First, when I try to load the blog, I get the error

"The file '/themes/Standard/site.master' does not exist."

So it appears that this is one of the places that does not honor the BlogEngine.VirtualPath appSetting, otherwise it would look for it at "/blog/themes/Standard/site.master". I found this in BlogBasePage.cs and changed the following line in the PreInit event

MasterPageFile = Utils.RelativeWebRoot + "themes/" + _Theme + "/site.master";
//MasterPageFile = "~/themes/" + _Theme + "/site.master";

I've documented these issues here
http://www.codeplex.com/blogengine/WorkItem/View.aspx?WorkItemId=4567

This got me up and running, but it wasn't applying my styles so I knew something was up with the Stylesheets.

Integration with ASP.NET Themes

After the previous steps were taken, I dug further to determine why my styles weren't being applied. I found these references in the rendered page

<link href="/blog/themes/Standard/css.axd?name=~/App_Themes/Default/default.css" type="text/css" rel="stylesheet" />
<link rel="stylesheet" href="/blog/themes/Standard/css.axd?name=style.css" type="text/css" />

There are problems illustrated by both of these links. The first link is obvious, it has ~/App_Themes in the URL. If I try to navigate to the above URL, i get this
"Could not find a part of the path 'E:\web\epreneurexc\htdocs\blog\themes\Standard\~\App_Themes\Default\default.css'."

I'm using ASP.NET Themes on my site so it adds the reference to each page to the appropriate CSS files. The BlogEngine then rewrites every CSS url on a page to point to css.axd so that CSSHandler process the requests (to remove whitespace). The CSSHandler is not translating the tilda (~) appropriately so it just gets passed straight through. I corrected it by changing this line in the CompressCSS method of the BLogBaseClass.cs file

// c.Attributes"href" = Utils.RelativeWebRoot + "themes/" + BlogSettings.Instance.Theme + "/css.axd?name=" + c.Attributes"href";
c.Attributes"href" = Utils.RelativeWebRoot + "themes/" + BlogSettings.Instance.Theme + "/css.axd?name=" +
((VirtualPathUtility.IsAppRelative(c.Attributes"href")) ? VirtualPathUtility.ToAbsolute(c.Attributes"href") : c.Attributes"href");

The second CSS link isn't as obvious because it's technically not a bug, but maybe a design issue. The second link actually works because the CSSHandler (which processes css.axd) takes the "name" querystring parameter and applies the BlogEngine.virtualPath appSetting to read and return the CSS file. The problem is when we apply that same logic to the first CSS link. then we end up with:
/blog/themes/Standard/App_Themes/Default/default.css

which is obviously an error. So basically, as it is today, this isn't compatible with ASP.NET themes unless you turn off the CompressCSS feature (i think, i didn't actually test this).

So to get this to work with the compression, I changed the design of CSSHandler to only use BlogEngine.VirtualPath if the "name" does not begin with a '/', meaning it's an absolute URL. So in the ProcessRequest method of the CSSHandler.cs file, I changed this code

//string file = context.Server.MapPath(Utils.RelativeWebRoot + "themes/" + BlogSettings.Instance.Theme + "/" + context.Request.QueryString"name");
string file = string.Empty;
if (context.Request.QueryString"name".StartsWith("/"))
file = context.Server.MapPath(context.Request.QueryString"name");
else
file = context.Server.MapPath(Utils.RelativeWebRoot + "themes/" + BlogSettings.Instance.Theme + "/" + context.Request.QueryString"name");


I've documented these issues here
http://www.codeplex.com/blogengine/WorkItem/View.aspx?WorkItemId=4569

Nested Master Pages

Upon making the changes above, I began modifying the site.master in the standard template. First, I added the MasterPageFile attribute to the <% Master %> directive so that it was now a nested master page. My Site master page had a content section within the <head></head> tags, one for each of the left and right navigation columns, and one for the body of the page. I took the appropriate pieces from the blog master page (site.master) and put them into the regions in which I felt they belonged.

I reloaded the page and immediately noticed that the CSS references made in the nested masterpage (to style.css in this case) was not getting stripped and redirected to css.axd. This step is performed in the OnLoad event of the BlogBasePage.cs file. It turns out that CompressCss (also in BlogBasePage.cs) was just redirecting the immediate children in the Page.Header.Controls collection. In my nested masterpage, my CSS link tags were contained within an <asp:Content/> control which was inside the Page.Header.Controls collection. So I changed the CompressCss method into a recursive function so that any immediate children as well as THEIR children got redirected if it was a CSS link tag. This seemed to do the trick!

protected virtual void CompressCss()
{
if (Request.QueryString"theme" != null)
return;
CompressCss(Page.Header.Controls);
}
private void CompressCss(ControlCollection ctls)
{
foreach (Control control in ctls)
{
if (control.Controls.Count != 0) CompressCss(control.Controls);

HtmlControl c = control as HtmlControl;
if (c != null && c.Attributes"type" != null && c.Attributes"type".Equals("text/css", StringComparison.OrdinalIgnoreCase))
{
if (!c.Attributes"href".StartsWith("http://"))
// c.Attributes"href" = Utils.RelativeWebRoot + "themes/" + BlogSettings.Instance.Theme + "/css.axd?name=" + c.Attributes"href";
c.Attributes"href" = Utils.RelativeWebRoot + "themes/" + BlogSettings.Instance.Theme + "/css.axd?name=" +
((VirtualPathUtility.IsAppRelative(c.Attributes"href")) ? VirtualPathUtility.ToAbsolute(c.Attributes"href") : c.Attributes"href");
}
}
}

This is also documented here (same as above)
http://www.codeplex.com/blogengine/WorkItem/View.aspx?WorkItemId=4569


This appears to have done the trick so far. I now have it rendering with ASP.NET Themes and a nested masterpage, although I haven't dug deeply into the customization yet. I'm sure I'll hit other roadblocks as I progress, but didn't want to wait until I had a novel :) This may make a good wiki page eventually, but I wanted to get some feedback from others that have tried it and those developers that are more familiar with the system. I've only just begun and so I'm not completely sure of all the ramifications of the above changes.

Cheers, Drew
Jan 11, 2008 at 2:55 PM
Edited Jan 11, 2008 at 3:03 PM
Drew,

Thanks so much for documenting all your changes. This was a tremendous help to me. I was able to get everything I needed working. The one thing that I haven't gotten to work is getting the style sheet format using your CompressCSS method. Unlike you, I'm not using a Content section in my Parent master page Header.

I am just loading in the style.css from the Child master page during Page_Init:

protected void Page_Init(object sender, EventArgs e)
{
// Add: <link rel="stylesheet" href="style.css" type="text/css" /> to the header tag
HtmlGenericControl mySs = new HtmlGenericControl();
mySs.TagName = "link";
mySs.Attributes.Add("type", "text/css");
mySs.Attributes.Add("rel", "stylesheet");
mySs.Attributes.Add("href", "style.css");
Page.Header.Controls.Add(mySs);
}

This adds the style.css to the head tag but it's not in the correct format for BlogEngine's theme. It creates the following: <link type="text/css" rel="stylesheet" href="style.css">

But it should be something like:
<link rel="stylesheet" href="/BlogEngineTest1/blog/themes/Standard/css.axd?name=style.css" type="text/css" />

----------------------------------------------
Anyway, for the time being it's not a big deal for me to figure out how to correct this issue, because I'm only using one theme (I've deleted all the themes except for Standard), so I can just put the style.css into my App_Themes folder. But if anyone has a solution, to the problem I'd be interested to find out what you did.

Thanks again Drew for all you hard work figuring out the Subdirectory, MasterPages, and Themes integration with BlogEngine. Great job!

d.
Jan 11, 2008 at 3:13 PM
Here's a possible solution to my question above... although I'm sure there's a better solution. You would have to add this to each themes site.master.cs files, and make sure to change the theme name in the href tag. So for the Standard theme you would need to have href tag reference "Standard" (blog/themes/Standard/css.axd?name=style.css) See below:

protected void Page_Init(object sender, EventArgs e)
{
/*
// Add: <link rel="stylesheet" href="style.css" type="text/css" /> to the header tag
HtmlGenericControl mySs = new HtmlGenericControl();
mySs.TagName = "link";
mySs.Attributes.Add("type", "text/css");
mySs.Attributes.Add("rel", "stylesheet");
mySs.Attributes.Add("href", "style.css");
Page.Header.Controls.Add(mySs);
*/

HttpContext context = HttpContext.Current;
string baseUrl = context.Request.Url.Scheme + "://" + context.Request.Url.Authority + context.Request.ApplicationPath.TrimEnd('/') + '/';

// Add: <link rel="stylesheet" href="style.css" type="text/css" /> to the header tag
HtmlGenericControl mySs = new HtmlGenericControl();
mySs.TagName = "link";
mySs.Attributes.Add("type", "text/css");
mySs.Attributes.Add("rel", "stylesheet");
mySs.Attributes.Add("href", baseUrl + "blog/themes/Standard/css.axd?name=style.css");
Page.Header.Controls.Add(mySs);
}

--------------------------------------

d.
Apr 23, 2008 at 8:44 PM
One additional thing that I needed to do was to uncheck the "Trim stylesheets" checkbox on the Settings.aspx page. For some reason, the styles from my App_Themes folder are not used if Trim stylesheets is checked.
Jan 24, 2009 at 3:45 AM
When I installed it as a subfolder I also had to replace all the ~/ to ~/Blog/ or ~/YourBlogFolderName/ so all the controls and pictures referenced the correct path as well.
Mar 16, 2009 at 4:07 PM
Thanks for the info. I followed the same steps and everything wen't well except from one thing: After the refactoring, it's impossible to make a comment to a post. When saving the comment, the 'saving the comment...' message pops up, but nothing happens. Did anyone experience this problem? Thanks for any feedback!