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

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

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

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

  • Для проверки этого я создал новый проект веб-приложения ASP.NET под названием ServiceSide,

  • В рамках этого проекта я добавил службу WCF, используя "Добавить → Новый элемент". Я назвал это 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 Прозрачный прокси, что я ожидаю?

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

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

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

    InnerException упоминается это 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 ответа

Решение

Вариант использования, который я описываю, напрямую не поддерживается 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 с новым адресом конечной точки для доступа к новому объекту.

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

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

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

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

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

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

Другой подход (как упомянуто на форумах MSDN) - вернуть EndpointAddress интерфейса службы вместо самого интерфейса.

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

Другие вопросы по тегам