I'm working on a .Net Core Console Application with two classes(GenerateCsv,GenerateTxt) which inherit from the same interface (GenerateFile). They Both take IReadFile,IWriteFile interfaces as constructor parameters.
I want to use Dependency Injection to create the right class to generate a file and return a string value when finished.
static void Main()
{
string result="Processed Nothing";
Console.WriteLine("To Generate: \n");
Console.WriteLine("1) English CSV File Type 1 \n"+
"2) Englsih Txt File Type 2 \n" +
"3) To Exit Type 3 \n");
var userInput = Convert.ToInt32(Console.ReadLine());
if (userInput == 10)
Environment.Exit(0);
var service = BuildDependencies.Build();
Console.WriteLine("Started Generating");
Console.WriteLine("Working, please wait");
switch (userInput)
{
case 1:
result = service.GetService<IGenerateFile>().GenerateFile();
break;
case 2:
result = service.GetService<IGenerateFile>().GenerateFile();
break;
}
Console.WriteLine(result);
class BuildDependencies
using Microsoft.Extensions.DependencyInjection;
public static class BuildDependencies
{
public static ServiceProvider Build()
{
var services = new ServiceCollection()
.AddTransient<IReadFile, ReaFile>()
.AddTransient<IWriteFile, WriteFile>()
.AddTransient<IGenerateFile,GenerateCsv>()
.AddTransient<IGenerateFile,GenerateTxt>()
.BuildServiceProvider();
return services;
}
You should replace the switch(userinput) block with a call to a factory class.
That class's contract might look something like:
interface IFileGeneratorFactory
{
// I used int because it's in your example code, but an enum would be a stronger contract
IGenerateFile GetFileGenerator(int userInput);
}
and the implementation like:
class FileGeneratorFactory : IFileGeneratorFactory
{
readonly IServiceProvider serviceProvider;
public FileGeneratorFactory(IServiceProvider serviceProvider)
=> this.serviceProvider = serviceProvider;
public IGenerateFile GetFileGenerator(int userInput)
{
switch (userInput)
{
// The Factory has explicit knowledge of the different generator types,
// but returns them using a common interface. The consumer remains
// unconcerned with which exact implementation it's using.
case 1:
return serviceProvider.GetService<GenerateCsv>();
case 2:
return serviceProvider.GetService<GenerateTxt>();
default:
throw new InvalidOperationException($"No generator available for user input {userInput}");
}
}
}
Register the classes like:
var services = new ServiceCollection()
.AddTransient<GenerateCsv>()
.AddTransient<GenerateTxt>()
.AddTransient<IFileGeneratorFactory, FileGeneratorFactory>();
Then, consume like:
IGenerateFile fileGenerator = service
.GetService<IFileGeneratorFactory>()
.GetFileGenerator(userInput);
string result = fileGenerator.GenerateFile();
This factory implementation is pretty simplistic. I wrote it that way to keep it approachable, but I highly recommend looking at the more elegant examples in the Simple Injector docs. Since those examples target Simple Injector, and not ASP.Net DI, they might need some tweaking to work for you--but the basic patterns should work for any DI framework.
You could create two interfaces
interface IGenerateCsv : IGenerateFile{}
interface IGenerateTxt: IGenerateFile{}
then register both with your DI provider:
public static ServiceProvider Build()
{
var services = new ServiceCollection()
.AddTransient<IReadFile, ReaFile>()
.AddTransient<IWriteFile, WriteFile>()
.AddTransient<IGenerateCsv ,GenerateCsv>()
.AddTransient<IGenerateTxt,GenerateTxt>()
.BuildServiceProvider();
return services;
}
Then you could create another class which has both instances and decides which to call. Then inject that class into your consumer.
enum OutputType
{
Csv,
Text
}
interface IFileGenerator
{
void GenerateFile(OutputType outputType);
}
class FileGenerator : IFileGenerator
{
// inject both or your file services into the constructor
public void GenerateFile(OutputType outputType)
{
switch(outputType)
{
// call the correct service here
}
}
Then register IFileGenerator
public static ServiceProvider Build()
{
var services = new ServiceCollection()
.AddTransient<IReadFile, ReaFile>()
.AddTransient<IWriteFile, WriteFile>()
.AddTransient<IGenerateCsv ,GenerateCsv>()
.AddTransient<IGenerateTxt, GenerateTxt>()
.AddTransient<IFileGenerator, FileGenerator>()
.BuildServiceProvider();
return services;
}
And do something like this in your consumer class:
class Consumer
{
private readonly IFileGenerator fileGenerator;
Consumer(IFileGenerator, fileGenerator)
{
this.fileGenerator = fileGenerator;
}
public void SomeMethod(string userInput)
{
switch(userInput)
{
case 1:
fileGenerator.GenerateFile(OutputType.Csv);
break;
case 2:
fileGenerator.GenerateFile(OutputType.Text);
break;
default:
break;
}
}
}
That is assuming you have some kind of consumer class. If you're just working in the Main method, do something like this:
var serviceProvider = BuildDependencies.Build();
var fileGenerator = serviceProvider.GetService<IFileGenerator>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With