Наследование от общего договора в WCF - PullRequest
7 голосов
/ 24 мая 2009

Больше проблем WCF ...:)

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

[ServiceContract(Namespace = "http://schema.company.com/messages/")]
public interface IBasicContract<TRequest, TResponse>
  where TRequest : class
  where TResponse : class
{
  [OperationContract(Name = "GetReport",
    Action = "http://schema.company.com/messages/GetReport",
    ReplyAction = "http://schema.company.com/messages/GetReportResponse")]
  TResponse GetReport(TRequest inquiry);

  [OperationContract(Name = "GetRawReport",
    Action = "http://schema.company.com/messages/GetRawReport",
    ReplyAction = "http://schema.company.com/messages/GetRawReportResponse")]
  string GetRawReport(string guid);

  [OperationContract(Name = "GetArchiveReport",
    Action = "http://schema.company.com/messages/GetArchiveReport",
    ReplyAction = "http://schema.company.com/messages/GetArchiveReportResponse")]
  TResponse GetArchiveReport(string guid);
}

Я также решил создать общую реализацию сервисного клиента:

public class BasicSvcClient<TRequest, TResponse> : ClientBase<IBasicContract<TRequest, TResponse>>, IBasicContract<TRequest, TResponse>
  where TRequest : class
  where TResponse : class
{
  public BasicSvcClient()
  {
  }

  public BasicSvcClient(string endpointConfigurationName) :
    base(endpointConfigurationName)
  {
  }

  public BasicSvcClient(string endpointConfigurationName, string remoteAddress) :
    base(endpointConfigurationName, remoteAddress)
  {
  }

  public BasicSvcClient(string endpointConfigurationName, EndpointAddress remoteAddress) :
    base(endpointConfigurationName, remoteAddress)
  {
  }

  public BasicSvcClient(Binding binding, EndpointAddress remoteAddress) :
    base(binding, remoteAddress)
  {
  }

  public TResponse GetReport(TRequest inquiry)
  {
    return Channel.GetReport(inquiry);
  }

  public string GetRawReport(string guid)
  {
    return Channel.GetRawReport(guid);
  }

  public TResponse GetArchiveReport(string guid)
  {
    return Channel.GetArchiveReport(guid);
  }
}

Проблема в том, когда я пытаюсь использовать это:

using (var client = new BasicSvcClient<TRequest, TResponse>())
{
  var response = client.GetReport(inquiry);

  context.Response.ContentType = "text/xml";
  context.Response.Write(response.AsXML());
}

Я всегда получаю сообщение о том, что не удается найти конфигурацию для контракта IBasicContract, в том странном синтаксисе, который использует .NET:

Не удалось найти конечную точку по умолчанию элемент, который ссылается на контракт «BasicWorkflow.IBasicContract`2 ...

Я пытался сделать это:

using (var client = new BasicSvcClient<TRequest, TResponse>("myConfig"))

Это не помогает - он все еще ищет этот конкретный контракт.

Я знаю, что атрибут ServiceContract имеет параметр ConfigurationName, но я не могу использовать его во время компиляции, потому что у меня много рабочих процессов, которые я вызываю из одной и той же программы (и, следовательно, много записей конфигурации). Есть ли способ установить ConfigurationName во время выполнения? Я думал, что это то, что должен был делать конструктор ClientBase, но, видимо, нет.

[Редактировать] Это конечная точка в файле .config, я не думаю, что это очень полезно в этом случае:

<endpoint address="https://localhost/services/Contract.svc"
    binding="basicHttpBinding"
    bindingConfiguration="httpsDataEndpoint"
    contract="IContract" name="IContractSvc" />

[Edit2] Хорошо ... Я нашел способ, который работает, хотя я все еще не полностью удовлетворен этим:

using (var wf = new BasicSvcClient<TRequest, TResponse>(
  new BasicHttpBinding("httpsDataEndpoint"),
  new EndpointAddress("https://localhost/services/Contract.svc")))

Единственная проблема, с которой я столкнулся сейчас, заключается в том, что я предпочел бы получить адрес конечной точки из файла .config (используя фактическое имя контракта, например, IContract). Кто-нибудь может мне помочь с этой частью?

[Edit3] Наконец-то нашли полное решение :) Да здравствует отражатель!

var cf = (ClientSection) ConfigurationManager.GetSection("system.serviceModel/client");
foreach (ChannelEndpointElement endpoint in cf.Endpoints)
{
  if (endpoint.Name != "ContractSvc")
    continue;

  using (var wf = new BasicSvcClient<TRequest, TResponse>(
    new BasicHttpBinding("httpsDataEndpoint"),
    new EndpointAddress(endpoint.Address.ToString())))
  {
      //... call wf.GetReport()
  }
  break;
}

Ответы [ 3 ]

3 голосов
/ 24 мая 2009

«этот странный синтаксис, который использует .NET» - это фактически имя типа во время выполнения для универсального типа, привязанного к конкретным типам. Typename`n [[Type], ...] где n обозначает количество аргументов типа, содержащихся в вашем универсальном типе.

Как тогда выглядит конфигурация вашей конечной точки?

1 голос
/ 25 мая 2009

Почему бы вам не указать имя для вашего контракта в атрибуте ServiceContract:

[
ServiceContract
   (
    Namespace = "http://schema.company.com/messages/", 
    Name="MyBasicContract"
    )
]

Если вы не укажете имя в явном виде, по умолчанию будет указано полное имя вашего интерфейса в «странном синтаксисе, который использует .NET».

0 голосов
/ 26 мая 2009

Вы должны использовать стандартный способ определения универсальных типов в файле конфигурации, если вы хотите использовать контракт на обслуживание, такой как IBasicContract

Это записано в файле конфигурации следующим образом: IBasicContract`2 [TRequest, TResponse]

Я также ответил в своем блоге (www.lybecker.com/blog/), когда вы разместили вопрос на них тоже.

:-) Андерс Либекер

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...