1

I wrote a WCF client that I want to consume in my asp.net core service (the client was written based on this post):

public class ExternalCompanyClient : IExternalCompanyClient
{
    public async Task<string> CallAsync() 
    {
        BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.None);
        EndpointAddress endpointAddress = new EndpointAddress(new Uri("http://........."));
        ChannelFactory<IExternalCompanyService> factory = new ChannelFactory<IExternalCompanyService>(basicHttpBinding, endpointAddress);
        IExternalCompanyService serviceProxy = factory.CreateChannel();

        ((ICommunicationObject)serviceProxy).Open();
        var opContext = new OperationContext((IClientChannel)serviceProxy);
        var prevOpContext = OperationContext.Current; 
        OperationContext.Current = opContext;

        ExternalCompanyResponse ret = null;

        var request = GetRequest();

        try
        {
            ret = await serviceProxy.GetCompanyInfoAsync(request);

            factory.Close();
            ((ICommunicationObject)serviceProxy).Close();
        }
        catch (MessageSecurityException ex)
        {
            throw;
        }
        catch (Exception ex)
        {
            throw;
        }
        finally
        {
            WCFHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
            OperationContext.Current = prevOpContext;
        }

        return ret;
    }
} 

After that, I just need to register this service via dependency injection:

public static IServiceCollection AddExternalCompanyClient(this IServiceCollection services) =>
        services.AddTransient<IExternalCompanyClient, ExternalCompanyClient>();

Now what I want to do is to move on all common code into separate ServiceCaller class and use this class for all other WCF clients for the usability purposes.

So, the ServiceCaller is as follows (I added the comments to see the differences in CallAsync method):

internal class ServiceCaller<TInterface, TIn, TOut> where TInterface : IService<TIn, TOut>
{
    private readonly Uri _uri;

    public ServiceCaller(Uri uri)
    {
        var attributes = typeof(TInterface).GetCustomAttributes(typeof(ServiceContractAttribute), true);

        if (attributes.Length == 0)
        {
            throw new ArgumentException($"Interface {typeof(TInterface).Name} does not have ServiceContract attribute");
        }

        _uri = uri;
    }

    public async Task<TOut> CallAsync(TIn request, TOut valueIfError = default(TOut))
    {
        BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.None);   
        EndpointAddress endpointAddress = new EndpointAddress(_uri);  // uri is passed into via a constructor
        ChannelFactory<TInterface> factory = new ChannelFactory<TInterface>(basicHttpBinding, endpointAddress);
        TInterface serviceProxy = factory.CreateChannel();  // now the serviceProxy is of type of TInterface

        ((ICommunicationObject)serviceProxy).Open();
        var opContext = new OperationContext((IClientChannel)serviceProxy);
        var prevOpContext = OperationContext.Current;
        OperationContext.Current = opContext;

        TOut ret = default(TOut);   // the return has TOut type now

        try
        {
            ret = await serviceProxy.InvokeAsync(request);   // and here I invoke the interface method

            factory.Close();
            ((ICommunicationObject)serviceProxy).Close();
        }
        catch (MessageSecurityException ex)
        {
            throw;
        }
        catch (Exception ex)
        {
            throw;
        }
        finally
        {             
            CloseCommunicationObjects.CloseOrAbort((ICommunicationObject)serviceProxy, factory);
            OperationContext.Current = prevOpContext;
        }

        return ret;
    }
}

So, I added IService interface:

public interface IService<TIn, TOut>
{
    Task<TOut> InvokeAsync(TIn request);
}

And, finally, my new simplified ExternalCompanyClient:

public class ExternalCompanyClient : IExternalCompanyClient, IService<ExternalCompanyRequest, ExternalCompanyResponse>
{
    private readonly IExternalCompanyService _externalCompanyService;

    public ExternalCompanyClient(IExternalCompanyService externalCompanyService)
    {
        _externalCompanyService = externalCompanyService;
    }

    private ExternalCompanyRequest GetRequest() =>  // some logic goes here   

    public async Task<string> GetCompanyInfoAsync()
    {
        var request = GetRequest();

        var serviceCaller = new ServiceCaller<ExternalCompanyClient, ExternalCompanyRequest, ExternalCompanyResponse>(
            new System.Uri("http://......................."));

        var result = await serviceCaller.CallAsync(request);

        return result;
    }

    public Task<ExternalCompanyResponse> InvokeAsync(ExternalCompanyRequest request)
    {
        return _externalCompanyService.GetCompanyInfoAsync(request);
    }
}

How should I register dependencies for the ExternalCompanyClient in that case?

Dmitry Stepanov
  • 2,776
  • 8
  • 29
  • 45

1 Answers1

1

Register the class and use the factory delegate when registering the individual interfaces

public static IServiceCollection AddExternalCompanyClient(this IServiceCollection services) =>
    services
        .AddTransient<ExternalCompanyClient>()
        .AddTransient<IExternalCompanyClient>(sp => sp.GetRequiredService<ExternalCompanyClient>())
        .AddTransient<IService<ExternalCompanyRequest, ExternalCompanyResponse>>(sp => 
            sp.GetRequiredService<ExternalCompanyClient>()
        );

resolving either of the interfaces will provided the derived class.

Note however that based on your shown refactor, ExternalCompanyClient is tightly coupled to ServiceCaller.

var serviceCaller = new ServiceCaller<ExternalCompanyClient, ExternalCompanyRequest, ExternalCompanyResponse>(
        new System.Uri("http://......................."));

Ideally, that should also be refactored out into its own abstraction

public interface IServiceCaller<TInterface, TIn, TOut> 
    where TInterface : IService<TIn, TOut> {
    Task<TOut> CallAsync(TIn request, TOut valueIfError = default(TOut));
}

internal class ServiceCaller<TInterface, TIn, TOut> : IServiceCaller<TInterface, TIn, TOut> 
    where TInterface : IService<TIn, TOut> {
    //...
}

and applied accordingly

public class ExternalCompanyClient : IExternalCompanyClient, IService<ExternalCompanyRequest, ExternalCompanyResponse> {
    private readonly IExternalCompanyService _externalCompanyService;
    private readonly IServiceCaller<ExternalCompanyClient, ExternalCompanyRequest, ExternalCompanyResponse> serviceCaller;

    public ExternalCompanyClient(IExternalCompanyService externalCompanyService, IServiceCaller<ExternalCompanyClient, ExternalCompanyRequest, ExternalCompanyResponse> serviceCaller) {
        _externalCompanyService = externalCompanyService;
        this.serviceCaller = serviceCaller
    }

    //...


    public async Task<string> GetCompanyInfoAsync() {
        var request = GetRequest();

        var result = await serviceCaller.CallAsync(request);

        return result;
    }

    //...
}

making sure to register the new abstraction.

This results in the previous example being changed to

public static IServiceCollection AddExternalCompanyClient(this IServiceCollection services) =>
    services
        .AddTransient<ExternalCompanyClient>()
        .AddTransient<IExternalCompanyClient>(sp => sp.GetRequiredService<ExternalCompanyClient>())
        .AddTransient<IService<ExternalCompanyRequest, ExternalCompanyResponse>>(sp => 
            sp.GetRequiredService<ExternalCompanyClient>()
        )
        .RegisterTransient<IServiceCaller<ExternalCompanyClient, ExternalCompanyRequest, ExternalCompanyResponse>> (sp => 
            new ServiceCaller<ExternalCompanyClient, ExternalCompanyRequest, ExternalCompanyResponse>(
                new System.Uri("http://......................."))
        );
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Many thanks for your help. But when I register the interfaces using the factory delegate, I get an error: `Unable to resolve service for type 'ExternalCompanyClient' while attempting to activate 'ExternalCompanyClient'`. I didn't refactor `ServiceCaller` so far (so, I just use the first snippet of your code - `AddExternalCompanyClient` method). – Dmitry Stepanov Sep 17 '19 at 08:28
  • @DmitryStepanov recheck the registration code. It should not be trying to resolve the **class** while trying to activiate the same **class**. You may have a typo somewhere. – Nkosi Sep 17 '19 at 10:05
  • Sorry, I gave you the wrong error. The error is as follows: `Unable to resolve service for type 'IExternalCompanyService' while attempting to activate 'ExternalCompanyClient'`. `IExternalCompanyService` is an interface that was automatically generated when I added WCF service to the project. Should I register it somehow? – Dmitry Stepanov Sep 17 '19 at 10:49
  • @DmitryStepanov Yes you would need to register all dependencies with the container so that it know how to resolve them. – Nkosi Sep 17 '19 at 10:49
  • Yes. I registered them and no more dependencies errors. While I still have another errors in `ServiceCaller` which are not due to dependencies I accept your answer. Thanks a lot. – Dmitry Stepanov Sep 17 '19 at 11:19
  • I'm stuck with `ServiceCaller` class, so I've asked another question [here](https://stackoverflow.com/questions/57976249/write-a-helper-class-to-invoke-wcf-services-in-asp-net-core) – Dmitry Stepanov Sep 17 '19 at 14:19