Saturday, February 2, 2008

ViewState-backed Properties - Part Two

Setting a Default Value on the "Get"

This is part two of a multi-part series on using ViewState to back your Page Properties.  For those of you who read ViewState-backed Properties - Part One, you'll notice the code is pretty similar.  The main difference is that instead of just returning a default value if one hasn't been set, we'll be setting one inside the "Get" portion of our Property.  Once again we'll be using one of my favorite operators, the Null Coalescing Operator.

Consider the following code...

private string MyDefaultValue 
{
    get { return (string)(ViewState["MyDefaultValue "] ?? (ViewState["MyDefaultValue"] = "My default value")); }
    set { ViewState["MyDefaultValue "] = value; }
}

Assuming nothing has been set in ViewState, the right side of the "??" will be evaluated.  By wrapping the right side in parenthesis we're telling .NET to evaluate this expression first and then return what's been set.  The effect is that not only is our default value returned but it is also persisted in ViewState.

Working Example

Below I've included a working page where we access ViewState directly and through our ViewState-backed Property.  More to come in this series....  stay tuned.

<%@ Page Language="C#" AutoEventWireup="false" CodeFile="DefaultAssignment.aspx.cs" Inherits="DefaultAssignment" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Default Value</title>
    </head>
    <body>
        <form id="frmMain" runat="server">
            <div>
                <p>
                    When this button is clicked, ViewState will be accessed directly (ViewState["MyDefaultValue"]) and nothing will be displayed because it has not been set.
                </p>
                <asp:Label ID="lblDefaultValue" runat="server" Text="The value is:  " />
                <br />
                <asp:Button ID="btnDefaultValue" runat="server" Text="Load Default" OnClick="btnDefaultValue_Click" />
            </div>
            <div>
                <p>
                    When this button is clicked, instead of accessing ViewState directly we will access the ViewState-backed property causing it to be set and displayed.
                </p>
                <asp:Label ID="lblSetValue" runat="server" Text="The value is:  " />
                <br />
                <asp:Button ID="btnSetValue" runat="server" Text="Set Value" OnClick="btnSetValue_Click" />
            </div>
            <p>
                Clicking the first button again will now display the new set value as well even though we never used the "set" of the property.
            </p>
        </form>
    </body>
</html>
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
 
public partial class DefaultAssignment : System.Web.UI.Page
{
    public DefaultAssignment() { Load += new EventHandler(Page_Load); }
 
    protected void Page_Load(object sender, EventArgs e) { }
 
    protected void btnDefaultValue_Click(object sender, EventArgs e)
    {
        lblDefaultValue.Text = "The value is:  " + ViewState["MyDefaultValue"];
    }
 
    protected void btnSetValue_Click(object sender, EventArgs e)
    {
        lblSetValue.Text = "The value is:  " + MyDefaultValue ;
    }
 
    private string MyDefaultValue 
    {
        get { return (string)(ViewState["MyDefaultValue "] ?? (ViewState["MyDefaultValue"] = "My default value")); }
        set { ViewState["MyDefaultValue "] = value; }
    }
}

Tuesday, January 15, 2008

ViewState-backed Properties - Part One

Why use a ViewState-backed Property?

This is the first post in a multi-part series on using ViewState to back your Page Properties.  If you've done any kind of development with ASP.NET then you've probably had to use a ViewState-backed Property at some point.  Accessing and setting a Property is much easier than working with ViewState directly throughout your code, especially if the name of your Key may change.  There is one thing to consider, though.

Default Value for a ViewState-backed Property

It's generally good practice to check your ViewState-backed Property for null before trying to access it.  One thing worth noting is that you may want to display a default value if one hasn't been set.  The following code may look familiar:

   1: private string Standard
   2: {
   3:     get
   4:     {
   5:         if (ViewState["Standard"] != null)
   6:         {
   7:             return (string)ViewState["Standard"];
   8:         }
   9:         else
  10:         {
  11:             return "My default value";
  12:         }
  13:     }
  14:     set { ViewState["Standard"] = value; }
  15: }

Null Coalescing Operator

Lately there have been quite a few blog posts on using the null coalescing operator (??).  I won't go into detail here about its use except to say that it returns the first operand if it is not null and the second operand if the first is null.  When applied to ViewState-backed Properties the code can be shortened and, in my opinion, made more readable.  Consider the following code which essentially works the same as the previous example:

   1: private string MyDefaultValue 
   2: {
   3:     get { return (string)(ViewState["MyDefaultValue "] ?? "My default value"); }
   4:     set { ViewState["MyDefaultValue "] = value; }
   5: }

If ViewState["MyDefaultValue"] is null then the string "My default value" is returned.  If it is not null then the value it holds is cast to a string and returned.  In the past, developers have argued that the null coalescing operator makes code more difficult to read.  With all the attention it's gotten lately, I no longer think that's a valid argument.  In fact, I'll be using it throughout this series on ViewState-backed Properties.

Working Example

Below I've included a working page demonstrating what will be returned before and after the ViewState-backed Property is set.  Enjoy!

   1: <%@ Page Language="C#" AutoEventWireup="false" CodeFile="DefaultValue.aspx.cs" Inherits="DefaultValue" %>
   2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
   3:  
   4: <html xmlns="http://www.w3.org/1999/xhtml" >
   5:     <head runat="server">
   6:         <title>Default Value</title>
   7:     </head>
   8:     <body>
   9:         <form id="frmMain" runat="server">
  10:             <div>
  11:                 <p>
  12:                     When this button is clicked, a default value will be displayed even though the ViewState-backed property has not been set.
  13:                 </p>
  14:                 <asp:Label ID="lblDefaultValue" runat="server" Text="The value is:  " />
  15:                 <br />
  16:                 <asp:Button ID="btnDefaultValue" runat="server" Text="Load Default" OnClick="btnDefaultValue_Click" />
  17:             </div>
  18:             <div>
  19:                 <p>
  20:                     When this button is clicked, the ViewState-backed property will be set and then displayed.
  21:                 </p>
  22:                 <asp:Label ID="lblSetValue" runat="server" Text="The value is:  " />
  23:                 <br />
  24:                 <asp:Button ID="btnSetValue" runat="server" Text="Set Value" OnClick="btnSetValue_Click" />
  25:             </div>
  26:             <p>
  27:                 Clicking the first button again will now display the new set value as well.
  28:             </p>
  29:         </form>
  30:     </body>
  31: </html>
   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Collections;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11:  
  12: public partial class DefaultValue : System.Web.UI.Page
  13: {
  14:     public DefaultValue() { Load += new EventHandler(Page_Load); }
  15:  
  16:     protected void Page_Load(object sender, EventArgs e) { }
  17:  
  18:     protected void btnDefaultValue_Click(object sender, EventArgs e)
  19:     {
  20:         lblDefaultValue.Text = "The value is:  " + MyDefaultValue;
  21:     }
  22:  
  23:     protected void btnSetValue_Click(object sender, EventArgs e)
  24:     {
  25:         MyDefaultValue = "My set value";
  26:         lblSetValue.Text = "The value is:  " + MyDefaultValue ;
  27:     }
  28:  
  29:     private string MyDefaultValue 
  30:     {
  31:         get { return (string)(ViewState["MyDefaultValue "] ?? "My default value"); }
  32:         set { ViewState["MyDefaultValue "] = value; }
  33:     }
  34:  
  35:     private string Standard
  36:     {
  37:         get
  38:         {
  39:             if (ViewState["Standard"] != null)
  40:             {
  41:                 return (string)ViewState["Standard"];
  42:             }
  43:             else
  44:             {
  45:                 return "My default value";
  46:             }
  47:         }
  48:         set { ViewState["Standard"] = value; }
  49:     }
  50: }

Sunday, January 6, 2008

Using the PreviousPage Property with Multiple Previous Pages

Scenario

Many of you may be familiar with the PreviousPage property of the Page object.  It returns the Page object that transferred control to the current page.  You may also be familiar with the PreviousPageType directive which provides a way to get strong typing against the previous page.

   1: <%@ PreviousPageType VirtualPath="~/YourSourcePage.aspx" %>

In most cases you need nothing more on the target page...  unless the target page has multiple previous pages from which it will be expecting data.

Solution

Enter the Reference directive.  Only one PreviousPageType directive can exist per page but any number of Reference directives can be used.  Most people have used this directive to reference user controls with the following syntax:

   1: <%@ Reference Control="YourControl.ascx" %>

 

The reference directive also has Page and virtualPath attributes, either of which can be used to reference other pages as follows:

   1: <%@ Reference Page="~/YourSourcePage.aspx" %>

 

There is, however, a down side to using the Reference directive over the PreviousPageType directive.  The PreviousPage property will have to be cast to the actual previous page type before use.  For example, if your previous page's class name was SourcePage and it had a property called Message, it would have to be accessed as follows:

   1: string message = ((SourcePage)PreviousPage).Message;

 

This also means that you'll have to do a check before you can cast it to the correct page like so:

   1: string message = string.Empty;
   2: if (PreviousPage is FirstSourcePage)
   3: {
   4:     message = ((FirstSourcePage)PreviousPage).Message;
   5: }
   6: else if (PreviousPage is SecondSourcePage)
   7: {
   8:     message = ((SecondSourcePage)PreviousPage).Message;
   9: }
  10: else
  11: {
  12:     message = "The previous page is unknown.";
  13: }

 

Personally I think this is a small price to pay if you only have a few possible previous pages.  Otherwise, there are always other options - query string, form collection, session state, etc.

Working Example

For those interested in quickly trying this out, I've included all the code you need below.  One scenario has a single source and single target page.  The other has multiple sources pointing to the same target page.

Code behind for the single source page used with the single target page:

   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Collections;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11:  
  12: public partial class SingleSourcePage : System.Web.UI.Page
  13: {
  14:     protected void Page_Load(object sender, EventArgs e) { }
  15:  
  16:     public string Message
  17:     {
  18:         get { return "This is a message from SingleSourcePage.aspx"; }
  19:     }
  20: }

 

Markup for the single source page used with the single target page:

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SingleSourcePage.aspx.cs" Inherits="SingleSourcePage" %>
   2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3: <html xmlns="http://www.w3.org/1999/xhtml" >
   4:     <head runat="server">
   5:         <title>Single Source Page</title>
   6:     </head>
   7:     <body>
   8:         <form id="form1" runat="server">
   9:             <h1>SingleSourcePage.aspx</h1>
  10:             <div>
  11:                 <ul>
  12:                     <li>
  13:                         <a href="SingleSourcePage.aspx">SingleSourcePage.aspx</a>
  14:                     </li>
  15:                     <li>
  16:                         <a href="TargetPageSingleSource.aspx">TargetPageSingleSource.aspx</a>
  17:                     </li>
  18:                     <li>
  19:                         <a href="FirstSourcePage.aspx">FirstSourcePage.aspx</a>
  20:                     </li>
  21:                     <li>
  22:                         <a href="SecondSourcePage.aspx">SecondSourcePage.aspx</a>
  23:                     </li>
  24:                     <li>
  25:                         <a href="TargetPageMultipleSources.aspx">TargetPageMultipleSources.aspx</a>
  26:                     </li>
  27:                 </ul>
  28:             </div>
  29:             <div>
  30:                 <asp:Button ID="btnPostToTargetPage" runat="server" Text="Post to TargetPageSingleSource.aspx" PostBackUrl="~/TargetPageSingleSource.aspx" />
  31:             </div>
  32:         </form>
  33:     </body>
  34: </html>

 

Code behind for the target page with one previous page:

   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Collections;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11:  
  12: public partial class TargetPageSingleSource : System.Web.UI.Page
  13: {
  14:     protected void Page_Load(object sender, EventArgs e)
  15:     {
  16:         if (PreviousPage != null)
  17:         {
  18:             lblMessage.Text = PreviousPage.Message;
  19:         }
  20:         else
  21:         {
  22:             lblMessage.Text = "No previous page was detected.";
  23:         }
  24:     }
  25: }

 

Markup for the target page with one previous page:

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="TargetPageSingleSource.aspx.cs" Inherits="TargetPageSingleSource" %>
   2: <%@ PreviousPageType VirtualPath="~/SingleSourcePage.aspx" %>
   3: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
   4: <html xmlns="http://www.w3.org/1999/xhtml">
   5:     <head runat="server">
   6:         <title>Target Page from a Single Source</title>
   7:     </head>
   8:     <body>
   9:         <form id="frmMain" runat="server">
  10:             <h1>TargetPageSingleSource.aspx</h1>
  11:             <div>
  12:                 <ul>
  13:                     <li>
  14:                         <a href="SingleSourcePage.aspx">SingleSourcePage.aspx</a>
  15:                     </li>
  16:                     <li>
  17:                         <a href="TargetPageSingleSource.aspx">TargetPageSingleSource.aspx</a>
  18:                     </li>
  19:                     <li>
  20:                         <a href="FirstSourcePage.aspx">FirstSourcePage.aspx</a>
  21:                     </li>
  22:                     <li>
  23:                         <a href="SecondSourcePage.aspx">SecondSourcePage.aspx</a>
  24:                     </li>
  25:                     <li>
  26:                         <a href="TargetPageMultipleSources.aspx">TargetPageMultipleSources.aspx</a>
  27:                     </li>
  28:                 </ul>
  29:             </div>
  30:             <div>
  31:                 <asp:Label ID="lblMessage" runat="server" />
  32:             </div>
  33:         </form>
  34:     </body>
  35: </html>

 

Code behind for source page one of two for use with the target page accepting multiple previous pages:

   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Collections;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11:  
  12: public partial class FirstSourcePage : System.Web.UI.Page
  13: {
  14:     protected void Page_Load(object sender, EventArgs e) { }
  15:  
  16:     public string Message
  17:     {
  18:         get { return "This is a message from FirstSourcePage.aspx"; }
  19:     }
  20: }

 

Markup for source page one of two for use with the target page accepting multiple previous pages:

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="FirstSourcePage.aspx.cs" Inherits="FirstSourcePage" %>
   2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
   3: <html xmlns="http://www.w3.org/1999/xhtml">
   4:     <head runat="server">
   5:         <title>First Source Page</title>
   6:     </head>
   7:     <body>
   8:         <form id="frmMain" runat="server">
   9:             <h1>FirstSourcePage.aspx</h1>
  10:             <div>
  11:                 <ul>
  12:                     <li>
  13:                         <a href="SingleSourcePage.aspx">SingleSourcePage.aspx</a>
  14:                     </li>
  15:                     <li>
  16:                         <a href="TargetPageSingleSource.aspx">TargetPageSingleSource.aspx</a>
  17:                     </li>
  18:                     <li>
  19:                         <a href="FirstSourcePage.aspx">FirstSourcePage.aspx</a>
  20:                     </li>
  21:                     <li>
  22:                         <a href="SecondSourcePage.aspx">SecondSourcePage.aspx</a>
  23:                     </li>
  24:                     <li>
  25:                         <a href="TargetPageMultipleSources.aspx">TargetPageMultipleSources.aspx</a>
  26:                     </li>
  27:                 </ul>
  28:             </div>
  29:             <div>
  30:                 <asp:Button ID="btnPostToTargetPage" runat="server" Text="Post to TargetPageMultipleSources.aspx" PostBackUrl="~/TargetPageMultipleSources.aspx" />
  31:             </div>
  32:         </form>
  33:     </body>
  34: </html>

 

Code behind for source page two of two for use with the target page accepting multiple previous pages:

   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Collections;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11:  
  12: public partial class SecondSourcePage : System.Web.UI.Page
  13: {
  14:     protected void Page_Load(object sender, EventArgs e) { }
  15:  
  16:     public string Message
  17:     {
  18:         get { return "This is a message from SecondSourcePage.aspx"; }
  19:     }
  20: }

 

Markup for source page two of two for use with the target page accepting multiple previous pages:

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SecondSourcePage.aspx.cs" Inherits="SecondSourcePage" %>
   2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
   3: <html xmlns="http://www.w3.org/1999/xhtml" >
   4:     <head runat="server">
   5:         <title>Second Source Page</title>
   6:     </head>
   7:     <body>
   8:         <form id="frmMain" runat="server">
   9:             <h1>SecondSourcePage.aspx</h1>
  10:             <div>
  11:                 <ul>
  12:                     <li>
  13:                         <a href="SingleSourcePage.aspx">SingleSourcePage.aspx</a>
  14:                     </li>
  15:                     <li>
  16:                         <a href="TargetPageSingleSource.aspx">TargetPageSingleSource.aspx</a>
  17:                     </li>
  18:                     <li>
  19:                         <a href="FirstSourcePage.aspx">FirstSourcePage.aspx</a>
  20:                     </li>
  21:                     <li>
  22:                         <a href="SecondSourcePage.aspx">SecondSourcePage.aspx</a>
  23:                     </li>
  24:                     <li>
  25:                         <a href="TargetPageMultipleSources.aspx">TargetPageMultipleSources.aspx</a>
  26:                     </li>
  27:                 </ul>
  28:             </div>
  29:             <div>
  30:                 <asp:Button ID="btnPostToTargetPage" runat="server" Text="Post to TargetPageMultipleSources.aspx" PostBackUrl="~/TargetPageMultipleSources.aspx" />
  31:             </div>
  32:         </form>
  33:     </body>
  34: </html>

 

Code behind for target page accepting multiple previous pages:

   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Collections;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11:  
  12: public partial class TargetPageMultipleSources : System.Web.UI.Page
  13: {
  14:     protected void Page_Load(object sender, EventArgs e)
  15:     {
  16:         if (PreviousPage != null)
  17:         {
  18:             if (PreviousPage is FirstSourcePage)
  19:             {
  20:                 lblMessage.Text = ((FirstSourcePage)PreviousPage).Message;
  21:             }
  22:             else if (PreviousPage is SecondSourcePage)
  23:             {
  24:                 lblMessage.Text = ((SecondSourcePage)PreviousPage).Message;
  25:             }
  26:             else
  27:             {
  28:                 lblMessage.Text = "The previous page is unknown.";
  29:             }
  30:         }
  31:         else
  32:         {
  33:             lblMessage.Text = "No previous page was detected.";
  34:         }
  35:     }
  36: }

 

Markup for target page accepting multiple previous pages:

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="TargetPageMultipleSources.aspx.cs" Inherits="TargetPageMultipleSources" %>
   2: <%@ Reference Page="~/FirstSourcePage.aspx" %>
   3: <%@ Reference Page="~/SecondSourcePage.aspx" %>
   4: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
   5: <html xmlns="http://www.w3.org/1999/xhtml" >
   6:     <head runat="server">
   7:         <title>Target Page from Multiple Sources</title>
   8:     </head>
   9:     <body>
  10:         <form id="frmMain" runat="server">
  11:             <h1>TargetPageMultipleSources.aspx</h1>
  12:             <div>
  13:                 <ul>
  14:                     <li>
  15:                         <a href="SingleSourcePage.aspx">SingleSourcePage.aspx</a>
  16:                     </li>
  17:                     <li>
  18:                         <a href="TargetPageSingleSource.aspx">TargetPageSingleSource.aspx</a>
  19:                     </li>
  20:                     <li>
  21:                         <a href="FirstSourcePage.aspx">FirstSourcePage.aspx</a>
  22:                     </li>
  23:                     <li>
  24:                         <a href="SecondSourcePage.aspx">SecondSourcePage.aspx</a>
  25:                     </li>
  26:                     <li>
  27:                         <a href="TargetPageMultipleSources.aspx">TargetPageMultipleSources.aspx</a>
  28:                     </li>
  29:                 </ul>
  30:             </div>
  31:             <div>
  32:                 <asp:Label ID="lblMessage" runat="server" />
  33:             </div>
  34:         </form>
  35:     </body>
  36: </html>

 

Conclusion

I've used this method several times with different websites I've worked on and it seems to do the trick for me.  The comments are open if anyone else has another way they prefer to do this.