Wednesday, November 25, 2009

Microsoft Web Development Server Command Line

If you ever need to host a web site on your machine (for testing or whatever), you can use the Microsoft Web Development Server (Visual Studio runs this from the IDE) manually via the command line.

I found this particularly useful to test deployment builds (eg: msdeploy) if you don’t have IIS!

There appears to be some confusion over where this EXE lives, but on my machine it is here:

C:\Program Files\Common Files\Microsoft Shared\DevServer\9.0\webdev.webserver.exe

All you need to do is give it a port, a file path, and the name of the virtual directory to use.

Example:

"C:\Program Files\Common Files\Microsoft Shared\DevServer\9.0\webdev.webserver.exe" /port:4955 /path:"C:\Build\Test\DotNet\Test.Project.Web" /vpath:/TestProj

Tuesday, July 14, 2009

WCF Security Issues With IIS

WCF Security shouldn't be that difficult, but I've recently run into some problems that just couldn't be forseen (at least for me).

I have a WCF Service hosted in IIS - I have set IIS to Require secure channel (SSL) traffic and set the virtual directory security to Integrated Windows authentication only.

In my WCF Service web.config, I use the following binding configuration:

<bindings>
<basichttpbinding>
<binding name="SecureTransport">
<security mode="Transport">
<transport clientcredentialtype="Windows">
</transport>
</security>
</binding>
</basichttpbinding>
The security mode of Transport is for communication over SSL. The client credential type of Windows is for Integrated Windows authentication.

This should work fine, right? Well, it did on one test box, but on another I had a number of problems, each outline below.

I would normally use a browser to test the connection first.


Error: Security settings for this service require Windows Authentication but it is not enabled for the IIS application that hosts this service.


Even if IIS is configured for Integrated Windows authentication only and your service configuration specifies Windows as the client configuration, you get the following error in your browser:

Security settings for this service require Windows Authentication but it is not enabled for the IIS application that hosts this service.

If this error is returned and Windows Authentication has been enabled in IIS, it means there is an issue with the supported network authentication schemes for the website that the web service is installed under. The most likely cause is that it is configured for NTLM only. We want to specify NTLM and Negotiate.

The following Microsoft KB article describes how to enable both schemes:
http://support.microsoft.com/kb/215383

After this has been done IIS will need to be restarted, or the web service application will need restarting (load the web.config file and save without changing anything).

It would appear that the Windows credential setting in WCF .NET maps directly to the Negotiate scheme in IIS.


Error: Could not establish trust relationship for the SSL/TLS secure channel with authority.

The certificate is not fully trusted. This is likely to be a temporary certficate or a locally issued certficate that the client machine does not trust.

The following client config change will ignore this particular certficate error:
<system.net>
<settings>
<servicePointManager checkCertificateName="false" checkCertificateRevocationList="false" />
</settings>
</system.net>

Error: The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate.


This appears to be an issue with .NET 3.5 SP1 (I say 'appears' as my only reference is a blog entry).

See this article: http://msmvps.com/blogs/alvin/archive/2008/11/14/net-3-5-sp1-breaking-change-to-wcf.aspx

The following client configuration change (for each endpoint) resolves this:
<system.serviceModel>
<client>
<endpoint address=... >
<identity>
<servicePrincipalName value="spn" />
</identity>
</endpoint>
</client>
</system.serviceModel>
Note that the value of servicePrincipalName is not important.


Error: This collection already contains an address with scheme http. There can be at most one address per scheme in this collection.

This error occurs when there are multiple entries for the Website Identification - it tries to create multiple service base addresses for each endpoint.

You can specify a filter in the server configuration to resolve this.

Here is an example:
<system.serviceModel>
<serviceHostingEnvironment>
<baseAddressPrefixFilters>
<add prefix="http://tstmachine:80"/>
</baseAddressPrefixFilters>
</serviceHostingEnvironment>
</system.serviceModel>

Thursday, January 22, 2009

Access Violations, Interop and VS2008 SP1

After installing SP1 for VS2008 the other day, one of our programs stopped working. In fact, it came up with an AccessViolationException:

System.AccessViolationException: Attempted to read or write protected
memory. This is often an indication that other memory is corrupt.
at VariantClear(tagVARIANT* pvarg)
at
System.Runtime.InteropServices.CustomMarshalers.EnumeratorViewOfEnumVariant.MoveNext()
at ...etc...

This was a surprise, because it ran fine before. Or, at least, that is what we thought.

An examination of the code revealed the exception was being thrown in a foreach() statement. Odd, but the foreach() statement was looping on a COM Interop collection. I've done a little Outlook Add-In development and one thing I've learnt about COM Interop is that it doesn't handle memory the way you would think .NET handles it.

Basically, there are two thing you need to do:
1) Do NOT use foreach() - these are bad, use for(;;)
2) Be very careful about releasing memory for references to COM objects behind Interop interfaces

There is a great article regarding this here:

OOM.NET: Part 2 - Outlook Item Leaks

If you do any Interop work, you really should read and understand it.

Back to the code that failed:

     private static bool ColIsForeignKey(Table t, Column col)
{
foreach (Key key in t.Keys)
{
if (key.Type != SQLDMO_KEY_TYPE.SQLDMOKey_Foreign) continue;
foreach (string keyCol in key.KeyColumns)
{
if (keyCol.ToLower() == col.Name.ToLower())
{
return true;
}
}
}
return false;
}

Now here is the code modified:
     private static bool ColIsForeignKey(Table t, Column col)
{
SQLDMO.Keys keys = t.Keys;
try
{
for (int i = 1; i <= keys.Count; i++)
{
Key key = keys.Item(i);
try
{
if (key.Type != SQLDMO_KEY_TYPE.SQLDMOKey_Foreign) continue;

Names columns = key.KeyColumns;
try
{
for (int j = 1; j <= columns.Count; j++)
{
string keyCol = columns.Item(j);
if (keyCol.ToLower() == col.Name.ToLower())
{
return true;
}
}
}
finally
{
Marshal.ReleaseComObject(columns);
columns = null;
}
}
finally
{
Marshal.ReleaseComObject(key);
key = null;
}
}
}
finally
{
Marshal.ReleaseComObject(keys);
keys = null;
}
return false;
}

It looks a lot uglier, but you can be sure you're not keeping any nasty
references around the place.