[Day09] 从 appsettings.json 取得设定

昨天我们新增了一个 UserServiceWithFile 操作本地端档案来管理使用者资料,而档案的路径是写死在程序码里面的。这种写死的设定有几个缺点:

  1. 不容易变更设定
    如果要变更设定,就必须修改程序,修改程序之後又要重新布署,不但麻烦还有增加错误的风险。
  2. 资安疑虑
    程序码终究要进入版控,机密的资讯跟着程序码进版控会有资安的疑虑。昨天的范例只有档案路径所以还勉强可以接受,以後用 MySQL 管理资料时,如果让资料库的连线资讯进到版控那问题就大了。

比较好的作法是把组态设定写在设定档(appsettings.json)里,再让我们的程序从这里读取设定。我们今天要来介绍如何在 .NET Web API 专案中,中读取组态设定。

把设定加入 appsettings.json

appsettings.json 是 .NET 5 Web API 专案预设的设定档,我们的程序在执行的时候,会从这里取得组态设定。但仔细看一下右边的方案总管,会发现 appsettings.json 其实可以展开,展开後会看到 appsettings.Development.json。这两者的差别是,debug 模式下 appsettings.Development.json 会盖掉 appsettings.json 的设定。

由於 appsettings.Development.json 不会进版控(因为正式环境不会用它),我们就能放心的把资料库连线等机密资讯存在这边,愉快的 debug。等到程序部属到正式环境之後,再用加密连线的方式,把机密资讯写到正式的 appsettings.json。如此一来,我们就能避免因为版控泄漏机密资讯。

现在,让我们把测试用的设定加入这两个档案:

// appsettings.json
{
  "Logging": {
	// 这个区块不变
  },
  "TestConfiguration": {
    "EnableLog": false,
    "FileName": "" // 这边先挖空才进版控,以後部属的时候再补上去
  }
}
// appsettings.Development.json 
{
  "Logging": {
	// 这个区块不变
  },
  "TestConfiguration": {
    "EnableLog": false,
    "FileName": "D:/TestUsers.csv"
  }
}

懒人作法 – 注入整个设定档

要取得设定档,一个最简单的做法是直接把 Startup.cs 的 public 属性 「Configuration」,以 IConfiguration 的形式注册到服务容器。下面这行程序码把 IConfiguration 这个介面以 singleton 的生命周期注册到服务容器让别人依赖,而实作这个介面的是「Configuration」这个属性。

public void ConfigureServices(IServiceCollection services)
{
    // 其他部分不变
    services.AddSingleton<IConfiguration>(Configuration); // 加入这一行 
    services.AddScoped<IUserCRUD, UserServiceWithFile>();
}

然後一样注入给 Controller,再从注入的IConfiguration 介面取得设定值

public class UserServiceWithFile : IUserCRUD
{
    private readonly string _fileName;
    private readonly List<User> _users;
    private readonly IConfiguration _config;

    public UserServiceWithFile(IConfiguration config)
    {
        _config = config;
        _fileName = _config.GetSection("TestConfiguration:FileName").Value;
        _users = ReadUsersFromFile(_fileName);
    }

   // 其他部分不变
}

上面的程序码使用 GetSection 这个方法,从 appsettings.json 取得设定值。用 GetSection("xxx").Value 取得设定有几点要注意

  • 如果设定值是巢状的 json 物件,可以用冒号(:)指定内层的 key 值
  • 如果指定的 key 值不存在,会回传 null
  • 取得的值不管 json 里是什麽资料型态,这里一律变成 string

改完之後,我们就成功的从设定档中取得资讯了!

注入 IOptions

上面的做法很简单而且已经能满足我们的需求,但是笔者更喜欢接下来要介绍的作法。这个做法可以直接取得强型别的设定物件。这个方法也很简单,只需要以下三个步骤

1 宣告一个用来储存设定的类别,这边要注意类别的属性名称要与 json 物件的 key 值一致,但不分大小写。

public class UserServiceOptions
{
    public bool EnableLog { get; set; }
    public string FileName { get; set; }
}

2 在 Startup.cs 抽取设定档的区段,将它对应到刚刚宣告的类别,接着注册到服务容器

public void ConfigureServices(IServiceCollection services)
{
    // 其他部分不变
    services.Configure<UserServiceOptions>( // 改成这一行
        Configuration.GetSection("TestConfiguration"));  
    services.AddScoped<IUserCRUD, UserServiceWithFile>();
}

上面那段程序码代表从 appsettings.json 中抽取 “TestConfiguration” 区段,然後把这个 json 物件对应到 UserServiceOptions 这个类别,最後以 IOptions 的介面注册到服务容器。

3 注入到 Controller 并使用。

public class UserServiceWithFile : IUserCRUD
{
        private readonly string _fileName;
        private readonly List<User> _users;
        private readonly IOptions<UserServiceOptions> _option;

        public UserServiceWithFile(IOptions<UserServiceOptions> option)
        {
            _option = option;
            _fileName = _option.Value.FileName;
            _users = ReadUsersFromFile(_fileName);
        }

	   // 其他部分不变
}

改完收工,我们的 API 程序就能从设定档读取设定。以後要变更设定,只要修改 appsettings.json 里的值,然後重启程序就可以了。

明天开始,我们将一步一步地介绍怎麽把目前完成的专案丢上云端执行。


<<:  [Angular] Day9. Transforming Data Using Pipes

>>:  Day 09:今天又想不出标题了!tmux plugin 和 mouse mode

[Day30] 总结

终於到最後一天啦! 很感谢 iT 铁人赛,在这一个月内,不只把我本来知道的东西透过文字或影像记录下...

人的管理 - 危机感 vs. 安全感

我在 MIT 的两年改变了我很多。其中一个重要的体验是有很多世界级的创业家、执行长会来学校演讲,甚至...

Day 29 - 开发流程(上) 瀑布式(Waterfall Model) & 敏捷式(Agile Model)

终於快结束30天的挑战了,专案开发的知识点除了环境安装、技术学习以外,还有一个重点知识,那就是专案的...

[Day - 25] - Spring Reactor Processor 之交易所OrderBook实作与设计

Abstract 好的,我们已先行叙述过Flux及Mano两项角色套件,最後,我们开始进行介绍Rea...

[day15]几个常用的LineAPI

今天Heroku大当机0rz,写一点Line API的使用教学 LineSDK已经将大部分的实作功能...