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 core 將圖片、檔案轉成 Base 64 字串

圖片取得 Hash:

首先 .net core 必須要引入 System.Draw.Common(參考:How do you use System.Drawing in .NET Core?),如此就可以使用 Image 接收 WinForm 傳入的圖檔。

Install-Package System.Drawing.Common -Version 4.5.1

 

Image to base64 string:

public static string ImageToBase64(Image image)
{
    using (MemoryStream ms = new MemoryStream())
    {
        image.Save(ms, image.RawFormat);
        byte[] bytesImage = ms.ToArray();
 
        return Convert.ToBase64String(bytesImage);
    }
}

針對 base64 產生 SHA256 特徵直:

public static string ComputeSha256(string base64)

{
    SHA256 sha256 = new SHA256CryptoServiceProvider();
    var hash = sha256.ComputeHash(Encoding.Default.GetBytes(base64));
 
    return Convert.ToBase64String(hash);
}

檔案類似,但直接讀取 file bytes 進行 Hash

 

比對方式:

傳入的圖片:同樣執行 圖片取得 Hash: 後,比對特徵值是否正確。

 

之後就可以利用特徵值進行簽章。不用 base64 string 的原因是因為圖片轉 base64 檔案大小其實跟原始圖片一樣大,因此可以不需要儲存圖片,只儲存特徵檔的方式處理。

 

另外也可以透過 base64 還原圖片與檔案,作法如下:

圖片還原:

public static Image Base64ToImage(string base64)
{
    byte[] bytesImage = Convert.FromBase64String(base64);
    using (MemoryStream ms = new MemoryStream(bytesImage, 0, bytesImage.Length))
    {
        ms.Write(bytesImage, 0, bytesImage.Length);
        return Image.FromStream(ms);
    }
}

檔案還原:

 

public static void Base64ToFile(string base64, string filepath)
{
    Byte[] bytes = Convert.FromBase64String(base64);
    File.WriteAllBytes(filepath, bytes);

}

 

建立 Winform Aspnetcore Signalr Client

首先要使用 .net framework 4.6.1 或以上版本,主因在於 asp.net core signalr 最低要求支援環境(這裡是使用 .net standard library)。

 

其次,在 Winform 因為有執行序的問題,因此,在 console 的範例呼叫方式:

_hub.StartAsync().Wait();

ConnectToHub().Wait();

 

在 winform 上會變成沒有回應(因為在等待 Wait() 執行完畢)。

正確做法是透過 Task.Run() 產生新的執行序:

Task.Run(() =>
{
_hub.StartAsync().Wait();

ConnectToHub().Wait();

});

 

但此時又會有無法存取 UI thread 的 controls(例如 textBox);因此必須要在 Form intialized 時候,加入:

CheckForIllegalCrossThreadCalls = false;

這代表不檢查控制項的安全執行緒呼叫,如此,就可以正確執行。

建立 .Net Standard Library

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

簡化維護兩套的困難。

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

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

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

Angular Material Flexbox 概念

Flexbox 簡介:

主要應用在RWD,概念在於 flex-container main axis 代表排列的主軸、cross asix 是垂直於 main axis 的主軸,這兩者方向可以改變

Flexbox 重點在於可以自適應螢幕大小而進行改變,並且也提供豐富的屬性可以自行條配。

Angular Material 使用 flexbox css 並且加上 mediaQuery (同 bootstrap responsive ),透過指定

@media (min-with: 980px)

設定可以應用的大小

如果要深入理解,建議參考: 深入解析 CSS Flexboxmedia query 小撇步

可以使用 scss 顯示 layout 的差異:

可以參考 https://tburleson-layouts-demos.firebaseapp.com/#/docs 可以直接看出各項 layout 的變化

讓 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

angular 前端直接使用 xlxs 將資料直接轉換成 Excel

套件網址: https://github.com/SheetJS/js-xlsx/tree/19620da30be2a7d7b9801938a0b9b1fd3c4c4b00/demos/angular2
使用方式:

import { WorkBook, utils, writeFile, WorkSheet } from 'xlsx';

table = [
  {
    First: 'one',
    Second: 'two',
    Third: 'three',
    Forth: 'four',
    Fifth: 'five'
  },
  {
    First: 'un',
    Second: 'deux',
    Third: 'trois',
    Forth: 'quatre',
    Fifth: 'cinq'
  },
];
onClick() {
  const json = this.replaceHeader(this.table);
  /* generate worksheet */
  // const ws: WorkSheet = utils.json_to_sheet(json);
  const ws: WorkSheet = utils.aoa_to_sheet(this.setupAoa(this.table));
  /* generate workbook and add the worksheet */
  const wb: WorkBook = utils.book_new();
  utils.book_append_sheet(wb, ws, 'Sheet1');
  /* save to file */
  writeFile(wb, 'SheetJS.xlsx');
}
private setupAoa(table) {
  var jsonArray = [];
  jsonArray.push(["This is a Ttile"]);
  jsonArray.push(["第一", "第二", "第三", "第四", "第五"]);
  for(var i = 0; i < this.table.length; i++) {
    jsonArray.push([this.table[i].First, this.table[i].Second, this.table[i].Third, this.table[i].Forth, this.table[i].Fifth]);
  }
  return jsonArray;
}

其中 aoa_to_sheet 可以透過二維陣列對應 Excel 表格的內容(另外也可以使用 json_to_sheet,不過要自行加入表頭設定就比較困難)

最基本的建立 Angular Material 教學

這篇是抄襲官方的 angular Get Start 教學,但簡化步驟,直接切入重點:

換個方式直接說明需要執行的步驟:

  1. 安裝 npm package

npm install --save @angular/material @angular/cdk

npm isntall --save @angular/animations

npm isntall --save hammerjs

  1. 修改以下內容

將所有的 angular material module 放入此處:請注意:很多…:

ng g m shared\material –flat

  1. App.module 加入 materialModule

  1. Style.css 中,加入 theme (否則畫面會很醜):

@import “~@angular/material/prebuilt-themes/indigo-pink.css”;

  1. hammerjs 放入到 main.ts (entry point 不需要每個地方重新呼叫)

import ‘hammerjs’;

  1. index.html 中加入 Material Icon stylesheet

<link rel=”stylesheet” href=”https://fonts.googleapis.com/icon?family=Material+Icons”>

附上所有 angualr module 列表:

CdkTableModule,

MatAutocompleteModule,

MatButtonModule,

MatButtonToggleModule,

MatCardModule,

MatCheckboxModule,

MatChipsModule,

MatStepperModule,

MatDatepickerModule,

MatDialogModule,

MatDividerModule,

MatExpansionModule,

MatGridListModule,

MatIconModule,

MatInputModule,

MatListModule,

MatMenuModule,

MatNativeDateModule,

MatPaginatorModule,

MatProgressBarModule,

MatProgressSpinnerModule,

MatRadioModule,

MatRippleModule,

MatSelectModule,

MatSidenavModule,

MatSliderModule,

MatSlideToggleModule,

MatSnackBarModule,

MatSortModule,

MatTableModule,

MatTabsModule,

MatToolbarModule,

MatTooltipModule

Angular 動態載入 Template:使用 Dynamic Component 方案

主要需求:由於 Template 不是固定的,因此需要可以由後端指定 HTML,前台 angular 動態的載入 tempalte。

 

最主要的參考:Angular 2 dynamic template url with string variable?

這裡的 Component (DynamicTemplateComponent) 中,透過 template 指定 ng-container ,透過 ViewChild 可以動態指定:

<ng-container #dynamicTemplate></ng-container>

@ViewChild(‘dynamicTemplate’, {read: ViewContainerRef}) dynamicTemplate;

 

DynamicTemplateComponent 每次 routing 呼叫後,利用 ViewChild 自動生成對應的 dynamic component 與其 template 的頁面。 angular 提供 Dynamic Component Loader 可以達成,

 

但原始文章有一個主要問題在於無法透過 templateUrl,因為 angular cli 會直接解析內容。

解決方案是透過 template 指定 html 內容:

完整程式如下:

export class DynamicTemplateComponent implements AfterViewInit, OnInit {
  @ViewChild('dynamicTemplate', { read: ViewContainerRef }) dynamicTemplate;

  constructor(private compiler: Compiler, private injector: Injector, private ngModuleRef: NgModuleRef<any>,
    private route: ActivatedRoute, private data: LhcService) { }

  id: number;
  ngOnInit() {
    this.route.params.subscribe(p => {
      this.id = p["id"];
    })
  }

  ngAfterViewInit(): void {
    this.data.getTemplate(this.id).subscribe(data => {
      const tmpComponent = Component({ moduleId: module.id, template: data })
        (class {
          cancel() {
            window.history.back();
          }
        });
 
      const tmpModule = NgModule({ declarations: [tmpComponent] })(class { });
 
      this.compiler.compileModuleAndAllComponentsAsync(tmpModule)
        .then((factories) => {
          const factory = factories.componentFactories[0];
          const cmpRef = factory.create(this.injector, [], null, this.ngModuleRef);
          cmpRef.instance.name = 'dynamic';
          this.dynamicTemplate.insert(cmpRef.hostView);
        })
    });
  }

這裡要額外宣告 module.id,是 angular 告訴 webpack or systemjs 如何 package module 的 metadata,因為我們使用 angular cli (webpack) ,會自動注入 module.id 到 component 中,唯一需要注意的是 typescript 要事先宣告避免無法編譯:

declare var module: {
id: string;
}

如果產生的 component 要加入 處理 *ngFor 等 browser 的內容,要在 NgModule 中,宣告 BrowserModule:

const tmpModule = NgModule({
declarations: [tmpComponent],
imports: [BrowserModule]
})(class { });

其次,在產生的 component 中,很難使用 constructor injection,最好的方式是產生時候,直接將服務對應到 class property 中:

const tmpComponent = Component({ moduleId: module.id, template: data })
  (class {
    private data: LhcService;
    patients: Array<RegFile>;
 
    getPatient() {
      this.data.getAllPatients().subscribe(data => this.patients = data);
    }
    cancel() {
      window.history.back();
    }
  });

…

this.compiler.compileModuleAndAllComponentsAsync(tmpModule)
  .then((factories) => {
    const factory = factories.componentFactories[0];
    const cmpRef = factory.create(this.injector, [], null, this.ngModuleRef);
    cmpRef.instance.name = 'dynamic';
    cmpRef.instance.data = this.data;
    cmpRef.instance.callback = this.interactive;

    this.dynamicTemplate.insert(cmpRef.hostView);
  })

其中 compRef.instance 就是代表 dynamic component 的函數物件,這裡將 parent 的 data (lch.service)對應到 child 中,讓他可以直接使用。

此外,也可以加入 callback function(cmpRef.instance.callback = this.interactive),與 patient controller 互動:

在 parnet 中宣告:

interactive(patient: RegFile): Observable<string> {
console.log("dynamic compoent callback, show reg_no: " + patient.RegNo);
return new Observable((observe) => {
observe.next(patient.Name);
observe.complete();
})
}

重點在於回傳 Observable 讓 child component 可以接收:

callback: Function;
clickPatient(patient: RegFile) {
 this.callback(patient).subscribe(m => console.log("easy way from patient: " + m));
}

此外,class 的宣告也可使用繼承或者介面:

const tmpComponent = Component({ moduleId: module.id, template: data })
(class implements OnInit {
ngOnInit(): void {
console.log("ng onitit");
}

請注意,這裡的 class 不可以移出外部,原因是如果移出去,第一次會成功、但第二次就會造成以下的錯誤:

ERROR Error: Type InnerComponent is part of the declarations of 2 modules: class_1 and class_1! Please consider moving InnerComponent to a higher module that imports class_1 and class_1. You can also create a new NgModule that exports and includes InnerComponent then import that NgModule in class_1 and class_1.

此外,也要注意因為動態生成 component 功能畢竟有限,有些無法執行;例如 ngx-bootstrap 的 datepicker 就因為 module 匯入造成失敗,目前找不到原因,還好可以用 input type=’date’ 方式叫出瀏覽器預設的 date picker:

imports: [BrowserModule, FormsModule, BsDatepickerModule.forRoot()] ERROR TypeError: Cannot read property 'isDisabled' of undefined at Object.eval [as updateRenderer] (BsDatepickerDayDecoratorComponent_Host.ngfactory.js? [sm]:1)

 

解決windows 10 升級後,無法遠端連線問題

在某一次升級後,發生遠端連線到其他電腦出現錯誤:

原因應該是出在被連線的電腦並沒有執行 windows update,但有些時候主機可能沒有權限,或者其他考量無法更新。因此如何解決(與解決後,微軟已經修復這個問題,該如何還原)。因此紀錄以下過程:

首先要執行 gpedit.msc【群組原則】後,在以下的【電腦設定】->【系統管理範本】->【系統】中:

點選【認證委派】,在【加密 Oracle 補救措施】 設定已啟用即可:

原先的未設定改為【已啟用】就可以進行遠端連線了: