2009-03-06 36 views
4

我正在通過快速入門來研究Prism v2。我已創建了以下簽名的WCF服務:從服務引用生成的類中解耦Silverlight客戶端

namespace HelloWorld.Silverlight.Web 
{ 
[ServiceContract(Namespace = "http://helloworld.org/messaging")] 
[AspNetCompatibilityRequirements(RequirementsMode = 
           AspNetCompatibilityRequirementsMode.Allowed)] 
    public class HelloWorldMessageService 
    { 
    private string message = "Hello from WCF"; 

    [OperationContract] 
    public void UpdateMessage(string message) 
    { 
     this.message = message; 
    } 

    [OperationContract] 
    public string GetMessage() 
    { 
     return message; 
    } 
    } 
} 

當我在我的Silverlight項目添加服務引用該服務將生成一個接口和一個類:

[System.ServiceModel.ServiceContractAttribute 
     (Namespace="http://helloworld.org/messaging", 
     ConfigurationName="Web.Services.HelloWorldMessageService")] 
public interface HelloWorldMessageService { 

    [System.ServiceModel.OperationContractAttribute 
      (AsyncPattern=true, 
     Action="http://helloworld.org/messaging/HelloWorldMessageService/UpdateMessage", 
ReplyAction="http://helloworld.org/messaging/HelloWorldMessageService/UpdateMessageResponse")] 
    System.IAsyncResult BeginUpdateMessage(string message, System.AsyncCallback callback, object asyncState); 

    void EndUpdateMessage(System.IAsyncResult result); 

    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://helloworld.org/messaging/HelloWorldMessageService/GetMessage", ReplyAction="http://helloworld.org/messaging/HelloWorldMessageService/GetMessageResponse")] 
    System.IAsyncResult BeginGetMessage(System.AsyncCallback callback, object asyncState); 

    string EndGetMessage(System.IAsyncResult result); 
} 

public partial class HelloWorldMessageServiceClient : System.ServiceModel.ClientBase<HelloWorld.Core.Web.Services.HelloWorldMessageService>, HelloWorld.Core.Web.Services.HelloWorldMessageService { 
{ 
    // implementation 
} 

我試圖通過傳遞接口而不是具體類來解耦我的應用程序。但我很難找到如何做到這一點的例子。當我嘗試調用EndGetMessage然後更新我的UI時,我得到了關於在錯誤線程上更新UI的異常。我如何從後臺線程更新UI?


我試過但我得到UnauthorizedAccessException : Invalid cross-thread access

string messageresult = _service.EndGetMessage(result); 

Application.Current.RootVisual.Dispatcher.BeginInvoke(() => this.Message = messageresult); 

Application.Current.RootVisual拋出異常。

回答

0

傳遞接口(一旦你實例化客戶端)應該像使用HelloWorldMessageService而不是HelloWorldMessageServiceClient類一樣簡單。

爲了更新UI,您需要使用Dispatcher對象。這使您可以提供在UI線程的上下文中調用的委託。有關詳細信息,請參閱此blog post

+0

我試過,但我得到UnauthorizedAccessException:無效的跨線程訪問。 string messageresult = _service.EndGetMessage(result); Application.Current.RootVisual.Dispatcher.BeginInvoke(()=> this.Message = messageresult ); Application.Current.RootVisual拋出異常 – 2009-03-06 01:20:12

1

好吧,所以我真正的問題是如何分離我的依賴於由我的服務引用創建的代理類。我試圖通過使用與代理類一起生成的接口來實現。這可能工作得很好,但是之後我也必須引用擁有服務引用的項目,因此它不會真正解耦。所以這就是我最終做的。這有點破解,但到目前爲止它似乎還在工作。

首先這裏是我的接口定義,並與我的代理產生的自定義事件處理程序ARGS適配器類:

using System.ComponentModel; 

namespace HelloWorld.Interfaces.Services 
{ 
    public class GetMessageCompletedEventArgsAdapter : System.ComponentModel.AsyncCompletedEventArgs 
    { 
     private object[] results; 

     public GetMessageCompletedEventArgsAdapter(object[] results, System.Exception exception, bool cancelled, object userState) : 
      base(exception, cancelled, userState) 
     { 
      this.results = results; 
     } 

     public string Result 
     { 
      get 
      { 
       base.RaiseExceptionIfNecessary(); 
       return ((string)(this.results[0])); 
      } 
     } 
    } 

    /// <summary> 
    /// Create a partial class file for the service reference (reference.cs) that assigns 
    /// this interface to the class - then you can use this reference instead of the 
    /// one that isn't working 
    /// </summary> 

    public interface IMessageServiceClient 
    { 
     event System.EventHandler<GetMessageCompletedEventArgsAdapter> GetMessageCompleted; 
     event System.EventHandler<AsyncCompletedEventArgs> UpdateMessageCompleted; 

     void GetMessageAsync(); 
     void GetMessageAsync(object userState); 

     void UpdateMessageAsync(string message); 
     void UpdateMessageAsync(string message, object userState); 
    } 
} 

然後我需要創建延伸服務引用生成的代理類的分部類:

using System; 

using HelloWorld.Interfaces.Services; 
using System.Collections.Generic; 

namespace HelloWorld.Core.Web.Services 
{ 
    public partial class HelloWorldMessageServiceClient : IMessageServiceClient 
    { 

     #region IMessageServiceClient Members 

     private event EventHandler<GetMessageCompletedEventArgsAdapter> handler; 
     private Dictionary<EventHandler<GetMessageCompletedEventArgsAdapter>, EventHandler<GetMessageCompletedEventArgs>> handlerDictionary 
      = new Dictionary<EventHandler<GetMessageCompletedEventArgsAdapter>, EventHandler<GetMessageCompletedEventArgs>>(); 

     /// <remarks> 
     /// This is an adapter event which allows us to apply the IMessageServiceClient 
     /// interface to our MessageServiceClient. This way we can decouple our modules 
     /// from the implementation 
     /// </remarks> 
     event EventHandler<GetMessageCompletedEventArgsAdapter> IMessageServiceClient.GetMessageCompleted 
     { 
      add 
      { 
       handler += value; 
       EventHandler<GetMessageCompletedEventArgs> linkedhandler = new EventHandler<GetMessageCompletedEventArgs>(HelloWorldMessageServiceClient_GetMessageCompleted); 
       this.GetMessageCompleted += linkedhandler; 
       handlerDictionary.Add(value, linkedhandler); 
      } 
      remove 
      { 
       handler -= value; 
       EventHandler<GetMessageCompletedEventArgs> linkedhandler = handlerDictionary[value]; 
       this.GetMessageCompleted -= linkedhandler; 
       handlerDictionary.Remove(value); 
      } 
     } 

     void HelloWorldMessageServiceClient_GetMessageCompleted(object sender, GetMessageCompletedEventArgs e) 
     { 
      if (this.handler == null) 
       return; 

      this.handler(sender, new GetMessageCompletedEventArgsAdapter(new object[] { e.Result }, e.Error, e.Cancelled, e.UserState)); 
     } 

     #endregion 
    } 
} 

這是一個顯式實現的事件處理程序,所以我可以將事件鏈接在一起。當用戶註冊我的適配器事件時,我註冊了實際的事件。當事件發生時,我會觸發我的適配器事件。到目前爲止,這個「在我的機器上工作」。

1

好的,我一整天都在搞這個,解決方案真的比這個簡單得多。我原本想調用接口上的方法而不是concreate類。由代理類生成器生成的接口只包含BeginXXXEndXXX方法,我在調用EndXXX時遇到了異常。

那麼,我剛剛讀完System.Threading.Dispatcher,我終於明白如何使用它了。 Dispatcher是從UI元素所做的DispatcherObject繼承的任何類的成員。 Dispatcher在UI線程上運行,對於大多數WPF應用程序,只有1個UI線程。有例外,但我相信你必須明確地做到這一點,所以你會知道你是否在做。否則,你只有一個UI線程。因此,存儲對Dispatcher的引用以便在非UI類中使用是安全的。

在我的情況下,我使用棱鏡和我的演示者需要更新UI(不直接,但它正在發射IPropertyChanged.PropertyChanged事件)。所以我做了什麼,在我Bootstrapper當我設置殼Application.Current.RootVisual我還可以存儲到Dispatcher這樣的引用:

public class Bootstrapper : UnityBootstrapper 
{ 
    protected override IModuleCatalog GetModuleCatalog() 
    { 
    // setup module catalog 
    } 

    protected override DependencyObject CreateShell() 
    { 
     // calling Resolve instead of directly initing allows use of dependency injection 
    Shell shell = Container.Resolve<Shell>(); 

     Application.Current.RootVisual = shell; 

     Container.RegisterInstance<Dispatcher>(shell.Dispatcher); 

     return shell; 
    } 
} 

然後我的演講具有接受IUnityContainer作爲參數構造函數(使用DI)那麼我可以做到以下幾點:

_service.BeginGetMessage(new AsyncCallback(GetMessageAsyncComplete), null);  

private void GetMessageAsyncComplete(IAsyncResult result) 
{ 
    string output = _service.EndGetMessage(result); 
    Dispatcher dispatcher = _container.Resolve<Dispatcher>(); 
    dispatcher.BeginInvoke(() => this.Message = output); 
} 

這太簡單了。我以前不明白。

0

你可以使這個更簡單。

代理工作的原因和您的合同副本不是因爲WCF生成的代理使用代碼在調用線程上「回傳」回調,而不是在服務執行時執行的線程上進行回調致電退貨。

一個更簡化的,未經檢驗的,部分實現,給你的WCF代理工作怎麼看起來像的想法:

{ 
    var state = new 
     { 
      CallingThread = SynchronizationContext.Current, 
      Callback = yourCallback 
      EndYourMethod = // assign delegate 
     }; 

    yourService.BeginYourMethod(yourParams, WcfCallback, state); 
} 

private void WcfCallback(IAsyncResult asyncResult) 
{ 
    // Read the result object data to get state 
    // Call EndYourMethod and block until the finished 
    state.Context.Post(state.YourCallback, endYourMethodResultValue); 
} 

的關鍵是syncronizationContext的存儲和調用Post方法。這將使得回調在與Begin被調用相同的線程上發生。只要您從UI線程調用Begin,它將始終工作而不涉及Dispatcher對象。如果你不這樣做,那麼你使用Dispatcher回到原來的狀態,但是WCF代理也會出現同樣的問題。

這個環節做了解釋的一個好工作如何手動做到這一點:
http://msdn.microsoft.com/en-us/library/dd744834(VS.95).aspx

2

這是我喜歡做......服務代理與接口生成

HelloWorldClient : IHelloWorld 

但問題是IHelloWorld不包含該方法的異步版本。所以,我創建一個異步接口:

public interface IHelloWorldAsync : IHelloWorld 
{ 
    void HelloWorldAsync(...); 
    event System.EventHandler<HelloWorldEventRgs> HelloWorldCompleted; 
} 

然後,你可以告訴服務代理通過局部實現接口:

public partial class HelloWorldClient : IHelloWorldAsync {} 

因爲HelloWorldClient確實,確實,實現這些異步方法,這作品。

然後,我可以隨處使用IHelloWorldAsync,並告訴UnityContainer使用HelloWorldClient代替IHelloWorldAsync接口。

+0

我喜歡這個,因爲你可以讓你的mock類的HelloWorldAsync方法調用HelloWorldCompleted函數,如果不是null,例如, HelloWorldCompleted(null,new HelloWorldEventArgs(「hello world」,null,false,null))。我在Silverlight單元測試中嘗試了這一點,它可以工作。不要搞亂Async單元測試,不需要調度你的RaisePropertyChanged通知。你只是想知道SL/WCF爲什麼不自動在服務接口上包含基於事件的方法。它可以節省很多代碼! – 2011-12-02 18:44:24

相關問題