4

我正在通过快速入门研究 Prism v2。我创建了一个具有以下签名的 WCF 服务:

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.

4

6 回答 6

2

这是我喜欢做的事情......服务代理是用一个接口生成的

HelloWorldClient : IHelloWorld

但问题是IHelloWorld不包括该方法的异步版本。所以,我创建了一个异步接口:

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

然后,您可以告诉服务代理通过部分实现接口:

public partial class HelloWorldClient : IHelloWorldAsync {}

因为HelloWorldClient确实实现了这些异步方法,所以这是可行的。

然后,我可以在IHelloWorldAsync任何地方使用并告诉接口UnityContainer使用HelloWorldClientIHelloWorldAsync

于 2009-11-09T18:38:35.183 回答
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
    }
}

这是事件处理程序的显式实现,因此我可以将事件链接在一起。当用户注册我的适配器事件时,我注册了实际触发的事件。当事件触发时,我触发我的适配器事件。到目前为止,这是“在我的机器上工作”。

于 2009-03-06T11:53:06.373 回答
1

好的,我整天都在搞这个,解决方案真的比这简单得多。我最初想调用接口上的方法而不是 concreate 类。代理类生成器生成的接口只包含BeginXXXandEndXXX方法,当我调用EndXXX.

好吧,我刚刚读完System.Threading.Dispatcher,我终于明白如何使用它了。是从UI 元素Dispatcher继承的任何类的成员。DispatcherObjectDispatcherUI 线程上运行,对于大多数 WPF 应用程序来说,只有 1 个 UI 线程。有例外,但我相信你必须明确地这样做,这样你就会知道你是否正在这样做。否则,您只有一个 UI 线程。因此,存储对 Dispatcher 的引用以在非 UI 类中使用是安全的。

就我而言,我使用的是 Prism,我的 Presenter 需要更新 UI(不是直接更新,而是触发IPropertyChanged.PropertyChanged事件)。所以我所做的是在我Bootstrapper将 shell 设置为时,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作为参数的 ctor(使用 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);
}

这太简单了。只是我之前没看懂。

于 2009-03-06T23:05:53.197 回答
0

传递接口(一旦您实例化了客户端)应该与使用 HelloWorldMessageService 而不是 HelloWorldMessageServiceClient 类一样简单。

为了更新 UI,您需要使用 Dispatcher 对象。这使您可以提供在 UI 线程的上下文中调用的委托。有关详细信息,请参阅此博客文章

于 2009-03-06T01:12:11.657 回答
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

于 2009-11-03T18:51:49.407 回答
0

只是重温旧帖子没有得到答案,我终于找到了答案。这是我最近写的一篇文章,详细介绍了我最终如何处理这一切:

http://www.developmentalmadness.com/archive/2009/11/04/mvvm-with-prism-101-ndash-part-6-commands.aspx

于 2009-12-08T01:08:06.173 回答