user2986756
user2986756

Reputation: 109

How to access the DbContext fom asp.net core service

I'm a bit stuck in the usage of ASP.NET core and entity framework and all it's (other) components. I'm working on a simple web-app where you can enter some data and have it calculate some statistics (basically strava ultra-light).

So If I open the default blazor app from Visual Studio (2019) I get something like this as Startup

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using BlazorApp1.Areas.Identity;
using BlazorApp1.Data;

namespace BlazorApp1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
            services.AddSingleton<WeatherForecastService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}

And something like this as a Service

using System;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorApp1.Data
{
    public class WeatherForecastService
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
        {
            var rng = new Random();
            return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray());
        }
    }
}

So I added a model for my data

using Microsoft.AspNetCore.Identity;
using BlazorApp1.Database.Types;
using System.ComponentModel.DataAnnotations;

namespace BlazorApp1.Database.Entity
{
    public class Activity
    {
        public string ActivityData { get; set; }
        public ActivityType ActivityType { get; set; }
        public float Distance { get; set; }

        [Key]
        public int Id { get; private set; }

        public IdentityUser User { get; set; }
    }
}

And added it to the ApplicationDbContext

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace BlazorApp1.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public DbSet<Activity> Activities { get; set; }
    }
}

So now I want to create my own Service similar to the WeatherForecastService and that is where I get stuck.

using log4net;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using BlazorApp1.Data.Model;
using BlazorApp1.Database;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace BlazorApp1.Data
{
    public class LeaderBoardService
    {
        private readonly static ILog _logger = LogManager.GetLogger(typeof(LeaderBoardService));


        public Task<List<LeaderBoardItem>> GetOverallLeaderboard()
        {
            //I want to access the database context from here.
            return Task.FromResult(result);
        }
    }
}

Also, I need to add this Service to the Startup.ConfigureServices(). From what I have found so far is that I can use services.AddScoped<LeaderBoardService>(), services.AddSingleton<LeaderBoardService>() and services.AddTransient<LeaderBoardService>() to accomplish this and it seems like services.AddScoped<LeaderBoardService>() is the best to use.

It could be just me that has this problem, but the documentation seems to be missing some hint on how to accomplish this seemingly easy task.

So far I looked at the following sites:

  1. https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/intro?view=aspnetcore-3.1&tabs=visual-studio
    • but there are no Services used in this example.
  2. https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext#using-dbcontext-with-dependency-injection
    • Although it seems like the solution is just to add the context as a parameter to the constructor of the service I can't seem to find how to add this parameter during any of the calls.
  3. https://stackoverflow.com/a/48698290
    • I'm not 100% sure if this is what I'm looking for but if it turns out to be I would like to have some hint on how to implement this.
  4. asp.net core access dbcontext within class
    • Here some middleware is used and I guess that is not what I'm looking for.
  5. https://stackoverflow.com/a/44484724/2986756
    • This one is an option to make the DbContext Transient, also not what I'm looking for.
  6. https://stackoverflow.com/a/37511175
    • Here the database is placed in a Singleton. Although I could use this I don't think I should.

Upvotes: 4

Views: 8217

Answers (1)

Arman Ebrahimpour
Arman Ebrahimpour

Reputation: 4461

Your LeaderBoardService should be implemented this way:

public class LeaderBoardService
{
    private readonly ApplicationDbContext dbContext;
    private readonly ILogger logger;

    public LeaderBoardService(ApplicationDbContext dbContext, ILogger<LeaderBoardService> logger)
    {
        this.dbContext = dbContext;
        this.logger = logger;
    }

    public async Task<List<LeaderBoardItem>> GetOverallLeaderboard()
    {
        return await dbContext.LeaderBoardItems.ToListAsync();
    }
}

About your service lifetime, it depends on your usage, but it should never be more course grain than its inner services lifetimes. So your service could be either scoped or transient, but not singleton because your DbContext declared as scoped (and of course you should never declare DbContext as singleton because of concurrency issues).

Upvotes: 4

Related Questions