Problème rencontré lors de l'appel à un service WCF
Lors de l'invocation initiale d'un service WCF, une expection courante survient :
System.ServiceModel.EndpointNotFoundException : Aucun point de terminaison n’écoute à l’adresse http://xx.xx.xx.xx:port/Service1.svc.
Cela est généralement dû à une adresse incorrecte ou à une action SOAP mal formée.
InnerException : Le serveur distant a retourné une erreur : (404) Introuvable.
En tentant d’accéder directement via le navigateur à une méthode comme http://xx.xx.xx.xx:port/Service1.svc/IService1/GetInfoByID, la réponse affichée est simplement :
« Aucun point de terminaison trouvé. »
Cette erreur indique clairement que le problème ne provient pas du client, mais bien de la configuration ou de l’hébergement du service côté serveur.
Analyse des causes possibles
Après plusieurs essais infructueux et modifications empiriques sans succès, deux sources principales ont été identifiées :
- Une mauvaise configuration dans web.config
- Un problème lié à l’environnement d’hébergement (IIS ou IIS Expresss)
La suppression complète du fichier web.config suivie par la réécriture à partir d’un exemple fonctionnel a permis de résoudre immédiatement le problème. Cela confirme que la cause racine était bien liée à la configuration du modèle de service WCF.
Configuration fonctionnelle de base pour un service WCF RESTful
Voici une configuration web.config validée, permettant à la fois l’exposition du contrat via webHttpBinding et l’accès aux métadonnées nécessaires au débogage :
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="metadataBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="webBehavior">
<webHttp helpEnabled="true"/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="MyNamespace.UserService" behaviorConfiguration="metadataBehavior">
<endpoint address=""
binding="webHttpBinding"
contract="MyNamespace.IUserContract"
behaviorConfiguration="webBehavior" />
</service>
</services>
</system.serviceModel>
</configuration>
Cette configuration simplifiée active :
- L'accès HTTP aux métadonnées (
httpGetEnabled) - Le mode débogage pour voir les excetpions internes
- Le comportement
webHttpnécessaire pour exposer des opérations REST
Support des appels AJAX depuis un navigateur
Pour permettre aux clients JavaScript d’appeler le service WCF via AJAX (y compris gestion des requêtes cross-origin), la configuration suivante étendue est nécessaire :
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="ajaxBehavior">
<enableWebScript />
<webHttp helpEnabled="true"/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="fullDebugBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<bindings>
<webHttpBinding>
<binding name="largeBufferBinding"
maxReceivedMessageSize="2147483647"
crossDomainScriptAccessEnabled="true">
<readerQuotas maxStringContentLength="2147483647"
maxArrayLength="2147483647"/>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="MyApp.Service1" behaviorConfiguration="fullDebugBehavior">
<endpoint address=""
binding="webHttpBinding"
bindingConfiguration="largeBufferBinding"
contract="MyApp.IService1"
behaviorConfiguration="ajaxBehavior" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
</customHeaders>
</httpProtocol>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
Les points clés ici sont :
enableWebScript: indispensable pour activer la compatibilité avec ASP.NET AJAXcrossDomainScriptAccessEnabled="true": autorise les requêtes JSONP ou CORS- Les en-têtes HTTP personnalisés pour gérer les politiques de sécurité du navigateur
runAllManagedModulesForAllRequests="true": assure que les modules .NET traitent toutes les requêtes, même sans extension .svc
Exemple d'interface de contrat compatible AJAX
Le contrat doit être correctement décoré pour supporter les appels HTTP non-SOAP :
[ServiceContract(Namespace = "MyAppService")]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "/AddTabEntry")]
string T_TabName_Add(string jsonParames);
}
Implémentation du service avec prise en charge contextuelle
Dans l’implémentation, il peut être utile de récupérer les paramètres depuis HttpContext si les données arrivent via GET ou sont encodées différemment :
public class Service1 : IService1
{
public string T_TabName_Add(string jsonParames)
{
// Gestion fallback pour requêtes GET
if (string.IsNullOrEmpty(jsonParames))
{
var context = HttpContext.Current;
if (context?.Request.QueryString["jsonParames"] != null)
{
jsonParames = context.Request.QueryString["jsonParames"];
}
}
if (string.IsNullOrEmpty(jsonParames))
{
return JsonConvert.SerializeObject(new { ret = "0", msg = "Paramètre manquant." });
}
// Traitement métier ici...
return JsonConvert.SerializeObject(new { ret = "1", msg = "OK" });
}
}
Fichier de service (.svc)
Le fichier Service.svc reste minimal :
<%@ ServiceHost Language="C#" Debug="true" Service="MyApp.Service1" CodeBehind="Service.svc.cs" %>
Il indique au moteur ASP.NET comment instancier le service WCF.