Search code examples
c#dependency-injectionservicefactory-pattern

Factory pattern with dependency injection, serviceProvider always return null


I tried to follow the example from https://chaitanyasuvarna.wordpress.com/2021/03/21/factory-pattern-di-in-net-core/ but i didn't use a classic console application, I created a .Net core service project in VS.

Please find below the files with their source code:

IShape.cs

public interface IShape
{
    public void GetInputValues();
    public void DisplaySurfaceArea();
    public void DisplayVolume();
}

Sphere.cs

public class Sphere : IShape
{
    public decimal Radius { get; set; }

    public void GetInputValues()
    {
        Console.WriteLine("Radius : ");
        Radius = decimal.Parse(Console.ReadLine());
    }

    public void DisplaySurfaceArea()
    {
        Console.WriteLine("Surface Area of the sphere is :" + (4 * 3.14m * Radius * Radius));
    }

    public void DisplayVolume()
    {
        Console.WriteLine("Volume of the sphere is :" + (4 / 3 * 3.14m * Radius * Radius * Radius));
    }
}

Cube.cs

public class Cube : IShape
{
    public decimal Side { get; set; }

    public void GetInputValues()
    {
        Console.WriteLine("Side : ");
        Side = decimal.Parse(Console.ReadLine());
    }

    public void DisplaySurfaceArea()
    {
        Console.WriteLine("Surface Area of the Cube is :" + (6 * Side * Side));
    }

    public void DisplayVolume()
    {
        Console.WriteLine("Volume of the Cube is :" + (Side * Side * Side));
    }
}

ShapeFactory.cs

public class ShapeFactory : IShapeFactory
{
    private readonly IServiceProvider serviceProvider;


    public ShapeFactory(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public IShape GetShape(ShapeEnum shapeEnum)
    {
        switch (shapeEnum)
        {
            case ShapeEnum.Cube:
                return (IShape)this.serviceProvider.GetService(typeof(Cube));
            case ShapeEnum.Sphere:
                return (IShape)this.serviceProvider.GetService(typeof(Sphere));
            default:
            throw new ArgumentOutOfRangeException(nameof(shapeEnum), shapeEnum, $"Shape of {shapeEnum} is not supported.");
        }
    }
}

public interface IShapeFactory
{
    public IShape GetShape(ShapeEnum shapeEnum);
}

ShapeCalculationService.cs

public class ShapeCalculationService : IShapeCalculationService
{
    private readonly IShapeFactory _shapeFactory;
    private IShape _shape;

    public ShapeCalculationService(IShapeFactory shapeFactory)
    {
        this._shapeFactory = shapeFactory;
    }

    public void CalculateShapeMeasurements()
    {
        this._shape = GetShapeFromUser();
        this._shape.GetInputValues();
        this._shape.DisplaySurfaceArea();
        this._shape.DisplayVolume();
    }

    private IShape GetShapeFromUser()
    {
        Console.WriteLine("Enter the serial no. for the shape you want to choose :");
        Console.WriteLine("1. Cube");
        Console.WriteLine("2. Sphere");
        var serialNumber = int.Parse(Console.ReadLine());

        switch (serialNumber)
        {
            case 1:
                return _shapeFactory.GetShape(ShapeEnum.Cube);
            case 2:
                return _shapeFactory.GetShape(ShapeEnum.Sphere);
            default:
                throw new ArgumentOutOfRangeException("Invalid input.");
        }
    }
}

public interface IShapeCalculationService
{
    public void CalculateShapeMeasurements();
}

worker.cs

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IShapeCalculationService shapeCalculationService;
    public Worker(ILogger<Worker> logger, IShapeCalculationService shapeCalculationService)
    {
        _logger = logger;
        this.shapeCalculationService = shapeCalculationService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            
            shapeCalculationService.CalculateShapeMeasurements();
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);
        }
    }
}

Program.cs

using TestFactory;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext,services) =>
    {
        services.AddTransient<IShapeFactory, ShapeFactory>();
        services.AddTransient<IShapeCalculationService, ShapeCalculationService>();
        services.AddScoped<Sphere>().AddScoped<IShape, Sphere>(s=>s.GetService<Sphere>());
        services.AddScoped<Cube>().AddScoped<IShape, Cube>(s => s.GetService<Cube>()); ;
        
        services.AddHostedService<Worker>();
    })
    .Build();

await host.RunAsync();

While going through with the debugger, I found that
public IShape GetShape(ShapeEnum shapeEnum) always returns null.

Please can you advice what I am doing wrong, and how I can correct it and make the example work.


Solution

  • You can simplify your solution by using delegate in DI, like this:

    {
        public delegate IShape ShapeResolver(int serialNumber);
    }
    

    then in program.cs:

    services.AddScoped<Sphere>();
    services.AddScoped<Cube>();
    
     services.AddTransient<ShapeResolver>(serviceProvider => key =>
                {
                    switch (key)
                    {
                        case 1:
                            return this.serviceProvider.GetRequiredService<Cube>();
    
                        case 2:
                            return this.serviceProvider.GetRequiredService<Sphere>();
    
                        default:
                            throw new KeyNotFoundException();
                    }
    

    ShapeCalculationService will look something like this:

    public class ShapeCalculationService : IShapeCalculationService
    {
        private readonly ShapeResolver _ShapeResolver;
        private IShape _shape;
    
        public ShapeCalculationService(ShapeResolver shapeResolver)
        {
            this._ShapeResolver= shapeResolver;
        }
    
        public void CalculateShapeMeasurements()
        {
            this._shape = GetShapeFromUser();
            this._shape.GetInputValues();
            this._shape.DisplaySurfaceArea();
            this._shape.DisplayVolume();
        }
    
        private IShape GetShapeFromUser()
        {
            Console.WriteLine("Enter the serial no. for the shape you want to choose :");
            Console.WriteLine("1. Cube");
            Console.WriteLine("2. Sphere");
            var serialNumber = int.Parse(Console.ReadLine());
    
            return _ShapeResolver(serialNumber);
        }
    }