【问题标题】:How to access IWebHostEnvironment from Program.cs in ASP.NET Core如何从 ASP.NET Core 中的 Program.cs 访问 IWebHostEnvironment
【发布时间】:2021-07-05 16:45:21
【问题描述】:

我有 ASP.NET Core Razor 页面应用程序,我想在我的 Program.cs 中访问 IWebHostEnvironment。我在应用程序开始时为 DB 播种,我需要将 IWebHostEnvironment 传递给我的初始化程序。这是我的代码:

Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            try
            {
                SeedData.Initialize(services);
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred seeding the DB.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

SeedData.cs

    public static class SeedData
    {
        private static IWebHostEnvironment _hostEnvironment;
        public static bool IsInitialized { get; private set; }

        public static void Init(IWebHostEnvironment hostEnvironment)
        {
            if (!IsInitialized)
            {
                _hostEnvironment = hostEnvironment;
                IsInitialized = true;
            }
        }

        public static void Initialize(IServiceProvider serviceProvider)
        {
            //List<string> imageList = GetMovieImages(_hostEnvironment);

            int d = 0;

            using var context = new RazorPagesMovieContext(
                serviceProvider.GetRequiredService<
                    DbContextOptions<RazorPagesMovieContext>>());

            if (context.Movie.Any())
            {
                return;   // DB has been seeded
            }

            var faker = new Faker("en");
            var movieNames = GetMovieNames();
            var genreNames = GetGenresNames();

            foreach(string genreTitle in genreNames)
            {
                context.Genre.Add(new Genre { GenreTitle = genreTitle });
            }

            context.SaveChanges();
            
            foreach(string movieTitle in movieNames)
            {
                context.Movie.Add(
                    new Movie
                    {
                        Title = movieTitle,
                        ReleaseDate = GetRandomDate(),
                        Price = GetRandomPrice(5.5, 30.5),
                        Rating = GetRandomRating(),
                        Description = faker.Lorem.Sentence(20, 100),
                        GenreId = GetRandomGenreId()
                    }
               );
            }

            context.SaveChanges();
        }

因为我在wwwroot 中有图像,我需要在初始化期间从那里获取图像的名称。我试图从 Startup.cs 在 configure 方法中传递 IWebHostEnvironment

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        int d = 0;
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        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();
        }

        SeedData.Init(env); // Initialize IWebHostEnvironment
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }

但似乎Startup.Configure 方法在Program.Main 方法之后执行。然后我决定用Startup.ConfigureServices方法做,结果发现这个方法最多只能取1个参数。有什么办法可以做到这一点?但是,我不确定我尝试播种数据的方式是不是最好的方式,我只是认为这种方式最适合我的情况,所以我非常感谢任何其他建议的方法。

我发现的类似问题:

【问题讨论】:

  • 这似乎是一个XY problem 并且有点过度设计。它还演示了如何尝试将 DI 与静态类一起使用会导致比它解决的问题更多的问题。您的播种器可以是一个作用域注册类,并在构建后从主机解析,通过构造函数注入显式注入主机环境。另一种方法可能是在 IHostedService 中完成所有播种,在应用程序运行时执行您所需的作用域功能。
  • 感谢@Nkosi 的建议,我查看了XY problem,发现它非常有用。

标签: c# asp.net-core dependency-injection development-environment


【解决方案1】:

最初的问题展示了尝试将 DI 与静态类一起使用会导致比它解决的问题更多的问题。

播种器可以是一个作用域注册类,并在构建后从主机解析。宿主环境和任何其他依赖可以通过构造函数注入显式注入

例如

public class SeedData {
    private readonly IWebHostEnvironment hostEnvironment;
    private readonly RazorPagesMovieContext context;
    private readonly ILogger logger;

    public SeedData(IWebHostEnvironment hostEnvironment, RazorPagesMovieContext context, ILogger<SeedData> logger) {
        this.hostEnvironment = hostEnvironment;
        this.context = context;
        this.logger = logger;
    }

    public void Run() {
        try {
            List<string> imageList = GetMovieImages(hostEnvironment); //<<-- USE DEPENDENCY

            int d = 0;

            if (context.Movie.Any()) {
                return;   // DB has been seeded
            }

            var faker = new Faker("en");
            var movieNames = GetMovieNames();
            var genreNames = GetGenresNames();

            foreach(string genreTitle in genreNames) {
                context.Genre.Add(new Genre { GenreTitle = genreTitle });
            }

            context.SaveChanges();
            
            foreach(string movieTitle in movieNames) {
                context.Movie.Add(
                    new Movie {
                        Title = movieTitle,
                        ReleaseDate = GetRandomDate(),
                        Price = GetRandomPrice(5.5, 30.5),
                        Rating = GetRandomRating(),
                        Description = faker.Lorem.Sentence(20, 100),
                        GenreId = GetRandomGenreId()
                    }
               );
            }

            context.SaveChanges();
        } catch (Exception ex) {
           logger.LogError(ex, "An error occurred seeding the DB.");
        }
    }

    // ... other code

}

请注意如何不再需要服务定位器反模式。所有必要的依赖项都根据需要显式注入到类中。

Program 然后可以简化

public class Program {
    public static void Main(string[] args) {
        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope()) {
            SeedData seeder = scope.ServiceProvider.GetRequiredService<SeedData>();
            seeder.Run();
        }    
        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices(services => {
                services.AddScoped<SeedData>(); //<-- NOTE 
            })
            .ConfigureWebHostDefaults(webBuilder => {
                webBuilder.UseStartup<Startup>();
            });
}

播种器在主机上注册并在运行主机之前根据需要解析。现在不需要访问除了播种机以外的任何东西。 IWebHostEnvironment 和所有其他依赖项将由 DI 容器解析并在需要的地方注入。

【讨论】:

  • 这种方式好多了,我不知道为什么,但是因为我决定让我的播种机完全静止,所以我朝那个方向思考,这让我很困惑......我只是没有考虑将它注入Main 并且不想使用一些控制器播种它,所以这就是我输掉的地方......非常感谢!
  • @Miraziz 很高兴为您提供帮助。有时,当您遇到障碍时,退后一步并尝试从更大的角度看待您想做的事情会有所帮助。快乐编码!!!
【解决方案2】:

我的问题的解决方案是简单地从ServiceProvider.GetRequiredService&lt;T&gt;请求IWebHostEnvironment

主要

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

using (var scope = host.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var hostEnvironment = services.GetRequiredService<IWebHostEnvironment>();

    try
    {
       SeedData.Initialize(services, hostEnvironment);
    }
    catch (Exception ex)
    {
       var logger = services.GetRequiredService<ILogger<Program>>();
       logger.LogError(ex, "An error occurred seeding the DB.");
    }
}

【讨论】:

  • 在.NET 5.0中,我在Program.cs文件中直接编写所有宿主机构建、服务和app管道配置时使用了这种方法获取环境,没有使用单独的Startup.cs文件/班级。在 webBuilder.Configure(app => ...) 我有这个:IWebHostEnvironment env = app.ApplicationServices.GetRequiredService&lt;IWebHostEnvironment&gt;();
  • 我没有提到它,但首先我还向webBuilder.Configure 要求它,然后向service 要求它。我必须承认,这非常可能而且非常方便。
  • @ryancdotnet 很好,谢谢。现在只是IWebHostEnvironment env = app.Services.GetRequiredService&lt;IWebHostEnvironment&gt;();
猜你喜欢
  • 2022-06-13
  • 2020-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多