BlogEngine.NET Widget: About Me

March 10, 2009 at 12:09 PMBen

Bloggers often have a few words they like to say about themselves.  With BlogEngine.NET, there are a few ways to achieve this.  In the default BE.NET installation, a TextBox widget titled "About the author" is already in the blog's sidebar.  The author can just click 'Edit' and quickly describe themselves using the WYSIWYG editor.  BE.NET users will find the TextBox widget is a very powerful widget because it's generic enough to display virtually anything.

Another option for About Me is to create a Page in BE.NET, and add a link on your blog to the About Me page.  The main difference with this 'Page' approach is the About Me content is only seen on the About Me page, rather than the widget approach where the About Me content is shown on every blog page in the sidebar.  With the Page approach, you also get a lot more room horizontally which is nice if you have some pictures or other space consuming content.  I'm currently using the Page approach for the About Me page on this blog.

A third approach which is very similar to using the TextBox widget described above is to use an "About Me" widget.  BE.NET allows each editor/administrator to create their own profile on the Profiles tab in the control panel.  On the Profile page, each editor/administrator can enter details such as their Name, Phone Number, Address, a Photo image, and last but not least, About Me!  The About Me box is a WYSIWYG editor.  Currently, profile information entered is hardly used anywhere within BE.NET.  The information is available, however, through the AuthorProfile class in BlogEngine.

The AboutMe widget displays the About Me text entered for a user on the Profiles tab in the control panel.

If multiple editors/administrators have been created on the Users tab in the control panel, a separate profile can be created for each one of these users on the Profiles tab.  Because multiple profiles may exist, the AboutMe widget needs to know which profile's About Me content it should display.  The edit control in the AboutMe widget allows the user to choose the profile to display the About Me content for.  Because the settings for each widget are stored independently of one another, multiple AboutMe widgets may be added to the blog -- one AboutMe widget for each profile.

The AboutMe widget consists of the 2 required files you find for widgets -- widget.ascx, widget.ascx.cs.  The edit control is made up of edit.ascx and edit.ascx.cs.  A download link for the entire widget is available at the bottom of this post if you are interesting in trying it out in your own BlogEngine.NET installation.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="widget.ascx.cs"
 Inherits="widgets_AboutMe_widget" %>

<asp:PlaceHolder runat="Server" ID="phAboutMe" />
#region Using

using System;
using System.Web;
using System.Web.UI;
using System.Collections.Specialized;
using BlogEngine.Core;

#endregion

public partial class widgets_AboutMe_widget : WidgetBase
{
    public override void LoadWidget()
    {
        string CacheKey = "widget_aboutme_" + this.WidgetID.ToString();
        string ProfileUserName = (string)HttpRuntime.Cache[CacheKey];

        if (ProfileUserName == null)
        {
            StringDictionary settings = GetSettings();

            if (settings.ContainsKey("UserName"))
                ProfileUserName = settings["UserName"];

            if (ProfileUserName == null)
                ProfileUserName = string.Empty;

            HttpRuntime.Cache[CacheKey] = ProfileUserName;
        }

        if (string.IsNullOrEmpty(ProfileUserName))
        {
            // Find the first profile with About Me content.
            foreach (AuthorProfile profile in AuthorProfile.Profiles)
            {
                if (!string.IsNullOrEmpty(profile.AboutMe))
                { 
                    ProfileUserName = profile.UserName;
                    HttpRuntime.Cache[CacheKey] = ProfileUserName;
                    break;
                }
            }
        }

        if (!string.IsNullOrEmpty(ProfileUserName))
        {
            AuthorProfile profile = AuthorProfile.GetProfile(ProfileUserName);
            if (profile != null && !string.IsNullOrEmpty(profile.AboutMe))
            {
                phAboutMe.Controls.Add(new LiteralControl(profile.AboutMe));
            }
        }        
    }

    public override string Name
    {
        get { return "AboutMe"; }
    }

    public override bool IsEditable
    {
        get { return true; }
    }
}
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="edit.ascx.cs"
Inherits="widgets_AboutMe_edit" %>

<div>

    <h3>Select the Profile to show About Me for</h3>
    
    <div id="noProfilesAvailable" runat="server">
        No profiles have yet been created.
    </div>
    
    <asp:RadioButtonList ID="rblProfileToDisplay"
     runat="server"
     DataTextField="UserName"
     DataValueField="UserName">
    </asp:RadioButtonList>
    
    <br />
    
    <div>
        Note: Make sure you have entered About Me content for
        the selected user above.  This content should be
        entered on the Profiles tab in the Control Panel.
    </div>

</div>
#region Using

using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using BlogEngine.Core;

#endregion

public partial class widgets_AboutMe_edit : WidgetEditBase
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            string SelectedUserName = null;

            StringDictionary settings = GetSettings();
            if (settings.ContainsKey("UserName"))
                SelectedUserName = settings["UserName"];

            if (!string.IsNullOrEmpty(SelectedUserName))
            {
                if (AuthorProfile.GetProfile(SelectedUserName) != null)
                    rblProfileToDisplay.SelectedValue = SelectedUserName;
            }

            rblProfileToDisplay.DataSource = AuthorProfile.Profiles;
            rblProfileToDisplay.DataBind();

            noProfilesAvailable.Visible = rblProfileToDisplay.Items.Count == 0;
        }
    }

    public override void Save()
    {
        StringDictionary settings = GetSettings();
        settings["UserName"] = rblProfileToDisplay.SelectedValue;
        SaveSettings(settings);
        HttpRuntime.Cache.Remove("widget_aboutme_" + this.WidgetID.ToString());
    }
}


AboutMe Widget Download (2.4 kb)

Posted in: Development

Tags: ,

Null Url Referrer going from HTTPS to HTTP

February 25, 2009 at 5:10 PMBen

Thought I would pass on a small issue I ran into recently when redirecting a user from an HTTPS page to an HTTP page.  When the person reached the HTTP page, Request.UrlReferrer was null.  There are browsers, add-ons, proxies, security suites and other entities that will strip the url referrer sent to a web server, but that was not the case in this instance as this was happening to me when testing a new site and didn't happen when I was redirected to the same page from an HTTP page.

It turned out this is a pretty standard security feature implemented by browsers to omit the referrer when a user is redirected from an HTTPS page to an HTTP page, or when a user clicks on a hyperlink taking them from an HTTPS page to an HTTP page.

This behavior does make sense considering sensitive information may be stored in query string parameters of the HTTPS page url.  I found this MS KB article explaining this behavior.  In the article, MS suggests some sites may even store credit card data in a url.  Credit card numbers in a url ... really??  I was thinking more along the lines of private session ids in the url.  I don't think I'd feel too comfortable shopping at a site if I saw my credit card number in the address bar ;-)

Posted in: Development

Tags: , ,

VS Intellisense Mental Lapse

February 8, 2009 at 11:03 AMBen

Yesterday I was working on a C# web site project in VS 2008.  After making some modifications to a class in the App_Code folder, when I later went back to the codefile for an ASPX webpage, intellisense no longer recognized the class or methods of the class I was earlier changing.  I wasn't getting the normal coloring for the class name and no intellisense listing of the class's methods.  Right-clicking on the class or its methods and trying to 'Go to Definition' resulted in a message stating it couldn't find the definition (or something to that effect).

I could build and run the site with no errors.  I tried closing and re-opening the solution, tried closing and re-opening VS, tried deleting all the files in the Temporary ASP.NET Files directory, tried restarting IIS and tried rebooting the PC.  Nothing worked.  After rebooting the PC, intellisense even stopped recognizing another App_Code class.  Life without intellisense sucks!

What finally did work was to right click on these classes in the solution explorer, select the 'Exclude from Project' option which excludes the files from the project by giving the files .exclude extensions, and then I right-clicked on the files again to Include them in the project.  That cured intellisense.

TimeZoneInfo - A Small .NET 3.5 Gem

December 7, 2008 at 4:10 PMBen

One new handy class introduced in the .NET 3.5 framework is the TimeZoneInfo class.  This class allows you to get date and time information for any time zone in the world.  Prior to .NET 3.5, the framework exposed methods to get date and time information only for the time zone the server was set at.

I've used this class to find the date/time of a place other than where the server is located.  If you're lucky, the server your website is running on will be in the same timezone you want to save and display dates and times for.  Even if you're in a different timezone, let's say in New York and your server is in California, well you can just add 3 hours to the server time to get New York time.

It isn't always this easy living in Arizona where daylight saving time (DST) is not observed.  In the summer, Arizona is in the same timezone as California (PST) three hours behind the east coast and in the winter, Arizona is in MST two hours behind the east coast.  Your server may be in Arizona but you want a west coast time for your application, or your server may be in California and you want Arizona time for your application.

If the server is in Arizona, and you want to know what time it is in California, you can use the TimeZoneInfo class to find this out.

TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
DateTime PstDateTime = TimeZoneInfo.ConvertTime(DateTime.Now, tzi);


PstDateTime now contains the current date/time in California.  This code will work year round as daylight saving time and any other adjustment rules are taken into account by the ConvertTime() static function.  So, in the winter, PstDateTime will be one hour earlier than Arizona time and in the summer, PstDateTime will be the same as Arizona time.  This functionality makes life very convenient since you don't need to worry about trying to calculate when DST starts and ends each year.

You may even want to just know if daylight saving time is in effect for any given date/time value.

bool IsDaylightSavingTime = tzi.IsDaylightSavingTime(PstDateTime);


This tells you whether it is daylight saving time for PstDateTime.  Let's check the status of DST for two different dates:

// false
bool IsDaylightSavingTimeAprilFirst2000 = tzi.IsDaylightSavingTime(DateTime.Parse("4/1/2000"));
// true
bool IsDaylightSavingTimeAprilFirst2008 = tzi.IsDaylightSavingTime(DateTime.Parse("4/1/2008"));


There's actually a handful of other static and non-static members of the TimeZoneInfo class available that can come in handy depending on your needs.  Members such as GetUtcOffset(), ConvertTimeToUtc(), DisplayName, etc. provide a wide range of built-in capability.  The list of available time zones you can pass into the FindSystemTimeZoneById method can be found via the GetSystemTimeZones method.

Posted in: Development

Tags:

Navigate to default document of current directory

November 4, 2008 at 8:15 PMBen

I was working on a web page where I wanted to add a link to take the visitor to the default document of the folder this particular page is in.  This page (e.g. page1.aspx) was in a folder (e.g. folder1).  I actually knew the default document in this folder was index.aspx, and could have just set the link's HREF to "index.aspx", but wanted to make this a little more generic so it didn't matter what the default document was.  Just to be clear, the location of the page I was putting this link on looked like:

http://www.example.com/folder1/page.aspx

Initially, I set the link's HREF to:

"../folder1/"

This worked well.  It would navigate the visitor to:

http://www.example.com/folder1/

But then I needed to copy page.aspx to another folder (e.g. folder2).  So the page was going to exist in both folder1 and folder2.  Once I copied page.aspx to folder2, I could have manually edited the link to:

"../folder2/"

This didn't have a very generic feel to it and I would always need to remember to change the HREF if I needed to copy the page again to other folders.  So I decided to make this link a server side HyperLink control and create some fairly simple .NET code that would determine what the current folder the page was in by parsing the URL and setting the link's HREF so it would take the visitor to the default document of the directory the page was in.  Once the .NET code determined the folder the page was in, it ended up setting the HyperLink's NavigateUrl to something similar to:

HyperLink1.NavigateUrl = "~/folder1/";
       - or even -
HyperLink1.NavigateUrl = "~/folder2/";

The worked well, but I was surprised when I looked at the HTML source to see what the HREF resolved to.  It essentially looked like:

<a id="HyperLink1" href="./">Go to the root of this folder</a>

As you can see, a HREF of "./" is the default document or root of the current folder!  Instead of running this .NET code I created to parse the URL and determine the current folder name, all that's needed is just statically setting the HREF to "./".  This gave me flashbacks to the old DOS days where running a simple DIR command (in a directory other than root) always results in the first two lines being:

<DIR> .
<DIR> ..

The "." DIR refers to the current folder and the ".." DIR refers to the parent folder.  You can still see this today by opening up a command prompt and running DIR.

It's also worth mentioning that you can use "./" when redirecting a visitor in server side code:

Response.Redirect("./");

I'm sure I'll now start finding lots of places to sprinkle "./" HREFs in!

Posted in: Development

Tags: ,

Commenting Code

October 30, 2008 at 8:15 PMBen

Do you add enough comments to your code?  Too many, perhaps?  The last few months, I've realized how helpful comments in source code can be.  Years ago, I wasn't a big comment guy.  I think I didn't want to spend the time commenting and felt like if I (or someone else) looked at the code for a couple of minutes, they would understand what's going on.  This is basically true, but comments remove or reduce the need to spend those few minutes studying the code to see what's going on.  When maintaining code, you still need to understand the code before making any changes, but spending 30 seconds, a minute or even a few minutes to add comments when you're writing code and everything is fresh in your mind is really proving to be beneficial when going back over code in the future.

There's also times when there are no comments and after spending a few minutes reacquainting myself with some code, I'm still not 100% sure if I fully grasp why something was done a certain way.  Comments are priceless for these cases too.

The other thing I've started doing as well is including a date with my comments (including the year!).  It all may seem unnecessary and a waste of time when writing code, but more times than not, pays off in the future.

Posted in: Development

Tags:

Disable ViewState - reap performance gains

October 19, 2008 at 11:17 AMBen

Turning off viewstate for an entire page or just for certain controls can potentially reduce bandwidth greatly.  This is not news for most developers, but at least for me, I often forget to take this into account when developing ASP.NET pages.

Just the other day, I found two server side controls that had enough content in them to make viewstate much larger than it should have been.  After disabling viewstate on those 2 controls, the viewstate hidden field sent to the client went down from about 6,500 bytes to 500 bytes!  That's 6 KB of unneeded data that was being sent down to each client.  And because viewstate is sent to the client in a hidden input field, if there's a postback, all that data gets sent back up to the server.  Most people don't have a very fast upload speed, so the post hurts performance more than sending the data to the client.

Especially for pages that don't do any postbacks, there's no reason I can think of to even have viewstate turned on at all.  For these pages, disable viewstate at the page level.  It's scary to imagine the number of ASP.NET pages out there that output static reports in large tables (gridview, datagrid, listview etc) with viewstate unnecessarily enabled.  Bandwidth savings on such pages could probably be as large as the tens or even hundreds of kilobytes.

There are reasons to leave viewstate enabled for server controls on pages that do postbacks, however.  During a postback, if the resources required are high for obtaining the data needed to put back into a disabled viewstate control, it may be more advantageous to leave viewstate enabled.  For instance, if you need to make a database call or consume a web service across a network to obtain the data, leaving viewstate turned on may be better.  Same with most standard form controls (e.g. input, select), there's typically not going to be enough of a performance gain to justify turning off viewstate and losing some of the perks you get with viewstate enabled when a page is going back and forth between the client and server one or more times.  The big performance gains are going to be with server controls that output large blocks of HTML not editable by the user.

Posted in: Development

Tags: ,

Simplifying prevention of undesired Css/Js file caching

September 26, 2008 at 8:12 PMBen

It's an unfortunate reality that after updating an external CSS or JavaScript file referenced from a web page, not all browsers that have been to your site before will detect an updated file it has previously cached.  When this happens, browsers are running outdated JS and applying old styles to your page elements.

One of the common workarounds for this situation that I've found quite effective is to append some value to the query string of the external file.  So instead of the typical link (to a CSS file) in the Head section of your document,

<link href="styles.css" type="text/css" rel="stylesheet" /> 


You instead use a link with an arbitrary value following the actual file name:

<link href="styles.css?v=1" type="text/css" rel="stylesheet" />


Browsers cache the CSS file with an Id of "styles.css?v=1".  If you update your CSS file, you can change the "v=1" to "v=2" in your HTML page and the browser treats styles.css?v=2 as a different file than styles.css?v=1.  Since styles.css?v=2 isn't already cached, the browser will fetch the latest copy of styles.css from your web server.  Constantly modifying (and trying to remember to modify) the query string value when I make changes to CSS and JS files has always been a manual task for me.  Recently, however, I created a mechanism to automate this process.

The automated process appends the timestamp of the external file (CSS of JS) to the query string following the file's name that is sent to the browser.  The file timestamp is stored in the .NET cache with a cache dependency to the actual file on the web server.  Whenever I update the CSS or JS file, the timestamp is automatically removed from the cache and the next time a visitor arrives at the page and the timestamp is needed, the timestamp is retrieved and stored in cache.  The purpose of the cache is to obviously reduce the amount of file I/O and overall time required in getting the page to the browser.  I've fallen in love with this process as it's made my life easier!

The download link at the bottom of this post contains the code for a static GetFileWithVersion() function.  The same function can be used for any external file that you want to prevent browsers from mistakenly hanging onto a copy of after you may have updated the file.  At present, GetFileWithVersion returns a string value containing the filename and appended query string value.  It's the caller's responsibility to add the necessary link or script tag to the head section of the page.

Example usage of the GetFileWithVersion() function with a CSS file:

string CssFileWithVersion = utils.GetFileWithVersion("MainCss", "~/styles.css");
 
System.Web.UI.HtmlControls.HtmlLink cssLink = new System.Web.UI.HtmlControls.HtmlLink();
cssLink.Href = CssFileWithVersion;
cssLink.Attributes.Add("type", "text/css");
cssLink.Attributes.Add("rel", "stylesheet");
this.Header.Controls.Add(cssLink);


Example usage of the GetFileWithVersion() function with a JavaScript file:

string JsKey = "MainJs";
if (!ClientScript.IsClientScriptIncludeRegistered(this.GetType(), JsKey))
{
    string JsFileWithVersion = utils.GetFileWithVersion(JsKey, "~/tools.js");
    ClientScript.RegisterClientScriptInclude(this.GetType(), JsKey, ResolveClientUrl(JsFileWithVersion));
}


I've been using the GetFileWithVersion() function from master pages and standalone pages for a few weeks now.  Putting this type of mechanism in place is definitely a time saver and since implementing this, I personally haven't seen or heard of any issues with old CSS or JavaScript showing up.

Code Download (1.74 kb)

Posted in: Development

Tags: ,

JavaScript Events Not Firing

September 21, 2008 at 3:45 PMBen

It's important to realize a JavaScript event is not guaranteed to fire.  The events I have in mind are events such as onmouseout and onmouseover.  I'm sure there are many other events that also may not fire depending on the circumstances at the time the events would normally fire.  If the element is right up against the edge of the browser viewport when the mouse moves away from the element and out of the browser, or if the browser or computer is busy processing something else at the time the event would be expected to occur, it may just not happen.

While working with jQuery recently I created a rollover image with the hover() function to show a different image when the mouse rolls over the image.  The particular piece of code was:

oImgBtn.hover(
  function(){ this.src = this.src.replace('.gif','_over.gif'); },
  function(){ this.src = this.src.replace('_over',''); }
);


This code works good assuming your rollover image has the same filename as the original image with "_over" at the end of the filename.  The problem occurred one time while testing the page.  I noticed the image replacement was not occurring and I was getting some ugly flickering when hovering over the image.  Fortunately, I had Firebug already turned on and checking the Net tab showed a red item indicating a 404 error.  The file that the browser was getting a 404 on had this filename:

myImage_over_over.gif

Apparently, one of the previous times I had rolled over the image, for whatever reason the onmouseout event didn't fire leaving the images source as myImage_over.gif (the rollover image).  The next time I rolled over the image, the onmouseover event fired changing the image's source from myImage_over.gif to myImage_over_over.gif.  Not good!  A very easy and effective solution in this case was to make sure a preexisting "_over" in the image source was removed prior to adding it back in the onmouseover event handler.

oImgBtn.hover(
  function(){ this.src = this.src.replace('_over', '').replace('.gif','_over.gif'); },
  function(){ this.src = this.src.replace('_over',''); }
);


This is a much more robust method of making sure things don't fall apart in the rare event a JavaScript event doesn't fire.  Although rollover images are not usually critical to a page's functionality, it's still a bad experience to the end user if they see things flickering that shouldn't be flickering.  This also makes you realize that for any client side code, it's important to not assume events will always fire and be sure your code is defensively designed so it can recover from unexpected situations.

Posted in: Development

Tags: ,

It's Alive!

September 20, 2008 at 9:28 PMBen

This is the first blog post on Ben's Quarters.  If you search for a post prior to this one, you simply won't find one!

I'm excited to finally put a blog on the web.  I can't tell you how many times I've had some code sample, problem, idea, rant or thought to share, but didn't have anywhere to post!  Well, those days are now over.  Although I don't anticipate very frequent posting, I do hope to post on a consistent basis.

If you don't know me, don't fret -- I put together an "About Me" page (see link on left side).

The focus of posts on Ben's Quarters will be programming and software development related.  This is a large field.  My software development career has revolved around Microsoft technologies -- .NET, Visual Studio, SQL Server, etc.

I finally got a chance to work with jQuery a couple of weeks back.  I'm honestly not crazy about these types of libraries and have actually never used a JS library prior to jQuery.  There was definitely a learning curve and I wouldn't say that I can't live without it.  It seems like most JavaScript I need is not a big deal to just create straight without any libraries.  I see the biggest benefit of using a library such as jQuery is the cross browser support it gives you.  But again, the JavaScript I usually create without jQuery for validating input and manipulating some elements on the page already seems to run fine in the browers I test against.  I still don't mind incorporating jQuery into some web projects I work on, but don't feel like I need to.

I actually have a short jQuery related post I'll get up shortly.  It'll be post #2 Wink

Posted in: Development | General

Tags: ,