Refactoring class to make it injectable/mockable - c#

I am really struggling to properly refactor my class so I can inject it.
This is the class I am talking about:
internal class OCRService : IDisposable, IOCRService
{
private const TextRecognitionMode RecognitionMode = TextRecognitionMode.Handwritten;
private readonly ComputerVisionClient _client;
public OCRService(string apiKey)
{
_client = new ComputerVisionClient(new ApiKeyServiceClientCredentials(apiKey))
{
Endpoint = "https://westeurope.api.cognitive.microsoft.com"
};
}
public async Task<List<Line>> ExtractTextAsync(byte[] image)
{
//Logic with _client here
}
}
I really don't know where to Initialize the ComputerVisionClient. I am thinking of the following options:
Make ComputerVisionClient a public property which can be set after injecting.
Putting the apikey in a config file and then read it in the constructor.
The problem is that I want to mock this service but when I mock it it still calls the constructor which connects to the ComputerVisionClient.

Depending on the rest of your architecture, you have a few options. The simplest is to inject the ComputerVisionClient (or IComputerVisionClient if you can create one) into the constructor, and mock it in your tests.
public class OCRService : IOCRService, IDisposable
{
public OCRService(IComputerVisionClient client)
{
_client = client;
}
}
If, for some reason, you must create the client in the constructor, you can create a factory and inject that:
internal class ComputerVisionClientFactory : IComputerVisionClientFactory
{
public GetClient(string apiKey)
{
return new ComputerVisionClient(new ApiKeyServiceClientCredentials(apiKey))
{
Endpoint = "https://westeurope.api.cognitive.microsoft.com"
};
}
}
// ...
internal class OCRService : IOCRService, IDisposable
{
public OCRService(string apiKey, IComputerVisionClientFactory clientFactory)
{
_client = clientFactory.GetClient(apiKey);
}
}
As #maccettura suggested, you can also further abstract away the apiKey by creating an IOCRServiceConfiguration that contains the logic for getting the key, and pass that into the constructor for either OCRService or ComputerVisionFactory, depending on your architecture. Naively:
internal class OCRServiceConfiguration : IOCRServiceConfiguration
{
public OCRServiceConfiguration(string apiKey)
{
ApiKey = apiKey;
}
public string ApiKey { get; }
}

Related

C# Injecting Specific Item from appSettings.json at runtime

How can I inject a specific setting (of possibly many) from an array appSettings.json in a C# .NET Core Web API, based on a runtime input value?
appSettings.json:
{
"SettingProfiles": [
{
"Name": "Profile1",
"SettingA": "SettingAValue1",
"SettingB": "SettingBValue1"
},
{
"Name": "Profile2",
"SettingA": "SettingAValue2",
"SettingB": "SettingBValue2"
}
...
}
Settings Classes:
public class Settings {
public List<SettingsProfile> SettingsProfiles { get; set; }
}
public class SettingsProfile {
public string Name { get; set; };
public string SettingA { get; set; };
public string SettingB { get; set; };
}
Service class:
public class MyService : IMyService {
private readonly SettingsProfile _Profile;
public MyService(SettingsProfile profile) {
_Profile = profile;
}
public void DoStuff() {
Console.WriteLine($"Setting A: {_SettingsProfile.SettingA}, Setting B: {_SettingsProfile.SettingB}")
}
}
The user will enter the setting name they want to apply. I am unsure how to do this if the service is configured in Startup.cs, at which point I don't yet have the setting to use.
I am understanding that "newing" the service would be bad practice, although that's the only way I can figure out how to make it work:
public class MyController {
private readonly Settings _Settings;
public MyController(Settings settings) {
_Settings = settings;
}
public IActionResult DoStuff(profileName) {
SettingsProfile profile = _Settings.Where(profile => profile.Name == profileName);
MyService service = new Service(profile);
}
}
I'm obviously missing something, but I've been watching YouTube videos on Dependency Injections and reading StackOverflow until my eyes bleed, and haven't figured it out yet. Can someone help me with a pattern that I should be following?
This is how I think it should work.
It will be a lot cleaner if you use another pattern: Factory.
interface ISettingServiceFactory{
MyService GetService(string profileName);
}
class SettingServiceFactory: ISettingServiceFactory
{
MyService GetService(string profileName){
}
}
Now you can implement GetService in two ways.
The first one is by creating new as you did in the controller and is not that bad as this is the purpose of the factory. In this way you kind of move that logic somewhere else.
A second one would be a bit uglier but something like this
interface ISettingServiceFactory{
MyService GetService(string profileName);
void SetCurrentProfile(SettingsProfile profile);
}
class SettingServiceFactory: ISettingServiceFactory
{
private IServiceProvider _serviceProvider;
private Settings _Settings;
public SettingServiceFactory(IServiceProvider serviceProvider,Settings settings){
_serviceProvider = serviceProvider;
_Settings = settings;
}
MyService GetService(string profileName){
var service = _serviceProvider.GetRequiredService<MyService>();
var profile = _Settings.Where(profile => profile.Name == profileName);
service.SetCurrentProfile(profile);
return service;
}
}
This second approach would be useful only if the implementation of MyService has a lot of other dependencies by itself and if you want to avoid new at any cost.
In both cases you will inject the factory in the controller
public MyController(ISettingServiceFactory settingServiceFactory) {
_settingServiceFactory= settingServiceFactory;
}
public IActionResult DoStuff(profileName) {
MyService service = _settingServiceFactory.GetService(profileName)
}

Passing IHttpClientFactory into class

If I have a controller class, and I want to pass it to a different namespace that handles my HTTP calls, such as in the below scenario, Main task calls TaskA() which calls TaskG(), do I need to pass it to TaskG via A like the below? Or is there someway to just create it in the namespace HttpClassFurtherDown without the calling classes needing to pass it.
namespace Controllers{
public class Drawings : ControllerBase
{
private IHttpClientFactory _client {get;set;}
public Drawings(IHttpClientFactory client)
{
_client=client;
}
[Route("api/Drawings")]
[HttpPost]
public async Task<ActionResult> PostAsync([FromBody] JsonFileContent[] content)
{
HttpExample e = new HttpExample(_client);
e.TaskA();
TaskB();
return Accepted($"Drawings/Job/{id}");
}
}}
namespace HttpClassExample{
public class HttpExample
{
private IHttpClientFactory _client {get;set;}
public HttpExample(IHttpClientFactory client)
{
_client=client;
}
public void TaskA()
{
DoSomeProcessing();
HttpClassExampleFurtherDown e = new HttpClassExampleFurtherDown(client);
e.TaskG();
}
}
}
namespace HttpClassExampleFurtherDown{
public class HttpExampleFurtherDown
{
private IHttpClientFactory _client {get;set;}
public HttpExampleFurtherDown(IHttpClientFactory client)
{
_client=client;
}
public void TaskG(client)
{
//Finally Using It Here. I want to avoid having to generate it at the controller level and pass it all the way down.
client.CreateClient();
client.SendAsync();
}
}
}
I want to avoid having to generate it at the controller level and pass it all the way down.
If following DIP then inject explicit dependencies where they are actually needed instead of tightly coupling to implementation concerns.
While I believe the example provided are oversimplified, here is what the example above should look like
Controllers.Drawings
namespace Controllers{
using HttpClassExample;
//...
public class Drawings : ControllerBase {
private readonly IHttpExample client;
public Drawings(IHttpExample client) {
this.client = client;
}
[Route("api/Drawings")]
[HttpPost]
public async Task<ActionResult> PostAsync([FromBody] JsonFileContent[] content) {
await client.TaskA();
TaskB();
return Accepted($"Drawings/Job/{id}");
}
}
}
HttpClassExample.HttpExample
namespace HttpClassExample{
using HttpClassExampleFurtherDown;
//...
public class HttpExample : IHttpExample{
private readonly IHttpExampleFurtherDown client;
public HttpExample(IHttpExampleFurtherDown client) {
this.client = client;
}
public async Task TaskA() {
DoSomeProcessing();
await client.TaskG();
}
//...
}
}
HttpClassExampleFurtherDown.HttpExampleFurtherDown
namespace HttpClassExampleFurtherDown{
public class HttpExampleFurtherDown : IHttpExampleFurtherDown {
private readonly IHttpClientFactory factory;
public HttpExampleFurtherDown(IHttpClientFactory factory) {
this.factory = factory;
}
public async Task TaskG() {
HttpClient client = factory.CreateClient();
//...
var response = await client.SendAsync();
//...
}
}
}
This assumes that a container is being used to manage the resolution and injection of dependency implementations based on their registered abstractions

How to add parameter to dependency injection via HostBuilder?

I have a simple HostBuilder setup:
private void BuildServices(IServiceCollection services) {
services.AddHttpClient<IApiClient, ApiClient>();
}
... where:
class ApiClient : IApiClient {
public ApiClient(HttpClient httpClient, ClientOptions options) {
// do stuff
}
}
I have a ClientOptions object configured. How do I register it so it will be injected into ApiClient?
I am going to give this a shot, but I am not 100% sure this is what you are asking for.
Typically, you would inject it in to your pipeline. So let's say you have ClientOptions set up this way:
ClientOptions.cs
public interface IClientOptions
{
int DeriveSomeValue();
}
public sealed class ClientOptions : IClientOptions
{
public int DeriveSomeValue() => 42;
}
You would then inject it in to your pipeline:
services.AddTransient<IClientOptions, ClientOptions>();
// or: services.AddScoped<IClientOptions, ClientOptions>();
// or: services.AddSingleton<IClientOptions, ClientOptions>();
Once that is done, you can inject it in to IApiClient like so:
public sealed class ApiClient : IApiClient {
private readonly IClientOptions _clientOptions;
public ApiClient(HttpClient httpClient, IClientOptions options) {
_clientOptions = options;
var myDerivedValue = _clientOptions.DeriveSomeValue();
}
}
I am hoping that's what you are asking, if not, please let me know and I can clarify.

How to resolve a dependency at runtime with parameters?

I have a simple interface ICloudStorageRepository which is implemented by two concrete classes:
public class AWSStorageRepository : ICloudStorageRepository
{
private readonly IS3Client _client;
public AWSStorageRepository(AWSCredentials credentials)
{
_client = new AWSStorageFactory(credentials).Create();
}
}
and:
public class AzureStorageRepository : ICloudStorageRepository
{
private readonly IAzureClient _client;
public AzureStorageRepository(AzureCredentials credentials)
{
_client = new AzureStorageFactory(credentials).Create();
}
}
then in my client I have a service:
public class CloudService
{
public CloudService(ICloudStorageRepository repository)
....
public Task<ListFiles> GetFiles(GetFilesInput input)
{
if(input.Provider == 'aws')
resolve with AWSStorageRepository
if(input.Provider == 'azure')
resolve with AzureStorageRepository
}
}
I'm using Castle Windsor as container, I'll get the credentials at runtime within the input parameter, just to use once, so I need to pass it as the argument dependency.
How can i do it in runtime without registering it on container?

How to inject WCF service client in ASP.Net core?

I have WCF service that I need to access from ASP.NET Core. I have installed WCF Connected Preview and created proxy successfully.
It created interface & client something like below
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.3.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IDocumentIntegration")]
public interface IDocumentIntegration
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IDocumentIntegration/SubmitDocument", ReplyAction="http://tempuri.org/IDocumentIntegration/SubmitDocumentResponse")]
[System.ServiceModel.FaultContractAttribute(typeof(ServiceReference1.FaultDetail), Action="http://tempuri.org/IDocumentIntegration/SubmitDocumentFaultDetailFault", Name="FaultDetail", Namespace="http://schemas.datacontract.org/2004/07/MyCompany.Framework.Wcf")]
System.Threading.Tasks.Task<string> SubmitDocumentAsync(string documentXml);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.3.0.0")]
public interface IDocumentIntegrationChannel : ServiceReference1.IDocumentIntegration, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.3.0.0")]
public partial class DocumentIntegrationClient : System.ServiceModel.ClientBase<ServiceReference1.IDocumentIntegration>, ServiceReference1.IDocumentIntegration
{
// constructors and methods here
}
The consumer class that calls the service looks like below
public class Consumer
{
private IDocumentIntegration _client;
public Consumer(IDocumentIntegration client)
{
_client = client;
}
public async Task Process(string id)
{
await _client.SubmitDocumentAsync(id);
}
}
How do I register the IDocumentIntegration with ConfigureServices method in Startup class?
I want to setup RemoteAddress & clientCredentials during the registration
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
// how do I inject DocumentIntegrationClient here??
var client = new DocumentIntegrationClient();
client.ClientCredentials.UserName.UserName = "myusername";
client.ClientCredentials.UserName.Password = "password";
client.Endpoint.Address = new EndpointAddress(urlbasedonenvironment)
}
Using the factory method overload seems suitable use case for it.
services.AddScoped<IDocumentIntegration>(provider => {
var client = new DocumentIntegrationClient();
// Use configuration object to read it from appconfig.json
client.ClientCredentials.UserName.UserName = Configuration["MyService:Username"];
client.ClientCredentials.UserName.Password = Configuration["MyService:Password"];
client.Endpoint.Address = new EndpointAddress(Configuration["MyService:BaseUrl"]);
return client;
});
Where your appsettings would look like
{
...
"MyService" :
{
"Username": "guest",
"Password": "guest",
"BaseUrl": "http://www.example.com/"
}
}
Alternatively, inject the Options via options pattern. Since the DocumentIntegrationClient is partial, you can create a new file and add a parameterized constructor.
public partial class DocumentIntegrationClient :
System.ServiceModel.ClientBase<ServiceReference1.IDocumentIntegration>, ServiceReference1.IDocumentIntegration
{
public DocumentIntegrationClient(IOptions<DocumentServiceOptions> options) : base()
{
if(options==null)
{
throw new ArgumentNullException(nameof(options));
}
this.ClientCredentials.Username.Username = options.Username;
this.ClientCredentials.Username.Password = options.Password;
this.Endpoint.Address = new EndpointAddress(options.BaseUrl);
}
}
And create a options class
public class DocumentServiceOptions
{
public string Username { get; set; }
public string Password { get; set; }
public string BaseUrl { get; set; }
}
and populate it from appsettings.json.
services.Configure<DocumentServiceOptions>(Configuration.GetSection("MyService"));

Categories

Resources