只有累積,沒有奇蹟

2019年4月29日 星期一

[.NETCore] Windows Service - 服務並未以適時的方式回應啟動或控制請求。

前言
在上一篇有提到如何使用指令 註冊 Window Service 服務,提到了如何用指令操作 Windows Service 看啟用的狀態,但有時在啟動時會發生錯誤造成啟動失敗的狀況發生,舉例來說在啟動服務時跳出  'Windows 無法啟動,本機電腦的 TestService 服務,錯誤 1503 : 服務並未已適時的方式回應啟動獲控制請求。 這篇要說明的是在註冊服務當下發生異常的處理方式若有問題歡迎提出一起討論或是給予指導。

解決方案
首先第一步要知道啟動失敗的原因,當 Windows Service 在啟動服務失敗時,會透過 GetLastError函數回傳錯誤訊息,並將其錯誤原因與錯誤代碼做 mapping,因此在錯誤訊息中可以看到錯誤代碼 1053,每個錯誤代碼背後都代表特定錯誤原因,可以透過清單查詢代碼對應的錯誤原因 傳送門
在 1053 對應的錯誤訊息為 : The service did not respond to the start or control request in a timely fashion.,疑似執行的程式內容並未執行正確,錯誤訊息內容太籠統無法得到處理方向,因此進行下一步處理。

事件檢視器
事件檢視器 Event Viewer 是提供使用者查看電腦中發生大大小小事情的工具,Server 的啟動關閉紀錄、IIS 站台的狀況、或是當應用程式忽然異常時都會將其 Log 資訊寫到事件檢視器中,因此當 Windows Service 啟動失敗時,也可以使用事件檢視器來查看可能異常的原因,開啟方式為在左下角開始,輸入 Event 並按下 Enter
可以透過時間搜尋發生問題的當下系統中的 log 資訊,可以發現啟動時 Windows Service 紀錄的錯誤為 服務並未以適當的方式回應啟動或控制請求,根本原因是等候 TestService 服務連線時發生逾時 (30000 毫秒) 造成
回到代碼發現舊有的代碼 Console 應用程式在啟動時並未加上 Log 紀錄,因此在 Console 進入點 main 方法加上 StreamWriter 加入 log 尋找可能錯誤原因,另外在與同事討論後提到 Serilog 在作為 Windows Service 時要特別注意 Log 路徑問題,建議在使用 Serilog 在啟動時加上指定路徑,因此在 Initial Log 時加入下列代碼
private static void InitialLogging()
{
    var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
    var pathToContentRoot = Path.GetDirectoryName(pathToExe);
    Directory.SetCurrentDirectory(pathToContentRoot);

    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .WriteTo.Console()
        .WriteTo.File("logs/App_.txt", rollingInterval: RollingInterval.Day)
        .CreateLogger();
}
接著在開啟 cmd 輸入指令重新啟動 Windows Service,指令為  start ServiceName  要啟動時狀態為 START_PENDING (2)
D:\>sc start testservice

SERVICE_NAME: testservice
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 26300
        FLAGS              : 
當啟動完畢之後,狀態就會更新為 RUN (4) 
SERVICE_NAME: testservice
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
服務啟動正常除蟲完畢,打完收工 !!

參考
powershell create windows service

2019年4月24日 星期三

[.NET] 當 Dapper 遇到 SQL uniqueidentifier Type

問題 
Dapper 的輕巧與容易上手程度是大家有目共睹的,在公司中大部分專案也是選擇 Dapper 
資料庫做存取資料的動作,近期接獲同事詢問在開發時透過 Stored Procedure 存取資料庫某個 Table 中欄位資料型別為 uniqueidentifier (GUID) 一直失敗,Error 錯誤訊息為  Object must implement IConvertible ,這篇就針對此案例作簡單紀錄與分享若是有不清楚或是錯誤的地方歡迎討論予糾正

解決方法 
首先先還原案發現場,代碼如下
// Query
public IEnumerable<MemberModel> GetAllMember(int countryCode)
{
    var parameters = new DynamicParameters();
    parameters.Add("@countryCode", countryCode, DbType.Int16);
    parameters.Add("@output", string.Empty, DbType.String, ParameterDirection.Output);

    return Query<MemberModel>("[dbo].[usp_Member_GetAllMember]", parameters).ToList();
}

protected virtual IEnumerable<T> Query<T>(string name, SqlMapper.IDynamicParameters parameters)
{
    IEnumerable<T> result;
    try
    {
 using (var connection = new SqlConnection(connectionString))
 {
   result = connection.Query<T>(name, parameters, commandType: CommandType.StoredProcedure);
 }
    }
    catch (Exception e)
    {
 logger.LogError(e, name);
 throw;
    }
 
    return result;
}

// Model
public class MemberModel
{
    public string ID { get; set; }
    public string Name { get; set; }
}
在資料庫所要存取的 Table 設計 ID 資料型態為 uniqueidentifier,執行以上代碼會發生錯誤訊息如下
2019-04-24 18:09:13.8418||ERROR|.ctor|[dbo].[usp_Member_GetAllMember] System.Data.DataException: Error parsing column 1 (ID=d27ee4a2-146c-43f7-afc5-4e6cafe262b3 - Object) ---> System.InvalidCastException: Object must implement IConvertible.
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at Deserializece44011c-705b-4af9-bbdd-7b47377c8cc6(IDataReader )
--- End of inner exception stack trace --- 
取得資料後在 parsing 欄位值時發生錯誤 (Error parsing),錯誤型別為System.InvalidCastException,也就是說有抓到欄位值為 d27ee4a2-146c-43f7-afc5-4e6cafe262b3,但在 Parse 的時候轉型別失敗,遇到此情況有兩種解決方式

調整回傳欄位
在目前此 Case 可以看到回傳型別物件 MemberModel 中的 ID 欄位為字串 String,因此將 SP ID欄位回傳型別由原本的 uniqueidentifier 改為字串,在 MSSQL 中可以使用 CAST and CONVERT 進行轉換的動作,如下 
select convert(nvarchar(36), ID) as ID

調整 Model
原先的欄位型態為 uniqueidentifier 對應到 C# 中就是 Guid 型別,因此有可能因為此 SP 回傳值有不能修改的情況,因此可以修改承接的 ModelName 對應的欄位 ID,由原先的 string 改為 Guid 類型,調整如下 
public class MemberModel
{
    public Guid { get; set; }
    public string Name { get; set; }
}
調整完畢之後,即可正常執行取得資料
Enjoy Coding !!

參考
CAST and CONVERT 

2019年4月22日 星期一

[.NETCore] Quartz.NET 初體驗

前言
在 ASP.NET 中常見的排程框架不外乎 Quartz.NET 與 Hangfire 兩種,過去自己在開發上比較常用到 Hangfire 搭配其後台管理介面,在使用上可以說是相當方便與容易上手,最近在新專案也有遇到 schedule 的需求,同事大推 Quartz.Net 來擔任工作排程器的工作,Quartz.Net 是一套功能齊全的工作排程框架,由 Java 熱門的排程框架 Quartz 移植到 .NET 上,open source 且提供彈性的設定讓開發者使用,在新版 3.0.7 支援 .NET Core 2.1 版本,今天就來簡單介紹 Quartz.NET  的安裝與基本應用使用若有問題或是錯誤的地方歡迎各位高手給予指導

安裝
首先,先建立一個名稱為 QuartzNetConsole 的 Console 專案,接著開啟 Nuget Package Mnage 輸入 "quartz" 搜尋,安裝目前最新版的 Quartz.NET 套件
或是在 Nuget Package Console 輸入指令
Install-Package Quartz 
如果有 Json 序列化需求,也可以一併加入 Quartz.Serialization.Json

使用
在使用前先介紹 Quartz.Net 中的幾個重要 API 與 Interface

  • IScheduler  : 主要工作排程 API、透過 Start 方法 run 排程。
  • JobBuilder、IJobDetail : 透過 JobBuilder.Create 產生 IJobDetail 的 Instance。  
  • TriggerBuilder、ITrigger : 透過 TriggerBuilder.Create 產生 ITrigger 的 Instance。   
  • IJob : 自定義的排程類別要實作的 Interface
  • 簡單整理關係圖如下
    接著在 Console 專案 Program 啟動加入下列代碼
    class Program
    {
        static void Main(string[] args)
        {
            // trigger async evaluation
            RunProgram().GetAwaiter().GetResult();
        }
    
        private static async Task RunProgram()
        {
            try
            {
                // 建立 scheduler
                StdSchedulerFactory factory = new StdSchedulerFactory();
                IScheduler scheduler = await factory.GetScheduler();
    
                // 建立 Job
                IJobDetail job = JobBuilder.Create<ShowDataTimeJob>()
                    .WithIdentity("job1", "group1")
                    .Build();
    
                // 建立 Trigger,每秒跑一次
                ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity("trigger1", "group1")
                    .StartNow()
                    .WithSimpleSchedule(x => x
                        .WithIntervalInSeconds(1)
                        .RepeatForever())
                    .Build();
    
                // 加入 ScheduleJob 中
                await scheduler.ScheduleJob(job, trigger);
    
                // 啟動
                await scheduler.Start();
    
                // 執行 10 秒
                await Task.Delay(TimeSpan.FromSeconds(10));
    
                // say goodbye
                await scheduler.Shutdown();
            }
            catch (SchedulerException se)
            {
                await Console.Error.WriteLineAsync(se.ToString());
            }
        }
    }
    其中要執行的 ShowDataTimeJob 類別代碼如下
    internal class ShowDataTimeJob :IJob
    {
        public async Task Execute(IJobExecutionContext context)
        {
            await Console.Out.WriteLineAsync($"現在時間 {DateTime.Now}");
        }
    }
    程式說明

  • 建立 scheduler : 透過 factory.GetScheduler() 取得 schedule
  • 建立 Job : 使用 JobBuilder.Create 建立 ShowDataTimeJob,並定義其 key 與 group 名稱
  • 建立 Trigger : 使用 TriggerBuilder 建立 ITrigger ,定義其 key 與 group 名稱,並設置立即執行執行時間為每一秒執行一次,其中 ShowDataTimeJob 的 Execute 就是此執行。執行結果如下


  • 感想
    以上透過簡單的說明與操作,就可以快速的 Demo 出 Quartz.NET 工作排程的功能,但其實在現實生活中代碼往往都不會那麼簡單,如果有興趣可以先參考官方網站的開發說明文件,日後如果遇到在分享給各位,Happy Coding :)

    參考
    ASP.NET 程式中的背景工作 (3) - 使用 Quartz.NET
    Quartz.NET Quick Start Guide

    2019年4月17日 星期三

    [UnitTest] 使用 Fluent Assertions 增加單元測試碼可讀性

    前言
    過去在撰寫單元測試代碼時都是使用 NUnit 內建的 Assert.AreEqual 來驗證是否符合預期,雖然早已聽過 Fluent Assertions 盛名但並未實際使用過,直到最近在與同事討論時同事大推發現真的很不錯,讓戴碼的可能性增加不少,想起之前上 91 Training 時不斷強調測試代碼可讀性的重要性,這一篇就來簡單介紹 Flnent Asserentions 的安裝與使用若有問題或是錯誤的地方歡迎各位高手給予指導

    安裝 Fluent Assertions
    Fluent Assertion 支援 .NET Framework 與 .NET Core,常用的測試框架像是 NUnit、MSTest、xUnit 等都支援,一開始在 Visual Studio 2019 建立名稱叫做 FluentAssertionsConsoleApp 的 ConsoleApp專案,接著在專案 Program.cs 輸入要測試的程式碼 Add 方法,如下所示
    public class TestMethod
    {
        public int Add(int numberOne, int numberTwo)
        {
            return numberOne + numberTwo;
        }
    }
    接著要建立測試專案,在 Visual Studio 2019 建立專案介面有做調整,可以透過搜尋輸入框及下拉選單過找到想要建立的專案範本,這陣子使用操作上覺得挺方便的,如下圖所示,在搜尋框輸入 NUnit 選定 NUmit 專案並按下一步 
    建立完畢 NUnit 測試專案後,下一步是在測試專案安裝 Fluent Assertions nuget 套件,步驟如下
    Step 1 : 按下 CTRL + Q 輸入 Nuget,開啟 nuget 
    Step 2 : 在 Nuget Package Mnager 輸入 FluentAssertions,目前最新版為 5.6.0 按下安裝
    安裝完畢後確認測試專案有 Fluent Assertional 即代表安裝成功

    測試代碼
    接著要開始測試 TestMethod 中的 Add 方法,開始嘗試用 Fluent Asserentions 語法寫測試,代碼如下
    using FluentAssertions;
    using FluentAssertionsConsoleApp;
    using NUnit.Framework;
    
    namespace Tests
    {
        public class Tests
        {       
            [Test]
            public void AddTest_Using_FluentAssertions()
            {
                var testMethod = new TestMethod();
                var actual = testMethod.Add(2, 4);
                actual.Should().Be(6);
            }
        }
    }
    使用 Fluent Assertions 寫法後代碼變得容易理解許多,actual.Should().Be(6) 用口語化解釋為 " 測試的結果應該為 6 ",另外 Be 方法也提供 Because 參數,加上後可以讓語句與閱讀上更加通順,加上 Because 後代碼如下
    [Test]
    public void AddTest_Using_FluentAssertions_Becasue()
    {
        var testMethod = new TestMethod();
        var actual = testMethod.Add(2, 2);
        actual.Should().Be(4, because:"2+2=4");
    }
    測試案例加上 because 後當測試失敗時,可以透過錯誤訊息內容讓你或是你的團隊成員更容易理解測試的目的,測試失敗的 Testcase 如下
    透過 Resharper 可以看到 Should 擴充方法內容如下,Should 與 Be 都是 FluentAssertions 針對 int 型別定義的擴充方法 ( Extension Methods ),透過 F12 後可以看到內容

    /// <summary>
    /// Returns an <see cref="T:FluentAssertions.Numeric.NumericAssertions`1" /> object that can be used to assert the
    /// current <see cref="T:System.Int32" />.
    /// </summary>
    [Pure]
    public static NumericAssertions<int> Should(this int actualValue)
    {
      return new NumericAssertions<int>((object) actualValue);
    }
    擴充方法可以參考 MSDN 說明 : 傳送門,擴充方法可以在很多地方看到像是 LINQ 中的 Where ,針對集合物件做篩選的動作在回傳 IEnumerable<T>,在 Fluent Assertions 針對現有類別加入方法,方便使用 Should、Be 或是其他語法,方便在單元測試中撰寫,以下列出一些常用的方式,

    public void TestBasicMethod()
    {
        // object
        object obj = null;
        obj.Should().BeNull("because the obj is null");
        obj.Should().NotBeNull();
    
        // string 
        var something = "something";
        something.Should().BeEmpty();
        something.Should().NotBeEmpty();
    
        // datetime 
        var time= new DateTime();
        time.Should().HaveYear(2019);
        time.Should().HaveDay(4);
        time.Should().HaveMonth(3);
        time.Should().HaveHour(22);
        time.Should().HaveMinute(15);
        time.Should().HaveSecond(0);
    
        // dictionary 
        var dic = new Dictionary<int, string>()
        {
            {1, "Marcus"},
            {2, "Flash"},
            {3, "Neil"}
        };
        dic.Should().NotBeEmpty();
        
        // type
        something.Should().BeOfType<string>("because a {0} is set", typeof(string));
        something.Should().BeOfType(typeof(string), "because a {0} is set", typeof(string));
    
        // equal 
        string otherObject = "whatever";
        something.Should().Be(otherObject, "because they have the same values");
        something.Should().NotBe(otherObject);
    
        // exception 
        something.Should().BeOfType<Exception>()
            .Which.Message.Should().Be("Other Message");
    }

    如果您希望測試代碼更容易理解,建議你可以使用 Fluent Assertion Library 來協助撰寫單元測試,更多的測試語法可以參考官方文件說明,這篇就簡單介紹 Fluent Assertion,日後如果有遇過不錯的寫法也會在一併分享給各位,Happy Coding :)

    參考
    Fluent Assertions 
    讓單元測試代碼更好寫、好讀:Fluent Assertions

    2019年4月16日 星期二

    [.NETCore] ASP.NET Core 環境佈署設定 appsettings.json

    前言
    在上一篇 [.NETCore] ASP.NET Core - ENVIRONMENT 提到了如何設定環境變數值,今天要提的也是與 Configuration 相關的,在開發時往往會因為不同環境讀取不同 Config 的需求,舉例來說如果 Development 與 Staging 環境的資料庫不同時,就會有在不同環境讀取各自的 DB 連線字串設定需求,這篇就介紹如何透過 .NET Core 專案預設的 appsettings.json 在不同環境設定及讀取相對應的組態設定檔資訊,若有問題或是錯誤的地方歡迎各位高手給予指導

    appsettings.json 
    首先,建立一個 .NET Core Application 應用程式 ( 測試的版本為 2.2 ),在建立完畢之後可以看到專案的 program.cs 預設內容如下
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    
    namespace CoreLabApplication
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                CreateWebHostBuilder(args).Build().Run();
            }
    
            public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>();
        }
    }
    在程式主要進入點一開始先呼叫靜態方法  CreateWebHostBuilder ,其內容是新增一個 default 的 Web Host Builder,並建立預設的 Configuration 與 Settings 資訊,在透過  IWebHostBuilder  中的 UseStartup 擴充方法指定 TStartUp 為同樣是內建的 StartUp 類別,接著使用 build 方法建立 WebHost。剛剛提到  CreateWebHostBuilder  會建立預設的 Configuration 資訊,接著我們為了瞭解實作細節透過反射查看此方法骨子裡面做了麼事
    /// <summary>
    /// Initializes a new instance of the <see cref="T:Microsoft.AspNetCore.Hosting.WebHostBuilder" /> class with pre-configured defaults.
    /// </summary>
    /// <param name="args">The command line args.</param>
    /// <returns>The initialized <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns>
    public static IWebHostBuilder CreateDefaultBuilder(string[] args)
    {
      WebHostBuilder hostBuilder = new WebHostBuilder();
      if (string.IsNullOrEmpty(hostBuilder.GetSetting(WebHostDefaults.ContentRootKey)))
        hostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
      if (args != null)
        hostBuilder.UseConfiguration((IConfiguration) new ConfigurationBuilder().AddCommandLine(args).Build());
      hostBuilder.UseKestrel((Action<WebHostBuilderContext, KestrelServerOptions>) ((builderContext, options) => options.Configure((IConfiguration) builderContext.Configuration.GetSection("Kestrel")))).ConfigureAppConfiguration((Action<WebHostBuilderContext, IConfigurationBuilder>) ((hostingContext, config) =>
      {
        IHostingEnvironment hostingEnvironment = hostingContext.HostingEnvironment;
    
        config.AddJsonFile("appsettings.json", true, true)
            .AddJsonFile("appsettings." + hostingEnvironment.EnvironmentName + ".json", true, true);
    
        if (hostingEnvironment.IsDevelopment())
        {
          Assembly assembly = Assembly.Load(new AssemblyName(hostingEnvironment.ApplicationName));
          if (assembly != (Assembly) null)
            config.AddUserSecrets(assembly, true);
        }
        config.AddEnvironmentVariables();
        if (args == null)
          return;
        config.AddCommandLine(args);
      })).ConfigureLogging((Action<WebHostBuilderContext, ILoggingBuilder>) ((hostingContext, logging) =>
      {
        logging.AddConfiguration((IConfiguration) hostingContext.Configuration.GetSection("Logging"));
        logging.AddConsole();
        logging.AddDebug();
        logging.AddEventSourceLogger();
      })).ConfigureServices((Action<WebHostBuilderContext, IServiceCollection>) ((hostingContext, services) =>
      {
        services.PostConfigure<HostFilteringOptions>((Action<HostFilteringOptions>) (options =>
        {
          if (options.AllowedHosts != null && options.AllowedHosts.Count != 0)
            return;
          string str = hostingContext.Configuration["AllowedHosts"];
          string[] strArray1;
          if (str == null)
            strArray1 = (string[]) null;
          else
            strArray1 = str.Split(new char[1]{ ';' }, StringSplitOptions.RemoveEmptyEntries);
          string[] strArray2 = strArray1;
          HostFilteringOptions filteringOptions = options;
          string[] strArray3;
          if (strArray2 == null || strArray2.Length == 0)
            strArray3 = new string[1]{ "*" };
          else
            strArray3 = strArray2;
          filteringOptions.AllowedHosts = (IList<string>) strArray3;
        }));
        services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>((IOptionsChangeTokenSource<HostFilteringOptions>) new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
        services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
      })).UseIIS().UseIISIntegration().UseDefaultServiceProvider((Action<WebHostBuilderContext, ServiceProviderOptions>) ((context, options) => options.ValidateScopes = context.HostingEnvironment.IsDevelopment()));
      return (IWebHostBuilder) hostBuilder;
    }
    透過反射可以得知主要是建立一個 WebHostBuilder 並定義相關設定初始值,由於代碼細節過多因此整理  CreateWebHostBuilder  方法重點如下

    appsettings.json 預設配置檔案
    - config.AddJsonFile("appsettings.json", true, true)
    首先 config Provider 來源設定為 Json 格式,並使用內建的 appsettings.json 作為 config 來源,並定義 reloadOnChange 為 true,也就是當檔案內容更新時後會重新載入。

    - AddJsonFile("appsettings." + hostingEnvironment.EnvironmentName + ".json", true, true);
    接著會在使用環境變數尋找相關環境設定檔,如果存在就覆蓋原本的預設值,舉例來說,在 Development 環境底下,此代碼就會尋找根目錄中是否存在 appsettings.Development.json 檔案,如果 appsettings.json 與 appsettings.Development.json 有設定值相同時,會以後面的值為主 (覆蓋前者)。

    備註 : 在 .NET Core 2.1 後的版本使用  IWebHostBuilder  取代既有的  IWebHost  方式,在過去如果要根據環境讀取相關 appsettings.json 資訊 ( 或是 Console )大多都要自行撰寫下列代碼
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .Build();
    在.NET Core 2.1 之後則提供靜態方法  CreateWebHostBuilder  將常用 Configuration 做設定,詳細可以參考 Migrate from ASP.NET Core 2.0 to 2.1

    Configuration 載入順序
    在 .NET Core 中提供不同 Provider 方式讀取/載入相關設定資訊,舉例來說在上述代碼中就可以看到有用到 CommandLine、Environment Variable、File (.Json)、UserSecrets 等多種方式,如果在自訂時也可以參考此種設定,詳細可以參考 MSDN 說明 : 傳送門,雖然有多種載入 Configuration 的方式,但讀取的優先順序也是很重要的,在啟動時後面載入的會將前者重複定義的覆蓋掉,因此在 Configuration 載入時載入順序如下
    • File 檔案
    • Azure Key Vault
    • User Secrets ( 僅 Development 環境 )  
    • 環境變數
    • 命令列 
    由於有順序性問題,因此在設定時候需多加小心。

    新增設定 
    透過以上詳細說明,可以得知如果要設定不同環境的設定檔時,僅需要加上  appsettings.{環境變數}.json 即可,就可以透過內建的組態設定機制來區分環境,取得 appsettings.json 方式之前有介紹過,這裡就不在多加說明,詳細可以參考 傳送門,下列為新增 Development 與 Production 範例

    縮排設定 
    如果想要調整 appsettings.json 縮排將前墜詞相同的放一起,可以透過以下步驟調整
    Step 1 : 開啟 Visual Studio 2019,在專案上滑鼠 double click 開啟專案 csproj 
    Step 2 : 加上以下代碼

    <ItemGroup>
        <Content Include="appsettings.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </Content>
        <Content Include="appsettings.Development.json;appsettings.Production.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          <DependentUpon>appsettings.json</DependentUpon>
        </Content>
      </ItemGroup>
    設定完成 !!! 設定成功畫面如下

    心得
    在查詢 appsettings 相關文章時大多都還是舊語法相關的資料,且日期多為 2018 年中左右,沒一年的時間就已經出新的語法版本從 2.1 到目前的最新的 3.0 preview,目前在查詢相關文件時還是以 MSDN 為主,避免得到錯誤的資訊造成誤判,希望自己可以盡快補齊 .NET Core 相關資訊,繼續努力往自己的目標邁進 :)

    參考
    ASP.NET Core 的設定
    What is the difference between IWebHost WebHostBuilder BuildWebHost

    Copyright © m@rcus 學習筆記 | Powered by Blogger

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com