Avoir une retour null avec un service WCF REST, c’est possible!
Pour un premier article technique sur ce blog, je voulais frapper fort. Attaquons nous donc à l’un des problèmes soi-disant insurmontable de WCF.
Par défaut, si WCF est utilisé pour exposer un service REST en JSON, les méthodes retournant une valeur null, ne renvoient pas une réponse telle que les frameworks javascript les attendent.
A la place, WCF nous retourne un message HTTP 200 (statut ok) avec un corps de message vide. La plus-part des frameworks javascript interprètent cela comme une erreur.
Exemple : JQuery par exemple appel systématiquement son callback
fail()
.
Si WCF avait la bonne idée de nous retourner null
ou {}
, tout ce passerait bien.
Heureusement, il est possible d’altérer le comportement de WCF en codant un DispatcherMessageInspector
qui se chargera de retourné un message convenable.
Afin d’obtenir les meilleurs performances possibles, ce DispatcherMessageInspector ne lit pas le message pour vérifier son contenu. Il n’intervient qu’en cas de réponse avec un statut OK et si il n’y a pas de corps de message.
/// <summary>
/// Inspector pour modifier les réponse de WCf pour des message JSON vides ou null
/// </summary>
public sealed class JsonNullResponseAllowedInspector : IDispatchMessageInspector
{
/// <summary>
/// Modification
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState"></param>
public void BeforeSendReply(ref Message reply, object correlationState)
{
// Test si la réponse est positive
HttpResponseMessageProperty messageProperty = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
// Test si la réponse est valide
if (messageProperty.StatusCode == System.Net.HttpStatusCode.OK
// Test si le corps du message de réponse est suprpimée par WCF
&& messageProperty.SuppressEntityBody)
{
// Le nouveau corps du message peut être "{}" ou "null"
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes("null"));
// Création du message JSON
XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max);
// Création du message qui est retourné
Message newMessage = Message.CreateMessage(reader, int.MaxValue, reply.Version);
// Ajout du status OK
newMessage.Properties.Add(
HttpResponseMessageProperty.Name,
new HttpResponseMessageProperty
{
StatusCode = System.Net.HttpStatusCode.OK
});
// Ajout du format de réponse JSON
newMessage.Properties.Add(
WebBodyFormatMessageProperty.Name,
new WebBodyFormatMessageProperty(WebContentFormat.Json));
reply = newMessage;
}
}
/// <summary>
/// Inutile
/// </summary>
/// <param name="request"></param>
/// <param name="channel"></param>
/// <param name="instanceContext"></param>
/// <returns></returns>
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
return null;
}
}
Pour utiliser ce DispatcherMessageInspector, il faut bien évidement coder le Behavior qui le fournira à nos services :
/// <summary>
/// Behavior pour utiliser l'inspecteur
/// </summary>
public sealed class JsonNullResponseAllowedBehavior : IEndpointBehavior
{
/// <summary>
/// Application du JsonNullResponseAllowedInspector
/// </summary>
/// <param name="endpoint"></param>
/// <param name="endpointDispatcher"></param>
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new JsonNullResponseAllowedInspector());
}
/// <summary>
/// Inutile
/// </summary>
/// <param name="endpoint"></param>
/// <param name="bindingParameters"></param>
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
/// <summary>
/// Inutile
/// </summary>
/// <param name="endpoint"></param>
/// <param name="clientRuntime"></param>
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
/// <summary>
/// Inutile
/// </summary>
/// <param name="endpoint"></param>
public void Validate(ServiceEndpoint endpoint) { }
}
Et créer une extension à WCF pour pouvoir intégrer le Behavior dans le fichier de configuration.
/// <summary>
/// Extension pour activer le Behavior
/// </summary>
public sealed class JsonNullResponseAllowedExtension : BehaviorExtensionElement
{
/// <summary>
/// Type de JsonNullResponseAllowedBehavior
/// </summary>
public override Type BehaviorType
{
get
{
return typeof(JsonNullResponseAllowedBehavior);
}
}
/// <summary>
/// Nouvelle instance de JsonNullResponseAllowedBehavior
/// </summary>
/// <returns></returns>
protected override object CreateBehavior()
{
return new JsonNullResponseAllowedBehavior();
}
}
Pour finir, la configuration complète. On notera, l’introduction de l’extension dans la collection behaviorExtensions
, et l’utilisation de celle-ci via le <JsonNullResponseAllowed/>
sur un endpointBehaviors
.
<system.serviceModel>
<extensions>
<behaviorExtensions>
<!-- Ajout de l'extension à la liste des extensions disponibles -->
<add name="JsonNullResponseAllowed" type="MyLib.JsonNullResponseAllowedExtension, MyLib"/>
</behaviorExtensions>
</extensions>
<!-- Proticoles à défaut pour REST-->
<protocolMapping>
<add scheme="http" binding="webHttpBinding" bindingConfiguration="rest.http" />
<add scheme="https" binding="webHttpBinding" bindingConfiguration="rest.https" />
</protocolMapping>
<bindings>
<!-- Bindings Rest -->
<webHttpBinding>
<clear />
<binding name="rest.http">
<security mode="None" />
</binding>
<binding name="rest.https">
<security mode="Transport" />
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="">
<webHttp defaultBodyStyle="Bare" defaultOutgoingResponseFormat="Json" />
<!-- Utilisation de l'extension -->
<JsonNullResponseAllowed/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Voilà une nouvelle preuve que WCF peut être étendu sans inventer une nouvelle usine à gaz