0

I have a .NET 6 Console app with a Product class, which depends on IDatabase.

Product.cs

public class Product
{
    private readonly IDatabase databaseConnection;

    public string Name { get; set; }

    public decimal Price { get; set; }

    public string Category { get; set; }

    private int StockQuantity { get; set; }

    public Product(string name, decimal price, string category, int stockQuantity, IDatabase databaseConnection)
    {
        this.databaseConnection = databaseConnection;

        //Id = productId;
        Name = name;
        Price = price;
        Category = category;
        StockQuantity = stockQuantity;

        CreateProduct();
    }
    //some code
}

Questions:

  1. How should my Program.cs look like making sure I use DI?
  2. What would be a better approach to initialize the attributes if I do not want to pass it in a constructor?

Currently this is what I have in Program.cs, but this gives an error since Product also requires IDatabase dependency which I already added in the container:

Program.cs

var host = CreateHostBuilder(args).Build();
host.Run();

static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
        })
        .ConfigureServices((hostContext, services) =>
        {
            string connectionString = hostContext.Configuration.GetConnectionString("DefaultConnection");
            services.AddSingleton(new DatabaseConnection(connectionString));
            services.AddTransient(new Product("",4,"",4));
        });
}

Alternate

Here, instead of passing the attributes to constructor, I declared them in a method. This resolves the DI issue. But I am still not sure how to resolve the previous problem, and which approach would be better?

var host = CreateHostBuilder(args).Build();
var product = host.Services.GetRequiredService\();
product.SetProductDetails(1, "Sample Product", 49.99m, "Electronics", 100);
host.Run();
vernou
  • 6,818
  • 5
  • 30
  • 58
Vijul
  • 1
  • 2
  • Maybe you can give a try at the factory pattern. – vernou Jul 22 '23 at 20:02
  • Related: https://stackoverflow.com/questions/4835046/why-not-use-an-ioc-container-to-resolve-dependencies-for-entities-business-objec. The most scored answer talks about the Active Record Pattern, which is what your `Product` class seems to be implementing. – Steven Jul 25 '23 at 15:01

1 Answers1

0

For solving the first problem you can add Product to DI like this:

services.AddTransient(serviceProvider =>
        {
            var database = serviceProvider.GetRequiredService<IDatabase>();

            return new Product("something", 12, "something", 12, database);
        });

But it is not a good idea to deal with DI, the issue is when you create an instance manually for the DI then you have to care about object disposition which means the Product needs to implement IDisposable interface and dispose of the object once you are done with it.

If you values in the structure are static you can put them in the configuration (appsettings.json) and just inject IConfiguration to the constructor.

appsettings.json

 "Product": {
    "Name": "something",
    "Price": 12,
    "Category": "something",
    "StockQuantity": 12
  }

Product

public Product(IDatabase databaseConnection, IConfiguration configuration)
    {
        this.databaseConnection = databaseConnection;

        Name = configuration["Product:Name"];
        Price = configuration.GetValue<decimal>("Product:Price");
        //...

        CreateProduct();
    }

Program.cs

...
services.AddTransient<Product>();

Of course you can use IOptions pattern for mapping configurations.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0

Update

If your product attribute can vary I think it's better to add it to the DI and after getting an instance from DI you can setup it up.

public Product(IDatabase databaseConnection)
    {
        this.databaseConnection = databaseConnection;
    }

public void Setup(string name, decimal price, string category, int stockQuantity)
{
        Name = name;
        Price = price;
        Category = category;
        StockQuantity = stockQuantity;
}
...
// add to DI
services.AddTransient<Product>();

...
// resolve product into another services

public class SomeService
{
   // come from DI
   private readonly Product _product;
   
   public SomeService(Product product)
   {
    _product = product;

    //you can setup product here or in another methods
    _product.Setup("something", 12, "something", 12, database);
   }

}


sa-es-ir
  • 3,722
  • 2
  • 13
  • 31
  • Thank you for the solution. But Product is **not static**, user can add as many products. In this case, what would be a good approach if not initializing its attributes in the constructor? – Vijul Jul 23 '23 at 17:12
  • @Vijul So you can get benefit from DI by giving the ``IDatabase`` in ctor and then setup another method for evaluating the properties the good point here is object will be disposed of by DI. – sa-es-ir Jul 24 '23 at 03:35