ASPNET Core User Secrets

One of things I recently know: ASPNET Core user secrets.

I took over another ASPNET Core project. As usual, I did a normal routine

  1. Check out the code from repository
  2. Compile and hit F5

Oops! It compiled but an exception was thrown at runtime. Usually, the configuration was wrong because the code was now running on my machine. I checked the source and looked for the appsettings.json or appsettings.development.json file. Bad news! They were not there.

How could the other developers run it locally? Were there any secrets? Apparently, yes there was a User Secret.

<PropertyGroup>
  <TargetFramework>netcoreapp3.1</TargetFramework>
  <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId>
</PropertyGroup>

And here is the complete article from Microsoft Docs. Once I read it, Aha! It has been around for a while. Just that I was behind the bar.

The purpose is clear. It enforces security by preventing accidental commits to repository. There are connection strings, credentials, passwords, … all kind of sensitive information. It is so easy to make a mistake and push them into the remote repositories.

However, I had a secret problem. There are more than 15 settings. Adding them one by one is not a pleasure task. Note that there was a configuration file with all default settings. If every developer names their database, port, folder, … consistently, the configuration is the same. I had that file by asking other developers. How do I import them into my secrets?

  1. Open the secret folder at %appdata%\Microsoft\UserSecrets\
  2. Find the folder with the secret ID
  3. Copy the appsettings.development.json file to it and rename to secrets.json. Delete the existing one if exists

And it works perfectly. If there are differences in configuration between branches, be aware of updating the secrets.

The secrets are revealed!

Watch Out Abstract Dependency

The context is a C# .NET Core WebAPI system. The system employs the Onion Architecture. There is an API layer which is ASP.NET MVC controller. Then there is an Application Service layer inside the API layer. In the core of the onion, it is the domain. One can quickly google for Onion Architecture. If you do not know it, I suggest you take a look at it first.

One of a key points in the Onion Architecture is the inner layer must not know the outer layer. Which translates into the C# project that the Application Service Layer must not reference directly to the API project. The API consumes the Application Service NOT the other way around.

There is a requirement that application configuration (note that it is not system configuration like connection string, certificates …) should be maintained in a JSON file. Let’s call it appConfig.json. The file is deployed with the API. The implementation takes the advantage of .NET Core JSON configuration so the runtime can load the file.

Let simplify the requirement to focus on the architecture. The appConfig defines the default currency. The value is used to display in the UI and for other processing in the Application Service.

Come to the tricky part. Where do we should place the implementation: API or Application Service?

My answer is always in the API. What if the implementation is in the Application Service? Let’s see the implementation and analyze.

By using the IConfiguration interface, the Application Service has a dependency on Microsoft.Extensions.Configuration package which seems fine. Because it is abstraction.

Has the dependency between API and Application Service changed? Implementation-wise NO. Because there is no direct reference from Application Service to the API.

But there is in term of design. The Application Service implementation is using the infrastructure supplied by the API – the configuration is managed by the API layer.

Will it cause any problem? Well, it depends on what we care most about. If the architecture is the main concern, then yes, it is a problem. The architecture is broken unintentionally. Think about a scenarios where the Application Service is by another client, such as WPF application. Does it make sense to bring the Microsoft.Extensions.Configuration package to WPF application?

The Application Service defines the interface, which says “hey! I need to know the default currency, but I cannot figure it out by myself.” The outer layers, can be a WebAPI, can be WPF application, will supply the implementation, which says “Not a problem! I know where to get the default currency for you.”

EF Core Getting Started

I am learning Entity Framework Core as part of my Azure journey. Database is an important part in an application. In the old days, developers wrote raw SQL queries. Later, we have had ADO.NET. Recently we have ORM. I have had a chance to work (know) the 2 big guys: NHibernate and Entity Framework.

ORM does more than just a mapping between object model and database representation, such as SQL Table, Column. Each ORM framework comes with a plenty of features, supports variety of scenarios. ORM helps you build a better application. Let’s discover some from the latest ORM from Microsoft: Entity Framework Core.

I was amazed by visiting the official document site. Everything you need to learn is there, in well-written, understandable pages. To my learning, I started with courses on Pluralsight, author Julie Lerman. If you happen to have Pluralsight account, go ahead and watch them. I is worth your time. Then I read the EF document on its official site.

It is  easy to say that “Hey I know Entity Framework Core“. Yes, I understand it. But I need the skill, not just a mental understanding. To make sure I build EF skill, I write blog posts and write code. It is also my advice to you, developers.

Journey to Azure

Getting Started Objectives

  1. Define a simple domain model and hook up with EF Core in ASP.NET Core + EF Core project
  2. Migration: From code to database
  3. API testing with Postman or Fiddler (I do not want to spend time on building UI)
  4. Unit Testing with In Memory and real databases.
  5. Running on Azure with Azure SQL
  6. Retry strategy

1 – Domain Model

To get started, I have only these super simple domain model

namespace Aduze.Domain
{
    public abstract class Entity
    {
        public int Id { get; set; }
    }

    public class User : Entity
    {
        public string LoginName { get; set; }
        public string FullName { get; set; }
        public Image Avatar { get; set; }
    }

    public class Image : Entity
    {
        public string Uri { get; set; }
    }
}

A User with an avatar (Image).

Next, I have to setup DbContext

namespace Aduze.Data
{
    public class AduzeContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        public AduzeContext(DbContextOptions options)
        :base(options)
        {
            
        }
        
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    }
}

Pretty simple just like the example in the document site. Just a quick note here, I organize domain classes in Domain project, data access layer in Data project. I do not like the term Repository very much.

Wire them up in the ASP.NET Core Web project

       public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<AduzeContext>(options =>
            {
                options.UseSqlServer(Configuration.GetConnectionString("AduzeSqlConnection"))
                    .EnableSensitiveDataLogging();
            });
            services.AddLogging(log =>
                log.AddAzureWebAppDiagnostics()
                    .AddConsole());
        }

Just call the extension method: AddDbContext and done. God damn simple!

2 – Migration

The system cannot work unless there is a database. There are 2 possible solutions

  1. Use your SQL skill and create database with correct schema.
  2. Use what EF offers

I have done the former many years. Let’s explore the later.

Having your VS 2017 opened, access the Package Manager Console window

Add-Migration

EF Core Add Migration
EF Core Add Migration

  1. Default project: Aduze.Data where the DbContext is configured.
  2. Add-Migration: A PowerShell command supplied by EF Core. Tips: Type Get-Help Add-Migration to ask for help
  3. InitializeUser: The migration name. One can give whatever makes sense.

After executed, The “Migrations” folder is added into the Data project. Visit EF Core document to understand what it does and syntaxes.

Script-Migration

So how does the SQL script look like?

PM> Script-Migration
IF OBJECT_ID(N'__EFMigrationsHistory') IS NULL
BEGIN
    CREATE TABLE [__EFMigrationsHistory] (
        [MigrationId] nvarchar(150) NOT NULL,
        [ProductVersion] nvarchar(32) NOT NULL,
        CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
    );
END;

GO

CREATE TABLE [Image] (
    [Id] int NOT NULL IDENTITY,
    [Uri] nvarchar(max) NULL,
    CONSTRAINT [PK_Image] PRIMARY KEY ([Id])
);

GO

CREATE TABLE [Users] (
    [Id] int NOT NULL IDENTITY,
    [AvatarId] int NULL,
    [FullName] nvarchar(max) NULL,
    [LoginName] nvarchar(max) NULL,
    CONSTRAINT [PK_Users] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Users_Image_AvatarId] FOREIGN KEY ([AvatarId]) REFERENCES [Image] ([Id]) ON DELETE NO ACTION
);

GO

CREATE INDEX [IX_Users_AvatarId] ON [Users] ([AvatarId]);

GO

INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20180420112151_InitializeUser', N'2.0.2-rtm-10011');

GO

Cool! I can take the script and run in SQL Management Studio. Having scripts ready, I can use them to create Azure SQL database later on.

Update-Database

Which allows me to create the database directly from Package Manager Console (which is a PowerShell). Let’s see

PM> Update-Database -Verbose

By turning Verbose on, It logs everything out in the console. The result is my database created

EF Update Database
EF Update Database

It is very smart. How could It do?

  1. Read the startup project Aduze.Web and extract the ConnectionString from appsettings.json
  2. Run the migrations created from Add-Migration command.

3 – API Testing

So far nothing has happened yet.

namespace Aduze.Web.Controllers
{
    public class UserController : Controller
    {
        private readonly AduzeContext _context;

        public UserController(AduzeContext context)
        {
            _context = context;
        }
        [HttpPost]
        public async Task<IActionResult> Create([FromBody]User user)
        {
            _context.Add(user);
            await _context.SaveChangesAsync();
            return Json(user);
        }

        [HttpGet]
        public async Task<IActionResult> Index()
        {
            var users = await _context.Users.ToListAsync();
            return Json(users);
        }
    }
}

A typical Web API controller.

  1. Create: Will insert a user. There is no validation, mapping between request to domain, … It is not a production code.
  2. Index: List all users.

Here is the test using Postman

API Test with Postman
API Test with Postman

If I invoke the /user endpoint, the user is on the list.

Hey, what was going on behind the scene?

EF SQL Log
EF SQL Log

There are plenty of information you can inspect from the Debug window. When inserting a user, those are queries sent to the database (you should see the one to insert the avatar image).

So far so good. I have gone from domain model and build a full flow endpoint API. How about unit testing?

4 – Unit Test

One of the biggest concern when doing unit test is the database dependency. How could EF Core help? It has In-Memory provider. But first, I have to refactor my code since I do not want to test API controller.

namespace Aduze.Data
{
    public class UserData
    {
        private readonly AduzeContext _context;

        public UserData(AduzeContext context)
        {
            _context = context;
        }

        public async Task<User> Create(User user)
        {
            _context.Add(user);
            await _context.SaveChangesAsync();
            return user;
        }

        public async Task<IEnumerable<User>> GetAll()
        {
            return await _context.Users.ToListAsync();
        }
    }
}

namespace Aduze.Web.Controllers
{
    public class UserController : Controller
    {
        private readonly UserData _userData;

        public UserController(UserData userData)
        {
            _userData = userData;
        }
        [HttpPost]
        public async Task<IActionResult> Create([FromBody]User user)
        {
            return Json(await _userData.Create(user));
        }

        [HttpGet]
        public async Task<IActionResult> Index()
        {
            return Json(await _userData.GetAll());
        }
    }
}

That’s should do the trick. Then just register the new UserData service to IoC

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<AduzeContext>(options =>
            {
                options.UseSqlServer(Configuration.GetConnectionString("AduzeSqlConnection"))
                    .EnableSensitiveDataLogging();
            });
            services.AddScoped<UserData>();
        }

Time to create a test project: Aduze.Tests. And then install the Microsoft.EntityFrameworkCore.InMemory package

PM> Install-Package Microsoft.EntityFrameworkCore.InMemory

This is really cool, see below

Unit Test DbContext in Memory
Unit Test DbContext in Memory

Because my refactor UserData uses async version. It seems to have a problem with MS Tests runner. But it is the same with testing directly again AduzeDbContext.

  1. Use DbContextOptionsBuilder to tell EF Core that the context will use In Memory provider.
  2. Pass the options to DbContext constructor.

Having the power to control which provider will be using is a powerful design. One can have a test suite that is independent to the provider. Most of the time we will test with In Memory provider. But when time comes to verify that the database schema is correct, can switch to a real database.

5 – Azure SQL

Time to grow up … to the cloud with these simple steps

  1. Publish the web to Azure
  2. Create Azure SQL database
  3. Update connection string
  4. Run the script (remember the Script-Migration command?) to create database schema

Azure set ConnectionString
Azure set ConnectionString

Just add the connection string: AduzeSqlConnection (is defined in appsettings.json at the local development).

Test again with Postman. Oh yeah baby. It works like a charm.

6 – Retry Strategy

This topic is not something I want to explore at this stage of my learning journey. But it is important to be aware of, at least note down the reference link to Connection Resiliency.

 

Wrap Up

It is not something new nor complicated if we look at its surface. However, when I get my hands dirty at the code and writing, I learn so much. Knowing how to define a DbContext is easy, understanding why it was designed that way is another complete story.

But is that all about EF Core? No. It is just a beginning. There are many things that developers will look at them when they experience problems in real projects. The document is there, the community is there. Oh, S.O has all answers.

What I will look at next is how EF Core supports developers with DDD (Domain Driven Design).

ASP.NET Core MVC – Doors Open, Come In!

Throughout the experiment, I have an empty project. Having an empty project is a good way to learn. It allows me to focus on other things first such as

  1. Build and Release with VSTS
  2. Play around with Docker and Part 2
  3. Fundamental – Configuration.

Ultimately, I want to use ASP.NET Core MVC to build a web application (and other services around it). Time to open the door and step into MVC world; but, as always, with the fundamental first.

ASP.NET Core application is enriched by the use of Middlewares at the developers’ will. MVC is one of the built-in middleware. Plug it in, you have MVC feature. Take it out, well, you are on your own.

MVC Middlewares High Level
MVC Middlewares High Level

Remember, order matters! Let’s take a quick tour with Middleware first.

Middleware

By default, even with an empty project, there are built-in middlewares auto generated. The detail is in the previous post.

Just plug the MVC Middleware in and we are good to go.

Model – View – Controller (MVC)

MVC Model High Level
MVC Model High Level

A request comes in, Controller does 2 things

  1. Build a proper Model.
  2. Decide what view to use.

Remember the keywords here: Build – Model, Decide – View.

The main purpose of a Model is to supply data for the View.

The View takes the data supplied from Model, and render the final HTML for the Response.

Why is that diagram important? Because It came from my understanding of MVC, in my own words (with some borrow from Courses I learned). The point is that I have my own way to express it instead of reading paragraphs of text. Diagrams, images increase my ability to explain.

When you learn something, try to draw diagrams. Try to explain it visually.

Show Me the Code

Let create a home page with some information from an empty project. From a design perspective, what do we need to build an MVC application? Well, I would naively say these

  1. Enable MVC middle
  2. Create a controller to handle the logic
  3. Create a view to display the result

With some fundamentals knowledge about MVC, I created these code and structure

AspNet MVC First Controller
AspNet MVC First Controller

Problems? When I hit F5, an empty page is displayed. I would have expected the Home/Index view displayed.

Old lesson: Nothing works at the first time. If it works, something must have been wrong.

I guess that I missed the routing. Basically, I have to tell the MVC framework how to map a request to a Controller.

Let dig a bit further into routing from MS docs site. There are so many things to learn from that site. However, I just want to get my first simple routing work. I pick what matters most to me.

       public void ConfigureServices(IServiceCollection services)
        {
            // Register the ability to read options in configuration

            services.AddRouting();
            services.AddMvc();
 
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    "default",
                    "{controller=Home}/{action=Index}/{id?}");
            });
        }

To make the magic work, we must

  1. Enable both Route (AddRouting) and MVC (AddMvc) services. This will register all the necessary services required for routing and MVC
  2. Use MVC with configured routing. Above I defined a default routing.

Visit the home page. It works as I expected. Note

Note: If you create an MVC project from an MVC template, they are all created for you, for free.

Add Logic

The next step is to work with the logic code. So far, I have had a Controller ready to play around, the mighty HomeController.

Display an ugly fact: How far are you to the creator?

Let’s assume a man lives for 80 years maximum. Given a birthday, display how many you have spent and how close you are to the creator.

It is a pretty simple logic. The questions I want to explore here are

  1. Where should I put the logic code?
  2. What kind of project should it be? .NET Standard? .NET Core? or .NET Framework?
  3. How to organize data and flow?

First, let make the functionality work

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// Serve the GET /home/uglyfact?birthday=dd-MM-yyyy
        /// </summary>
        /// <param name="birthday"></param>
        /// <returns></returns>
        public IActionResult UglyFact(string birthday)
        {
            // Ignore all data validation. Simply assume that the request is correct.
            var dob = DateTime.ParseExact(birthday, "dd-MM-yyyy", CultureInfo.CurrentCulture);
            var meetCreatorOn = dob.AddYears(80);
            var spent = (int)Math.Round(DateTime.Now.Subtract(dob).TotalDays / 7);
            var closed = (int)Math.Round(meetCreatorOn.Subtract(DateTime.Now).TotalDays / 7);
            return View( new UglyFactModel
            {
                SpentWeeks = spent,
                CloseToCreatorWeeks = closed
            });
        }
    }

There is an UglyFact Action which will do some simple calculation. And then it returns an UglyFactModel to the View. Pretty typical MVC stuff. When we take a closer look

When we take a closer look at the UglyFact action implementation, it is a typical piece of business logic. We know that it should not be there. The business logic should not be in the Controller-Action code.

The business logic should bot be in the Controller-Action code.

I decided to go with .NET Standard. Because the business logic code does not depend on the .NET Framework.

namespace Coconut.CoreLogic
{
    public class UglyFactTeller
    {
        public UglyFactModel TellMe(DateTime dob)
        {
            var meetCreatorOn = dob.AddYears(80);
            var spent = (int)Math.Round(DateTime.Now.Subtract(dob).TotalDays / 7);
            var closed = (int)Math.Round(meetCreatorOn.Subtract(DateTime.Now).TotalDays / 7);
            return new UglyFactModel
            {
                SpentWeeks = spent,
                CloseToCreatorWeeks = closed
            };
        }
    }
}

The logic in the Controller-Action has been moved to a separated assembly. And here the nice-clean controller

namespace Coconut.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly UglyFactTeller _uglyFactTeller;

        public HomeController(UglyFactTeller uglyFactTeller)
        {
            _uglyFactTeller = uglyFactTeller;
        }
        public IActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// Serve the GET /home/uglyfact?birthday=dd-MM-yyyy
        /// </summary>
        /// <param name="birthday"></param>
        /// <returns></returns>
        public IActionResult UglyFact(string birthday)
        {
            // Ignore all data validation. Simply assume that the request is correct.
            var dob = DateTime.ParseExact(birthday, "dd-MM-yyyy", CultureInfo.CurrentCulture);
            return View( _uglyFactTeller.TellMe(dob));
        }
    }
}

To make everything works as before I tell the HomeController how to resolve its dependency (the UglyFactTeller class)

services.AddTransient<UglyFactTeller>();

What’s Next?

At the beginning, I wanted to write more in the post. However, it seems too much for a single post. It is a better idea to stop here and enjoy the “get the job done” feeling.

Next, I will continue with how to make the code structure better (with MediatR (mediator pattern)), explore the unit test.

One of the fun thing when doing from the scratch (or at least with an empty project) is that you think you know them but you do not. You do not know unless you start coding/implementing.

ASP.NET Core Fundamental – Configuration

I started my learning journey with this ASP.NET Core. From my point of view, there are 3 major concepts I have to delve into: Configuration, Service, and Middleware. For each concept, I must learn the fundamentals; learn the things that will stick around with me; allow me to work on advanced features if I want to (of course, I will soon).

Let’s start with Configuration. If you are an advanced developer, you might want to read the full document at Microsoft docs. For me, I want to take a different approach. I want to start with my needs. I, then, extract the information I need, build the fundamental knowledge that is mine.

Goals

I want to know

  1. What kind of configuration options I have
  2. How to build a configuration file
  3. How to consume a configuration file
  4. How to deal with environments such as development, staging, and production
  5. How about deploying to Azure, how I change my configuration values on the fly.

Along the way, I might want to explore the flexibilities that I might have with .NET Core.

Code and Magic

Basic

I want to say “hello world” in a configurable way. Open VS2017, I add a new json file, named it appSettings.json

{
  "Message" :  "Hello word. This is Mr. JSON speaking." 
}

And consume the setting file

    public class Startup
    {
        private readonly IConfigurationRoot _configuration;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appSettings.json");

            _configuration = builder.Build();
        }
        // 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)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //app.UseMvc();
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync($"{_configuration["Message"]}. And you are? {context.Request.Path}");
            });
        }
    }

And it just works.

A Bit Deeper

There is more cool stuff that you could do with the JSON configuration. Should you need them all, refer back to the full documentation from Microsoft. I am only interested in the things that make more sense for common scenarios.

In a real application, the settings are more complex than single key-value pairs. Take a look at this

{
  "Message": "Override the simple message",
  "ConnectionStrings": {
    "ef": "Connection string to SQL database for EF"
  },
  "Author": {
    "Name": "Thai Anh Duc",    
    "Job": "Software Developer" 
  } 
}

How could I get a connection string for EF? Super easy with the support of accessing the child element with “:” separator

   public class Startup
    {
        private readonly IConfigurationRoot _configuration;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appSettings.json")
                .AddJsonFile("appComplexSettings.json");

            _configuration = builder.Build();
        }
        // 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)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //app.UseMvc();
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync($"{_configuration["Message"]}; " +
                                                  $"EF Connection String: {_configuration["ConnectionStrings:ef"]}; " +
                                                  $"Author: {_configuration["Author:Name"]}");
            });
        }
    }

I have added AddJsonFile(“appComplexSettings.json”) after the call to the first appSettings.json file. Let’s take a look at the output before continue

Override the simple message; EF Connection String: Connection string to SQL database for EF; Author: Thai Anh Duc
  1. The “Message” key has been overridden by the latest config file (appComplexSettings.json). The order matters.
  2. ConnectionString:ef“: navigate to the “ConnectionString” node, then get value from its child node: ef.

It is flexible and easy to code. But, I am in VS2017 with C# code. I want OOP style. For many reasons, the settings should be modeled with OOP fashion. Let model it

    public class ConnectionStrings
    {
        public string Ef { get; set; }
    }

    public class Author
    {
        public string Name { get; set; }
        public string Job { get; set; }
    }
    public class AppSettings
    {
        public string Message { get; set; }
        public ConnectionStrings ConnectionStrings { get; set; }
        public Author Author { get; set; }
    }

With the magic of .NET Core, let’s try to create a connection between the configuration and the model. Turn out it is pretty easy.

    public class Startup
    {
        private readonly IConfigurationRoot _configuration;
        private readonly AppSettings _appSettings = new AppSettings();
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appSettings.json")
                .AddJsonFile("appComplexSettings.json");

            _configuration = builder.Build();
            _configuration.Bind(_appSettings);
        }
        // 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)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //app.UseMvc();
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync($"{_appSettings.Message}; " +
                                                  $"EF Connection String: {_appSettings.ConnectionStrings.Ef}; " +
                                                  $"Author: {_appSettings.Author.Name}");
            });
        }
    }

All I need to do is instantiate a new AppSettings instance, and then Bind to Configuration.

However, the Bind approach is manual. And in the production code, we might not use it. .NET Core supports the dependency injection with IOption. You can register it at the ConfigureServices step, and the use it everywhere. Let’s give it a try.

    public class AppSettingsController
    {
        private readonly AppSettings _appSettings;
        public AppSettingsController(IOptions<AppSettings> appSettingsOptions)
        {
            _appSettings = appSettingsOptions.Value;
        }

        public Task GetAppSettings(HttpContext context)
        {
            return context.Response.WriteAsync($"Thie is AppSettingsController: {_appSettings.Message}; " +
                                               $"EF Connection String: {_appSettings.ConnectionStrings.Ef}; " +
                                               $"Author: {_appSettings.Author.Name}");
        }
    }
    public class Startup
    {
        private readonly IConfigurationRoot _configuration;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appSettings.json")
                .AddJsonFile("appComplexSettings.json");

            _configuration = builder.Build();
        }
        public void ConfigureServices(IServiceCollection services)
        {
            // Register the ability to read options in configuration
            services.AddOptions();
            services.Configure<AppSettings>(_configuration);

            // Register a controller with a lifestyle of httprequest scoped
            services.AddScoped<AppSettingsController>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //app.UseMvc();
            app.Run(HandleRequest);
        }

        public Task HandleRequest(HttpContext context)
        {
           var controller = context.RequestServices.GetRequiredService<AppSettingsController>();
            return controller.GetAppSettings(context);
        }
    }

There are a couple of things here

  1. Remove the appSettings variable, and instead use the services.AddOptions and services.Configure to register the AppSettings binding.
  2. Create AppSettingsController class. I just want to have a feeling of Controller here. And then register it with the services.
  3. In the HandleRequest method, resolve the Controller and delegate the work.

It works perfectly. Things are much easier than I did with Docker and deployment stuff 🙁

When we look at the HandleRequest again, it looks like the MVC framework started. Basically what it does is take a request (via HttpContext), process it, and prepare the Response object.

Deployment

Application settings are environment-dependent. Depending on either Development, Staging, or Production, keys have different values. Because .NET Core allows us to add multiple files. Order matters. Therefore, we can accomplish the goal with this code

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appSettings.json")
                .AddJsonFile($"appSettings.{env.EnvironmentName}.json")
                .AddJsonFile("appComplexSettings.json");

            _configuration = builder.Build();
        }

Whatever keys defined in the second appSettings file will override the default one (the appSettings.json).

The last question remains. How do I change values from Azure Portal?

First, let build and deploy it to the Azure portal using VSTS.

Welcome back to VSTS. Click Build. Build succeeded. Good. Click Release. Oops! Failed with “ERROR_FILE_IN_USE”. There is a long story discussed here in github. The quickest solution is to stop the App Service. Release completed. The result is the same as running locally. Cool.

Open the Azure Portal, look at the configuration of my App Service, seems there is no place to change values in JSON configuration. Let’s ask Google.

I have to make some modification to meet the convention structure.

{
  "AppSettings": {
    "Message": "Setting from app settings"
  }
}
    public class AppSettingsController
    {
        private readonly AppSettings _appSettings;
        private readonly GlobalSettings _globalSettings;
        public AppSettingsController(IOptions<AppSettings> appSettingsOptions, IOptions<GlobalSettings> globalSettingsOptions)
        {
            _appSettings = appSettingsOptions.Value;
            _globalSettings = globalSettingsOptions.Value;
        }

        public Task GetAppSettings(HttpContext context)
        {
            return context.Response.WriteAsync($"Thie is AppSettingsController: {_appSettings.Message}; " +
                                               $"EF Connection String: {_globalSettings.ConnectionStrings.Ef}; " +
                                               $"Author: {_globalSettings.Author.Name}");
        }
    }
    public class Startup
    {
        private readonly IConfigurationRoot _configuration;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json")
                .AddJsonFile("appComplexSettings.json");

            _configuration = builder.Build();
        }
        public void ConfigureServices(IServiceCollection services)
        {
            // Register the ability to read options in configuration
            services.AddOptions();
            services.Configure<AppSettings>(_configuration.GetSection("AppSettings"));
            services.Configure<GlobalSettings>(_configuration);

            // Register a controller with a lifestyle of httprequest scoped
            services.AddScoped<AppSettingsController>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //app.UseMvc();
            app.Run(HandleRequest);
        }

        public Task HandleRequest(HttpContext context)
        {
           var controller = context.RequestServices.GetRequiredService<AppSettingsController>();
            return controller.GetAppSettings(context);
        }
    }

The AppSettings section will match the way web.config structured. Deploy to Azure portal again and see what happens.

Still cannot update the AppSettings directly in Azure Portal. This is a long post already. And I should call it a day (it is Friday night).

Recap

Even just a small step in the whole application development, when I get my hands on it, there are so many things I can learn from the design and implementation. The new configuration design gives us a lot of flexibilities. The system can access the configuration by using index access (by key with “:” separator), or build a configuration object (call as Option) and inject into many places. Whenever we want to use it, we can inject it. We get the dependency injection for free.

Because I have setup the deployment, I have a chance to use it. In short, I have a full lifecycle of development ready.

Happy Weekend!

 

%d bloggers like this: