Author: pongspat.h

  • AutoMapper กับขัอมูลชนิด Collection

    การใช้งาน AutoMapper กับ object type array หรือ list ไม่จำเป็นต้องกำหนดค่า config การ map ของ array หรือ list เพียงแค่ config การ map ของ member ของ array หรือ list ที่ใช้งานเท่านั้น ตัวอย่างเช่น object ง่ายๆสองชนิดดังต่อไปนี้

    public class Person
    {
        public string name { get; set; }
    }
    
    public class Employee
    {
       public string name { get; set; }
    }

    กำหนดค่า config ของ AutoMapper สำหรับการ map ข้อมูลจาก Person ไปยัง Employee

    Mapper.Initialize(cfg => cfg.CreateMap<Person, Employee>());
    
    var persons = new[]
    {
       new Person  { Name = "A" },
       new Person  { Name = "B" },
       new Person  { Name = "C" }
    };

    AutoMapper สามารถใช้งานได้กับ generic collection type ดังนี้

    • IEnumerable
    • IEnumerable<T>
    • ICollection
    • ICollection<T>
    • IList
    • IList<T>
    • List<T>
    • Arrays

    ในการทำ mapping ของ AutoMapper ถ้า collection ของ object ปลายทางที่ถูก map มี member อยู่แล้ว AutoMapper จะทำการ remove member ของ collection ปลายทางออกก่อน ที่จะทำการ mapping ข้อมูลจาก Object ต้นทาง

    var enumEmployee = Mapper.Map<Person[], IEnumerable<Employee>>(persons);
    var colEmployee = Mapper.Map<Person[], ICollection<Employee>>(persons);
    var ilistEmployee = Mapper.Map<Person[], IList<Employee>>(persons);
    var listEmployee = Mapper.Map<Person[], List<Employee>>(persons);
    var arrayEmployee = Mapper.Map<Person[], Employee[]>(persons);

    กรณีที่ collection ต้นทางที่ทำการ mapping มีค่าเป็น null, AutoMapper จะทำกำหนดค่าให้กับ collection ปลายทางเป็น empty collection ไม่ใช่ null collection เราสามารถเปลี่ยนค่า default โดยการกำหนดค่า AllowNullCollections = true ในตอน config mapper

    Mapper.Initialize(cfg => {
        cfg.AllowNullCollections = true;
        cfg.CreateMap<Person, Employee>();
    });

    ในกรณีที่ collection ที่ต้องการ mapping มี member ที่มีลักษณะ hierarachy ก็สามารถใช้ AutoMapper ในการ mapping ข้อมูลได้เช่นกัน โดยกำหนด config ของ collection member โดยใช้ method Include หลังจาก config mapping ของ parent object แล้ว และเรียกใช้งาน Map ตามปกติ ดังนี้

    public class ChildPerson : Person 
    {
        public string JobName { get; set; }
    }
    
    public class ChildEmployee : Employee
    {
       public string JobName { get; set; }
    }
    
    
    Mapper.Initialize(c=> 
    {     c.CreateMap<Person, Employee>()        
              .Include<ChildPerson, ChildEmployee>();        
          c.CreateMap<ChildPerson, ChildEmployee>(); 
    }); 
    
    var persons = new[]
    {
       new Person  { Name = "A" },
       new ChildPerson  { Name = "B" },
       new ChildPerson  { Name = "C" }
    }; 
    
    var enumEmployee = Mapper.Map<Person[], IEnumerable<Employee>>(persons);

    อ้างอิง : http://docs.automapper.org/en/stable/Lists-and-arrays.html

  • AutoMapper

    AutoMapper คือ component ที่ใช้ในการ map ข้อมูลระหว่าง object ต่างชนิดกัน โดยทำการแปลงข้อมูลจาก object ชนิดหนึ่ง ไปกำหนดค่าให้กับ object อีกชนิดหนึ่ง ซึ่งถ้า object ปลายทางตรงตามข้อกำหนดของ AutoMapper ก็ไม่จำเป็นต้อง config ค่าใดๆเลย

    การแปลงข้อมูลจาก object ชนิดหนึ่งไปยัง object อีกชนิดหนึ่ง เกิดขึ้นได้บ่อยในการพัฒนา application โดยเฉพาะ application ที่อยู่ในลักษณะ multi tiers ที่ต้องมีการส่งผ่าน object ระหว่างกัน เช่น UI layer กับ Service layer หรือ Data access layer กับ Service layer

    การใช้งาน AutoMapper

    เมื่อต้องการแปลงข้อมูลจาก object ต้นทางไปยัง object ปลายทางอีกชนิดหนึ่ง ถ้า object ปลายทางมี property name ชื่อเดียวกับ object ต้นทาง AutoMapper จะทำการกำหนดค่าให้กับ property ของ object ปลายทางโดยอัตโนมัติ การกำหนดค่า config การแปลงข้อมูลระหว่าง object ทำได้โดยใช้ MapperConfiguration ซึ่งจะมีเพียงหนึ่งเดียว และสร้างขึ้นตอนเริ่ม application หรือ ใช้ Mapper.Initialize static method

    //static method 
    Mapper.Initialize(cfg => cfg.CreateMap<Person, PersonPoco>());
    
    //instance method
    var config = new MapperConfiguration(cfg => cfg.CreateMap<Person, PersonPoco>());

    type ที่อยู่ทางซ้ายของ cfg.CreateMap<>() คือ type ต้นทาง ส่วนทางด้านขวาคือ type ปลายทาง ซึ่งเราจะทำการ mapping โดยใช้ static Mapper method หรือจะใช้ instance method ก็ได้

    var mapper = config.CreateMapper();
    
    //instance method
    var mapper = new Mapper(config);
    PersonPoco poco = mapper.Map<PersonPoco>(person);
    
    //static method
    PersonPoco poco = Mapper.Map<PersonPoco>(person);

    ในกรณีที่เรามี object ที่มีโครงสร้างซับซ้อน ซึ่งการใช้งานในบางโอกาสที่ต้องการดูข้อมูลเพี่ยงบางส่วน เราสามารถใช้งาน AutoMapper mapping ข้อมูล มาสู่ object ที่มีโครงสร้างเรียบง่ายใช้งานได้สะดวก ตัวอย่างเช่น object Order ที่มีโครงสร้างค่อนข้างซับซ้อนด้านล่างนี้

    public class Order
    {
        public Customer Customer { get; set; }
        public OrderLineItem[] OrderLineItems()
        public decimal GetTotal()
        {
           return OrderLineItems.Sum(li => li.GetTotal());
        }
    }
    
    public class Product
    {
        public decimal Price { get; set; }
        public string Name { get; set; }
    }
    
    public class OrderLineItem
    {
        public Product Product { get; private set; }
        public int Quantity { get; private set;}
        public decimal GetTotal()
        {   
           return Quantity*Product.Price;
        }
    }
    
    public class Customer
    {
         public string Name { get; set; }
    }

    ถ้าต้องการข้อมูลเพียงแค่ CustomerName และ Total เราสามารถนำ AutoMapper มาช่วย Mapping ข้อมูลได้ดังนี้

    public class OrderPoco
    {
        public string CustomerName { get; set; }
        public decimal Total { get; set; }
    }
    
    Mapper.Initialize(cfg => cfg.CreateMap<Order, OrderPoco>()); 
    OrderPoco poco = Mapper.Map<Order, OrderPoco>(order); 
    

    config mapping โดยใช้ static method ทำการ map Order กับ OrderPoco และทำการ mapping โดยเรียกใช้ medthod Map ซึ่งการทำงานเบื้องหลัง AutoMapper จะทำการจับคู่ OrderPoco.Total property กับ Order.GetTotal(), ในส่วนของ OrderPoco.CustomerName จะทำการจับคู่กับ Order.Customer.Name ซึ่งจะเห็นว่าถ้า property name ของ object ปลายทางของการ mapping มีความเหมาะสม ก็ไม่จำเป็นต้องทำการ config mapping สำหรับ property นั้นๆ

    อ้างอิง : http://docs.automapper.org/en/stable/Lists-and-arrays.html

  • Stencil : Styling

    Shadow DOM

    Shadow DOM เป็น API ที่อยู่ใน browser ที่ให้ความสามารถในทำ DOM encapsulation และ style encapsulation โดย Shadow DOM จะแยก component ออกจากภายนอก ทำให้ไม่ต้องกังวลกับ scope ของ css หรือผลกระทบกับ component ภายนอก หรือ component ภายนอกจะกระทบกับ component ภายใน

    ใน Stencil ค่า default การใช้งาน Shadow DOM ใน web component ที่สร้างด้วย Stencil จะถูกปิดอยู่  หากต้องการเปิดใช้งาน Shadow DOM ใน web component ต้องกำหนดค่า shadow param ใน component decorator ดังตัวอย่างด้านล่างนี้

    @Component({
      tag: 'shadow-component',
      styleUrl: 'shadow-component.scss',
      shadow: true
    })
    export class ShadowComponent {
    
    }
    

    สิ่งจำเป็นเมื่อเปิดใช้งาน Shadow DOM

    • QuerySelector เมื่อต้องการ query element ที่อยู่ภายใน web component จะต้องใช้ this.el.shadowRoot.querySelector() เนื่องจาก DOM ภายใน web component อยู่ภายใน shadowRoot
    • Global Styles จะต้องใช้ CSS custom properties
    • css selector สำหรับ web component element คือ “:host”  selector

    โดยทั่วไป จะเก็บ styles ไว้ภายไต้ ชื่อ tag ของ component นั้น

    my-element {
      div {
        background: blue;
      }
    }
    

    ในกรณีของ Shadow DOM  อยู่ภายใต้ tag :host

    :host {
      div {
        background: blue;
      }
    }
    

    Scoped CSS

    สำหรับ browser ที่ไม่สนับสนุน Shadow DOM, web component ที่สร้างโดย Stencil จะกลับไปใช้ scoped CSS แทนที่จะ load Shadow DOM polyfill ที่มีขนาดใหญ่  Scoped CSS จะทำการกำหนดขอบเขต CSS ให้กับ element โดยอัตโนมัตตอน runtime

    CSS Variables

    CSS Variables เหมือนกับ Sass Variables แต่ต่างกันตรงที่ CSS Variables รวมอยู่ใน browser โดยที่ CSS Variables ให้ความสามารถในการกำหนด CSS properties ที่ใช้ได้ภายใน app  ตัวอย่างการใช้งานที่พบบ่อยคือ การกำหนดสี (color) ถ้ามีสีหลักที่ต้องการใช้ร่วมกันทั้ง app แทนที่จะกำหนดสีนั้นๆในแต่ละที่ที่ใช้งาน ก็จะสร้าง variable ขึ้นมาและใช้ variable นั้นในทุกๆที่ที่ต้องการ ซึ่งถ้าต้องการเปลี่ยนสี ก็สามารถเปลี่ยนที่ variable ที่เดียว

    การใช้งาน CSS Variables ใน Stencil

    สร้าง file : variables.css ที่ใช้เก็บ CSS Variabless ใน “src/global/” directory  และเพิ่ม config globalStyle: ‘src/global/variables.css’  ใน stencil.config.js

    ตัวอย่าง การกำหนด CSS Variable ใน src/global/variables.css

    :root {
      --app-primary-color: #488aff;
    }
    

    จากตัวอย่างด้านบน สร้าง CSS Variable ชื่อ –app-primary-color ที่เก็บค่าสี #488aff อยู่ภายใต้ :root selector (:root selector คือ CSS pseudo selector ที่หมายถึง root element ของ app) การใช้ CSS Variable ที่กำหนดไว้ทำได้ดังนี้

    h1 {
      color: var(--app-primary-color)
    }
    

    เป็นการกำหนดสี ที่เก็บไว้ใน CSS Variable –app-primary-color ให้กับ h1 element

     

    อ้างอิง : https://stenciljs.com/docs/styling

  • Stencil : JSX

    Stencil component ใช้ JSX template syntax ในการกำหนดรูปแบบการแสดงผลที่จะถูก render ของ component  ซึ่งแต่ละ component จะมี render function ที่จะทำหน้าที่ return โครงสร้างของ component ที่จะเปลี่ยนเป็น DOM ตอน runtime เพื่อแสดงผลบนหน้าจอ

    class MyComponent {
        render() {
           return (
              <div>
                <h1>Hello World</h1>
                <p>This is JSX!</p>
              </div>
           );
        }
    }
    

    จาก class Mycomponent ด้านบน render function จะ return div element ที่ภายในประกอบไปด้วย h1 และ p

    Data Binding

    เมื่อ component ต้องการที่จะ render ข้อมูลที่มีการเปลี่ยนแปลง dynamic data ทำได้โดยการ bindind ข้อมูลนั้นๆ โดยใช้ {variable}  (ซึ่งจะใกล้เคียงกับ ES6 template ที่ใช้ ${variable})

    render() {
         return (
            <div>Hello {this.name}</div>
         )
    }
    

    Conditionals

    เมื่อ component มีการแสดงผลที่ขึ้นอยู่กับเงื่อนไข สามารถใช้ JavaScript if/else statements ใน render function ดังเช่นตัวอย่างด้านล่างนี้ ถ้า this.name ไม่มีการกำหนดค่า จะแสดงผล “Hello, World”

    render() {
        if (this.name) {
            return ( <div>Hello {this.name}</div> )
        } else {
            return ( <div>Hello, World</div> )
        }
    }
    

    หรือ จะเขียนในรูปแบบ inline conditionals ก็ได้เช่นกัน

    render() { 
           return ( 
              <div> 
              {this.name 
                   ? <p>Hello {this.name}</p> 
                   : <p>Hello World</p> 
              } 
              </div> 
           ); 
    }
    

    Loops

    ใน JSX สามารถใช้ array operators : map ในการทำงานแบบ loop  จากตัวอย่างด้านล่าง มี lists ของ todo object ใน this.Objects ซึ่ง map function ทำหน้าที่ loop ในแต่ละ todo object ใน this.Objects แล้วสร้าง new JSX sub tree และ add เข้าไปใน array ที่จะ return ออกจาก map function ซึ่งจะเพิ่มเข้าไปใน JSX tree ด้านนอกนั่นคือ div element

    render() {
       return (
         <div>
           {this.todos.map((todo) =>
               <div>
                 <div>{todo.taskName}</div>
                 <div>{todo.isCompleted}</div>
               </div>
           )}
         </div>
      )
    }
    

    Handling User Input

    Stencil สามารถใช้ native DOM events ได้ดังตัวอย่างด้านล่าง

    export class MyComponent 
    { 
           handleClick(event: UIEvent) { 
                alert('Received the button click!'); 
           } 
           render() { 
               return ( 
                  <button onClick={ (event: UIEvent) => this.handleClick(event)}>Click Me!               
                  </button> 
               ); 
           } 
    }
    

    หรือจะเขียนในรูปแบบ  onClick={this.handleClick.bind(this)} ก็ได้เช่นกัน

      handleClick(event: UIEvent) {
        alert('Received the button click!');
      }
    
      render() {
        return (
          <button onClick={this.handleClick.bind(this)}>Click Me!</button>
        );
      }
    

     

    อ้างอิง : https://stenciljs.com/docs/templating-jsx

     

  • Stencil : Decorators

    Component Decorator

    แต่ละ Stencil component จะต้องขึ้นต้นด้วย @Component() decorator เสมอ โดย import มาจาก @stencil.core package ซึ่งภายใต้ @Component() decorator สามารถกำหนด tag name และ styleUrl ของ component

    import { Component } from '@stencil/core';
    
    @Component({
      tag: 'todo-list',
      styleUrl: 'todo-list.scss'
    })
    export class TodoList {
      ...
    }
    

    @Component() decorator ให้ความสามารถในการกำหนด CSS classes และ attributes บน componnet ที่สร้างโดยใช้ host option ดังนี้

    import { Component } from '@stencil/core';
    
    @Component({
      tag: 'todo-list',
      styleUrl: 'todo-list.scss',
      host: {
        theme: 'todo',
        role: 'list'
      }
    })
    

    เมื่อใช้งาน component ตัวนี้ ก็จะมีการกำหนดค่า class เป็น todo และกำหนด role attribute ให้อัตโนมัติ

    <todo-list class='todo' role='list'></todo-list>

     

    Prop Decorator

    @Prop() decorator ใช้ระบุ attribute หรือ properties สำหรับ element ที่ผู้พัฒนาสามารถกำหนดค่าให้กับ component นั้นได้  เนื่องจาก Children component จะไม่สามารถเข้าถึง properties หรือ reference ของ parent component ได้จึงใช้ Props ในการผ่านข้อมูลจาก parent มาสู่ child และเมื่อใดก็ตามที่ค่าของ Props เปลี่ยนแปลงจะทำให้ component ทำการ re-render

    type ของ Props ที่รองรับมีหลากหลาย ไม่ว่าจะเป็น number, string, boolean หรือแม้กระทั่ง Object หรือ Array

    import { Prop } from '@stencil/core';
    ...
    export class TodoList {
      @Prop() color: string;
      @Prop() favoriteNumber: number;
      @Prop() isSelected: boolean;
      @Prop() myHttpService: MyHttpService;
    }
    

    ภายใน function ใน TodoList class สามารถเรียกใช้งาน Props ผ่าน this operator

    logColor() {
      console.log(this.color)
    }
    

    สำหรับการใช้ภายนอก ใน HTML การกำหนดค่าทำได้โดย set ค่าให้ attributes ของ component tag โดยใช้ dash-case  เช่น Prop favoriteNumber ใช้ attribute favorite-number

    <todo-list color="blue" favorite-number="24" is-selected="true"></todo-list>

    สำหรับการใช้ใน JSX การกำหนดค่าทำได้โดย set ค่าให้ attributes ของ component tag โดยใช้ camelCase

    <todo-list color="blue" favoriteNumber="24" isSelected="true"></todo-list>

    Prop สามารถเรียกใช้งานผ่านทาง element ของ  javascript

    const todoListElement = document.querySelector('todo-list');
    console.log(todoListElement.myHttpService); // MyHttpService
    console.log(todoListElement.color); // blue
    

     

    Component State

    @State() decorator ใช้ในการจัดการ internal data ของ component ซึ่งผู้ใช้ไม่สามารถแก้ไขข้อมูลจากภายนอก component  เมื่อข้อมูลมีการเปลี่ยนแปลง จะทำให้ component ทำการ re-render เช่นเดียวกับ Prop

    import { State } from '@stencil/core';
    
    ...
    export class TodoList {
      @State() completedTodos: Todo[];
    
      completeTodo(todo: Todo) {
        // This will cause our render function to be called again
        this.completedTodos = [...this.completedTodos, todo];
      }
    
      render() {
        //
      }
    }
    

     

    Element Decorator

    @Element() decorator ใช้ในการเข้าถึง host element ภายใน class instance โดย return type คือ HTMLElement  ทำให้สามารถใช้ standard DOM methods/events ได้

    import { Element } from '@stencil/core';
    
    ...
    export class TodoList {
    
      @Element() todoListEl: HTMLElement;
    
      addClass(){
        this.todoListEl.classList.add('active');
      }
    }
    

     

    อ้างอิง : https://stenciljs.com/docs/decorators

  • Stencil : Web component compiler

    Stencil เป็น compiler ที่ทำหน้าที่สร้าง standards-based web component (custom element) ซึ่งรวบรวมแนวคิดที่ดีของ framework ต่างๆที่ได้รับความนิยมสูง มาไว้ใน component โดย build-time tool ที่ใช้งานง่าย เช่น

    • Virtual DOM
    • Async rendering (inspired by React Fiber)
    • Reactive data-binding
    • TypeScript
    • JSX

    web component ที่ได้จาก Stencil เป็น standards-based web component ทำให้สามารถใช้งานร่วมกับ framework ต่างๆที่ได้รับความนิยม และสามารถใช้ได้โดยไม่มี framework ก็ได้ จากเดิมเมื่อพัฒนาด้วย framework หนึ่งแล้ว ไม่สามารถนำไปใช้ร่วมกับ framework อื่นได้

    Stencil มี APIs เช่น Virtual DOM, JSX, และ async rendering  ที่ทำให้สามารถพัฒนา web component ที่ทำงานได้เร็ว มีประสิทธิภาพดีกว่า และสร้างได้ง่ายกว่าเมื่อเปรียบเทียบกับการเขียน custom element โดยตรงโดยไม่ใช้ Stencil  และใน Stencil ยังมี small dev server พร้อมความสามารถ live reload อยู่ด้วย

    เริ่มต้นสร้าง project กับ Stencil

    การสร้าง component ทำได้โดยเริ่มต้นจาก component starter ดังนี้

    git clone https://github.com/ionic-team/stencil-component-starter my-component
    cd my-component
    git remote rm origin
    npm install
    

    จากนั้น ถ้าต้องการ start  live-reload server สำหรับการพัฒนา ให้ใช้คำสั่ง

    npm start

    Updating Stencil
    ถ้าต้องการ update Stencil เป็น version ล่าสุดให้ใช้คำสั่ง

    npm install @stencil/core@latest --save-exact

    Stencil components

    component ถูกสร้างโดยการสร้าง file .tsx ใน “src/components” directory เขียน component ด้วย JSX และ Typescript  ซึ่ง component ที่สร้างมาจาก component starter คือ my-component.tsx

    import { Component, Prop } from '@stencil/core';
    
    @Component({
      tag: 'my-first-component',
      styleUrl: 'my-first-component.scss'
    })
    export class MyComponent {
    
      @Prop() name: string;
    
      render() {
        return (My name is {this.name}); 
      } 
    }
    

    เมื่อ compile แล้วเสร็จ สามารถนำ component ไปใช้ใน HTML page ได้เช่นเดียวกับ tag อื่นๆ Web Components จะต้องมี “-” ภายใน tag (“myFirstComponent” เป็นชื่อที่ไม่สามารถใช้งานได้)

    <my-first-component name="Max"></my-first-component> 
    

    เมื่อเปิดผ่าน browser จะแสดงผล My name is Max

     

    อ้างอิง : https://stenciljs.com

  • Migration project.json to csproj format (C#)

    ในช่วงการพัฒนาของ .NET Core tooling จนถึงปัจจุบัน มี design/component หลายอย่างที่มีการเปลี่ยนแปลงในลักษณะที่ไม่ compatible กับ version ก่อนหน้า หรือยกเลิกการใช้งาน หนึ่งในนั้นก็คือ project config file ที่เริ่มต้นใช้รูปแบบ json ซึ่งอยู่ใน file ที่ชื่อ project.json แต่ปัจจุบัน เปลี่ยนมาใช้ MSBuild/csproj format

    การ migration project.json ไปสู่ .csproj format ทำได้ด้วยกันสองวิธีคือ

    • Visual Studio 2017
    • dotnet migrate command-line tool

    ทั้งสองวิธีใช้กลไกการทำงานเดียวกัน ซึ่งผลที่ได้จะเหมือนกัน

    Visual Studio 2017

    เปิด project โดยเปิด file .xproj ใน Visual Studio 2017 จะปรากฎ One-way upgrade dialog ขึ้นมาให้เลือก OK, Visual Studio จะทำการ migrate โดย file ที่ถูก migrate (project.json, global.json, .xproj) จะถูกย้ายไปสำรองไว้ใน folder Backup

    dotnet migrate

    ใช้ command-line เข้าไปที่ folder ที่เก็บ project และใช้คำสั่ง dotnet migrate ซึ่งจะทำการ migrate โดย file ที่ถูก migrate (project.json, global.json, .xproj) จะถูกย้ายไปสำรองไว้ใน folder Backup

    <Project Sdk="Microsoft.NET.Sdk">
      ... 
    </Project>
    

    ข้อแตกต่างระหว่าง project.json กับ csproj format ( อยู่ในรูปแบบ XML-based ซึ่งมี root node ระบุ sdk ที่ใช้คือ Microsoft.NET.Sdk สำหรับ web project sdk ที่ใช้คือ Microsoft.NET.Sdk.Web ) มีดังนี้

    Common options

    ****** JSON format ******
    {
      "name": "MyProjectName",
      "version": "1.0.0-alpha-*",
    
      "authors": [ "name1", "name2" ],
      "company": "PSU",
      "language": "en-US",
      "title": "My library",
      "description": "This is my library.",
      "copyright": "PSU 3000",
      "userSecretsId": "xyz123"
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <AssemblyName>MyProjectName</AssemblyName>
     <PackageId>MyProjectName</PackageId>
     <VersionPrefix>1.0.0</VersionPrefix>
     <VersionSuffix>alpha</VersionSuffix>
    
     <Authors>name1;name2</Authors>
     <Company>PSU</Company>
     <NeutralLanguage>en-US</NeutralLanguage>
     <AssemblyTitle>My library</AssemblyTitle>
     <Description>This is my library.</Description>
     <Copyright>PSU 3000</Copyright>
     <UserSecretsId>xyz123</UserSecretsId>
    </PropertyGroup>
    

    frameworks

    ****** JSON format ******
    {
      "frameworks": {
        "netcoreapp1.0": {},
        "net451": {}
      }
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <TargetFrameworks>netcoreapp1.0;net451</TargetFrameworks>
    </PropertyGroup>
    

    dependencies

    ****** JSON format ******
    {
      "dependencies": {
        "NETStandard.Library": "1.6.0",
        "Microsoft.AspNetCore": "1.1.0",
    
        "MyOtherProject": "1.0.0-*",
        "AnotherProject": {
          "type": "project"
        }
      }
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion> 
    </PropertyGroup>
    
    <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" /> 
    </ItemGroup>
    
    <ItemGroup>
     <ProjectReference Include="..\MyOtherProject\MyOtherProject.csproj" />
     <ProjectReference Include="..\AnotherProject\AnotherProject.csproj" /> 
    </ItemGroup>
    

    tools

    ****** JSON format ******
    {
      "tools": {
        "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-*"
      }
    }
    
    ****** csproj format ******
    <ItemGroup>
     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> 
    </ItemGroup>
    

    buildOptions

    ****** JSON format ******
    {
      "buildOptions": {
        "emitEntryPoint": true
        "warningsAsErrors": true,
        "nowarn": ["CS0168", "CS0219"],
        "xmlDoc": true,
        "preserveCompilationContext": true,
        "outputName": "Different.AssemblyName",
        "debugType": "portable",
        "allowUnsafe": true,
        "define": ["TEST", "OTHERCONDITION"]
      }
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <OutputType>Exe</OutputType>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <NoWarn>$(NoWarn);CS0168;CS0219</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>     
     <PreserveCompilationContext>true</PreserveCompilationContext> 
     <AssemblyName>Different.AssemblyName</AssemblyName>
     <DebugType>portable</DebugType> 
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks> 
     <DefineConstants>$(DefineConstants);TEST;OTHERCONDITION</DefineConstants>
    </PropertyGroup>
    

    testRunner

    ****** JSON format ******
    {
      "testRunner": "xunit",
      "dependencies": {
        "dotnet-test-xunit": ""
      }
    }
    
    ****** csproj format ******
    <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-*" />
     <PackageReference Include="xunit" Version="2.2.0-*" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-*" /> 
    </ItemGroup>
    

    อ้างอิง : https://docs.microsoft.com/en-us/dotnet/core/tools/project-json-to-csproj

  • Auto remove schema in EDMX on build

    Entity Framework (EF)  คือ data access technology ที่เริ่มเปิดตัวครั้งแรกเป็นส่วนหนึ่งของ .NET Framework 3.5 SP1 โดยตัว EF จะทำหน้าที่เป็น object-relational mapper ที่ทำให้ผู้พัฒนาไม่จำเป็นต้องเขียน code ในส่วน data access ก็สามารถใช้ข้อมูลจาก relational database โดยผ่าน object model

    การพัฒนาโปรแกรมโดยใช้ EF นั้นจำเป็นต้องมี Entity Data Model เป็น model ที่กำหนดรายละเอียดเกี่ยวกับ entity และ relationship ระหว่าง entity นั้นๆ การสร้าง Entity Data Model สามารถแยกออกเป็น 2 แนวทางคือ “Code First” เป็นการกำหนดรูปร่างของ model โดยการสร้าง class (เขียน code) จะมี database หรือไม่มีอยู่ก่อนก็ได้  และ “Database First” ที่จะทำการสร้าง model ( reverse engineer) จาก database ที่มีอยู่โดย EF Designer ซึ่ง model ที่ได้จะเก็บอยู่ใน EDMX file (.edmx) สามารถเปิดหรือแก้ไขเพิ่มเติมได้ด้วย EF Designer สำหรับ class ที่ใช้ในโปรแกรมจะถูกสร้างโดยอัตโนมัติจาก EDMX file

    ข้อมูล Entity Data Model ใน EDMX file อยู่ในรูปแบบ xml สามารถแบ่งออกเป็น 3 ส่วนคือ Storage model, Conceptual model และ Mapping ซึ่งในส่วนของ Storage model จะเป็นข้อมูลรายละเอียดของ entity จาก database เช่น

    ข้อมูล EntityType ที่ให้รายละเอียดของชื่อของ entity (table ใน database), ชื่อและประเภทของ property (column ของ table ใน database)

     <EntityType Name="VF_CONFIG_REPORT">
      <Key>
        <PropertyRef Name="ID" />
      </Key>
        <Property Name="ID" Type="number" Precision="38" Scale="0" Nullable="false" />
        <Property Name="REPORT_NAME" Type="varchar2" MaxLength="512" />
        <Property Name="REPORT_PATH" Type="varchar2" MaxLength="512" />
        <Property Name="GROUP_TYPE" Type="number" Precision="38" Scale="0" />
        <Property Name="SIGN_NUM" Type="number" Precision="38" Scale="0" />
        <Property Name="SIGNS" Type="varchar2" MaxLength="128" />
     </EntityType>

    ข้อมูล EntitySet ที่ประกอบด้วย ชื่อ,ประเภทของ entity, schema และ query ที่ใช้ดึงข้อมูล

    <EntitySet Name="VF_CONFIG_REPORT" EntityType="Self.VF_CONFIG_REPORT" store:Type="Views" store:Schema="FINANCE">
       <DefiningQuery>
          SELECT 
           "VF_CONFIG_REPORT"."ID" AS "ID",
           "VF_CONFIG_REPORT"."REPORT_NAME" AS "REPORT_NAME", 
           "VF_CONFIG_REPORT"."REPORT_PATH" AS "REPORT_PATH", 
           "VF_CONFIG_REPORT"."GROUP_TYPE" AS "GROUP_TYPE", 
           "VF_CONFIG_REPORT"."SIGN_NUM" AS "SIGN_NUM", 
           "VF_CONFIG_REPORT"."SIGNS" AS "SIGNS"
         FROM "FINANCE"."VF_CONFIG_REPORT" "VF_CONFIG_REPORT"   
       </DefiningQuery>
    </EntitySet>

    เมื่อมีการระบุ schema ของ entityใน EDMX file  นั่นทำให้การ deploy ระบบ(โปรแกรมและ database) จำเป็นต้องมี database ที่มี schema ชื่อเดียวกับที่กำหนดใน EDMX file เท่านั้น(schema ได้มาจากการ generate ของ EF Designer ในขั้นตอนการพัฒนาระบบ) ถ้าต้องการให้ EF ทำงานกับ database schema อื่นจะต้องแก้ไข schema ใน EDMX file ให้ตรงกัน หรือไม่ระบุ schema โดยลบส่วนที่ระบุ schema ออก ซึ่งการแก้ไขจะต้องทำการแก้ไขโดยตรงไปที่ EDMX file แล้วทำการ build ใหม่ (ในกรณีที่ไม่ได้เลือก build EDMX file เป็นแบบ embeded resource สามารถแก้ไขที่ .ssdl file ได้โดยไม่ต้อง build ใหม่)

    ในการเปลี่ยน schema ใน EDMX file นั้นจะต้องแก้ทุก EntitySet ที่มี ทำให้มีความเสี่ยงที่จะเกิดความผิดพลาดในการแก้ไข ทำให้ระบบไม่สามารถทำงานได้ และถ้ามีความจำเป็นต้องปรับ Entity Data Model เพื่อเพิ่ม, แก้ไข หรือลบ entity ใดๆ EF Designer จะทำการ update .EDMX file ใหม่ ทำให้ schema ที่แก้ไขไปแล้วกลับมาเหมือนเดิม ต้องเปลี่ยน schema ใหม่อีกครั้ง ก็ยิ่งจะเพิ่มความเสี่ยงที่จะเกิดความผิดพลาด และยุ่งยากในการบริการจัดการ source code

    เราสามารถทำให้กระบวนการแก้ไขหรือลบ schema ใน EDMX file เป็นไปโดยอัตโนมัติ โดยการแก้ใข .csproj เพิ่มกระบวนการแก้ไขหรือลบ schema เข้าไปในขั้นตอนการ build ของ MsBuild หลังจากกระบวนการ “EntityDeployEmbededResource” ของ EF ดังนี้

     

    <Target Name="RemoveSchemaEntityDeployEmbeddedResources" AfterTargets="EntityDeployEmbeddedResources" Condition="'@(EntityDeployEmbeddingItems)' != ''">
      <PropertyGroup>
        <RemoveSchemaEmbeddedResources>"Libs\EFRemoveSchema" $(EntityDeployIntermediateResourcePath)%(EntityDeployEmbeddedResources.EntityDeployRelativeDir)</RemoveSchemaEmbeddedResources>
      </PropertyGroup>
      <Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="$(RemoveSchemaEmbeddedResources)" />
    </Target>

    “Libs\EFRomoveSchema” เป็นโปรแกรมเล็กๆที่พัฒนาเพื่อลบ schema ใน Entity Data Model ที่อยู่ใน folder   $(EntityDeployIntermediateResourcePath)%(EntityDeployEmbeddedResources.EntityDeployRelativeDir) โดยใช้ เทคนิคการค้นหา attribute ของ node ที่ต้องการใน XML file (EDMX file) เพื่อลบ และบันทึกกลับลงไปที่ XML file นั้นๆ

     

    อ้างอิง : https://msdn.microsoft.com/en-us/data/ee712907

  • ASP.NET Core Part I

    asp.net core คือ cross-platform framework สำหรับการพัฒนา web application ที่ทำงานบน .net core หรือ  full .net framework เดิม ( .net core สามารถใช้งานได้ทั้ง Windows , Linux และ MacOS โดยที่ส่วนประกอบต่างๆของ .net core ไม่ว่าจะเป็น runtime, libraries, compiler, language และเครื่องมือต่างๆ เป็น open source ทั้งหมด )  ซึ่ง asp.net core ได้รับการออกแบบใหม่ให้มีประสิทธิภาพดีกว่า asp.net เดิมโดยแบ่งส่วนต่างๆออกเป็น module ย่อยเพื่อลด overhead ในการเริ่มต้นทำงาน ซึ่ง asp.net core ประกอบไปด้วยกลุ่มของ NuGet package แทนที่การใช้งาน System.Web.dll ใน asp.net เดิม ซึ่งผู้พัฒนาสามารถเลือกเฉพาะ package ที่ต้องใช้งานเท่านั้น ทำให้ application มีขนาดเล็กลง มีประสิทธิภาพเพิ่มขึ้น,การพัฒนา Web UI และ Web API จะใช้ libraries เดียวกัน, สนับสนุนการใช้งาน dependency injection, web application สามารถใช้งานบน IIS หรือ self-host ภายใต้ process ของตัวเอง

    ในการพัฒนา asp.net core เราสามารถใช้เครื่องมือที่เป็น text editor ธรรมดาหรือจะใช้เครื่องมือช่วยในการพัฒนาอย่างเช่น Visual Studio ก็ได้ ในส่วนของโครงสร้างของ project asp.net core จะเปลี่ยนไปจากเดิม โดยการกำหนดค่า config ของ project สามารถกำหนดได้ที่ project.json

    {
     "title": "asp.net.core",
     "version": "1.0.0",
    
     "dependencies": {
       "NETStandard.Library": "1.6.0",
       "Newtonsoft.Json": "9.0.1"
     },
    
     "frameworks": {
       "netstandard1.6": {
          "imports": "dnxcore50"
        }
      }
    }

    การ reference ไปยัง NuGet package ที่ต้องการใช้งานใน project สามารถกำหนดได้ใน project.json โดยพิมพ์ชื่อ NuGet package ที่ต้องการพร้อมทั้งระบุ vesrion ในส่วน “dependencies” ซึ่งเมื่อทำการบันทึก project.json เครื่องมืออย่างเช่น visual studio จะทำการ restroe NuGet package ให้กับ project โดยอัตโนมัติ

    asp.net core ได้รับการออกแบบให้รองรับ client-side framework ต่างๆเช่น AngularJS หรือ bootstrap โดยใช้เครื่องมือที่เป็น package manager ในติดตั้ง client-side package ที่ต้องการใช้งาน อย่างเช่น Bower ที่จะกำหนด package ที่ต้องการใช้งานใน bower.json

    {
     "name": "asp.net",
     "private": true,
     "dependencies": {
       "bootstrap": "3.3.6",
       "jquery": "2.2.0",
       "jquery-validation": "1.14.0",
       "jquery-validation-unobtrusive": "3.2.6"
     }
    }

    หรือ npm ที่จะกำหนด package ที่ต้องการใช้งานใน package.json

    {
     "name": "asp.net",
     "version": "1.0.0",
     "private": true,
     "devDependencies": {
       "gulp": "3.8.11",
       "gulp-concat": "2.5.2",
       "gulp-cssmin": "0.1.7",
       "gulp-uglify": "1.2.0",
       "rimraf": "2.2.8",
       "typings": "1.0.5"
     },
     "dependencies": {
       "core-js": "^2.4.0",
       "reflect-metadata": "^0.1.3",
       "rxjs": "5.0.0-beta.6",
       "systemjs": "0.19.27",
       "zone.js": "^0.6.12"
     }
    }

    โดยเมื่อทำการบันทึก package manager จะทำการ restore package ที่ระบุ (bower.json หรือ package.json) ให้กับ project โดยอัตโนมัติ โดยที่ Bower จะติดตั้งลงใน /wwwroot/lib ในขณะที่ npm จะติดตั้งลงไปที่ folder /node_modules

    ในส่วนของ web root ของ asp.net core project จะอยู่ที่ folder /wwwroot ซึ่งต่างจาก asp.net เดิมที่ใช้ root folder ของ project เป็น web root โดยที่ /wwwroot จะเป็น folder ที่เก็บพวก static resources ต่างๆเช่น css, js และ image files

    entry point สำหรับ asp.net core application จะอยู่ที่ class “Startup” ซึ่งจะเป็น class ที่ใช้ในการกำหนด configuration และ service ต่างๆที่จะใช้ใน application โดย asp.net จะทำการค้นหา class ที่มีชื่อ startup ใน primary assembly (ในทุก namespace และไม่สนใจว่า class Startup จะเป็น public class หรือไม่ก็ตาม) ใน class Startup จะต้องมี method “Configure” ซึ่ง asp.net จะเรียกใช้งานตอนเริ่มต้น application (สำหรับ method “ConfigureServices” จะมีหรือไม่ก็ได้)

    สำหรับ Configure method จะต้องรับ parameter type “IApplicationBuilder” และอาจจะระบุ service ที่ต้องการใช้งานเช่น IHostingEnvironment และ ILoggerFactory ซึ่ง service เหล่านี้จะถูก inject โดย server โดยอัตโนมัติ

    IApplicationBuilder ถูกใช้ในการสร้าง application request pipeline ซึ่งสามารถเข้าถึงได้ผ่านทาง Configure method ใน Startup เท่านั้น

    IHostingEnvironment จะใช้เพื่อเข้าถึงข้อมูลสภาพแวดล้อมของ application เช่น EnvironmentName, ContentRootPath, WebRootPath และ web root file provider

    ILoggerFactory ให้ความสามารถในการทำ logging ซึ่งสามารถเข้าถึงได้ผ่านทาง Startup constructor และ Configure method ใน Startup เท่านั้น

     public void Configure(IApplicationBuilder app, 
                           IHostingEnvironment env,
                           ILoggerFactory loggerFactory)
     {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
    
        if (env.IsDevelopment())
        {
          app.UseDeveloperExceptionPage();
          app.UseDatabaseErrorPage();
          app.UseBrowserLink();
        }
        else
        {
          app.UseExceptionHandler("/Home/Error");
        }
    
        app.UseStaticFiles();
    
        app.UseMvc(routes =>
        {
           routes.MapRoute(
           name: "default",
           template: "{controller=Home}/{action=Index}/{id?}");
         });
    }

    จะเห็นว่ามีการใช้ “Use” extension method เพื่อเพิ่ม middleware เข้าไปสู่ request pipeline ของ asp.net ตัวอย่างเช่น “UseMvc” extension method จะเป็นการเพิ่ม routing middleware เข้าไปสู่ request pipeline

    ใน part I นี้ได้กล่าวถึงส่วนหลักๆของ asp.net core (asp.net 5)  ที่เปลี่ยนไปจาก asp.net เดิม ทั้งในส่วนของ framework และการรองรับการพัฒนา client-side ในส่วนต่อไปเราจะเริ่มต้นพัฒนา asp.net core โดยการสร้าง asp.net core web application project ในแบบง่ายๆ

    อ้างอิง : https://docs.asp.net