Отключение клиента Silverlight от сгенерированного класса обслуживания

StackOverflow https://stackoverflow.com/questions/617377

  •  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, а затем обновить свой пользовательский интерфейс, я получаю исключение об обновлении пользовательского интерфейса в неправильном потоке. Как я могу обновить пользовательский интерфейс из фонового потока? <Ч>

Я пытался, но я получил UnauthorizedAccessException: недопустимый межпоточный доступ .

string messageresult = _service.EndGetMessage(result);

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

Исключение выдается Application.Current.RootVisual .

Это было полезно?

Решение 6

Просто пересмотр старых постов остался без ответа, где я наконец нашел ответ. Вот статья, которую я недавно написал, которая подробно описывает, как я наконец справился со всем этим:

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

Другие советы

Вот что мне нравится делать: служебный прокси генерируется с интерфейсом

HelloWorldClient : IHelloWorld

Но проблема в том, что IHelloWorld не включает в себя асинхронные версии метода. Итак, я создаю асинхронный интерфейс:

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

Затем вы можете указать служебному прокси-серверу, чтобы он реализовывал интерфейс с помощью частичного:

public partial class HelloWorldClient : IHelloWorldAsync {}

Поскольку HelloWorldClient действительно реализует эти асинхронные методы, это работает.

Затем я могу просто использовать IHelloWorldAsync везде и указать UnityContainer использовать HelloWorldClient для IHelloWorldAsync интерфейсов.

Хорошо, я весь день возился с этим, и решение действительно намного проще, чем это. Первоначально я хотел вызвать методы интерфейса вместо класса concreate. Интерфейс, сгенерированный генератором прокси-классов, включает только методы BeginXXX и EndXXX , и я получал исключение, когда я вызывал EndXXX .

Хорошо, я только что закончил читать System.Threading.Dispatcher и наконец понял, как его использовать. Dispatcher является членом любого класса, который наследуется от DispatcherObject , что делают элементы пользовательского интерфейса. Dispatcher работает в потоке пользовательского интерфейса, который для большинства приложений WPF имеет только 1 поток пользовательского интерфейса. Есть исключения, но я считаю, что вы должны сделать это явно, чтобы вы знали, делаете ли вы это. В противном случае у вас есть только один поток пользовательского интерфейса. Поэтому безопасно хранить ссылку на Dispatcher для использования в классах, не связанных с пользовательским интерфейсом.

В моем случае я использую Prism, и моему Presenter необходимо обновить пользовательский интерфейс (не напрямую, но он запускает события 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;
    }
}

Тогда у моего докладчика есть ctor, который принимает 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);
}

Это оооочень намного проще. Я просто не понял этого раньше.

Итак, моя настоящая проблема заключалась в том, как отделить мою зависимость от прокси-класса, созданного моей ссылкой на сервис. Я пытался сделать это с помощью интерфейса, созданного вместе с прокси-классом. Который мог бы работать нормально, но тогда мне также пришлось бы ссылаться на проект, которому принадлежала ссылка на сервис, и поэтому он не был бы действительно отделен. Итак, вот что я в итоге сделал. Это что-то вроде хака, но, похоже, работает пока.

Сначала приведу определение моего интерфейса и класс адаптера для пользовательских аргументов обработчика событий, сгенерированных моим прокси-сервером:

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
    }
}

Это явная реализация обработчика событий, поэтому я могу связать воедино события. Когда пользователь регистрируется для моего события адаптера, я регистрируюсь для фактического инициированного события. Когда происходит событие, я запускаю событие адаптера. Пока что это «Работает на моей машине».

Передача интерфейса (после создания экземпляра клиента) должна быть такой же простой, как использование HelloWorldMessageService вместо класса HelloWorldMessageServiceClient.

Для обновления пользовательского интерфейса вам необходимо использовать объект Dispatcher. Это позволяет вам предоставить делегата, который вызывается в контексте потока пользовательского интерфейса. См. Этот пост блога для получения дополнительной информации.

Вы можете сделать это еще проще.

Причина, по которой прокси работает, а ваша копия контракта - нет, заключается в том, что WCF генерирует прокси с кодом, который " Posts " обратный вызов в вызывающем потоке, а не обратный вызов в потоке, который выполняется при возврате вызова службы.

Очень упрощенная, непроверенная, частичная реализация, дающая представление о том, как работают прокси 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. Он всегда будет работать без использования объекта Dispatcher, если вы вызываете Begin из потока пользовательского интерфейса. Если вы этого не сделаете, то вернетесь к исходной точке с помощью Dispatcher, но та же проблема возникнет с прокси WCF.

Эта ссылка хорошо объясняет, как сделать это вручную:
http://msdn.microsoft.com/en-us /library/dd744834(VS.95).aspx

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top