Inheriting from a generic contract in WCF

More WCF woes... :)

All my workflows implement the same 3 methods. After a lot of copy and paste, I decided to make them inherit from the same interface:

[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);
}

I have also decided to create a common implementation of the service client:

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

The problem is when I try to use this:

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

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

I am always getting an error saying that it cannot find the configuration for contract IBasicContract, in that weird syntax that .NET uses:

Could not find default endpoint element that references contract 'BasicWorkflow.IBasicContract`2...

I tried doing this:

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

It doesn't help - it's still also looking for that specific contract.

I know that the ServiceContract attribute has a ConfigurationName parameter, but I cannot use that at compile time, because I have many workflows I'm calling from the same program (and therefore many configuration entries). Is there a way to set the ConfigurationName at runtime? I thought that this is what the ClientBase constructor was supposed to do, but apparently not.

[Edit] This is the endpoint in the .config file, I don't believe it's very helpful in this case:

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

[Edit2] Ok... I found a way that's working, though I'm still not completely satisfied with it:

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

The only problem I have now is that I would prefer to retrieve the endpoint address from the .config file (using the actual contract name, like IContract). Anybody who can help me with that part?

[Edit3] Finally found the complete solution :) Long live Reflector!

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

"that weird syntax that .NET uses" is actually the type name at runtime for a generic type bound to specific types. Typename`n[[Type],...] where n denotes the number of type arguments contained in your generic type.

How does your endpoint configuration then look like?


Why don't you specify a name for your contract in the ServiceContract attribute:

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

If you don't explicitlly specify a name, it will default to the qualified name of your interface in "that weird syntax that .NET uses".


You must use the standard way of defining generic types in configuration file if you want to use a service contract like IBasicContract

It is written in the configuration file like this: IBasicContract`2[TRequest,TResponse]

I also replied on my blog (www.lybecker.com/blog/) as you posted the question their too.

:-) Anders Lybecker

链接地址: http://www.djcxy.com/p/42664.html

上一篇: 当涉及到绑定时,如何覆盖NSError表示?

下一篇: 从WCF中的通用契约继承