4

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.
user2986756
  • 89
  • 1
  • 9
  • 1
    Add a constructor to your `LeaderBoardService` with `LeaderBoardService(ApplicationDbContext context)` save the context into member variable `_context`. Register the `LeaderBoardService` as Transient or Scoped (check the difference between them), Access the context from the method `_context.YourTable`. – Max Jun 07 '20 at 17:58
  • 1
    Also, log4net has an integration with `Microsoft.Extensions.Logging`. You *really* shouldn't be using `ILog` or `LogManager.GetLogger` directly.. Instead you should be adding the log4net services and injecting an `ILogger` into your constructor. https://www.nuget.org/packages/Microsoft.Extensions.Logging.Log4Net.AspNetCore/ – pinkfloydx33 Jun 07 '20 at 19:51

1 Answers1

4

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).

Arman Ebrahimpour
  • 4,252
  • 1
  • 14
  • 46
  • Ok, that worked. But now I'm wondering how this is accomplished. I guess trough the magic of dependency injection. Is this the correct documentation for that [msdn](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1) or is there a more detailed description somewhere? – user2986756 Jun 08 '20 at 16:39
  • 1
    @user2986756: Yes all of these happened by asp.net core IoC container. msdn is well detailed in this topic. – Arman Ebrahimpour Jun 08 '20 at 16:52