サービス参照生成クラスからSilverlightクライアントを分離する
-
03-07-2019 - |
質問
私は、クイックスタートを使用して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
古い記事を再訪すると、答えが見つからず、最終的に答えが見つかりました。以下は最近書いた投稿で、最終的にこれらすべてをどのように処理したかについて詳しく説明しています。
他のヒント
私がやりたいことがあります...サービスプロキシはインターフェイスで生成されます
HelloWorldClient : IHelloWorld
しかし、問題は IHelloWorld
にメソッドの非同期バージョンが含まれていないことです。そこで、非同期インターフェイスを作成します:
public interface IHelloWorldAsync : IHelloWorld
{
void HelloWorldAsync(...);
event System.EventHandler<HelloWorldEventRgs> HelloWorldCompleted;
}
その後、部分を介してインターフェイスを実装するようにサービスプロキシに指示できます:
public partial class HelloWorldClient : IHelloWorldAsync {}
HelloWorldClient
は実際にこれらの非同期メソッドを実装しているため、これは機能します。
その後、どこでも IHelloWorldAsync
を使用して、 IHelloWorldAsync
インターフェースに HelloWorldClient
を使用するように UnityContainer
に指示することができます。
OK、私はこれを一日中いじっていましたが、解決策はそれよりもはるかに簡単です。私はもともと、concreateクラスの代わりにインターフェース上のメソッドを呼び出したいと思っていました。プロキシクラスジェネレーターによって生成されたインターフェイスには BeginXXX
および EndXXX
メソッドのみが含まれ、 EndXXX
を呼び出したときに例外が発生していました。
まあ、私は System.Threading.Dispatcher
を読み終えたところで、ついにその使い方を理解しました。 Dispatcher
は、UI要素が行う DispatcherObject
を継承するクラスのメンバーです。 Dispatcher
はUIスレッドで動作します。ほとんどのWPFアプリケーションでは、UIスレッドは1つしかありません。例外もありますが、これを明示的に行う必要があると思うので、あなたがそれをやっているかどうかを知ることができます。それ以外の場合、単一のUIスレッドしかありません。そのため、非UIクラスで使用するためにDispatcherへの参照を保存しても安全です。
私はPrismを使用しており、プレゼンターはUIを更新する必要があります(直接ではなく、 IPropertyChanged.PropertyChanged
イベントを発生させています)。そのため、シェルを Application.Current.RootVisual
に設定したときに Bootstrapper
で行ったのは、 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);
}
これは非常に簡単です。以前は理解できませんでした。
そう、私の本当の問題は、サービス参照によって作成されたプロキシクラスへの依存関係を切り離す方法でした。プロキシクラスと共に生成されたインターフェイスを使用して、それを行おうとしていました。それはうまくいくはずでしたが、サービス参照を所有するプロジェクトも参照する必要があったため、それは本当に切り離されませんでした。だからここに私がやったことです。ちょっとしたハックですが、今のところ機能しているようです。
まず、インターフェイス定義と、プロキシで生成されたカスタムイベントハンドラー引数のアダプタークラスを次に示します。
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
}
}
これはイベントハンドラーの明示的な実装であるため、イベントを連結できます。ユーザーがアダプターイベントに登録するとき、実際に発生したイベントに登録します。イベントが発生すると、アダプターイベントが発生します。これまでのところ、この「Works on My Machine」。
(クライアントをインスタンス化したら)インターフェイスを渡すのは、HelloWorldMessageServiceClientクラスの代わりにHelloWorldMessageServiceを使用するのと同じくらい簡単です。
UIを更新するには、Dispatcherオブジェクトを使用する必要があります。これにより、UIスレッドのコンテキストで呼び出されるデリゲートを提供できます。詳細については、ブログ投稿をご覧ください。
これをさらに簡単にすることができます。
プロキシが機能し、契約のコピーが機能しない理由は、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