[Silverlight 3] Silverlight 3 et la gestion des erreurs WCF
Tous les développeurs Silverlight ont déjà rencontré l’erreur suivante lorsqu’ils travaillent avec WCF:
The remote server returned an error: NotFound.
Quoi de plus frustrant de savoir qu’une exception est déclenchée, sur le serveur, mais de ne pas en connaitre la cause !
Avec Silverlight 2, pour débugger ce genre d’erreurs, il n’y a pas 36 solutions: on remonte ces manches et on se triture les méninges pour essayer de savoir quelle exception pourrait survenir, sur le code serveur.
Avec Silverlight 3, le modèle de programmation des erreurs SOAP a été amélioré et on est maintenant capable de remonter, coté client, l’erreur qui est survenu sur le serveur. Mais avant de rentrer dans les détails, il convient de chercher pourquoi, à l’heure actuelle, l’erreur SOAP n’est pas renvoyée au client.
En fait, lorqu’une exception est déclenchée, celle-ci provoque l’envoi d’un code réponse HTTP 500. Or, dans son implémentation, Silverlight n’est capable de comprendre/intercepter que les code 200. Pour faire en sorte que le message d’erreur soit correctement envoyé au client, il est donc nécessaire, lorsqu’une exception est déclenchée, de lui faire renvoyer un code d’erreur 200, dans tous les cas.
Pour cela, plusieurs étapes sont nécessaires. La première consiste à créer, coté serveur, un “endpoint behavior” qui va se charger d’intercepter les réponses envoyées au client et, lorsqu’il s’agit de fautes, de renvoyer le code réponse 200:
public class SilverlightFaultBehavior : BehaviorExtensionElement, IEndpointBehavior
{
#region Properties
public override Type BehaviorType
{
get { return typeof(SilverlightFaultBehavior); }
}
#endregion
#region Override Methods
protected override object CreateBehavior()
{
return new SilverlightFaultBehavior();
}
#endregion
#region IEndpointBehavior Members
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
var inspector = new SilverlightFaultMessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
#region Nested type: SilverlightFaultMessageInspector
public class SilverlightFaultMessageInspector : IDispatchMessageInspector
{
#region IDispatchMessageInspector Members
public void BeforeSendReply(ref Message reply, object correlationState)
{
if (reply.IsFault)
{
var property = new HttpResponseMessageProperty();
// Here the response code is changed to 200.
property.StatusCode = HttpStatusCode.OK;
reply.Properties[HttpResponseMessageProperty.Name] = property;
}
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
// Do nothing to the incoming message.
return null;
}
#endregion
}
#endregion
}
Ensuite, il faut enregister ce behavior dans le fichier web.config, puis indiquer à notre service qu’il doit l’utiliser:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="silverlightFaults"
type="DemoErreursWCF.Web.SilverlightFaultBehavior, DemoErreursWCF.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="SilverlightFaultBehavior">
<silverlightFaults />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="DemoErreursWCF.Web.ServiceWithGoodErrorBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="customBinding1">
<binaryMessageEncoding/>
<httpTransport/>
</binding>
</customBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<services>
<service behaviorConfiguration="DemoErreursWCF.Web.ServiceWithGoodErrorBehavior" name="DemoErreursWCF.Web.ServiceWithGoodError">
<endpoint address=""
binding="customBinding"
bindingConfiguration="customBinding1"
contract="DemoErreursWCF.Web.ServiceWithGoodError"
behaviorConfiguration="SilverlightFaultBehavior"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
A présent, il ne reste plus qu’à écrire une méthode dans notre service WCF et lui faire déclencher une exception:
public class ServiceWithGoodError
{
[OperationContract]
[FaultContract(typeof(ArithmeticFault))]
public double Divide(int a, int b)
{
try
{
return Convert.ToDouble(a / b);
}
catch(DivideByZeroException)
{
var fault = new ArithmeticFault
{
Description = "Cannot divide by zero !"
};
throw new FaultException<ArithmeticFault>(fault, new FaultReason(fault.Description));
}
}
}
public class ArithmeticFault
{
public string Description { get; set; }
}
Notez au passage l’utilisation de l’attribut FaultContract, qui permet d’indiquer que la méthode va, potentiellement, retournée une exception du type indiqué.
A présent, il ne reste plus, coté client, qu’à utiliser notre service et sa méthode:
private void btnCallGoodError_OnClick(object sender, RoutedEventArgs e)
{
var client = new ServiceWithGoodErrorClient();
client.DivideCompleted += this.ClientGoodErrorDivideCompleted;
client.DivideAsync(5, 0);
}
private void ClientGoodErrorDivideCompleted(object sender, DivideCompletedEventArgs e)
{
if (e.Error == null)
{
this.tbResults.Text = e.Result.ToString();
}
else if (e.Error is FaultException<ArithmeticFault>)
{
var fault = e.Error as FaultException<ArithmeticFault>;
this.tbResults.Text = Environment.NewLine + fault.Detail.Description;
}
}
Et voici ce que l’on obtient en cas d’erreur:
Inversement, voici ce que l’on obtient dans le cas où l’on utilise pas cette technique:
Comme vous pouvez le constater, aucunes informations n’est passée/indiquée quant à la nateure de l’exception déclenchée coté serveur !
Voila qui s’avère tout de même bien pratique! 
Un lien dans lequel vous pourrez retrouver tout cela, est plus encore: http://msdn.microsoft.com/en-us/library/dd470096(VS.95).aspx
A+
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :