将Silverlight客户端与服务引用生成的类分离

时间:2009-03-06 01:00:05

标签: wcf silverlight prism

我正在通过快速入门研究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抛出异常。

6 个答案:

答案 0 :(得分:2)

这是我喜欢做的事情......服务代理是通过接口

生成的
HelloWorldClient : IHelloWorld

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

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

然后,您可以通过partial:

告诉服务代理实现接口
public partial class HelloWorldClient : IHelloWorldAsync {}

因为HelloWorldClient确实实现了那些异步方法,所以这很有效。

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

答案 1 :(得分: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
    }
}

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

答案 2 :(得分:1)

好的,我一整天都在搞乱这个问题,解决方案真的要简单得多。我原本想调用接口上的方法而不是concreate类。代理类生成器生成的接口仅包含BeginXXXEndXXX方法,当我调用EndXXX时,我收到异常。

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

在我的情况下,我正在使用Prism,我的Presenter需要更新UI(不是直接,但它正在触发IPropertyChanged.PropertyChanged事件)。所以当我将shell设置为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作为参数的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);
}

这太简单了。我以前根本不理解。

答案 3 :(得分:0)

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

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

答案 4 :(得分: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

答案 5 :(得分:0)

只是重新访问未经回复的旧帖子,我终于找到了答案。这是我最近写的一篇文章,详细介绍了我最终如何处理这一切:

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