Wednesday, October 28, 2009

SiteMap Custom Attributes

In this example we will use a custom attribute to hide some SiteMapNodes from a Menu control.
You can create custom attributes in a sitemap file easily as follows:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode>
<siteMapNode url ="./Default.aspx" title="Find Equipment" description="Find Equipment" />
<siteMapNode url="./RentProductPage.aspx" title="Make Reservation" description="Make Reservation" />
<siteMapNode url="./SearchResults.aspx" title="Search Results" description="Search results" IsMenuVisible="false" />
<siteMapNode url="./ProductDetails.aspx" title="Product Details" description="Product Details" />
<siteMapNode url="./ManageEquipment.aspx" title="Manage Equipment" description="Manage Equipment" />
<siteMapNode url="./SampleContract.aspx" title="View Sample Contract" description="View Sample Contract" />
</siteMapNode>
</siteMap>





On the SearchResults.aspx node there is a custom attribute called “IsMenuVisible”. The “IsMenuVisible” attribute has been set to “False” since we do not want this node showing in the nav menu.



This attribute can easily be accessed from the MenuItemDataBound event in the codebehind as follows:




void navMenu_MenuItemDataBound(object sender, MenuEventArgs e)
{
SiteMapNode node = ((SiteMapNode) e.Item.DataItem);
MenuItem parentItem = e.Item.Parent;

string isMenuVisible = node["IsMenuVisible"];
if (isMenuVisible != null)
{
if (isMenuVisible.ToUpper() == "FALSE")
{
if (parentItem != null)
{
parentItem.ChildItems.Remove(e.Item);
}
else
{
navMenu.Items.Remove(e.Item);
}
}
}
}





In the above example we cast “e.Item.DataItem” back to a SiteMapNode and access the custom attribute through it’s string index.  The remaning code is used to remove it from the Menu

Wednesday, September 9, 2009

ASP.NET Confirmation LinkButton

Below is the code to a very simple and reusable Confirmation LinkButton. Basically the control emits the standard "Are you sure?" type text before actually submitting the event allowing the user to click "Ok" or "Cancel". Just create a new .CS file and copy/paste the code below into the file. This will create the new control. Here it is:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace CreateSoftwareUtils.Web.Controls
{
public class ConfirmLinkButton:LinkButton
{
[
Browsable(true),
PersistenceMode(PersistenceMode.Attribute),
Themeable(true)
]
public string ConfirmationText
{
get
{
string confirmationText = ViewState["ConfirmationText"].ToString();
if (String.IsNullOrEmpty(confirmationText))
confirmationText = "Are you sure?";

return confirmationText;
}
set
{
ViewState["ConfirmationText"] = value;
}
}

protected override void OnPreRender(EventArgs e)
{
this.OnClientClick = String.Format("return confirm(\"{0}\");" + this.OnClientClick,ConfirmationText);
base.OnPreRender(e);
}
}
}





You can then reference this control in your page like any other normal LinkButton. Except there is a new property called "ConfirmationText" that you can set to specify a confirmation message, or just let it default to "Are you sure?"

Wednesday, August 26, 2009

ASP.NET GridView Confirm Delete Button Field

Here is a quick and easy reusable "ConfirmButtonField" for use on an ASP.NET GridView

Just simply derive from ButtonField and override a couple things:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CreateSoftwareUtils.Web.GridViewControls
{
    public class ConfirmButtonField : ButtonField
    {
        public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
        {
            base.InitializeCell(cell, cellType, rowState, rowIndex);
            if(cellType == DataControlCellType.DataCell)
            {
                cell.Attributes.Add("onclick","return confirm(\"Are you sure?\");");
            }
        }
    }
}
Then on the ASPX page you can do this:
<asp:GridViewID="gvEquipment"runat="server"AutoGenerateColumns="false"DataKeyNames="ProductId">
    <Columns>                                                                 
        <cs:ConfirmButtonField ButtonType="Link"Text="Delete"CommandName="Delete"/>
        <asp:ButtonField ButtonType="Link"Text="View/Edit"CommandName="Select"/>
        <asp:BoundField DataField="Name"HeaderText="Title"HeaderStyle-Width="300px"ItemStyle-Width="300px"ItemStyle-Wrap="true"/>
        <asp:BoundField DataField="PriceDaily"HeaderText="Daily Rate"DataFormatString="{0:c}"HeaderStyle-Width="80px"/>
    </Columns>
</asp:GridView>
I also like to register controls I am going to use all over the site in the Web.Config like this:

<add tagPrefix="cs"namespace="CreateSoftwareUtils.Web.GridViewControls"assembly="CreateSoftwareUtils"/>

Wednesday, November 19, 2008

Browse the Internet while connected through VPN

A common scenario for developers and other IT professionals is to work remotely via terminal services (remote desktop) through a VPN connection. One problem I'm sure everyone has expererienced (besides it being slow) is that by default it is not possible to browse the internet or keep Microsoft Outlook connections established once you have established the VPN connection to the server. Luckily, there is actually an easy solution to this problem!

The lies in the routing table. By default once the VPN connection is formed the routing table is modified so that all outbound packets are sent to the VPN servers gateway address instead of your own gateway which would lead to the internet.
To correct this issue in Windows XP simply open the properties for the VPN connection (by right-clicking the VPN connection icon and choosing properties) and then select the "Networking" tab:


Next select "Internet Protocol (TCP/IP)" and press the properties button which opens a new window with TCP/IP properties, you will then need to click on advanced:


And finally we make it to the magic checkbox that will make your life easier. Uncheck "Use default gateway on remote network". One this has been disabled you should find that browsing the internet while also being connected to a VPN server is no longer issue.




Hope this helps! Haven't tried this under Vista, leave a comment and let me know if it is the same...


Thanks




Friday, November 14, 2008

Tip for Managing Keys for QueryString, ViewState, Session, Etc.

When developing web applications you always have to manage all those "names" for the QueryString, ViewState, Session, etc. and its good to have consistent and "typed" way to too keep track of that stuff.

To manage this information on a per page basis I simply create private classes within the page so I can have a typed way of accessing these keys. For example:


namespace GTTPatentSearchWeb.Search
{
public partial class ViewPatent : System.Web.UI.Page
{
private class QueryStringKeys
{
public const string PatentId = "PatentId";
public const string UserId = "Uid";
public const string RegionId = "Rid";
}

private class ViewStateKeys
{
public const string SelectedPatentId = "SelPatentId";
public const string IsNew = "IsNew";
}

public int PatentId
{
get
{
object patentId = Request.QueryString[QueryStringKeys.PatentId];
if (patentId != null)
{
return Convert.ToInt32(patentId);
}

return -1;
}
}

...

As you can see this provides a nice typed method for managing keys rather than trying to remember the names you used throughout the page.
For SessionKeys I generally will create a global class in App_Code to manage the SessionKeys since they cross page and not specific to the page itself. This prevents me from accidentally using a SessionKey in 2 places in application not realizing it had already been used.

Have a great day!

Thursday, October 16, 2008

Creating Easy ASP.NET Custom Web Controls

public void Warning()
{
/*
WARNING: This is my first post so I apologize in advance for any strange formatting errors you may experience and also my inability to write complete sentences or sometimes just keep on going and never acutally end the sentence making for a very long sentence that just goes on and on and on and on, and on... ;-)
*/
throw new BadWriterException("Keep your day job!");
}
Overview
In this quick article I am going to show you a method I use to quickly make "Custom Web Controls" without having to deal with manually building the layout declaritvely in code or by hardcoding the markup directly into the control.  
This will essentially be accomplished by saving the control markup as a seperate file and marking the file as an embedded resource within the web control project.
Sound interesintg, then read on...
Let's Get Started
Many times when I am working on an ASP.NET site I realize that one of my "UserControls" I have created in my web project would be extremely useful in my toolkit and warrants conversion into a "Custom Web Control".
"Custom Web Controls" have several advantages over "User Controls" such as: easier/reliable method of sharing across multiple projects/solutions and developers/people, and the ability to add more "Designer" features, to name a few...

After spending hours of tweaking the nuances of the visual portion of my "UserControl" I don't want to have to go back to square one and spend alot of time converting my XHTML markup to a declaritive control tree in code.  I also don't want to have to spend alot of time trying to get my markup to hard-code into the control which also makes it more difficult to manage.
The solution I have created is to put the markup into a seperate ".txt" file and save it as an "Embedded Resource" in my custom web control project and then parse the control text dynamically at runtime.

So enough typing, lets look at some pictures!
In the following pictures I will be demonstrating a simple templated container control with a "Header" and "Body".  I am generally buidling business applications and I use this type of control all the time to group sections of a page into logical sections.
The final result looks like this:

Example 1:
And of course you can change the style sheets and customize however you want:
Example 2:
And here is what the usage looks like in an .ASPX or .ASCX:
Building the Control
To create this control you will need to create a custom webcontrol project and then create (2) files:
  • One for the Control itself
  • and one more for the Control markup; which is just a .txt file
Make to sure to right-click the Control markup file, choose properties, and sets its "Build Action" to "Embedded Resource"
When you're done it should something like this:
And here is the control markup that gets placed into the SectionHeaderControl.txt file:

<asp:panel runat="server" CssClass="sectionHeaderContainer">
<asp:panel runat="server" CssClass="sectionHeaderTitleContainer">
<span class="sectionHeaderTitleContainerCell"><asp:Label runat="server" ID="lblTitle" CssClass="sectionHeaderTitleText" /></span>
</asp:panel>
<asp:panel runat="server" CssClass="sectionHeaderContentContainer">
<asp:PlaceHolder runat="server" ID="plContent" />
</asp:panel>
</asp:panel>

The Code
Just like in most CustomControls the magic happens in the "CreateChildControls()" method. Here is the entire section of code in the "CreateChildControls()" method:
protected override void CreateChildControls()
{
string controlText = CSUtility.GetEmbeddedResource("CreateSoftwareUtils.Web.Controls.SectionHeaderControlText.txt");
ControlParser parser = new ControlParser();
Control parsedMasterControl = parser.Parse(controlText);
parsedMasterControl.ID = "masterControl";
this.Controls.Add(parsedMasterControl);

_contentControls = parsedMasterControl.FindControl("plContent") as PlaceHolder;
Label lblTitle = parsedMasterControl.FindControl("lblTitle") as Label;

lblTitle.Text = Title;
base.CreateChildControls();
}
}
So, as you can see, there isn't much to it!
I have wrapped a couple of generic things into some utlity classes so I don't have to keep typing the same stuff over and over again; also just in-case I change how I am accomplishing those particular tasks it will be an easy change across all the controls. 
The first is CSUtility.GetEmbeddedResource():
This method is responsible for getting the control text from the embedded resource and is pretty simple...

public static string GetEmbeddedResource(string resourceName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Stream resourceStream = assembly.GetManifestResourceStream(resourceName);
StreamReader reader = new StreamReader(resourceStream);
string resourceText = reader.ReadToEnd();
return resourceText;
}

The next is the "ControlParser" object.  Those of you who have used the "ParseControl()" method before probably did this on the "Page" object.  Unfortunatly the Page object is not available all the time, and so when one of the properties calls "EnsureChildControls()" we get a object null error.
The ControlParser class inherits from the TemplateControl abstract class (same as Page) and is also quite simple.  Setting the .AppRelativeVirtualPath is what allows things to work correctly and not receive "virtualPath" errors:
Here is entire code base of that control:
/// <summary>
/// Allows parsing of controls without an HttpContext or Page object
/// Normally ParseControl would be called from the Page object but thats
/// not possible when the Page object is not in scope/available
/// </summary>
public class ControlParser : TemplateControl, INamingContainer
{
public Control Parse(string controlText)
{
this.AppRelativeVirtualPath = "/";

return this.ParseControl(controlText);
}
}
Ok, now that you know the secret parts of the control lets go back and talk about the CreateChildControls() method, here it is again so you don't have to scroll up:

protected override void CreateChildControls()
{
string controlText = CSUtility.GetEmbeddedResource("CreateSoftwareUtils.Web.Controls.SectionHeaderControlText.txt");
ControlParser parser = new ControlParser();
Control parsedMasterControl = parser.Parse(controlText);
parsedMasterControl.ID = "masterControl";
this.Controls.Add(parsedMasterControl);

_contentControls = parsedMasterControl.FindControl("plContent") as PlaceHolder;
Label lblTitle = parsedMasterControl.FindControl("lblTitle") as Label;

lblTitle.Text = Title;
base.CreateChildControls();
}
What's happening?
Basically heres what happens:
  • (1): We get the control markup from the embedded resource
  • (2,3):  Use ParserControl to create a new Control from the control markup
  • (4): Set the ID for fun
  • (5): Add the Control we just created dynamically to the Control Tree
  • (6,7): Get references to controls within the dynamic control so we can do stuff with them
  • (8): Set the title
This, to me, is a lot easier to look at than 300 lines of declartive C# code that creates a big long control tree!
Full Source:
Here is the complete source of the control:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;


namespace CreateSoftwareUtils.Web.Controls
{
public class SectionHeader : WebControl, INamingContainer
{
private string _title;
[
Browsable(true),
PersistenceMode(PersistenceMode.Attribute)]
public string Title
{
get
{
return _title;
}
set
{
_title = value;
}
}

private PlaceHolder _contentControls;
[PersistenceMode(PersistenceMode.InnerProperty)]
public PlaceHolder ContentControls
{
get
{
EnsureChildControls();
return _contentControls;
}
}

protected override void CreateChildControls()
{
string controlText = CSUtility.GetEmbeddedResource("CreateSoftwareUtils.Web.Controls.SectionHeaderControlText.txt");
ControlParser parser = new ControlParser();
Control parsedMasterControl = parser.Parse(controlText);
parsedMasterControl.ID = "masterControl";
this.Controls.Add(parsedMasterControl);

_contentControls = parsedMasterControl.FindControl("plContent") as PlaceHolder;
Label lblTitle = parsedMasterControl.FindControl("lblTitle") as Label;

lblTitle.Text = Title;
base.CreateChildControls();
}
}
}
Conclusion
Now you may be wondering why I didn't use ITemplate's and TemplateContainers for creating this templated control, here's why:
Anything contained within an ITemplate is not accessible from within the page unless you use FindControl() on the parent to locate what you need.  I find this to be very annoying and adds alot of ugly code in the back end to get references and you don't have intellisense available.
[NOTE: it also possible to use ITemplate instead of PlaceHolder by setting the TemplateInstance.Single attribute on the property so that the internal Template controls would be accesible outside the container.  This is new in 2.0 and later]
By exposing a PlaceHolder as the Template instead of ITemplate inside the custom control, you still get to have Intellisence, normal control references, and everything just works as expected on the consuming .ASPX/.ASCX page
One last thing, since I didn't create any "Designer" for this Control it of course doesn't render/pukes in the "Design" view of Visual Studio.  If you end up writing a designer for this control let me know and I will post it here for all to enjoy, including me! ;-)
Let me know what you think of this article, the more responses/followers/comments I receive the more encouraged I will be to write another.  Unless of course the responses/comments are all "You Suck!" ;-)
Thanks,
Joshua Mason
Senior Software Engineer