مطالب
پیاده سازی PWA در Angular
امروزه طراحی اپلیکیشن‌های موبایل بخش زیادی از جامعه را در برگرفته است و روز به روز در حال توسعه میباشند. موازی با رشد روز افزون و نیاز بیشتر به این اپلیکیشن‌ها فریمورک‌های زیادی نیز  ابداع شده اند. از جمله این فریم ورک‌ها می‌توان به موارد زیر اشاره کرد:

Ionic  , react native , flutter , xamarin ….

دیگر لازم نیست برای طراحی اپلیکیشن خود حتما از زبان‌های native  استفاده کنید. بیشتر فریم ورک‌های معرفی شده جاوا اسکریپتی میباشند.

 
مزایای نوشتن یک اپلیکیشن با  فریم ورک‌ها:

1-  نوشتن کد به مراتب آسانتر است.
۲- چون اکثر فریم ورک‌ها، فریم ورک‌های جاوا اسکریپتی هستند، سرعت بالایی هنگام run کردن برنامه دارند ولی در build آخر و خروجی نهایی بر روی پلتفرم، این سرعت کندتر می‌شود.
۳- cossplatform بودن. با طراحی یک اپلیکیشن برای یک پلتفرم می‌توان همزمان برای پلتفرم‌های دیگر خروجی گرفت.


معایب:

۱- برنامه نویس را محدود  میکند.
۲- سرعت اجرای پایینی دارد.
۳- حجم برنامه به مراتب بالاتر میباشد.
۴- از منابع سخت افزاری بیشتری استفاده می‌کند.

اگر شما هم میخواهید از فریم ورک‌ها برای طراحی اپلیکیشن استفاده کنید در اینجا  می‌توانید اطلاعات بیشتری را درباره هر کدام از فریم ورک‌ها ببینید. هر کدام از این فریم ورک‌ها دارای مزایا معایب و همچنین رزومه کاری خوب می‌باشند. بیشتر فریم ورک‌های بالا در رندر آخر، همان کد native را تولید می‌کنند؛ مثلا اگر برنامه‌ای را با react native بنویسید، میتوانید همان برنامه را بر روی اندروید استودیو و کد native بالا بیارید. توضیحات  بالا مقایسه فریم ورک‌های Cross Platform با زبان‌های native بود. اما در این مقاله قصد آشنایی با تکنولوژی جدیدی را داریم که شما را قادر می‌سازد یک وب اپلیکیشن را با آن طراحی کنید.


 pwa چیست؟

pwa مخفف Progressive Web Apps است و یک تکنولوژی برای طراحی وب اپلیکیشن‌های تحت مرورگر میباشد. شما با pwa میتوانید اپلیکیشن خود را بر روی پلتفرم‌های مختلفی اجرا کنید، طوری که کاربران متوجه نشوند با یک صفحه‌ی وبی دارند کار میکنند. شاید شما هم فکر کنید این کار را هم میتوان با html ,css و responsive کردن صفحه انجام داد! ولی اگر بخواهید کاربر متوجه استفاده‌ی از یک صفحه‌ی وبی نشود، باید حتما از pwa استفاده کنید! برای مثال یک صفحه‌ی وبی معمولی حتما باید در بستر اینترنت اجرا شود، ولی با pwa با یکبار وصل شدن به اینترنت و کش کردن داده‌ها، برای بار دوم دیگر نیازی به اینترنت ندارد و میتواند به صورت offline  کار کند. شما حتی با pwa می‌توانید اپلیکیشن را در background اجرا کنید و notification ارسال کنید.


مزیت pwa

۱- یکی از مزیت‌های مهم pwa، حالت offline آن میباشد که حتی با قطع اینترنت، شما میتوانید همچنان با اپلیکیشن کار کنید.
۲- با توجه به اینکه شما در حقیقت با یک صفحه‌ی وبی کار میکنید، دیگر نیازی به دانلود و نصب ندارید.
۳- امکان به‌روز رسانی کردن، بدون اعلام کردن نسخه جدید.
۴- از سرعت بسیار زیادی برخوردار است.
۵- چون pwa از پروتکل https استفاده می‌کند، دارای امنیت بالایی میباشد.


معایب

۱- محدود به مرورگر می‌باشد.
۲- هرچند امروزه اکثر مرورگر‌ها pwa را پشتیبانی میکنند، ولی در بعضی از مرورگر‌ها و مرورگر‌های با ورژن پایین، pwa پشتیبانی نمیشود.
3- نمیتوان یک برنامه‌ی مبتنی بر os را نوشت و محدود به مرورگر میباشد


چند نکته درباره pwa

1- برای pwa  لزومی ندارد حتما از فریم ورک‌های spa استفاده کنید. شما از هر فریم ورک Client Side ای می‌توانید استفاده کنید.
۲- چون صفحات شما در پلتفرم‌های مختلف و با صفحه نمایش‌های مختلفی اجرا می‌شود، باید صفحات به صورت کاملا responsive شده طراحی شوند.
۳- باید از پروتکل https استفاده کنید.

ما در این مقاله از فریم ورک angular  استفاده  خواهیم کرد.

قبل از شروع، با شیوه کار pwa آشنا خواهیم شد. یکی از قسمت‌های مهم  Service Worker ،pwa میباشد که از جمله کش کردن، notification فرستادن و اجرای پردازش‌ها در پس زمینه را بر عهده دارد.


با توجه به شکل بالا، Service Worker مانند یک لایه‌ی نرم افزاری مابین کلاینت و سرور قرار دارد که درخواست‌های داده شده از کلاینت را در صورت اتصال به اینترنت، ارسال کرده و مجددا response را برگشت میدهد‌. اگر به هر دلیلی اینترنت قطع باشد، درخواست به صورت آفلاین به کش مرورگر که توسط proxy ساخته شده است، فرستاده میشود و response را برگشت میدهد.


چند نکته  در رابطه با Service Worker

- نباید برای  نگهداری داده global از Service Worker استفاد کرد. برای استفاده از داده‌های Global میتوان از Local Storage یا IndexedDB استفاده کرد.
- service worker  به dom دسترسی ندارند.


 سناریو

 قبل از شروع، سناریوی پروژه را تشریح خواهیم کرد. رکن اصلی یک برنامه‌ی وب، UI آن میباشد و قصد داریم کاربران را متوجه کار با یک صفحه‌ی وبی نکنیم. قالبی که ما در این مثال در نظر گرفتیم خیلی شبیه به یک اپلیکیشن پلتفرم اندرویدی میباشد. یک کاربر با کشیدن منوی کشویی میتواند گزینه‌های خود را انتخاب نمایند. اولین گزینه‌ای که قصد پیاده سازی آن را داریم، ثبت کاربران میباشد. بعد از ثبت کاربران در یک Component جدا، کاربران را در یک جدول نمایش خواهیم داد.


 قبل از شروع لازم است به چند نکته زیر توجه کرد

1-  سرویس crud را به صورت کامل در پروژه قرار خواهیم داد، ولی چون از حوصله‌ی مقاله خارج است، فقط ثبت کاربران و نمایش کاربران را پیاده سازی خواهیم کرد.
2 - هر اپلیکیشنی قطعا نیاز به یک وب سرویس دارد که واسط بین دیتابیس و اپلیکیشن باشد. ولی ما چون در این مثال به عنوان front end کار قصد طراحی  اپلیکیشن را داریم، برای حذف back end از firebase استفاده خواهیم کرد و مستقیما از انگولار به دیتابیس firebase کوئری خواهیم زد.


 شروع به کار

پیش نیاز‌های یک پروژه‌ی انگیولاری را بر روی سیستم خود فراهم کنید. ما در این مثال از یک template آماده انگیولاری استفاده خواهیم کرد. پس برای اینکه با جزئیات و طراحی ui درگیر نشویم، از لینک github پروژه را دریافت کنید.
سپس وارد root پروژه شوید و با دستور زیر پکیج‌های پروژه را نصب کنید:
 npm install
پس از نصب پکیج‌ها، با دستور ng serve، پروژه را اجرا کنید.

پس از اجرا باید خروجی بالا را داشته باشید. می‌خواهیم یکی از قابلیت‌های pwa را بررسی کنیم. بر روی صفحه کلیک راست کرده و وارد inspect شوید. وارد تب Application  شوید و Service Worker را انتخاب کنید.
 


قبل از اینکه کاری را انجام دهید، چند بار صفحه را refresh کنید! صفحه بدون هیچ تغییری refresh میشود. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید.
این بار صفحه No internet به معنی قطع بودن اینترنت نمایش داده میشود و شما دیگر نمی‌توانید بر روی اپلیکیشن خود فعالیتی داشته باشید! 


نصب pwa  بر روی پروژه

برای اضافه کردن pwa به پروژه وارد ریشه‌ی پروژه شوید و دستور زیر را وارد کنید:
 ng add @angular/PWA
پس از  اجرای دستور بالا، تغییراتی در پروژه لحاظ میشود از جمله در فایل‌هایindex.html,app-module,angular.json، همچنین یک پوشه‌ی جدید به نام icons در assets و دو فایل جیسونی زیر به روت پروژه اضافه شده‌اند:
 Manifest.json : اگر محتویات فایل را مشاهده کرده باشید، شامل تنظیمات فنی وب اپلیکیشن می‌باشد؛ از جمله Home Screen Icon و نام وب اپلیکیشن و سایر تنظیمات دیگر.
 Nsgw-config.json : این فایل نسبت به فایل manifest فنی‌تر میباشد و بیشتر به کانفیگ مد آفلاین و کش کردن مرتبط میشود. در ادامه با این فایل بیشتر کار داریم.

بعد از نصب pwa  بر روی پروژه، همه تنظیمات به صورت خودکار انجام می‌شود. البته می‌توانید تنظیمات مربوط به کش کردن داده‌ها را به صورت پیشرفته‌تر کانفیگ کنید.


  اجرا کردن وب اپلیکیشن

 برای اجرا کردن و نمایش خروجی از وب اپلیکیشن، ابتدا باید از پروژه build گرفت. با استفاده از دستور زیر از پروژه خود build بگیرید:
 ng build -- prod
سپس  با دستور زیر وارد پوشه‌ای که فایل‌های Build  شده در آن قرار دارند، شوید:
 cd dist/Web Application  Pwa
بعد با دستور زیر برنامه را اجرا کنید:
 Http-server -o
اگر چنانچه با دستور بالا به خطا برخوردید، با دستور زیر پکیج npm آن را نصب کنید:
 npm i http-server
اگر تا به اینجای کار درست پیش رفته باشید، پروژه را بدون هیچ تغییری مشاهده خواهیم کرد. inspect گرفته و وارد تب Application شوید. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید! اینبار برنامه بدون هیچ مشکلی کار میکند. حتی شما میتوانید در کنسول، برنامه را به صورت کامل stop کنید.


برسی فایل Nsgw-config.json 

وارد فایل Nsgw-config.json شوید: 

"$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }

    }
  ],
  "dataGroups": [
    {
      "name": "api-performance",
      "urls": [
        "https://api/**"
      ],
      "cacheConfig": {
        "strategy": "performance",
        "maxSize": 100,
        "maxAge": "3d"
      }
    }
  ]
}
 این فایل مربوط به کش کردن داده‌ها میباشند و میتواند شامل چند object زیر باشد. مهمترین آنها را بررسی خواهیم کرد:
  ۱- assetGroups : کش کردن اطلاعات مربوط به اپلیکیشن
  ۲- Index : کش کردن  فایل مربوط به index.html
  ۳-  assetGroups : کش کردن فایل‌های مربوط به asset، شامل فایل‌های js، css  و  غیره
  ۴- dataGroups : این object مربوط به وقتی است که برنامه در حال اجرا است. میتوان داده‌های در حال اجرای اپلیکیشن را کش کرد. داده‌ی در حال اجرا میتواند شامل فراخوانی apiها باشد. برای مثال فرض کنید شما در حالت کار کردن online با اپلیکیشن، لیستی از اسامی کاربران را از api گرفته و نمایش میدهید. وقتی دفعه‌ی بعد در حالت offline  اپلیکیشن را باز کنیم، اگر api را کش کرده باشیم، اپلیکیشن داده‌ها را از کش فراخوانی میکند. این عمل درباره post کردن داده‌ها هم صدق میکند.

خود dataGroups شامل چند object زیر میباشد:
  ۱- name :  یک نام انتخابی برای Groups  میباشد.
 ۲- urls  : شامل آرایه‌ای از آدرس‌ها میباشد. میتوان آدرس یک دومین را همراه با کل apiها به صورت زیر کش کرد:
"https://api/**"
۳- cacheConfig  : که شامل
  ۱- maxSize  : حداکثر تعداد کش‌های مربوط به Groups .
  ۲- maxAge  : حداکثر  lifetime مربوط به کش.
  ۳- strategy  : که میتواند یکی از مقادیر freshness به معنی Network-First یا performance  به معنی Cache-First باشد.


 پیاده سازی پیغام نمایش به‌روزرسانی جدید وب اپلیکیشن

در اپلیکیشن‌های native  وقتی به‌روزرسانی جدیدی برای app اعلام میشود، در فروشگاهای اینترنتی پیغامی مبنی بر به‌روزرسانی جدید app برای کاربران ارسال میشود که کاربران میتوانند app خود را به‌روزرسانی کنند. ولی در pwa تنها با یک رفرش صفحه میتوان اپلیکیشن را به جدیدترین امکانات به‌روزرسانی کرد! برای اینکه بتوانیم با هر تغییر، پیغامی را جهت به‌روزرسانی نسخه یا هر  پیغامی دیگری را نمایش دهیم، از کد زیر استفاده میکنم:
if(this.swUpdate.isEnabled)
    {
      this.swUpdate.available.subscribe(()=> {

        if(confirm("New Version available.Load New Version?")){
          window.location.reload();
        }
      })
    }


وصل شدن به دیتابیس

برای اینکه بتوانیم یک وب اپلیکیشن کامل را طراحی کنیم، قطعا نیاز به دیتابیس داریم. برای دیتابیس از firebase استفاده میکنیم. قبل از شروع، وارد سایت firebase  شوید. پس از لاگین، بر روی Add Project کلیک کنید. بعد از انتخاب نام مناسب، create Project را انتخاب کنید. ساختار دیتابیس‌های firebase  شبیه nosqlها می‌باشد و بر پایه‌ی json کار میکنند. پس از ساخت پروژه و دریافت کد جاواسکریپتی زیر شروع به طراحی فیلد‌های دیتابیس خواهیم کرد.


در منوی سمت چپ، بر روی database کلیک کنید و یک دیتابیس را در حالت test mode  ایجاد نماید. سپس یک collection را به نام user ایجاد کرده و فیلد‌های زیر را به آن اضافه کنید:
Age :number
Fullname :string
Mobile : string
مرحله‌ی بعد، config پروژه‌ی انگیولاری میباشد. وارد  vscode شوید و با دستور زیر پکیج firebase را اضافه کنید:
  npm install --save firebase @angular/fire
سپس وارد appmodule شوید و آن‌را به صورت زیر کانفیگ کنید:

@NgModule({
  declarations: [AppComponent, RegisterComponent,AboutComponent, UserListComponent],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    HttpClientModule,
    SharedModule,
    AppRoutingModule,
    AngularFireModule.initializeApp(environmentFirebase.firebase),
    AngularFireDatabaseModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
  ],
  providers: [FirebaseService],
  bootstrap: [AppComponent]
})
export class AppModule {}
خط ۱۰ مربوط به pwa است که هنگام نصب pwa اضافه شده‌است و خط ۸ و مربوط به apiKey فایربیس میباشد. می‌شود گفت environmentFirebase.firebase شبیه  connection string می‌باشد که به صورت زیر کانفیگ شده است:
export const environment ={
   production: true,
   firebase: {
     apiKey: "AIz×××××××××××××××××××××××××××××××××8",
     authDomain: "pwaangular-6c041.firebaseapp.com",
     databaseURL: "https://pwaangular-6c041.firebaseio.com",
     projectId: "pwaangular-6c041",
     storageBucket: "pwaangular-6c041.appspot.com",
     messagingSenderId: "545522081966"
   }

}

FirebaseService در قسمت providers مربوط به سرویس crud میباشد. اگر وارد فایل مربوطه شوید، چند عمل اصلی به صورت زیر در آن پیاده سازی شده است:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import * as firebase from 'firebase';
import { AngularFireDatabase } from '@angular/fire/database';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { ThrowStmt } from '@angular/compiler';
@Injectable({
  providedIn: 'root'
})
export class FirebaseService {

  ref = firebase.firestore().collection('users');
  constructor(public db: AngularFireDatabase,public _http:HttpClient) { 
 
  }

  getUsers(): Observable<any> {
    return new Observable((observer) => {
      this.ref.onSnapshot((querySnapshot) => {
        let User = [];
        querySnapshot.forEach((doc) => {
          let data = doc.data();
          User.push({
            key: doc.id,
            fullname: data.fullname,
            age: data.age,
            mobile: data.mobile
          });
        });
        observer.next(User);
      });
    });
  }

  getUser(id: string): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).get().then((doc) => {
        let data = doc.data();
        observer.next({
          key: doc.id,
          title: data.title,
          description: data.description,
          author: data.author
        });
      });
    });
  }

  postUser(user): Observable<any> {
    return new Observable((observer) => {
      
      this.ref.add(user).then((doc) => {
        observer.next({
          key: doc.id,
        });
      });
    });
  }

  updateUser(id: string, data): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).set(data).then(() => {
        observer.next();
      });
    });
  }

  deleteUser(id: string): Observable<{}> {
    return new Observable((observer) => {
      this.ref.doc(id).delete().then(() => {
        observer.next();
      });
    });
  }

getDataOnApi(){
  return this._http.get('https://site.com/api/General/Getprovince')
  .pipe(
      map((res: Response) => {
return res;

      })
  );
}

getOnApi(){
  return  this._http.get("https://site.com/api/General/Getprovince",).pipe(
    map((response:any) => {
       
return  response
    } )
    );
}
}
اگر دقت کرده باشید، خیلی شبیه به کد نویسی سمت سرور میباشد.

با دستورات زیر میتوانید مجددا پروژه را اجرا کنید:
ng build --prod
cd dist/Pwa-WepApp
Http-server -o


تست وب اپلیکیشن بر روی پلتفرم‌های مختلف

برای اینکه بتوانیم خروجی وب اپلیکیشن را بر روی پلتفرم‌های مختلفی تست کنیم، میتوانیم آن را آپلود کرده و مثل یک سایت اینترنتی، با وارد کردن دومین، وارده پروژه شد. ولی کم هزینه‌ترین راه، استفاده از ابزار ngrok میباشد. میتوانید توسط این مقاله پروژه خودتان در لوکال بر روی https سوار کنید.

نکته! توجه کنید apiهای مربوط به firebase را نمیتوان کش کرد.


کد‌های مربوط به این قسمت را میتوانید از این repository دریافت کنید .
مطالب
استفاده از GitHub Actions برای Build و توزیع خودکار پروژه‌های NET Core.
پیشتر مطلب « تولید و ارسال خودکار بسته‌های NuGet پروژه‌های NET Core. به کمک AppVeyor » را در این سایت مطالعه کرده‌اید. اخیرا GitHub نیز دقیقا همین امکانات یکپارچگی مداوم یا Continuous Integration را تحت عنوان GitHub Action، به مخازن کد خود اضافه کرده‌است. البته این قابلیت هنوز در مرحله‌ی بتا است و برای فعالسازی آن بر روی مخازن کد خود نیاز است در اینجا ثبت نام کنید. بعد از یکی دو روز صبر کردن، این برگه‌ی جدید، به مخازن کد شما اضافه خواهد شد:



فعالسازی GitHub Action مخصوص NET Core.

در ادامه قصد داریم از این قابلیت جدید، جهت Build خودکار پروژه‌های NET Core. و در آخر ارسال خودکار بسته‌های نیوگت متناظر آن‌ها به سایت nuget.org، استفاده کنیم. برای این منظور به برگه‌ی Actions مخزن کد خود مراجعه کنید (تصویر فوق). سپس در این صفحه، بر روی لینک Work flows for … more کلیک کنید:


تا امکان انتخاب گردش کاری متناظر با NET Core. ظاهر شود:


در اینجا بر روی دکمه‌ی «Set up this workflow» کلیک کنید تا صفحه‌ی ویرایشی این گردش کاری که با فرمت yml است، ظاهر شود.


 محتویات آن‌‌را برای نمونه می‌توانید به صورت زیر تغییر دهید:
name: .NET Core Build

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v1
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.100-preview9-014004        
    - name: Build DNTCaptcha.Core lib
      run: dotnet build ./src/DNTCaptcha.Core/DNTCaptcha.Core.csproj --configuration Release
در این گردش کاری، ابتدا SDK با شماره 3.0.100-preview9-014004 به صورت خودکار نصب خواهد شد. سپس دستور dotnet build بر روی یک فایل csproj خاص، اجرا می‌گردد. اگر نتیجه‌ی این Build موفقیت آمیز بود، یک علامت چک‌مارک سبز رنگ ظاهر می‌شود و در غیر اینصورت یک ایمیل شکست عملیات را نیز دریافت خواهید کرد.
پس از تکمیل محتوای فایل yml در این مرحله، در کنار صفحه بر روی لینک start commit کلیک کنید، تا این فایل را به صورت خودکار در مسیر github\workflows\aspnetcore.yml ذخیره کند. بدیهی است تغییرات آن‌را در قسمت commits مخزن کد نیز می‌توانید مشاهده کنید.


این فایل on: [push] کار می‌کند. یعنی اگر تغییری را به مخزن کد اعمال کردید، همانند یک تریگر عمل کرده و عملیات Build را به صورت خودکار آغاز می‌کند.



اضافه کردن نماد گردش کاری GitHub به پروژه

هر گردش کاری تعریف شده را می‌توان با یک نماد یا badge در فایل readme.md پروژه نیز نمایش داد:


فرمول آن نیز به صورت زیر است:
https://github.com/<OWNER>/<REPOSITORY>/workflows/<WORKFLOW_NAME>/badge.svg
یک مثال:
 ![Github tags](https://github.com/VahidN/DNTCaptcha.Core/workflows/.NET%20Core%20Build/badge.svg)
در اینجا نام گردش کاری، همان نامی است که داخل فایل yml درج شده‌است و نه نام فایل متناظر. بدیهی است این نام را باید به صورت encoded وارد کنید. برای مثال اگر دارای فاصله است، نیاز است 20%‌های فواصل را به این نام اضافه کنید تا URL نهایی درست کار کند.


تکمیل گردش کاری Build، جهت تولید خودکار و ارسال یک بسته‌ی نیوگت

برای ارسال خودکار حاصل Build به سایت نیوگت، نیاز است یک API Key داشته باشیم. به همین جهت به صفحه‌ی مخصوص آن در سایت nuget پس از ورود به سایت آن، مراجعه کرده و یک کلید API جدید را صرفا برای این پروژه تولید کنید (در قسمت Available Packages بسته‌ی پیشینی را که دستی آپلود کرده بودید انتخاب کنید).


پس از کپی کردن کلید تولید شده‌ی در سایت nuget:


به قسمت settings -> secrets مخزن کد خود مراجعه کرده و این کلید را به صورت زیر وارد کنید:


در ادامه برای دسترسی به این کلید با نام NUGET_API_KEY، می‌توان از روش {{ secrets.NUGET_API_KEY }}$ در اسکریپت گردش کاری استفاده کرد.

اکنون که مخزن کد به همراه کلید API نیوگت است، می‌توان مراحل dotnet pack (برای تولید فایل nupkg) و سپس dotnet nuget push (برای انتشار خودکار فایل nupkg) را به صورت زیر، به گردش کاری خود اضافه نمود:
name: .NET Core Build

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v1
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.100-preview9-014004        
    - name: Build DNTCaptcha.Core lib
      run: dotnet build ./src/DNTCaptcha.Core/DNTCaptcha.Core.csproj --configuration Release
    - name: Build NuGet Package
      run: dotnet pack ./src/DNTCaptcha.Core/DNTCaptcha.Core.csproj --configuration Release
    - name: Deploy NuGet Package
      run: dotnet nuget push ./src/DNTCaptcha.Core/bin/Release/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
ویرایش یک گردش کاری، همانند ویرایش یک فایل متنی معمولی مخزن کد است. می‌توان آن‌را به صورت محلی در مسیر github\workflows\aspnetcore.yml. ویرایش کرد و سپس commit، تا سبب به روز رسانی گردش کاری پروژه شود.
نظرات مطالب
React 16x - قسمت 34 - توزیع برنامه
دستور npm run build نام فایل‌های نهایی را به همراه هش محتوای آن‌ها تولید می‌کند:

این روشی هست که در Angular هم برای cache busting مورد استفاده قرار می‌گیرد. به همین جهت فایل‌های تغییر یافته، دارای هش جدیدی خواهند بود که به عنوان یک فایل js جدید، حتما مجددا توسط مرورگر از سایت دریافت خواهند شد و کش نمی‌شوند.

مطالب
ایجاد ابزارهای سراسری ویژه NET Core.
از زمان ارائه نگارش net core 2.1.، ابزارهای سراسری (Global tools) نیز معرفی شدند. استفاده از این ابزارها در محیط cli در جهت آسان‌تر شدن و سریعتر شدن وظایف، صورت می‌پذیرد. net core sdk. مربوطه، تمامی امکانات لازم از جهت ایجاد، حذف و به روزرسانی ابزارها را از طریق nuget شامل می‌گردد. تعداد بسیار زیادی از این ابزارها در حال حاضر ایجاد شده‌اند که در لیست زیر، تعدادی از آن‌ها را معرفی میکنیم و سپس به نحوه‌ی ایجاد این نوع ابزارها میپردازیم.
  • dotnet-ignore : این ابزار جهت دریافت فایل‌های gitignore. کاربرد داشته و از یک مخزن عمومی گیت هاب جهت دریافت این فایل‌ها استفاده میکند. این مخزن شامل انواع قالب‌های gitignore در پروژه‌های متفاوت میباشد. با استفاده از این ابزار، ایجاد فایل gitignore راحت‌تر و سریعتر امکانپذیر میباشد.
  • dotnet-serve : میزبانی و نمایش لیست فایل‌های استاتیک محلی و اجرای آن‌ها را در بستر http، فراهم مینماید.
  • dotnet-cleanup : جهت پاکسازی محیط بیلد مانند دایرکتوری‌های bin و obj میباشد. همان کار گزینه clean در منوی بیلد را بازی میکند.
  • dotnet-warp : این ابزار در واقع پروژه Warp است که برای ایجاد یک تک فایل اجرایی جهت انتقال راحت‌تر فایل پروژه صورت میگیرد که همه وابستگی‌های آن در همان تک فایل قرار میگیرد.
  • Amazon.ECS.Tools Amazon.ElasticBeanstalk.Tools  و  Amazon.Lambda.Tools  : این ابزارها که به صورت رسمی از طرف آمازون ارائه شده‌اند که جهت deploy شدن راحت‌تر پروژه به محیط‌های توسعه وب آمازون مورد استفاده قرار میگیرند.
جهت مشاهده لیست کامل این ابزارها، به این مخزن گیت هاب مراجعه نمایید. نام ابزار و همچنین لینک‌ها و توضیحات هر کدام، در این مخزن موجود است. همچنین جهت اضافه شدن ابزاری که در لیست نیست، از طریق ایجاد issue یا pull request لیست را به روزرسانی نمایید.

نحوه‌ی نصب، حذف و به روزرسانی ابزارهای سراسری
جهت نصب یک ابزار، از دستور زیر استفاده میکنیم:
dotnet tool install -g dotnet-ignore
سوییچ g به معنای نصب سراسری ابزار و افزوده شدن آن به متغیرهای محیطی PATH میباشد که به راحتی در هر مسیری از محیط کنسول در دسترس خواهد بود و به مسیر dotnet/tools/. محدود نخواهد بود.
جهت مشاهده لیست تمامی ابزاهای سراسری نصب شده بر روی سیستم میتوانید از کامند زیر استفاده نمایید:
dotnet tool list -g
نحوه به روزرسانی ابزار و ارتقا آن به آخرین نسخه پایدار، با دستور زیر میباشد:
dotnet tool update -g dotnet-ignore
دستور حذف:
dotnet tool uninstall -g dotnet-ignore

ایجاد یک ابزار سراسری
جهت ساخت یک ابزار سراسری نیاز است تا یک پروژه را از نوع کنسول ایجاد نمایید و سپس به فایل csproj، خطوط زیر را اضافه کنید:
<PropertyGroup>
    <PackAsTool>true</PackAsTool>
    <ToolCommandName>dotnet-mytool</ToolCommandName>
    <PackageOutputPath>./nupkg</PackageOutputPath>
</PropertyGroup>
گزینه PackAsTool، امکان تبدیل فایل اجرایی شما را به یک ابزار سراسری فراهم میکند. دو گزینه بعدی که اختیاری است، به ترتیب شامل نام ابزار سراسری است که در صورت ذکر نشدن نام فایل پروژه، بدون پسوند csproj. میباشد و سومین مورد نیز مسیر قرارگیری فایل ابزار سراسری به عنوان یک بسته nuget میباشد.
جهت ساخته شدن فایل، ابتدا یکبار پروژه را بیلد کرده و پس از اجرای دستور dotnet pack، فایل پکیج در مسیر ذکر شده ساخته میشود و آماده انتقال به مخازن nuget میباشد. جهت تست و اجرای ابزار بر روی سیستم خود قبل از عرضه نهایی نیاز است تا با دستور زیر آن را بر روی سیستم خود نصب و آزمایش نمایید:
dotnet tool install --global --add-source ./nupkg globaltools
سوییچ global که در بالاتر نیز توضیح داده شد، باعث نصب سراسری ابزار میگردد و سوییچ add-source که بعد از آن مسیر فایل ابزار، آمده است، به این معنا است که به صورت موقت، این دایرکتوری یا مسیر را به عنوان مخزن nuget  شناسایی کرده تا امکان یافتن بسته در آن مسیر مهیا گردد و سپس نام پروژه در پایان ذکر میگردد. در آخر جهت اطمینان از نصب میتوانید ابزار را صدا بزنید:
dotnet-mytool
با توجه به اینکه اصل مطلب گفته در رابطه با ایجاد یک ابزار سراسری در اینجا به پایان میرسد، ولی ایجاد یک ابزار خط فرمانی نیازمند یک سری کدنویسی‌ها جهت ایجاد کامندها و سوییچ‌ها و راهنمای مربوط به آن نیز میباشد. بدین جهت کتابخانه زیر را نصب نمایید:
https://www.nuget.org/packages/McMaster.Extensions.CommandLineUtils
این کتابخانه شامل کلاس هایی جهت ایجاد یک ابزار خط فرمانی راحت‌تر میباشد.

ایجاد یک ابزار عمومی جهت یادداشت نویسی
برای استفاده از این کتابخانه، یک پروژه از نوع کنسول را با نام globaltools ایجاد نمایید و کتابخانه‌ی بالا را نصب نمایید. سپس به ازای هر کامند، یک کلاس را ایجاد میکنیم. ابتدا جهت ایجاد کامندی با نام NewNote یک کلاس را به همین نام میسازیم:
[Command(Description="Add a new note")]
    public class NewNote
    {
        [Required]
        [Option(Description="title of note")]
        public string Title{ get; set; }

        [Option(Description="content of note")]
        public string Body{ get; set; }
    }
با مزین کردن کلاس به ویژگی command، این کلاس را یک کامند معرفی کرده و شرحی از کاری که این کامند را انجام میدهد، نیز وارد می‌کنیم. این شرح بعدا در ابزار تولید شده به عنوان متن راهنما به کار می‌رود. سپس پراپرتی‌هایی را که با ویژگی option مزین گشته‌اند، به عنوان سوییچ معرفی میکنیم. همچنین میتوان از DataAnotation‌ها نیز جهت اعتبار سنجی نیز استفاده نمود. 
بعد از ایجاد موارد بالا، نیاز است که اکشنی که باید این کامند را اجرا کند، به آن اضافه کرد. جهت افزودن این اکشن، یک متد را با نام OnExecute، به بدنه این کلاس اضافه می‌کنیم:
[Command(Description="Add a new note")]
    public class NewNote:BaseClass
    {
        [Required]
        [Option(Description="title of note")]
        public string Title{ get; set; }

        [Option(Description="content of note")]
        public string Body{ get; set; }

        public void OnExecute(IConsole console)
        {

            var dir = GetBaseDirectory();
            if(!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            var filePath = Path.Combine(dir, Title + ".txt");
            File.WriteAllText(filePath, Body);
            console.WriteLine("the note is saved");
        }
    }
در پارامتر این متد، یک اینترفیس با نام IConsole جهت ارتباط با محیط کنسول دیده میشود که در پایان عملیات، پیام «یادداشت ذخیره شد» توسط آن چاپ میگردد. کار این متد به طور خلاصه این است که مسیر اجرایی ابزار جاری را دریافت کرده و سپس در یک دایرکتوری با نام notes، برای هر یادداشت یک فایل ایجاد شده و محتوای دریافتی از کاربر داخل آن قرار میگرد و نام هر فایل، موضوع یادداشتی است که کاربر وارد کرده‌است. متد GetBaseDirectory که مسیر ذخیره یادداشت‌ها را بر میگرداند، در کلاس BaseClass با محتوای زیر قرار گرفته است:
public class BaseClass
    {
        protected string GetBaseDirectory(){
            var baseDirectory = Environment.CurrentDirectory;
            return (Path.Combine(baseDirectory, "notes"));
        }
    }

کامند بعدی، لیست یادداشت‌های ثبت شده‌است:
public class List:BaseClass
    {
        [Option(Description="search a phrase in notes title")]
        public string Grep{ get; set; }
        public void OnExecute(IConsole console)
        {
            try
            {
                var baseDirectory = GetBaseDirectory();

                var dir = new DirectoryInfo(baseDirectory);
                var files = dir.GetFiles();
                foreach(var file in files)
                {
                    if(!String.IsNullOrEmpty(Grep) && !file.Name.Contains(Grep))
                        continue;

                    console.WriteLine(Path.GetFileNameWithoutExtension(file.Name));
                }
            }
            catch (Exception e)
            {
                console.WriteLine(e.Message);
            }
        }
    }
کار این کلاس، بازگردانی لیستی از یادداشت‌های ثبت شده است که حاوی سوییچ grep برای فیلتر کردن اسامی یادداشت هاست.
کلاس بعدی show نیز جهت نمایش کلاس بر اساس عنوان یادداشت است:
[Command(Description="show contnet of note")]
    public class Show:BaseClass
    {
        [Required]
        [Option(Description="title of note")]
        public string Title{ get; set; }


        public void OnExecute(IConsole console){
            var baseDirectory = GetBaseDirectory();
            var file = Path.Combine(baseDirectory, Title+".txt");

            if(!File.Exists(file))
            {
                console.WriteLine("The Note NotFound...");
                return;
            }
            console.WriteLine(File.ReadAllText(file));

        }
    }
در صورتیکه یادداشت مورد نظر وجود نداشته باشد، با پیام The Note NotFound کار به پایان میرسد.
بعد از اتمام کامندهای مربوطه، به کلاس program رفته و برای آن نیز ویژگی command را اضافه می‌کنیم و همچنین ویژگی subCommand را جهت معرفی کامندهایی که در برنامه در دسترس کاربر قرار میگیرند، اضافه میکنیم:
    [Command(Description="An Immediate Note Saver")]
    [Subcommand(typeof(NewNote),typeof(List),typeof(Show))]
    class Program
    {
        static int Main(string[] args)
        {
            return CommandLineApplication.Execute<Program>(args);
        }

        public int OnExecute(CommandLineApplication app, IConsole console)
        {
            console.WriteLine("You must specify a subcommand.");
            console.WriteLine();
            app.ShowHelp();
            return 1;
        }
    }
از آنجا که کلاس Program نیز به ویژگی command مزین شده‌است، متد OnExecute را اضافه می‌کنیم. تنها تفاوت این متد با متدهای قبلی، در نوع خروجی آن است که هر مقدار غیر از صفر، به منزله خطا میباشد. در این حالت چون کاربر کامندی را صادر نکرده است، ابتدا به کاربر اجباری بودن کامند را گوشزد کرده و سپس از طریق متد ShowHelp، راهنمای کار با ابزار را به او نشان داده و سپس کد یک را به منزله رخ دادن خطا یا اعلام شرایط غیرعادی بازمیگردانیم. نوع خروجی متد OnExecute در صورتی که void باشد، به معنای مقدار 0 میباشد که در کلاس‌های قبلی از آن استفاده کرده‌ایم.
در نهایت متد Main را نیز به شکل زیر تغییر می‌دهیم:
        static int Main(string[] args)
        {
            return CommandLineApplication.Execute<Program>(args);
        }
تکه کد CommandLineApplication.Execute آرگومان‌های ورودی را دریافت کرده و کامند مورد نظر را شناسایی میکند و همچنین مقدار عددی که از آن جهت return شدن استفاده می‌کند، همان عددهای صفر و غیر صفر میباشد که در بالا توضیح داده شده است.

نمونه استفاده از ابزار نهایی
PS D:\projects\Samples\globaltools> dotnet-notes new-note -t "sample1" -b "this is body"
the note is saved
PS D:\projects\Samples\globaltools> dotnet-notes new-note -t "test1" -b "this is body of another note"
the note is saved
PS D:\projects\Samples\globaltools> dotnet-notes list
sample1
test1
PS D:\projects\Samples\globaltools> dotnet-notes list -g sa
sample1
PS D:\projects\Samples\globaltools> dotnet-notes show -t sample1
this is body
در ابزار بالا کامند new-note به صورت جدا از هم با خط تیره مشخص شده‌است. دلیل این امر نیز جداشدن این کلمات در نام کلاس با حروف بزرگ است. در صورتیکه قصد ندارید نام کامندها با خط تیره از هم جدا شوند، باید نام کلاس را از NewNote به Newnote تغییر دهید.
نظرات مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
روش دیگری بجای استفاده از فایل‌های bat

در انتهای بحث یک سری فایل bat معرفی شدند که کار اجرای دستورات مرتبط را ساده‌تر می‌کنند. روش دوم اجرای این نوع دستورات، استفاده از همان فایل package.json است. قسمت scripts آن‌را به صورت ذیل تکمیل کنید:
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "_0-prerestore": "npm install",
    "_0-restore": "dotnet restore",
    "_1-ng-build-dev": "ng build --watch",
    "_1-ng-build-prod": "ng build --prod --watch",
    "_2-dotnet-run": "dotnet watch run",
    "_2-ng-serve-proxy": "ng serve --proxy-config proxy.config.json -o"
  },
سپس افزونه‌ی NPM Task Runner را نیز نصب کنید.
اکنون اگر بر روی فایل package.json، در ویژوال استودیو کلیک راست کنید، گزینه‌ی Task Runner Explorer اضافه شده‌است. با انتخاب آن امکانات ذیل ظاهر می‌شوند:


در اینجا با کلیک راست بر روی هر گزینه، امکان اجرای آن وجود خواهد داشت.
نظرات مطالب
استفاده از GitHub Actions برای Build و توزیع خودکار پروژه‌های NET Core.
یک نکته تکمیلی تر:
اگر بخواهید پروژه خود را توسط چندین تارگت NET Core. ایی Build کنید در حالت عادی امکان پذیر نیست. این محدودیت اینجا گزارش شده است و راه حل (workaround) آن نیز اینجا ارائه شده است.
در مورد پشتیبانی از چندین target (توسط خاصیت TargetFrameworks در فایل csproj.) قبلا اینجا توضیح داده شده است. تنها نکته این کد این است که برای تارگت هایی که توسط NET Core. بیلد میشوند کار میکند و نه تارگت‌های NET Framework Full.
با فرض اینکه مثلا target پروژه بر روی netcoreapp2.1 و  netcoreapp3.0  تنظیم شده است و یا هر target دیگری که توسط NET Core. قابل  Build شدن است (مثلا  netstandard2.0 و  netstandard2.1) مثال آن به نحو زیر خواهد بود.
name: Build
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Setup .NET Core 2.1
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 2.1.607

    - name: Setup .NET Core 3.0
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.101

    - name: .NET Core SxS
      run: |
        rsync -a ${DOTNET_ROOT/3.0.101/2.1.607}/* $DOTNET_ROOT/

    - name: Build (Release - netcoreapp2.1)
      run: dotnet build --configuration Release --framework netcoreapp2.1

    - name: Build (Release - netcoreapp3.0)
      run: dotnet build --configuration Release --framework netcoreapp3.0  
  • NET Core. نسخه‌های 2.1.607 و 3.0.101 در دو step نصب شده اند.
  • سپس sync کردن این دو توسط دستور rsync انجام شده است.
  • توسط تنظیم --framework به مقادیر netcoreapp2.1 و netcoreapp3.0 ، عملیات build توسط این دو target انجام شده است.
نظرات مطالب
استفاده از GitHub Actions برای Build و توزیع خودکار پروژه‌های NET Core.
یک نکته تکمیلی:
بعد از Build کردن ریپازیتوری یا Pack کردن پکیج خود می‌توانید فایل‌های تولید شده را در جایی به نام "Artifacts" ذخیره کنید تا در زمان دلخواه بتوانید آنها را دانلود کنید
به جهت انجام این کار باید از اکشن upload-artifact استفاده کنید. مثال:
name: Build
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
    - name: Build (Release)
      run: dotnet build src --configuration Release
    - name: Pack (Release)
      run: dotnet pack src --configuration Release

    - name: Upload Artifact
      uses: actions/upload-artifact@v1.0.0
      with:
        name: Release_Artifacts
        path: src\bin\Release
  • عبارت src به پوشه پروژه اصلی اشاره میکند. (در صورت نیاز آن را تغییر دهید)
  • عبارت "Release_Artifacts" نام فایل Artifacts خروجی شماست که با این نام ذخیره می‌شود.
  • مسیر "src\bin\Release" پوشه ای است که میخواهید محتوای آن را در Artifacts ذخیره کنید.
بعد از اتمام فرایند در صورت موفقیت آمیز بودن فرایند، فایل Artifacts را میتوانید از این قسمت دانلود کنید.