tags:

views:

833

answers:

3

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;
}
+1  A: 

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

flq
I know about the generic type "mangling" :)I have added the endpoint to the question.
Marcel Popescu
Maybe I am totally off the track, but your ClientBase does reference the IBasicContract, while you seem to make a definition for IContract which is, I presume, the inheritor of your IBasicContract? What if you specify the contract in your endpoint configuration in that "type mangling" fashion?
flq
It works, but it looks ugly as hell :)contract="BasicWorkflow.IBasicContract`2[[Company.CommonObj.Request, CommonObj, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f459617168919938],[Company.CommonObj.Response, CommonObj, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f459617168919938]]"
Marcel Popescu
In which case I answered your question? ;)
flq
Not really, I just replaced one ugliness with another :P I'll add +1 to the answer and let others decide. BTW, I found the complete solution - posting it :)
Marcel Popescu
Hmm... I forgot, others can't decide to mark it as the answer. Ok, since nobody else answered, yours it is :)
Marcel Popescu
A: 

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

Joe
The problem with that is that I would need to specify the name on the common interface; this doesn't help me because I have a program that calls 20 workflows, all of them derived from the same interface.
Marcel Popescu
A: 

Hi Marcel,

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

Thank you :) The problem with that would have been the fact that I reference several distinct workflows from the same program... they can't all use the same contract. Anyway, as I said above, I managed to solve the issue. Thanks again for taking the time to answer!
Marcel Popescu