Upload
bartlomiej-zass
View
748
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Przegląd najważniejszych elementów Windows Communication Foundation (WCF). Od wprowadzenia przez bardziej szczegółowe omówienie koncepcji bindingów, kontraktów, sposobów instancjonowania, obsługi błędów, bezpieczeństwa, itp.
Citation preview
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…