Marcel Popescu
Marcel Popescu

Reputation: 3351

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

Upvotes: 7

Views: 4081

Answers (3)

Reputation:

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

Upvotes: 0

to StackOverflow
to StackOverflow

Reputation: 124794

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".

Upvotes: 1

flq
flq

Reputation: 22859

"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?

Upvotes: 3

Related Questions