dotnet core 讀取 big 5 檔案

讀取古老的 windows xp 程式所產出的檔案,最大的問題就是編碼。

因為這些程式仍然採用 big-5 編碼,因此往往會產生亂碼,而且很理解原因(因為在 windows 使用記事本開啟仍然可以看到中文)。

此時,最簡單的方案就是直接加入 Encoding.GetEcoding(“big5”) 解決。然而這個在 dotnet core 不是內建 Encoding 的 code pages,必須要額外安裝:

Install-Package System.Text.Encoding.CodePages

安裝完畢後,在程式一開始就將編碼擴充:

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

之後就可以使用以下方式讀取中文了:

using (StreamReader sr = new StreamReader(file, Encoding.GetEncoding("big5")))
{
    var line = sr.ReadLine();
}

建立 .Net Standard Library

主要重點:透過 .net standard library 可以提供給 .net core and .net framework 的專案使用

簡化維護兩套的困難。

依據微軟的架構, .net standard 是作為各個 framework 底層使用:

因此,我們在維護共享元件時候,只有選擇 .net standard 才可以給各個平台架構所使用。

建立方式很簡單,直接在【新增專案】中選擇 .net standard 即可:

讓 swagger 可以使用 IdentityServer 4 的測試驗證

IdentityServer 4 提供線上可以測試的網址: https://demo.identityserver.io 可以讓專案直接連上進行測試。以下採用 Swashbuckle.AspNetCore 範例說明。

首先,nuget 安裝 IdentityServer4.AccessTokenValidation & Swashbuckle.AspNetCore  因為設定的 web api 只使用 Access token 作為驗證的工具,因此不需要安裝整套 IdentityServer

IdentityServer 4 deom site:主要使用 implicit, api scope:

設定 WebAPI 使用 Identity Server Token
1. 註冊 Identity Server Authentication library in ConfigureService:

  services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
    .AddIdentityServerAuthentication(options =>
    {
        options.Authority = "https://demo.identityserver.io";
        options.ApiName = "api";
    });

2.設定 Swagger 產生時候,加入 Oauth 的設定 in ConfigureService:

  services.AddSwaggerGen(s =>
{
    s.SwaggerDoc("v1", new Info { Title = "Test WebAPI", Version = "V1" });
    s.AddSecurityDefinition("oauth2", new OAuth2Scheme
    {
        Flow = "implicit",
        AuthorizationUrl = "https://demo.identityserver.io/connect/authorize",
        Scopes = new Dictionary<string, string> { { "api", "Access to the api" } }
    });
});

透過指定 identityserver 認證,將頁面導入 demo site,其中 flow & scope 對應前面設定的 client_id 的對應項目。

3.按下【Authorize】時候就會出現以下畫面,填寫之前指定的 client_id: implicit 即可導入登入頁面

輸入 demo site 上指定的使用者帳號:bob/bob 就完成登入:

4. swagger 設定 endpoint 需要認證,同時設定 Authorize 的錯誤 401, 403 回應。這裡透過實作 IOperationFilter & 加入 AddSwaggerGen:

public class AuthorizeCheckOperationFilter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        var authAttrs = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
            .Union(context.MethodInfo.GetCustomAttributes(true))
            .OfType<AuthorizeAttribute>();
 
        if (authAttrs.Any())
        {
            operation.Responses.Add("401", new Response { Description = "尚未被授權存取" });
            operation.Responses.Add("403", new Response { Description = "網頁禁止被存取" });
            operation.Security = new List<IDictionary<string, IEnumerable<string>>>
            {
                new Dictionary<string, IEnumerable<string>>{{"oauth2", new [] {"api"}}}
            };
        }
    }
}

簡單說明就是會將所有的介面檢查是否具備 Authorize Attribute,如果有,則加入 security 接受 access token: oauth2 與 api scope
在 AddSwaggerGen 加入:

s.OperationFilter<AuthorizeCheckOperationFilter>();

如此,當呼叫 swagger 頁面時候,就會出現以下錯誤:

最後在 Configure 中,設定 app 加入處理 swagger UI 的 middleware:

  // Enable Middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
 
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// Specifying the Swagger JSON endpoint
app.UseSwaggerUI(s =>
{
    s.SwaggerEndpoint("/swagger/v1/swagger.json", "Rabbit framework WebAPI");
    s.OAuthClientId("implicit");
    s.OAuthAppName("Rabbit framework WebAPI");
});

參考文章:ASP.NET Core Swagger UI Authorization using IdentityServer4

asp.net core 2.0 升級 2.1

升級 2.1 的主要原因是因為從 2.1 開始,微軟加入了 LTS 至少三年的支持:

.NET Core 2.1 will be a long-term support (LTS) release. This means that it is supported for three years. We recommend that you make .NET Core 2.1 your new standard for .NET Core development.

以下說明如何完成升級的每一個步驟

安裝 dotnet core 2.1 SDK

首先要先安裝 dotnet core 2.1 SDK,下圖可以看到目前並只支援 2.0

可以直接點選【安裝其他架構…】就會導入到安裝 dotnet core for VS 的畫面,或者直接到 dotnet core SDK 安裝 下載。執行後就會看到以下安裝畫面,依據畫面指示逐步安裝即可:

安裝完畢後,就可以看到 2.1 的選項:

 

將現有 asp.net core 2.0 專案升級到 2.1

主要參考官方說明: Migrate from ASP.NET Core 2.0 to 2.1

         1. 變更專案為 2.1

可以直接在專案屬性中變更,或者手動調整 TargetFramework 2.0 -> 2.1

 

 

2. 變更 nuget package:原先的 .All 變為 .App,同時也沒有版本的設定

  <ItemGroup>
    <PackageReference Include=Microsoft.AspNetCore.All Version=2.0.5 />
    <PackageReference Include=Microsoft.VisualStudio.Web.CodeGeneration.Design Version=2.0.2 />
  </ItemGroup>
改為

  <ItemGroup>
    <PackageReference Include=Microsoft.AspNetCore.App />
    <PackageReference Include=Microsoft.VisualStudio.Web.CodeGeneration.Design Version=2.1.0 PrivateAssets=All />
  </ItemGroup>

 

        3. 移除 DotnetCliToolReference 這些內容已經改成 Globals 支援了:

  <ItemGroup>
    <DotNetCliToolReference Include=Microsoft.VisualStudio.Web.CodeGeneration.Tools Version=2.0.2 />
  </ItemGroup>

 

4. 修改 Progarm 呼叫 BuildWebHost 的寫法,改為 CreateWebHostBuilder。這個步驟不修改也是可以執行的(請注意,這個步驟不要執行,因為執行之後可以會造成 update-dabase 失敗Unable to create an object of type ‘xxx’. Add an implementation of

‘IDesignTimeDbContextFactory<xxx>’)。

 

5. 修改 Startup 的寫法,主要重點是  app.UseBrowserLink(); 已經被移除。

6. 如果有引用到其他專案(例如 UnitTest Project),這時候要注意預設的版本,在這個版本中,單元測試可以忽略 Nuget 許多套件,原因是會自動由 Global 的設定中帶入;但因為我們是由 dotnet core 2.0 升級,預設的套件還是停留在 2.0,因此會跟 reference project 衝突。這時候必須要明確定義有哪些 packages 需要引用;大部分使用以下即可:

    <PackageReference Include=Microsoft.AspNetCore.App Version=2.1.1 />
    <PackageReference Include=Microsoft.EntityFrameworkCore Version=2.1.1 />
    <PackageReference Include=Microsoft.EntityFrameworkCore.InMemory Version=2.1.1 />
    <PackageReference Include=Microsoft.NET.Test.Sdk Version=15.7.2 />
    <PackageReference Include=Moq Version=4.8.3 />
    <PackageReference Include=xunit Version=2.3.1 />
    <PackageReference Include=xunit.runner.visualstudio Version=2.3.1 />
    <DotNetCliToolReference Include=dotnet-xunit Version=2.3.1 />

整合 Asp.net Core & Angular Cli 混合開發模式

Angular Cli 可以說是目前使用 Angular 2 以上的開發主要啟動方案。透過簡單的命令頁可以快速依據設定加入 component 的內容,降低維護基本的 import 項目的複雜度。可以參考:基本的 Angular Cli 說明

但對於使用 aps.net core 開發的人員而言,以下說明如何將 angular cli 整合到專案內,同時可以獲得兩者的便利性。

  • 首先,在專案目錄下執行 ng new(如果不知道 angular cli command 請參閱上面的說明連結),執行完畢後會產生 angular cli 預設的 source code folder:

這裡包含完整的啟動程式架構,可以透過瀏覽 index.html 進行操作。

  • 將產生的檔案移動到 Asp.net Core 的專案下。要複製兩種內容:

angular 執行程式:指定 angular source code 放入到 任意的指定目錄下(例如:ClientApp\ ),這裡的 source code 就是指 src\ 目錄下的所有內容:

angular cli 的組態設定檔案:就是指跟 src\ 目錄平行的檔案,複製到專案的根目錄下(與 Startup.cs 平行的路徑):

  • 修改變更路徑後的 angular cli 相關組態設定

tsconfig.json 用來編譯 typescript to javascript,修改:

1. outDir 改為 asp.net wwwroot: “outDir”./wwwroot/clientapp/out-tsc”

2. include 改為前面所設定的程式目錄(例如: ClientApp\)

修改 angular-cli.json,同樣將 outDir 改為 wwwroot 目錄

完成相關設定後,就可以在專案目錄下執行 angular cli 了。

 

Asp.net Core 使用 In Memory Caching

Cache 主要目的就是為了減少資料庫存取,加快應用程式速度

但系統的架構設計是,必須要測試是否資料存在 in memory cache,否則就要跟底層的資料庫取得,因為 in memory cache 不保證資料的存在期(也就是他有可能會消失)。

此外,如果是 web farm (代表有多個 web server)就必須要使用 分散式 Cached 機制,確保每台都可以讀取。

 

使用方式:

加入 package 連結:

"Microsoft.Extensions.Caching.Memory":
"1.0.0-rc2-final",

因為 Cache 是一種 Service,因此要透過 DI 加入:

接下來就可以使用 Constructor injection 呼叫:

Cache 使用方式很簡單,用 Get 取出資料(無 = null),或者用 TryGet 使用 out 取出資料,會回傳 true/false。

用 set 設定資料,並且可以用  MemoryCacheEntryOptions 指定 cache 的有效期間;範例如下:

請注意:預設 MemoryCache 會自動調整 Cache 內容,如果記憶體過大,會自動移除部份內容。可以使用 CacheItemPriority.NeverRemove 讓它不要移除:

如果要移除,就用以下命令:

cache.Remove(cacheKey);

其他內容包含:CancellationTokenSource、Cache Dependencies and
Callbacks
主要用途為控制跟後端資料提供者同步,因使用機會不大,若有需要請自行參考。

 

如何讓 TagHelper 取出 Model 屬性的特性(Attribute)

客製化 TagHelper 是 Asp.net Core 一個很重要的擴充,允許我們非常方便的建立各種彈性的運用。另外一方面在 Model 中如何定義好屬性(Property),也有相當一部分要透過 Attribute 擴充我們的定義。

以下定義一個 Attribute 用途只為了標註在特定屬性中,可以對應的代碼:

[AttributeUsage(AttributeTargets.Property)]
public class DropdownAttribute : Attribute
{
    public string CodeMap { get; set; }

    public DropdownAttribute(string code)
    {
        this.CodeMap = code;
    }
}

使用方式很簡單,直接在物件中的屬性加入此 Attribute 就可以指定代碼:

public class DailyReportItem
{
    public int Visitors { get; set; }
    
    [Required]
    [Dropdown("DailyProgress")]
    public string Progress { get; set; }
}

現在我們希望可以自訂 TagHelper,用來處理 Required attribute 的內容(上面範例的 DailyProgress)。在轉寫自訂義的 TagHelper,只有一點需要注意:HtmlAttributeName 要使用 ModelExpression 型態,不可以用 String。這裡最大差別在於如果指定 String 則 razor 會傳入值,而非物件本身;但我們需要物件本身用來解析屬性值。

程式碼如下:

public class CustomerTagHelper: TagHelper
{
    [HtmlAttributeName("asp-for")]
    public ModelExpression Source { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "p";
        output.TagMode = TagMode.StartTagAndEndTag;

        var contents = $@"
            Model name: {Source.Metadata.ContainerType.FullName}<br/>
            Property name: {Source.Name}<br/>
            Current Value: {Source.Model}<br/> 
            Is Required: {Source.Metadata.IsRequired}";

        output.Content.SetHtmlContent(new HtmlString(contents));
    }
}

其中 IsRequired 就是代表是否有宣告 required attribute。使用方式跟一般宣告一樣:

<customer asp-for="Name"></customer>

如果需要處理 Dropdown attributte,可以用 MetaData 取出客製化的屬性:

var dropdown = model.Metadata.ContainerType.GetProperty(code)
                    .GetCustomAttributes()
                    .OfType<DropdownAttribute>().FirstOrDefault();

Asp.net core Identity 如何使用既有的 AspnetUser 密碼驗證?

主要重點在於密碼的加密機制,因為 .net framework (MVC5) 使用 IdentityV2,其 hash 的加密方式比較不嚴謹,新版的 dotnet core 使用 IdentityV3 更好的 hash 機制,因此兩者不相容。

修改方案如下:

You just have to add this line in ConfigureServices in Startup.cs:

services.Configure<PasswordHasherOptions>(options =>
    options.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2
);

來自 <https://stackoverflow.com/questions/47850720/signinmanager-passwordsigninasync-always-return-failure>

補充說明:

_signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false)

這段寫法第一個雖然是用 EMAIL,但實際上 PASSWORD checking 是用 UserName,因此,如果 UserName = Email 就會正確,否則就必須要先讀出 UserName 才可以正確比對。

此外,新的 IdentityV3 一定要使用 NormalizedUserName property 作為登入的 User name(但 UserName 仍然有使用,兩者最大的差異在於 Normalized 一律轉成大寫)。

Asp.net Core 2.0 用既有的使用者帳密作為驗證機制

主要使用情境為採用企業內部其他的既有網站授權機制(例如簡單的 User Table),裡面包含使用者的資料,往往同仁會希望採用同樣的帳號與密碼就可以登入,而不要再產生新的帳密。在這種情境下,無法使用 Asp.Net 推薦的 Aspnet.Identity 的驗證方式,因為要整個移轉有資料庫方面的問題。

但 Asp.net Core 提供 Cookie Authentication 可以存取既有的資料庫做為驗證的來源。參閱官方說明:Using Cookie Authentication without ASP.NET Core Identity官方設定範例

 

實做參考:Cookie Authentication In ASP.net Core 2.0  主要修改:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication("CookieAuthenticationScheme")
        .AddCookie("CookieAuthenticationScheme", options =>
        {
            options.AccessDeniedPath = "/Account/Forbidden/";
            options.LoginPath = "/Account/Login/";
            options.ExpireTimeSpan = TimeSpan.FromHours(2);
        });
 
    services.AddMvc();
}

使用 [Authorize] attribute 時候,必須要指定要使用的認證方式,否則不會自動導入到 Login Page

在 Sign In 的時候,可以直接建立一個虛假的 Claim,只需要給名字即可:

private async Task SignInAsync(User user)
{
    var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.Name) };
    var userIdentity = new ClaimsIdentity(claims, "login");
    ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
 
    await HttpContext.SignInAsync("CookieAuthenticationScheme", principal);
}

請注意這裡也要使用同樣的字串名稱。