Как создать веб-службу 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).