[GH-ISSUE #63] Support for dependency injection in Models #36

Closed
opened 2026-05-05 11:00:33 -06:00 by gitea-mirror · 9 comments
Owner

Originally created by @MaxMommersteeg on GitHub (Aug 20, 2018).
Original GitHub issue: https://github.com/qmlnet/qmlnet/issues/63

Currently working on a simple todo application (https://github.com/MaxMommersteeg/Qml.Net.TodoApp) and trying to use a repository in my model. I tend to use the Models (as seen in the examples) as my controller (like a controller in mvc). Creating a controller/model without a parameterless constructor throws a System.MissingMethodException exception.

I setup my dependency injection as follows:

Program.cs

 // Dependency injection.
var serviceCollection = new ServiceCollection();

var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "todoApp.db" };
var connectionString = connectionStringBuilder.ToString();

serviceCollection.AddDbContext<TodoAppDbContext>(options => options.UseSqlite(connectionString));
serviceCollection.AddSingleton<ITodoItemRepository, TodoItemRepository>();

var serviceProvider = serviceCollection.BuildServiceProvider();

serviceProvider.GetService<TodoAppDbContext>();

using (var dbContext = serviceProvider.GetService<TodoAppDbContext>())
{
    await dbContext.EnsureSeedData();
}

TodoItemsController.cs

public class TodoItemsController
{
    private readonly ITodoItemRepository _todoItemRepository;

    private IList<TodoItemModel> _todoItems = new List<TodoItemModel>();

    public TodoItemsController(ITodoItemRepository todoItemRepository)
    {
        _todoItemRepository = todoItemRepository;
    }

    [NotifySignal]
    public IList<TodoItemModel> TodoItems
    {
        get { return _todoItems; }
        private set { _todoItems = value; }
    }

    public async Task AddTodoItem(string title)
    {
        await _todoItemRepository.Add(new Core.Entities.TodoItem
            {
                Title = title,
            })
            .ConfigureAwait(false);

        await UpdateTodoItems()
            .ConfigureAwait(false);
    }

    public async Task MarkAsDone(int todoItemId)
    {
        var todoItem = await _todoItemRepository.Get(todoItemId)
            .ConfigureAwait(false);
        if (todoItem == null)
        {
            return;
        }

        todoItem.CompletedAt = DateTime.UtcNow;

        await _todoItemRepository.Update(todoItem)
            .ConfigureAwait(false);

        await UpdateTodoItems()
            .ConfigureAwait(false);
    }

    public async Task DeleteTodoItem(int todoItemId)
    {
        await _todoItemRepository.Delete(todoItemId)
            .ConfigureAwait(false);

        await UpdateTodoItems()
            .ConfigureAwait(false);
    }

    private async Task UpdateTodoItems()
    {
        _todoItems = (await _todoItemRepository.GetAll().ConfigureAwait(false)).ToModel();
        this.ActivateSignal("todoItemsChanged");
    }
}

Exception

System.MissingMethodException: 'No parameterless constructor defined for this object.'

You can clone and run the TodoApp.FrontEnd project to reproduce the problem. Any ideas on how to handle dependency injection?

Originally created by @MaxMommersteeg on GitHub (Aug 20, 2018). Original GitHub issue: https://github.com/qmlnet/qmlnet/issues/63 Currently working on a simple todo application (https://github.com/MaxMommersteeg/Qml.Net.TodoApp) and trying to use a repository in my model. I tend to use the `Models` (as seen in the examples) as my controller (like a controller in mvc). Creating a controller/model without a parameterless constructor throws a `System.MissingMethodException` exception. I setup my dependency injection as follows: ### Program.cs ```csharp // Dependency injection. var serviceCollection = new ServiceCollection(); var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "todoApp.db" }; var connectionString = connectionStringBuilder.ToString(); serviceCollection.AddDbContext<TodoAppDbContext>(options => options.UseSqlite(connectionString)); serviceCollection.AddSingleton<ITodoItemRepository, TodoItemRepository>(); var serviceProvider = serviceCollection.BuildServiceProvider(); serviceProvider.GetService<TodoAppDbContext>(); using (var dbContext = serviceProvider.GetService<TodoAppDbContext>()) { await dbContext.EnsureSeedData(); } ``` ### TodoItemsController.cs ```csharp public class TodoItemsController { private readonly ITodoItemRepository _todoItemRepository; private IList<TodoItemModel> _todoItems = new List<TodoItemModel>(); public TodoItemsController(ITodoItemRepository todoItemRepository) { _todoItemRepository = todoItemRepository; } [NotifySignal] public IList<TodoItemModel> TodoItems { get { return _todoItems; } private set { _todoItems = value; } } public async Task AddTodoItem(string title) { await _todoItemRepository.Add(new Core.Entities.TodoItem { Title = title, }) .ConfigureAwait(false); await UpdateTodoItems() .ConfigureAwait(false); } public async Task MarkAsDone(int todoItemId) { var todoItem = await _todoItemRepository.Get(todoItemId) .ConfigureAwait(false); if (todoItem == null) { return; } todoItem.CompletedAt = DateTime.UtcNow; await _todoItemRepository.Update(todoItem) .ConfigureAwait(false); await UpdateTodoItems() .ConfigureAwait(false); } public async Task DeleteTodoItem(int todoItemId) { await _todoItemRepository.Delete(todoItemId) .ConfigureAwait(false); await UpdateTodoItems() .ConfigureAwait(false); } private async Task UpdateTodoItems() { _todoItems = (await _todoItemRepository.GetAll().ConfigureAwait(false)).ToModel(); this.ActivateSignal("todoItemsChanged"); } } ``` ### Exception `System.MissingMethodException: 'No parameterless constructor defined for this object.'` You can clone and run the `TodoApp.FrontEnd` project to reproduce the problem. Any ideas on how to handle dependency injection?
Author
Owner

@pauldotknopf commented on GitHub (Aug 21, 2018):

Set Qml.Net.TypeCreator.Current to your own implementation of ITypeCreator.

<!-- gh-comment-id:414517765 --> @pauldotknopf commented on GitHub (Aug 21, 2018): Set ```Qml.Net.TypeCreator.Current``` to your own implementation of ```ITypeCreator```.
Author
Owner

@MaxMommersteeg commented on GitHub (Aug 21, 2018):

Thanks, is there an example showing the intended use (registering an implementation to an interface.)?

<!-- gh-comment-id:414560361 --> @MaxMommersteeg commented on GitHub (Aug 21, 2018): Thanks, is there an example showing the intended use (registering an implementation to an interface.)?
Author
Owner

@pauldotknopf commented on GitHub (Aug 21, 2018):

var services = new ServiceCollection();
services.AddTransient<QmlModel>();
var provider = services.BuildServiceProvider();
TypeCreator.Current = TypeCreator.FromDelegate((type) => provider.GetRequiredService(type));
<!-- gh-comment-id:414626995 --> @pauldotknopf commented on GitHub (Aug 21, 2018): ```c# var services = new ServiceCollection(); services.AddTransient<QmlModel>(); var provider = services.BuildServiceProvider(); TypeCreator.Current = TypeCreator.FromDelegate((type) => provider.GetRequiredService(type)); ```
Author
Owner

@MaxMommersteeg commented on GitHub (Aug 22, 2018):

Sadly to no avail. The TodoAppDbContext throws an ObjectDisposedException. Issue can be reproduced by starting the app and adding a todo item using the textfield and button.

Not sure whether TodoAppDbContext is setup correctly for dependency injection yet. The ITodoItemRepository is correctly injected into the TodoItemsController class.

Program.cs

public class Program
{
    public static async Task Main(string[] args)
    {
        // Dependency injection.
        var serviceCollection = new ServiceCollection();

        var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "todoApp.db" };
        var connectionString = connectionStringBuilder.ToString();

        serviceCollection.AddDbContext<TodoAppDbContext>(options => options.UseSqlite(connectionString));
        serviceCollection.AddSingleton<ITodoItemRepository, TodoItemRepository>();
        serviceCollection.AddSingleton<TodoItemsController>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        // Seed database.
        using (var dbContext = serviceProvider.GetService<TodoAppDbContext>())
        {
            await dbContext.EnsureSeedData().ConfigureAwait(false);
        }

        QQuickStyle.SetStyle("Material");

        using (var application = new QGuiApplication(args))
        {
            using (var qmlEngine = new QQmlApplicationEngine())
            {
                QQmlApplicationEngine.RegisterType<TodoItemsController>("TodoApp");

                TypeCreator.Current = TypeCreator.FromDelegate((type) => serviceProvider.GetRequiredService(type));

                qmlEngine.Load("Main.qml");

                application.Exec();
            }
        }
    }
}
<!-- gh-comment-id:415125298 --> @MaxMommersteeg commented on GitHub (Aug 22, 2018): Sadly to no avail. The `TodoAppDbContext` throws an `ObjectDisposedException`. Issue can be reproduced by starting the app and adding a todo item using the textfield and button. Not sure whether `TodoAppDbContext` is setup correctly for dependency injection yet. The `ITodoItemRepository` is correctly injected into the `TodoItemsController` class. ### Program.cs ```csharp public class Program { public static async Task Main(string[] args) { // Dependency injection. var serviceCollection = new ServiceCollection(); var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "todoApp.db" }; var connectionString = connectionStringBuilder.ToString(); serviceCollection.AddDbContext<TodoAppDbContext>(options => options.UseSqlite(connectionString)); serviceCollection.AddSingleton<ITodoItemRepository, TodoItemRepository>(); serviceCollection.AddSingleton<TodoItemsController>(); var serviceProvider = serviceCollection.BuildServiceProvider(); // Seed database. using (var dbContext = serviceProvider.GetService<TodoAppDbContext>()) { await dbContext.EnsureSeedData().ConfigureAwait(false); } QQuickStyle.SetStyle("Material"); using (var application = new QGuiApplication(args)) { using (var qmlEngine = new QQmlApplicationEngine()) { QQmlApplicationEngine.RegisterType<TodoItemsController>("TodoApp"); TypeCreator.Current = TypeCreator.FromDelegate((type) => serviceProvider.GetRequiredService(type)); qmlEngine.Load("Main.qml"); application.Exec(); } } } } ```
Author
Owner

@pauldotknopf commented on GitHub (Aug 22, 2018):

Try making sure all your types are registered as a singleton.

<!-- gh-comment-id:415125714 --> @pauldotknopf commented on GitHub (Aug 22, 2018): Try making sure all your types are registered as a singleton.
Author
Owner

@MaxMommersteeg commented on GitHub (Aug 22, 2018):

I don't think making TodoAppDbContext a singleton would be a good practice.. Not sure how to continue from here. For sake of the example app I could write to a simple json file on desk I suppose.

<!-- gh-comment-id:415128690 --> @MaxMommersteeg commented on GitHub (Aug 22, 2018): I don't think making `TodoAppDbContext` a singleton would be a good practice.. Not sure how to continue from here. For sake of the example app I could write to a simple json file on desk I suppose.
Author
Owner

@pauldotknopf commented on GitHub (Aug 22, 2018):

When running desktop apps, scope practices are a little different that the per-request model of ASP.NET. I've seen (and used) a singleton model because there isn't a good location to create scope in desktop apps.

<!-- gh-comment-id:415129223 --> @pauldotknopf commented on GitHub (Aug 22, 2018): When running desktop apps, scope practices are a little different that the per-request model of ASP.NET. I've seen (and used) a singleton model because there isn't a good location to create scope in desktop apps.
Author
Owner

@MaxMommersteeg commented on GitHub (Aug 22, 2018):

I agree with you. So the default AddDbContext<TodoAppDbContext>() method sets the lifetime to Scoped. I tried setting it to singleton manually: serviceCollection.AddDbContext<TodoAppDbContext>(options => options.UseSqlite(connectionString), ServiceLifetime.Singleton);. Sadly it is giving me the same exception. For some reason TodoAppDbContext is still disposed.

<!-- gh-comment-id:415168252 --> @MaxMommersteeg commented on GitHub (Aug 22, 2018): I agree with you. So the default `AddDbContext<TodoAppDbContext>()` method sets the lifetime to `Scoped`. I tried setting it to `singleton` manually: `serviceCollection.AddDbContext<TodoAppDbContext>(options => options.UseSqlite(connectionString), ServiceLifetime.Singleton);`. Sadly it is giving me the same exception. For some reason `TodoAppDbContext` is still disposed.
Author
Owner

@MaxMommersteeg commented on GitHub (Sep 1, 2018):

Stupid issue, totally my mistake:

// Seed database.
using (var dbContext = serviceProvider.GetService<TodoAppDbContext>())
{
    await dbContext.EnsureSeedData().ConfigureAwait(false);
}

Above code was the problem. Setting ServiceLifetime.Singleton was correct, but retrieving the TodoAppDbContext in a using disposes (of course) the object after use. See current Program.cs in my repository for the working code.

<!-- gh-comment-id:417852955 --> @MaxMommersteeg commented on GitHub (Sep 1, 2018): Stupid issue, totally my mistake: ```csharp // Seed database. using (var dbContext = serviceProvider.GetService<TodoAppDbContext>()) { await dbContext.EnsureSeedData().ConfigureAwait(false); } ``` Above code was the problem. Setting `ServiceLifetime.Singleton` was correct, but retrieving the `TodoAppDbContext` in a using disposes (of course) the object after use. See current `Program.cs` in my repository for the working code.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/qmlnet#36
No description provided.