(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
WCF
WCF permet l'échange de données (lecture et écriture) au travers d'un réseau ou d'un pipe.
WCF introduit le moteur de sérialisation DataContract.
Bon choix pour les communications non-HTTP (MSMQ, Named Pipes) et SOAP.
Pour les services web basés sur HTTP, préférer ASP.NET Core ou ASP.NET Web API 2.x.
[DataContract]
publicclassName
{
[DataMember]
publicstring First { get; set; }
[DataMember]
publicstring First { get; set; }
}
App.conf
<configuration><system.serviceModel><!-- Liste des services --><services><servicename="SoapDataService.HelloService"><!-- Liste des endpoints --><endpointaddress="net.tcp://localhost:8080/HelloService/"binding="netTcpBinding"contract="Contracts.IHelloService" /><endpointaddress="http://localhost/HelloService/"binding="basicHttpBinding"contract="Contracts.IHelloService" /><endpointaddress="net.pipe://localhost/HelloService/"binding="netNamedPipeBinding"contract="Contracts.IHelloService" />
<system.serviceModel><behaviors><serviceBehaviors><behavior><!-- To avoid disclosing metadata information, set the values below to false before deployment --><serviceMetadatahttpGetEnabled="true"httpsGetEnabled="true"/><!-- To receive exception details in faults for debugging purposes, set the value below to true.
Set to false before deployment to avoid disclosing exception information --><serviceDebugincludeExceptionDetailInFaults="false"/></behavior></serviceBehaviors></behaviors><!-- définit les binding par défaut en fonction de scheme --><protocolMapping><addscheme="http"binding="wsHttpBinding" /><addscheme="https"binding="wsHttpBinding" /></protocolMapping><serviceHostingEnvironmentaspNetCompatibilityEnabled="true"multipleSiteBindingsEnabled="true" /><!-- WCF configure lui-même les services à partir des fichiers *.svc
il n'est donc pas nécessaire d'ajouter le code suivant --><services><servicename="Namespace.HelloService"><endpointaddress=""binding="wsHttpBinding"contract="Namespace.IHelloService" /></service></services></system.serviceModel>
<system.serviceModel><services><servicename="Service.HelloService"><!-- adresse est vide ici car elle est définie dans le projet --><endpointaddress=""binding="wsHttpBinding"contract="Contracts.IHelloService" /></service></services><!-- fichier svc virtuel --><serviceHostingEnvironment><serviceActivations><addservice="Service.HelloService"relativeAddress="HelloService.svc"/></serviceActivations></serviceHostingEnvironment>
Lancement du service par le code - Self Host
// assembly System.ServiceModelServiceHosthost = newServiceHost(typeof(HelloService));
try
{
// Open the service host to accept incoming calls
host.Open();
// The service can now be accessed.
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
host.Close();
}
catch (CommunicationExceptioncommProblem)
{
Console.WriteLine("There was a communication problem. " + commProblem.Message);
Console.Read();
}
Client
Le client doit connaître l'interface. Ceci peut être fait par une copie du fichier contenant l'interface dans le projet client ou bien par référence de service.
Génération automatique du proxy
Lancer le service
Dans le projet client → clique-droit sur References → Add Service Reference → Coller l'adresse http://domain:port/service.svc
Changer le namespace: HelloServiceReference
// BasicHttpBinding_IHelloService: nom du endpoint du client, à récupérer dans le fichier App.conf du projet clientvarproxy = newHelloServiceClient("BasicHttpBinding_IHelloService");
proxy.Endpoint.Address; // http://localhost:53129/HelloService.svc
proxy.Endpoint.Binding; // System.ServiceModel.BasicHttpBinding
proxy.Endpoint.Contract; // System.ServiceModel.Description.ContractDescription
Console.WriteLine(proxy.Hello("everybody !!!"));
App.config
<!-- code généré par l'ajout de la référence à HelloService --><system.serviceModel><bindings><basicHttpBinding><bindingname="BasicHttpBinding_IHelloService" /></basicHttpBinding></bindings><client><endpointaddress="http://localhost:56307/HelloService.svc"binding="basicHttpBinding"bindingConfiguration="BasicHttpBinding_IHelloService"contract="HelloServiceReference.IHelloService"name="BasicHttpBinding_IHelloService" /></client></system.serviceModel>
<configuration><system.serviceModel><client><!-- s'il y a plusieurs endpoint avec le même contract, il faut leur donner un name pour les différencier. --><endpointaddress="net.tcp://localhost:8080/HelloService/"binding="netTcpBinding"contract="Contracts.IHelloService"name="tcpEP"/><endpointaddress="http://localhost/HelloService/"binding="basicHttpBinding"contract="Contracts.IHelloService"name="httpEP"/>
varfactory = newChannelFactory<IHelloServiceLocal>("httpEP");
IHelloServiceLocalproxy = factory.CreateChannel();
// avec ClientBaseHelloClientproxy = newHelloClient("tcpEP");
proxy.Hello("You!");
// génère un objet proxy à partir de la configclassHelloClient : ClientBase<IHelloService>, IHelloService
{
publicHelloClient(stringendpointName)
: base(endpointName)
{
}
publicstringHello(stringname)
{
return Channel.Hello(name);
}
}
Configuration
Binding
La configuration est à faire sur le serveur et sur les clients.
Web.config
<services><servicename="Namespace.PersonService"><endpointaddress="http://localhost/PersonService/"binding="wsHttpBinding"contract="Namespace.IPersonService"bindingConfiguration="specificWsHttpBindingConfig"/></service></services><bindings><netTcpBinding><!-- configuration par défaut pour tous les netTcpBinding --><binding></binding></netTcpBinding><wsHttpBinding><!-- configuration nommée --><bindingname="specificWsHttpBindingConfig"sendTimeOut="00:01:00"maxReceivedMessageSize="64000"><reliableSessioninactivityTimeout="00:20:00"order="false"/></binding></wsHttpBinding></bindings>
Behavior
Seulement sur le serveur.
Web.config
<services><servicename="Namespace.PersonService"behaviorConfiguration="MyBehavior"><endpointaddress="http://localhost/PersonService/"binding="wsHttpBinding"contract="Namespace.IPersonService"/></service></services><behaviors><serviceBehaviors><!-- sans name, devient le behavior par défaut --><behaviorname="MyBehavior"><serviceDebugincludeExceptionDetailInFaults="true" /><serviceThrottlingmaxConcurrentSessions="100"maxConcurrentCalls="16"maxConcurrentInstances="116" /></behavior></serviceBehaviors><endpointBehaviors></endpointBehaviors></behaviors>
À la différence de WebAPI, WCF fournit des metadata. Ce qui permet d'avoir une description complète de l'utilisation du service.
Dans VS, l'ajout d'une Service Reference utilise les metadata pour la création automatique de:
A new InstanceContext is created for each client request.
PerSession
A new InstanceContext is created for each new client session and maintained for the lifetime of that session. This requires a binding that supports transport sessions: TCP, IPC, WS-HTTP with Reliability or Security
Single
A single InstanceContext handles all client requests for the lifetime of the application.
Un seul appel à la fois par proxy. Les autres appels sont mis en attente. ThreadSafe.
Multiple
Tous les appels sont gérés en même temps. Locker les modifications des données.
Reentrant
Similaire à Single, mais permet plusieurs appels en même temps pour le même client (callback).
Contract Mismatch Equivalency
L'interface entre le serveur et le client doit être la même.
Si ce n'est pas le cas, il faut gérer les différences: Namespace, nom de l'interface, noms des méthodes.
namespaceClient
{
// même Namespace que pour le server// changement du nom de l'interface
[ServiceContract(Namespace = "http://localhost/HelloService", Name = "IHelloService")]
interfaceIHelloServiceClient
{
// changement du nom de la méthode
[OperationContract(Name = "Hello")]
stringHelloClient(stringname);
}
}
ContractNamesapce Assembly Attribut
Permet de définir le Namespace de tous les contrats de cette assembly.
La définition du Namespace dans l'atribut ServiceContract peut donc être supprimée.
Si on ajoute une propriété sur une classe de données sur le serveur mais pas sur celle du client, permet de récupérer quand même cette propriété sur le client.
DataContractSerializer ne permet pas la (dé)sérialisation entre le client et le serveur, car le xml généré contient le xmlns correspondant au namespace des classes qui est différent entre le client et le serveur.
Utiliser DataContractJsonSerializer ou [XmlSerializerFormat()]
Serveur REST
IPersonService.cs
// Création d'une interface de contrat
[ServiceContract]
[XmlSerializerFormat()] // utilisation de XmlSerializer au lieu de DataContractSerializerpublicinterfaceIPersonService
{
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)] // réponse au format JSON, XML est le format par défautIEnumerable<Person> GetAllPersons();
[OperationContract]
[WebGet(UriTemplate = "persons/{id}")]
PersonGetPerson(stringid);
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "persons")]
voidAddPerson(Personperson);
[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "persons/{id}")]
voidModifyPerson(Personperson);
[OperationContract]
[WebInvoke(Method = "DELETE", UriTemplate = "persons/{id}")]
voidDeletePerson(intid);
}
[DataContract]
publicclassPerson
{
[DataMember]
publicstring Id { get; set; }
[DataMember]
publicstring Name { get; set; }
}
App.conf
<system.serviceModel><!-- Liste des services --><services><servicename="RestDataService.PersonService"><host><baseAddresses><addbaseAddress = "http://localhost:8733/Design_Time_Addresses/RestDataService/PersonService/" /></baseAddresses></host><endpointaddress=""binding="webHttpBinding"contract="RestDataService.IPersonService"behaviorConfiguration="restfulBehavior" /><endpointaddress="mex"binding="mexHttpBinding"contract="IMetadataExchange"/></service></services><behaviors><endpointBehaviors><behaviorname="restfulBehavior"><webHttp /></behavior></endpointBehaviors></behaviors>
Client REST
Le client doit connaître l'interface. Ceci peut être fait par une copie du fichier contenant l'interface dans le projet client ou bien par référence de service.
Les Windows Credentials du client sont envoyés automatiquement au service. Ok pour une Desktop App, mais pose problème pour une WebApp, car le client est IIS.
App.config
<services><servicename="HostConsole.PersonService"><endpointaddress="net.tcp://localhost:8010/Service/"binding="netTcpBinding"contract="HostConsole.IService"/><endpointaddress="net.tcp://localhost:8010/ServiceAdmin/"binding="netTcpBinding"contract="HostConsole.IAdminService"bindingConfiguration="admin"/></service></services><bindings><netTcpBinding><bindingtransactionFlow="true"sendTimeout="00:20:00"><!-- turn off security --><securitymode="None" /></binding><bindingname="admin"transactionFlow="true"sendTimeout="00:20:00"><securitymode="Transport"><!-- Windows Auth (token) --><transportclientCredentialType="Windows" /></security></binding></netTcpBinding></bindings>
// restreint l'accès aux utilisateur authentifiés qui appartiennent au role Administrator// sinon SecurityAccessDeniedException : Access is denied.
[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
publicvoidDoSomething()
{ ... }
Impernation pour les applications web clientes
using (((WindowsIdentity)User.Identity).Impersonate())
{
var factory = newChannelFactory<IService>("tcpEP");
IServiceproxy = factory.CreateChannel();
proxy.DoSomething();
proxy.Close();
}
Internet Security
Pour permettre à tous les types de clients (non-Windows), l'authentification de fait avec un Username et un Password.
On utilise un certificat pour chiffrer les échanges (Username, Password)
Sur le serveur, l'authentification peut être fait via Windows ou ASP.NET
Négociation
PeerTrust le client a une copie du certificat (sans la clé privé)
ChainTrust le client a la clé publique encodé en base 64
HostConsole/App.config
<services><servicename="HostConsole.PersonService"><endpointaddress="http://localhost/PersonService/"binding="wsHttpBinding"contract="HostConsole.IPersonService"/><endpointaddress="http://localhost/PersonServiceAdmin/"binding="wsHttpBinding"contract="HostConsole.IPersonAdminService"bindingConfiguration="admin"/></service></services><bindings><wsHttpBinding><bindingtransactionFlow="true"sendTimeout="00:20:00"><securitymode="None" /></binding><bindingname="admin"transactionFlow="true"sendTimeout="00:20:00"><securitymode="Message"><messageclientCredentialType="UserName"negotiateServiceCredential="false"/></security></binding></wsHttpBinding></bindings><behaviors><serviceBehaviors><behavior><serviceDebugincludeExceptionDetailInFaults="true"/><serviceCredentials><!-- où se trouve le certificat --><serviceCertificatestoreLocation="LocalMachine"storeName="Root"findValue="WcfEndToEnd"x509FindType="FindBySubjectName" /><!-- authentifier le username et password --><userNameAuthenticationuserNamePasswordValidationMode="Windows" /></serviceCredentials><serviceAuthorizationprincipalPermissionMode="UseWindowsGroups" /></behavior></serviceBehaviors></behaviors>
L'utilisation des metadata mex permettent d'obtenir le certificat à la création de la référence au service.
REM génération d'un fichier de certificat et installation dans Trusted Root Certification AuthoritiesREM makecert.exe n'est pas fournit pas Windows
makecert.exe -sr LocalMachine -ss Root -pe -sky exchange -n "CN=WcfEndtoEnd" WcfEndToEnd.cer
Générer le fichier de la public key: Win → certmgr.msc → Certificates (Local Computer) → Trusted Root Certification Authorities → Certificates → clique-droit sur le certificat → All Tasks → Export:
REM génération d'un fichier de certificat et installation dans PersonalREM makecert.exe n'est pas fournit pas Windows
makecert.exe -sr LocalMachine -ss My -pe -sky exchange -n "CN=WcfEndtoEnd" WcfEndToEnd.cer
Installer le certificat sur le client dans Trusted People.
<client><endpointaddress="http://localhost/PersonServiceAdmin/"binding="wsHttpBinding"contract="ClientConsole.IAdminService"bindingConfiguration="admin"behaviorConfiguration="admin"><identity><!-- par défaut, cherche un certificat ayant le même nom que le domain (localhost) --><dnsvalue="WcfEndToEnd"/></identity></endpoint></client><behaviors><endpointBehaviors><behaviorname="admin"><clientCredentials><serviceCertificate><authenticationcertificateValidationMode="PeerTrust" /></serviceCertificate></clientCredentials></behavior></endpointBehaviors></behaviors>
ASP.NET Provider / Identity
Permet de coder son propre moyen d'authentification.
Création du certificat:
IIS Manager → sélectionner serveur → Server Certificates → Create Self-Signed Certificate
IIS Self-Signed Certificate créé l'erreur The certificate is not valid for the name ...
Menu → Turn windows features on or off → Internet Information Services → World Wide Web Services → Application Development Features
Cocher tous sauf CGI
The page you are requesting cannot be served because of the extension configuration
Menu → Turn windows features on or off → .NET Framework Advanced Services → WCF Services → HTTP Activation
The Web server is configured to not list the contents of this directory
Web.config
<configuration><system.webServer><!-- autoriser le serveur à lister son contenu --><directoryBrowseenabled="true" />