November 12, 2008

How to access the IIS Metabase programmatically, IIS metabase

The IIS Metabase is XML representation of the IIS instance, it's actually an XML file stored in IIS. It can be access using apps like MetaEdit or programmatically using the Active Directory through a class System.DirectoryServices.DirectoryEntry.

using System.Collections.Generic;
using System.Text;

using System;
using System.Data;
using System.Configuration;


using System.DirectoryServices;
using System.Web.Configuration;

namespace IISMetabase
{
class IISMetabase
{
private string _machineName = "localhost";
private System.Collections.IDictionary _applications;//collection of websites
private Configuration _config;

public IISMetabase()
{
_applications = new System.Collections.Hashtable();
}

public System.Collections.IDictionary GetApplications()
{
DirectoryEntry webentry = new DirectoryEntry();
String path = "IIS://" + _machineName + "/W3SVC/1/ROOT";


webentry.Path = path;
Boolean exists = false;
try
{
exists = DirectoryEntry.Exists(path);
}
catch (System.Runtime.InteropServices.COMException e)
{
String error = e.Message;
Console.WriteLine(error + "\n");
}

if (exists)
{
DirectoryEntries webSiteChildren = webentry.Children;


foreach (DirectoryEntry website in webSiteChildren)
{
String scn = website.SchemaClassName;
String se = website.SchemaEntry.ToString();

if (website.Properties.Contains("KeyType"))
{
String webName = website.Name;

try
{
Object keyTypeValue = website.InvokeGet("KeyType");
String keyTypeValueStr = keyTypeValue.ToString();
if (!keyTypeValueStr.Equals(""))
{
try
{
_config = WebConfigurationManager.OpenWebConfiguration("/" + webName);

_applications.Add(webName, _config);
}
catch (ConfigurationErrorsException err)
{
String error = err.Message;
Console.WriteLine(error + "\n");
}
}
}
catch (Exception err)
{
Console.WriteLine(err.Message + "\n");
continue;
}
}
}

}
else
{
String err = "The Directory " + webentry.Path + "does not exist\n";
Console.WriteLine(err + "\n");
}

return _applications;
}

}
}

August 15, 2008

ActiveX Stopwatch Timer Control

Here's an example of a StopWatch control implemented in Csharp and used as a control on a web client. The control needs to be register on the clients machine then added to the clients web application (in html) and then called from Javascript.

<OBJECT id="activeXTimerControl" name="activeXTimerControl" 
        classid="clsid:4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7" 
        style="width: 504px; height: 182px" 
       codebase="Bin/ActiveXTimerControl.dll" class="ActiveXTimerControl.TimerControl">
       </OBJECT>

To start the Stopwatch call Start() and to end call End() then call Time() to return the result as a string or use the AddTimeToGrid to add the time to a DataGrid Control (supplied by the StopWatch control) or call AddTimeToLabel which adds the time to a textbox.
activeXTimerControl.Start();
///do someting
activeXTimerControl.Stop();
var time = activeXTimerControl.Time;
activeXTimerControl.AddTimeToGrid("my timer", time);
activeXTimerControl.AddTimeToLabel("my timer", time);

Here's the full csharp code and at the end the full html


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;

namespace ActiveXTimerControl
{
[ClassInterface(ClassInterfaceType.AutoDual)]
[GuidAttribute("4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7")]
public partial class TimerControl : UserControl, IActiveXTimerControl
{
public TimerControl()
{
InitializeComponent();
}
}
}


namespace ActiveXTimerControl
{
public interface IActiveXTimerControl
{
///
/// Add resulting timer name and time (as a string) to the GridView.
///
///
///
void AddTimeToGrid(string timerName, string time);
///
/// Add the resulting timer name and the time (as a string) to the Label.
///
///
///
void AddTimeToLabel(string timerName, string time);
///
/// Start the timer.
///
void Start();
///
/// Stop the timer, call Start() before this.
///
void Stop();
///
/// Get the Time between the Start() and Stop() in milliseconds.
///
string Time { get; }
}

partial class TimerControl : IActiveXTimerControl
{
///
/// Required designer variable.
///
private System.ComponentModel.IContainer components = null;

///
/// Clean up any resources being used.
///
/// true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Component Designer generated code

///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this._timerDataGridView = new System.Windows.Forms.DataGridView();
this._timerNameColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this._timerTimeColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this._timerLabel = new System.Windows.Forms.Label();
this._timerTextBox = new System.Windows.Forms.TextBox();
((System.ComponentModel.ISupportInitialize)(this._timerDataGridView)).BeginInit();
this.SuspendLayout();
//
// _timerDataGridView
//
this._timerDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this._timerDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this._timerNameColumn,
this._timerTimeColumn});
this._timerDataGridView.Location = new System.Drawing.Point(6, 108);
this._timerDataGridView.Name = "_timerDataGridView";
this._timerDataGridView.Size = new System.Drawing.Size(261, 106);
this._timerDataGridView.TabIndex = 0;
//
// _timerNameColumn
//
this._timerNameColumn.HeaderText = "Timer Name";
this._timerNameColumn.Name = "_timerNameColumn";
//
// _timerTimeColumn
//
this._timerTimeColumn.HeaderText = "Time (msec)";
this._timerTimeColumn.Name = "_timerTimeColumn";
//
// _timerLabel
//
this._timerLabel.AutoSize = true;
this._timerLabel.Location = new System.Drawing.Point(223, 78);
this._timerLabel.Name = "_timerLabel";
this._timerLabel.Size = new System.Drawing.Size(129, 13);
this._timerLabel.TabIndex = 1;
this._timerLabel.Text = "Timer Name | Time (msec)";
//
// _timerTextBox
//
this._timerTextBox.Location = new System.Drawing.Point(6, 20);
this._timerTextBox.Multiline = true;
this._timerTextBox.Name = "_timerTextBox";
this._timerTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this._timerTextBox.Size = new System.Drawing.Size(261, 55);
this._timerTextBox.TabIndex = 2;
this._timerTextBox.Text = "Timer Name | Time (msec)";
//
// TimerControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this._timerTextBox);
this.Controls.Add(this._timerLabel);
this.Controls.Add(this._timerDataGridView);
this.Name = "TimerControl";
this.Size = new System.Drawing.Size(383, 313);
((System.ComponentModel.ISupportInitialize)(this._timerDataGridView)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();

}



#endregion



public void Start()
{
if (_stopWatch == null)
_stopWatch = new System.Diagnostics.Stopwatch();
_stopWatch.Start();
}

public void Stop()
{
_stopWatch.Stop();
}
public string Time
{
get
{
return string.Format("{0}", _stopWatch.ElapsedMilliseconds);
}
}

private System.Diagnostics.Stopwatch _stopWatch;

private System.Windows.Forms.DataGridView _timerDataGridView;
public void AddTimeToGrid(string timerName, string time)
{
if (_timerDataGridView == null)
_timerDataGridView = new System.Windows.Forms.DataGridView();
_timerDataGridView.Rows.Add(timerName, time);
this.Refresh();

}

private System.Windows.Forms.DataGridViewTextBoxColumn _timerNameColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn _timerTimeColumn;

private System.Windows.Forms.Label _timerLabel;
public void AddTimeToLabel(string timerName, string time)
{
//if (_timerLabel == null)
// _timerLabel = new System.Windows.Forms.Label();
//_timerLabel.Text += ( "\n" + timerName + " " + time);
//this.Refresh();
if (_timerTextBox == null)
{
_timerTextBox = new System.Windows.Forms.TextBox();
_timerTextBox.Text = "Timer Name | Time (msec)";
}
_timerTextBox.Text += ("\r\n" + timerName + " " + time);
this.Refresh();
}

private System.Windows.Forms.TextBox _timerTextBox;

}

}




The aspx:



<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>

<OBJECT id="activeXTimerControl" name="activeXTimerControl" classid="clsid:4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7" style="width: 504px; height: 182px" codebase="Bin/ActiveXTimerControl.dll" class="ActiveXTimerControl.TimerControl">
</OBJECT>


<form name="frm" id="frm">
<input type=button value="Click me" onClick="doScript();">
</form>


</body>

<script language="javascript">
function doScript()
{
activeXTimerControl.Start();

activeXTimerControl.Stop();
var time = activeXTimerControl.Time;
activeXTimerControl.AddTimeToGrid("my timer", time);
activeXTimerControl.AddTimeToLabel("my timer", time);




}
</script>

</html>



August 14, 2008

AcitveX Controls - how to create one and call with Javascript

One way to get a .NET object to run in a browser is to create a User Control in .NET.
You can then call this User Control from Javascript in your client.

There's nothing magical about this except, it only works on IE and there are a few small bits that you must implement in order for your User Control to be picked up, the errors you get aren't very helpful so I recommend following these steps (Some of these I'm not sure if they're required but they've worked for me).

  1. Create a Class Project in Visual Studio 2005.
  2. Add a User Control to the project.
  3. In the User Control Designer add a TextBox Control
  4. Add a Property to your class to set the Text of the TextBox you've just added.
public void AddText(string time)
{
if (_timerTextBox == null)
{
_timerTextBox = new System.Windows.Forms.TextBox();
_timerTextBox.Text = " Time (msec)";
}
_timerTextBox.Text += ("\r\n" + time);
this.Refresh();
}
  1. Create an Interface in the same namespace as your UserControl and add the signature of the Property you've just added.
public interface IActiveXTimerControl
{
void AddText( string time);
}
  1. Mark the Class with a the GuidAttribute and also inherit it from the interface you've just created.
namespace ActiveXTimerControl
{
[ClassInterface(ClassInterfaceType.AutoDual)]
[GuidAttribute("4F3B57CE-XXXX-XXXX-ACBD-EBB2380990E7")]
public partial class TimerControl : UserControl, IActiveXTimerControl
{
public TimerControl()
{
InitializeComponent();
}
}
}
  1. Mark the Assembly as Com Visible in the Project Properties "Assembly Information".
  2. Compile this and register it by adding a Post-Build Event to your project
  3. regasm $(TargetFileName) /tlb /codebase

You now have an Assembly which has a control usable in a browser.
Now to use this Control
Create a website and add the following aspx page (the content of this could also be in html page) inside the body




<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>

<OBJECT id="activeXTimerControl" name="activeXTimerControl" classid="clsid:4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7" style="width: 504px; height: 182px" codebase="Bin/ActiveXTimerControl.dll" class="ActiveXTimerControl.TimerControl">
</OBJECT>

<form name="frm" id="frm">
<input type=button value="Click me" onClick="doScript();">
</form>


</body>

<script language="javascript">
function doScript()
{
activeXTimerControl.Start();
activeXTimerControl.Stop();
activeXTimerControl.AddText("time");

}
</script>

</html>



Note the clsid above this is crucial it must be the same GUID value as you gave the Class.

August 07, 2008

Localization and Globalization

Using Localization from Code

ASP.NET provides all the Localization you need by just adding .RESX files to your Web App in the right location but sometimes you want to invoke the Localization yourself. To do this you'll use the System.Resources.ResourceManager class. This allows you to get resources by simply supplying the name of the resource and the Culture but to setup the resource itself is a bit trickier (in Visual Studio 2005) as some of the files required have been removed from Visual Studio's Templates. Because of this you'll need to use some tools to create the Resource (and optionally the Assembly to store it know as Resource Assemblies or Satellite Assemblies).
There are 3 steps to creating resources for use with ResourceManager:
1. Write a text file or .RESX file to hold your resources.
2. Compile the resources in step 1 into a .RESOURCES file.
3. Compile the .RESOURCES file in step 2 into a Satellite Assembly.
4. Locate the Satellite Assembly from step 3 into a location which is recognised by the Localisation mechanism.

1. If your resources are all strings then the easiest thing to do is add them to a .TXT file. If your resources are more than strings e.g. images then you should probably use a .RESX file. .RESX files are the usual location for resources. These are added to your Class Library Project in VS by selecting the project and clicking the Add File and then choosing Resource File.
If you wish to use a .TXT file then here's how you enter the resources:
myFirstResource = here is my first resources value
mySecondResource = here is my second resources value
(Note that the .TXT file must be saved with UTF-8 support, you'll probably find this in your text editor save options somethere, notepad has it, this is required to support some culture specific characters like accents).

2. The .TXT file or .RESX file is not enough for the ResourceManager, this .RESX file has to be converted to a .RESOURCES file before it can be used, this can be done with the tool, ResGen.exe,
resgen strings.en.txt strings.resources
resgen strings.es.txt strings.es.resources.

for more see http://msdn.microsoft.com/en-us/library/xbx3z216(VS.80).aspx

If you wish to use these bare .RESOURCES files then you can by following this http://msdn.microsoft.com/en-us/library/khyt7e7y.aspx but note that there are issues with Concurrent access on IIS so you may want to embed the resource in an Assembly (next step).

3. This .RESOURCES file can then be embedded in your DLL for later use by the ResourceManager but I found some little caveats here which cost me at least 1 day to figure out. This caveat is that the naming of the .RESOURCES files is also used as well as the DLL.
What I found it that for other cultures the .RESOURCES file must be named with that Culture as it does get used e.g.
The Default culture is English (en), the other culture used is Spanish (es).

You create 2 .TXT files to store the resources
strings.txt and strings.es.txt.

You then generate 2 .RESOURCES files from these with, it is these files that must have the correct naming i.e. the Spanish version must have the ".es" in the name before the extension.
resgen strings.en.txt strings.resources
resgen strings.es.txt strings.es.resources.

Now you use a Linker tool call AL.exe to embed each resource into it's own dll, note that you supply the culture here at the command line and also you must name the DLLS for the Cultures other than the default to have the ".resources" before the extentsion:
al /embed:strings.resources /culture:en /out:strings.dll
al /embed:strings.es.resources /culture:es /out:strings.resources.dll

You now copy only the .dll into the correct directories i.e. if it's aWebsite the root directory is the "bin" directory and it it's a standalone app then just copy into the same dir as the .exe.
Place the default assembly. strings.dll into the root and them create a subdirectory "es" and place the Spanish DLL in there
e.g.
/bin/strings.dll
/bin/es/strings.resources.dll

for more see http://msdn.microsoft.com/en-us/library/21a15yht(VS.71).aspx

4. If you use the bare .RESOURCES file then you can use the method described here http://msdn.microsoft.com/en-us/library/khyt7e7y.aspx. All you need to do is name the files uniquely using the pattern described, e.g. strings.es.resources for Spanish. The use the ResourceManager _rm = ResourceManager.CreateFileBasedResourceManager("strings",Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Resources" ), null);
CultureInfo cInfo = new CultureInfo(culture);
string result = _rm.GetString(resourceStringName, cInfo);

If you use an Assembly to store your Resources then you cannot use the CreateFileBasedResourceManager instead you must use the ResourceManager's constructor.
Assembly stringsAssembly = Assembly.Load("strings");//the default assemblies name see step 3.
ResourceManager rm = new ResourceManager("strings", stringsAssembly );//"Strings" is the resource name of the default Culture i.e. strings.resource that you embedded earlier in the DLL.
string result = _rm.GetString("myFirstResource", new CultureInfo("es"));//get me the resource from the Spanish Culture DLL.

May 23, 2008

VSPackage - moving the assembly out of the GAC

Some tips when moving your VS Package assembly out of the GAC.
First of all in order for Visual Studio to find the Package in it's new location you need to tell it where to find it, this is done in the Registry with the CodeBase key.
This is done in the Registry at

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Packages\{1237c0fa-4f6e-43c5-9ee4-a5938847c54a}]
"Codebase"="C:\\Program Files\\MyCompany\\MyProduct\\MyPackageAssembly.dll"


This is also the case for other classes which have GUIDs in your package.
Use regasm with the /codebase switch to tell you which classes may need an additional registry entry.

April 17, 2008

Automated update of AssemblyVersion and AssemblyFileVersion

Like so many others I wanted to update the Version of all of my assemblies with one version value but I could not find the solution I wanted, hence I've written my own, see the source code below.

This implementation fits my own problem, a build script which gets all our codebase sourcecode from SourceSafe, builds all the projects and then labels all the sourecode with a single label passed in by the builder at command-line.

This implementation takes either 1 or 2 arguments.
If only 1 is supplied it will do a check for a valid Version value i.e. the version must be of the format 1.0.0.0 and the third value must be less than 65535 (This is a .NET requirement, the assembly would not build if the number is above 65535, see here for more).
If 2 arguments are supplied it will do all that step 1 does and also will set the version to AssemblyVersion and AssemblyFileVersion in all AssemblyInfo.cs files under the directory specified (and it's subdirectories).

e.g. here's how I call it from my build.bat, NOTE AssemblyUpdateVersion.exe is the name of the Console app which I built the sourcecode shown later into.
@echo off
setlocal
c:
pushd ..\
echo "%cd%"
set CDIR=%cd%

if "%1" neq "" goto update_assembly_version
if "%1"=="" goto end

:update_assembly_version
REM Validate the label using the Custom tool AssemblyUpdateVersion.exe
call AssemblyUpdateVersion.exe %1 || goto validate_version_error

echo -- Update the Versions of the Assemblies before Building
REM First checkout all of the AssemblyInfo.cs files
set SSDIR=\\SourceSafe\sscodebase || goto error
call sscodebase C:\src checkout -R AssemblyInfo*.cs

REM now apply the new label to the AssemblyInfo.cs files using our custom tool AssemblyUpdateVersion.exe
call AssemblyUpdateVersion\bin\Debug\AssemblyUpdateVersion.exe %1 %CDIR% || goto update_version_error

REM now checking the file with the new version
call sscodebase . checkin -R AssemblyInfo*.cs || goto checkin_error

:end_update_assembly_version

goto great_success

:validate_version_error
echo --- Error with Version Validation
goto end
:end_validate_version_error

:checkout_error
echo --- Error with checkout
goto end
:end_checkout_error

:update_version_error
echo --- Error with Update Version
goto end
:end_update_version_error

:checkin_error
echo --- Error with checkin
goto end
:end_checkin_error


:great_success
echo --- Great Success!!!
goto end
:end_great_success
:end
Here's the code:
First File:
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace AssemblyUpdateVersion
{
///
/// Apply Version information to Assemblies before they are built by editing the AssemblyInfo.cs
/// Or just Validate the Version number against a valid format by supplying a single argument e.g. 1.0.0.1
/// Or Validate and Update the Version number by supplying 2 arguments e.g. 1.0.0.1 and C:\Galaxy\src
///
public class Program
{
static int Main(string[] args)
{

string versionValueEntered;
string directory;

if (args.Length == 1)
{
versionValueEntered = args[0];
if (!ValidateVersion(versionValueEntered))
return 1;
}
else if (args.Length == 2)
{
versionValueEntered = args[0];
directory = args[1];
if (!RunUpdateVersion(versionValueEntered, directory))
return 1;
}
else
{
Console.WriteLine("The new Version must be provided e.g. 1.2.3.4 ");
Console.WriteLine("The root directory must also be provided.");
return 1;

}
return 0;//Success
}

public static bool RunUpdateVersion(string version, string directory)
{
if (!ValidateVersion(version))
return false;

AssemblyUpdateVersion assemblyUpdateVersion = new AssemblyUpdateVersion(version, directory);
return true;
}
public static bool ValidateVersion(string version)
{
Console.WriteLine("ValidateVersion with {0}", version);
//Validate the format of the value entered. It must be of the format ...
//all ints and the cannot be more than 65535.
Regex assemblyVersionPattern = new Regex("[0-9]*[.][0-9]*[.][0-9]*[.][0-9]*");
if (!assemblyVersionPattern.IsMatch(version))
{
Console.WriteLine("The AssemblyVersion must be in the format 1.0.0.0.");
return false;
}
string[] values = version.Split('.');
if (values.Length != 4)//must be 4 values
{
Console.WriteLine("The AssemblyVersion must be in the format 1.0.0.0.");
return false;
}
else
{

int result;
Int32.TryParse(values[2], out result);
if (result > 65535)
{
Console.WriteLine("The AssemblyVersion must be in the format 1.0.0.0 and the 3rd value cannot be more than 65535.");
return false;
}
}
return true;

}
}
}

2nd File:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
namespace AssemblyUpdateVersion
{
public class AssemblyUpdateVersion
{
private string _valueEntered;
private string _directory;

public AssemblyUpdateVersion(string valueEntered, string directory)
{
_valueEntered = valueEntered;
_directory = directory;

ChangeVersion();
}

///

/// Change the version number of all AssemblyInfo.cs files found in the current directory.
///
private void ChangeVersion()
{
string newAssemblyVersion;
string newAssemblyFileVersion;

string inputFile = "AssemblyInfo.cs";
string tempFile = "AssemblyInfoTemp.cs";

DirectoryInfo dirInfo = new DirectoryInfo(_directory);


foreach (FileInfo assemblyInfoFileInfo in GetFilesRecursive(dirInfo, inputFile))
{
string tempFileFullName = Path.Combine(assemblyInfoFileInfo.Directory.FullName, tempFile);


//[assembly: AssemblyVersion("1.2.3.9")]
newAssemblyVersion = @"[assembly: AssemblyVersion(""" + _valueEntered + @""")]";
//[assembly: AssemblyFileVersion("1.0.0.0")]
newAssemblyFileVersion = @"[assembly: AssemblyFileVersion(""" + _valueEntered + @""")]";

try
{
using (StreamReader sr = new StreamReader(assemblyInfoFileInfo.OpenRead()))
{
using (StreamWriter sw = new StreamWriter(tempFileFullName, false, sr.CurrentEncoding))
{
String line;
while ((line = sr.ReadLine()) != null)
{
if (line.Contains("AssemblyVersion"))
{
string result = line.Replace(line, newAssemblyVersion);
sw.WriteLine(result);
}
else if (line.Contains("AssemblyFileVersion"))
{
string result = line.Replace(line, newAssemblyFileVersion);
sw.WriteLine(result);
}
else
{
sw.WriteLine(line);
}
}
}
}

File.Copy(tempFileFullName, assemblyInfoFileInfo.FullName, true);
File.Delete(tempFileFullName);
Console.WriteLine("Successfully set Version to {0} on File {1} ", newAssemblyVersion, assemblyInfoFileInfo.FullName);
}
catch (UnauthorizedAccessException uae)
{
Console.WriteLine("Access Denied: ");
Console.WriteLine(uae.Message);
}
catch (Exception e)
{
// Let the user know what went wrong.
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}
}
}
public IEnumerable GetFilesRecursive(DirectoryInfo dirInfo, string searchPattern)
{
foreach (DirectoryInfo di in dirInfo.GetDirectories())
foreach (FileInfo fi in GetFilesRecursive(di, searchPattern))
yield return fi;

foreach (FileInfo fi in dirInfo.GetFiles(searchPattern))
yield return fi;
}

}
}

February 06, 2008

Configuration namespace and reading settings from the Configuration file,app.config,web.config

This one always catches me out.
There are a few different ways to read settings in from a .config file.
The confusion for me occurs when reading from certain sections and from config files other than ones for the currently running application.

The easiest example is reading settings from the currently running application, from within it's own code.
You can access the AppSettings Section
<configuration><appSettings>
<add key="hostname1" value="localhost"/><add key="hostname2" value="othername"/></appSettings></configuration>
using:

NameValueCollection appSettings = ConfigurationManager.AppSettings;
foreach(string key in appSettings.AllKeys)
Console.WriteLine(key + " " + appSettings[key]);

So now you've all the keys in the AppSettings section i.e. "hostname1" and "hostname" and all their values accessed with appSettings[key].
Note: First you must add a reference to 'System.Configuration.dll' to your project and also include the namespace 'using System.Configuration;' You'll also need 'using System.Collections.Specialized;' in order to use the 'NameValueCollection' type.

Similarly but slightly more tricky you can access the settings of another assembly by using the following:

Configuration config = ConfigurationManager.OpenExeConfiguration(@".dll");
KeyValueConfigurationCollection kvpCollection = config.AppSettings.Settings;
foreach (string key in kvpCollection.AllKeys)
Console.WriteLine(key + " " + kvpCollection[key].Value);


You'll see the Settings collection is access in the same way and can be enumerated in the same way but the 'value' of the AppSetting must be accessed using the .Value property?
This is because the former call to AllKeys returns a NamedValueCollection and the latter returns a KeyValueCollection, this means that for one you have access to the values as strings themselves and the other you have access to the type KeyValueConfigurationElement, if you want to get the string value you additionally need to call the Value property!



Open a config for a dll, a dll being referenced by an exe

This reads the config file that's in the location that the exe is referencing the dll from, actually it makes a copy of the dll to the exes bin dir, so you have to add your dll's config file to the exe's bin directory and withing the dll's code add the following

//Only finds copy of the assembly as it's added as a reference to the exe.
System.Reflection.Assembly ass = System.Reflection.Assembly.GetAssembly(typeof(Class1));
System.Reflection.AssemblyName assName = ass.GetName();
string codeBase = assName.CodeBase;
int length = "file:///".Length;//remove the "file:///" from the string
string result = codeBase.Substring(length);
config = ConfigurationManager.OpenExeConfiguration(result);//open the dll's config
kvpCollection = config.AppSettings.Settings;
value = kvpCollection["hostname1"].Value;

February 04, 2008

ASP.NET HttpHandler how to

HttpHandlers are class which extend HttpHandler and which IIS recognises as the classes to use to handle errors etc.
The HttpHandler class is usually stored inside a .ashx file.

You can create your on .ashx and place it in you web app root dir, it will be entered whenever a request comes in from the client.

If you do not want to show your HttpHandler file code in your web app then you can hide it in a dll see details below.

How to hide the .ashx with a HttpHandler

I'll describe here how to create a handler for a file with the extension .ashx, it's coincidental I require a HttpHandler in my web.config in order to alias my .ashx file which is too a HttpHandler.

Note: Before you can compile a HttpHandler into a dll you must remove the directive at the start of the .ashx file e.g. delete this line
<%@ WebHandler Language="C#" Class="MyWebHandler" %>
If you fail to do this your .ashx code will not appear in your dll and you will not be able to register the type MyWebHandler.

One way to hide .ashx files is by a creating a handler for .ashx files.
The Handler is a HttpHandler.
This Handler will handle any calls to to files with the extension .ashx, this could be any extesion (I think), whatever extension you want will be supplied in the Web Applications web.config
e.g. if you created a HttpHandler and you want it to cater for all .ashx files in your Web Application add something like this to your Web Application's web.config :

In IIS 6 Classic Pipeline:
<system.web>
<httpHandlers>
<add verb="*" path="*.ashx"
type="MyWebHandler.Service1,
WebServiceHandler, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=9bae47114a98ed1a"/>
<add verb="*" path="Service5.asmx" type="Service4,
WebServiceHandler, Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=9bae47114a98ed1a"/>

Or if you want to provide handler for a particular ashx file use something like this
<add verb="*" path="Service3.ashx"
type="MyWebServiceHandlerClass,
MyWebServiceHandlerDLL,
Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=9bae47114a98ed1a"/>


in IIS 7 Integrated Pipeline, the section exists on the <system.webserver> and it has an additional Name attribute:

<system.webServer>
<httpHandlers>
<add name="MyHandler" verb="*" path="*.ashx"
type="MyWebHandler.Service1,
WebServiceHandler, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=9bae47114a98ed1a"/>


<add name="Service4Handler" verb="*" path="Service5.asmx" type="Service4,
WebServiceHandler, Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=9bae47114a98ed1a"/>

<system.webServer>

http://msdn.microsoft.com/en-us/library/46c5ddfy.aspx

Above the strong name is being used to locate the dll, this is because the dll is in the GAC, you do not have to put the dll in the GAC.

The second parameter in the 'type' list above is actually the name of the dll file, the Version, culture, PublicKeyToken, are all the fullname of the dll containing your HttpHandler code i.e. the strong name.

The Handler itself must include your code that was originally in your original file and also Implement the IHttpHandler Interface.
You can compile your HttpHandler into a seperate dll and add it to the GAC with the signature provided in the web.config of vice versa, just make sure that the Strong Name in the DLL is identical to the String name provided in the type in the Web Applications type.

If you choose to put your dll in the web apps bin directory then it can be registered as follows:
<add verb="*" path="Service3.ashx"
type="MyWebServiceHandlerClass,
MyWebServiceHandlerDLL"/>

Note: Only the dll name is required, no extension and no strong name it just needs to exist in the web apps bin directory.

Bug:
There's a small bug in ASP.NET 2.0, if you create a handler and you do not wish to use an assembly to store the HttpHandler code you can simple add the source file to the App_Code directory and just use the type in the web.config without and assembly, well this is how it's supposed to work but actually you have to do one more thing and that is you must create a codebehind file and place the code in there instead and add the details to the Http handlers directive
<%@ WebHandler Language="C#" Class="MyHandler" CodeBehind="MyHandler.ashx.cs" %>
web.config
<add verb="*" path="myfile.something"
type="MyHandler"/>

February 01, 2008

Remoting, Remote Computing

Remote Computing

http://del.icio.us/rss/learnerplates/remoting
http://del.icio.us/search/?fr=del_icio_us&p=remoting&type=user


There are 2 main ways in .NET to invoke a method and/or create an instance of an object on machine other than the one your
1. Webservices
2. .NET Remoting.

1. Webservices allow the client to communicate with the server be emitting an receiving XML, these XML messages are first wrapped on both the client and server side into SOAP envelopes..NET Remoting on the other hand allows a remote client to invoke a method through Proxy classes.

2. .NET Remoting
You can access remote objects in 2 ways, by reference and by value. The first manipulates the actual original object on the server(the objects class must be MarshalByRefObject) , the second a copy of the original object is used which means any changes made will only effect the local copy, copy on the client, of the object (the objects class must be Serializable).

The communication between Server and Client is taken care of for you by a Proxy class. All you have to do is create your remote class, inherit from MarshalByRefObject (or implement the ISerializable interface depending on how your client will be calling your remote object), usually inherit from MarshalByRefObject .
Create a Server class which creates an a remote instance of the remote class and also create a Channel to allow the client to communicate with the remote object.


Here's a very simple Remote class:

public class RemotingClass : MarshalByRefObject
{
public string HelloWorld()
{
return "Hello World!";
}
}
Or better still, in order to hide your actual remote objects implentation, create an interface in a common dll instead (below) of a concrete class (above).
Include the dll as a reference on you Server side and implement the interface with some class. On the Client side also include the dll as a reference and use the interface type to get your remote object instance.
like so:
public interface IRemotingClass
{
string HelloWorld();
}
And change your remote class implementation accordingly
public class RemotingClass : MarshalByRefObject, IRemotingClass
{
public string HelloWorld()
{
return "Hello World!";
}
}
The client code calling this method could be a standalone app (Console App) or a web application (Http Client, running on IIS), the Console App would use TcpChannel the Http Client would use HttpChannel.
Here's a simple Server class (Note if you plan on running your server as a web app or in a web application, place this code inside your Global.asax files Application_Start method and remove the port numbers they're no longer required, if you do this your RemotingClass.cs will need to be in the App_Code dir) :

using System.Runtime.Remoting;
using System.Runtime.Remoting.Services;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public partial class ServerApp
{
public void main()
{
// Create an instance of a channel
HttpChannel channel = new HttpChannel(1234);//the client must use this port too
ChannelServices.RegisterChannel(channel); // Register as an available service with the name
HelloWorld RemotingConfiguration.RegisterWellKnownServiceType(
typeof (Remoting.RemotingClass),
"RemotingClass.soap", //The name used by client when requesting this remote object, this tells IIS to deal with the request as a Remote request and let the .NET Remoting take care of it.
WellKnownObjectMode.Singleton); // Create an instance of the remote object

}


The Client code then creates an remote instance of the object using a url with the name just given, "RemotingClass.soap", and a channel to the same object with the same port 1234.


class ClientStartup
{
static void Main(string[] args)
{
//Send out messages on any port port
HttpChannel httpChannel = new HttpChannel();
//Requests will be sent through Http, which are SOAP messages.
ChannelServices.RegisterChannel(httpChannel);
//Get a reference to the remote object using the url
//Note that the same port number is used as on the Server, 1234
IRemotingClass remoteClass = (IRemotingClass)Activator.GetObject(
typeof(IRemotingClass),
"http://localhost:1234/RemotingClass.soap");

string result = remotingClass.HelloWorld();
Console.WriteLine("The Customer has been gotten on the client remotely with the age : " + result);
}
}

Hosting the Remote objects on IIS

When your objects live in a Web Application i.e. in IIS, you can use IIS's power to do some of the work for you, like security.

The above example can be run on IIS by simply placing a version of the code we had in the Server's main method into our web applications Global.asax files Application_Start method. This ensures that the remote service is initiallized only once i.e. when the web app starts.

The code will have to change slightly

HelloWorld RemotingConfiguration.RegisterWellKnownServiceType( typeof (Remoting.RemotingClass), "RemotingClass.soap", WellKnownObjectMode.Singleton);

As you can see the code has been simplified, there's no HttpChannel or Port number, because this remote object will not be referenced by the client using a Url instead of a machine and Port number i.e. using http://localhost//RemotingClass.soap

like so

HttpChannel httpChannel = new HttpChannel();
ChannelServices.RegisterChannel(httpChannel);
IRemotingClass remoteClass = (IRemotingClass)Activator.GetObject( typeof(IRemotingClass), http://localhost///RemotingClass.soap");
string result = remotingClass.HelloWorld();
Console.WriteLine("The Customer has been gotten on the client remotely with the age : " + result);

SoapSuds.exe

In the above examples we've used an interface on both Server and Client sides, this interface hid our true implementation from the Client, all the Client is aware of is the interface. This works fine but there is another way to implement this and that is with a representation of our remote class that is created from the WSDL of our remote object. The WSDL of our remote object can be accessed using the Url to our remote object followed by ?WSDL, (remember this in SOAP and webservices).
The SoapSuds.exe is an app found in the Visual Studio SDK e.g. C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\soapsuds.exe.
You pass soapsuds.exe your WSDL output and it generates a class representation of your remote object, this representation will be very similar to your real object on the Server side with additional SOAP attributes.
e.g.
C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0>soapsuds -url:http://localhost//RemotingClass.soap?wsdl -od:C:\temp

This class can now be used by the Client instead of the Interface IRemotingClass.

But what is the usefulness of this?
One use of this is that is provides the concrete class instead of the interface which is required when you are using Configurable Settings on your Server side.
Configurable settings allow you to dynamically change the remote object name and port at runtime using you config file, one issue with this however is that the Client side requires the concrete implentation in order to reference it.
e.g.
<configuration><
system.runtime.remoting><
application><
service><wellknown mode="Singleton" type="RemotingClass, RemotingClassAssemblyName"objectUri="RemotingClass.rem"/>
</service>
<channels>
<channel name="MyChannel" priority="100" ref="http"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>

January 22, 2008

MVC in ASP.NET

MVC - Model-View-Controller is a sort of design pattern used to develop applications in an uncoupled manner i.e. seperate the data (Model) from the User interface (View) and the business logic (Controller).

I first came across this with MFC applications, it was well known design pattern when developing standalone applications now it appears to be the latest craze in ASP.NET 3.5 applications! In this post I'll try and find out why and what is the significance and benefits to us, developers.

MVC (scott gu)
Example Building a Simple Blog engine using MVC in ASP.NET (aspalliance.com)

January 11, 2008

WebServices

Webservices are exposed methods which can be accessed through a uri e.g.your code is contained in a .asmx file.
The .asmx file contains webmethods (ordinary methods with the [WebMethod] attribute). You place your .asmx file inside your webserver somewhere and these can be consumed directly by browsing to the uri or by consuming the webservice by adding it as a reference to your application and then accessing that reference. Webservices can also be created with Codebehind meaning the code itself is in another file, this results in the .asmx file just containing a Directive to the class name containing the webmethod, to use this webservice the codebehind dll must be placed within the websites bin directory.

The WebService works by the Client and Server sending SOAP XML (SOAP is just the root element, it's something that Internet Explorer knows how to parse) up and down over HTTP. Both sides need to know how to create this SOAP XML, on the Client side this is done by a Proxy class which Serializes/De-Serializes the SOAP XML into objects.

Proxy Class

Client side class, usually called Reference.cs, which Serializes/De-Serializes the SOAP XML into objects. This lives on the Client side (in .NET) e.g. if the client is an .NET Console application then in order to gain access to the methods available on a website the application will need a Proxy class (this is created for you by VS.NET when you add a Web Reference which points to the Webservice on the website i.e. http:..........).
If you want to use Webservices from a Client such as an ASPX form then you again need to add the Web Reference but a Proxy class is not generated, instead because your making calls on the same web application you can call into the WebService class directly like a normal class.
If your calling a WebService from on Web app to another then it's the same as a Console app, you'' have to use a Proxy class, again this is generated for you in the calling website by VS.NET when you add the Web Reference.

Hiding the asmx

I have not actually found a way to get rid of the .asmx file completely. You can hide the webservice endpoints functionality in an assembly and create a handler for it as described below but to implement the webservice you will always need a .asmx file somewhere. Even the Proxy Class uses the .asmx file. In order to implement a remote call to a method inside your own code I think the only solution is .NET Remoting.


An Extensive Examination of WebServices (4guysfromrolla thorough).
Creating a .NET Web Service (15seconds goood)
ASP.NET Web Services Techniques (Very thorough explanation of how WebServices work)
Writing a raw Web Service using HttpHandler

January 07, 2008

Active Directory

Active Directory is the windows method to access all distributed devices such as files, printers.

In .NET the Active Directory is accessed from System.DirectoryServices namespace.

.NET uses Windows Active Directory Service Interfaces (ADSI) to interact with the distributed devices, there are 5 of these ADSIs;
Path Windows NT version 5.0, Windows 2000, or Windows XP WinNT://path, Lightweight Directory Access Protocol (LDAP) ldap://path/, Novell NetWare Directory Service NDS://path, Novell Netware 3.x NWCOMPAT://path, Internet Information Services (IIS) IIS://.

e.g.

DirectoryEntry webentry = new DirectoryEntry();
String path = "IIS://localhost/W3SVC/1/ROOT";


DirectoryEntries webSiteChildren = webentry.Children;
foreach (DirectoryEntry website in webSiteChildren)
{

//can access metadata of each website on iis here
}