LocTranHuynhCong
LocTranHuynhCong

Reputation: 11

How to solve docker-compose environment variables not working ASP.Net Core MVC

My config variables in docker-compose file can't read in Startup.cs. I'm using Asp.net Core 3.1 and SQL Server.

This is my error message

blog-app_1      | warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
blog-app_1      |       Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
blog-app_1      | warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
blog-app_1      |       No XML encryptor configured. Key {58394e97-3215-43de-abbc-ae7ab801d661} may be persisted to storage in unencrypted form.
blog-app_1      | crit: Microsoft.AspNetCore.Hosting.Diagnostics[6]
blog-app_1      |       Application startup exception
blog-app_1      | System.ArgumentNullException: Value cannot be null. (Parameter 'connectionString')
blog-app_1      |    at Microsoft.EntityFrameworkCore.Utilities.Check.NotEmpty(String value, String parameterName)
blog-app_1      |    at Microsoft.EntityFrameworkCore.SqlServerDbContextOptionsExtensions.UseSqlServer(DbContextOptionsBuilder optionsBuilder, String connectionString, Action`1 sqlServerOptionsAction)
blog-app_1      |    at BlogApplication.Startup.<>c__DisplayClass4_0.<ConfigureServices>b__1(DbContextOptionsBuilder options) in /src/BlogApplication/Startup.cs:line 56
blog-app_1      |    at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass1_0`2.<AddDbContext>b__0(IServiceProvider p, DbContextOptionsBuilder b)
blog-app_1      |    at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.CreateDbContextOptions[TContext](IServiceProvider applicationServiceProvider, Action`2 optionsAction)

This is my Startup.cs

I'm using Configuration["..."] to initial environment variables. You can see it in public void ConfigureServices(IServiceCollection services).

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor();

        services.AddControllersWithViews().AddJsonOptions(option =>
        {
            option.JsonSerializerOptions.PropertyNamingPolicy = null;
            option.JsonSerializerOptions.DictionaryKeyPolicy = null;
        });

        //Config Database Identity
        var server_identity = Configuration["DBServer"] ?? "mssql-server";
        var port_identity = Configuration["DBPort"] ?? "1433";
        var user_identity = Configuration["DBUsername"] ?? "sa";
        var password_identity = Configuration["DBPass"] ?? "Loc@04071999";
        var dbName_identity = Configuration["DBNameIdentity"] ?? "Identity_BlogApplication";

        //Config Database Blog
        var dbName_blog = Configuration["DBNameBlog"] ?? "Data_BlogApplication";

        string DefaultConnection = $"Server={server_identity}, {port_identity}; Database={dbName_identity}; User ID={user_identity}; Password={password_identity}";
        string DataConnection = $"Server={server_identity}, {port_identity}; Database={dbName_blog}; User ID={user_identity}; Password={password_identity}";

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString(DefaultConnection)));
        services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddDbContext<BlogApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString(DataConnection)));

        services.AddTransient<ICommentService, CommentService>();
        services.AddTransient<IBlogService, BlogService>();

        services.AddControllersWithViews().AddRazorRuntimeCompilation();
        services.AddRazorPages();

        //services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
    }

    // 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("/Home/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.MapControllerRoute(
                name: "default",
                pattern: "{controller=Blog}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });

        
        app.ApplicationServices.CreateScope().ServiceProvider.GetService<ApplicationDbContext>().Database.Migrate();
        app.ApplicationServices.CreateScope().ServiceProvider.GetService<BlogApplicationDbContext>().Database.Migrate();
    }c
}

This is my docker-compose.xml

and in docker-compose, I set "environment: " for blog-app

version: '3'
services:
  mssql-server:
    image: "microsoft/mssql-server-linux"
    environment:
      ACCEPT_EULA: "Y"
      SA_PASSWORD: "Loc@04071999"
      MSSQL_PID: Express
    ports:
      - "1433:1433"
  blog-app:
    build: .
    environment:
      DBServer: "mssql-server"
      DBPort: "1433"
      DBUsername: "sa"
      DBPass: "Loc@04071999"
      DBNameIdentity: "Identity_BlogApplication"
      DBNameBlog: "Data_BlogApplication"
    ports: 
      - "3000:80"

Thanks for see my problem

Upvotes: 1

Views: 2161

Answers (1)

Jehof
Jehof

Reputation: 35554

The method Configuration.GetConnectionString() you use gets the connection-string with the provided name from the configuration. In your case the name is the value of variable DefaultConnection. The variable should have value

$"Server=mssql-server, 1433; Database=Identity_BlogApplication; User ID=sa; Password=Loc@04071999"; 

But a connection string with this name does not exist and therefore null is returned and you get the ArgumentNullException.

You may fix it by not calling Configuration.GetConnectionString() and instead pass your connection-strings directly to method UseSqlServer, because you build the connection-string by yourself with the values of your environment variables.

So the following should fix your problem.

services.AddDbContext<ApplicationDbContext>(options =>  options.UseSqlServer(DefaultConnection));
...
services.AddDbContext<BlogApplicationDbContext>(options => options.UseSqlServer(DataConnection));

But IMHO you should not build the connection-string by yourself, but provide the full connection-string to your database with a single environment variable. This is done using a connecting-string environment variable (CONNECTIONSTRINGS)

version: '3'
services:
  mssql-server:
    image: "microsoft/mssql-server-linux"
    environment:
      ACCEPT_EULA: "Y"
      SA_PASSWORD: "Loc@04071999"
      MSSQL_PID: Express
    ports:
      - "1433:1433"
  blog-app:
    build: .
    environment:
      CONNECTIONSTRINGS__IDENTITY: "Server=mssql-server, 1433; Database=Identity_BlogApplication; User ID=sa; Password=Loc@04071999"
      CONNECTIONSTRINGS__BLOG: "Server=mssql-server, 1433; Database=Data_BlogApplication; User ID=sa; Password=Loc@04071999"
    ports: 
      - "3000:80"

Using this approach you can use the call Configuration.GetConnectionString() in your code like

services.AddDbContext<ApplicationDbContext>(options =>  options.UseSqlServer(Configuration.GetConnectionString("IDENTITY")));
...
services.AddDbContext<BlogApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("BLOG")));

Upvotes: 3

Related Questions