Как создать веб-службу WCF в приложении ASP.NET, которая может возвращать экземпляры интерфейса в виде прозрачного прокси - PullRequest
4 голосов
/ 07 февраля 2012

Мой вариант использования:

  • У меня уже есть работающее приложение ASP.NET
  • Я хотел бы реализовать новую веб-службу как часть этого приложения
  • Я должен использовать службу WCF (* .svc), не веб-службу ASP.NET (* .asmx)
  • Служба должна иметь одну операцию, давайте вызовемэто GetInterface(), который возвращает экземпляр интерфейса.Этот экземпляр должен находиться на сервере, не быть сериализованным для клиента;методы, вызываемые на этом интерфейсе, должны выполняться на сервере.

Вот что я попробовал (пожалуйста, скажите, где я ошибся):

  • Для целейпроверяя это, я создал новый проект ASP.NET Web Application с именем ServiceSide.

  • В этом проекте я добавил WCF Service используя «Добавить → Новый элемент».Я назвал это MainService.Это создало как класс MainService, так и интерфейс IMainService.

  • Теперь я создал новый проект Библиотека классов , который называется ServiceWorkLibrary и содержит толькообъявление интерфейса, которое должно быть разделено между клиентом и сервером, ничего больше:

    [ServiceContract]
    public interface IWorkInterface
    {
        [OperationContract]
        int GetInt();
    }
    
  • В ServiceSide я заменил метод DoWork() по умолчанию в IMainService интерфейс, а также его реализация в классе MainService, и я также добавил простую реализацию для общего IWorkInterface.Теперь они выглядят так:

    [ServiceContract]
    public interface IMainService
    {
        [OperationContract]
        IWorkInterface GetInterface();
    }
    
    public class MainService : IMainService
    {
        public IWorkInterface GetInterface()
        {
            return new WorkInterfaceImpl();
        }
    }
    
    public class WorkInterfaceImpl : MarshalByRefObject, IWorkInterface
    {
        public int GetInt() { return 47; }
    }
    

    Теперь запуск этого приложения «работает» в том смысле, что он дает мне страницу веб-службы по умолчанию в браузере, которая говорит:

    Вы создали сервис.

    Чтобы протестировать этот сервис, вам нужно будет создать клиент и использовать его для вызова сервиса.Это можно сделать с помощью инструмента svcutil.exe из командной строки со следующим синтаксисом:

    svcutil.exe http://localhost:59958/MainService.svc?wsdl
    

    . Это создаст файл конфигурации и файл кода, содержащий класс клиента.Добавьте эти два файла в ваше клиентское приложение и используйте сгенерированный клиентский класс для вызова Сервиса.Например:

  • Итак, клиенту.В отдельной Visual Studio я создал новый проект Консольное приложение под названием ClientSide с новым решением.Я добавил проект ServiceWorkLibrary и добавил ссылку на него из ClientSide.

  • Затем я выполнил вышеуказанный вызов svcutil.exe.Это сгенерировало MainService.cs и output.config, которые я добавил в проект ClientSide.

  • Наконец, я добавил следующий код в метод Main:

    using (var client = new MainServiceClient())
    {
        var workInterface = client.GetInterface();
        Console.WriteLine(workInterface.GetType().FullName);
    }
    
  • Это уже завершается ошибкой с загадочным исключением в вызове конструктора.Мне удалось это исправить, переименовав output.config в App.config.

  • Я заметил, что тип возвращаемого значения GetInterface() равен object вместо IWorkInterface.Кто-нибудь знает почему?Но давайте двигаться дальше ...

  • Теперь, когда я запускаю это, я получаю CommunicationException при вызове GetInterface():

    Основное соединение былозакрыто: соединение было неожиданно закрыто.

Как это исправить, чтобы получить ожидаемый прозрачный прокси IWorkInterface? 1115 *

Вещи, которые я пробовал

  • Я пытался добавить [KnownType(typeof(WorkInterfaceImpl))] к объявлению WorkInterfaceImpl.Если я сделаю это, я получу другое исключение в том же месте.Теперь это NetDispatcherFaultException с сообщением:

    Средство форматирования выдало исключение при попытке десериализации сообщения: при попытке десериализации параметра произошла ошибка http://tempuri.org/:GetInterfaceResult. Сообщение InnerExceptionwas 'Ошибка в строке 1, позиция 491. Элемент' http://tempuri.org/:GetInterfaceResult' содержит данные из типа, который сопоставляется с именем 'http://schemas.datacontract.org/2004/07/ServiceSide:WorkInterfaceImpl'. Десериализатор не знает ни о каком типе, который сопоставляется с этим именем.Попробуйте использовать DataContractResolver или добавить тип, соответствующий WorkInterfaceImpl, в список известных типов, например, используя атрибут KnownTypeAttribute или добавив его в список известных типов, передаваемых DataContractSerializer.Пожалуйста, смотрите InnerException для более подробной информации.

    InnerException mЭто SerializationException с сообщением:

    Ошибка в строке 1 позиции 491. Элемент 'http://tempuri.org/:GetInterfaceResult' содержит данные из типа, который сопоставляется с именем' http://schemas.datacontract.org/2004/07/ServiceSide:WorkInterfaceImpl'. Десериализатор не знаетлюбого типа, который сопоставляется с этим именем.Попробуйте использовать DataContractResolver или добавить тип, соответствующий WorkInterfaceImpl, в список известных типов, например, с помощью атрибута KnownTypeAttribute или добавив его в список известных типов, передаваемых DataContractSerializer.

    Обратите внимание, как это указывает на то, что система пытается сериализовать тип. Он не должен этого делать. Вместо этого он должен генерировать прозрачный прокси.Как мне сказать, чтобы он прекратил попытки сериализовать его?

  • Я попытался добавить атрибут [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] в класс WorkInterfaceImpl.Без эффекта.

  • Я попытался изменить атрибут [ServiceContract] на интерфейсе IWorkInterface (объявленном в общей библиотеке ServiceWorkLibrary) на [ServiceContract(SessionMode = SessionMode.Required)].Также без эффекта.

  • Я также попытался добавить следующий магический элемент system.diagnostics к Web.config в ServerSide:

      <system.diagnostics>
        <!-- This logging is great when WCF does not work. -->
        <sources>
          <source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true">
            <listeners>
              <add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\traces.svclog"  />
            </listeners>
          </source>
        </sources>
      </system.diagnostics>
    

    Это генерируетc:\traces.svclog файл, как и было обещано, но я не уверен, что смогу разобраться в его содержимом. Я разместил сгенерированный файл в pastebin здесь. Вы можете просмотреть эту информацию в более удобном пользовательском интерфейсе, используя svctraceviewer.exe.Я сделал это, но, честно говоря, все эти вещи мне ничего не говорят ...

Что я делаю не так?

Ответы [ 3 ]

1 голос
/ 09 февраля 2012

Вариант использования, который я описываю, напрямую не поддерживается WCF.

Принятым обходным решением является возврат экземпляра EndpointAddress10 , который указывает на службу для «другогоИнтерфейс.Затем клиент должен вручную создать канал для доступа к удаленному объекту.WCF неправильно инкапсулирует этот процесс.

Пример, демонстрирующий это, связан с статьей MSDN «От .NET Remoting к Windows Communication Foundation (WCF)» (найдитетекст с надписью «Нажмите здесь, чтобы загрузить пример кода для этой статьи»).Этот пример кода демонстрирует как .NET Remoting, так и WCF.Он определяет интерфейс, который выглядит следующим образом:

[ServiceContract]
public interface IRemoteFactory
{
    IMySessionBoundObject GetInstance();
    [OperationContract]
    EndpointAddress10 GetInstanceAddress();
}

Обратите внимание, что метод возврата интерфейса не является частью контракта, только тот, который возвращает EndpointAddress10, помечен [OperationContract].В этом примере первый метод вызывается с помощью Remoting, где он правильно создает удаленный прокси-сервер, как и следовало ожидать, но при использовании WCF он прибегает ко второму методу, а затем создает отдельный ChannelFactory с новым адресом конечной точки для доступа к новому объекту.

0 голосов
/ 07 февраля 2012

То, что вы пытаетесь сделать, является прямым нарушением принципа SOA: «Службы разделяют схему и контракт, а не класс». Это означает, что вы на самом деле не передаете код реализации от сервиса его потребителям, а только возвращаемые значения, которые указаны в самом договоре.

Основное внимание WCF и SOA в целом уделяется взаимодействию, то есть сервисы должны быть доступны для клиентов любой платформы. Как потребитель Java или C ++ сможет использовать этот сервис, который вы разрабатываете? Короткий ответ: это невозможно, поэтому вам будет трудно, если не невозможно, сериализовать этот код с использованием стандартов обмена сообщениями, таких как SOAP.

Более подходящим способом структурирования этого кода было бы размещение каждой реализации IWorkerInterface в качестве своей собственной службы (в конце концов, она была определена как контракт на обслуживание) и предоставление каждой службы в другой конечной точке. Вместо MainService, работающей в качестве удаленной фабрики для прокси-серверов IWorkerInterface, она может выступать в качестве фабрики конечных точек для различных настроенных вами служб. Метаданные конечной точки могут быть легко сериализованы и предоставлены клиенту IMainService. Затем клиент может взять эти метаданные и создать прокси для удаленной реализации, либо с помощью некоторой пользовательской реализации IServiceProxy, либо даже с помощью объектов, уже предоставленных вам WCF (например, ChannelFactory).

0 голосов
/ 07 февраля 2012

Что такое MainServiceClient()? Это класс, маршалирующий клиентские сообщения на сервер.

Вы должны взглянуть на соответствующий пост SO на , возвращающий интерфейсы в качестве параметров в WCF . ServiceKnownTypeAttribute может быть полезным.

Сеансы также могут быть тем, что вы ищете MarshalByRef, поскольку это относится к поведению .NET Remoting.

Другой подход (, как упомянуто на Форумы MSDN ), заключается в возвращении EndpointAddress интерфейса службы вместо самого интерфейса.

WCF сериализует все - независимо от привязки. Наилучший подход, который вам следует использовать, если вам необходимо установить связь со службой в той же системе, - это использовать привязка транспорта IPC ( net.pipe ).

...