我正在通过快速入门研究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
抛出异常。
答案 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类。代理类生成器生成的接口仅包含BeginXXX
和EndXXX
方法,当我调用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)
只是重新访问未经回复的旧帖子,我终于找到了答案。这是我最近写的一篇文章,详细介绍了我最终如何处理这一切: