Download - Windows Communication Foundation
Windows Communication Foundation
Bartłomiej ZassISV Developer EvangelistMicrosoft
Od obiektów do usług
PolimorfizmHermetyzacjaDziedziczenie
KomunikatSchemat + kontrakt + polisaSzeroka współpraca
Położenie dowolneŚcisły związekMetadane w czasie działania
Obiektowe UsługiKomponentowe
1980 20001990
Zadania serwera aplikacyjnego
• „Pojemnik” na obiekty realizujące daną funkcjonalność– Jak pisać logikę obiektów?
• Infrastruktura: transakcje, sesje, stan…• Mechanizmy komunikacji• Skalowalność: platforma + „opcje dla programisty”• Administrator:
– Nadzór nad działaniem „platformowym”• Zużycie pamięci, procesora, wątki itp.
– Monitorowanie działania „biznesowego”• O ile będzie rejestrowane w zrozumiały sposób…
Serwer aplikacyjny to urządzenie które dostarcza aplikację do urządzeń klienckich (za Wikipedia).Komputer dedykowany do wykonania określonych programów/zadań.
+ architektura
MTSKomponentyRuntime aplikacjiDeklaratywne transakcje i bezpieczeństwoAutoryzacja oparta o role
COM+ Luźno powiązane zdarzeniaKomponentyu kolejkowane„przepustnica” – nie więcej niż x komponentów
Enterprise ServicesModel programowania w kodzie zarządzalnymOparty o atrybuty, deklaratywny, konfiguracja w XML
Windows Communication Foundation• Komunikaty oparte o XML• Dowolny transport• Zorientowane na usługi• Bezpieczeństwo: Federacja, CardSpace
(dowody tożsamości)• Hosting - gdziekolwiek
Ewolucja usług aplikacyjnych
-2002 2002-2006 2006-
Problem – komunikacja…
4 podstawowe doktryny SOA
Wyraźne granice
Usługi są autonomiczne
Usługi dzielą kontrakt nie klasę
Kompatybilność określana przez
policy
Wprowadzenie do WCF
WCF: Adres, Binding, Kontrakt
Klient Usługa
KomunikatABC A B C
A B C
Adres Binding Kontrakt
(Gdzie) (Jak) (Co)
Endpoint
Endpoint
Endpoint
EncoderTransport
BasicHttp, WSHttp, WSDualHttp,
WSFederation…Context…
NetTcp, NetNamedPipe,
NetPeerTcp
NetMsmq, MsmqIntegration
WCF – standardowe bindingiBinding Interop Bezp. Sesja Trans. Duple
x BasicHttpBinding BP 1.1 N, T N N n/a
WSHttpBinding WS M, T, X N, T, RS N, Tak n/a
WSDualHttpBinding WS M RS N, Tak Tak
WSFederationBinding Federacja M N, RS N, Tak Nie
NetTcpBinding .NET T, M T ,RS N, Tak Tak
NetNamedPipeBinding .NET T T, N N, Tak Tak
NetPeerTcpBinding Peer T N N Tak
NetMsmqBinding .NET T, M, X N N, Tak Nie
MsmqIntegrationBinding MSMQ T N N, Tak n/a
N = Brak | T = Transport | M = Wiadomość | B = Oba | RS = Pewna sesja
WCF – podstawy (wyjaśnienie)
• Separacja kontraktu i implementacji• Wzorce komunikacyjne
– Komunikacja jednokierunkowa (IsOneWay)– Zwracanie wartości– Sesja,– Kontrakt „zwrotny” (po stronie klienta)– Kontekst
• Separacja szczegółów komunikacyjnych• Hosting: Jakkolwiek
WCF na jednym slajdzie
Definicja „końcówki”Adres + Binding +
Kontrakt
Definicja kontraktu
Implementacja usługi
[ServiceContract]public interface IMyInterface {
[OperationContract]void MyMethod();
[ServiceBehavior(InstanceContextMode=Single]public class MyService: IMyInterface {[OperationBehavior(Impersonation = ImpersonationOption.Required)]public void MyMethod() { … }
<service name="MyService"> <endpoint address=“net.tcp://localhost:1234/MySvc" binding="netTcpBinding" contract="IMyInterface" />o
Kontrakt
• Definiują funkcjonalność usługi. • Atrybuty kontraktu:
– ServiceContract• OperationContract
– DataContract– FaultContract– MessageContract
• MessageBody• MessageHeader
• Zachowania kontraktu– Sesja, transakcje, sposób inicjacji…
Kontrakt
• Możliwość implementacji wielu kontraktów• Publiczny konstruktor• [ServiceContract(Namespace = „”)]
– Domyślnie – tempuri.org– Intranet – np. nazwa aplikacji– Intenet – URL
• Nazwa metody usługi – domyślnie z klasy– [OperationContract(Name = „…”)]
Host
• IIS + WAS (Vista+, Windows Server 2008+)– Zarządzanie, skalowalność, itp.
• IIS 5/6 – tylko HTTP– Web.config – eksponowane usługi
• Self-hosting– InProc – szczególny przypadek (klient i serwer w
tym samym procesie)– Między procesami– Między maszynami
ServiceHost
public static void Main( ){ Uri baseAddress1 = new Uri("net. tcp: //localhost: 8001/"); ServiceHost host1 = new ServiceHost(typeof(MyService), baseAddress1) ; ServiceHost host = new ServiceHost(typeof(MyService)); host.Open( );
//Możliwe blokujące wywołania Application.Run(new MyForm( )); host.Close( );}
// WcfSvcHost
WAS
• Nie ograniczony do HTTP– Dowolny transport, port, kolejka
• Zalety vs self-hosting– Application pooling, recycling, zarządzanie idle-
time, izolacja– Zalecane, kiedy dostępne Windows Server 2008
• Interakcja z procesem hosta<%@ ServiceHost
Language = "C#" Debug = "true" CodeBehind = "~/App_Code/MyService. cs" Service = "MyService" Factory = "MyServiceFactory" %>
Service Factory
class MyServiceFactory : ServiceHostFactory{ protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) { ServiceHost host = new ServiceHost(serviceType,
baseAddresses);
// Dodatkowe kroki – np. logowanie return host; }}
Bindingi
• Protokół (HTTP, TCP, IPC, MSMQ)• Enkodowanie (plain text, MTOM)• Bezpieczeństwo (transport, komunikat)• …
Inne popularne bindingi
• NetPeerTcpBinding• WSFederationHttpBinding• WS2007FederationHttpBinding• MsmqIntegrationBinding• WebHttpBinding• WS2007HttpBinding
Wybór bindingu
Endpoint
• „Końcówka”, z którą możemy się komunikować– Przynajmniej jeden dla każdej usługi
• Dokładnie jeden kontrakt
Endpointy - konfiguracja<system.serviceModel = ""> <services> <service name = "MyNamespace.MyService"> <endpoint address = "http://localhost:8000/MyService" binding = "wsHttpBinding" contract = "MyNamespace.IMyContract„
bindingConfiguration = "TransactionalTCP /> </service> </services>
<!–- <bindings> <netTcpBinding> <binding name = "TransactionalTCP" transactionFlow = "true" /> </netTcpBinding>
</bindings> -->
</system.serviceModel>
Metadane• Domyślnie – nie publikowane (nie przeszkadza w działaniu)• Dedykowany endpoint (MEX)
– Eksponowany w dowolny sposób (HTTP, TCP, IPC)
<endpoint address = "http: //localhost: 8000/MEX" binding = "mexHttpBinding" contract = "IMetadataExchange" />
• HTTP-GET (nie ma gwarancji interop)
<behaviors> <serviceBehaviors>
<behavior name = "MEXGET"> <serviceMetadata httpGetEnabled = "true"/> </behavior>
</serviceBehaviors> </behaviors>
Po stronie klienta
• Add service reference – automatycznie• SvcUtil
SvcUtil http://localhost:8000/MEX /out:Proxy.cs
• WcfTestClient. exe http: //localhost:9000/• Ręcznie - kodpublic abstract class ClientBase<T> : ICommunicationObject, IDisposable{ protected ClientBase(string endpointName); protected ClientBase(Binding binding, EndpointAddress remoteAddress); public void Open(); public void Close(); protected T Channel { get; } //...}
[ServiceContract(Namespace = "MyNamespace")]interface IMyContract{ [OperationContract] void MyMethod();}
partial class MyContractClient : ClientBase<IMyContract>, IMyContract{ public MyContractClient() { } public MyContractClient(string endpointName) : base(endpointName) { } public MyContractClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { } /* Dodatkowe konstruktory */ public void MyMethod() { Channel.MyMethod(); }}
Wywoływanie usługi
MyContractClient proxy = new MyContractClient("MyEndpoint");proxy.MyMethod( );proxy.Close( );
// Można również using…
Bez generacji kodu - ChannelFactory
Binding binding = new NetTcpBinding( );
EndpointAddress address = new EndpointAddress("net.tcp://localhost:8000");
IMyContract proxy =
ChannelFactory<IMyContract>.CreateChannel(binding, address) ;
using(proxy as IDisposable) {
proxy.MyMethod( ); }
Transport session• Niektóre bindingi (np. basic – nie)• Mapowanie komunikatu na konkretny kontekst wywołujący
operacje usługi• Reliability
– Transport reliability – pakiety, itp.– Message reliability – kolejność (domyślnie) i informowanie kiedy nie
dostarczono (ponawianie, itp.)
<binding name = "ReliableTCP"> <reliableSession enabled = "true"/>
</binding>
Kolejność komunikatów• Nie powinniśmy wymagać konkretnego bindingu, ale czasem
istotna jest kolejność.
[DeliveryRequirements(TargetContract = typeof(IMyContract), RequireOrderedDelivery = true) ]
class MyService : IMyContract, IMyOtherContract { ... }
[ServiceContract] [DeliveryRequirements(RequireOrderedDelivery = true) ] interface IMyContract { ... }
Kontrakt
Overloading
// Błąd!
[ServiceContract]interface ICalculator{ [OperationContract] int Add(int arg1, int arg2);
[OperationContract] double Add(double arg1, double arg2);}
// Rozwiązanie:// [OperationContract(Name = „AddInt”)]
// Proxy: AddInt -> również możliwa ręczna zmiana// nazw i OperationContract
Dziedziczenie• Możliwe, ale [ServiceContract] nie jest dziedziczony
[ServiceContract]interface ISimpleCalculator{ [OperationContract] int Add(int arg1, int arg2);}[ServiceContract]interface IScientificCalculator : ISimpleCalculator{ [OperationContract] int Multiply(int arg1, int arg2);}
// „Spłaszczane” po stronie klienta// Możliwe ręczne przywrócenie hierarchii po stronie klienta i usunięcie atrybutów// [OperationContract]
[OperationContract(Action = "... /ISimpleCalculator/Add", ReplyAction = "... /ISimpleCalculator/AddResponse")]
int Add(int arg1, int arg2) ;
[OperationContract(Action = "... /IScientificCalculator/Multiply",
ReplyAction = "... /IScientificCalculator/MultiplyResponse")] int Multiply(int arg1, int arg2) ;
Zgodność z kontraktem// Dynamiczna weryfikacja zgodności na podstawie WSDL
bool contractSupported = false; Uri mexAddress = new Uri("...?WSDL") ;
ServiceEndpointCollection endpoints = MetadataResolver. Resolve(typeof(ISimpleCalculator) ,
exAddress, MetadataExchangeClientMode. HttpGet) ;
if(endpoints. Count > 0) {
contractSupported = true; }
Kontrakty danych
Wymiana danych
• Infoset – obsługiwane typy parametrów, …• Serializacja w .NET
– [Serializable], [NonSerialized]– BinaryFormatter, SoapFormatter – wymaga .NET i
konkretnego typu– WCF – DataContractSerializer – nie udostępnia
informacji o typie; tylko kontrakt danych• Wyłącznie serializowalne typy
– Klasa musi być dostępna także po stronie klienta
Atrybuty kontraktu danych
• Samo [Serializable] – zbyt ścisłe powiązanie• [DataContract]
– Publikowane w MEX
[DataContract]struct Contact{ [DataMember] public string FirstName;
[DataMember] public string LastName;}// *Dla uproszczenia bez właściwości// Właściwości mogą być private, koniecznie get i set
Zdarzenia serializacji
• Np. inicjalizacja• [OnSerializing], [OnSerialized]
[OnDeserializing], [OnDeserialized]
[DataContract]class MyDataContract{
[OnSerializing] void OnSerializing(StreamingContext context) {. . .}
}
Inne• Współdzielenie kontraktu
– Problem: ten sam typ w 2 serwisach (przestrzenie nazw)– „Reuse types in reference assemblies”
• Dziedziczenie– [KnownType], [ServiceKnownType]
// Customer : ContactContact contact = new Customer( ); ContactManagerClient proxy = new ContactManagerClient( );
// Błąd w czasie działaniaproxy.AddContact(contact) ; proxy.Close( );
// ------------------------
[DataContract][KnownType(typeof(Customer))]class Contact { ... }
[DataContract]class Customer : Contact { ... }
Współdzielenie kontraktu
• Zgodność z infoset - np. wersjonowanie• Dostosowanie klasy właściwością Name
[DataContract(Name = "Contact")]struct Person{ [DataMember(Name = "FirstName")] public string Name; [DataMember(Name = "LastName")] public string Surname;}
Współdzielenie a serializacja (1)
• Od najbardziej ogólnej do najbardziej szczegółowej
• Pola – alfabetycznie
[DataContract] class Contact { [DataMember] public string FirstName; [DataMember] public string LastName;}[DataContract]class Customer : Contact{ [DataMember] public int CustomerNumber;}// Infoset: Firstname, Lastname, CustomerNumber
Współdzielenie a serializacja (2)[DataContract(Name = "Customer")]public class Person{ [DataMember(Name = "FirstName")] public string Name; [DataMember(Name = "LastName")] public string Surname; [DataMember] public int CustomerNumber;}// Kolejność: CustomerNumber, Firstname, Lastname
[DataContract(Name = "Customer")]public class Person{ [DataMember(Name = "FirstName", Order = 1)] public string Name; [DataMember(Name = "LastName", Order = 1)] public string Surname; [DataMember(Order = 2)] public int CustomerNumber;}// Kolejność zgodna z infoset: Firstname, Lastname, CustomerNumber
Wersjonowanie kontraktu
• Dodawanie nowych pól– Nadmiarowość jest dozwolona i kompatybilna
• Brakujące pola– Wartości domyślne (null, itp.)– [OnDeserializing]– [DataMember(IsRequired = true)] – wyjątek– Problem z przekazywaniem dalej – utracona
informacja
Wersjonowanie c.d.[DataContract]class Contact : IExtensibleDataObject{ ExtensionDataObject IExtensibleDataObject.ExtensionData { get; set; }
[DataMember] public string FirstName; [DataMember] public string LastName;}// Nie tracimy nadmiarowej informacji
// Umożliwia to interakcję z serwisem spodziewającym się innego// kontraktu. Dlatego można wykluczyć taki scenariusz:
[ServiceBehavior(IgnoreExtensionDataObject = true)] class ContactManager : IContactManager { ... }
// Najlepsza praktyka: zawsze obsługa IExtensibleDataObject// unikać IgnoreExtensionDataObject
Sposoby instancjonowania
Kontekst
Zarządzanie kontekstem
• Tryby instancjonowania kontekstów• Per Call• Per Session• Singleton
Per Call
• Bezstanowość, skalowalność– Konieczna inicjalizacja i zapis stanu
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]class MyService : IMyContract{ ... }
[ServiceContract]interface IMyContract{ [OperationContract] void MyMethod( );}[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall) ]class MyService : IMyContract, IDisposable{ int m_Counter = 0; MyService( ) { Trace. WriteLine("MyService. MyService( )"); } public void MyMethod( ) { m_Counter++; Trace. WriteLine("Counter = " + m_Counter) ; } public void Dispose( ) { Trace. WriteLine("MyService. Dispose( )"); }}///////////////////////// Client Code /////////////////////MyContractClient proxy = new MyContractClient( );proxy.MyMethod( );proxy.MyMethod( );proxy.Close( );// Wyjście:MyService.MyService( )Counter = 1MyService.Dispose( )MyService.MyService( )Counter = 1MyService.Dispose( )
Per Call i sesja komunikacyjna
• Jeśli serwis ma tryb single-threaded i włączone transport session, żądania przetwarzane jeden po drugim– Bez sesji – możliwa losowa kolejność na wyjściu
• Uwaga: Load Balancer i sesja– Sticky Sessions
Per Session• Domyślny tryb• Instancja utrzymywana w pamięci
– Skalowalność
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] class MyService : IMyContract { ... }
• Wymagane transport session– Zalecane wymuszenie na poziomie kontraktu
public enum SessionMode{ Allowed, Required, NotAllowed}
[ServiceContract(SessionMode = SessionMode.Allowed)] interface IMyContract {. . . }
Singleton
• Tworzony wraz ze startem procesu hosta• Sesja nie wymagana
– Po zakończeniu, nadal instancja w pamięci
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] class MyService : IMyContract { ... }
• Możliwe przekazanie zainicjalizowanej instancji klasy do konstruktora hosta
Sesja a kontrakt
• Atrybuty gwarantujące prawidłową kolejność wywołań
[ServiceContract(SessionMode = SessionMode.Required)]interface IMyContract{ [OperationContract] void StartSession(); [OperationContract(IsInitiating = false)] void CannotStart(); [OperationContract(IsTerminating = true)] void EndSession(); [OperationContract(IsInitiating = false, IsTerminating = true)] void CannotStartCanEndSession();}
Zwalnianie kontekstu
• ReleaseInstanceMode.BeforeCall• ReleaseInstanceMode.AfterCall• ReleaseInstanceMode.BeforeAndAfterCall
Durable services• Długotrwałe procesy
– Wywołanie – długi czas oczekiwania - wywołanie• Trwałe zapisywane stanu (np. baza danych)
– Serializacja – deserializacja– Provider (domyślny lub własny)
• ID instancji– Komunikaty aktywacyjne (jeśli brak ID)– Komunikaty kończące (usunięcie instancji)
<behaviors> <serviceBehaviors> <behavior name = "DurableService"> <persistenceProvider type = "... type ... , ... assembly ... " <!-- Provider-specific parameters --> /> </behavior> </serviceBehaviors></behaviors>
[Serializable][DurableService]class MyCalculator : ICalculator{ double Memory { get; set; }
[DurableOperation(CanCreateInstance = true)] public double Add(double number1, double number2) { return number1 + number2; } public void MemoryStore(double number) { Memory = number; }
[DurableOperation(CompletesInstance = true)] public void MemoryClear() { Memory = 0; } //Rest of the implementation}
Throttling• Tymczasowe piki w obciążeniu
– Kolejkowanie, spłaszczenie piku
• Nie działa w przypadku stałego zwiększenia obciążenia
• Możliwe do konfiguracji: maxConcurrentCalls, maxConcurrentSessions, maxConcurrentInstances
Operacje
One-way
• [OperationContract(IsOneWay = true)]– Przed metodą usługi– Domyślnie – false– Metoda musi zwracać void (inaczej błąd)
• Wyjątki usługi nie dotrą do klienta• Błędy komunikacyjne nadal się pojawiają• Jeśli reliablesession (transport session) – wtedy jest
przerywana
• Najlepsza praktyka: tylko PerCall i Singleton
Operacje duplex
• Niektóre bindingi– WSDualHttpBinding, NetTcpBinding, NetNamedPipeBinding– Nie jest standardem
• Odpowiedź wywoływana przez serwis na klasie klienta– WSDualHttpBinfing - domyślnie port 80 (możliwa zmiana)
• Wywołanie natychmiastowe lub referencja na poźniej
IMyContractCallback callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );
interface ISomeCallbackContract{ [OperationContract] void OnCallback();}[ServiceContract(CallbackContract = typeof(ISomeCallbackContract))]interface IMyContract{ [OperationContract] void DoSomething();}
Po stronie klienta
class MyCallback : IMyContractCallback { public void OnCallback( ) { ... } }
IMyContractCallback callback = new MyCallback(); InstanceContext context = new InstanceContext(callback); MyContractClient proxy = new MyContractClient(context) ; proxy.DoSomething();
Callback
• Wywoływanie callbacku w operacji usługi– Domyślnie 1 wątek ma dostęp do metod– W trakcie wywoływanej operacji lock na instancję
kontekstu– Po wywołaniu callback – odpowiedź do serwera na
ten sam (zablokowany) kanał• ConcurrencyMode
– Single– Multiple – ok, kłopoty z synchronizacją– Reentrant – ok
• Lub: metody OneWay (brak odpowiedzi)
Streaming
• Klasyczne wywołania – buforowane po stronie serwera– Blokowanie do czasu zakończenia przesyłania
komunikatu• Streaming (wyłącznie klasa Stream)
– Niektóre bindingi (TCP, IPC, Basic HTTP)– Niemożliwy do wykorzystania z message-level security<bindings>
<basicHttpBinding> <binding name = "StreamedHTTP"
transferMode = "Streamed„ maxReceivedMessageSize = "120000"/>
</basicHttpBinding> </bindings>
Streaming c.d.[ ServiceContract] interface IMyContract {
[OperationContract] Stream StreamReply1( );
[OperationContract] void StreamReply2(out Stream stream) ;
[OperationContract] void StreamRequest(Stream stream) ;
[OperationContract(IsOneWay = true)]void OneWayStream(Stream stream);
}
Błędy
Podstawy
• Błędy komunikacyjne i kanałów• Błędy po stronie serwisu
– Izolacja błędów od klienta!• Jeśli jest sesja – automatycznie stan kanału
CommunicationState.Faulted
// w klasycznym .NET ok – tu CommunicationObjectFaultedException// uwaga na using!- po dispose nowa instancja proxy
IMyContract obj = new MyClass( );try{ obj.MyMethod( );}catch{}obj.MyMethod( );
Propagacja błędów
• Ustandaryzowane – SOAP fault• FaultException<T>
class Calculator : ICalculator{ public double Divide(double number1, double number2) { if (number2 == 0) { DivideByZeroException exception = new DivideByZeroException(); throw new FaultException<DivideByZeroException>(exception); } return number1 / number2; }}
Kontrakty błędów• Każdy błąd dociera do klienta jako FaultException
– Także FaultException<T> (dziedziczy po FaultException)– Wszystko co jest wymieniane musi być w kontrakcie
• [FaultContract]– Musi być dokładnie klasa błędu– Nie może być to nawet klasa dziedzicząca!– Może być kilka – dla kilku typów– Nie można aplikować dla operacji OneWay (błąd)– Nie zamyka kanału komunikacji (każdy dziedziczący po FaultException)
• Kontrakt publikowany w metadanych– Klasy błędów importowane podczas generacji proxy
• IErrorHandler
[OperationContract] [FaultContract(typeof(DivideByZeroException))] double Divide(double number1, double number2) { ... }
// jeśli typeof(Exception) -może być tylko throw Exception!
Debugowanie - ExceptionDetail[ServiceBehavior(IncludeExceptionDetailInFaults = true)]class MyService : IMyContract{ public void MethodWithError() { throw new InvalidOperationException("Some error"); }}
// ---------------------------------------
MyContractClient proxy = new MyContractClient( );try{ proxy.MethodWithError( );}catch(FaultException<ExceptionDetail> exception){ Debug.Assert(exception.Detail.Type == typeof(InvalidOperationException).ToString( )); Debug.Assert(exception.Message == "Some error") ;}
// Może być ustawiane ręcznie / w konfiguracji hosta (debugowanie istniejącego serwisu!)// Uwaga przy deployment!
Transakcje
Transakcje• Wymagane zasoby transakcyjne
– WCF Resource Managers– Np. baza danych czy kolejka MSMQ, Volatile Resource Managers– http://msdn.microsoft.com/en-us/magazine/cc163688.aspx#S8
• Transakcje rozproszone– Two-phase commit i transaction manager– 1 faza: voting, 2 faza: faktyczny commit
• Protokoły (wybór automatyczny)– Lightweight – app domain– OleTX – intranet– WS-Atomic Trnsaction (WSAT) - internet
Propagacja transakcji
• Obsługują wybrane bindingi– TCP, IPC, WS http
• Domyślnie – wyłączone– Konieczne włączenie po stronie klienta i serwera
• Sesja – nie wymagana, ale zalecana
<bindings> <netTcpBinding> <binding name = "TransactionalTCP" transactionFlow = "true" /> </netTcpBinding></bindings>
Propagacja transakcji c.d.
• Nie wszystkie operacje muszą obsługiwać transakcje• [TransactionFlow]
– Allowed– NotAllowed– Mandatory
• Operacje OneWay – błąd
[ServiceContract]interface IMyContract{ [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void MyMethod();}
Transaction Manager• Nadzoruje transakcję• Lightweight Transaction Manager (LTM)
– AppDomain, SQL 2005/2008• Kernel Transaction Manager (KTM)
– Vista / Windows Server 2008– KRM: Transactional file system (TxF), Transaction registry (TxR)– Tylko jeden serwis
• Distributed Transaction Coordinator (DTC)– Wykorzystywany w transakcjach rozproszonych– OleTX / WSAT
• Każda transakcja najpierw zarządzana przez LTM– Jeśli więcej usług / zasobów – promocja do DTC– Jeśli zasób KRM – promocja do KTM
Transakcja z DTC
Ambient transaction
• Transakcje wewnątrz thread local storage (TLS)– Transaction t = Transaction.Current;
• Automatyczne przenoszenie transakcji na stronę usługi• Transaction Scope
class MyService : IMyContract{ [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod() { Transaction transaction = Transaction.Current; Debug.Assert(transaction != null); }}
Przykładowy przepływ
Głosowanie (commit)
• Deklaratywnie – Zalecane– domyślnie TransactionAutoComplete = true– Jeśli mamy strukturę try-catch, wyrzucić wyjątek dalej
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void MyMethod( ) { ... }
[OperationBehavior(TransactionScopeRequired = true)] public void MyMethod( )
{ ... }
Głosowanie (kod)• Zatwierdzenie powinno być ostatnim wywołaniem w metodzie
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false) ]
public void MyMethod( ){ try { /* Na koniec: */ OperationContext.Current.SetTransactionComplete( ); } catch { /* np. logowanie, następnie: */ throw; }}
Izolacja transakcji
• Domyślnie – Unspecified– Wykorzystywany poziom transakcji klienta
• Jeśli inny – klient musi mieć zadeklarowany poziom transakcji taki sam (inaczej błąd)
class MyService : IMyContract { ... }
[ServiceBehavior(TransactionIsolationLevel = IsolationLevel.Unspecified)]
class MyService : IMyContract
{ ... }
Klientusing (TransactionScope scope = new TransactionScope()){ MyContractClient proxy1 = new MyContractClient( ); proxy1.MyMethod( ); proxy1.Close( ); MyOtherContractClient proxy2 = new MyOtherContractClient( ); proxy2.MyOtherMethod( ); proxy2.Close( ); scope.Complete( );}
Instancjonowanie PerCallMyContractClient proxy = new MyContractClient( );using(TransactionScope scope = new TransactionScope( )){ proxy.MyMethod( ... ) ; proxy.MyMethod( ... ) ; scope.Complete( );}proxy.Close( );
Instancjonowanie PerSession
• Domyślnie – podobnie jak PerCall– Po scope.Complete – instancja usuwana– ReleaseServiceInstanceOnTransactionComplete– ConcurrencyMode musi być Single
[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = true)]class MyService : IMyContract{ [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod( ) { ... } [OperationBehavior( ... )] public void MyOtherMethod( ) { ... }}
„Prawdziwe” PerSession• ReleaseServiceInstanceOnTransactionComplete = false• Zapisywanie stanu
– Resource Manager – jako klucz identyfikator sesji OperationContext.Current.SessionId
– Volatile Resource Managers – pola obiektu instancji
PerSession – c.d.
• Możliwe zrównanie czasu trwania sesji z czasem trwania transakcji– ReleaseServiceInstanceOnTransactionComplete = false– TransactionAutoComplete = false
– Uwaga na timeout• Nie zalecane[ ServiceBehavior(TransactionAutoCompleteOnSessionClose = true) ]
class MyService : IMyContract {. . . }
Durable Services
• Stan zapisywany w bazie– Transakcja może niechcący przed tym
powstrzymać• Domyślnie – zapisywanie stanu nie jest
częścią transakcji• [SaveStateInOperationTransaction]
– Domyślnie – false– True – aby stan był zarządzany transakcją
Singleton
• Domyślnie – PerCall– ReleaseServiceInstanceOnTransactionComplete
• Stan w Volatile Resource Managers
Wielowątkowość
Tryby• [ServiceBehavior(ConcurrencyMode=…)]
– Single– Reentrant– Multiple
• Single– Domyślny– Tylko jeden wątek na raz
• Multiple– Konieczne synchronizowanie dostępu
• lock { }• [MethodImpl(MethodImplOptions.Synchronized)]• W takim wypadku – niewielki zysk
– Limitowane przez throttle – domyślnie 16 połączeń• Reentrant
Wątek UI• WPF, Silverlight – Dispatcher
partial class MyForm : Form{ Label m_CounterLabel; public SynchronizationContext MySynchronizationContext { get; set; } public MyForm() { InitializeComponent(); MySynchronizationContext = SynchronizationContext.Current; } void InitializeComponent() { . . . m_CounterLabel = new Label( ); . . . } public int Counter { get { return Convert.ToInt32(m_CounterLabel.Text); } set { m_CounterLabel.Text = value.ToString(); } }}
Synchronization Context – c.d.[ServiceContract]interface IFormManager{ [OperationContract] void IncrementLabel();}
class MyService : IFormManager{ public void IncrementLabel() { MyForm form = Application.OpenForms[0] as MyForm; Debug. Assert(form ! = null) ; SendOrPostCallback callback = delegate { form.Counter++; }; form.MySynchronizationContext.Send(callback, null); }}
static class Program{ static void Main() { ServiceHost host = new ServiceHost(typeof(MyService)); host.Open(); Application.Run(new MyForm()); host.Close(); }}
Wątek UI
[ServiceBehavior(UseSynchronizationContext = true)] class MyService : IMyContract {. . . }
• Jeśli wątek uruchamiający usługę ma Synchronization Context, automatycznie kontekst przechodzi do usługi• Form jako usługa (i tak ścisłe powiązanie)
– Form jako callback – ok• Inne rozszerzenia
– Np. Ustawianie priorytetów dla wywołań http://msdn.microsoft.com/en-us/magazine/cc163321.aspx
Operacje asynchroniczne
• Specjalne pole checkbox podczas dodawania referencji
• Klasycznie jak w .NET– Begin<operacja>, End<operacja>– End – blokuje– Callback– Polling – IsCompleted– Wiele wywołań – WaitAll() i
IAsyncResult.WaitHandle
Serwisy kolejkowane
Serwisy kolejkowane
• Dostępność usługi• Load leveling – skolejkowanie „piku”• Kolejkowanie niezależnych operacji
biznesowych• Kompensacja (druga kolejka z wynikami)• Kolejkowanie w WCF
– NetMsmqBinding– Opakowanie komunikatu SOAP w komunikat
MSMQ
Architektura
• Klient wywołuje proxy• Proxy zapisuje komunikat do kolejki• Usługa netMsmqBinding instaluje Channel Listener• Channel listener powiadamia o komunikacie –
komunikat zdejmowany i przetwarzany
Kolejkowanie w WCF• Tylko operacje OneWay
– Nie mogą zwracać wartości– Nie mogą zwracać błędów
• Kolejki MSMQ– Public – pomiędzy serwerami w domenie– Private – lokalne, nie wymagają DC– Jeden endpoint = jedna kolejka
• Hosting WAS – nazwa kolejki = nazwa pliku svc– address = "net. msmq: //localhost/private/WASService. svc"
<endpoint address = "net.msmq: //localhost/private/MyServiceQueue" binding = "netMsmqBinding"
... />
Transakcje
• MSMQ uczestniczy w transakcjach (opcja)– Jeśli włączone – komunikaty zapisywane na dysk– Transakcje playback – nasz kod
• Publiczna kolejka i reliable messeging– Kolejka proxy– Automatyczne ponawianie wysłania komunikatu
Sesja i MSMQ
• SessionMode.Allowed lub SessionMode.NotAllowed – brak sesji– Każdy komunikat pojedynczo
• SessionMode.Required– Sessiongram – pakowanie komunikatów w
„paczkę”– Zachowana kolejność doręczenia
Instancjonowanie PerCall//Klient using(TransactionScope scope = new TransactionScope( )) {
MyContractClient proxy = new MyContractClient( );
proxy.MyMethod( ); //Komunikat wysłany proxy.MyMethod( ); //Komunikat wysłany proxy.Close( ); scope.Complete( );
} // Zatwierdzane
• Brak przejścia transakcji na stronę usługi• Każde wywołanie osobno, osobne instancje
Instancjonowanie PerSessionusing(TransactionScope scope = new TransactionScope( )) {
MyContractClient proxy = new MyContractClient( );proxy.MyMethod(); proxy.MyMethod();
proxy.Close( ); //Skomponowano komunikat, zapis. scope.Complete( ); //Musi być za proxy.Close!! } //Pojedynczy komunikat zapisany
Instancjonowanie PerSession
• Nie mogą mieć trwającej sesji – Podobnie do PerCall
• Ale - wszystkie komunikaty do tej samej instancji
Throttling
• Po włączeniu usługi – wszystkie komunikaty na raz– Duża liczba instancji kontekstu – obciążenie
• Umożliwia stopniowe przetwarzanie
Kiedy pojawią się problemy
• Błędy– Komunikacja– Bezpieczeństwo– Quota– …
• Dead-letter queue (DLQ)– Problem z doręczeniem– Nieudany commit transakcji playback– Przetwarzanie kolejki jak zwykły serwis (kontrakt musi być
ten sam / dziedziczący)
<! -- Client side --><system. serviceModel> <client> <endpoint address = "net. msmq: //localhost/private/MyServiceQueue" binding = "netMsmqBinding" bindingConfiguration = "MyCustomDLQ" contract = "IMyContract" /> </client> <bindings> <netMsmqBinding> <binding name = "MyCustomDLQ" deadLetterQueue = "Custom" customDeadLetterQueue = "net.msmq: //localhost/private/MyCustomDLQ"> </binding> </netMsmqBinding> </bindings></system. serviceModel><! -- DLQ service side --><system. serviceModel> <services> <service name = "MyDLQService"> <endpoint address = "net.msmq://localhost/private/MyCustomDLQ" binding = "netMsmqBinding" contract = "IMyContract" /> </service> </services></system. serviceModel>
Komunikaty Poison
• Komunikaty Poison– Nieustanne próby doręczenia– Zawsze błąd– Również usługa z polimorficznym kontraktem
• Możliwe akcje– Drop – odrzucenie i potwierdzenie, że dostarczono– Reject – odrzucenie i NACK– Move – przeniesienie do odpowiedniej kolejki
Popularne wzorce• Response service
– Komunikaty mogą być jednokierunkowe– Można zapisywać odpowiedzi w innej kolejce
• HTTP Bridge– Kolejki – raczej intranet– Nie interoperacyjne– Mostek WS-HTTP
Bezpieczeństwo(podstawy)
Tryby
• Transport – Negocjacja (protokół), zależy od protokołu– Weryfikacja integralności treści komunikatu – Akcelerowane przez karty sieciowe
• Message– Treść komunikatu szyfrowana
• Mieszane– Mix - transport do integralności i szyfrowanie
hasła– Oba – uwaga na wydajność
Bindingi i tryby bezpieczeństwa
Intranet• Bindingi
– NetTcpBinding– NetNamedPipeBinding– NetMsmqBinding
• Transport security– None– Signed – tożsamość nadawcy i integralność komunikatu– Encrypted and signed – dodatkowo szyfrowanie
• Najczęściej – uwierzytelnienie Windows (domain / username / password)• Autoryzacja – grupy Windows
<bindings> <netTcpBinding> <binding name = "TCPWindowsSecurity"> <security mode = "Transport"> <transport clientCredentialType = "Windows" protectionLevel = "EncryptAndSign" /> </security> </binding> </netTcpBinding></bindings>
Intranet – c.d.• ServiceSercurityContext.Current
– IsAnonymous– PrimaryIdentity– WindowsIdentity
• Thread.CurrentPrincipal : IPrincipal• PrincipalPermissionMode
– UseWindowsGroups– UseAspNetRoles– None, Custom
• Deklaratywne wymuszanie– [PrincipalPermission(SecurityAction.Demand, Role = „Manager”)]
• Impersonacja– Możliwa, ale niezalecana
Internet• Bindingi
– WSHttpBinding– WSDualHttpBinding
• Message security– Szyfrowanie certyfikatem X509
• Certyfikat– Plik konfiguracyjny (więcej kłopotu ze zmianami)– Trusted People – zalecane
• Tryb walidacji certyfikatu (certificateValidation)– PeerTrust – TrustedPeople– ChainTrust – zaufane root authority: Verisign / Thwart / …– PeerOrChainTrust
<behavior name = "Internet"><serviceCredentials>
<serviceCertificate findValue = "MyServiceCert" storeLocation = "LocalMachine" storeName = "My" x509FindType = "FindBySubjectName" />
</serviceCredentials>
</behavior>
Uwierzytelnienie// Hasło (klasycznie)
MyContractClient proxy = new MyContractClient( );proxy.ClientCredentials.UserName.UserName = "MyUsername";proxy.ClientCredentials.UserName.Password = "MyPassword";proxy.MyMethod( );proxy.Close( );
// Windows (raczej nie stosowane w Internet, chociaż możliwe) <behavior name = "UsernameWindows"> <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode = "Windows"/> <serviceCertificate . . . /> </serviceCredentials> </behavior>
Autoryzacja
• Najprościej – Role Provider– Model znany z ASP.NET– Dowolne źródło danych (własna lub generowana
baza)• Duplex
– Odradzane, w prosty sposób autoryzacja niedostępna
B2B
• Nie współdzielą kont– Przedstawianie się certyfikatem X509 (klient)– Dozwoleni klienci instalowani w Trusted People
• Szyfrowanie certyfikatem serwera• Najczęściej nie potrzebna autoryzacja
– Niewielka liczba lub jeden klient– Możliwe wykorzystanie Role Providera –
identyfikator certyfikatu jako username
Podsumowanie
Programowanie Web w WCF
REST, Get, Post, Delete,RSS (i HTTP)
Terminologia• RSS – prosty format XML dla zestawu danych• ATOM – jak RSS, ale bardziej ustrukturalizowany• Syndykacja (Syndication) – nadzbiór RSS, ATOM, inne• AJAX – DHTML + JavaScript + asynchroniczne
przetwarzanie• JSON – Format danych (tablicy) w JavaScript• REST – …• POX – plain old XML (bez koperty SOAP)
REST• Protokół HTTP został zaprojektowany z 8 słowami• Low REST – API bazujące na GET & POST
– Też tak działa przeglądarka
• High REST sugeruje stosowanie 4 głównych słow– GET/PUT/DELETE/POST– CRUD dla Web
• Wywoływanie usługi bez warstwy „komunikatu” czy „koperty”– …sam HTTP
• Oparte na– URI do definowania endpoint (zasób)
• Spacje & URITemplates
– Polecenia HTTP definiują operacje• GET/PUT/DELETE/POST
– Typy zawartości• XML , JSON, <mikroformaty>
Aplikacje REST - atrybuty• WebGet: Atrybut do oznaczania operacji które są
wołane przez HTTP GET• webHttpBinding: Binding dla usług REST• webServiceHost: Klasa do hostowania usług REST• webServiceHostFactory: Klasa do hostowania usług
REST które nie wymagają pliku konfiguracyjnego
[OperationContract][WebGet(UriTemplate=“/WeatherMap/{country}/{zipcode}”)]Stream GetWeatherMap(String country, String zipcode);
Developing REST ApplicationsWCF & URI Spaces: URITemplate
• QueryString syntax still available• URITemplate: formalism for binding URI structure to method
parameters
[OperationContract][WebGet(UriTemplate=“/WeatherMap/{country}/{zipcode}”)]Stream GetWeatherMap(String country, String zipcode);
http://myserver/WeatherMap/USA/98052
http://myserver/GetWeatherMap?country=USA&zipcode=98052
Developing REST ApplicationsWCF & Transfer: HTTP verbs support
• WebInvoke: new attribute for making a method accept any non-GET verb– PUT, DELETE, POST...
[OperationContract][WebInvoke(METHOD=“PUT”)]WeatherReport UploadWeatherReport(WeatherReport theReport);
Ręczne modelowanie URI• Pomoc: System.UriTemplate
Uri address = new Uri(“http://localhost:2000”);
UriTemplate template =
new UriTemplate(“{artist}/{album}”);
Uri boundUri =
template.BindByPosition(address,
“Northwind”, “Overdone”);
UriTemplateMatch match = template.Match(address,
boundUri);
String bandName = match.BoundVariables[“artist”];
URI w kontrakcie WCF
• Składnia QueryString nadal dostępna• Typ UriTemplate mapuje na parametry
– Jako atrybut albo jako oddzielny typ
[OperationContract][WebGet(UriTemplate=“/Image/{bandName}/{album}”)]Stream GetAlbumImage(String bandName, String album);
[OperationContract][WebGet(UriTemplate=“/Image?name={bandName})]Stream GetMainImage(String bandName);
Kontrakt typu zobacz / zrób
[OperationContract]
[WebGet(UriTemplate=“/Image/{bandName}/{album}”)]
Stream GetAlbumImage(String bandName, String album);
[OperationContract]
[WebInvoke(METHOD=“PUT”)] // {PUT, POST, DELETE}
void AddAlbum(AlbumInfo albumInfo);
REST – zwracanie wartości• Typ zawartości zależy od:
– Zachowanie enableWebScript (JSON) albo ustawione w klasie hostującej
– Atrybutu Response.Format– Ręcznie:
• WebOperationContext.Current.OutgoingResponse.ContentType
[OperationContract][WebGet(UriTemplate = "WeatherReport/{country}/{zipcode}/JSON",ResponseFormat=WebMessageFormat.Json)]WeatherReport GetWeatherReportWithTemplateJSON(string country, string zipcode);
JSON – co to jest?• JavaScript Object Notation• Format do łączenia JavaScript i obiektów
– Łatwiejsze niż XML
• Użycie– ASP.NET AJAX – Skrypty JS– Dynamiczny kod…
var data = {“temp” : 59, “descr” : “cloudy”};
document.write (“The weather is “ + data.descr);
Konwencje związane z danymi
[OperationContract(Name=“TestOp”)]
[WebInvoke(METHOD=“PUT”)] // {PUT, POST, DELETE}
String[] AddAlbum(AlbumInfo albumInfo);
PUT /albumservice/AddAlbum HTTP 1.1
HOST: contoso.com<albumInfo> <Name>Hysteria</Name> <RelDate>8/3/1987</RelDate> ...</albumInfo>
•XML Namespace pochodzi z kontraktu
•Nazwy parametrów z sygnatury operacji
Zwrócenie rysunku w WCF
Stream GetAlbumImage(String bandName, String album){ Stream stream; // obraz „skądś”
// ustawiamy ContentType i zwracamy strumień WebOperationContext.Current.OutgoingResponse.ContentType = “image/jpeg”; return stream;}
WCF i syndykacjaSyndykacja: Gromadzenie danych z różnych źródeł w jednym miejscu (blogi, artykuły itp.)SyndicationFeed: pojęcie „feedu” niezależne od formatuSyndicationFeedFormatter<>
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
[ServiceContract]
interface IPictureSyndication {
[OperationContract]
[WebGet(UriTemplate=“Images/{format}")]
SyndicationFeedFormatter<SyndicationFeed>
GetImage(String format);
}
Kontekst bez protokołu WS=*• Konwersacja bazująca na kanale komunikacyjnym - problem• Konwencja wywołania
– W tle
• Binding: basicHttpContextBindingNetTcpContextBindingBasicHttpContextBinding
• Behavior:– <persistenceProvider>
• Ten sam co w WF
• Uwaga! Klient musi też być świadomy kontekstu– WCF Service Host nie jest…