Search code examples
asp.net-coreblazordbcontext

InvalidOperationException: Cannot provide a value for property '_companyRepo'


I am building an ASP.NET Core 8.0 Blazor solution. This solution has 3 projects. 1st is an iStar.Framework.dll project that contains Model Classes. 2nd is iStar.Framework.ModelIRepos.dll that contains Interface repository classes. 3rd is iStar.Framework.Application.dll which contains repository classes to perform database operations.

iStar.Framework.dll has a model class named Company.

using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;

namespace iStar.Framework.Models
{
    public class Company
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [Required]
        [StringLength(150)]
        public string Name { get; set; } = string.Empty;

        [DataType(DataType.Url)]
        public string? NavBarLogoUrl {  get; set; }

        public bool UseNavBarLogoUrl { get; set; } = true;



        /// <summary>
        /// A collection of workplaces managed by this company
        /// </summary>
        public virtual ICollection<WorkStation> Workstations { get; set; } = new HashSet<WorkStation>();

    }
}

It also has an interface named IModelRepo

namespace iStar.Framework
{
    public interface IModelRepo<T> where T : class
    {
        /// <summary>
        /// GetOne a list of all instances of the <typeparamref name="T"/> satisfying the set of values passed through parameters
        /// </summary>
        /// <returns>List<typeparamref name="T"/></returns>
        Task<List<T>?> GetAllAsync();

        /// <summary>
        /// GetOne a list of all instances of the <typeparamref name="T"/> satisfying the set of values passed through parameters
        /// </summary>
        /// <returns>List<typeparamref name="T"/></returns>
        Task<List<T>?> GetActiveOnly();

        /// <summary>
        /// GetOne an instance of <typeparamref name="T"/> with provided primary key value <paramref name="id"/>
        /// </summary>
        /// <param name="id">Primary Key value</param>
        /// <returns></returns>
        Task<T>? GetOneAsync(int id);

        /// <summary>
        /// GetOne a list of all the instances contining the string <paramref name="name"/> in its name value.
        /// </summary>
        /// <param name="name"></param>
        /// <returns>list of <typeparamref name="T"/></returns>
        Task<List<T>?> GetAllByNameAsync(string name);

        /// <summary>
        /// Adds <paramref name="item"/> of type <typeparamref name="T"/> to the end of relevant database table
        /// </summary>
        /// <param name="item">An instance of <typeparamref name="T"/></param>
        /// <returns></returns>
        Task AddAsync(T item);

        /// <summary>
        /// Updates the changes to the database made to the <paramref name="item"/> of type  <typeparamref name="T"/>
        /// </summary>
        /// <param name="model">An already saved instance of <typeparamref name="T"/> with changes</param>
        /// <returns></returns>
        Task UpdateAsync(T item);

        /// <summary>
        /// Attempts to perform a dellete operation on database against an instances of <typeparamref name="T"/> with provided primary key value <paramref name="id"/>
        /// </summary>
        /// <param name="id">Primary key value of the instance of <typeparamref name="T"/> to be delleted</param>
        /// <returns></returns>
        Task DeleteAsync(int id);

        /// <summary>
        /// Attempts to dellete the provided instance of <typeparamref name="T"/>
        /// </summary>
        /// <param name="item">An instance of <typeparamref name="T"/> to be delleted</param>
        /// <returns></returns>
        Task DeleteAsync(T item);



        /// <summary>
        /// GetOne a list of all instances of the <typeparamref name="T"/> satisfying the set of values passed through parameters
        /// </summary>
        /// <param name="all">Returns all instances if set to True</param>
        /// <param name="ActiveOnly">Returns only active instances</param>
        /// <returns>List<typeparamref name="T"/></returns>
        List<T>? Get(bool all = true, bool ActiveOnly = false);

        /// <summary>
        /// GetOne an instance of <typeparamref name="T"/> with provided primary key value <paramref name="id"/>
        /// </summary>
        /// <param name="id">Primary Key value</param>
        /// <returns></returns>
        T? GetOne(int id);

        /// <summary>
        /// GetOne a list of all the instances contining the string <paramref name="name"/> in its name value.
        /// </summary>
        /// <param name="name"></param>
        /// <returns>list of <typeparamref name="T"/></returns>
        List<T>? GetAllByName(string name);

        /// <summary>
        /// Adds <paramref name="item"/> of type <typeparamref name="T"/> to the end of relevant database table
        /// </summary>
        /// <param name="item">An instance of <typeparamref name="T"/></param>
        /// <returns></returns>
        void Add(T item);

        /// <summary>
        /// Updates the changes to the database made to the <paramref name="item"/> of type  <typeparamref name="T"/>
        /// </summary>
        /// <param name="model">An already saved instance of <typeparamref name="T"/> with changes</param>
        /// <returns></returns>
        void Updatec(T item);

        /// <summary>
        /// Attempts to perform a dellete operation on database against an instances of <typeparamref name="T"/> with provided primary key value <paramref name="id"/>
        /// </summary>
        /// <param name="id">Primary key value of the instance of <typeparamref name="T"/> to be delleted</param>
        /// <returns></returns>
        void Delete(int id);

        /// <summary>
        /// Attempts to dellete the provided instance of <typeparamref name="T"/>
        /// </summary>
        /// <param name="item">An instance of <typeparamref name="T"/> to be delleted</param>
        /// <returns></returns>
        void Delete(T item);



    }
}

iStar.Framework.IModelRepo has an interface for Company

public interface ICompanyRepo : IModelRepo<Company> { }

iStar.Framework.Application has repository class for Company

using iStar.Framework.Application.Data;
using iStar.Framework.ModelIRepos;
using iStar.Framework.Models;
using Microsoft.EntityFrameworkCore;

namespace iStar.Framework.Application.Repositories
{
    public class CompanyRepo : ICompanyRepo
    {
        private readonly IDbContextFactory<FrameworkDbContext> _contextFactory;

        public CompanyRepo(IDbContextFactory<FrameworkDbContext> contextFactory)
        {
            _contextFactory = contextFactory;
        }

        public void Add(Company item)
        {
            throw new NotImplementedException();
        }

        public async Task AddAsync(Company item)
        {
            using var context = _contextFactory.CreateDbContext();
            context.Companies.Add(item);
            await context.SaveChangesAsync();

        }

        public void Delete(int id)
        {
            throw new NotImplementedException();
        }

        public void Delete(Company item)
        {
            throw new NotImplementedException();
        }

        public Task DeleteAsync(int id)
        {
            throw new NotImplementedException();
        }

        public Task DeleteAsync(Company item)
        {
            throw new NotImplementedException();
        }

        public List<Company>? Get(bool all = true, bool ActiveOnly = false)
        {
            throw new NotImplementedException();
        }

        public Task<List<Company>?> GetActiveOnly()
        {
            throw new NotImplementedException();
        }

        public async Task<List<Company>?> GetAllAsync()
        {
            using var context = _contextFactory.CreateDbContext();
            var lst = await context.Companies.ToListAsync();
            return lst;
        }

        public List<Company>? GetAllByName(string name)
        {
            throw new NotImplementedException();
        }

        public Task<List<Company>?> GetAllByNameAsync(string name)
        {
            throw new NotImplementedException();
        }

        public Company GetOne(int id)
        {
            throw new NotImplementedException();
        }

        public async Task<Company>? GetOneAsync(int id)
        {
            using var context = _contextFactory.CreateDbContext();
            var cpy = await context.Companies
                .Where(c => c.Id == id)
                .FirstOrDefaultAsync();
            if (cpy is not null) return cpy; else return null;
        }

        public Task UpdateAsync(Company item)
        {
            throw new NotImplementedException();
        }

        public void Updatec(Company item)
        {
            throw new NotImplementedException();
        }
    }
}

Another Blazor Server app in the same solution uses all above details in this way.

Here is the program.css class code.

using iStar.Framework.Application.Data;
using iStar.Framework.Application.Repositories;
using iStar.Framework.Application.Repositories.Identity;
using iStar.Framework.ModelIRepos;
using iStar.Framework.Models.Identity;
.
.
var builder = WebApplication.CreateBuilder(args);
.
.
builder.Services.AddScoped<ICompanyRepo, CompanyRepo>();
.
.
builder.Services.AddDbContextFactory<FrameworkDbContext>(options =>
    {
        //options.UseLazyLoadingProxies();
        options.UseSqlServer(connectionString);
    });
.
.


Below is CompanyList.razor code.

@* @page "/companies/list" *@
@page "/cmpnylist"

@using Microsoft.EntityFrameworkCore

@inject CompanyRepo _companyRepo

<div class="container-fluid bg-light py-2">
    <button class="btn btn-primary" @onclick="ShowAddCompanyModal">Add Company</button>
</div>

<h3>Company List</h3>

@if (companies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table table-hover">
        <thead>
            <tr>
                <th @onclick="() => SortCompanies(nameof(Company.Name))">Name</th>
                <th @onclick="() => SortCompanies(nameof(Company.NavBarLogoUrl))">NavBar Logo URL</th>
                <th @onclick="() => SortCompanies(nameof(Company.UseNavBarLogoUrl))">Use NavBar Logo URL</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var company in companies)
            {
                <tr>
                    <td>@company.Name</td>
                    <td>@company.NavBarLogoUrl</td>
                    <td>@company.UseNavBarLogoUrl</td>
                </tr>
            }
        </tbody>
    </table>
}

<div class="container-fluid bg-light py-2">
    <button class="btn btn-primary" @onclick="ShowAddCompanyModal">Add Company</button>
</div>

@if (showModal)
{
    <div class="modal fade show d-block" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Add New Company</h5>
                    <button type="button" class="btn-close" @onclick="HideAddCompanyModal"></button>
                </div>
                <div class="modal-body">
                    <EditForm Model="newCompany" OnValidSubmit="AddCompany">
                        <DataAnnotationsValidator />
                        <ValidationSummary />

                        <div class="mb-3">
                            <label for="name" class="form-label">Name</label>
                            <InputText id="name" class="form-control" @bind-Value="newCompany.Name" />
                        </div>
                        <div class="mb-3">
                            <label for="navBarLogoUrl" class="form-label">Nav Bar Logo URL</label>
                            <InputText id="navBarLogoUrl" class="form-control" @bind-Value="newCompany.NavBarLogoUrl" />
                        </div>
                        <div class="form-check">
                            <InputCheckbox id="useNavBarLogoUrl" class="form-check-input" @bind-Value="newCompany.UseNavBarLogoUrl" />
                            <label for="useNavBarLogoUrl" class="form-check-label">Use Nav Bar Logo URL</label>
                        </div>

                        <button type="submit" class="btn btn-success mt-3">Save</button>
                    </EditForm>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private List<Company>? companies;
    private bool ascending = true;
    private string currentSortColumn;
    private bool showModal = false;
    private Company newCompany = new Company();

    protected override async Task OnInitializedAsync()
    {
        companies = await _companyRepo.GetAllAsync();
    }

    private void SortCompanies(string columnName)
    {
        if (currentSortColumn == columnName)
        {
            ascending = !ascending;
        }
        else
        {
            currentSortColumn = columnName;
            ascending = true;
        }

        companies = ascending
            ? companies!.OrderBy(c => EF.Property<object>(c, columnName)).ToList()
            : companies!.OrderByDescending(c => EF.Property<object>(c, columnName)).ToList();
    }

    private void ShowAddCompanyModal()
    {
        newCompany = new Company();
        showModal = true;
    }

    private void HideAddCompanyModal()
    {
        showModal = false;
    }

    private async Task AddCompany()
    {
        await _companyRepo.AddAsync(newCompany);
        companies = await _companyRepo.GetAllAsync();
        showModal = false;
    }

}

Below is _imports.razor code

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using TutorsOnlinePortal
@using TutorsOnlinePortal.Client
@using TutorsOnlinePortal.Components
@using iStar.Framework.Models.Identity
@using iStar.Framework.Application.Repositories
@using iStar.Framework.Models

When I run this app, everything goes fine until I click the clink that loads CompanyList.razor page. It shows the following error.

InvalidOperationException: Cannot provide a value for property '_companyRepo' on type 'TutorsOnlinePortal.Components.Framework.Company.CompanyList'. There is no registered service of type 'iStar.Framework.Application.Repositories.CompanyRepo'.

I have checked all the paths, refferences etc. I have cleaned and rebuilt solution many times but the error does not go away. Remember that app logs in using Microsoft Identity Core infratscture.


Solution

  • Make sure you have register CompanyRepo and ICompanyRepo like below in your Program.cs file.

    ...
    builder.Services.AddDbContextFactory<FrameworkDbContext>(options =>
    {
        options.UseSqlServer(connectionString);
    });
    
    // Make sure you have this line
    builder.Services.AddScoped<ICompanyRepo, CompanyRepo>();
    
    var app = builder.Build();
    ...
    

    And also find the CompanyList.razor file, modify this line

    @inject CompanyRepo _companyRepo
    

    to

    @inject ICompanyRepo _companyRepo