Search code examples
c#asp.net-coredependency-injectionautofac

how to access IApplicationBuilder in a controller?


Just wondering, is that possible to access IApplicationBuilder propteries outside of the startup.cs? Like in a controller?

I know it's only used to define the app pipeline so what would be the solution? Something like register a service that packages the instance, then inject the service instead of the IApplicationBuilder?

I'm trying to get back my DbConext from Autofac. Code is as following :

In Business project :

 public class AutofacBusinessModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterModule(new AutofacDataModule());
        }
    }

In Data project :

 public class AutofacDataModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<AppDbContext>().InstancePerLifetimeScope();
        }
    }

The DbContext :

public class AppDbContext : DbContext
    {
        private const string DbContextName = "AppDbConnectionString";
        public DbSet<Contest> Contests { get; set; }

        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }

        public AppDbContext()
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (optionsBuilder.IsConfigured) return;

            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();
            var connectionString = configuration.GetConnectionString(DbContextName);
            optionsBuilder.UseSqlServer(connectionString,
                x => x.MigrationsAssembly("Cri.CodeGenerator.Data"));
        }

        public virtual void Commit()
        {
            base.SaveChanges();
        }
    }

And the ConfigureServices in startup.cs in Web project :

public IServiceProvider ConfigureServices(IServiceCollection services)
{


    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    var builder = new ContainerBuilder();

   builder.RegisterModule(new AutofacBusinessModule());


    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

    services.AddScoped(sp => {
        var actionContext = sp.GetRequiredService<IActionContextAccessor>().ActionContext;
        var urlHelperFactory = sp.GetRequiredService<IUrlHelperFactory>();
        var urlHelper = urlHelperFactory.GetUrlHelper(actionContext);
        return urlHelper;
    });

    services.AddDistributedMemoryCache();
    builder.Populate(services);

    ApplicationContainer = builder.Build();

    //return the IServiceProvider implementation
    return new AutofacServiceProvider(ApplicationContainer);

}

I'm surely missing something, but really newbie when it comes to DI and .net core...

-- EDIT --

In my Controller

  private readonly IHostingEnvironment _hostingEnvironment;
        private readonly IApplicationBuilder _app;


        private const int NumberOfCharactersRepetion = 4;

        public UniqueCodesController(IHostingEnvironment hostingEnvironment, IApplicationBuilder app)
        {
            _hostingEnvironment = hostingEnvironment;
            _app = app;
        }

...

if (selectedAnswer == FileExtension.XLSX.GetStringValue())
                {
                    await FilesGenerationUtils.GenerateExcelFile(_app, uniqueCodesHashSet, model);
                }

in the GenerateExcelFile method :

public static async Task GenerateExcelFile(IApplicationBuilder app, IEnumerable<string> hashSetCodes, ContestViewModel model)
        {
...

 try
                    {
                        var context = app.ApplicationServices.GetRequiredService<AppDbContext>();

                        var contest = new Contest
                        {
                            Name = model.ProjectName,
                            UserId = 1,
                            FileGenerationStatus = true,
                            FilePath = fileInfo.FullName
                        };
                        context.Contests.Add(contest);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }

}

But when I run the app, I get this message :

InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Builder.IApplicationBuilder' while attempting to activate 'Cri.CodeGenerator.Web.Controllers.UniqueCodesController'.

Solution

  • Sounds like you're trying to get a new instance of AppDbContext.

    If you have to keep the GenerateExcelFile() as static and want to reuse AppDbContext via a parameter, you could make it accept an instance of AppDbContext instead of the IApplicationBuilder.

    Firstly, simply inject such a service instance directly:

        private readonly IHostingEnvironment _hostingEnvironment;
        private readonly AppDbContext _dbContext;
    
        // ...
    
        public UniqueCodesController(IHostingEnvironment hostingEnvironment, AppDbContext dbContext)
        {
            _hostingEnvironment = hostingEnvironment;
            _dbContext = dbContext;
        }
    

    And then change the GenerateExcelFile() to accept a parameter of AppDbContext

        public static async Task GenerateExcelFile(IApplicationBuilder app, IEnumerable<string>hashSetCodes, ContestViewModel model)
        public static async Task GenerateExcelFile(AppDbContext dbContext, IEnumerable hashSetCodes, ContestViewModel model)
        {
            ...
    
            try{
                var context = app.ApplicationServices.GetRequiredService();
                var contest = new Contest
                {
                    Name = model.ProjectName,
                    UserId = 1,
                    FileGenerationStatus = true,
                    FilePath = fileInfo.FullName
                };
                context.Contests.Add(contest);
                context.Contests.Add(contest);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
    
        }
    

    Finally, you could invoke it as below :

        await FilesGenerationUtils.GenerateExcelFile(_dbContext, uniqueCodesHashSet, model);
    

    As a side note, if you can't determine the required type at compile-time, and want to resolve some service type dynamically, you could inject an IServiceProvider instead of the IApplicationBuilder. In this way, You could resolve any instance as you like :

        var dbContext= sp.GetRequiredService<AppDbContext>();
        // or if you need a service only available within a scope 
        using(var scope = this._sp.CreateScope()){
            var _anotherDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            ...
        }
    

    Taking your code as an example, you could pass an IServiceProvider to GenerateExcelFile(IServiceProvider sp, IEnumerable<string> hashSetCodes, ContestViewModel model), and within the GenerateExcelFile() method, you could resolve the AppDbContext in the following way:

        public static async Task GenerateExcelFile(IServiceProvider sp, IEnumerable<string> hashSetCodes, ContestViewModel model)
        {
            ...
    
            var dbContext= sp.GetRequiredService<AppDbContext>();
    
            try{
                var contest = new Contest
                {
                    Name = model.ProjectName,
                    UserId = 1,
                    FileGenerationStatus = true,
                    FilePath = fileInfo.FullName
                };
    
                dbContext.Contests.Add(contest);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
    
        }