Usługi przepływu pracy dla komunikacji lokalnej

Autor: Matt Milner

W poprzednim artykule (patrz „Workflow Communications” w numerze z września 2007 magazynu MSDN Magazine na stronie msdn.microsoft.com/magazine/cc163365.aspx), pisałem o podstawowej architekturze komunikacji w produkcie Windows Workflow Foundation 3 (WF3). Nie opisałem jednego tematu dotyczącego działań komunikacji lokalnej, które znajdują się jedną abstrakcję powyżej tej architektury. Kiedy przyjrzymy się programowi .NET Framework 4 Beta 1, zauważymy brak działań HandleExternalEvent. W rzeczywistości, w WF4, zawarte działania komunikacji wbudowane są w produkt Windows Communication Foundation (WCF). W tym miesiącu pokażę, jak używać produktu WCF do komunikacji między przepływem pracy a hostem aplikacji w Windows Workflow Foundation 3. Wiedza ta powinna pomóc w pracach deweloperskich przy użyciu WF3 i przygotować na nadejście WF4, gdzie WCF jest jedyną abstrakcją powyżej kolejek (w WF4 nazywanymi „zakładkami”) dostarczaną z platformą. (Podstawowe informacje na temat Usług przepływu pracy w WF3 znajdują się w moim artykule Foundations w Visual Studio 2008, w pierwszym wydaniu MSDN Magazine, na stronie msdn.microsoft.com/magazine/cc164251.aspx.)

Opis ogólny

Komunikacja między aplikacjami hosta a przepływami pracy okazuje się sporym wyzwaniem dla niektórych deweloperów, ponieważ łatwo jest przeoczyć fakt, że przepływ pracy i host często wykonują działania w różnych wątkach. Model architektury komunikacji ma na celu zwolnienie deweloperów z odpowiedzialności za zarządzanie kontekstem wątku oraz za organizowanie danych i innych szczegółów niższego poziomu. Jedną abstrakcję powyżej architektury kolejkowania w programie WF znajduje się integracja komunikatów WCF, która została wprowadzona w wersji programie .NET Framework Version 3.5. Większość przykładów i laboratoriów pokazuje, jak działania oraz rozszerzenia WCF mogą zostać wykorzystane, w celu uwidocznienia przepływu pracy klientom zewnętrznym dla procesu hostowania. Jednak tej samej struktury przepływu pracy można użytych do komunikowania wewnątrz tego samego procesu.

Wdrażanie komunikacji składa się z kilku etapów, ale potrzebny do tego nakład pracy nie przewyższa ilości pracy, jaką trzeba włożyć w wykonanie czynności komunikacji lokalnej.

Przed rozpoczęciem jakichkolwiek działań, należy określić (lub rozpocząć określanie stopniowo) kontrakty dla komunikacji korzystającej z kontraktów serwisowych WCF. Następnie należy użyć ich w przepływie pracy, w celu modelowania punktów komunikacji w układzie logicznym. Podsumowując, przepływ pracy oraz inne usługi powinny być hostowane, jako usługi WCF ze skonfigurowanymi punktami końcowymi.

Modelowanie komunikacji

Pierwszy krok w modelowaniu komunikacji polega na określeniu kontraktów między aplikacją hosta a przepływem pracy. Usługi WCF używają kontraktów do zdefiniowania zbioru operacji godzących usługi oraz komunikaty wysyłane i otrzymywane. W tym przypadku, ponieważ komunikujemy się z hosta do przepływu pracy i z powrotem, należy określić dwa kontrakty serwisowe oraz powiązane kontrakty danych, jak pokazuje Ilustracja1.

Ilustracja 1 Kontrakty komunikacji

[ServiceContract(
    Namespace = "urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IHostInterface
{
[OperationContract]
void OrderStatusChange(Order order, string newStatus, string oldStatus);
}

[ServiceContract(
    Namespace="urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IWorkflowInterface
{
    [OperationContract]
    void SubmitOrder(Order newOrder);

    [OperationContract]
    bool UpdateOrder(Order updatedOrder);
}

[DataContract]
public class Order
{
    [DataMember]
    public int OrderID { get; set; }
    [DataMember]
    public string CustomerName { get; set; }
    [DataMember]
    public double OrderTotal { get; set; }
    [DataMember]
    public string OrderStatus { get; set; }
    }

Kiedy kontrakty znajdą się we właściwym miejscu, modelowanie przepływu pracy wykorzystujące działania Wyślij i Odbierz działa tak, jak w przypadku komunikacji zdalnej. Jest to jedna z zalet WCF. Model programowania, bez względu na to, czy zdalny, czy lokalny, jest taki sam. Prosty przykład zawarty jest na Ilustracji 2 pokazującej przepływ pracy z dwoma działaniami Odbierz i jednym działaniem Wyślij modelującymi komunikację między przepływem pracy a hostem. Działania Odbierz konfigurowane są przy użyciu kontraktu serwisowego IWorkflowInterface, a działania Wyślij używają kontraktu IHostInterface.

Do tego momentu, użycie WCF wobec komunikacji lokalnej niewiele różni się od użycia WCF wobec komunikacji zdalnej i jest bardzo podobne do wykorzystania działań i usług komunikacji lokalnej. Główna różnica polega na tym, jak zapisany jest kod hosta, w celu uruchomienia przepływu pracy oraz organizacji komunikacji pochodzącej z przepływu pracy.

Ilustracja 2 Przepływ pracy modelowany wobec kontraktów

 

Hostowanie usług

Ponieważ chcemy, aby komunikacja przepływała w obu kierunkach przy użyciu funkcji WCF, musimy hostować dwie usługi – usługę przepływu pracy, w celu uruchomienia przepływu pracy oraz usługę w aplikacji hosta, aby odbierać komunikaty od przepływu pracy. W moim przykładzie, utworzyłem prostą aplikację Windows Presentation Foundation (WPF), która ma spełniać rolę hosta, a do zarządzania hostami użyłem metod OnStartup i OnExit klasy aplikacji. Z początku możemy skłaniać się ku utworzeniu klasy WorkflowServiceHost i otwarcia jej w metodzie OnStartup. Skoro metoda Open nie blokuje się po otwarciu hosta, można kontynuować przetwarzanie, załadować interfejs użytkownika i rozpocząć interakcję z przepływem pracy. Ponieważ funkcja WPF (oraz inne technologie klienckie) do przetwarzania używa pojedynczego wątku, może to szybko spowodować problemy, gdyż wywołanie usługi oraz wywołanie klienta nie mogą korzystać z tego samego wątku, więc klient przekracza limit czasu. Aby tego uniknąć, klasa WorkflowServiceHost została utworzona na innym wątku przy użyciu ThreadPool, jak pokazuje Ilustracja 3.

Ilustracja 3 Hostowanie usługi przepływu pracy

ThreadPool.QueueUserWorkItem((o) =>
{

//host the workflow
workflowHost = new WorkflowServiceHost(typeof(
    WorkflowsAndActivities.OrderWorkflow));
workflowHost.AddServiceEndpoint(
    "Contracts.IWorkflowInterface", LocalBinding, WFAddress);
try
{
    workflowHost.Open();
}
catch (Exception ex)
{
    workflowHost.Abort();
    MessageBox.Show(String.Format(
        "There was an error hosting the workflow as a service: {0}",
    ex.Message));
}
});

Kolejnym wyzwaniem będzie wybór odpowiedniego wiązania dla komunikacji lokalnej. Obecnie nie istnieje żadne wewnątrzpamięciowe, ani wewnątrzprocesowe wiązanie, które byłoby nieskomplikowane dla tego typu scenariuszy. Najlepszym rozwiązaniem, aby uzyskać nieskomplikowany kanał, jest użycie NetNamedPipeBinding z wyłączonymi zabezpieczeniami. Niestety, jeśli spróbujemy użyć tego wiązania i hostować przepływ pracy, jako usługę, otrzymamy błąd informujący, że host wymaga wiązania z kanałem Context, ponieważ kontrakt serwisowy może wymagać sesji. Ponadto w platformie .NET Framework, która dostarczana jest tylko z trzema wiązaniami BasicHttpContextBinding, NetTcpContextBinding oraz WSHttpContextBinding, nie zawarto NetNamedPipeContextBinding. Na szczęście możemy tworzyć własne niestandardowe wiązania i zawierać je w kanale Context. Ilustracja 4 pokazuje niestandardowe wiązanie, które wywodzi się z klasy NetNamedPipeBinding i wprowadzane jest do wiązania ContextBindingElement. Komunikacja dwukierunkowa może skorzystać z tego wiązania w końcowym punkcie rejestracji przy użyciu różnych adresów.

Ilustracja 4 NetNamedPipeContextBinding

public class NetNamedPipeContextBinding : NetNamedPipeBinding
{
    public NetNamedPipeContextBinding() : base(){}

    public NetNamedPipeContextBinding(
        NetNamedPipeSecurityMode securityMode):
        base(securityMode) {}

    public NetNamedPipeContextBinding(string configurationName) :
        base(configurationName) {}

    public override BindingElementCollection CreateBindingElements()
    {
        BindingElementCollection baseElements = base.CreateBindingElements();
        baseElements.Insert(0, new ContextBindingElement(
            ProtectionLevel.EncryptAndSign,
            ContextExchangeMechanism.ContextSoapHeader));

        return baseElements;
    }
}

Dzięki temu nowemu wiązaniu możemy utworzyć punkt końcowy w WorkflowServiceHost i otworzyć hosta bez błędów. Przepływ pracy jest gotowy, aby odebrać dane od hosta przy użyciu kontraktu serwisowego. Aby przesłać dane, należy utworzyć proxy i wywołać operację, jak pokazuje Ilustracja 5.

Ilustracja 5 Kod hosta do rozpoczęcia przepływu pracy

App a = (App)Application.Current;
    IWorkflowInterface proxy = new ChannelFactory<IWorkflowInterface>(
    a.LocalBinding, a.WFAddress).CreateChannel();

    proxy.SubmitOrder(
        new Order
        {
            CustomerName = "Matt",
            OrderID = 0,
            OrderTotal = 250.00
        });

Ponieważ udostępniamy kontrakty, nie istnieje klasa proxy, więc aby utworzyć klienta proxy, należy użyć ChannelFactory<TChannel>.

Kiedy przepływ pracy jest już hostowany i gotowy do obierania komunikatów, nadal należy go skonfigurować do wysyłania komunikatów do hosta. Co najważniejsze, przepływ pracy musi mieć możliwość otrzymania klienckiego punktu końcowego podczas korzystania z działania Wyślij. Działanie to pozwala określić nazwę punktu końcowego, co zwykle jest mapowaniem do nazwanego punktu końcowego w pliku konfiguracji. Choć umieszczanie informacji o punkcie końcowym w pliku konfiguracji działa, można również użyć ChannelManagerService (omówiłem to w artykule z sierpnia 2008 na stronie msdn.microsoft.com/magazine/cc721606.aspx), w celu zawieszenia klienckich punktów końcowych przez działania Wyślij w przepływie pracy. Ilustracja 6 pokazuje kod macierzysty, służący do utworzenia usługi, zapewnienia jej nazwanego punktu końcowego i dodanie jej do WorkflowRuntime hostowanego w WorkflowServiceHost.

Ilustracja 6 Dodawanie ChannelManagerService do czasu wykonywania

ServiceEndpoint endpoint = new ServiceEndpoint
(
    ContractDescription.GetContract(typeof(Contracts.IHostInterface)),
        LocalBinding, new EndpointAddress(HostAddress)
);
endpoint.Name = "HostEndpoint";

WorkflowRuntime runtime =
    workflowHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().
WorkflowRuntime;

ChannelManagerService chanMan =
    new ChannelManagerService(
        new List<ServiceEndpoint>
        {
            endpoint
        });

runtime.AddService(chanMan);

Posiadanie hostowanej usługi przepływu pracy zapewnia możliwość wysyłania komunikatów z hosta do przepływu pracy. Jednak, aby otrzymać komunikaty z powrotem do hosta, potrzebna jest usługa WCF, która ma możliwość odbierania komunikatów z przepływu pracy. Usługa ta jest standardową usługą WCF samohostującą się w aplikacji. Ponieważ jest ona usługą przepływu pracy, skorzystać można ze standardowego NetNamedPipeBinding, lub ponownie użyć wcześniej pokazanego NetNamedPipeContextBinding. I na koniec, ponieważ usługę tę wywołuje się z przepływu pracy, może być ona hostowana w wątku interfejsu użytkownika, dzięki czemu interakcja z elementami interfejsu użytkownika jest prostsza. Ilustracja 7 pokazuje kod hostujący dla usługi.

Ilustracja 7 Hostowanie usługi hosta

ServiceHost appHost = new ServiceHost(new HostService());
appHost.AddServiceEndpoint("Contracts.IHostInterface",
LocalBinding, HostAddress);

try
{
    appHost.Open();
}
catch (Exception ex)
{
    appHost.Abort();
    MessageBox.Show(String.Format(
        "There was an error hosting the local service: {0}",
    ex.Message));
}

Kiedy obie usługi są już hostowane, możemy uruchomić przepływ pracy, wysyłać komunikaty i otrzymywać je z powrotem. Jednak, jeśli spróbujemy wysłać drugą wiadomość przy użyciu tego kodu do drugiego działania odbierania w przepływie pracy, otrzymamy błąd dotyczący kontekstu.

Obsługa korelacji wystąpień

Jeden ze sposobów rozwiązania problemu kontekstu polega na użyciu tego samego klienta proxy przy każdym wywołaniu usługi. Umożliwia to klientowi proxy zarządzanie identyfikatorami kontekstu (przy użyciu NetNamedPipeContextBinding) i odsyłanie ich do usługi z kolejnymi żądaniami.

W niektórych scenariuszach niemożliwe jest utrzymanie tego samego proxy dla wszystkich żądań. Rozważmy przypadek, gdy uruchamiamy przepływ pracy, utrwalamy go w bazie danych i zamykamy aplikację kliencką. Kiedy aplikacja ta ponownie się uruchomi, należy znaleźć sposób wznowienia przepływu pracy poprzez wysłanie kolejnego komunikatu do określonego wystąpienia. Innym powszechnym przypadkiem jest, kiedy chcemy użyć pojedynczego klienta proxy, ale musimy komunikować się z kilkoma wystąpieniami przepływu pracy, każdy z niepowtarzalnym identyfikatorem. Na przykład, interfejs użytkownika zapewnia listę zamówień, każde z odpowiadającym mu przepływem pracy. Kiedy użytkownik wywołuje akcję na wybranym zamówieniu, należy wysłać komunikat do wystąpienia przepływu pracy. W tym scenariuszu nie zadziała ustawienie, w którym zezwolimy wiązaniu na zarządzanie identyfikatorem kontekstu, ponieważ zawsze będzie ono używało identyfikatora ostatniego przepływu pracy, z którym weszliśmy w interakcję.

W przypadku pierwszego scenariusza (użycie nowego serwera proxy dla każdego wywołania) należy ręcznie ustawić identyfikator przepływu pracy w kontekście przy użyciu interfejsu IContextManager. Dostęp do IContextManager uzyskujemy metodą GetProperty<TProperty> w interfejsie IClientChannel. Kiedy posiadamy już IContextManager, możemy użyć go do uzyskania, lub ustawienia kontekstu.

Sam kontekst jest słownikiem par nazwa-wartość, z których najważniejsza jest wartość instanceId. Następujący kod pokazuje, jak pobiera się identyfikator z kontekstu, aby mógł zostać przechowany na później przez daną aplikację kliencką, kiedy będziemy mieć potrzebę przeprowadzenia interakcji z tym samym wystąpieniem przepływu pracy. W tym przykładzie, identyfikator zostaje wyświetlony w klienckim interfejsie użytkownika, a nie jest przechowywany w bazie danych:

IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
      
string wfID = mgr.GetContext()["instanceId"];
wfIdText.Text = wfID;

Po wykonaniu pierwszego wywołania usługi przepływu pracy, kontekst zostaje automatycznie wypełniony identyfikatorem wystąpienia przepływu pracy przez wiązanie kontekstu w punkcie końcowym usługi.

Korzystając z nowego proxy utworzonego do komunikowania się z wystąpieniem przepływu pracy, które zostało utworzone wcześniej, możemy użyć podobnej metody, aby ustawić identyfikator w kontekście, w celu upewnienia się, że nasz komunikat zostanie skierowany do odpowiedniego wystąpienia przepływu pracy, jak pokazano poniżej:

IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
  mgr.SetContext(new Dictionary<string, string>{
    {"instanceId", wfIdText.Text}
  });

W przypadku nowo utworzonego proxy, kod ten działa prawidłowo za pierwszym razem, ale nie będzie działał, jeśli spróbujemy ustawić kontekst po raz drugi, w celu wywołania kolejnego wystąpienia przepływu pracy. Błąd, który się pojawi mówi, że nie możemy zmienić kontekstu, kiedy włączone jest automatyczne zarządzanie kontekstem. Zasadniczo, oznacza to, że możemy mieć albo jedno, albo drugie. Jeśli chcemy, aby kontekst zarządzany był automatycznie, nie możemy manipulować nim ręcznie. Niestety, jeśli chcemy zarządzać kontekstem ręcznie, zarządzanie automatyczne nie będzie możliwe, co oznacza brak możliwości pobrania identyfikatora wystąpienia przepływu pracy z kontekstu, tak jak pokazałem wcześniej.

Aby poradzić sobie z tą niezgodnością, każdym przypadkiem należy zająć się osobno. Inicjalizujące wywołanie przepływu pracy powinno zostać wykonane przy użyciu nowego proxy, ale do wszystkich kolejnych wywołań do istniejącego wystąpienia przepływu pracy, używamy pojedynczego klienta proxy i zarządzamy kontekstem ręcznie.

W przypadku wywołania inicjalizującego należy użyć pojedynczego ChannelFactory<TChannel>, w celu utworzenia wszystkich proxy. Rezultatem będzie wyższa wydajność, ponieważ utworzenie ChannelFactory posiada pewne obciążenie, którego nie chcemy duplikować dla każdego pierwszego wywołania. Wykorzystując kod przedstawiony wcześniej na Ilustracji 5, możemy użyć pojedynczego ChannelFactory<TChannel>, w celu utworzenia początkowego proxy. W kodzie wywołania, po użyciu proxy, należy zastosować najlepszą praktykę polegającą na wywołaniu metody Close, w celu uwolnienia proxy.

Jest to standardowy kod WCF do tworzenia proxy przy użyciu metody fabryki kanałów. Ponieważ wiązanie jest wiązaniem kontekstowym, domyślnie mamy do dyspozycji automatyczne zarządzanie kontekstem, co oznacza, że można wyodrębnić identyfikator wystąpienia przepływu pracy z kontekstu po wykonaniu pierwszego wywołania do przepływu pracy.

Aby wykonać kolejne wywołania, należy samodzielnie zarządzać kontekstem, co pociąga za sobą użycie klienckiego kodu WCF, który rzadko jest wykorzystywany przez deweloperów. Aby ustawić kontekst ręcznie, należy użyć klasy OperationContextScope i samodzielnie utworzyć MessageContextProperty. MessageContextProperty ustawiane jest w komunikacie podczas jego wysyłania, co jest równorzędne z użyciem IContextManager do ustawienia kontekstu, z takim wyjątkiem, że bezpośrednie użycie właściwości działa nawet, jeśli wyłączone jest zarządzanie kontekstem. Ilustracja 8 pokazuje kod służący do utworzenia proxy przy użyciu tego samego ChannelFactory<TChannel>, którego użyto dla proxy inicjalizującego. Różnica polega na tym, że w tym przypadku IContextManager jest użyty do wyłączenia funkcji automatycznego zarządzania kontekstem, a zamiast tworzenia nowego proxy dla każdego żądania, używa się proxy buforowanego.

Ilustracja 8 Wyłączanie automatycznego zarządzania kontekstem

App a = (App)Application.Current;

if (updateProxy == null)
{
    if (factory == null)
        factory = new ChannelFactory<IWorkflowInterface>(
            a.LocalBinding, a.WFAddress);

        updateProxy = factory.CreateChannel();
        IContextManager mgr =
            ((IClientChannel)updateProxy).GetProperty<IContextManager>();
        mgr.Enabled = false;
        ((IClientChannel)updateProxy).Open();
}

Po utworzeniu proxy, należy utworzyć OperationContextScope i do właściwości komunikatów wychodzących dodać MessageContextProperty. Umożliwia to załączenie właściwości w komunikatach wychodzących podczas trwania określania zakresu. Ilustracja 9 pokazuje kod, za pomocą którego tworzymy i ustawiamy właściwość komunikatu przy użyciu OperationContextScope.

Ilustracja 9 Użycie OperationContextScope

using (OperationContextScope scope =
    new OperationContextScope((IContextChannel)proxy))
{
    ContextMessageProperty property = new ContextMessageProperty(
        new Dictionary<string, string>
        {
            {“instanceId”, wfIdText.Text}
        });

OperationContext.Current.OutgoingMessageProperties.Add(
    "ContextMessageProperty", property);

proxy.UpdateOrder(
    new Order
        {
            CustomerName = "Matt",
            OrderID = 2,
            OrderTotal = 250.00,
            OrderStatus = "Updated"
        });
}

Komunikacja między hostem a przepływem pracy może wydać się dość pracochłonna. Dobra wiadomość jest taka, że większość niniejszych układów logicznych oraz zarządzanie identyfikatorami można zamknąć w kilku klasach. Jednak, taka komunikacja obejmuje kodowanie klienta w określony sposób, aby upewnić się, że kontekst jest zarządzany odpowiednio dla tych przypadków, w których zaistnieje potrzeba wysłania więcej, niż jednego komunikatu do wystąpienia przepływu pracy. W pobieraniu kodu dla niniejszego artykułu zawarłem przykładowy host dla przepływu pracy korzystającego z komunikacji lokalnej, który ma na celu pokazać złożoność problemu, oraz przykładową aplikację, która pokazuje, jak należy używać hosta.

Kilka informacji na temat interakcji interfejsu użytkownika

Jednym z głównych powodów przesyłania danych z przepływu pracy do hosta jest fakt, iż chcemy je pokazać użytkownikowi w interfejsie aplikacji. Na szczęście, w tym modelu istnieją dwa sposoby skorzystania z funkcji interfejsu użytkownika, w tym wiązanie danych w WPF. Podam prosty przykład. Jeśli chcemy, aby interfejs użytkownika korzystał z wiązania danych oraz aby uaktualniał się po otrzymaniu danych z przepływu pracy, możemy powiązać go bezpośrednio z wystąpieniem usługi hosta.

Kluczem do korzystania z wystąpienia usługi, jako kontekstu danych dla okna jest fakt, iż wystąpienie musi być hostowane jako wystąpienie pojedyncze. Kiedy hostujemy usługę, jako usługę pojedynczą, mamy dostęp do wystąpienia i możemy go użyć w interfejsie użytkownika. Prosta usługa hosta pokazana na Ilustracji 10 uaktualnia właściwość, kiedy otrzymuje informację z przepływu pracy i używa INotifyPropertyChangedInterface, aby pomóc infrastrukturze wiązania danych natychmiast wprowadzić zmiany. Zwróćcie uwagę na atrybut ServiceBehavior, który wskazuje, że klasa ta powinna być hostowana jako klasa pojedyncza. Kiedy wrócimy do Ilustracji 7, zobaczymy, że wystąpienie ServiceHost tworzymy nie z typem, ale z wystąpieniem klasy.

Ilustracja 10 Wdrażanie usługi przy użyciu INotifyPropertyChanged

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
internal class HostService : IHostInterface, INotifyPropertyChanged
{
    public void OrderStatusChange(Order order, string newStatus,
        string oldStatus)
    {
        CurrentMessage = String.Format("Order status changed to {0}",
            newStatus);
    }

private string msg;

public string CurrentMessage {
get { return msg; }
set
    {
        msg = value;
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(
                "CurrentMessage"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Aby wykonać wiązanie danych do tej wartości, DataContext okna, lub określony formant okna, może zostać użyty z wystąpieniem. Można je pobrać poprzez użycie właściwości SingletonInstance w klasie ServiceHost, jak widać poniżej:

HostService host = ((App)Application.Current).appHost.SingletonInstance as HostService;
  if (host != null)
    this.DataContext = host;

Możemy zatem po prostu wiązać elementy okna z właściwościami obiektu, jak pokazuje niniejszy TextBlock:

<TextBlock Text="{Binding CurrentMessage}" Grid.Row="3" />

Jak już wspomniałem, to tylko prosty przykład tego, jakie są możliwości. W prawdziwej aplikacji, prawdopodobnie nie powiązalibyśmy się bezpośrednio z wystąpieniem usługi. W zamian za to dokonalibyśmy powiązania z niektórymi obiektami, do których dostęp mają zarówno okno, jak i wdrażanie usług.

Przyszłość z WF4

W WF4 wprowadzono kilka funkcji, które znacznie ułatwią komunikację lokalną przez WCF. Podstawową funkcją jest korelacja komunikatów, która nie korzysta z protokołu. Oczywiście użycie identyfikatora wystąpienia przepływu pracy nadal będzie dostępne, ale nowa opcja umożliwi skorelowanie komunikatów w oparciu o zawartość komunikatu. Tak więc, jeśli każdy komunikat zawiera identyfikator zamówienia, identyfikator klienta, lub jakiekolwiek inne dane, możliwe jest określenie korelacji między tymi komunikatami i nie ma potrzeby użycia wiązania obsługującego zarządzanie kontekstem. 

Ponadto, fakt iż zarówno WPF, jak i WF zbudowane są na tych samych podstawowych interfejsach XAML API w .NET Framework Version 4 może prowadzić do stworzenia interesujących możliwości integrowania technologii w innowacyjny sposób. W miarę zbliżania się do momentu wypuszczenia .NET Framework 4 będę przedstawiał więcej szczegółów dotyczących integracji WF z WCF i WPF oraz informacje na temat wewnętrznego działania WF4.