Main

March 30, 2006

Good post on User Controls

Roland Weigelt has a good post here about VS 2005 User Controls.

Remember to add Browsable, DefaultValue, Category, and Description attributes to public properties.

Browsable and DefaultValue in particular can help you design your control the way you want without having the designer run code it shouldn’t at design time.

 

January 29, 2006

First Effort at Smart Device Development

Just finished my first mobile .NET application.

Things that were easy:

·        Creating a skin for the Treo 700w. I happened to download SOTI Pocket-Controller Professional which had a skin for remote access. I grabbed its bmp file and found the folder that defined the existing skins (C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Deviceemulation\). A quick edit of an existing skin XML file and some Photoshopping of the borrowed skin image followed by using the ToolsàDevice ToolsàForm Factors and Devices UIs in Visual Studio 2005 to copy an existing emulator configuration (Windows Mobile 5.0 Pocket PC Phone Square Emulator) and assign it a new Form Factor.

·        Put an application CAB file on a web server. Using IE on the phone to browse the CAB file triggers a download dialog. Check the open after download box and the installer starts automatically.

·        Getting .NET 2.0 framework installed on the phone.

o       Deploying an application directly to the device triggers installation of the .NET 2.0 runtime.

Things that were hard:

·        Getting internet connectivity within the emulators. Remember to have the emulator cradled (use the ToolsàDevice Emulator Manager) and within the emulator enable a network adapter (FileàConfigureàNetworkàEnable NE2000 PCMCIA network adapter…

·        Understanding the difference between all the available emulators. A Smartphone has a limited UI but includes a phone. Pocket PC has an enhanced UI (it assumes the ability to enter keys on a keyboard, graphical or otherwise). And a Pocket PC Phone has both the enhanced UI and a phone.

·        Getting an ASP.NET 2.0 project deployed from a development server to a deployment server with mixed 1.1 and 2.0 based web apps.

o       Copying doesn’t seem to do anything about configuring the virtual directory or file security settings.

o       Publishing is hidden on one of the menus. It doesn’t do the whole thing either.

o       It would be great to have something generate the xml file from which either the virtual directory or web site was configured.

·        Avoiding ObjectDisposed exceptions when shutting down the windows mobile application. There’s a web request pending at the time of shutdown. The request uses a thread pool work item to implement the request. It looks like the request is completing only to find its world disposed. Resolved by calling Abort on the request, not on the thread. And arranging for the thread to exit cleanly.

·        A related issue is what event’s fire when a windows mobile application is minimized (x in corner is clicked) and when the red button is pressed to turn off the screen. An application continues to run if the OK or minimize button are used to hide the application window while leaving it active. Execution is suspended by the red power button. Execution resumes when the red button is pressed again.

·        Icon colors.

o       Use photoshop to convert the image to indexed colors with the Windows System palette.

o       For some reason, icons installed in the emulator seem to be sticky. They don’t update when the application that owns them is updated.

·        The screen on the Treo 700w is 240 x 240 pixels but after the top and bottom bars are lost to the environment, there’s only a 240 x 188 left. Is there any way to get that space back? When the window form is running in maximized mode, it seems to have the top bar in its client rectangle but there’s still a bar visible.

·        Figuring out how to do resources for localization correctly. The breakthrough was finding the localizable property on a Windows.Forms.Form. Turning this on causes magic to happen in the generated code. Selecting a non-default Language property automatically adds a new resource file. Haven’t chased down the whole satellite assembly building and deployment concept yet. Since a smart device app must stay small, you never want to drag around resources that aren’t used.

·        Getting the application shortcut in the right folder.

o       When you create a Smart Device CAB project to setup a smart device application, the default file system view doesn’t contain the Programs Folder. You can add it manually. This is where the application shortcut has to go.

·        Getting a \r\n sequence into a string resource J. I was moving the string from C# code to the resource editor. “\r\n” was interpreted as a literal, so I tried \u000D\u000A and finally 
. Looking at the resource file in the xml editor showed the last attempt being escaped so I un-escaped them and voila! All I had to do was hit the enter key when replacing the original “\r\n”. Doh.

Things I don’t know how to do yet:

·        Signing the application and CAB file in such a way that the phone doesn’t display the unknown publisher dialog. Many commercial packages behave the same way.

·        Building a smart device installer that triggers an ActiveSyn install. Right now the Smart Device CAB setup project requires that you copy the CAB to the device and run it there. It seems you build an ordinary Windows Setup project but there’s some magic involved in communicating an .ini file to ActiveSync that I just haven’t come across yet.

 

December 06, 2005

Event registration details in C#

What happens if you add a delegate with an event more than once? The delegate will be invoked as many times as it is registered when the event “occurs”.

What happens if you remove a delegate from an event after adding it more than once? One registration is removed at a time.

What happens if you remove a delegate from an event if it has never been registered or if it has already been removed? Nothing.

Since delegates are equal so long as the wrapped methods are the same, it is not necessary to save the actual delegate instance that was added if you intend to remove it later.

 

        public class C1 {

            public event EventHandler E1;

            public void InvokeE1() {

                if (E1 != null) E1(this, new EventArgs());

            }

        }

 

        int invocations = 0;

 

        public void h(object sender, EventArgs args) {

            invocations++;

        }

 

        [TestMethod]

        public void TestRepeatedEventHandlerAddRemove1() {

            C1 c1 = new C1();

            c1.E1 += new EventHandler(h);

            Assert.AreEqual(0, invocations);

            c1.InvokeE1();

            Assert.AreEqual(1, invocations);

 

            // Verify that adding an event handler is invoked as many times as it is added.

            c1.E1 += new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(3, invocations);

 

            // Verify that removing an event handler removes only one copy at a time.

            c1.E1 -= new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

            c1.E1 -= new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

 

            // Verify that removing a non-existant event handler does nothing.

            c1.E1 -= new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

        }

 

        [TestMethod]

        public void TestRepeatedEventHandlerAddRemove2() {

            // Verify that it doesn't matter whether the identical delegate instance is

            // used instead of just an "equal" delegate.

            C1 c1 = new C1();

            EventHandler eh = new EventHandler(h);

            c1.E1 += eh;

            Assert.AreEqual(0, invocations);

            c1.InvokeE1();

            Assert.AreEqual(1, invocations);

            c1.E1 += eh;

            c1.InvokeE1();

            Assert.AreEqual(3, invocations);

            c1.E1 -= eh;

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

            c1.E1 -= eh;

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

            c1.E1 -= eh;

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

        }

 

November 27, 2005

C# 2.0 Transactions framework

Pointer for future reference: Volatile Resource Managers in .NET Bring Transactions to the Common Type, by Juval Lowy

 

October 28, 2005

Avoiding Out of Memory Exceptions in .NET

Updated 2005-11-16 15:33 Wed

One solution to automatically setting the /LARGEADDRESSAWARE switch in the copy of an exe that gets built into a Windows Forms setup file is to add the following line to the project’s post-build event:

editbin /LARGEADDRESSAWARE "$(ProjectDir)obj\Debug\$(TargetFileName)"

This appears to work because the setup project takes its copy of the exe (primary project output) from under the “obj” folder, and not the “bin” folder.

Updated 2005-11-15 08:57 Tue

Getting access to more memory for your .NET 2.0 windows forms applications using the /LARGEADDRESSAWARE switch is described below, but there’s a problem with distributing applications that use this switch.

If you use the method described below to automatically set the switch each time you build a project in Visual Studio 2005, you will discover that it fails to set the switch in executables included in setup projects.

It appears that project build events are not executed as part of building a setup project that includes their primary outputs.

 

Running a simple .NET (2.0 beta 2) application that allocates 1MB buffers until the OutOfMemoryException is thrown gives me these results (on Windows XP SP2):

Scenario

Successful Allocations

Default configuration, running under Visual Studio 2005 beta 2

1605 MB

Default configuration, running from the command line

1704 MB

With /3GB and /LARGEADDRESSAWARE switches

2685 MB

 

A “pure” .NET application appears to be “largeaddressaware”-safe. If your application uses third party libraries, they may not like receiving addresses that exceed 2GB.

Making maximum use of RAM on a Windows XP system with more than 2GB or RAM requires a few extra steps:

·        /3GB Switch

Windows XP looks for a /3GB switch in the boot.ini file when it loads to determine whether to partition the 4GB address space as 2GB user / 2GB system (the default), or as 3GB user / 1GB system.

To turn on this switch:

o       Control PanelàSystemàAdvancedàStartup and Recovery SettingsàEdit

o       Add “ /3GB” to the end of the line following the “[operating systems]” section.

o       Restart the machine for the change to take effect.

·        /LARGEADDRESSAWARE Switch

An executable must have this switch enabled to make use of the extra memory beyond 2GB.

To turn on this switch:

o       Add to PATH environment variable:

§         c:\Program Files\Microsoft Visual Studio 8\common7\ide

§         c:\Program Files\Microsoft Visual Studio 8\vc\bin

o       In Visual Sutdio, add a Post-Build Event:

§         editbin /LARGEADDRESSAWARE “$(TargetPath)”

Links:

http://www.dotnet247.com/247reference/msgs/55/276803.aspx

 

 

September 04, 2005

Creating a Word Add-in using Visual Studio 2005 beta 2

Visual Studio 2005 beta 2 includes the beta 2 version of Visual Studio Tools for Office, but it lacks a template for creating a Word Add-in.

Here’s how you can create one manually:

1.      Create a new Class Library project.

2.      Go to Application tab of the project’s properties:

a.       Click Assembly Information…

b.      Click “Make assembly COM-Visible

3.      Go to Build tab of the project’s properties:

a.       Check “Register for COM interop”

4.      Add references to:

a.       C:\Program Files\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\extensibility.dll

5.      Add “using” statements to the class file for the following:

using Extensibility;

using System.Runtime.InteropServices;

6.      Add a Guid attribute to the class:

[Guid("E1234594-4A14-4a6f-8612-6019EB11CE70"), ProgId("MyDomain.MyWordAddInClass")]

7.      Create a setup project for the class library.

a.       Under the Registry settings to be created by the setup project add the following key:

                                                               i.      HKEY_CURRENT_USER/Software/Microsoft/Office/Word/Addins/MyDomain.MyWordAddInClass

b.      Under this key, create the following values:

                                                               i.      Description (string) = “My Description”

                                                             ii.      FriendlyName (string) = “My Friendly Name”

                                                            iii.      LoadBehavior (DWORD) = 3

That’s it. You should be able to build, install, and uninstall the add-in.

 

August 28, 2005

Automating Word, Add-In vs. Template

At its simplest, the problem is to run some code when CTRL-ALT-A is pressed while editing a Microsoft Word 2003 file.

The code that runs wants to be written in C# and it’s purpose is to modify the content and styles of the current document.

In my first solution, I created a Word Add-In (using Visual Studio Tools for Office) and Visual Studio 2003. The Add-In has to register its DLL under the right Office keys to be found by Word. When Word starts, the assembly’s Connect method gets called. It proceeds to install a new toolbar into Word containing a few buttons. It’s easy to connect the Click event of the buttons with the C# code I want to run. Getting the code to run in response to a CTRL-ALT-A is considerably harder.

The first trick was to discover that a macro of the form below will invoke the C# event handler assigned to a toolbar button:

Sub TonesNotesNew()

    Dim cb As CommandBar

    Set cb = Application.CommandBars("TonesNotes")

    Dim cbc As CommandBarButton

    Set cbc = cb.FindControl(Tag:="New")

    cbc.Execute

    cbc.State = msoButtonUp

End Sub

The last piece of plumbing is to assign CTRL-ALT-A to invoke the macro “TonesNotesNew”.

The second time around I implemented the same functionality as a Word template file which is one of the new Visual Studio 2005 beta 2 project templates. The template file now holds the macros, the styles I use for this functionality, and the C# assembly that does the actual work.

 

 

August 23, 2005

LoaderLock MDA warning in VS 2005 beta 2

After making some fairly innocuous changes, I started getting the following error dialog during application startup running within the VisualStudio 2005 beta 2 IDE:

LoaderLock was detected

Message: Attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function.

See C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215\sdk\bin\mdaBoilerplate.exe.mda.config for documentation.

 

Commenting out the code executing at the time of the exception moved it to a new location in the startup code.

I tested the same code built on a second PC with similar general configuration (but obviously many minor differences) and the problem did not occur.

So I followed the instructions in the MSDN help article referenced by the error dialog…

Added this key:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]

"MDA"="0"

Following this article:

ms-help://MS.VSCC.v80/MS.MSDNQTR.v80.en/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_fxdebug/html/76994ee6-9fa9-4059-b813-26578d24427c.htm

This masks the problem. I don’t know if it was a bug in the detection code or a bug in the initialization code that just happens to skate by if ignored. I suspect the later.

A bit scary.

 

 

Amazing post on startup & shutdown

August 22, 2005

How to convert a hex string to an integer

The MSDN page that tries to answer this question is an absolutely horrible example of how a compact and simple piece of information can be made almost impossible to understand: How to use the Int32.Parse method to convert the string representation of a hexadecimal integer to a decimal integer by using Visual C# .NET

Here is what it should have said:

To convert a hexadecimal string to an integer:

string s = "a0"; // 160 in hexadecimal

int i = int.Parse(s, System.Globalization.NumberStyles.AllowHexSpecifier);

 

NOTE: If the string includes the standard "0x" prefix, a System.FormatException will occur.

 

August 13, 2005

Publishing ASP .NET 2.0 to Windows XP Pro

On a newly configured Windows XP Professional system, the following steps may help you configure it to accept ASP .NET 2.0 applications published from a remote machine:

1.      Add IIS with FrontPage Server Extensions to the Windows XP configuration using the Add/Remove Windows Components tab on the Add or Remove Programs control panel.

2.      Run “C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215>aspnet_regiis –i” if unsure about ASP .NET integration with IIS.

3.      Check that the IUSR_<machine_name> and IWAM_<machine_name> accounts have access to the IIS wwwroot folder (typically c:\Inetpub\wwwroot). IUSR_ at least requires read & execute access.

a.       If Windows XP is using simple file sharing, you may not see the Security tab in the folder Properties dialog and won’t be able to set individual NTFS account access permissions. Uncheck the “Use simple file sharing” folder option (Explorer à Tools à Folder Options à View tab).

4.      Make sure any specific pages you’re trying to access have anonymous access enabled for them (Administrative ToolsàInternet Information Services à Default Web Site à Properties à Directory Security à Edit… à Anonymous access checked.

5.      If the Windows XP Firewall is enabled, create an exception for HTTP. Advanced à Local Area Connection à Settings… à Web Server à Edit (if needed) make sure machine name or IP is correct. Machine name is fine even if access is via IP address (or presumably DNS).

At this point you should be able to use the Visual Studio 2005 beta 2’s Build à Publish. Use an http:// path to the web app’s remote root.

 

August 08, 2005

Generating Patch Files for MSI Setup Files

Building an installer for just about anything with dotNET produces a multi-megabyte setup file which contains mostly dead-weight in terms of original content.

I found myself wanting to avoid transferring these large files each time I made a small update to a program on a remote PC, so I went looking for a compact and simple patch generating tool.

I ended up finding bsdiff.exe and bspatch.exe which require bzip2.exe. Lean, mean, and exactly what I wanted.

 

July 07, 2005

Visual Studio 2005 Beta 2 Problems, Properties Settings of User Types are Impossible to Add

Opened by ToneEngel on 2005-06-16 at 06:02:59    
On the Settings tab of the application Properties GUI the Browse... option on the Type column combo box brings up a Select a Type dialog that does not include types defined in all referenced assemblies or types defined in the application project.

Beta 1 allowed these types to be selected for property settings and the functionality worked. After conversion to beta 2, these user types appear in the combo box list, but not the browse list.

Manually editing the settings XML file can cause the types to appear in the combo box list.

 

Edited by Microsoft on 2005-06-17 at 00:25:17

The Microsoft Sub-status is now "Reproduced"

Thanks for reporting this bug, we have been able to repro this issue and are investigating.

Thank you,
Vivek, VS2005 Product Team.

 

Resolved as Won't Fix by Microsoft on 2005-06-24 at 10:37:11

The issue with using types from referenced assemblies showing up was found too late to be fixed in the Beta2 release (see http://lab.msdn.microsoft.com/ProductFeedback/viewFeedback.aspx?feedbackid=45bfedf6-825b-478b-861e-c2be77bcbdd9) It has been fixed since, and using types from referenced assemblies will work in the RTM release of Visual Studio 2005.

Being able to use types defined in the current project is a very good suggestion, but given our current schedule, we will unfortunately not have time to address this in the Visual Studio 2005 release. I will make sure that we revisit this for the next release of Visual Studio.

There are inherent problems with types that are defined in the same project/in project-to-project references in that they are very likely to change. The settings designer actually has to be able to create instances of the current type in order to function correctly (it needs that information in order to be able to show you the right editor, if any, to edit values as well as to be able to write the default values in code and in the app.config file).

As a work-around, if you really need to use types defined in the current project, you can use the user-editable part of the settings class (the file that opens if you push "View Code" in the settings design) and manually add the settings to this class:

<Global.System.Configuration.UserScopedSettingAttribute(), _
Global.System.Configuration.DefaultSettingValueAttribute("(Optional) serialized representation of an instance of this type")> _
Public Property Setting() As MyTypeDefinedInTheCurrentProject
Get
Return CType(Me("Setting"),MyTypeDefinedInTheCurrentProject)
End Get
Set
Me("Setting") = value
End Set
End Property

 

 

 

June 15, 2005

Visual Studio 2005 Beta 2

Working with beta 2 of Whidbey (Visual Studio 2005) has been difficult so far, surprisingly difficult given how comfortable I had become at developing Windows.Forms based applications with beta 1.

I went back to my notes from the time I started using beta 1 of Whidbey to remember how many problems I had to learn to live with. Like many things, familiarity breeds comfort.

Among the problems I’ve had are:

·        Enabling break when an exception is thrown from C++ or Win32 caused an InvalidDeploymentException to be thrown when accessing simple Properties.Settings.Default properties during application startup. The problem disappeared when I cleared the DebugàExceptions checkboxes, but not before spending several hours trying to work around the exception. And now I’m hitting a vanished XML serialization assembly.

See http://www.dotnet247.com/247reference/msgs/42/211509.aspx

·        Source code coloring in the editor keeps going away for some of my source files. If I cut and paste the contents between source files it comes back. But only to disappear again if I reload the project. The code compiles and runs without trouble.

·        The GUI for defining settings under the application properties editor doesn’t allow settings to be declared from user types. By following the example created by converting a beta 1 application, I was able to manually edit the xml files to cause the type to appear in the list of choices.

·        There are enough changes to the Windows.Forms controls and the designer generated code that it is often easier to recreate a Windows.Forms class from scratch using the beta 1 file as a reference than it is to modify the beta 1 file to be compatible with beta 2.

 

 

June 07, 2005

Migrating from NUnit to Unit Test Framework in Visual Studio 2005 Beta 2

Visual Studio 2005 Beta 2 includes an integrated testing framework which includes, among other types of tests, support for unit tests.

It proved to be very simple to migrate NUnit based tests to this new framework.

Here are the changes that had to be made to individual test files:

NUnit Syntax

Visual Studio 2005 Syntax

using NUnit.Framework;

using Microsoft.VisualStudio.QualityTools.UnitTesting.Framework;

[TestFixture]

[TestClass]

[Test]

[TestMethod]

Assertion.AssertEqual

Assert.AreEqual

Assertion.Assert

Assert.IsTrue

 

The only hiccup I experienced was an unexplained “Invalid character in path” error dialog which appeared initially when trying to run the test project. The problem vanished after removing all the test files from the project and re-adding them one at a time.

 

May 03, 2005

DataGridView Validation

Windows Forms 2.0 has a new general purpose data display control called a DataGridView. It is intended to be easier to use and more powerful than the original DataGrid control.

The following experience may help you when it comes to adding data validation logic to a rather simple application of the DataGridView control.

An obvious (but WRONG) place to start is the CellValidating event of the DataGridView object. Here is a fairly reusable implementation of a CellValidating event delegate method:

 

    this.grid.CellValidating += new DataGridViewCellValidatingEventHandler(grid_CellValidating);

 

    void grid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) {

        DataGridViewCell cell = grid.Rows[e.RowIndex].Cells[e.ColumnIndex];

        TypeConverter tc = TypeDescriptor.GetConverter(cell.ValueType);

        try {

            object newValue = tc.ConvertFrom(e.FormattedValue);

            object oldValue = cell.Value;

            try {

                cell.Value = newValue;

            } finally {

                cell.Value = oldValue;

            }

        } catch (Exception ex) {

            grid.Rows[e.RowIndex].ErrorText = ex.Message;

            SystemSounds.Beep.Play();

            e.Cancel = true;

        }

    }

 

There are a few general things to point out about this method:

·        It uses a TypeConverter to determine if the new value (e.FormattedValue) can be converted to the type of value stored in the cell. Using a TypeConverter is a good general approach because FormattedValue isn’t always a string. It can, in theory, be just about any type.

·        It provisionally assigns the new value to the cell’s value to trigger any argument checking done by the underlying DataSource’s property setter. If there is a value range restriction on the data, you should be checking it at the first public interface level, not in some form specific UI code.

·        A finally block is used to restore the current value of the cell after the provisional assignment. There are several things not to like about this technique. The setter can have side effects (not good coding practice, but it happens). The setter can be resource intensive.

·        Validation errors result in exceptions being thrown. The exception’s message is set as the grid row’s ErrorText property and a beep sound is generated. The ErrorText property will cause a small error notification icon to appear in the row header with a tool tip set to the exception’s message.

·        The Validating event doesn’t happen when a value is simply assigned to a grid cell. For example if your paste-from-clipboard method attempts to assign the contents of the clipboard to a grid cell, you’ll have to implement separate validation logic there and validation failure notification logic.

When you use the ErrorText property, don’t forget to reset it after the problem has been fixed. The simplest way to do this is with the Validated event:

    this.grid.CellValidated += new DataGridViewCellEventHandler(grid_CellValidated);

 

    void grid_CellValidated(object sender, DataGridViewCellEventArgs e) {

        grid.Rows[e.RowIndex].ErrorText = String.Empty;

    }

 

The RIGHT place to put your validation logic is in the DataSource’s property setters, throw an ArgumentValueException for bad values, and use the DataError event instead of the CellValidating event to handle the exceptions:

    this.grid.DataError += new DataGridViewDataErrorEventHandler(grid_DataError);

 

    DateTime lastDataError = DateTime.MinValue;

    void grid_DataError(object sender, DataGridViewDataErrorEventArgs e) {

        e.Cancel = true;

        e.ThrowException = false;

 

        // A single error may cause secondary calls to this handler, ignore these.

        if (grid.Rows[e.RowIndex].ErrorText != String.Empty && null == e.Exception) return;

 

        TimeSpan sinceLast = DateTime.Now - lastDataError;

        lastDataError = DateTime.Now;

 

        string message;

        if (null == e.Exception)

            message = "Bad value.";

        else {

            message = e.Exception.Message;

            if (e.Exception is System.Reflection.TargetInvocationException)

                if (e.Exception.InnerException is FormatException)

                    message = "Bad value format. Check the type of value entered.";

        }

        grid.Rows[e.RowIndex].ErrorText = message;

        if (sinceLast.TotalSeconds < 1.0)

            MessageBox.Show(message + "\r\n\r\nPress Ctrl-Z to restore previous value.", "Bad Value");

        else

            SystemSounds.Beep.Play();

    }

 

The DataError event has a richer (more complicated) calling context than the CellValidating event, but this is a good thing. It allows you to be consistent and focused about your error handling and notification.

A few comments about the grid_DataError method:

·        The e.Context field of the event arguments is a DataGridViewDataErrorContext, which is a flags style enumerator that tells you the general context of the data error. You may want to specialize your handling based on the context in production situations.

·        In addition to the Cancel property there is a ThrowException property. Generally you can set these to true and false respectively because you’re going to handle the problem and take care of notifying the user.

·        A single error may cause a secondary call to the handler. This is the kind of thing that may get fixed by the time 2.0 goes into production. For now, ignore the second call by noticing that the exception property is null and an error message has already been set.

·        Many exception messages are fairly cryptic from an ordinary user’s perspective. Do whatever massaging is necessary for your application.

·        In addition to setting the ErrorText property for a small visual indicator of an error condition, either play a beep sound or, if this error followed the last by less than a second, put up a dialog to display the message directly and require the user to acknowledge it.

Hope this helps you…

 

 

 

April 05, 2005

Whidbey setup project race condition

Whidbey appears to suffer from an unhandled race condition when you “simultaneously” re-build a Windows Forms setup project and use the Add or Remove Programs control panel to remove the previously installed version.

Sometimes the build will fail with a missing file error in the installation folder. Sometimes the application will fail with an unregistered dependency.

 

December 08, 2004

Why does InvokeRequired return false when you'd like it to return true?

I’ve seen a number of developers surprised by InvokeRequired returning false in situations when they know it’s a cross thread call.

The reason is that the underlying window handle associated with the control has not been created yet at the time of the call.

Since InvokeRequired is meant to be used with either BeginInvoke or just Invoke, and neither of these methods can succeed until a windows message pump gets associated with the control, it elects to return false.

 

 

 

December 02, 2004

Whidbey FTP Support

There is some support for FTP in Whidbey beta 1, but it still failed my real world requirements.

Using both the FtpWebRequest / FtpWebResponse and WebClient classes failed to connect to an embedded system running an ftp server that Windows Explorer had no trouble connecting to.

I tried two third party FTP components for .NET (Mabry's FTP/NET and CSpot’s FTPComponent). Both components succeeded in connecting to the server when run as Visual Studio 2003 projects (using their own sample code). But both components failed (typically by hanging in the class constructors) when run after conversion to Visual Studio 2005 (Whidbey beta 1).

It seems that at the moment, there’s just enough FTP implementation in Whidbey to work with some FTP servers, but not all, and just enough changes to interfere with the existing crop of 3rd party components.

 

August 03, 2004

C# changes from 1.1 to 2.0

As of Beta 1.

C# 1.1 à 2.0

Generic classes, interfaces, and methods. Introduces the <type,argument,list> syntax.

Generic constraints. Introduces the syntax: “where T : (class_type | “class” | “struct” ,)? (interface_type | type_parameter ,)* (“new()”)?

Anonymous methods. Re-uses “delegate” and curly braces to declare and define inline methods.

Iterators. Methods that return IEnumerator or IEnumerable (or generic variants) may use “yield return” and “yield break” syntax within a loop to yield control on each item.

Partial types. Introduces “partial” syntax.

Nullable types. Introduces “int?” syntax for all value types and “??” operator.

 

 

Use using to alias long generic type names

Assigning a type name alias with a “using” directive in C# is a handy way to shorten a long type name and simplifies using generic types much as you would normally do with a typedef in C++.

namespace Foo {

            using Stack1 = Stack<Bar<int, string>>;

            … use Stack1 as a type alias for Stack<Bar<int, string>> …

}

 

July 30, 2004

Dynamically replacing controls, ViewState and PostData issues

For ViewState and PostData to work correctly when restoring state to the server controls during a POST to an ASP.NET aspx page, the UniqueID’s must be the same when state is restored as they were when state was saved..

I ran into trouble when I used a button’s click event handler to remove some controls and then added some replacement controls. The replacement controls were numbered sequentially starting after the original control numbering. When a postback occurred, my override of LoadViewState created only the replacement controls which were therefore numbered differently (since the original controls were never created). ViewState wasn’t being restored correctly and the checked property of CheckBox controls wasn’t being set correctly.

What’s missing from ASP.NET is a way of updating the naming scheme originated by the removed controls’ naming managers. If this were done, the replacement controls could be named consistently.

My workaround in this situation was to do a redirect back to the page with a query string argument that causes the correct new control layout to be created.

 

 

July 26, 2004

ICSharpCode.SharpZipLib access denied

To work around an access denied error in ASP.NET applications using ICSharpCode.SharpZipLib, register the assembly in the GAC.

There’s more to it. But for a quick fix this might solve your problem.

See also:

813833 - PRB: "Access Denied" Error Messages When You Do Not Put Strong-Named Assemblies in the Global Assembly Cache - http://support.microsoft.com/default.aspx?scid=kb%3ben-us%3b813833

 

 

July 05, 2004

Viewing a byte[] array contents in a Visual Studio memory window

I haven’t found a good source of tips yet on how to make effective use of Visual Studio’s memory windows when working with C# objects, so here’s one that may work to view the contents of a byte[] array:

1.      Open the memory window.

2.      Type the name of the array in the address box, this will show you the memory occupied by the System.Array object corresponding to your byte[] array.

3.      Enter the first four byte values you see, in reverse order with a “0x” prefix, in the address box.

4.      Ignore the first two bytes, or add 2 to the address entered in step 3.

 

 

June 17, 2004

Yeah! First post on a new overhauled dasBlog engine

From the start, I wasn’t as interested in a wildly popular blog as much as a reliable publicly accessible place to share the notes I’d been accumulating for a few years. And perhaps with Weblogs.com being unceremoniously shut down, reliability and self-determination aren’t so bad.

Anyone who’s reached the point in their blogging where they’re ready to move on to a new engine knows that preserving permalinks can dominate the equation. So, being a bit anally retentive, because I clearly don’t have the volume of high quality posts and user comments to justify it, I decided to invest in the future by making the URL scheme of all public site links a top priority of the move. After looking around a bit, here’s what I wanted to end up with:

Purpose

Public URL Scheme

default

 

login

login

logout

logout

search

search?q=.*

post

yyyy/mm/dd/[0-9a-z\-]{1,48}

postWithComments

yyyy/mm/dd/[0-9a-z\-]{1,48}?c=1

day

yyyy/mm/dd

month

yyyy/mm

category

category/[0-9a-z\-\., ]{1,48}

Rss Main

rss/main.xml

Rss Comment

rss/comment.xml

Rss Comment Post

rss/comment/yyyy/mm/dd/[0-9a-z\-]{1,48}.xml

Rss Category

rss/category/[0-9a-z\-\., ]{1,48}.xml

Atom Main

atom/main.xml

Atom Category

atom/category/[0-9a-z\-\., ]{1,48}.xml

Click Through

ct/yyyy/mm/dd/[0-9a-z\-]{1,48}?u=.*

Trackback

trackback/yyyy/mm/dd/[0-9a-z\-]{1,48}

Aggregator Bug

aggbug/yyyy/mm/dd/[0-9a-z\-]{1,48}

Crosspost Tracker

cptrk/yyyy/mm/dd/[0-9a-z\-]{1,48}

pingback

pingback

 

I wanted a scheme that hid the implementation details and made it simple to figure out what post a URL relates to. If it was possible to build something today that would implement this scheme, I figured I could live with it for a long time and through multiple technology transitions. Only time will tell how truly myopic I’m being, but hell, you have to try.

WordPress was my first choice of engines based on its free, open source status, and the quality of some of the sites that use it. It was my first exposure to MySQL (which was really, really simple to get going – no wonder its doing so well) and PHP (which would have been simple if I’d remembered to remove the wildcard mapping from my IIS 5.0 server before trying to serve my first page. Overall the functionality was reasonably impressive, the standard interface was clean and appealing, but… On looking into the details of the PHP implementation it became clear that this was ASP vintage technology in an ASP.NET and JSP world. Not really un-expected, but definitely a step backwards.

The real stopping point with WP was my lack of interest in running Apache to serve it. Without Apache, there was no replacement for mod_rewrite. Despite what this post says, using PathInfo to forward information to what will always look like a PHP is a big sacrifice on the GUBUS. Without mod_rewrite, there was no grand-unified-blog-URL-scheme (Unless I wanted to write a custom ISAPI filter or something to do the work. Thanks, but no.)

It also became clear that the sites I’d liked were much more than straight WordPress. They were heavily customized and artistically templated.

So it was back to the blog engine comparison table. If I wanted to stick with a .NET codebase, the choices seemed to be Dottext or dasBlog. So I took another look at dasBlog.

dasBlog’s information schema was simpler than WP’s, but pleasantly transparent due to its use of XML files for storage. I can see a storage subsystem reimplementation on the horizon, but it makes for a quick and open prototyping platform. One of the glaring holes is the lack of a real Category entity. Event though dasBlog implements a visually attractive hierarchical category system, internally the implementation is out of gas. Because categories don’t have independent IDs and display strings, everything you might want to do with them is a compromise. A bit more work for the future.

First things first though. On to the grand-unified-BLOG-url-scheme implementation.

Having no clear idea of the level of effort (this was my learn-the-codebase project); I dove in and started making changes.

Along the way I abandoned the existing URL and link rewriting support for being non-scalable. Learned when not to use Request.MapPath (a post on that later); and about remote debugging in ASP.NET (another post); and crawled through the bowels of pingbacks and trackbacks.

By the time I finished, I’d modified every place in the code were a link was being generated to bring them all through the same centralized module that was responsible for parsing incoming URLs. Keeping things in sync is hard enough without separating where things have to be maintained.

One of the goals of the effort was to be able to run the exact same configuration on http://localhost/dasblog and http://tonesnotes.org, with or without an IIS wildcard mapping in each case. This turned out to be so cool that I ended up completely forgetting about how painful it had been to transfer a blog from one context to another. The ONLY change that has to be made is the “External Weblog URL”. Hide the ASP.NET extensions or not, run it at the root of its own IIS 6 web site or deep down on some IIS 5 developer box, it just works.

Okay, more testing is needed before I can make that claim, but there are enough cards on the table to be sure the outcome’s going to be good.

Another piece of work that came out of the effort was an extension of Scott’s import tool to consume Dottext blogs. (another post)

Time for bed now…

June 16, 2004

ASP.NET code block mystery <%= vs. <%#

It’s remarkably hard to search for syntax like “<%=” if you want to find something that speaks about the syntax rather than all the pages that use it.

I ran into a case where apparently identical code blocks in the same .ascx file were being treated differently. One was expanded and the other was treated as a literal.

The problem ended up being that a “runat=server” attribute had been added to the element with the failing code block.

I’m sure there’s a concise statement somewhere about how to escape code blocks and how they can be embedded in layers of quotations. I just haven’t found it yet.

Any pointers out there?

 

Google Free Site Search

Signed up for Google’s free site search service.

Modified their template to the following:

<form id="searchform" method="get" action="http://www.google.com/custom">

<input type="hidden" name="cof"

 value="S:http://blogs.toneengel.com/;GL:0;AH:left;LH:60;L:http://blogs.toneengel.com/skins/btone/images/sggb60.jpg“

+“;LW:60;AWFID:b764e198d2896699;">

<input type="hidden" name="domains" value="blogs.toneengel.com">

<input type="hidden" name="sitesearch" value="blogs.toneengel.com">

<p id="searchlabel"><label for="q" accesskey="4">Search this site:</label></p>

<p id="searchinput"><input type="text" id="q" name="q" size="18" maxlength="255" value="">&nbsp;
<input type="submit" value="Search"></p>

</form>

 

 

But this works much better within an ASP.NET page:

function googleSearch(searchString) {

     var cof = "S:http://blogs.toneengel.com/;GL:0;AH:left;LH:60;L:http://blogs.toneengel.com/skins/btone/images/sggb60.jpg;“+

“LW:60;AWFID:b764e198d2896699;";

     var domains = "blogs.toneengel.com";

     var sitesearch = "blogs.toneengel.com";

     var q = escape(searchString).replace(/\+/g,"%2B");

     location.href="http://www.google.com/custom?cof="+cof+"&domains="+domains+"&sitesearch="+sitesearch+"&q="+q;

}

<input type="text" id="searchString" onkeypress="if (event.keyCode == 13) googleSearch(searchString.value);" >

<input type="button" value="Google" onclick="googleSearch(searchString.value);">

 

Thanks to Mark Pilgrim for putting me on to this service.

 

June 13, 2004

12 rules to clarify HttpRequest & Uri properties

The documentation for the HttpRequest and Uri classes was a little thin on some of their properties, so I ran some experiments to figure it out. Quite a bit of clarify emerged along with a few quirks that suggested some design rules to work around. Here’s the executive summary along with the actual experimental results.

Summary:

1.      PathInfo is path-like information following the target page and preceding the query string. It is parsed in the Url.Segments array just like the rest of the path.

2.      Path = FilePath + PathInfo.

3.      Authority = Host + Port, if Port is non-standard, otherwise Authority = Host.

4.      Fragment is generally, but not always, missing from Request.Url.Fragment.

5.      Don’t assume the last Segments array item will be the filename of the executing page because of PathInfo parsing.

6.      All non-physical paths are relative. They contain no scheme, host, or port information.

7.      PathInfo can not be included in the argument to Server.Transfer. You get an exception.

8.      Never supply Query arguments or Fragment in a Server.Transfer. You’ll get unpredictable results when combined with IHttpModule RewritePath.

9.      The PhysicalPath property corresponds to the FilePath property and NOT the CurrentExecutionFilePath.

10.  The ONLY evidence of a server transfer (Server.Transfer) is in the CurrentExecutionFilePath property.

11.  The ONLY evidence of a rewrite (Context.RewritePath) is in the RawUrl property.

12.  Always use the three argument flavor of RewritePath.

Experiment 1:

The first experiment is a fully loaded request to a file “a.aspx” in the web server’s root folder. The server is running on port 82 instead of 80 to highlight the difference between Host and Authority properties.

Request: http://localhost:82/a.aspx/path/info?query=arg#fragment

ApplicationPath

=

/

CurrentExecutionFilePath

=

/a.aspx

FilePath

=

/a.aspx

Path

=

/a.aspx/path/info

PathInfo

=

/path/info

PhysicalApplicationPath

=

c:\inetpub\wwwroot\

PhysicalPath

=

c:\inetpub\wwwroot\a.aspx

RawUrl

=

/a.aspx/path/info?query=arg

Url.Scheme

=

http

Url.Host

=

localhost

Url.Authority

=

localhost:82

Url.Segment[0]

=

/

Url.Segment[1]

=

a.aspx/

Url.Segment[2]

=

path/

Url.Segment[3]

=

info

Url.Query

=

?query=arg

Url.Fragment

=

 

Url.LocalPath

=

/a.aspx/path/info

Url.PathAndQuery

=

/a.aspx/path/info?query=arg

Url.AbsoluteUri

=

http://localhost:82/a.aspx/path/info?query=arg

 

Conclusions from Experiment 1:

1.      Path = FilePath + PathInfo

2.      There’s no trace of the fragment in any of the properties. Perhaps it is being consumed by the .NET Framework in some way.

3.      Authority = Host + Port

4.      Don’t assume the last segment is the executing file since PathInfo is parsed after it.

5.      The MSDN documentation for FilePath falsely suggests that it includes scheme and host fields.

Experiment 2:

The second experiment aims a similar request at a file “b.aspx” which immediately does a server transfer to “a.aspx”.

Request: http://localhost:82/b.aspx/path/info?query=arg#fragment

Transfer: Server.Transfer("a.aspx?st_query=st_arg#st_fragment")

ApplicationPath

=

/

CurrentExecutionFilePath

=

/a.aspx

FilePath

=

/b.aspx

Path

=

/b.aspx/path/info

PathInfo

=

/path/info

PhysicalApplicationPath

=

c:\inetpub\wwwroot\

PhysicalPath

=

c:\inetpub\wwwroot\b.aspx

RawUrl

=

/b.aspx/path/info?query=arg

Url.Scheme

=

http

Url.Host

=

localhost

Url.Authority

=

localhost:82

Url.Segment[0]

=

/

Url.Segment[1]

=

b.aspx/

Url.Segment[2]

=

path/

Url.Segment[3]

=

info

Url.Query

=

?query=arg

Url.Fragment

=

 

Url.LocalPath

=

/b.aspx/path/info

Url.PathAndQuery

=

/b.aspx/path/info?query=arg

Url.AbsoluteUri

=

http://localhost:82/b.aspx/path/info?query=arg

 

Conclusions from Experiment 2:

1.      PathInfo can not be included in the argument to Server.Transfer.

2.      The PhysicalPath property does corresponds to the FilePath property and NOT the CurrentExecutionFilePath. Use MapPath on the CurrentExecutionFilePath to find out where the actual page you’re running is located.

3.      The query string and fragment in the argument to Server.Transfer are ignored in this case (but not in all cases, see Experiment 4).

4.      The ONLY impact of a server transfer to a.aspx is in the CurrentExecutionFilePath property. In all other ways the request still looks like the original to b.aspx.

Experiment 3:

The third experiment uses an IHttpModule to do a Context.RewritePath of a similar initial request. The rewrite attempts to alter the PathInfo and Query properties of the request.

Request: http://localhost:82/c.aspx/path/info?query=arg#fragment

Rewrite: Context.RewritePath("a.aspx", "/rw_path/rw_info", "rw_query=rw_arg")

ApplicationPath

=

/

CurrentExecutionFilePath

=

/a.aspx

FilePath

=

/a.aspx

Path

=

/a.aspx/rw_path/rw_info

PathInfo

=

/rw_path/rw_info

PhysicalApplicationPath

=

c:\inetpub\wwwroot\

PhysicalPath

=

c:\inetpub\wwwroot\a.aspx

RawUrl

=

/c.aspx/path/info?query=arg

Url.Scheme

=

http

Url.Host

=

localhost

Url.Authority

=

localhost:82

Url.Segment[0]

=

/

Url.Segment[1]

=

a.aspx/

Url.Segment[2]

=

rw_path/

Url.Segment[3]

=

rw_info

Url.Query

=

?rw_query=rw_arg

Url.Fragment

=

 

Url.LocalPath

=

/a.aspx/rw_path/rw_info

Url.PathAndQuery

=

/a.aspx/rw_path/rw_info?rw_query=rw_arg

Url.AbsoluteUri

=

http://localhost:82/a.aspx/rw_path/rw_info?rw_query=rw_arg

 

Conclusions from Experiment 3:

1.      PathInfo can not be included in the argument to Server.Transfer. An exception is thrown if it is.

2.      The RewritePath PathInfo replaces the original query’s except for in the RawUrl property.

3.      The ONLY evidence of a rewrite is in the RawUrl property.

Experiment 3a:

A modification of the third experiment does a Rewrite without PathInfo or QueryString overrides to see what is preserved from the original request.

Request: http://localhost:82/g.aspx/path/info?query=arg#fragment

Rewrite: Context.RewritePath("a.aspx")

ApplicationPath

=

/

CurrentExecutionFilePath

=

/a.aspx

FilePath

=

/a.aspx

Path

=

/a.aspx

PathInfo

=

 

PhysicalApplicationPath

=

c:\inetpub\wwwroot\

PhysicalPath

=

c:\inetpub\wwwroot\a.aspx

RawUrl

=

/g.aspx/path/info?query=arg

Url.Scheme

=

http

Url.Host

=

localhost

Url.Authority

=

localhost:82

Url.Segment[0]

=

/

Url.Segment[1]

=

a.aspx

Url.Query

=

?rw_query=rw_arg

Url.Fragment

=

 

Url.LocalPath

=

/a.aspx

Url.PathAndQuery

=

/a.aspx?query=arg

Url.AbsoluteUri

=

http://localhost:82/a.aspx?query=arg

 

Conclusions from Experiment 3a:

1.      PathInfo must be explicitly transferred from the original query in a rewrite or it will be lost.

2.      Query arguments from the original query are preserved in a rewrite.

3.      In general, always use the three argument flavor or RewritePath and think carefully about what should happen to PathInfo and Query arguments.

Experiment 4:

The fourth experiment is a composition of two and three. The original query is rewritten by an IHttpModule to a page that does a server transfer to the page that finally responds to the request.

Request: http://localhost:82/d.aspx/path/info?query=arg#fragment

Rewrite: Context.RewritePath("b.aspx" "/rw_path/rw_info", "rw_query=rw_arg")

Transfer: Server.Transfer("a.aspx?st_query=st_arg#st_fragment")

ApplicationPath

=

/

CurrentExecutionFilePath

=

/a.aspx

FilePath

=

/b.aspx

Path

=

/b.aspx/rw_path/rw_info

PathInfo

=

/rw_path/rw_info

PhysicalApplicationPath

=

c:\inetpub\wwwroot\

PhysicalPath

=

c:\inetpub\wwwroot\b.aspx

RawUrl

=

/d.aspx/path/info?query=arg

Url.Scheme

=

http

Url.Host

=

localhost

Url.Authority

=

localhost:82

Url.Segment[0]

=

/

Url.Segment[1]

=

b.aspx/

Url.Segment[2]

=

rw_path/

Url.Segment[3]

=

rw_info

Url.Query

=

?st_query=st_arg

Url.Fragment

=

#st_fragment

Url.LocalPath

=

/b.aspx/rw_path/rw_info

Url.PathAndQuery

=

/b.aspx/rw_path/rw_info?st_query=st_arg

Url.AbsoluteUri

=

http://localhost:82/b.aspx/rw_path/rw_info?st_query=st_arg#st_fragment

 

Conclusions from Experiment 4:

1.      In this case the server transfer Query arguments and Fragment were transferred to the final request. Compare this to the behavior when just a server transfer is done in which there is no effect from supplying Query arguments or Fragment with the server transfer request. This suggests strongly that you NEVER supply Query arguments or Fragment in a Server.Transfer.

Experiment 5:

The last experiment is similar to Experiment 4 except it is run in an application folder two levels removed from the root folder and each step drops down an additional folder. The original request for f.aspx is rewritten into a request for rewrite/b.aspx, which does a server transfer to transfer/a.aspx. As usual, we attempt to inject alternate PathInfo, Query, and Fragment where possible.

Request: http://localhost:82/QEs/ASP.NET%20Web%20Application/f.aspx/path/info?query=arg#fragment

Rewrite: Context.RewritePath("rewrite/b.aspx", "/rw_path/rw_info", "rw_query=rw_arg")

Transfer: Server.Transfer("transfer/a.aspx?st_query=st_arg#st_fragment")

 

ApplicationPath

=

/QEs/ASP.NET Web Application

CurrentExecutionFilePath

=

/QEs/ASP.NET Web Application/rewrite/transfer/a.aspx

FilePath

=

/QEs/ASP.NET Web Application/rewrite/b.aspx

Path

=

/QEs/ASP.NET Web Application/rewrite/b.aspx/rw_path/rw_info

PathInfo

=

/rw_path/rw_info

PhysicalApplicationPath

=

C:\Tone\kzDev\Quick Experiments\ASP.NET Web Application\

PhysicalPath

=

C:\Tone\kzDev\Quick Experiments\ASP.NET Web Application\rewrite\b.aspx

RawUrl

=

/QEs/ASP.NET Web Application/f.aspx/path/info?query=arg

Url.Scheme

=

http

Url.Host

=

localhost

Url.Authority

=

localhost:82

Url.Segment[0]

=

/

Url.Segment[1]

=

QEs/

Url.Segment[2]

=

ASP.NET%20Web%20Application/

Url.Segment[3]

=

rewrite/

Url.Segment[4]

=

b.aspx/

Url.Segment[5]

=

rw_path/

Url.Segment[6]

=

rw_info

Url.Query

=

?st_query=st_arg

Url.Fragment

=

#st_fragment

Url.LocalPath

=

/QEs/ASP.NET%20Web%20Application/rewrite/b.aspx/rw_path/rw_info

Url.PathAndQuery

=

/QEs/ASP.NET%20Web%20Application/rewrite/b.aspx/rw_path/rw_info?st_query=st_arg

Url.AbsoluteUri

=

http://localhost:82/QEs/ASP.NET%20Web%20Application/rewrite/b.aspx/rw_path/rw_info?st_query=st_arg#st_fragment

 

No surprises here.

 

 

May 28, 2004

The Right Way to Rotate JPEGs

The combination of the FromFile, RotateFlip and Save methods from System.Drawing.Image is an obvious approach when looking for functions to rotate JPEGs by 90 degrees, but there are reasons for not doing it this way:

      public static void SimpleRotate90(string inFile, string outFile, bool clockwise) {

            using (Image image = Image.FromFile(inFile)) {

                  image.RotateFlip(clockwise ? RotateFlipType.Rotate90FlipNone : RotateFlipType.Rotate270FlipNone);

                  image.Save(outFile);

            }

      }

Why isn’t this a good solution? Here’s why:

1.      The saved image is typically much larger than the original.

2.      While the image displays fine in IE and some other tools, Photoshop 8 complains about an “invalid jpeg marker” in the file and won’t open it.

3.      It’s slower than it needs to be.

 

One solution is to let the Save method do more of the work by passing it an Encoder parameter that tells it to transform the image as it is saved.

The code looks like this:

      using System.Drawing;

      using System.Drawing.Imaging;

 

      ...

 

      public static ImageCodecInfo GetImageCodecInfo(ImageFormat format) {

            foreach (ImageCodecInfo info in ImageCodecInfo.GetImageEncoders())

                  if (info.FormatID == format.Guid) return info;

            return null;

      }

 

 

      public static void Rotate90(string inFile, string outFile, bool clockwise) {

            using (Image image = Image.FromFile(inFile)) {

                  EncoderParameters eps = new EncoderParameters(1);

                  EncoderValue v = clockwise ? EncoderValue.TransformRotate90 : EncoderValue.TransformRotate270;

                  eps.Param[0] = new EncoderParameter(Encoder.Transformation, (long) v);

                  image.Save(outFile, GetImageCodecInfo(ImageFormat.Jpeg), eps);

            }

      }

 

Comments about this implementation:

1.      The saved image is about the same size as the original.

2.      Photoshop 8 seems happy.

3.      The using statement guarantees that image.Displose() gets called which guarantees the inFile gets released.

4.      The helper function GetImageCodecInfo uses the ImageFormat class instead of relying on mime type strings.

 

 

 

May 23, 2004

Details on behavior of forms authentication RequireSSL attribute

Setting RequireSSL to true causes authentication to require that all requests uses https; but it does NOT automatically switch to https when redirecting to the loginUrl page. If you don’t start out using https to access protected pages you are still transferred to the http flavor of your login page but login will not appear to work. What appears to be happening is this: authentication succeeds but does not end up adding a valid authentication cookie to the session (because https isn’t being used), you are redirected to the original protected content page, because there’s no cookie you are re-redirected back to the login page.

Therefore the practical consequence of RequireSSL is not to require SSL for login but rather to require it for all access to protected pages.

It would be nice if you could set forms authentication to force SSL for login and then have the redirectUrl return to the previous access mode (https or http).

See this post for more information.

 

May 21, 2004

NT Security Classes for .NET

May 20, 2004

John Robbins BugSlayer Tips

May 19, 2004

ASP.NET Forms Authentication with SSL/https

So you read the available documentation an put together the basic Forms authentication framework for an ASP.NET application (for which there’s a good article by Abel Banda on O’Reilly) and you say to yourself, boy it really sucks that these passwords are being sent over the internet unencrypted; which leads to the next logical step of using SSL for the login.aspx page.

The steps to follow are these:

1.      Add a server certificate to the web site root folder (Internet Information ServicesàDefault Web SiteàPropertiesàDirectory SecurityàServer Certificate…)

2.      Create folder in your web app for which you will require SSL access. Call it “ssl”.

3.      Move your login.aspx file to ssl/login.aspx.

4.      Make sure the NTFS security settings on the web application folder includes the anonymous IIS user account (IUSR_machine typically).

5.      Disable Integrated Windows authentication and make sure Anonymous access is enabled for the web app (Internet Information Servicesàweb app folderàPropertiesàDirectory SecurityàAnonymous access and authentication controlàEdit…)

6.      Require SSL for your ssl folder (Internet Information Servicesàssl folderàPropertiesàDirectory SecurityàServer Certificate…àcheck the “Require Secure Channel” box.)

7.      Modify the loginURL attribute of the Forms element in your web.config file to reference the new login.aspx location with an absolute url beginning with “https://”.

8.      Test your application.

At this point if you’re like me, a few things will begin to annoy you. First you’ll be reminded that you can’t debug under Visual Studio 2003 without Integrated Windows authentication. That’s okay. Leave it off for now. Just open a browser and manually try to hit your test page.

Again, if you’re like me, you should be getting a 401.2 error attempting to get the login.aspx page.

You might even try directly accessing the login page from the browser with https – no good. And you might try changing the loginUrl back to a relative url (“ssl/login.aspx”) and requesting your test page with https. Wow! That works!?! Hmm… You might also test what happens if you change the “deny users=”?”” to “deny users=”bob””. Again it works accessing each page with https, but you’ve given up forcing the redirect to the login page. If you actually tried any of these things, put things back the way they were and move on to the solution.

Add this in front of your existing <system.web> element in the web.config file:

<location path="ssl/login.aspx">

     <system.web>

         <authorization>

             <allow users="?" />

         </authorization>

     </system.web>

 </location>

 

This block tells the authorization logic to explicitly allow the anonymous user to access the ssl/login.aspx page and overrides the blanket deny element that comes later. There seems to be some special case code that handles relative login URLs but fails on absolute login URLs.

Change your loginUrl attribute back to an absolute “https://”  url and the authentication should work.

One annoyance remains: The ReturnUrl passed to the login page doesn’t include the request scheme (the “http://” part) so you end up stuck in SSL after you login.

So I tried taking charge of the redirect URL with the following code. Thanks to Scott Hanselman for the xml timeout hack, and yes, it needs better packaging for production:

System.Xml.XmlDocument x =new System.Xml.XmlDocument();

x.Load(Request.PhysicalApplicationPath + "web.config");

System.Xml.XmlNode node = x.SelectSingleNode("/configuration/system.web/authentication/forms");

int timeout = int.Parse(node.Attributes["timeout"].Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);

 

string userData = "Place application specific data for this user here.";

 

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(

      1,

      UserName.Value,

      System.DateTime.Now,

      System.DateTime.Now.AddMinutes(timeout), // Should be timout from web.config

      PersistCookie.Checked,

      userData,

      FormsAuthentication.FormsCookiePath);

 

string encTicket = FormsAuthentication.Encrypt(ticket);

 

Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

 

string url = FormsAuthentication.GetRedirectUrl(UserName.Value, PersistCookie.Checked);

 

Uri uri = Request.Url;

 

url = uri.Scheme.Replace("https", "http") + "://" + uri.Host + url;

 

Response.Redirect(url, true);

 

That does the trick but generates a very annoying Security Alert dialog (“You are about to be redirected to a connection that is not secure.”).

To avoid this warning which is intended to remind naïve users that they are being taken away from the protection of https by the never-to-be-trusted server you must arrange for the client to request the shift back to non-SSL traffic.

Instead of the “Response.Redirect(url, true)” at the end of the previous block of code, replace it with:

(FindControl("RedirectUrl") as System.Web.UI.HtmlControls.HtmlInputHidden).Value = url;

 

And merge the following bit of code into the login.aspx file:

            <script language="javascript">

            <!--

            function DoOnLoad() {

                  if (Form1.RedirectUrl.value)

                        window.navigate(Form1.RedirectUrl.value);

            }

            -->

            </script>

      </HEAD>

      <body onload="DoOnLoad()">

            <form id="Form1" method="post" runat="server">

                  <input type=hidden id="RedirectUrl" name="RedirectUrl" runat="server" />

 

That should do it. Nothing could be simpler…. J

The following notes are from Jason Loader,

2005-04-08

Hi Tone,

You know, you're the only person whose posted a solution to this problem I could find. Anyway, I was getting a windows login prompt/401.2 error (I was using Anonymous/Integrated Authentication on IIS), which was odd, as my application uses role-based security, but I'd already given anonymous/all users access to the login page via a location element. I tried what you suggested, but it still didn't work (it did get rid of the windows login prompt though), I couldn't find an explicit solution to the problem, but it would appear that the answer is actually in the Duwamish 7.0 sample.

You need to give the login page subdirectory it's own web config file that gives access to it for all users (that's all that needs to be in there) if you want to hard code the absolute url in the forms loginurl e.g.

In the application web.config file, you'd put -

<forms loginUrl="https://[your server]/[your application directory]/Secure/login.aspx" name="AuthCookie" timeout="30" slidingExpiration="true" path="/" protection="All">

</forms>

In the Secure subdirectory web.config file, you'd put -

<system.web>

  <authorization>

    <allow users="*" />

  </authorization>

</system.web>

then remove the location section for the login page from the main app config file (the bit below) -

<location path="Secure/Logon.aspx">

  <system.web>

    <authorization>

      <allow users="?"/>

    </authorization>

  </system.web>

</location>

From what I can tell, it's probably something to do with ASP.NET not picking up the authentication from the config file for the sub directories correctly when using absolute urls, as it works when you use relative urls and SSL for the whole site.

However, you still had to change the config file if you moved the directories, changed the server name etc. With a bit of playing about, I found that you can force Forms Authentication to use SSL without hard-coding HTTPS in the loginURL by using the following code in globals.aspx -

Sub Application_EndRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.EndRequest

Dim responseURI As System.Uri

Dim responseURIBuilder As System.UriBuilder

Dim pageName As String

' check to see if we're being redirected to the login page

If Not Response.RedirectLocation Is Nothing AndAlso Response.RedirectLocation.Length > 0 Then

' load the redirect into the uri

responseURI = New System.Uri(Request.Url,

Response.RedirectLocation)

pageName = GetPageFromURL(responseURI.AbsolutePath).ToLower()

If pageName = "login.aspx" AndAlso responseURI.Scheme <> "https"

Then

' force it to be HTTPS

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "https"

' remove the port number from the url and write it back

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

ElseIf pageName <> "login.aspx" AndAlso responseURI.Scheme = "https" Then

' force it to be HTTP

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "http"

' remove the port number from the url and write it back

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

End If

End If

End Sub

GetPageFromURL is a function which gets the page from a URL string, there's plenty of examples out there if you need to do the same.

What Duwamish does is automatically prefix absolute urls in the aspx files so you can turn SSL on and off through web.config, but I prefer to do that in the vb code so I can use relative links. One thing I haven't done is check that the login page is always being requested over secure sockets and if not force it to be, but you can get round that through IIS configuration (just in case you don't configure IIS and someone tries to access the login page directly). The next step is to make this configurable, like Duwamish is, and possibly picking up the HTTP/HTTPS ports if you've changed them (I've only tried it on default ports with IE).

Cheers,

Jason Loader

Software Developer, GVAS

Wealth Management Software PLC

E-mail: jason.loader@wms-plc.com

 

2005-04-08

one thing to note is that the duwamish sample doesn't actually work by using an absolute url in the loginURL, it uses a relative one and then absolute urls for it's links on the pages and redirects in the code. I am suprised about the Forms Authentication not behaving correctly though, as I thought they would just do what I'm doing and simply re-write the response.redirectlocation (although that would probably cause problems as it redirects using relative paths, which may not work if you'd redirected to a different site. maybe that's something to play about with later). i also found that if you're re-writing the url, you don't even need a config file in the subdirectory.

when i get round to rolling this into our application, if there's any changes or further issues i'll give you a shout.

Cheers

Jason

 

2005-04-11

there's a bit of a glitch in the solution i sent you. if you try to open the project in vs.net you sometimes get an error to do with redirect from a non-secure to a secure connection, which then stops the project from opening. the reason is that when vs.net opens the project, it tries to get a file called get_aspx_ver.aspx, which it looks like it dynamically creates to check if ASP.NET is running on the server. The reason it fails is due to forms authentication and the code in globals.asax. As we haven't been authenticated yet, we're redirected to the login page, but our code forces it to be via HTTPS, which VS.NET can't handle. There are several solutions to this -

1. Remove the forms authentication from web.config before opening the project and then put it back in.

2. Delete the contents of the projects bin directory before opening.

3. Put the following in web.config to allow access to the temporary file -

<location path="get_aspx_ver.aspx">

<system.web>

<authorization>

<allow users="?"/>

</authorization>

</system.web>

</location>

4. Add a condition into our code to always use HTTP for the above page.

Sub Application_EndRequest(ByVal sender As Object, ByVal e As

System.EventArgs) Handles MyBase.EndRequest

' check to see if we're being redirected to the login page

Dim responseURI As System.Uri

Dim responseURIBuilder As System.UriBuilder

Dim pageName As String

If Not Response.RedirectLocation Is Nothing AndAlso Response.RedirectLocation.Length > 0 Then

' load the redirect into the uri

responseURI = New System.Uri(Request.Url,

Response.RedirectLocation)

pageName =

RetrieveRequestPage(responseURI.AbsolutePath).ToLower()

If RetrieveRequestPage(Request.Url.AbsolutePath).ToLower() <> "get_aspx_ver.aspx" _

AndAlso pageName = "login.aspx" _

AndAlso responseURI.Scheme <> "https" Then

' force it to be HTTPS

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "https"

' remove the port number from the url

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

ElseIf pageName <> "login.aspx" AndAlso responseURI.Scheme = "https" Then

' force it to be HTTP

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "http"

' remove the port number from the url

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

End If

End If

End Sub

I'd go with 4, but you'll have to do one of the others first to be able to open the project to make the change (and you may need to restart the web service too)...

Cheers

Jason

Troubleshooting ASP.NET authentication, authorization, and security issues

May 17, 2004

ViewState, LoadViewState, and dynamically added UserControls

Dynamically adding UserControls (ascx files and classes derived from System.Web.UI.UserControl) gets tricky when you start needing the controls re-instantiated at postback time.

For this discussion, I start with a conventional Web Form page (aspx file and class derived from System.Web.UI.Page) where the Page_Load method contains logic to create and add a variety of System.Web.UI.Control derived instances to the page’s Controls collection. The Page_Load implementation should only do the construction if IsPostPack is false because the context that determines how many and/or which controls to create should come from the ViewState for a post back.

Here are a few things to keep in mind:

1.      LoadViewState gets called before Page_Load and only on post back (i.e. when the request contains a hidden __VIEWSTATE form field.)

2.      If the correct structure of controls exists at the end of the Page derived class’ LoadViewState method then their state will automatically be restored.

3.      The content of the savedState argument to LoadViewState is just the state for the current control. It does not include the state for child controls.

4.      The Page class’ LoadViewState implementation restores any ViewState[string] assignments that were made.

 

A simple way to have the state of dynamically created controls restored on post back is to do the following:

1.      At the end of the Page_Load implementation, store just enough information in ViewState to recreate the same structure on post back. E.g. ViewState[“DynamicControlCount”] = 6;

2.      Override the LoadViewState method in the derived Page class.

a.       First call base.LoadViewState(savedState). This will restore the page’s ViewState. E.g. the value of ViewState[“DynamicControlCount”].

b.      Use ViewState[“DynamicControlCount”] to recreate the prior control structure.

 

That’s it! After calling the derived page class’ LoadViewState method, the framework will call the LoadViewState methods on each child control in a fixed traversal order.

 

XmlSerializer ctor failure in ASP.NET context

Using the XmlSerializer class to deserialize an object from its XML representation from an assembly invoked in an ASP.NET context on Windows Server 2003 fails with a file not found error.

The problem is that the “INTERNET SERVICE” account doesn’t have write access to the c:\winnt\Temp folder.

This is a recognized problem.

 

May 13, 2004

Creating Windows Services

Summary

Here’s the summary of what to remember when creating a new Windows Service. For more information on the process of discovering these tips read the details below.

1.      By default, a Windows Service project is missing an installer. From a service design view, right click select “Add Installer”.

2.      By default, the ServiceProcessInstaller component is configured to use a “User” account, change it to either “LocalService” or “LocalSystem” depending on security concerns.

3.      By default, a setup project does not invoke the custom installers contained within assemblies added to it. From the setup project’s Custom Actions Editor, right click select “Add Custom Action…” and select the primary output from the service application.

4.      When having difficulties, use the installutil tool to obtain detailed installation progress logging output.

Details

I started this note when what should have been a simple process, creating the shell for a new Windows Service project, began taking much too long.

Conceptually the goal isn’t complicated:

1.      Create an EXE containing the service implementation following the Visual Studio 2003 Windows Service project template.

2.      Create an installer for the service.

3.      Install it.

4.      Verify that it appears in the list of installed services.

The first time around I made changes to the default names of the various components as appropriate for a real project. When this didn’t work I tried again and left every name at its default value. I created a new empty solution, added a Windows Service project, opened the design view on Service1.cs, right clicked and selected “Add Installer”, added a Setup project to the solution consuming the primary output of the WindowsService1 project.

This results in the following names being assigned:

1.      WindowsService1: The name of the service project.

2.      WindowsService1.exe: The executable containing the service implementation.

3.      WindowsService1: The service project namespace.

4.      Service1: The service class.

5.      Service1: The “ServiceName” assigned in InitializeComponent to instances of class Service1.

6.      ProjectInstaller: The name of the class created by the “Add Installer” command.

7.      Service1: The “ServiceName” assigned to the serviceInstaller1 installer within ProjectInstaller.

It all builds smoothly but when I “Install” the Setup1 project nothing appears in the installed service list (Administrative ToolsàServices).

Next I tried the “installutil” tool from the service project’s bin\Debug folder. The first time I executed it with the command line “installutil *.exe” because I’m lazy. This results in the cryptic error message “Exception occurred while initializing the installation: System.ArgumentException: Invalid directory on URL..”

A much better error message might have been: “Installation file not found: *.exe”. It seems that installutil doesn’t expand wildcards.

Trying again with an explicit filename, “installutil WindowsService1.exe”, worked much better. It puts up an account credentials dialog because the default Account property of the serviceProcessInstaller1 component has a value of “User”. When I entered my user name without a preceding domain name the installation failed, but at least it failed with a good error message. Trying again with a “domain\username” succeeded and now I have a “Service1” showing up in the service list (don’t forget to hit refresh).

Now to work backwards and figure out how to make the production service work:

1.      Ran “installutil /u WindowsService1.exe”.

2.      Changed the Account property of serviceProcessInstaller1 to “LocalService”.

3.      Ran “installutil WindowsService1.exe”

That got rid of the account credentials dialog.

1.      Ran “installutil /u WindowsService1.exe”

2.      Rebuilt the Setup1 project

3.      Verified Service1 was not installed

4.      Ran “Install” on the Setup1 project.

Silent failure.

Manually added “Primary output from WindowsService1 (Active)” to each Custom Actions category in the Setup1 project:

1.      Select the Setup1 project in the Solution Explorer.

2.      Click on the Custom Actions icon in the Solution Explorer toolbar.

3.      Right click on “Custom Actions” and select “Add Custom Action…”

4.      Expand the Application Folder (double click)

5.      Select “Primary output from WindowsService1 (Active)”

6.      OK.

7.      Rebuild and test.

That does it.

 

 

April 15, 2004

Interfaces and when what appears private is actually public.

If you’ve ever looked into the implementation details of CollectionBase, especially the IEnumerator support, or wondered why ICloneable appears to force you to return an object from Clone, you probably discovered Explicit interface member implementations which allow you to define a weakly typed method to satisfy the interface while also defining a strongly typed method.

I’m one of those people who doesn’t type “private” in front of every class field and method since I know it’s the default access modifier.

So when I see something like the following I assume it’s a private method, but it’s actually public in the sense that anyone with a reference to one of my objects can call it.

int IList.Add(object card) { return list.Add((Card) card); } // This method is public

 

Anyone can call the IList.Add method on one of my objects by using a cast to get at the IList interface. E.g.

CardCollection cards = new CardCollection();

((IList)cards).Add(someObject); // invokes what you may have thought was a private method.

 

If you consider that all interface methods must have public access, this makes sense and is desirable. I found it confusing that it is a compile error to put a “public” access modifier on an explicit interface method implementation. The default access modifier is private, and yet in this case the behavior is public.

If you simply implement a method that matches the signature of an interface method without using the explicit interface member syntax, you must explicitly declare it “public”.

 

 

April 13, 2004

Design “issues” with .NET

I came across the following reference while looking for a definitive statement on WHY the documentation says that you MUST follow the EventHandler / EventArgs pattern when declaring events and their delegates.

Here’s a good list of design issues with .NET. It’s not a new list (seems to be 2002 to 2003 vintage work) and unfortunately most of the points are still valid. Good reading for anyone doing software design against .NET. Reminds you of when the pain your feeling is due to other people’s design mistakes and the features you should avoid or compartmentalize.

 

EventHandler and EventArgs pattern

Okay, so who out there knows what pain will follow breaking the recommended EventHandler / EventArgs design pattern?

As far as I currently know, it’s purely a convention and motivated by an ill-defined intent of being general enough to handle all possible event data signaling requirements with a fixed set of parameters.

There doesn’t seem to be any compiler or debugger issue with using any combination of arguments you like for your delegate.

I’m guessing the designers figured events have a way of wanting to pass more information back to their clients over time and we don’t want our users’ code to break everywhere when they change their parameter list. So let’s train them to use derived classes to hold the actual parameters and then they’ll be in a good position to add more data through derivation without breaking clients who don’t care.

The drawback of this approach is that most times you have to experimentally guess what the actual type of the “sender” will be and whether it will ever be null or not. The same goes for the EventArgs derived parameter ever being null. The rule is return a default constructed EventArgs when you don’t have any data to include.

The thinking must be that someday in the future, you’re event source will start returning an EventArgs derived object with interesting data in it. You’ll then start writing client code that expects the argument to be non-null and of the derived type. If you completely fail to code defensively when accessing the parameter, your new client code could break if it ever runs against old event source code (which might return null). Seems like quite a stretch.

 

April 06, 2004

Inserting data into an Excel worksheet from a C# assembly

My first approach to inserting a new row of data into an existing Excel worksheet from an executing C# assembly was to use the interop assembly generated from the COM Microsoft Excel 11.0 Object Library.

I was successful in creating an application, opening a workbook, getting a reference to the worksheet and updating values in existing cells; but I failed to find a method for inserting a new row. The documentation for the automation interface is poorly organized and I may just have missed it.

My second approach was to use System.Data.Odbc. By assigning a name to the worksheet data range I knew that I could read and write data to the worksheet. The advantage of this approach is a looser coupling to the Excel document. So long as the named range and column names are unchanged, the code should continue to work.

Here are the “issues” I ran into before getting this to work:

·        The connection string must include a “ReadOnly=0” clause or you’ll get an exception with a message of “Operation must use an updateable query”. E.g.
Driver={Microsoft Excel Driver (*.xls)};DBQ=c:\Tone\Notebook\Data.xls;ReadOnly=0

·        The SQL query editor accessed through the Server Explorer of the Visual Studio Enterprise and Universal editions mis-parses column names such as “[Weight in pounds]”. It adds spaces before and after the braces resulting in a syntax error when you rerun the command.

·        The ODBC data type of an Excel column assigned a date format becomes OdbcType.Timestamp which isn’t convertible with a System.DateTime. Instead of using Parameters, I had to resort to String.Format with a format string of {0:yyyy-MM-dd hh:MM:ss} in single quotes for the DateTime.

·        I tried using the OdbcCommandBuilder to generate an Insert command for me to see how it handled the Timestamp column but couldn’t get it to generate them. Even after filling a DataSet using an OdbcDataAdapter, changing a value and calling Update, only the original Select statement appeared to be valid. By now I’m way over budget on getting this to work, so I’m leaving this mystery for the future.

 

March 22, 2004

Serializing DateTimes

A DateTime value doesn’t include time zone information. You must therefore keep track of the implied time zone by other means.

When DateTime values are serialized using the XML serializer, the default format includes the local machine’s time zone, which may not be correct, if for example your keeping all DateTime values in UTC.

On the flip side, deserializing a DateTime will return a value adjusted for the local time zone, taking the serialized time zone value into account.

Clemens Vasters has a nice detailed write up of the DateTime UTC serialization problem and answered my question of whether there was a way to tell the serializer how to handle the time zone.

 

March 02, 2004

Automating web site management, NAnt, WebDav, Windows 2003 Server VPN

Looking at NAnt, VPNs, WebDav, and other tools for automating web site management. What’s really needed is an index of each file on the site indicating where it came from and an integrated set of release tools/scripts to maintain and key off the index. Sounds like a description of software that exists out there and is priced at stratospherically ridiculous “enterprise” levels.

The goal is to be able to automatically and efficiently propagate a change to one of many Visual Studio projects or even in a folder of files to the web server. A related goal is to be able to automatically publish a new release of a project.

NAnt seems like a good framework for building the scripts, but it seems to lack a way of managing remote files. With a VPN to the server, that wouldn’t be a problem except potential efficiency issues if scripts can’t be written to minimize the amount of uploading.

For future reference, Sitecopy is a cygwin based tool for synchronizing a local file tree with a remote one and supports WebDav.

WebDav is a bit of a mystery. Windows 2003 Server allows WebDav to be enabled on a web site. Windows XP can then add Network Places that provide Windows Explorer level access to the remote files. There doesn’t seem to be any programmatic support in .NET for it though.

Downloaded independentsoft.de’s .NET webdav support. It didn’t appear to work initially. Couldn’t copy a file or list folder contents. Fired off an e-mail to support which they responded to quickly. Unfortunately the library is obfuscated which makes trying to guess what the problem is hard. They’ll give you source code access but for much more money. Turns out they may only have a bug accessing the root folder of a site. My other difficulties were due to not realizing that Write and Directory browsing permissions were required by WebDav. They aren’t required for “Web Folder” access which Microsoft seems to link with WebDav but appears to march to a different drummer.

To “enable” WebDav on IIS 6.0, the first step is to enable it generally as a configured web service, and second, to enable Write and Directory browsing access via IIS for each site/vroot to be accessed. It appears as long as there is a valid default document, HTTP requests that specify only the folder do not generate folder listings. I have not confirmed, but suspect, that Windows ACLs combined with WebDAV NetworkCredentials can effectively restrict the users with WebDav access. WebDav traffic is encrypted only if the site supports SSL. Otherwise passwords “may” be encrypted, presumably only if integrated windows authentication is used and plain text credentials aren’t passed explicitly.

Getting a VPN up between my collocated Windows 2003 Server and my Windows XP workstation required adding Routing and Remote Access as a server role, configuring a range of static IP addresses (don’t configure DHCP unless its needed for other reasons) on a different subnet from my home network, and punching a hole through my firewall for PPTP, IPSec, and L2TP (UDP 500, 4500, 1701). There’s an attitude in the security space that non-hardware-SSL based VPN’s are problematic. Probably a combination of security and performance issues.

While waiting for progress on the VPN & WebDav fronts I started coding a custom file tree merge Web Service. I was in the middle of this when I happened across John Shewchuk’s MSDN TV interview about Indigo. Quite amusing to have him comment on how Indigo was going to help facilitate just the kind of plumbing work I was hacking away at.

 

 

March 01, 2004

NAnt 0.84 installation and initial test problems, 2004-03-01 17:39:26 Monday

To change the default .NET framework to 1.1 instead of 1.0, edit the NAnt.exe.config file found in the bin folder and change <platform name="win32" default="net-1.0"> to <platform name="win32" default="net-1.1">.

The test at line 221 of tests\NAnt.Console.NAntTest.cs fails because the copyright year hasn’t been bumped up to 2004 yet. Comment it out for now.

The C++ tests fail because vsvars32.bat wasn’t automatically configured and the cl task fails.

I followed this registry tweak to automatically run vsvars32.bat whenever cmd.exe is run.

 

 

webinfo files simplify locating ASP.NET projects with Visual Studio .NET

If you’ve ever had trouble adding an existing web application to a solution when the application isn’t under the c:\inetpub\wwwroot folder, adding a webinfo file to the project may help. Typically I have this problem when I use source control to copy my development files to a new machine and need to re-establish proper registration with IIS. It sometimes seems simplest to remove references to projects in a solution that aren’t loading on the new machine and to add them again as existing projects.

The typical error message I get when adding the existing project is:

The project you are trying to open is a Web project. You need to open it by specifying its URL path.

 

The trick to successfully adding an existing project is to use a file path with a webinfo file instead of an http path, despite what the error message says.

When you create a new web application using Visual Studio .NET 2003 a webinfo file is created automatically for you. This file defines the web path to the application. If the file is missing, or the contents of the file no longer agree with the configuration of your web server, you will have trouble adding the project.

For example, say you have http://localhost/QEs as a virtual directory to “c:\Projects\Quick Experiments” and you have a web application in the folder “c:\Projects\Quick Experiments\WebApp1”.

The project filename is “WebApp1.csproj” and the corresponding webinfo filename is “WebApp1.csproj.webinfo” with the contents:

<VisualStudioUNCWeb>

    <Web URLPath = "http://localhost/QEs/WebApp1.csproj" />

</VisualStudioUNCWeb>

 

Once you’ve added this file to the project folder, you should have no trouble adding the project to a solution by specifying the file path “c:\Projects\Quick Experiments\WebApp1\WebApp1.csproj”.

For no good reason of which I’m aware, this seems to work far more reliably than trying to add the project with the web path of “http://localhost/QEs/WebApp1/WebApp1.csproj”

 

February 23, 2004

Good COM Interop Article Series

Beyond (COM) Add Reference: Has Anyone Seen the Bridge? and the follow up article Using the .NET Framework SDK Interoperability Tools by Sam Gentile.

Useful stuff when you have to use a .NET un-friendly COM component.

 

February 22, 2004

Acquiring images from USB connected cameras

The new API for doing this is WIA, Windows Image Acquisition. Older devices may not have WIA drivers (my cheap web cam doesn’t). The WIA scripting library can be used from .NET but it is not strongly typed and has manual wrapper and dispose requirements.

TWAIN is the older API. It may require a windows message pump. My web cam has a TWAIN compatible driver.

WIA provides TWAIN compatibility so a TWAIN application will see WIA devices. An application that speaks both TWAIN and WIA will see the WIA devices twice.

An example using each API is found on CodeProject: http://www.codeproject.com/dotnet/wiascriptingdotnet.asp

WIA scripting can capture an image from simple still cameras but not directly from video cameras (like my Sony DTRV20). Instead, use the support for video to create an object with access to the video stream coming from the device and use a method of that interface to snap individual pictures.

 

 

‘Unable to start debugging’ ASP.NET application

I typically run into the “Unable to start debugging” error message after when trying to continue development of a source controlled an ASP.NET application for the first time on a newly configured system with tight file access security.

Giving “everyone” read access does the trick. What’s the minimum it needs?

 

February 11, 2004

.NET COM Add-In installs but doesn’t run from the IDE

Managed Add-Ins built with Visual Studio .NET use the DLL mscoree.dll to load.

 

Microsoft is aware of a problem affecting some developers where the add-in works fine when built and installed by an add-in setup project, but fails to load when recompiled by the IDE. What drives this behavior is a registry key value that is written with a full path by the installer but without a path by the IDE’s build process.

 

Every add-in class has to have a GUID registered under HKCR\CLSID in the registry. The registration includes an “InprocServer32” key which points to the mscoree.dll.

 

If the value includes a full path, the add-in always loads (all other issues aside). If the value is just “mscoree.dll”, the add-in only works for some people. It is not known at this time by Microsoft what configuration differences cause this behavior.

 

When you get tired of the build, install, run manually, attach debugger cycle, which does work reliably, and wish you could just press F5 and hit your breakpoint, do the following:

 

-         Add a new text file to your solution with the name “FixMscoree.vbs”.

-         Edit the following into the file, replacing the guid with the one your add-in uses:

' Run with project build event: cscript $(SolutionDir)FixMscoree.vbs

Dim wshShell,wshEnv,sPath

 

Set wshShell = CreateObject("Wscript.Shell")

set wshEnv = wshShell.Environment("process")

sPath = wshEnv("SYSTEMROOT")

sPath = sPath & "\System32\mscoree.dll"

 

'guid is of the form {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

wshShell.RegWrite "HKCR\CLSID\{77C3AFCA-8AF6-4b7f-8D7C-657CDB17856E}\InprocServer32\", sPath, "REG_SZ"

 

Set wshEnv = Nothing

Set wshShell = Nothing

-         Set ProjectàPropertiesàBuild EventsàPost-build Event Command Line to “cscript $(SolutionDir)FixMscoree.vbs

 

To the best of my knowledge there are no relevant knowledgebase articles on this problem yet. If you know of one, please post or send the reference.

 

February 09, 2004

Is it just me or should the ErrorProvider provide and indexer or a HasErrors property.

This thread from the managed MS discussion forum captured on .NET247 indicates I’m not alone (and that MS isn’t jumping up and down over the idea!), but it would be very helpful for the ErrorProvider to expose its internal collection of error strings assigned to controls in some way.

A simple “bool HasErrors” property would be great.

 

February 04, 2004

Post to your .Text blog using this add-in for Word

I released the alpha version of my blog posting add-in for Microsoft Word 2003 or XP as a GotDotNet workspace: http://workspaces.gotdotnet.com/TonesNotes

TonesNotes is a free and lets you post to a .Text blog from any open Word document.

The intended mode of operation is to use a single Word document in which the ideas you’re working on take shape. When an idea is baked, place the insertion point within its content, click the “Post” button on the TonesNotes toolbar, and the content is simultaneously posted to one or more blogs and archived as a separate Word document on your local file system.

The command bar has a “New” button which starts a new entry at the bottom of the active Word document. An entry is demarked by a paragraph with the style “Heading 1” by default. The “New” command inserts a paragraph with a date stamp which will become the post creation time. Complete the note heading with a comma separated list of categories and a title. Anything in the note heading paragraph not recognized as a date or category name becomes part of the title.

Since not all ideas are suitable for public posting, the behavior of the Post command is driven by assigning a category name to each configured blog. A note is posted to each blog whose category name is listed in the entry heading, or simply archived if it isn't meant to be blogged.

In addition to the “New” and “Post” functions, the toolbar has a “Config” button which brings up the configuration dialog.

Here’s a topic page in the .Text wiki that lists pros, cons, and configuration for various authoring options including TonesNotes: http://dottextwiki.scottwater.com/default.aspx/Dottext.AuthoringOptions

 

 

January 31, 2004

Copy Constructors, MemberwiseClone, and the ICloneable Interface

After looking at the options a bit, here are some conclusions about Copy Constructors, MemberwiseClone, and the ICloneable Inerface in C#:

1.       Generally favor factory methods (like Clone and Deserialize) over copy constructors unless you’re really aiming at value semantics. They’ll be more consistent with the way serialization works and the existing patterns.

2.       It’s easy to implement a Clone method in terms of a copy constructor and impossible to do the opposite, but…

3.       Object.MemberwiseClone supports writing Clone methods and not copy constructors.

4.       Like many things in the pre-generics flavor of C#, the “object” return type of ICloneable.Clone is unfortunate, but just deal with it.

5.       Don’t overlook using serialization if you just need some kind of copy operation.

 

January 30, 2004

Getting correctly encoded HTML from Microsoft Word in a .NET implemented COM Add-in

In the context of a COM Add-in for Microsoft Word 2003, I wanted to convert selected content in a Word document to an HTML string for shipment up to a web service.

The first approach was, in C#:

                  Selection.Cut();

                  IDataObject cdo = Clipboard.GetDataObject();

                  body = (string) cdo.GetData(DataFormats.Html);

Which fails on things like the copyright symbol and smart quotes. It appears that the wrong encoding is being applied to the clipboard data on the way to creating what should be a Unicode string.

To get a bit more control, the second approach saved a temporary document as FilteredHTML and then read it back in as a string.

Looking at the content of the temporary files it appeared that they were being encoded using the windows-1252 character set. Since UTF-8 and Unicode encodings were failing, I leaped to the conclusion that the code page 1252 encoding might do the trick and voila:

                  using (FileStream fs = File.OpenRead(Path.Combine(path, filename))) {

                        byte[] bytes = new byte[fs.Length];

                        fs.Read(bytes, 0, (int)fs.Length);

                        html = Encoding.GetEncoding(1252).GetString(bytes);

                  }

 

This produces a Unicode string “html” which gets symbols such as €£¥©®™±≠≤≥αβ right.

 

Word add-in supporting posting to Dottext from Word

When I'm working, my usual mode of operation is to have a Word document open (typically Notebook.doc) in which I'll have several active entries. We rarely get to work on one thing at a time anymore, and that's reflected in my note taking.

To start a new entry, I hit a control-alt-A, and a macro inserts the start of an entry header at the end of the file. I complete the header by typing a comma separated list of categories and a short descriptive title; all in one paragraph; hit enter and start the body of the entry.

As I bounce between tasks, I'll edit the body of the corresponding entry and maybe the header as well.

Eventually the entry is done and it needs to be archived and optionally posted to a blog.

I archive each entry as an ordinary Word document in a tree of folders on my laptop. This is my primary repository since it is secure, accessible off-line and can handle rich content. The search function of Windows Explorer is also useful for finding old entries.

If the entry is suitable for posting to a blog, I add the name of the blog to the header and it gets posted at the same time.

This is the first real entry made this way, I'll be adding more information and sharing the implementation once it gets more fully baked.

January 21, 2004

Initial experiences with FlexWiki setup

I installed FlexWiki on my public web server under http://wiki.toneengel.com without a hitch.

The next step was to enable secure access to a client namespace.

I settled on Windows Integrated security because Forms isn’t really secure without SSL, Passport is too expensive, and Windows Integrated is secure and relatively easy to enable.

I’ve discovered two problems with Windows Integrated security for this application:

1.       There doesn’t seem to be any browser level support for changing passwords which means I’ll end up having to manually build some kind of password add/change user functionality which will again leave application code handling user passwords.

2.       A login dialog appears separately for each web site with Windows Integrated security enabled and access restricted. I was initially storing restricted image content on one site and referring to them from my wiki site. Created a separate folder in the wiki namespace root folder to store downloadable content so that it would inherit the same ACLs as the wiki.

Along the way to getting the wiki namespace setup and after creating a bunch of pages I discovered that I’d chosen a poor namespace naming convention which led to moving the pages from one namespace root folder to a new one. This probably should have been painless except along the way to settling on ACLs I’d left things such that all the content was inaccessible from the administrator account used to manage the web server. After taking ownership of the content and moving it and fixing a few links, everything worked great except for one thing.

I’m now noticing that when I click Edit to begin modifying an existing page FlexWiki very often seems to get confused about the presence of an existing page and displays the new blank page template and the new page creation notice. This is particularly likely if the browser’s refresh or back commands were used prior to clicking Edit. Following wiki links to the page always seems to make Edit work correctly. Very strange.

2004-01-22 09:01 Thu, The beginnings of understanding.

They key is that some operations trigger a Windows Authentication login and others don’t.

Actions that trigger login:

Requesting a ACL restricted page. (with or without the IIS Anonymous Access enabled)

Requesting a page from a site or subfolder that has Anonymous Access disabled.

Things that don’t trigger login:

                File.OpenRead, File.OpenWrite, File.Exists, new DirectoryInfo of an ACL restricted file or location.

January 20, 2004

Web Site Security, Authentication

Good MSDN overview topic: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsent7/html/vxconaspnetauthentication.asp

Use System.Threading.Thread.CurrentPrincipal to obtain and IPrincipal interface, then Idendity for an IIdentity interface, which has properties Name, IsAuthenticated, AuthenticationType.

System.Security.Principal.IIdentity i = System.Threading.Thread.CurrentPrincipal.Identity;

Trace.Write("Authentication", string.Format("Name={0} Is={1} Type={2}", i.Name, i.IsAuthenticated, i.AuthenticationType));

 

The web.config file defines which authentication modules will be activated for an ASP.NET application.

Windows is the default.(?)

Windows is secure and easy in terms of coding; a bit tricky to ensure correct behavior; and only handles IE >5.x browsers. It uses Kerberos if the server is running Active Directory, otherwise NTLM.

Passport is a commercial solution. Licensing is $10k per year per company + $1.5k per year per URL. Coding is required. Start by downloading the SDK.

Forms is the most flexible and requires the most work to make secure. SSL is required for any real security. Requires clients to trust each web site with their un-encrypted password.

Windows Authentication Details

Add the following to web.config as children of the system.web element:

<authentication mode="Windows" />

<identity impersonate="true"/>

 

I still haven’t experimentally confirmed the behavior of identity impersonation.

The authorization element can be used to add allow and deny elements. "*" means all users. "?" means the anonymous user. Both lists are supposed to allow comma separated lists of users. I haven’t determined the required syntax yet. E.g. with or without domain or machine prefix, fully qualified or not, etc.

<authorization>

    <allow users="*" />

    <deny users="?" />

</authorization>

 

You can also put a block like the following at the configuration element child level to put constraints on a specific path. If the path is omitted, the constraint is applied to the folder containing the web.config file.

<location path="consulting.aspx">

   <system.web>

       <authorization>

        <allow users="*" />

    </authorization>

   </system.web>

 </location>

 

Access control is a combination of file system ACLs, IIS security settings, and web.config authorization elements.

 

ACL

IIS Directory Security à

Authentication & Access Control

web.config

Baseline Settings
Public access

Everyone: full control

INTERACTIVE: Read & Execute (folders only)

NETWORK: Read & Execute (folders only)

SYSTEM: Read (folders only) & Execute (folders only)

Anonymous access enabled.

Integrated Windows authentication.

No authorization elements: access allowed.

<deny users="?" />: forces authentication then allows access.

Access Restricted to Group

Group: full control

Administrators: full control

SYSTEM: Read (folders only) & Execute (folders only)

Anonymous access DISABLED.

Integrated Windows authentication.

No authorization elements: access is allowed only after login by group member.

Could not simply add the Administrator account to the Group. For some reason this doesn’t work.

<allow users="*" />: BOGUS, can’t give privileges the ACL doesn’t allow.

<deny users="*" />: Denies access to everyone. (UNSURE THIS ALWAYS WORKS)

 

Microsoft Passport authentication service licensing fee

The cost for using Microsoft's Passport authentication service on a web site is roughly $10,000 per year per company plus $1,500 per year per URL.

See the Passport Review Guide .

January 16, 2004

SCC Switch Provider, GotDotNet Source Control

Joined the FlexWiki GotDotNet group last night for source code access which is provided through a custom source code control provider that integrates with Visual Studio.

That led to looking for an easy tool to switch SCC between GDN and Perforce. Such a tool existed on GDN but only as a rather anonymous exe, so I used Lutz Roeder’s Reflector to reverse engineer it into a new C# project.

Only snag was not calling the Microsoft.Win32.Registry.LocalMachine.OpenSubKey method with a second argument of true to indicate that write access was required. That led to a rat chase on security and permission settings.

SCC Switch Provider: C# Visual Studio project to change source control provider.

Joined the FlexWiki GotDotNet group last night for source code access which is provided through a custom source code control provider that integrates with Visual Studio.

That led to looking for an easy tool to switch SCC between GDN and Perforce. Such a tool existed on GDN but only as an exe, so I used Lutz Roeder’s Reflector to reverse engineer it into a new C# project. Download.

Remember to call the Microsoft.Win32.Registry.LocalMachine.OpenSubKey method with a second argument of true to indicate that write access is required when writing to a key.

January 13, 2004

Excellent source tree comparison and differencing tool: WinMerge

To speed up integrating Scott's latest Dottext changes with my own mods I went looking for an easy way to pinpoint changes in trees of source files.

WinMerge does the job well and its free. Highly recommended.

January 11, 2004

Controlling Internet Explorer caching of static content

HTTP Headers

Cache-control: post-check=60,pre-check=3600

Post-check means update the content after returning what you’ve got if necessary.

Pre-check means update the cache if necessary before returning the content.

Expires:

Time in minutes from retrieval to expiration. Stored as an expiration time with cached content.

Setting HTTP Headers

Programmatically when constructing a response.

      Response.Expires = (int) TimeSpan.FromMinutes(1.0).TotalMinutes;

In HTML. <META HTTP-EQUIV="REFRESH" CONTENT="0;URL=foo.aspx">

In IIS under page, folder, and site properties à HTTP Headers.

Behavior

With an HTTP Expires header value of one, a new page is fetched from the server if the locally cached copy is more than one minute stale.

Note that requesting a refresh in the browser always goes to the server.

Just entering the address again and pressing Go (or typing Enter) will only go to the server if Expires is set and has expired.

The IE cache behavior is the same between “.htm” and “.aspx” content.

With an Expires HTTP header, a new browser process will return a client cached page if it’s not expired.

Without an Expires HTTP header (None), a new browser always fetches a page the first time, even if it is in the cache.

Subsequently, enter will not fetch but refresh will.

In IE, ToolsàInternet Options…àSettings…àView Files… brings up a Windows Explorer view of cached content including expiration datetimes.

Cached IE content is stored here by default: C:\Documents and Settings\{profile_name}\Local Settings\Temporary Internet Files.

January 08, 2004

Visual Studio option to open .ascx files in HTML mode by default

 The Visual Studio option to open .ascx files in HTML mode by default is under:

Tools->Options->HTML Designer->General

Fixing inaccessible object owners in a restored SQL Server database

After restoring a backup of a hosted database using SQL Server Authentication I was unable to access all the objects (tables and stored procedures).

I guessed that the problem stemmed from the “User” not appearing under Security as a “Login”. I can create an identically named Login but it appears to remain separate from the User, complaining if I attempt to give it access to the restored database.

 This MSDN article contains a stored procedure that should be able to change the object owner on all objects in a database.

Executing the stored procedure runs through all the correct objects without error, but doesn’t change anything.

Reading the fine print confirms that it only prints out the commands that should do the actual work. Copy the output and execute it.

If you execute all the statements printed, expect to see a variety of error messages: Object ‘foo’ does not exit… These appear to be caused by key, index, and trigger ownership change being effected by a prior table ownership change.

 

January 06, 2004

Hashing with Salt, SHA-1, Base64, MD5

Hashing with Salt, SHA-1, Base64, MD5

November 19, 2003

Permission Requests

Since the syntax of permission requests is obscure and poorly documented, here’s a list of references:

[assembly:EnvironmentPermissionAttribute(SecurityAction.RequestMinimum, Read="USERNAME")]

[assembly:FileIOPermission(SecurityAction.RequestMinimum, Unrestricted=true)]

[assembly:IsolatedStorageFilePermission(SecurityAction.RequestMinimum, UserQuota=1048576)]

[assembly:SecurityPermission(SecurityAction.RequestRefuse, UnmanagedCode=true)]

[assembly:FileIOPermission(SecurityAction.RequestOptional, Unrestricted=true)]

 

Using System.Reflection.GetConstructor() was failing (returning null) for a type with a public default constructor because declarative security in the Assembly.cs file was denying FileIOPermission. With Unrestricted=true FileIOPermission it works fine. Doesn’t seem to need MemberAccess=true on ReflectionPermission. Oddly, over 500 MemberInfo instances were returned by a call to GetMembers at the same time the GetConstructor & GetConstructors calls were returning nothing. Tested whether it mattered that the constructor was accessing the file system. It did not.

 

[assembly:ReflectionPermission(SecurityAction.RequestMinimum,MemberAccess=true)]

[assembly:FileIOPermission(SecurityAction.RequestMinimum, Unrestricted=true)]

 

November 17, 2003

Visual Studio .NET 2003 solution files with corrupted source control settings

Spent entirely too much time today figuring out why Visual Studio .NET 2003 solutions sometimes get wedged in a "some of the properties associated with the solution could not be read" state.

The solutions appear to be okay otherwise, but each time they are opened, you get the error message.

The problem is introduced when projects are added to the solution. I’ve observed it with web application projects already under Perforce source control.

The solution file (.sln) will have a SccNumberOfProjects value greater than the actual number of projects and there will be duplicates of some projects. The duplicates are typically missing the SccProjectFilePathRelativizedFromConnection# property.

To fix the problem (or at least to get rid of the error message):

1.       Adjust the SccNumberOfProjects property value to the expected number.

2.       Delete duplicate SourceCodeControl information. The usual sequence of properties is something like:

         GlobalSection(SourceCodeControl) = preSolution

         SccNumberOfProjects = 11

         SccProjectName0 = Perforce\u0020Project

         SccLocalPath0 = .

         SccProvider0 = MSSCCI:Perforce\u0020SCM

         CanCheckoutShared = true

         SolutionUniqueID = {A0C7BB98-83C3-491B-9A97-CDE906E50E22}

 

         SccProjectUniqueName1 = http://localhost/jq/h/JobQuake\u0020Help.csproj

         SccLocalPath1 = JobQuake\u0020Help

         CanCheckoutShared = true

         SccProjectFilePathRelativizedFromConnection1 = http://localhost/jq/h

 

         SccProjectUniqueName2 = http://localhost/jq/a/JobQuake\u0020Admin.csproj

         SccLocalPath2 = JobQuake\u0020Admin

         CanCheckoutShared = true

         SccProjectFilePathRelativizedFromConnection2 = http://localhost/jq/a

 

3.       Adjust the trailing indices on SccProjectUniqueName, SccLocalPath, and SccProjectFilePathRelativizedFromConnection to run from 1 to SccNumberOfProjects, inclusive.

 

November 13, 2003

Web Application Virtual Directory Binding

The .csproj.webinfo file should be in source control. It binds the folder to an IIS virtual directory. Typical contents are:

<VisualStudioUNCWeb>

    <Web URLPath = "http://localhost/csk/csk.csproj" />

</VisualStudioUNCWeb>

November 10, 2003

Lessons learned from FxCop

Add the following to Assembly.cs

[assembly:System.CLSCompliant(false)]

[assembly:System.Runtime.InteropServices.ComVisible(false)]

 

Specify an assembly KeyFile:

[assembly: AssemblyKeyFile(@"..\..\..\..\kzKey.snk")]

 

Set the projects “Wrapper Assembly Key File” property to assign strong names to automatically generated interop assemblies.

October 20, 2003

VB vs. C# Comparison

The purpose of this table is to save time for people who spend most of their time with one language and need a quick reminder on analagous syntax in the other language.

Based on an original table by Dan Appleman.

VB.NET

C#

Comments

NotInheritable

sealed

Specifies that a class cannot be used as a base class, i.e. that it cannot be inherited.

NotOverridable

sealed

Specifies that a method cannot be overridden.

MustInherit

abstract

Specifies that a class can only be inherited (an instance of the class cannot be created).

MustOverride

abstract

Specified that a method must be implemented in a derived class.

Overridable

virtual

Specifies that a member can be overridden.

Shared

static

Specifies that a member is shared by all instances of a class. An instance of the class is not required to call the member.

Static

no equivalent

Specifies that a local variable's value is preserved between calls.

Public

public

Class/member is visible outside of project or assembly.

Friend

internal

Class/member is invisible outside of the assembly.

Private

private

Class/member is visible only within the project.

Overloads

not required

Specifies that a member is overloading another member.

Overrides

override

Specifies that a member is overriding another member.

Implements I1

class C1:I1

Specifies that the class (C1) implements the interface I1.

Inherits C2

class C1:C2

Specifies that the class (C1) inherits class C2.

Implements I1

Inherits C2

class C1:C2,I1

Specifies that the class (C1) implements the interface I1 and inherits class C2.

Shadows

new

 

Finalize

~C1 (destructor)

Method called by system just before garbage collection reclaims object. C1 is classname.

New

C1

Constructor method, called when object is created. C1 is classname.

Dim x as Int32

Int32 x

Declares variable x as type System.Int32.

Dim t1 As New SomeType()
Try
t1.SomeMethod()
Finally
If (t1 Is Nothing) Then
If TypeOf t1 Is IDisposable Then
CType(t1, IDisposable).Dispose()
End If
End If
End Try

using(SomeType t1 = new SomeType())
{
t1.SomeMethod;
}

 

Imports

using

Allows methods to be called without fully qualifying with Namespace name.

<> 

[]

Specifies parameters.

Function x as Int32

Sub y

Int32 x

void y

Specifies return values (Int32 and none).

_

;

Line continuation character in VB, end of line character in C#.

AndAlso

OrElse

&&

||

Short circuit versions of And and Or.

not supported

<< >>

Shift operators.

X+=1 (x=x+1)

X-=1 (x=x-1)

also *=, /=, ^= etc..

x++

x--

Short cut incrementers.

Dim x(4) as Int32

= 5 items (0 to 4)

Int32[] x = new Int32[4];

= 4 items (0 to 3)

Differences in array declarations and the number of elements created.

Dim x as Int32

Int32 x = 0

x initialized as zero automatically in VB.