Robust pattern for hvordan man lukker WCF-klienter

19. May 2009 10:39

Lad os se det i øjnene: systemintegration med web-services er nok den letteste og mest populære måde at integrere systemer på. Hvis man i dag laver web-services på .NET-platformen, bør man bruge Microsoft’s WCF-teknologi. WCF er et slagkraftigt stykke framework, men med magten følger der også et ansvar. Der findes en del faldgruber som jeg ofte har set andre falde i – og selvfølgelig også selv er faldet i.

En af de mest almindelige faldgruber er hvordan man lukker en WCF klient ordentligt. Sådan kunne en typisk (fejlagtig)interaktion med en web service foregå:

using (var wcfClient = new SimpleServiceClient())
{
string response = wcfClient.SampleMethod("test");
Console.WriteLine(response);
wcfClient.Close();
}

Problemet her ligger i at man glemmer at tage højde for er at der kan opstå exceptions i Close(). Og gæt hvad – implementationen af Dispose() på ClientBase kalder Close() – altid! (kan ses med Reflector). Det kan til tider medføre uventede og mærkelige exceptions. For eksempel, hvis der opstår en fejl ved kaldet til SampleMethod vil wcfClient disposes, og Close() kaldet. Men så finder Close() ud af at klienten er i Faulted-tilstanden, og kaster en ny og uønsket exception (“The service is in the Faulted state” el.lign.) Denne MSDN artikel beskriver det samme problem bare på teknisk engelsk.

Løsningen er, at når der opstår fejl skal klienten lukkes med Abort() i stedet for med Close(). Abort() sørger for at klienten afbrydes med det samme – så der ikke kan opstå nye exceptions. Jeg har ofte glemt at bruge den, så derfor, har jeg lavet denne lille klasse som hjælper mig med det. Jeg kalder den ServiceOperationScope:

/// <summary>
/// Encapsulates a web-service client instance and makes sure it gets properly closed.
/// </summary>
public class ServiceOperationScope<T> : IDisposable
where T:class
{
private readonly T m_Instance;

public ServiceOperationScope(T instance)
{
m_Instance = instance;

var commObj = m_Instance as ICommunicationObject;
if (commObj != null)
commObj.Faulted += ServiceOperationFaulted;
}

public T Instance
{
get { return m_Instance; }
}

private void ServiceOperationFaulted(object sender, EventArgs e)
{
((ICommunicationObject)m_Instance).Abort();
}

protected virtual void Dispose(bool isDisposing)
{
var commObj = m_Instance as ICommunicationObject;
if (commObj != null && (commObj.State != CommunicationState.Faulted &&
commObj.State != CommunicationState.Closed))
commObj.Close();

var d = m_Instance as IDisposable;
if (d != null)
d.Dispose();
}

public void Dispose()
{
Dispose(true);
}
}

Og sådan bruger man den:

try
{
using (var scope = new ServiceOperationScope<ISimpleService>(new SimpleServiceClient()))
{
string response = scope.Instance.SampleMethod("test");
Console.WriteLine(response);
}
}
catch (TimeoutException te)
{
// handle timeout error
}
catch (FaultException<SimpleServiceFault> simpleServiceFault)
{
// handle custom web service fault
}
catch (FaultException)
{
// handle generic web service fault
}
catch (CommunicationException)
{
// handle communication error
}

Rækkefølgen af de forskellige catch-linjer følger Microsofts anbefalinger.

Håber nogen kan bruge det til noget. ServiceOperationScope er i hvert fald blevet en fast del af min værktøjskasse.

Sequential GUID i SQL Server og i .NET

13. May 2009 13:11

Jeg havde aldrig gættet at min første blog post ville omhandle noget der har med SQL Server at gøre, men – der kan man bare se…

I det sidste er jeg blevet mere varm på idéen med at bruge GUIDs som PK i SQL Server. Den største ulempe er nok at ens index begynder at performe dårligt når man bruger almindelige, tilfældige GUIDs (NEWID eller Guid.NewGuid ).

Som en løsning på problemet kom Microsoft op med NEWSEQUENTIALID i SQL Server 2005. Denne funktion (som kun kan bruges ifm. en kolonnes DEFAULT værdi) returnerer altid den næste GUID i rækkefølge, uanset hvilken tabel Guid’en skal bruges i.

Det er jo genialt! Men hvad hvis man gerne vil populere ID-kolonnen selv, og ikke gider hente den fra databasen efter at rækken er blevet tilføjet? Artiklen her dokumenterer at NEWSEQUENTIALID bruger en Windows API metode ved navn UuidCreateSequential - og den kan vi bruge fra C#!

Efter at have googlet lidt rundt lavede jeg først denne metode:

public static class SequentialGuid
{
[DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid guid);

public static Guid NewGuid()
{
Guid newGuid;
int returnVal = UuidCreateSequential(out newGuid);

if (returnVal != 0)
throw new Exception("UuidCreateSequential failed with exit code " + returnVal);

return newGuid;
}
}

Da jeg kørte en test der sammenlignede de GUIDs som min metode genererer og dem som SQL Server genererer fandt jeg ud af at de er formateret lidt forskelligt. Det var da irriterende:

SQL Server:
E4DF655E-A03F-DE11-9DE7-001FF3ABBCAF
E5DF655E-A03F-DE11-9DE7-001FF3ABBCAF

SequentialGuid.NewGuid():
5E665517-3FA0-11DE-9DE7-001FF3ABBCAF
5E665518-3FA0-11DE-9DE7-001FF3ABBCAF

Jeg har fremhævet de bytes der “tæller opad”. Der sker åbenbart det, at SQL Server konverterer de tre første byte-gruppers endianness for bedre at kunne sortere. Derfor lavede jeg en ny metode som returnerer GUIDs i SQL Server formatet:

public static Guid NewSqlServerFormattedGuid()
{
var bytes = NewGuid().ToByteArray();

CopyBigEndianBytes(BitConverter.ToInt32(bytes, 0), 4, bytes, 0);
CopyBigEndianBytes(BitConverter.ToInt16(bytes, 4), 2, bytes, 4);
CopyBigEndianBytes(BitConverter.ToInt16(bytes, 6), 2, bytes, 6);

return new Guid(bytes);
}

// Tak til Jon Skeet http://www.yoda.arachsys.com/csharp/miscutil
private static void CopyBigEndianBytes(long value, int bytes, byte[] buffer, int index)
{
int endOffset = index + bytes - 1;
for (int i = 0; i < bytes; i++)
{
buffer[endOffset - i] = unchecked((byte)(value & 0xff));
value = value >> 8;
}
}

Jeg fik inspiration fra artiklen her, som fremlægger en lidt anden løsning, men som jeg synes ikke var så elegant.

Der er også andre sjove finurligheder med Sequential GUIDs – bl.a. at man kan se computerens MAC adresse i de sidste 6 bytes. Så hvis du laver systemer til PET er det måske bedre at bruge noget andet.

Archive