Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheriting from a generic contract in WCF

Tags:

c#

generics

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;
}
like image 791
Marcel Popescu Avatar asked May 24 '09 15:05

Marcel Popescu


People also ask

Can one service contract inherit another service contract?

Service contract interfaces can derive from each other, enabling you to define a hierarchy of contracts. However, the ServiceContract attribute is not inheritable: [AttributeUsage( Inherited = false ,...)] public sealed class ServiceContractAttribute : Attribute {...}

What is used if you expect the service to accept and return inherited types?

If you expect the service to accept and return an inherited type then we use the knowntype attribute.

What is the KnownType attribute in WCF?

The KnownTypeAttribute class allows you to specify, in advance, the types that should be included for consideration during deserialization.

Which type of contract is applicable to interface in WCF?

The Service Contract declares an interface in the WCF service for the client to get access to the interface. The Operation Contract declares a function inside the interface, the client will call this function.


2 Answers

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

like image 150
flq Avatar answered Oct 05 '22 03:10

flq


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

like image 20
Joe Avatar answered Oct 05 '22 05:10

Joe