雜物儲存室 Coding Blog

WSL2 安裝筆記

至從升級到 Windows 10 20H1 後 WSL2 一直都沒辦法用 vscode 連上, 今天弄了一個乾淨的環境重頭開始也順利安裝完成.

安裝

  1. 首先確認 Windows 10 是否為 Windows 10 20H1 以後的版本.

  2. 安裝 vscode 並安裝套件 Remote - WSL

  3. 使用 PowerShell 安裝 WSL.

     dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
    
  4. 安裝VirtualMachinePlatform 並 重新啟動 Windows

     dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
    
  5. 安裝 WSL 2 Linux kernel.

  6. 將 WSL 2 設定為預設

     wsl --set-default-version 2
    
  7. 安裝 Ubuntu 並設定帳號密碼.

  8. 輸入 vscode . 後會安裝 WSL Server 並啟動 vscode

ISSUE

如果啟動 Ubuntu 後出現以下錯誤代碼 0xc03a001a

Installing, this may take a few minutes...
WslRegisterDistribution failed with error: 0xc03a001a
Error: 0xc03a001a The requested operation could not be completed due to a virtual disk system limitation.  Virtual hard disk files must be uncompressed and unencrypted and must not be sparse.

Press any key to continue...

請在資料夾 %LOCALAPPDATA%/packages/ 找到 Ubuntu 的資料夾, 例如 CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc

並在進階裡把 壓縮內容以節省磁碟空間 取消勾選即可

參考資料

Windows Subsystem for Linux Installation Guide for Windows 10


NSwag 自訂物件範例

直接上 code

public void ConfigureServices(IServiceCollection services)
{
    services.AddOpenApiDocument(config =>
    {
        config.TypeMappers.Add(new PrimitiveTypeMapper(typeof(Geometry), schema =>
        {
            schema.Title = "GeoJson";
            schema.Properties.Add("type", new JsonSchemaProperty
            {
                IsRequired = true,
                Title = "GeoJson type",
                Type = JsonObjectType.String
            });
            schema.Properties.Add("coordinates", new JsonSchemaProperty
            {
                IsRequired = true,
                Title = "Coordinates"
            });
            schema.Example = new
            {
                type = "Point",
                coordinates = new[] { 121.517283, 25.047732 }
            }; //在這邊寫輸入範例
        }));
    });
}

在 Console Application 使用 DI

直接上 code

dotnet add package Microsoft.Extensions.DependencyInjection --version 3.1.4
// 取得config
var config = new ConfigurationBuilder()
                // ...
                .Build();

var services = new ServiceCollection();

// 注入 ILogger<T> 元件 (Microsoft.Extensions.Logging)
// 如果有安裝 Microsoft.Extensions.Logging.Console 就可以像 ASP.NET Core 的 console 顯示訊息
services.AddLogging(configure => LoggingHelper.SetLoggingBuilder(configure, config));

// 跟 ASP.NET Core 一樣註冊 Service
services.AddTransient<Service>();

var provider = services.BuildServiceProvider();

var service = provider.GetService<Service>(); // 使用 provider 取得所需要的 service

建立自訂的 ILogger 元件

加入參考

dotnet add package Microsoft.Extensions.Logging.Configuration

建立 LoggerConfiguration

LoggerConfiguration 的作用在於傳入自訂的 Log 元件設定檔, 比如說 LogPathLog 記錄等級.

public class CustomLoggerConfiguration
{
    public string LogPath { get; set; }

    private readonly IList<LogLevel> _logLevels;

    public CustomLoggerConfiguration()
    {
        _logLevels = new List<LogLevel>
        {
            LogLevel.Error,
            LogLevel.Critical
        };
    }

    public void AddLogLevel(params LogLevel[] levels)
    {
        foreach (var level in levels)
        {
            if (IsLogLevelEnable(level))
            {
                return;
            }
            _logLevels.Add(level);
        }
    }

    public bool IsLogLevelEnable(LogLevel level)
    {
        return _logLevels.Contains(level);
    }
}

建立 CustomLogger

繼承介面 ILogger 建立我們自訂的 Logger 元件, 將訊息寫入原本就建立好的 MyLogger 元件.

public class CustomLogger : ILogger
{
    private readonly string _name;
    private readonly Func<CustomLoggerConfiguration> _getCurrentConfig;
    private readonly MyLogger _loggerService;
    public CustomLogger(string name, Func<CustomLoggerConfiguration> getCurrentConfig)
    {
        _name = name;
        _getCurrentConfig = getCurrentConfig;
        _loggerService = new MyLogger(_getCurrentConfig().LogPath);
    }
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        try
        {
            var logLevel = MapLevel(logLevel);
            var message = formatter(state, exception);

            _loggerService.Log(_name, logLevel, message, exception);
        }
        catch (Exception e)
        {
            Debug.WriteLine(e);
        }
    }
    private static MyLogLevel MapLevel(LogLevel level)
    {
        return level switch
        {
            LogLevel.Trace => MyLogLevel.Trace,
            LogLevel.Debug => MyLogLevel.Debug,
            LogLevel.Information => MyLogLevel.Info,
            LogLevel.Warning => MyLogLevel.Warn,
            LogLevel.Error => MyLogLevel.Error,
            LogLevel.Critical => MyLogLevel.Fatal,
            LogLevel.None => MyLogLevel.Trace,
            _ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
        };
    }
    public bool IsEnabled(LogLevel logLevel) => _getCurrentConfig().IsLogLevelEnable(logLevel);
    public IDisposable BeginScope<TState>(TState state) => default;
}

建立 CustomLoggerProvider

繼承介面 ILoggerProvider, 建立 CustomLoggerProvider.

public class CustomLoggerProvider : ILoggerProvider
{
    private readonly IDisposable _onChangeToken;
    private CustomLoggerConfiguration _currentConfig;

    public CustomLoggerProvider(IOptionsMonitor<CustomLoggerConfiguration> config)
    {
        _currentConfig = config.CurrentValue;
        _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
    }

    public ILogger CreateLogger(string categoryName) => new CustomLogger(categoryName, GetCurrentConfig);

    private CustomLoggerConfiguration GetCurrentConfig() => _currentConfig;

    public void Dispose()
    {
        _onChangeToken.Dispose();
    }
}

註冊 CustomLoggerProvider

最後我們將剛的建立的 CustomLoggerProvider 註冊到 Microsoft.Extensions.DependencyInjectionIServiceCollection, 讓 ASP.NET Core 可以建立我們自訂的 CustomLogger.

in Startup.cs

services.AddLogging(builder =>
{

    Action<CustomLoggerConfiguration> configure = config =>
    {
        var logPath = Configuration.GetSection("Log:LogPath").Value;
        config.LogPath = logPath;
        config.AddLogLevel(LogLevel.Warning);
    }

    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, CustomLoggerProvider>());

    LoggerProviderOptions.RegisterProviderOptions<CustomLoggerConfiguration, CustomLoggerProvider>(builder.Services);

    builder.Services.Configure(configure);
});

這樣, 使用注入的 ILogger 時就會將資訊一併寫入我們自訂的 Log 元件了.


在 .NET Core Angular 範本使用新版本的 Angular 專案

目前 .NET Core 範本提供的是 Angular 8, 但是想使用新的版本建立專案所以研究了一下

  1. 使用 ASP.NET Core with Angular 範本建立專案.

     dotnet new angular
    
  2. 移除專案內的 ClientApp 資料夾

  3. 使用 Angular CLI 建立新的 Angular 專案

     ng new ClientApp --skipGit --skipInstall
    
  4. 修改 angular.json 內的輸出路徑, 讓 .NET Core 在 publish 或 debug 時可以找到 Angular 前端檔案

     {
         "options": {
             "outputPath": "dist"
         }
     }
    
  5. 如果 .NET Core 在 debug 時一直無法正確開啟前端專案, 則將 package.json 內的 ng server 修改成 ng serve --verbose

     {
       "scripts": {
         "start": "ng serve --verbose"
       }
     }
    

Publish

dotnet publish -c release

修改 html base tag 的 href 屬性

如果網站不是放在網站根目錄, 需要修改 package.json 裡的 build 指令, 或是發行後手動修改 index.html 裡的 base HTML tag.

{
  "scripts": {
    "build": "ng build --base-href=/YourBasePath/"
  }
}