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.