Pobranie i instalacja: - http://www.nservicebus.com/ - download v3.0 now - rozpakować - MSMQ powinno być zainstalowane (Panel Sterowania -> Dodaj/Usuń programy -> Składniki systemu Windows -> Kolejkowanie wiadomości) Komunikaty - nowy projekt (class library): - referencja do NServiceBus.dll - każdy przesyłany komunikat musi dziedziczyć po interfejsie IMessage - pola powinny być właściwościami namespace Komunikaty { public class MyMessage: IMessage { public string text { get; set; Jednokierunkowe przesyłanie komunikatów: Serwer hostowany (odbiorca komunikatów) nowy projekt (class library): - aplikacja będzie zbiorem klas uruchamianych przez hosta gotowy program dostarczony z NServiceBus (binaries\nservicebus.host.exe lub binaries\nservicebus.host32.exe) - referencje - NServiceBus.dll - NServiceBus.Core.dll - log4net.dll - NServiceBus.Host.exe (lub NServiceBus.Host32.exe) - projekt z komunikatami - project -> properties -> debug -> Start action -> Start external program = ścieżka do katalogu bin\debug projektu\nservicebus.host.exe spowoduje to uruchomienie hosta w momencie uruchomienia projektu (np. naciskając F5) - zakończenie działania hosta Ctrl + Break - kod: using Komunikaty; namespace HostedReceiver { public class Receiver: IConfigureThisEndpoint, AsA_Server { public class Handler: IHandleMessages<MyMessage> { public void Handle(MyMessage msg) { Console.WriteLine("MSG: " + msg.text); public class Startup: IWantToRunAtStartup { public void Run() { Console.WriteLine("receiver started"); public void Stop() { Console.WriteLine("receiver stopped"); IConfigureThisEndpoint, AsA_Server skonfigurowanie klasy jako serwera (odbiorcy) - IHandleMessages<typ> oznaczenie, jakie komunikaty mają być odbierane - wymaga implementacji metody Handle(typ_komunikatu) 1
- IWantToRunAtStartup oznaczenie klasy do uruchomienia w momencie uruchomienia usługi (jeżeli nie potrzebujemy, nie musimy jej tworzyć) - Run metoda wywoływana w chwili uruchomienia - Stop metoda wywoływana na zakończenie - Uwaga: klasa Handler będzie tworzona na nowo przy każdym obsługiwanym komunikacie - konfiguracja endpoint-a (App.config trzeba dodać do projektu): <?xml version="1.0" encoding="utf-8"?> <configsections> <section name="msmqtransportconfig" type="nservicebus.config.msmqtransportconfig, NServiceBus.Core" /> </configsections> <MsmqTransportConfig ErrorQueue="error" NumberOfWorkerThreads="1" MaxRetries="5" /> - MsmqTransportConfig konfiguracja odbierania komunikatów - ErrorQueue nazwa kolejki, w której umieszczane będą wadliwe komunikaty (np. nieodebrane) - NumberOfWorkerThreads liczba wątków obsługujących komunikaty (tylko 1 przy braku licencji) - MaxRetries maksymalna liczba prób nadania komunikatów (po niej komunikat przenoszony jest to ErrorQueue - starsze wersje NServiceBus: - InputQueue nazwa kolejki z komunikatami z tej kolejki będą odbierane komunikaty - nazwa w przypadku kolejki na bieżącym komputerze - nazwa@komputer w przypadku kolejki na zdalnym komputerze - komunikaty będą odbierane z kolejki o nazwie takiej samej, jak nazwa namespace projektu (pierwsze uruchomienie wyświetli ostrzeżenie, że kolejka nie istnieje, ale host ją utworzy) - jeżeli występuje wyjątek związany z log4net (dotyczący ładowania assembly ze ścieżki file://...) należy umożliwić ładowanie assembly ze ścieżek sieciowych, w tym celu należy: - dodać do projektu nowy element: Application Configuration File o nazwie NServiceBus.Host.exe.config - ustawić w jego właściwościach: Copy to output = copy always - nadać mu treść: <?xml version="1.0" encoding="utf-8"?> <runtime> <loadfromremotesources enabled="true"/> </runtime> - jeżeli występuje wyjątek dotyczący RavenDB, należy: - dodać nowy interfejs bazowy dla klasy dziedziczącej po IConfigureThisEndpoint: IWantCustomInitialization - zaimplementować metodę Init: public void Init() { Configure.With().XmlSerializer().DefaultBuilder().RunTimeoutManagerWithInMemoryPersistence(); - mówimy w ten sposób, że chcemy wykonać dodatkowy kod konfigurujący szynę (metoda Init), w której żądamy przechowywania danych menedżera timeout-ów w pamięci (zamiast w bazie danych, w przypadku RavenDB) 2
Klient hostowany (nadawca komunikatów) nowy projekt (class library): - referencje - NServiceBus.dll - NServiceBus.Core.dll - log4net.dll - NServiceBus.Host.exe - projekt z komunikatami - uruchomienie jak wyżej; aby wystartować równocześnie kilka projektów należy kliknąć prawym na solution -> Set Startup projects -> Common properties -> Multiple startup projects - kod: using Komunikaty; namespace HostedSender { public class Sender: IConfigureThisEndpoint, AsA_Client { public void Run(IBus bus) { Console.ReadKey(); var m = new MyMessage(); m.text = "Asd"; bus.send(m); Console.WriteLine("sent"); public class Startup: IWantToRunAtStartup { public IBus Bus { get; set; public void Run() { Console.WriteLine("client started"); var s = new Sender(); s.run(bus); public void Stop() { Console.WriteLine("client stopped"); - Startup zostanie skonfigurowany przez dependency injection (nie trzeba nic robić, aby otrzymać obiekt Bus) - konfiguracja (App.config) <?xml version="1.0" encoding="utf-8"?> <configsections> <section name="msmqtransportconfig" type="nservicebus.config.msmqtransportconfig, NServiceBus.Core" /> <section name="unicastbusconfig" type="nservicebus.config.unicastbusconfig, NServiceBus.Core" /> </configsections> <MsmqTransportConfig ErrorQueue="error" NumberOfWorkerThreads="1" MaxRetries="5" /> <UnicastBusConfig> <MessageEndpointMappings> <add Messages="Komunikaty.MyMessage, Komunikaty" Endpoint="HostedReceiver" /> </MessageEndpointMappings> </UnicastBusConfig> 3
- MessageEndpointMappings konfiguracja, do jakich kolejek mają być wkładane komunikaty - add - Messages nazwa assembly zawierającego komunikaty, lub nazwa konkretnej klasy komunikatów w formacie namespace.klasa, assembly - Endpoint do jakiej kolejki mają być wkładane wysyłane komunikaty - nadawca musi mieć także skonfigurowaną część odbiorczą (np. na okoliczność błędów) Nadawca / Odbiorca samodzielny (aplikacja konsolowa): - referencje - NServiceBus.dll - NServiceBus.Core.dll - log4net.dll - dll-ka z komunikatami - aplikacja może być i klientem i serwerem - kolejki nie zostaną stworzone automatycznie trzeba dodać je ręcznie (Panel sterowania-> Narzędzia administracyjne -> Zarządzanie komputerem -> Usługi i aplikacje -> kolejkowanie wiadomości) - project -> properties -> Application -> Target framework =.Net Framework 4 - Uwaga: bez Client Profile - kod: using Komunikaty; namespace ConServerOrClient { // odbieranie komunikatów class Recv: IHandleMessages<MyMessage> { public void Handle(MyMessage msg) { Console.WriteLine("got msg = {0", msg.text); class Program { static void Main(string[] args) { // jeżeli chcemy zmienić ustawienia logowania NServiceBus.SetLoggingLibrary.Log4Net(log4net.Config.XmlConfigurator.Configure); // samodzielne utworzenie obiektu szyny IBus bus = Configure.With().DefineEndpointName("nazwa kolejki").log4net().defaultbuilder().xmlserializer().msmqtransport().istransactional(true).unicastbus().loadmessagehandlers().createbus().start(); // nadawca Console.WriteLine("receiver ready"); Console.ReadKey(); // odbiorca Console.WriteLine("sender ready"); var m = new TxtMessage() { text = "aaa" ; bus.send(m); Console.WriteLine("sent"); 4
- konfiguracja nadawcy/odbiorcy (App.config): <?xml version="1.0"?> <configsections> <section name="msmqtransportconfig" type="nservicebus.config.msmqtransportconfig, NServiceBus.Core" /> <!-- jeżeli chcemy zmienić ustawienia logowania --> <section name="log4net" type="log4net.config.log4netconfigurationsectionhandler, log4net"/> <!-- tylko gdy wysyłamy --> <section name="unicastbusconfig" type="nservicebus.config.unicastbusconfig, NServiceBus.Core" /> </configsections> <MsmqTransportConfig ErrorQueue="error" NumberOfWorkerThreads="1" MaxRetries="5" /> <log4net debug="false"> <!--<appender name="console" type="log4net.appender.consoleappender"> <layout type="log4net.layout.patternlayout"> <param name="conversionpattern" value="%d [%t] %-5p %c [%x] <%X{auth> - %m%n"/> </layout> </appender>--> <root> <level value="error"/> <!-- lub WARN, DEBUG, INFO\ --> <!--<appender-ref ref="console"/>--> </root> </log4net> <!-- tylko gdy wysyłamy --> <UnicastBusConfig> <MessageEndpointMappings> <add Messages="Messages.TxtMessage, Messages" Endpoint="ConsoleReceiver" /> </MessageEndpointMappings> </UnicastBusConfig> <startup> <supportedruntime version="v4.0" sku=".netframework,version=v4.0"/> </startup> Komunikacja dwukierunkowa (FullDuplex): - komunikacja w modelu klient/serwer (zob. poprzednia instrukcja) - zaimplementować dwa komunikaty: zwykły i zwrotny (zwrotny będzie przesyłany w odpowiedzi na komunikat zwykły) - odbiorca odpowiada na komunikat w procedurze obsługi komunikatu, wołając metodę Reply: - nadawca może ustawić dodatkowe nagłówki w wiadomości korzystając z metody SetHeader lub słownika bus.outgoingheaders - odbiorca może skopiować nagłówek odebranej wiadomości do odpowiedzi korzystając z metody CopyHeaderFromRequest public class Handler: IHandleMessages<MyMessage> { public void Handle(MyMessage msg) { Console.WriteLine("MSG: " + msg.text); var resp = Bus.CreateInstance<ResponseMessage>(); // albo resp = new ResponseMessage(); resp.copyheaderfromrequest( hdr ); resp.setheader("xxx", "zzz"); Bus.Reply(resp); 5
- nadawca nadaje wiadomość - jeżeli odbiorca nada wiadomość zwrotną, nadawca musi stworzyć klasę obsługującą te wiadomości (class Handler: IHandleMessages<ResponseMessage>) - odbiorca nie musi konfigurować wysyłania wiadomości (UnicastBusConfig w w App.config-u) - odbiorca może także zarejestrować wołanie zwrotne informujące o przyjściu wiadomości, służy do tego metoda Register wywołana na obiekcie zwróconym przez Send, parametrami do Register są: - metoda z jednym parametrem typu int, lub - statyczna metoda z parametrem IAsyncResult i dodatkowym obiektem (callback podobny jak w WCF) - obiekt IAsyncResult zawiera w polu AsyncState obiekt NServiceBus.CompletionResult, a ten z kolei w polu State zawiera obiekt przekazany jako drugi argument - klient może odczytać nagłówki wiadomości zwrotnej ze słownika bus.currentmessagecontext.headers bus.outgoingheaders["hdr"] = "mmm"; bus.setheader("yyy") = "abc"; bus.send(msg).register<int>(p => { Console.WriteLine("got = {0 {1", bus.currentmessagecontext.headers["hdr"], bus.currentmessagecontext.headers["xxx"]); ); albo bus.send(msg).register(cb, bus);... static void cb(iasyncresult r) { IBus b = (NServiceBus.CompletionResult)r.AsyncState).State as IBus; Console.WriteLine("got = {0 {1", b.currentmessagecontext.headers["hdr"], b.currentmessagecontext.headers["xxx"]); - uwaga: w niektórych wersjach NServiceBus callback zostanie wywołany tylko dla pierwszej odebranej wiadomości zwrotnej (aby zapobiegać wyciekom pamięci) 6