Saturday, April 30, 2005

Source Code for MessageBox Button

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/source_code_for_messagebox_button.htm]

A couple people have requested the source code for the ServerConfirm button I wrote. This adds a yes-no MessageBox to a regular html button. Clicking either yes or no triggers the appropriate server event.

Note that I wrote this version over a year ago, and there are some changes I would make:

  1. The JavaScript used to get the selected MessageBox value doesn't work in NN.
  2. This is a custom-rendered control, which is created entirely from scratch - i.e. just html controls. It could be made as an extended control instead. This would still be compiled to a DLL and be reusable among ASP.Net projects (unlike UserControls), but would give the additional advantage of keeping all the WebControl.Button's extra properties that merely the Html button lacks.

I'll cover these in a future blog post. FYI, other blog posts related to this are:

With that all said, here's the source code below. It has three main regions:

  1. Public properties (Text for the button's text value, and Message for the MessageBox).
  2. Postback Data - handles posting data back to the server
  3. Postback Events - has a yes and no event

The Render method renders all the necessary Html and JavaScript to create the button.

Imports System.ComponentModel
Imports System.Web.UI

Namespace Stall.WebControls

    "Text"
), ToolboxData("<{0}:ServerConfirmButton runat=server>")> Public Class ServerConfirmButton
        Inherits System.Web.UI.WebControls.WebControl
        Implements IPostBackDataHandler
        Implements IPostBackEventHandler

#Region
"Properties"

        Protected _strText As
String
        True), Category("Appearance"), DefaultValue("")> Property [Text]() As
String
           
Get
                Return _strText
            End
Get

            Set(ByVal Value As String)
                _strText = Value
            End
Set
        End
Property

        Protected _strMessage As
String
        True), Category("Appearance"), DefaultValue("")> Property Message() As
String
           
Get
                Return _strMessage
            End
Get

            Set(ByVal Value As String)
                _strMessage = Value
            End
Set
        End
Property

#End Region

        Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
           
'output.Write([Text])

            Dim strHdnName As String = Me.UniqueID

           
'generate hidden field to store answer:
           
'
            output.WriteBeginTag("Input")
            output.WriteAttribute("type", "hidden")
            output.WriteAttribute("name", strHdnName)
            output.WriteAttribute("value", "none")
            output.Write(HtmlTextWriter.TagRightChar)

           
'generate button:
            output.WriteBeginTag("Input")
            output.WriteAttribute("value", Me.Text)
            output.WriteAttribute("type", "button")
            output.WriteAttribute("onclick", "javascript:if (confirm('" & _strMessage & "')) {document.all.item('" & strHdnName & "').value = 'Yes';} else{document.all.item('" & strHdnName & "').value = 'No';}" & Page.GetPostBackClientEvent(Me, "EventClicked"))
            output.Write(HtmlTextWriter.TagRightChar)

           
'write out source of control:
            output.Write("")

        End
Sub

#Region
"PostBack Data"

        Protected _blnConfirm As Boolean =
False

        Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData

           
'Note: a field with Me.UniqueID must be declared for this to fire.
           
'Get the hidden field value so we know which event to trigger
            If (postCollection(postDataKey).Equals("Yes"))
Then
                _blnConfirm =
True
           
Else
                _blnConfirm =
False
            End
If

        End
Function

        Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent
           
'Get the hidden field value so we know which event to trigger
        End
Sub

#End Region

#Region
"PostBack Events"

        Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent
           
'check the hidden field to determine which event to raise.
            If (_blnConfirm)
Then
                OnConfirmYes(EventArgs.Empty)
           
Else
                OnConfirmNo(EventArgs.Empty)
            End
If
        End
Sub

        Event ConfirmYes(ByVal sender As Object, ByVal e As EventArgs)

        Protected Overridable Sub OnConfirmYes(ByVal e As EventArgs)
           
'raise the event
            RaiseEvent ConfirmYes(Me, EventArgs.Empty)
        End
Sub

        Event ConfirmNo(ByVal sender As Object, ByVal e As EventArgs)

        Protected Overridable Sub OnConfirmNo(ByVal e As EventArgs)
            RaiseEvent ConfirmNo(Me, EventArgs.Empty)
        End
Sub

#End Region

    End
Class

End
Namespace

Wednesday, April 27, 2005

Windows User Controls: Mult-View and File TextBoxes

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/windows_user_controls_multview_and_file_textboxes.htm]

A while ago I wrote some advanced TextBox windows controls:

  1. MutliTextbox - Designed to split a single big window into multiple sections, just like splitting an Excel worksheet into multiple views.
  2. FileTextBox - Designed to easily load and save files to the text area.

These take advantage of WinForms ability to write your own custom controls. These are standard Windows User Controls (System.Windows.Forms.UserControl). Such controls can be added to the toolbox under the "UserControls" tab, and have full design support.

Both of these are free controls at: http://www.timstall.com/Code/WinControls.aspx. I find these useful when I making my own tools.

Monday, April 25, 2005

Hosting Multiple Instances of an ASP.Net App

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/hosting_multiple_instances_of_an_aspnet_app.htm]

Hosting multiple instances of an ASP.Net web app on a single server can be useful. While running multiple instances of an *.exe is trivial, multiple instances of a web app are not because they're both served through IIS. For example, the client may want a static copy of the app for internal demos, but for various reasons unique to that client, they can't use the staging instance for their demo. Or the client may want separate apps for two totally separate vendors. They can then configure (not re-compile) the apps appropriately.

.Net can do this. Assuming that you're deploying the app via an MSI, you  primarily need to just change the MSI's version. You'll then also need to modify the apps config, such as making it point to it's own database.

However, be aware of two limitations:

  1. You'll need to change the version of the MSI file, which requires checking it out from source control and recompiling.
  2. You can't install an earlier version if a later already exists.

So, you could compile two instances, label them version 1.0 and 1.1, and then run each MSI (1.0 and then 1.1) and configure them as needed.

Saturday, April 23, 2005

Using JavaScript to read a client-side file

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/using_javascript_to_read_a_clientside_file.htm]

One of the benefits of blogs is that you get to write about oddball topics just for fun. One such topic is using JavaScript to read a client-side file. Initially this wasn't possible - JavaScript was not designed to allow this due to security concerns. Imagine a malicious app reading/writing all your important system files! It is certainly not advisable due to security concerns. A good security overview of JavaScript is in the "Security" section at: http://www.quirksmode.org/js/intro.html

Most of the time if you do need a client file, you could use ASP.Net's file uploaded control to upload files from the client to the server.

However, it is still possible for JavaScript to read client files using ActiveX:

function ReadFromFile() {
    var strContent = ReadFileToString(document.Form1.TxtFileName.value);
    document.Form1.HdnContent.value = strContent;
    document.Form1.submit();
}

function ReadFileToString(strFileName) {
    var strContents;
    strContents = "";

    objFSO = new ActiveXObject("Scripting.FileSystemObject");
    if (objFSO.FileExists(strFileName)) {
        strContents = objFSO.OpenTextFile(strFileName, 1).ReadAll();
    }
   
    return strContents;
} //end of function

This script first uses the ActiveX Scripting.FileSystemObject to read the file, then stores the contents in a hidden field, and lastly submits to the server so it can do whatever it needs with that client data.

Note that you'll need to enable ActiveX objects in the browser, else you'll get an error like: "Error: Number:-2146827859 Description:Automation server can't create object". You can do this by:

  1. In Internet Explorer > Tools > Internet Options > Security > Custom Level
  2. Enabling or prompting "Initializing and Script Activex controls not marked as safe"

While this is a cute trick to know, again I emphasize be cautious of using it in any enterprise app due to security reasons.

Tuesday, April 19, 2005

Remove ListItems, by value, from the DropDownList

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/remove_listitems_by_value_from_the_dropdownlist.htm]

There are times when you'll want to add a set of items to a DropDownList, and then remove certain items. For example, you may databind a code-value table from a shared database, but then need to exclude potential values based on individual configuration settings.

The DropDownList.Items property is a ListItemCollection that supports the Remove and RemoveAt methods. The RemoveAt simply takes an index (of type integer) and removes the item at that specific index. However, referencing items by index can be delicate. For example, if the DropDownList added a blank space at the top to indicate that the field was optional, that would bump all the indexes off by one. For lookup dropdowns, where the text is a description and the value is a unique primary key, we'd like to be able to remove items by value.

The problem is that the Remove(string) method is based on a "string representation" of the list item, NOT just the value. So if we add items to a dropdown like so:

this.DropDownList1.Items.Add(new ListItem("Apple","101"));
this.DropDownList1.Items.Add(new ListItem("Banana","201"));
this.DropDownList1.Items.Add(new ListItem("Orange","301"));
this.DropDownList1.Items.Add(new ListItem("Mango"));

 The following will remove nothing:

this.DropDownList1.Items.Remove("Banana");    //Text
this.DropDownList1.Items.Remove("201");    //Value

Remove(string) works by first constructing a new ListItem from that string, and then removing that ListItem. In other words, Remove works acts like so:

this.DropDownList1.Items.Remove(new ListItem("Banana"));


This will create a new ListItem with both the text and description being the same ("201"). Therefore if the list items have different text and values, which they almost always do, then the Remove(string) method by itself won't work.

However, we can make our own method that cycles through the dropdown and removes if the value matches. Note that we could make this more reusable by sending in the dropdown as a ref parameter.

public static void RemoveByValue(ref DropDownList d, string strKey)
{
    for (int i = 0; i < d.Items.Count; i++)
    {       
        if (d.Items[i].Value == strKey)
        {
            d.Items.RemoveAt(i);
            return;
        }
    }
}

Given that dropdowns are only intended for small sets of data, cycling through all the items isn't a significant performance hit.

This provides a reusable method to remove items (by value) from a dropdown list.

Sunday, April 17, 2005

Maintaining Scroll Position on PostBacks

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/maintaining_scroll_position_on_postbacks.htm]

A classic problem is ASP.Net is then when you postback on a long page, you lose your position. For example, if you were at the bottom of the page and hit a server control, the page may refresh and put you at the top. This can be very frustrating for pages that are more than one screen long.

One solution is to use the Page property, SmartNavigation. This supposedly will persist position upon postback, but it is very delicate and can induce many rendering bugs. The page may work fine on your local machine, but may have all its styles messed up in other environments. I've tried using SmartNavigation before, only to have more hassle than its worth.

A more durable solution is described in an article by Steve Stchur: http://aspnet.4guysfromrolla.com/articles/111704-1.aspx. It walks through a solution and provides a custom control.

Monday, April 11, 2005

Adding and Finding UserControls

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/adding_and_finding_usercontrols.htm]

Dynamically adding a UserControl to a page provides great flexibility and code reuse. It's also relatively easy. You can add a control to a page by putting it in a PlaceHolder, and then retrieve it later using the Page.FindControl method. While there are other articles that address this, this blog post will highlight 3 tips:

  1. Make sure you give your added-control an ID so that you can reference it later
  2. Call the method that adds the controls from the OnInit method. This will then handle viewstate for you.
  3. Use Request.ApplicationPath to get the application path of the UserControl.

The code below provides a method, InitializeUserControl, which is called from the OnInit of the page. The method first gets the path of the UserControl, then loads it into the page, sets the ID, and then adds it to a placeholder. Note that if you don't set the ID, then you can't (easily) reference it later.

One convenient convention is having all directory paths end with a "/". By creating a utility method GetApplicationPath, we can wrap the Request.ApplicationPath method and add the "/" to the end. In real applications this Utility method would be abstracted to its own class.

public void InitializeUserControl()
{
   
// Put user code to initialize the page here
    string strUrl = GetApplicationPath() + "AddControls/WebUserControl1.ascx";
    UserControl uc = null;
    uc = (UserControl)Page.LoadControl(strUrl);
    uc.ID = "UC1";
    this.PlaceHolder1.Controls.Add(uc);
}

public static string GetApplicationPath()
{
    return System.Web.HttpContext.Current.Request.ApplicationPath + @"/";
} //end of method

We can find the control using the Page.FindControl method and the ID we assigned above. In the snippet below we cast the UserControl to the appropriate type (WebUserControl1), and then call its custom property MyNewProperty.

UserControl uc = null;
uc = (UserControl)Page.FindControl("UC1");
this.Label1.Text = ((WebUserControl1)uc).MyNewProperty;