Monday, December 22, 2008

 

Recovering the Missing

Under IIS 5.0 and before, and when debugging under Visual Studio 2005, HTTP file-not-found errors can be caught in a global.asax Application_Error event function, obscurely documented by Microsoft, whose first argument can be cast to global_asax, which supplies context, including an error message but not an HTTP error code. By using Server.Transfer error handling can be redirected to an ASP.NET page.

Microsoft documentation for Visual Studio 2005 does not explain how to catch HTTP file-not-found errors under IIS 6.0, and many of the contributors to forums and Web logs have spread misinformation. IIS 6.0 will redirect HTTP file-not-found errors when configured for Custom Errors: HTTP Error 404, Type URL and Contents a so-called "absolute URL" for an ASP.NET page, for example, /VirtualDirectory/ErrorHandler.aspx where ErrorHandler.aspx.cs provides code to handle the file-not-found error.

The key to this approach, omitted from Microsoft documentation, is that Request.RawUrl in the code for the error handler page identifies the HTTP error and provides the original URL that provoked it. It has the format, ErrorHandlerURL?nnn;OriginalURL, where ErrorHandlerURL is the URL to reach the error handler page, nnn is the HTTP error code, and OriginalURL is the URL that provoked the HTTP error. An error handler page can set Response.ContentType and write to the Response.Output stream to supply content for the OriginalURL.

Sunday, November 30, 2008

 

Remoting without Heartburn

The infrastructure for .NET Remoting is capable of supporting any number of remotable objects, each with any number of remotable functions. For applications implemented on a LAN, the simplest and most efficient approach is a Windows service using one or more TCP channels with binary formatting serviced by one or more remotable objects with infinite life. If each potential client is serviced by its own object, no conflicts need occur, and services will be supplied simultaneously to multiple clients. This approach is not scalable, but it is suitable when there will be only a small number of clients.

Microsoft documentation and several Web logs provide instructions to create services and remotable objects. What they do not clearly explain is that remotable objects need not be registered. When registered the framework may create instances on client demand rather than let the service control objects. Microsoft refers to the approach to remoting described here as "direct remoting" and "dynamic publication" but provides only a single example and a cursory description.

Microsoft explains remotable objects with infinite life only in a Developer's Guide article and not in the class reference for MarshalByRefObject.InitializeLifetimeService. The VS2005 Microsoft documentation is misleading as to specifying paths to configuration files and as to specifying full URIs when launching remotable objects. For whatever reasons, you will not find direct remoting detailed in books that purport to explain .NET Remoting, including Ingo Rammer and Mario Szpuszta, "Advanced .NET Remoting" (2 Ed., 2005, Apress), a circumstance that probably tended to retard adoption of remoting technology.

The configuration file for a Windows service with a "TcpAgent" channel is simple, in the following form:

<~configuration~>
    <~system.runtime.remoting~>
        <~application~>
            <~channels~>
                <~channel name="TcpAgent" ref="tcp" port="9000"
                 useIpAddress="true" bindTo="10.20.40.99" /~>
            <~/channels~>
        <~/application~>
    <~/system.runtime.remoting~>
<~/configuration~>

Using ASP.NET 2.0, the OnStart and Onstop methods for an "Agent" Windows service with a "Remote" class, providing an "AgentRemote" object, are also simple, in the following form:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.ServiceProcess;
static Remote m_oRemote = null;
public partial class Agent : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        try
        {
            AppDomain oAppDomain = AppDomain.CurrentDomain;
            string sBaseDirectory = oAppDomain.BaseDirectory;
            string sConfigurationPath = sBaseDirectory + "Agent.exe.config";
            RemotingConfiguration.Configure(sConfigurationPath, true);
            m_oRemote = new Remote();
            RemotingServices.Marshal(m_oRemote, "AgentRemote", typeof(Remote));
        }
        catch (Exception oException)
        {
        }
    }
    protected override void OnStop()
    {
        if (m_oRemote != null)
            RemotingServices.Disconnect(m_oRemote);
        m_oRemote = null;
    }
}
public class Remote : MarshalByRefObject
{
    public override Object InitializeLifetimeService()
    {
        return null;
    }
    // remotable service functions
}

In a VS2005 Windows service project, add an Application Configuration File item named app.config, and it will be automatically copied to the executable file directory and renamed using the ".exe.config" form shown above. For Windows services it is necessary to provide a full path to the configuration file, done as above when in the same directory as the executable. Returning null from InitializeLifetimeService allows infinite life. Multiple, named remotable objects from the same or different classes can be launched with multiple copies of new and Marshal statements.

Client access to the "AgentRemote" object above is also simple, in the following form:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.ServiceProcess;
class AgentRemoteClient
{
    void ClientFunction()
    {
        TcpChannel oTcpChannel = new TcpChannel();
        ChannelServices.RegisterChannel(oTcpChannel, true);
        Remote oRemote = (Remote)Activator.GetObject(
         typeof(Remote), "tcp://10.20.40.99:9000/AgentRemote");
        if (oRemote != null)
        {   // invoke methods on oRemote
        }
        else
        {   // log exception
        }
    }
}

The client needs a reference to the server executable and a  using  statement specifying the server namespace. Both client and server need references to System.Runtime.Remoting.dll, usually found in C:\Windows\Microsoft.NET\Framework\v2.0.50727.

A Microsoft remoting configuration does not provide syntax for specifying multiple, named object instances launched via Marshal, but it is simple to provide an auxiliary text file that designates an object type and name for each, thus obtaining the benefits of configuration files without undesirable complications. The OnStart and OnStop methods and the remotable object functions can be elaborated with a variety of logging techniques to create a record of operations and exceptions.

The Microsoft framework does not provide a way to associate remotable objects launched by Marshal with channels. That could be achieved with a Windows service for each channel. Otherwise, as a Developer's Guide article says, remotable objects can share but cannot own channels. Contrary to what Microsoft documentation implies in places, adding network addresses and ports to object names in the second Marshal argument will not work.

To configure multiple channels and multiple remotable objects dynamically, the Microsoft configuration file can be replaced with a text file specifying channels to be registered with ChannelServices.RegisterChannel and remotable objects to be launched with RemotingServices.Marshal. Channels created using the TcpServerChannel class can be associated with IP adddresses, port numbers, formatters and other sink objects. With that approach, custom sinks can be added to provide interpretation, compression, encryption and other services.

[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

Saturday, November 22, 2008

 

Remotely Possible

The .NET Remoting services introduced with ASP.NET made interprocess communication much less burdensome for the C# (and C++) developer. Visual Basic developers long had the benefit of COM automation, but C++ developers were stuck with the tedious tasks of writing proxies and marshalling code. So .NET Remoting is life in the fast lane.

However, the Microsoft examples of .NET Remoting and their emulations in most Web logs are only half debuggable (on the client side). With Microsoft's approach, the work on the server side is done in an object encapsulated in a DLL, not accessible to the debugger running a server (actually a server launcher). Beneficiaries of welcome automation must resort to archaic, file-writing techniques to debug a server-side process.

The solution is actually simple. Just insert the remote object code in the server code, and don't put it in a separate project. In the client code, create a reference to the .exe file for the server. The Server code now looks like the following example, using a console application as a test vehicle for a TCP channel server:

using System;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace Server
{
    public class ServerFunctions : MarshalByRefObject
    {
        public int ServerFunction1( . . . .
        public bool ServerFunction2( . . . .
        . . . .
    }
 
    public class ServerLauncher
    {
        static void Main(string[] args)
        {
            try
            {
                TcpServerChannel oTcpChannel = new TcpServerChannel(9000);
                ChannelServices.RegisterChannel(oTcpChannel, true);
                RemotingConfiguration.RegisterWellKnownServiceType
                (
                    typeof(ServerFunctions), "RemoteService",
                    WellKnownObjectMode.Singleton
                );
                Console.WriteLine("Type Enter to exit");
                System.Console.ReadLine();
            }
            catch (Exception oException)
            {
                Console.WriteLine("Error: " + oException.Message);
                Console.ReadLine();
            }
        }
    }
}

Be sure to add a reference to System.Runtime.Remoting.dll in a .NET Remoting project, when using ASP.NET 2.0 typically found in C:\Windows\Microsoft.NET\Framework\v2.0.50727. With code structured this way running under the Visual Studio debugger, functions in the ServerFunctions class can be debugged as usual. Corresponding client programs will acquire copies of the entire server .exe instead of just a .dll; that is the difference.

Saturday, December 29, 2007

 

Staying in Bounds

ASP.NET 2.0 translates its Label control to an HTML SPAN element enclosing the text. Although a Label provides Width and Height attributes, they are simply translated to corresponding sytle properties of the SPAN and in most browsers have no effect. Since the SPAN does not contain an HTML tag, nothing constrains the Label text to an HTML box. How can you make a Label stay within bounds?

Only by going a little outside the bounds of ASP.NET 2.0, as it turns out. For example, to constrain a Label to a single-line box, add a subsequent Label at the same top coordinate and at a left coordinate where the first Label should end, with Text=" " specified. Give both labels a style="white-space: nowrap" property, which is documented by MSDN under Web Development, not under .NET Development. Browser interpretation will overlay text of the first Label with the offset, blank text of the second Label, hiding text from the first Label that extends beyond the desired box boundaries.

Thursday, November 15, 2007

 

Clicking and Gambling

As Mose Allison might have said, clicking can be a risky business. Especially when you click() on an ASP.NET 2.0 Button control. See "Check, set, click, whirr," May 26, 2007.

Internet Explorer 6.0 will crash and burn if you do that inside the handler for an 'onclick' event. So tarry a bit, and keep customers happy. Just timeout:

    window.setTimeout(ClickButton, 50);

function ClickButton()
{
    var oButton =
     document.getElementById('<~% = ofButton.ClientID %~>');
    if (oButton != null)
         oButton.click();
}

[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

Sunday, October 07, 2007

 

Validators Lost

An obvious ASP.NET 2.0 wart is its poor documentation of validators. As previously noted, there is a March, 2002, background article for ASP.NET 1.0 validators, no longer distributed with MSDN. However, it has not been updated since 2002, and it fails to explain many validator behaviors.

For example, when you enclose validators in a DIV that is initially hidden, you will find they don't work. That is because ASP.NET 2.0 conveniently disables them for you. Validators undergo preprocessing by the JavaScript that ASP.NET 2.0 emits, at the time a page loads in a browser. Hidden validators are disabled then; ASP.NET designers apparently thought that would be helpful. However, if you make a DIV visible, they didn't follow through by enabling validators it may enclose.

To remedy the deficiency, enable validators in JavaScript. ASP.NET 2.0 provides a global JavaScript function to do that, invoked in the following example:

    var oValidator =
     document.getElementById('<~% = ofValidator.ClientID %~>');
    if (oValidator != null)
    {
        ValidatorEnable(oValidator, true);
        if (oValidator.style.display != 'none')
             oValidator.style.visibility = 'hidden';
    }

A wrinkle, as the example suggests, is that the Text message for a validator will be conveniently made visible when the validator is enabled. If that is not so convenient, then hide the message as shown.

[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

Wednesday, September 12, 2007

 

Attributes Lost

All ASP.NET 2.0 controls accept an arbitrary collection of string-named and string-valued attributes in an Attributes property, populated with server-side C# statements such as:
    ofCheckBox.Attributes["oDatum2"] = "23a";
    string sDatum2 = ofCheckBox.Attributes["oDatum2"];
This example assigns the value "23a" to an attribute named "oDatum2" for a control with ID="ofCheckBox" and then fetches the value assigned into a string. Undocumented by Microsoft is that assigning an attribute value quietly replaces any prior attribute value with the same name.

Sometimes Attributes items are used to add control-specific information sent to a browser, but they are also useful as memo fields and for communication with client-side JavaScript. Although not documented by Microsoft, values of Attributes items are maintained with control state. They do not depend on view state, and after a postback they can be retrieved from controls server-side even if using the Page EnableViewState="false" option.

Looked at client-side, controls may seem to lose attributes. Use the JavaScript debugger to examine one:
    var oCheckBox =
     document.getElementById('<~% = ofCheckBox.ClientID %~>');
When "ofCheckBox" is the ID for an ASP.NET 2.0 CheckBox, the JavaScript debugger will not show properties of oCheckBox corresponding to any Attributes assignments made server-side. The attributes are not lost, however. They are elsewhere.

Some ASP.NET 2.0 controls generate HTML composites; CheckBox is one of them. Following are the ways, undocumented by Microsoft, in which some of the common ASP.NET 2.0 controls are translated to HTML elements:
    Label -- span
    TextBox -- input
    RequiredFieldValidator -- span
    CheckBox -- span (label, input)
    RadioButton -- span (input, label)
    DropDownList -- select
    Button -- input

A CheckBox is translated to a span that contains a label and an input. Attribute items assigned to a composite control will be encoded as properties of the outermost HTML element. For a CheckBox, they will appear on the corresponding span. The ID for an ASP.NET 2.0 composite control, however, does not necessarily go to the outermost element. In particular, undocumented by Microsoft, it becomes the ClientID property of the input element for a CheckBox or a RadioButton.

In order to get the client-side value of an Attributes item named "oDatum2" that was assigned server-side to a CheckBox with ID="ofCheckBox" as ahown above, use a JavaScript statement like the following:
    var oDatum2 = oCheckBox.parentNode.oDatum2;
Communication using Attributes items is one-way. One can create attributes and assign values to attributes client-side, as JavaScript object properties, but new attributes and attribute values will not be transmitted to a server with postbacks.

[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

Tuesday, August 28, 2007

 

Passage to India

Despite company origins as a Basic compiler vendor, Microsoft has long shown an erratic, often indifferent attitude toward software tools. It dropped the ball in the mid-1980s and was scooped by Borland with C++. It dropped the ball again in the mid-1990s and was scooped by Sun with Java. After a surge of work in the early 2000s on the second (and the first successful) versions of C# and ASP.NET, it again shows signs of fatigue.

Key management roles have been turned over to sharp pencils, and they have outsourced support to India. While India certainly has many competent software engineers, those working on Microsoft tools support either do not have or are not allowed to use much skill, and they apparently lack close relationships with developers. Their working knowledge matches what appears in Microsoft documentation.

Microsoft support for software tools has become relatively expensive and also relatively ineffective in just those situations where it could matter the most. If one has a problem, as one frequently will, with software issues not explained or poorly explained by documentation, and if one has already studied the documentation, then there will be little help from Microsoft. Time and money will be wasted revisiting the documentation, and usually there will be nothing beyond it.

Keepers of the various Microsoft and Microsoft-oriented Web forums are similar. The latter often have business interests with Microsoft, and they will rarely venture beyond the company lines. Many forum-keepers appear to lack knowledge other than documentation, although some of them try to make up for that with arrogance. There are, fortunately, several book authors and Web logs, most independent from Microsoft, covering trouble spots. As a practical matter, they have become our key sources of support.

Tuesday, August 07, 2007

 

What were they thinking?

In winter, 2007, Microsoft provided Service Pack 1 for Visual Studio 2005. It arrived hobbled with an installer that could take as long as 6 hours to run, using up to 8 GB of system drive storage when the VS software was on some other drive. That put it far down the priority list.

Eventually, Service Pack 1 got installed in the hope of solutions to at least a few of the longstanding bugs and problems with VS 2005, including these:
* random process lockup with concurrent activities
* random errors with application domain unload
* random errors responding to view change keystrokes
* random errors with focus lost
* random errors with miscolored text
* random errors indenting inserted text
* design views unusable from clumsy formatting
* inability to generate resources if using includes
* lack of response during searches and builds
* hyperactive popup of error and status panes
* very slow searches across project files
* failure to maintain context of a project search
* missing resources not reported at build time
* JavaScript errors not reported at build time
* JavaScript breakpoint requires code insertion
* inability to compare files or file versions
* inability to search within Quick Watch panels
* only dynamic ListBox usable with AutoPostBack

A couple month's use shows not a single bug fixed or problem solved, certainly not the mother of all bugs. See "Cover your buttons," March 2, 2007. So what were they thinking?

Monday, June 04, 2007

 

Button validation and more

ASP.NET 2.0 introduced the ValidationGroup property associating validators with action controls. It is often used to make asp:Button controls activate a designated set of validators before a postback. Sometimes buttons also need to trigger other client-side work before a postback. For such purposes ASP.NET 2.0 introduced the OnClientClick property of asp:Button, specifying JavaScript to be executed before postback, which can return a boolean value to control whether or not postback occurs.

Useful features, except when you need them both. If you try that, you will find that your OnClientClick code runs but your validators do not. Microsoft does not give advice on how to handle this situation or even tell you that it happens. What is going on here is that the HTML produced to represent an asp:Button with validators has an onclick property specifying a function that runs the validators in the associated ValidationGroup. The OnClientClick property of asp:Button overrides that onclick HTML property.

You get both features to work together by activating the validators inside the JavaScript code specified by OnClientClick, using undocumented variables and functions as shown in the following partial example:


<~asp:Button ID="ofButtonTest" runat="server"
 OnClientClick="return ValidateAndDoWork('Submit')"
 OnClick="ButtonTest_Click" ValidationGroup="Submit" ... /~>


function ValidateAndDoWork(oValidationGroup)
{
    if (Page_Validators == null || ValidatorValidate == null
     || ValidatorUpdateIsValid == null || Page_BlockSubmit == null
     || ValidationSummaryOnSubmit == null || Page_IsValid == null
     || __defaultFired == null)
    {
        DoWork();
        return true;
    }
    var oValidatorCount = Page_Validators.length;
    for (var oValidatorIndex = 0;
     oValidatorIndex < oValidatorCount; ++oValidatorIndex)
        ValidatorValidate(Page_Validators[oValidatorIndex],
         oValidationGroup, null);
    ValidatorUpdateIsValid();
    ValidationSummaryOnSubmit(oValidationGroup);
    Page_BlockSubmit = !Page_IsValid;
    if (Page_IsValid)
        DoWork();
    else
        __defaultFired = false;
    return Page_IsValid;
}


function DoWork()
{
    ...
}


The ValidateAndDoWork function first executes all validators for a specified validation group. Note that the JavaScript specified for OnClientClick must return a value and must know, determine or receive as a function argument the name of the associated ValidationGroup. If validation passes, ValidateAndDoWork invokes DoWork to perform other client-side work before a postback and returns true. Otherwise ValidateAndDoWork returns false, preventing a postback.

ASP.NET client-side validation does not operate under FireFox, Mozilla and Netscape. Thanks to Tom Newman, posting on Scott Guthrie's Web log at http://weblogs.asp.net/scottgu/archive/2005/08/04/421647.aspx, who illustrated undocumented client-side variables and functions that work under ASP.NET 2.0 and are used here.

[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

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

Update, August 12, 2007

Page_Validators, ValidatorValidate, etc. are not entirely undocumented. Anthony Moore, Microsoft's Development Lead for .NET Framework and later for Base Class Libraries, wrote a helpful article on validation describing these elements at http://msdn2.microsoft.com/en-us/library/aa479045.aspx, last updated for ASP.NET 1.1 in March, 2002. This article has not been updated for ASP.NET 2.0 and is not distributed with the Visual Studio 2005 documentation.

Saturday, May 26, 2007

 

Check, set, click, whirr


A useful characteristic of most ASP.NET controls, not documented by Microsoft, is an ability to be addressed as ordinary HTML elements while also practicing all their usual ASP.NET behaviors. From JavaScript, for example, ASP.NET checkboxes can be checked, text fields can be filled, and pushbuttons can be pressed. Postbacks will respond in the same ways that they respond to equivalent user actions. Typical JavaScript looks like this:


    var oCheckbox = document.getElementById('<~% = ofCheckbox.ClientID ~>');
    if (oCheckbox != null)
        oCheckbox.checked = true;
 

     var oTextbox = document.getElementById('<~% = ofTextbox.ClientID ~>');
    if (oTextbox != null)
        oTextbox.value = 'Sample text string';


The <~% = ... %~> display blocks reference ID properties of asp.CheckBox and asp.TextBox markup elements, here illustrated as ofCheckbox and ofTextbox. They are needed for compatibility with master pages. See "Mastering and paging JavaScript: Solutions," June 1, 2006.


Hidden pushbuttons are handy for server-side interaction. Just add "display: none" to the Style attributes of an asp.Button element. Typical JavaScript looks like this:


    var oButton = document.getElementById('<~% = ofButton.ClientID %~>');
    if (oButton != null)
        oButton.click();


Hidden textboxes, checkboxes and other controls are an easy way to exchange context information with a server. This approach is simpler than using the also undocumented __doPostBack function sometimes recommended, which requires additional JavaScript enoding and server-side decoding. It minimizes exposure to proprietary features of the Microsoft environment.


[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

Friday, March 02, 2007

 

Cover your buttons

Microsoft users sometimes wait years for mistakes to be repaired. Such appears to be our fate with the "Hurry Up and Crash" bug that has plagued ASP.NET 2.0 since its release. A report of the bug appeared on Microsoft's Connect site three months after product release, followed by repeated comments from developers urging Microsoft to fix the problem.

The "Hurry Up and Crash" bug appears when a user of an ASP.NET 2.0 application clicks on a button or exercises another control "too soon." That crashes the application. If the user is lucky, a diagnostic appears saying, "Validation of viewstate MAC failed." Only users in Microsoft's UI development group are likely to understand. The cause of the problem is that ASP.NET 2.0 event validation may fail if a postback occurs before a page has been fully interpreted by Internet Explorer or by another browser that provides early activation of controls.

Developers were alarmed. This is a Class 1 bug threatening most ASP.NET 2.0 applications. It is unpredictable and also fatal. Forum pages buzzed with unhappiness, but Microsoft took a couple of months to say "Many thanks for your feedback. We are investigfating [sic]." The "Hurry Up and Crash" bug is so gross it seems hard to believe Microsoft testers missed it before product release. But maybe they didn't.

Finally, about a year after a report of the bug appeared on the Connect site, Microsoft offered its lordly opinion, a gem of sorts: "...we are being very strict about the number and scope of bugs that are fixed...we are not able to address it....Before starting work on the next full release of the .NET Framework, we will review the Connect data to help us identify and address the top customer reported issues." So there we have it. A bug that can crash almost any application -- maybe it will be fixed some time in 2009 or 2010. Maybe not; this bug might never make the cut.

The answer, of course, is to cover your buttons. Since the release of ASP.NET 2.0 a new Web site paradigm has begun to appear: the page with a curtain of decoration that vanishes to reveal content. A less drastic approach is to specify every potentially troublesome control with visibility hidden. Then at the end of page markup add JavaScript to make them visible. By the time a browser reaches that point it will (probably) have interpreted Microsoft's critical elements and can do postbacks correctly.

Monday, February 19, 2007

 

Debug-heaven in Server 2003 Web Edition

Users of Visual Studio 2005 and Internet Information Services 6.0 will probably discover that the Visual Studio tools cannot be used to build a Web site via a network link to Windows Server 2003 Web Edition, because these tools require more network connections than the Web Edition server will provide. At first glance this limitation appears to prevent remote debugging of an ASP.NET 2.0 Web Forms application that is running on a Web Edition server under IIS 6.0.

However, Microsoft provides Visual Web Developer 2005 Express as a free download. Installing this tool on the server and building on the server, with debug enabled, generates a Web site that can be opened via a network share in Visual Studio 2005 Professional or Team Edition. The Express tool is also useful to verify that a site will behave properly when it runs in a production user account environment.

Visual Studio 2005 Professional or Team Edition can use the Visual Studio 2005 Remote Debugger service installed with the Express tool, attaching an IIS 6.0 process. A developer will then be able to set breakpoints and debug code normally. Running under IIS 6.0, properties of the Environment class may be different from those found when running under a built-in Visual tool debugging server. When a rebuild is needed, the Express tool must be used to do it, and the site should not be open in both Express and Pro tools at the same time.

To use this approach, create Administrator accounts with the same name and password on both the development machine and the server. Also visit the Visual Studio 2005 Remote Debugger service properties, enable and start the service, and specify the developer's user account under the Log On tab. While debugging under IIS 6.0, the worker process is stopped whenever the code is on break. That causes any other session or Web site in the same IIS 6.0 application pool to stop.

Wednesday, January 17, 2007

 

C# as evolution

When introduced with trumpets and fanfares, Microsoft's C# language provided little beyond the C++ language, for which Microsoft already offered a widely used tool set, that was not also provided by the Java language. Unlike the extensive, collaborative and sometimes public efforts in which Bjarne Stroustrup designed C++ and James Gosling designed Java, the design of C# was a mostly private process. Initially rather constricted and viewed by some as a crippled C++, the C# language was revised in a fall, 2005, release that improved its capabilities.

While C++ and Java were each major innovations, C# has an industrial tone. It is mostly a language for applications rather than systems. It disfavors pointers, which can crash applications but are often necessary for systems. As did Java, it revives from classic application languages like PL/I constructs such as built-in strings, arrays and streams that may be misfits or excess baggage in systems. Thread management is provided by built-in constructs similar to Java, while the ability to pass function arguments by reference rejects Java's limitations. The native class library was strengthened in the second edition with type-safe "generic" classes, but collections classes remain scattershot as compared with the C++ template library.

C# is performance-oriented. Although it allows copy-constructors, it rejects the C++ convention of using them for collection objects, gaining run-time performance at a potential cost in robustness. Passing function arguments by reference likewise trades performance against robustness, protected to some extent by requiring matching "ref" or "out" attributes in function declarations and references. Unlike Java, C# retains the "enum" and the "struct" from C++, originally taken from C in round-about inheritance from Pascal, PL/I and COBOL. In C#, however, the "struct" is distinct from the class, more efficiently allocated and accessed and providing no inheritance. Once again the main benefit is performance.

A key differentiator for C#, not apparent from syntax, is strong typing when implementing its type-safe classes, improving performance as compared to Java and preventing run-time errors as compared to C++. Distinctive features of C# syntax are built-in constructs for events and delegates, main ingredients of ASP.NET applications, and for class properties. In a similar spirit of automation its development environment automatically prepares declarations for and extracts them from compiled assemblies, eliminating "header" files of declarations.

Perhaps as a marketing ploy, Microsoft has played up what it calls "innovations" in C#, but nearly all are new words or new implementations for well-established concepts. The "managed code" concept is a good (and key) example; its roots are in the automatic bounds checking of PL/I and other classic procedural languages. The Ada language remains a reference standard for managed environments, with rigorous component definition, type safety, bounds checking, automatic garbage collection and exception handling. C# joins these concepts with a concise syntax and an orientation to high-performance, event-driven applications.

The future development of C# is reported to be integration with database access, currently supported by class libraries. That would be a risk-laden approach for Microsoft, likely to be seen as a venture to build out a monopoly for its SQL Server database products. Signals as to Microsoft's intents, if such a road is taken, will be whether or not unique features or syntax of SQL Server become imbedded in C# language constructs.

Monday, January 08, 2007

 

Who's on first? An IIS identity crisis

Microsoft Internet Information Services (IIS) version 6.0 introduced essential capabilities for managing Web page services to the Windows environment, including process pools, caching and server farms. Along with that came several more security features, including detailed control of the privileges under which Web page services execute on a server.

IIS 6.0 includes several protentially conflicting ways to specify privileges, yet Microsoft provides very little information about how they interact. The most obvious of these potential conflicts is specifying a user account for a Web site and also specifying a user account for the process pool in which the site's services execute. When they are not the same, who's on first? What happens?

The Web site's user account is specified in the IIS Manager tool under the properties "Directory Security," "Authentication and access control (Edit)," "Enable anonymous access." The process pool's user account is specified in IIS Manager under the properties "Identity," "Configurable." Both affect documented fields in the XML metabase of IIS, but undocumented is what will happen when the specified user accounts are not the same. Other, largely undocumented complications are "special privileges" and the default user accounts similarly specified for all Web sites and all process pools.

At present most of this remains a mystery to be disentangled mainly through experimental programming. The few Internet forums and Web logs that touch on these topics reveal a general lack of knowledge. The only significant book on IIS 6.0, by Mitch Tulloch (Osborne, 2003), provides no more help than one can get on-line from Microsoft. At the "deliberate speed" with which Microsoft provides documentation, the next version of IIS is likely to be available before the current one has been explained.

Tuesday, January 02, 2007

 

Postback grief: some causes and a cure

Validity checking of postbacks in ASP.NET 2.0 is a useful feature, but it can fail, generating mysterious messages that may begin, "Exception reported in __Page view...Invalid postback or callback argument...." One may also see, "Invalid postback or callback argument...Description: An unhandled exception occurred...." The messages go on with paragraphs that are likely to confuse a software developer, let alone a hapless user.

Many problems result from Internet Explorer or other browsers responding to user actions before a page has been completely constructed. An early postback may lack necessary information from controls that have not yet been fully processed by a browser. That is likely to happen when a page contains a grid or other bulky data or when it uses third-party controls that construct themselves on a client through JavaScript. In its designs for ASP.NET 2.0 and Internet Explorer, Microsoft evidently failed to anticipate such synchronization errors, and its marketing and support cheerleaders do not candidly acknowledge them either.

Validity checking can be disabled for all pages in web.config:

    <~system.web~>
        <~pages buffer="true" validateRequest="true"
          enableEventValidation="false" /~>
    <~/system.web~>


Validity checking can be disabled for individual pages by adding markup attributes ValidateRequest="false" and EnableEventValidation="false" to Page directives. Although sometimes recommended by novices, these are usually poor approaches, since they will open major security holes in applications. Yet how else to prevent users from tripping across ugly error boxes? Microsoft does not advise, and neither do makers of third-party controls such as Telerik.

Many such problems can be solved by strategic testing and design. Pages that contain bulky data or self-constructing controls must be tested aggressively to see whether early user action can cause errors. If such errors can occur, then controls that would provoke them need to be kept hidden until a page's controls have all been processed by a browser. The trouble-provoking controls are often buttons, but any control that is configured to generate a postback or an AJAX callback may be the source of a problem.

To hide a control, in markup add the option "visibility: hidden" to its Style attribute. At the end of the markup for a page, add a JavaScript block such as:

    <~script type="text/javascript"~>
        var oControl = document.getElementById
         ('<~% = ofControl.ClientID %~>');
        if (oControl != null)
        {
            oControl.style.visibility = 'visible';
        }
    <~/script~>


In the example, ofControl matches the ID attribute of a control that can trigger a postback and is initially hidden. The JavaScript block makes these controls visible. By the time it does so a browser will have processed a page's controls, and they should be prepared to participate correctly in a postback.

[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

Tuesday, December 05, 2006

 

Web delivery and its discontents

Promises of Web delivery of applications have been circulating for years, but until ASP.NET 2.0 and its tools the technologies could rarely produce a credible and maintainable product. Microsoft has not been eating its own cooking from this kitchen. None of its major applications has been reimplemented for Web delivery. Lack of responsiveness comparable to client-based and client-server applications may have provoked the troubled "Atlas" effort, which at this juncture looks to be all hat and no cattle.

In favorable circumstances, AJAX technology can remedy the lack of responsiveness from Web delivery. There are two proven success cases: point update of a limited display from a large backing store, and continuous update of display borders to produce smooth scroll. However, AJAX is uncomfortable for software writers, because it disrupts orderly processing to gain performance.

Some vendors have been grandly promoting AJAX "frameworks" that more nearly resemble plaster patches for bunions. These home remedies all aim to reduce the amount of cookbook coding to process AJAX callbacks, but some, including Microsoft's and Telerik's, also try to relieve discomfort by providing orderly processing.

Results show one can't have it both ways. We recall "cold fusion," "artificial intelligence" and their great ancestor "perpetual motion." The overhead of orderly processing sacrifices the performance potential and yields nothing that anyone can take to the bank.

Monday, December 04, 2006

 

Quo vadis server transfer?

Nearly all professional quality Web Forms applications use Server.Transfer to switch pages rather than the easily hijacked cross-page posting approach. But when running under IIS in a virtual directory, where does Server.Transfer transfer?

With IIS 6.0, Server.Transfer stays within the same virtual directory if there is no directory component to the page path. A directory component may specify either a different virtual directory or a subdirectory. For an application contained within a single directory, only the original URL accessing the first page specifies the virtual directory.

Wednesday, November 22, 2006

 

Debug ASP.NET 2.0 running under IIS 6.0

Visual Studio 2005 Professional or Team Edition can perform remote debugging of an ASP.NET 2.0 Web Forms application running in its prime deployment environment, Internet Information Services (IIS) 6.0 on a Windows Server 2003 R2 platform. However, sloppy Microsoft technical writers and their dimwitted managers have made it a labor of experimental programming to find out how to do this. Their documentation on the topic is scattered across many articles and notes, omitting critical information that may be known to some Microsoft insiders but will be neither known nor obvious to experienced application developers and system administrators. We found that an MSDN support engineer could not explain how to do remote debugging under IIS 6.0 and that so-called "experts" from the MSDN Visual Studio Debugger Forum could not explain it either.

A key element is that to debug remotely the server running IIS 6.0 and the development machine running Visual Studio 2005 (Pro or Team) must have Administrator accounts with the same user name and password. The Visual Studio Remote Debugger must be started on the server or run as a service under this user name and password, and the user at the development machine must be logged in under the same user name and password. Otherwise Visual Studio 2005 will fail to connect with the server. Some Microsoft documentation claims that the server must be operating in a network domain, but that is not necessary. A domain is a convenient way to distribute account identification, but the server can also be in a workgroup. Visual Studio Remote Debugger must be installed on the server directly from Visual Studio 2005 Pro or Team media, either CD or DVD. Some Microsoft documentation claims it can be installed from a development computer, but that is not true with any standard installation of Visual Studio 2005.

Installation of Visual Studio Remote Debugger is provided by rdbgsetup.exe, located on the CD "Disc 2" in the Remote Debugger directory and on the DVD in the vs\Remote Debugger directory and in the x86, x64 or IA64 subdirectory that matches the server processor. An installation wizard provides an option to install a service. With that approach, it is necessary to visit the Visual Studio Remote Debugger service from Control Panel/Administrative Tools/Services and bring up a context menu with the alternate mouse button to access its Property page. On the Property page under the Log On tab enter the key user name and password, and under the General tab change Start Up from Disabled to Manual or Automatic. After exiting the Property page start the server from the context menu. If not installed as a service in this way, the Visual Studio Remote Debugger must be started prior to debugging by logging on the server under the key user name and password and launching msvsmon.exe from directory \Program Files\Microsoft Visual Studio 8\Common7\IDE\Remote Debugger on the system drive and the subdirectory that matches the server processor.

To debug an application running under IIS 6.0, source code must be placed on the server. A published build with source code only on the development machine but not on the server will not debug. By using a server share, Visual Studio 2005 can access an application Web site’s directories on the server and will build the application. A permission error that it may report at the end of the build can be ignored. Before building, the Debug option must be included in the web.config file for the application. Before debugging, an option in the IIS properties for the application must also be enabled. This is found by visiting Control Panel/Administrative Services/Internet Information Services, expanding Web Sites, selecting the application and then selecting Properties from a context menu. Under the ASP.NET tab, the Edit Configuration button brings up an ASP.NET Configuration Settings dialog. Under the Application tab of this dialog the Enable Debugging option must be checked.

Before trying to debug, make sure that the application pool and Web site are running by looking on the server using Control Panel/Administrative Services/Internet Information Services and checking the status shown when selecting Application Pools and Web Sites. If either is not running, select it and start it from a context menu. Using Visual Studio 2005 on a development machine, bring up the source code for the application that is located on the server. In this view of the application, breakpoints can be set in the code. Attach Visual Studio 2005 to the IIS 6.0 worker process, using Debug/Attach to Process. The appropriate Transport option is Default, and the Qualifier option is the network name of the server. The IIS 6.0 worker process under Available Processes will have the name w3wp.exe. Select this process and click the Attach button. Launch the application from the development machine by pointing Internet Explorer to the Web site, using its network address. Visual Studio 2005 does not do this automatically. When the code reaches a breakpoint, Visual Studio 2005 will show the same debugging display that it does when using its built-in server and will step through the code and show watchpoint information in the same ways.

While debugging under IIS 6.0, the worker process is stopped whenever the code is on break. That causes any other session or Web site in the same IIS 6.0 application pool to stop. For this reason, it is best to perform remote debugging with a dedicated test server, if available, or to set up a separate application pool for testing.

Sunday, September 03, 2006

 

Conveniences of controls

As one of its undisclosed "features" ASP.NET 2.0 will conveniently inhibit control events when you try to use "too many" of them. For example, suppose you need to confirm a user action and also validate that the user has specified an action to take. You might think you could use a standard approach -- a JavaScript function for OnClientClick with a Button and a RequiredFieldValidator in the < body > section of your .aspx file:

    < script type="text/javascript" >
    function ConfirmActivate()
    {
        var bConfirm = confirm('Confirm Activate');
        return bConfirm;
    }
    < /script >
    
    < asp:Button ID="ofButtonActivate" runat="server"
     Style="left: 8px; position: absolute; top: 24px;"
     Text="Activate" Width="176px" OnClick="Activate_Click"
     ValidationGroup="Test" Font-Size="Large"
     OnClientClick="return ConfirmActivate()" / >
    
    < Asp:TextBox ID="ofTextActivate" runat="server"
     Style="left: 200px; position: absolute; top: 24px"
     Height="24px" Width="600px" / >
    
    < asp:RequiredFieldValidator ID="ofValidActivate"
     runat="server" ControlToValidate="ofTextActivate"
     ErrorMessage="Selection required" ValidationGroup="Test" / >
    
    < asp:ValidationSummary ID="ofValidSummary" runat="server"
     ShowSummary="False" ShowMessageBox="True"
     ValidationGroup="Test" / >


Apparently Microsoft doesn't think your users should be bothered with "too many" messages, so ASP.NET 2.0 will conveniently inhibit the ValidationSummary, and no popup message will appear. One solution is to take out the validators and provide your own validation in JavaScript, for example:

    function ConfirmActivate()
    {
        var oTextActivate = document.
         getElementById('< % = ofTextActivate.ClientID % >');
        var oText = oTextActivate.value;
        if (oText == null || oText.length < 1)
        {
            alert('Activation name required');
            return false;
        }
        var bCancel = confirm('Please confirm Activate');
        return bCancel;
    }


[Note: because of display limitations, characters "< " and " >" here are shown with a space after or before them.]

Saturday, August 19, 2006

 

Reference to the wise

A Russian-born friend, now a U.S. citizen, has a watchword for software systems. "The most important things," he says, "are never written down."

Hardly a better example can be found than documentation of the ASP.NET 2.0 collection classes. Many such classes, including all the hobbled C++ imitators that Microsoft calls "generic collections" in C#, accept objects -- or rather, references to objects. What they collect is merely the references. This probably seemed so obvious to Microsoft writers that they did not think to mention it, but neither did the redoubtable Herb Schildt, in his book on C# 2.0 laughingly labeled "The Complete Reference."

Although it is possible to write copy-constructors in C# just as one usually does in C++, Microsoft does not provide default copy-constructors or use copy-constructors in its ASP.NET interfaces. Omission of copy-constructors as a routine practice makes it tricky to produce collections like those of C++ providing copy-semantics. Microsoft does not seem to have tried. Its practices extend to the collections implemented for visual controls such as TemplateGroupCollection, yielding interesting consequences.

Experiments suggest that all ASP.NET collections accepting reference types will accept duplicate references without an exception and are unprotected against changes to the referenced objects after they have been added. In some situations, such as user interfaces, effects of adding duplicate references or changing an object after a reference to it has been added can depend on timing and may be unpredictable.

In all but special circumstances, therefore, rules of thumb for peaceful coexistence with ASP.NET 2.0 collections have to be: add only unique objects, and don't change objects after adding them to these collections.

Thursday, July 27, 2006

 

Panel Dynamics

Browser renderings of data and controls can produce dynamic displays that traditional client-side applications could only hope for. Particularly useful is a scrollable collection of controls, including ones such as buttons that produce actions. In a sterling example of cuteness and opacity, Microsoft's ASP.NET 2.0 documentation hints that creating such a display might be possible but won't tell you how to do it. Neither will the usually helpful Dino Esposito, although his book "Programming ASP.NET 2.0 Core Reference" does have a sentence explaining that dynamic controls must be regenerated each time a page loads (see page 113).

Microsoft recommends its PlaceHolder as a container for dynamic controls, but that is a dead object, offering no mobility. Much more useful is Panel, which can provide both horizontal and vertical scrolling. Action controls inside a Panel operate normally as long as they are visible, no matter where they may be moved on the display.

Each action control has one or more collections of event handlers. Button has Click, CheckBox has CheckChanged, and so on. Server-side event handler functions are connected to the corresponding events by adding a delegate in C#. To get the events from dynamic controls, the code to create the controls and specify their event handler functions can be placed in the Page_Load function and must execute every time. For example, using a statically declared Panel:

In the .aspx markup --
    < asp:Panel id="ofPanelView" runat="server" >

In the .aspx.cs Page_Load function --
    ControlCollection oControls = ofPanelView.Controls;
    oControls.Clear();
    Button oSpecialButton1 = new Button();
    oSpecialButton1.CommandName = "1";
    oSpecialButton1.Click += new System.EventHandler(SpecialButton_Click);
    oControls.Add(oSpecialButton1);
    Button oSpecialButton2 = new Button();
    oSpecialButton2.CommandName = "2";
    oSpecialButton2.Click += new System.EventHandler(SpecialButton_Click);
    oControls.Add(oSpecialButton2);

Elsewhere in the .aspx.cs code --
    SpecialButton_Click(object sender, EventArgs e)
    {   string sCommandName = ((Button)sender).CommandName;
        . . .

If you will be changing a pattern of dynamic controls for a page, then you must also assign the .ID property of each control and do so in a consistent way, so that on postback values and events will be paired with corresponding server-side objects. If a pattern of dynamic controls does not change, default ID values will do the job.


[Note: because of display limitations, characters "< " and " >" here are shown with a space after or before them.]

Tuesday, July 04, 2006

 

DIV and conquer

ASP.NET 2.0 becomes most unfriendly to JavaScript when you want to change the visibility of Web form controls. Documentation won't explain how to do this, but it's simple. Set style.visibility to "visible" or "hidden" for most browsers. Problems arise when you want to change controls such as CheckBox or RadioButton that generate multiple elements, an input and a label in these cases. Addressing a control in the usual way affects only one element. In these cases, it does not change the label.

The solution is to put an HTML DIV element around one or more controls that you want to change, such as:

< div id="ofDivCheck23" style="visibility: hidden" runat="server" >
    < asp:CheckBox ID="ofCheck23" runat="server" Text="Report Required" / >
< /div >

Be sure to put "id" in lower case, or Visual Studio 2005 will bark at you with a diagnostic such as "Error...name contains uppercase characters, which is not allowed," a rule routinely violated by its own markups.

With such a div element in place, show or hide everything that it brackets with JavaScript statements such as:

var oDivCheck23 = document.getElementById('< % = ofDivCheck23.ClientID % >');
var oCheckEnabled = document.getElementById('< % = ofCheckEnabled.ClientID % >');
if (oCheckEnabled.checked == true)
{
    oDivCheck23.style.visibility = "visible";
}
else
{
    oDivCheck23.style.visibility = "hidden";
}

[Note: because of display limitations, characters "< " and " >" here are shown with a space after or before them.]

Thursday, June 29, 2006

 

Say it in Chinese

Expressing binary information by using text characters has always been wasteful. The simplest solutions use octal or hexadecimal representations, and they waste half or more of storage space or transmission bandwidth. In a Unicode environment like that provided by ASP.NET 2.0, the bloat is much worse.

A Unicode environment, however, permits a safe and relatively efficient alternative, using the segment of the 16-bit code points occupied by the "Unihan" characters. Set the high-order two bits of each character to 01, and use the low-order fourteen bits for binary data. Data expressed in this way will populate 16K of the 27K code points assigned by 16-bit Unicode to Chinese characters. Data bit efficiency is 87.5 percent.

Thursday, June 22, 2006

 

Lightly crossing pages

The Server.Transfer method is an efficient way to transition between pages under server control, but it transmits no parameters to specify a context. There are indirect ways to do this with added costs in performance and data management. However, the C# language provides data transfer with no added programming or overhead, provided the destination page knows the type of the origin page. Simply declare and assign public members in the origin page and use their values in the destination page. This is made possible by the PreviousPage property of the Page class, the strong typing of C# and the run-time type identification system.

In a Login page, for example, declare a member such as m_ilUserTag to identify a validated user:

public partial class Login : System.Web.UI.Page
{
    public long m_ilUserTag = 0;

Once a user is validated, assign an identifier value to m_ilUserTag and transfer to the Home page:

        Server.Transfer("Home.aspx");

The Home page can pick up the value of m_ilUserTag directly. For example:

    protected void Page_Load(object sender, EventArgs e)
    {
        object oObject = (object)PreviousPage;
        Login oLoginPage = (Login)oObject;
        long ilUserTag = oLoginPage.m_ilUserTag;

Although you might think that the reference to the Page base class furnished by PreviousPage could be cast immediately to the Login derived class, Visual Studio 2005 will bark at that with a diagnostic such as "Error...Cannot convert type 'System.Web.UI.Page' to 'System.Web.UI.WebControls.Login'." If you pass the reference through the type, object, it's happy. RTTI will check validity of the cast and throw an exception if PreviousPage did not reference a Login object.

Sunday, June 18, 2006

 

Events in the life of a page

In its cute fashion, Microsoft discourages application programmers from combining JavaScript with most ASP.NET programming, while making frequent use of the very same JavaScript in the HTML markups that it renders for ASP.NET pages, as a look at the source for many such pages will show. Microsoft does not worry itself with telling you about attributes available for controls in the Source (or markup) view of .aspx files. What you get from them is just a description of server-side classes. Of course Microsoft has all this knowledge well organized and documented. They just don't let you know. If you use the < asp: > elements on a page, as you nearly must to take best advantage of the tools, how do you respond to events on the client side?

The quick answer is that you put them in, just as though you were writing HTML as you otherwise might. For example:

    < asp:DropDownList ID="ofTextHour" runat="server" OnChange="TimeExtender()" >

The tools will bark at you, "Warning...Validation (ASP.Net): Attribute 'OnChange' is not a valid attribute of element 'DropDownList'." However, they will copy what you gave them to the HTML markup they produce, resulting in:

    < select name="ofTextHour" id="ofTextHour" OnChange="TimeExtender()" >

When you add a corresponding JavaScript function to the .aspx markup, it will be called as you would expect on a change to the selection for the ofTextHour element.

Microsoft's official approach to supporting client-side events, as one might expect, requires server-side code. Each control has a server-side Attributes property that is an AttributeCollection object with an Add function to add pairs of strings. For the example shown, the code usually placed in Page_Load() would be:

    ofTextHour.Attributes.Add("OnChange", "TimeExtender()");

Of course this breaks Microsoft's widely touted "declarative" paradigm. If you take the official approach, you can't see all the attributes of a control in one place. Some server properties are supported in markup, however. This one might have been:

    < Attributes >
        < Add OnChange="TimeExtender()" / >
    < /Attributes >

Not so, unfortunately, and the above markup will stop the build.


[Note: because of display limitations, characters "< " and " >" here are shown with a space after or before them.]

Thursday, June 15, 2006

 

Comments in a skin file

Skin files, introduced in ASP.NET 2.0, make it easier to specify an application's look and feel. Commercial developers will want to add a comment with their copyright notice, but Microsoft does not explain how to put a comment in a skin file. In his book "Programming ASP.NET 2.0 Core Reference," Dino Esposito shows skin file content with an HTML comment, but that will cause a build failure in the release version of Visual Studio 2005.

Although a skin file looks like .aspx markup, it is highly restricted. However, it does recognize ASP code blocks and will accept code block comments, such as this example:

< %-- Copyright 2006 XYZ Corp. All rights reserved. --% >

[Note: because of display limitations, characters "< " and " >" here are shown with a space after or before them.]

Wednesday, June 14, 2006

 

Setting a default button with default input focus

Microsoft has not documented how to set a default button through ASP.NET 2.0 when a page also has data entry elements that need a default focus. It's easy, though. Before rendering the page, set focus on the default button, then on the default data entry element. For example, in the Page_Load function:

    ofButtonLogin.Focus();
    ofTextUserName.Focus();

To change the default button, depending on which data entry element has focus, the easiest way is JavaScript. See Darrell Norton's approach at http://codebetter.com/blogs/darrell.norton/archive/2004/03/03/8374.aspx.

Thursday, June 01, 2006

 

Mastering and paging JavaScript: Problems

ASP.NET 2.0 introduced the potentially useful master and content pages, but with them it also introduced new problems for client-side scripts. The ID of each content page element is extended and qualified in one of Microsoft's obscurely documented conventions. Client-side scripts that try to use the ID values that a programmer specified will fail.

Microsoft usually gives client-side scripts a pass; ASP.NET 2.0 was designed to promote and sell server software. Client-side scripts have the added demerits, in Microsoft's eyes, of using JavaScript, a language designed by a competitor, or ECMAscript, an international standard. Their document purgers, currently in ascendance with VS (Visual Studio), have removed from distributed "Help" documents several articles and sections of articles that might reveal the client-side problems and assist in solving them. However, many articles remain available in libraries distributed by MSDN (Microsoft Developer Network) and displayed at Microsoft's web sites. Key sections of documentation are the topics "Web Server Controls," "ASP.NET Programming" and "ASP.NET Controls" under "Visual Web Developer" in the VS library and the topic "ASP.NET Server Controls" under "Creating Web Applications and Services" in the MSDN library.

The root of the client-side problems with master pages is name qualification, which occurs when a control is located within the scope of another control that acts as a "naming container." Naming containers are largely of interest with controls that generate collections of other controls--any templated control and all standard controls that use templates: Repeater, DataGrid, DataList, CheckBoxList, ChangePassword, LoginView, Menu, SiteMapNodeItem and RadioButtonList. See the articles "Web Forms Control Identification," available in both VS and MSDN, and "Referencing Controls in Web Forms Pages," available only in MSDN. The article "Client Script in ASP.NET Web Pages" says that the ID values of controls in the scope of a naming container will be extended and qualified so that they are unique on a page, but it does not say how this is done. The article warns against using the extended and qualified names in client-side script: "The formula used to generate unique IDs for child controls can change."

A master page is associated with the MasterPage class, which is derived from the UserControl class and therefore implements the INamingContainer Interface. ID values of all controls in the scope of a master page will be extended and qualified--including everything on associated content pages. The fact that a master page acts as a naming container is referred to obliquely in just a single "Visual Web Developer" article, "How to: Reference ASP.NET Master Page Content." This article says that to identify a control located on a master page, one needs "the FindControl method, using the value returned by the Master property as the naming container." An October, 2003, article from MSDN, "Web Site Consistency Made Easy with ASP.NET 2.0 Master Pages," says, "Each instance of System.Web.UI.ContentPlaceHolder within a Master Page acts as a naming container."

Most client-side scripts used with master and content pages must somehow deal with controls on their pages, but Microsoft provides no guidance for how to do this. Its documentation addresses only issues of how to identify controls in server-side code. There is no equivalent to the FindControl method available to a client-side script, yet Microsoft has been aware since at least 2003 that its master-content architecture would be incompatible with client-side scripts.

 

Mastering and paging JavaScript: Solutions

After uncovering problems with JavaScript in master and content pages, some software developers found solutions using ASP code blocks, a technology that ASP.NET 2.0 carried from the original 1996 ASP (Active Server Pages) but now documents only sparsely in the VS article "Embedded Code Blocks in ASP.NET Web Pages." A more thorough discussion can be found in the January, 2002, article "How to Upgrade ASP Pages to ASP.NET Pages that Use JScript .NET" supplied by MSDN. These articles describe a "display block" with the < % = ... % > format. The content of such a block is compiled and executed by the page server at runtime; the entire block is replaced by the result when a page is rendered. If it references a page element's ID value, then the rendered page will contain a runtime control's ID value that may have been extended and qualified.

ASP display blocks obtain correct control ID values for JavaScript with statements such as:

var oControl1 = document.getElementById('< % = ofControl1.ClientID % >');

where ofControl1 is the ID value given by a programmer to a control on the page. The server-side run-time evaluation of ofControl1.ClientID returns an extended and qualified name replacing the ASP code block, and the JavaScript variable oControl1 can then be used for access to properties of that control.

A limitation to this approach was soon discovered. Once a master page header has taken code blocks, it refuses to accept dynamic scripts. However, dynamic scripts will be accepted for content pages. Another limitation is that the run-time processing provided for scripts imbedded in .aspx page files is not provided for dynamic scripts. Dynamic scripts must be created using the FindControl method in server-side code to generate correct ID values for controls.

The run-time processing to obtain correct control ID values will be provided for scripts included from files. The .aspx page file that is to contain the JavaScript needs a < script > element such as:


    < script type="text/javascript" >
    < !--#include file='~\JavaScript\ControlValidator.js'-- >
    < /script >


The included file, called JavaScript\ControlValidator.js here, contains a JavaScript program such as:


function ValidateControl(source, arguments)
{
    var oRadioControl1 = document.getElementById('< % = ofRadioControl1.ClientID % >');
    var oRadioControl2 = document.getElementById('< % = ofRadioControl2.ClientID % >');
    arguments.IsValid = (oRadioControl1.checked | | oRadioControl2.checked);
}


A minor problem with this approach to using JavaScript is its incompatibility with Microsoft's clumsy Design mode editor. If you imbed Javascript as shown and you try to switch to Design mode, Visual Studio 2005 will bark at you with a diagnostic such as "Error Creating Control...Object reference not set to an instance of an object." On those rare occasions when you need to use Design mode, the cure is to cut the Javascript out of the markup and paste it into a plaintext editor such Notepad, then when done paste it back into the markup file.

[Note: because of display limitations, characters "< " and " |" and " >" here are shown with a space after or before them.]

Sunday, April 02, 2006

 

The search for intelligent C# containers

The well crafted C++ associative containers are missing in C#. While Microsoft introduced "generic collections" with ASP.NET 2.0, providing configurable element types, capabilities of these Microsoft C# classes are limited.

For example, the C# SortedDictionary class is superficially similar to the C++ map class. However, SortedDictionary methods lack equivalents to the C++ upper_bound and lower_bound; they are only able to identify an exact key match. Unlike the bidirectional iterators of a C++ map, C# is unable to traverse SortedDictionary nodes in the vicinity of an identified node. Its only enumeration passes through the entire collection.

Relief is available at a price. For a few hundred U.S. dollars, Recursion Software sells a "C# ToolKit" component package including an OrderedMap container functionally equivalent to the C++ map. Recursion's containers do not use templated declarations, so they are not type-safe like the C++ containers and the C# "generic collections."

One can wrap Recursion Software containers in C# "generic classes" and write type-safe C# code with them. As of early 2006 this appears to be the best solution. Unfortunately there are far fewer components available for C# than in the more established Java, C++ and Visual Basic markets.

Note: Recursion Software updated its C# Toolkit in March, 2006. With the update its OrderedMap and OrderedSet classes provide O(log n) performance on all basic operations. These classes support either unique or multiple occurrence keys and are now equivalent in performance to the C++ map, multimap, set and multiset.

Tuesday, March 28, 2006

 

C# makes a struct invalid by default

C# creates invalid structures when there are array members, as illustrated in the following example:

    class Program
    {
        public struct Problems
        {
            public int[] iArray;
        }
        static void Main(string[] args)
        {
            Problems oProblems = new Problems();
            Console.WriteLine("Length = {0}",
             oProblems.iArray.Length);
        }
    }

This example will build but fail when run. The default constructor for Problems does not initialize iArray. It is impossible to remedy the situation by writing a default constructor, because C# insists on providing the default constructor of a struct automatically.

A workaround is to provide any struct that contains an array with an initializing method, as in the following:

    class Program
    {
        public struct Problems
        {
            public int[] iArray;
            public void Initialize()
            {
                iArray = new int[0];
            }
        }
        static void Main(string[] args)
        {
            Problems oProblems = new Problems();
            oProblems.Initialize();
            Console.WriteLine("Length = {0}",
             oProblems.iArray.Length);
        }
    }

Neither Microsoft's opaque C# documentation nor Herb Schildt's generally useful book, "C# 2.0 The Complete Reference," describe this problem or explain that a struct may include methods. The workaround is not really satisfactory, because programmers must remember to invoke initializing methods or otherwise initialize array members of a struct.

Monday, March 20, 2006

 

Proximity and "fuzzy" searching

As of 2006, the ASP.NET framework does not support proximity or fuzzy search, although Microsoft does provide these capabilities in SQL Server and Office automation. For data that can be characterized phonetically, Adam Nelson has released a codeset, available at http://www.codeproject.com/csharp/dmetaphone5.asp, implementing the Double Metaphone algorithm developed by Lawrence Phillips. This algorithm is most effective for English.

Wednesday, March 08, 2006

 

Reset? Reset!

Herb Schildt's book "C# 2.0 The Complete Reference" claims that C# enumerators have a Reset() method that restores the inital state created by a GetEnumerator() method on an enumerable object (pp. 737 and 765).

Great idea, Herb! Do tell Microsoft about it, too, won't you? When you try this method for enumerators over generic collections, you will be surprised. It's not there.

Most enumerators, like those for arrays, have a Reset() method. However, for generic collections Microsoft chose not to implement the usual IEnumerator interface, instead implementing the "IEnumerator Generic" interface, which lacks a Reset() method. ASP.NET 2.0 documentation is otherwise opaque. The Reset() method does not depend on data types of a collection and could easily have been implemented.

The workaround is to execute the required GetEnumerator method again. That will require making the source object accessible when otherwise it would not have been needed.

 

Subarrays have no C# value

The multidimensional arrays of C# are supposed to be reference types but behave more like value types. In ASP.NET 2.0, C# is unable to generate subarrays, as in the following example:

int[,] iaValues = { {1, 2}, {3, 4} };
foreach (int iElement in iaValues)
. . .

An attempt to specify "int[] iaSubarray" instead of "int iElement" will fail with a compiler diagnostic. This burdens the programmer to subdivide the array.

As often happens, Microsoft ASP.NET 2.0 documentation is opaque on the order of elements delivered by "foreach" or by an Enumerator. Experimental programming shows that delivery is with last subscript varying most rapidly, first subscript least rapidly, sometimes called "row-wise" by those who think that the first of two dimensions must represent a row index. For the above example, delivery is in the order 1, 2, 3, 4.

 

Enumerators to nowhere

ASP.NET 2.0 Enumerator objects and their derivatives can point to nowhere. That is their initial state when created by GetEnumerator() methods on collection objects, and it is their final state after moving through all the items of a collection. Evaluating an Enumerator to nowhere will lead to an exception. How does one otherwise detect that an Enumerator points to nowhere?

As often happens, Microsoft ASP.NET 2.0 documentation is opaque. However, it offers a hint that while the Current property of an Enumerator to nowhere provides a valid KeyValuePair, the Value property of that object is undefined.

Although Microsoft does not document this, when the type of Key is numeric, the Key value will be zero; and when it is a string, the Key value will be empty. That behavior does not suffice, since such Key values might be allowed.

To detect an Enumerator to nowhere, the reliable approach is to test the Value property of Current for null, as in this abbreviated example:

SortedDictionary<...> oMap = new SortedDictionary<...>();
SortedDictionary<...>.Enumerator oMapEnum = oMap.GetEnumerator();
if (oMapEnum.Current.Value == null)
. . .

Tuesday, March 07, 2006

 

Associative arrays: form over substance

The ASP.NET 2.0 Dictionary and Hashtable objects provide the form of associative arrays without guaranteeing the substance. The main objective of conventional implementations has always been to provide rapid insertion and retrieval.

Dictionary and Hashtable objects are containers of (key, value) pairs, and they provide the semantics of associative arrays. If oTable is one of these, then oTable[qKey] retrieves a value that has been associated with a key.

Conventional implementations of associative arrays maintain binary indexes and provide rapid insertion and retrieval using binary search. In its C++ library, Microsoft provides map<> containers committed to these O(log n) levels of performance.

Bjarne Stroustrop has a concise summary of C++ container performance guarantees in his book, "The C++ Programming Language," Third Edition, p. 464. David Musser and Atul Saini explain the meaning of such performance guarantees in their book, "STL Tutorial and Reference Guide," pp. 12-15.

By comparison, Microsoft documentation of ASP.NET 2.0 is scattered and opaque. Because of that, the programmer who needs O(log n) performance for both insertion and retrieval is forced to carry out performance testing and may have to write what should be, and in other environments what is, a standard library class.

Sunday, March 05, 2006

 

Trigger ValidationSummary popup?

A ValidationSummary control collects the ErrorMessage fields from all active validator objects in its group that have IsValid==false and displays them when it is triggered, typically by a Button. In many situations a ShowMessageBox popup message box is required.

However, when there is a CustomValidator using server-side validation in its group, it does not have the result from this validator available at postback, and it will not display a popup message for it at this time.

Upon redisplay of a page after postback, if the CustomValidator.IsValid was set to false at the server, then the message from that CustomValidator is displayed, but no popup message box is displayed by the ValidationSummary.

Is there a way to trigger the ValidationSummary control to display a popup message box after server-side validation?

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]