Eric Gunnerson has posted an interesting and useful article on dynamic assembly loading and unloading using appdomains.
Thursday, 7 June 2007
Monday, 4 June 2007
SQL Server - Changing collation order for an entire cluster
An annoying situation arose today with a project - development machines were using legacy SQL collation orders whilst the production cluster was using a windows collation. As all servers and databases should really use the same collation order, cross database joins and tempdb actions will cause you problems if not , it was decided that we'd change the production cluster to use the SQL_ collation orders!
What should have been a straight forward process actually turned out to be more difficult than expected and I found the following information useful, and largely undocumented;
The process for changing the collation order on a SQL server cluster is as follows;
- Backup all of your databases and then detach them all
- Take the SQL services offline in cluster management
- Slap your SQL install disk into a drive and run the following command from it:
start /wait setup.exe
/qn VS=<cluster name>
INSTANCENAME=<sql instance name or MSSQLSERVER for default>
REINSTALL=SQL_Engine
REBUILDDATABASE=1
SAPWD=<new strong SA password>
SQLCollation=<new collation order>
AdminPassword=<strong password>
SQLAccount=<domain\account>
SQLPassword=<strong password>
AGTAccount=<domain\account>
AGTPassword=<domain\account> - Bring SQL online in cluster management
- Restore / re-attach your databases
Your master database etc will have been rebuilt and everything should be fine.
The problem I ran into however was when I used a different location to run setup from than was used in the original installation. Setup kept failing saying that it couldn't find valid setup package for "SQL Standard Edition (64 bit)". After trying lots of different combinations of the above, I discovered that despite running setup.exe from a new location, it actually looks at the registry to discover where it was installed from originally and wants to get the MSI files from there!!
The solution? You could change the registry to point to your new location, but easier is this extra parameter on your command line:
REINSTALLMODE=vomus
This tells the setup not to bother looking at the old location, and use the location where the setup is residing. This 60 minute job ended up taking almost 3 hours in the end - but at least I'll know for next time!
Wednesday, 16 May 2007
How fast is reflection?
Most .NET developers have an affinity to using reflection for things like configuration, inversion of control, dependency injection etc, but I was wondering today just how fast IS reflection - what overhead does it bring? So I wrote a (far from exhaustive) test;
The test consisted of code that would simulate 500,000 cycles of loading 9 different objects from an external assembly, create an instance of each of those objects and then invoke a method on it.
The method itself then resolves and creates 5 instances of 5 different objects before returning. In total this activity equates to 3 million reflection resolutions by type and 3 million object constructions followed by 500,000 method calls.
The results? The total time from start to finish on my laptop was 19.7 seconds. That seems pretty quick to me.
For refernce my laptop is an Intel Centrino duo core, 2Ghz with 2Gb of memory.
Saturday, 28 April 2007
Losing intellisense in your content pages?
When using master pages in ASP.NET, visual studio 2005 often gets confused and loses its ability to display intellisense in your content pages. IE: Within your asp:Content tags, if you type asp: you won't get any intellisense for the standard asp controls.
There's a simple work around for this and that's to ensure you have your master page file open in a tab in the editor. If it's open, you'll get the intellisense back in your content page.
I'm sure this will be fixed in a later release, but until then, the above should suffice.
Wednesday, 14 March 2007
System.Net.WebRequest problems
This problem has been killing me. The overall problem is within a complex app, site and service installation, but i've scaled it right back to a simple web application that invokes a web request on another unsecured resource. My web app is very simple with just the following in Page_Load:
1
|
System.Net.WebRequest tx =
System.Net.WebRequest.Create ("http://othermachine/listener.aspx");
|
This script is hosted on localhost and othermachine is within the intranet zone and no proxies etc are involved. On my machine things work fine, but on the other machines in the office (who also have the above installed on localhost) we the error message below during execution of the web request to the other machine (line 2 of the source);
A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
Now, here's the kicker. the above code works when it's run through the VS.NET ASP.NET Development web server! It only fails when it's served through IIS!!! I've been trying to work this out for 2 days now to no avail. If anyone has any ideas, please get in touch....there's a beer in it for the solver!
UPDATE - HERE'S THE SOLUTION
Despite it working when hosted outside of IIS, and despite checking in several places to see if proxy / firewalls were to blame it turned out that when the above code was hosted within IIS it WAS using a proxy server on the failing machines. The solution was to simply disable the proxy for the app through web.config:
<system.net>
<defaultProxy enabled="false"/>
</system.net>
For some unknown reason, when hosted under IIS, the WebRequest would initialise and then ask the proxy server (configured in IE) for access to othermachine, but it would obviously ask the proxy server for this access as LOCALMACHINE\ASPNET or NETWORK SERVICE - which of course would probably be refused access to the proxy.
Really don't know why this was a problem under IIS only, but the above setting should work regardless of hosting environment.
Tuesday, 13 March 2007
The old double hop issue
Recently I had a WCF service calling into a simple ASMX service, but I got a 401 error each time it did so, even though my WCF service was setup to impersonate correctly.
The problem boils down to something called "double-hopping", which is where by default you're not allowed to pass network credentials beyond a single network hop.
Assuming the client application, the WCF and the ASMX are on separate servers, the client connects to the WCF, impersonating the client caller identity. When the WCF then connects on to the ASMX however it isn't allowed to pass up the impersonated credentials and instead uses the process identity for the worker thread (commonly ASPNET or NETWORK SERVICE).
Establishing trust for delegation is the answer - setting up the various servers to allow them to pass on impersonated credentials. The following support article at Microsoft covers how to configure everything: http://support.microsoft.com/default.aspx?scid=kb;en-us;810572
For reference here's a good posting about it.
Once you understand double hop issues, the problem is undeniably straight forward, however, if you're not "in-the-know" as I wasn't at the time I was struggling, double hopping in service oriented environments can be a royal pain in the ass.
Monday, 12 March 2007
WCF Impersonation
Impersonation (running code as the invoking user) under WCF is configured slightly differently to ASMX services. You need to first of all tell your binding to use windows authentication to identify the caller as follows;
<bindings>
<wsHttpBinding>
<binding name="AuthBinding">
<security>
<transport clientCredentialType="Windows"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
However, impersonation wise this won't do a thing - when your service code attempts to do something like write to the file system, it will do this as the user your worker processes are configured to use (normally ASPNET or NETWORK SERVICE).
Normally in ASMX you'd configure impersonation using web.config's <identity impersonate="true"/>, but WCF appears to ignore this. Instead you're individual methods in WCF need to be marked up by adding the following attribute onto the method;
[OperationBehavior(Impersonation:=ImpersonationOption.Required)]
When you consume the service on the client just add this code;
ClientCredentials.Windows.AllowedImpersonationLevel =
System.Security.Principal.TokenImpersonationLevel.Impersonation;
The service will now impersonate the calling client user.
Thursday, 8 March 2007
Identifying Styles in Sharepoint Services 3.0
Sharepoint has moved on leaps and bounds with the release of SPS 3.0, but it's still a pig when it comes to customisation. If you're changing the look and feel of a theme in SPS, you've got a tough time ahead! If it's available to you, using SPS designer aids with this greatly, but regardless, one of the hardest jobs in customising a theme is knowing which of the hundreds of styles apply to an element you want to change.
And so, in rolls the following code that you can re-use. By adding a content editor web part and using the source editor you can paste the following code in and have a web part that shows you the full CSS hierarchy of any element you move your mouse over:
<table border="1" width="100%">
<td valign="top" width="100%">
<div id="spsClassTitle">TITLE</div>
<div id="spsClassTree" style="height:300px;">CSS</div>
</td>
</tr>
</table>
<script language="JavaScript">
function GetSPSClasses()
{
var currElement = window.event.srcElement;
var classTree = "";
var Style1 = "color:green;";
var Style2 = "color:red;";
var CurrentStyle = Style1;
if( currElement != null )
{
spsClassTitle.innerText = "Styles under the cursor";
if( currElement.id != null && currElement.id != "" )
spsClassTitle.innerText += "(" + currElement.id + ")";
while( currElement != null )
{
if(currElement.className != null && currElement.className != "")
{
var newEntry = "<div style=\"" + CurrentStyle + "\"><B>" +
currElement.tagName + ":</B><BR/>" +
currElement.className + "</div>"
if( classTree != "" )
classTree = newEntry + classTree;
else
classTree = newEntry;
if( CurrentStyle == Style1 ) CurrentStyle = Style2;
else CurrentStyle = Style1;
}
currElement = currElement.parentElement;
}
spsClassTree.innerHTML = classTree;
}
else
{
spsClassTitle.innerText = "No styles under cursor";
spsClassTree.innerText = "";
}
}
window.document.body.onmouseover = GetSPSClasses;
</script>
SQL and Remote Connections
Excuse the simplicity of this post but we've all been tripped up by SQL Server / SQL Express not accepting remote connections or not allowing SQL Logins and have had to hunt through our scratty notes to remember what we did last time we installed it. Well here's the definitive guide to getting SQL to accept remote connections and getting it to accept the sa (or other SQL user) login.
- Ensure TCP/IP is enabled in surface area configuration
- Ensure "enabled" for TCP/IP in Network Configuration -> protocols in SQL Server Configuration manager.
- Ensure sqlservr.exe exempt in firewall (if necessary)
That'll get it accepting remote connections. If you've installed with windows auth mode only though and you want to login with SQL users, you'll need to turn mixed mode on too:
- Open Enterprise Manager and open the server properties
- Set security to SQL Server and Windows auth mode
- If necessary enable the sa user:
- Open security -> logins and open properties for the sa user
- Select the status tab from the left hand side
- Set Login: Enabled
- Restart SQL Server if you changed security at step 2
I know this was all very simple, but if nothing else I'll find it useful to refer back to when I try to connect and get a "connection refused" error and scratch my head for a bit.... [8-|]
Wednesday, 7 March 2007
WCF - Handling large data sizes
WCF is a bit fussy when it comes to protecting you against denial of service attacks by sending large data packets. This is great when you don't need to send large amounts of data, but when you do, it can cause you a few problems.
I've come across a situation recently sending multi megabyte attachments through a WCF service. In its default state WCF won't accept large data transmissions, you have to specifically configure it to accept large data by changing the binding as follows:
<binding name="AuthBinding" maxReceivedMessageSize="100000">
As the name implies maxReceivedMessageSize tells WCF how big a message it will be allowed to receive. If you're sending a large byte array across the wire - maybe an attachment file or something - you need to also configure the XmlReader that WCF uses to tell it how big an array to accept. You do this through the use of the readerquotas declaration within your binding:
<readerquotas maxArrayLength="10485760" />
This would allow the XmlReader to accept a byte array equivalent to a 10mb file.