‫۱ سال و ۴ ماه قبل، دوشنبه ۲۸ فروردین ۱۴۰۲، ساعت ۰۳:۰۸
یک نکته‌ی تکمیلی

دقت داشته باشید در حالت استفاده از schedule event (مخصوصاً بازه‌های کوتاه (حداقل باید ۵ دقیقه باشد)) workflowها ممکن است با تاخیر اجرا شوند (چند دقیقه، چند ساعت) یا حتی اصلاً اجرا نشوند؛ در اینحالت بهتر است یک مکانیزم پشتیبان هم در نظر بگیرید (مثلاً استفاده از cloudflare  workers یا AWS lambda یا سرویس  IFTTT ) به عنوان مثال من یک schedule بر روی موبایل تنظیم کرده‌ام که هر روز در یک تایم مشخصی workflowها را به صورت اجرا کند چون تقریباً تنها دیوایسی که همیشه مطمئنم به اینترنت دسترسی دارد گوشی همراهم است.

البته نکات بالا با فرض اینکه این موارد ابتدا بررسی شده باشند:
  • عبارت CRON به درستی تنظیم شده باشد
  • تایم‌زون به درستی در نظر گرفته شده باشد (UTC حالت پیش‌فرض است)
  • مسیر workflowها صحیح باشند (.github/workflows)
  • workflow روی برنچ دیفالت باشد (مگر اینکه به صورت صریح برنچ را مشخص کرده باشید)
  • اکشن برای ریپوزیتوری فعال شده باشد (در قسمت تنظیمات)
  • از میزان مجاز استفاده از GitHub Actions عبور نکرده باشید؛ این مورد احتمالش خیلی کم است.
  • روی ریپوزیتوری fork شده نباشید؛ در اینجالت workflowها به secrets دسترسی ندارند. 
‫۱ سال و ۷ ماه قبل، چهارشنبه ۱۴ دی ۱۴۰۱، ساعت ۲۲:۳۰
یک نکته تکمیلی:
یکی از مشکلات استفاده از JSON.parse(JSON.stringify(originalObject)) برای کپی کردن آبجکت‌ها این است که از آبجکت‌های circular پشتیبانی نمیکند؛ به عنوان مثال کد ساختار زیر را در نظر بگیرید:
const a = { x: 20, date: new Date() };
a.c = a;
استفاده از JSON.parse... خطای زیر را صادر خواهد کرد:
Uncaught TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'c' closes the circle
    at JSON.stringify (<anonymous>)
    at <anonymous>:1:17
یکی دیگر از مشکلات این است که برای پراپرتی‌هایی از نوع Date به صورت خودکار Date.prototype.toJSON صدا زده خواهد شد:
const a = { x: 20, date: new Date() };
JSON.parse(JSON.stringify(a))
{x: 20, date: '2023-01-04T15:17:02.957Z'}
برای رفع این دست از مشکلات میتوانید از تابع توکار structuredClone استفاده کنید:
structuredClone(a)
// {x: 20, date: Wed Jan 04 2023 15:17:02 GMT+0000 (Greenwich Mean Time), c: {…}}
البته این تابع یکسری مشکلات هم دارد: توابع، کلاس‌ها، DOM قابل clone نیستند. 
‫۱ سال و ۸ ماه قبل، دوشنبه ۵ دی ۱۴۰۱، ساعت ۱۹:۲۱
اخیراً Docker Desktop از WebAssembly پشتیبانی میکند. به این معنا که بدون نیاز به یک کانتینر خاص میتوانیم توسط یک Wasm runtime با نام WasmEdge اپلیکیشن‌های وب‌اسمبلی را اجرا کنیم. در واقع توسط این runtime رفتار یک کانتینر شبیه‌سازی شده است بدون اینکه ایمیج کانتینر حاوی OS یا runtime context باشد. با این اوصاف میتوانیم یک پروژه NET.ی را توسط Wasi.Sdk به Wasm تبدیل کنیم و آن را درون Docker به صورت مستقیم اجرا کنیم:

dotnet new console -o MyFirstWasiApp
cd MyFirstWasiApp
dotnet add package Wasi.Sdk --prerelease
dotnet build
اگر به مسیر bin/Debug/net7.0 مراجعه کنید خواهید دید که MyFirstWasiApp.wasm نیز تولید شده است؛ از دستور wasmtime برای اجرای آن نیز میتوانیم استفاده کنیم:
wasmtime bin/Debug/net7.0/MyFirstWasiApp.wasm
در ادامه میتوانیم یک Dockerfile ایجاد کرده و خروجی Wasm applicationمان را به اصطلاح containerised کنیم:
FROM scratch

COPY bin/Debug/net7.0/MyFirstWasiApp.wasm MyFirstWasiApp.wasm

ENTRYPOINT [ "MyFirstWasiApp.wasm" ]
برای بیلد کردن ایمیج فوق میتوانیم از دستور زیر استفاده کنیم:
docker buildx build . --file=Dockerfile --tag=dotnet-webassembly --platform wasi/wasm32
در اینجا با کمک فلگ platform به Docker گفته‌ایم که برای ساخت ایمیج موردنظر از معماری Wasm استفاده کند؛ به این معنا که برای کامپیوترهای مختلف نیاز نخواهد بود که ایمیج‌های جداگانه‌ایی تهیه کنیم بلکه wasm runtime اینکار را برای ما به صورت خودکار انجام خواهد داد. در نهایت بعد از ایجاد ایمیج میتوانیم از دستور docker run برای اجرای Wasm applicationمان استفاده کنیم:
docker run --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 dotnet-webassembly
نکته: برای اجرای Wasm application باید مطمئن شوید که فیچر containerd image store فعال باشد:


دقت داشته باشید که این فیچر هنوز در مرحله Beta قرار دارد؛ و ممکن است در حین تهیه ایمیج با edge caseهای روبرو شوید به عنوان مثال من سعی کردم یک وب‌سرور ASP.NET Core (توسط Wasi.Sdk این امکان وجود دارد) را containerised کنم که در نهایت با خطای زیر مواجه شدم:

[error] instantiation failed: incompatible import type, Code: 0x61
     Mismatched function type. Expected: FuncType {params{i32 , i32 , i32} returns{i32}} , Got: FuncType {params{i32 , i32} returns{i32}}
     When linking module: "wasi_snapshot_preview1" , function name: "sock_accept"
     At AST node: import description
     At AST node: import section
     At AST node: module


‫۱ سال و ۹ ماه قبل، پنجشنبه ۱۹ آبان ۱۴۰۱، ساعت ۱۶:۳۶
بلاک clean نیز از نسخه 7.3 به PowerShell اضافه شده است. از این بلاک میتوانیم برای آزادسازی منابع به‌کار رفته استفاده کنیم. این بلاک خیلی شبیه به finally عمل میکند. مزیت آن نیز reliable بودن آن است. در حالت استفاده از finally، متوقف شدن Pipeline خیلی reliable نیست و بهتر است از بلاک clean بجای آن استفاده شود:
using namespace System.Net.NetworkInformation

## Pinging a remote system
function Get-PingReply {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('IPAddress', 'Destination')]
        [string] $Target
    )
    begin {
        $Ping = [Ping]::new()

        # Timeout is in ms
        $Timeout = 2500
        [byte[]] $Buffer = 1..32

        # 128 TTL and avoid fragmentation
        $PingOptions = [PingOptions]::new(128, $true)
    }
    process {
        $Ping.Send($Target, $Timeout, $Buffer, $PingOptions)
    }
    clean {
        if ($Ping) { $Ping.Dispose() }
    }
}

'1.2.3.4', 'www.google.com', '8.8.8.8' | Get-PingReply
‫۲ سال و ۳ ماه قبل، سه‌شنبه ۳ خرداد ۱۴۰۱، ساعت ۱۸:۵۷
در همین خصوص (+ ):
 

ذخیره‌سازی JSON در دیتابیس یک موضوعی است که خیلی‌ها فکر میکنند مطلوب نیست؛ اما به نظرم بیشتر RDBMSها امکانات کار با JSON رو به خوبی فراهم میکنند و پرفورمنس مطلوبی هم دارند. چندسالی هم است که موضوع Polyglot Persistence نیز ترند شده است اما در بیشتر جاهایی که امکان اضافه کردن یک Database Technology دیگر وجود ندارد میتوان از دیتاتایپ JSON استفاده کرد. 
‫۲ سال و ۳ ماه قبل، سه‌شنبه ۳ خرداد ۱۴۰۱، ساعت ۱۸:۲۸
اگر میخواهید از طریق خط فرمان (غیر ویندوزی) پروژه سری جاری را ایجاد و دستورات آن را اجرا کنید:
mkdir MinimalAPI && cd "$_"

dotnet new sln --name MinimalAPI
mkdir src && cd "$_"

dotnet new classlib --name MinimalBlog.Dal
dotnet new classlib --name MinimalBlog.Domain
dotnet new console --name MinimalBlog.Api

cd ..
dotnet sln add src/MinimalBlog.Dal
dotnet sln add src/MinimalBlog.Domain
dotnet sln add src/MinimalBlog.Api
dotnet sln list

Project(s)
----------
src/MinimalBlog.Api/MinimalBlog.Api.csproj
src/MinimalBlog.Dal/MinimalBlog.Dal.csproj
src/MinimalBlog.Domain/MinimalBlog.Domain.csproj


cd src/MinimalBlog.Dal
dotnet add reference ../MinimalBlog.Domain/
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools

cd ..
cd MinimalBlog.Api
dotnet add reference ../MinimalBlog.Dal/
dotnet add reference ../MinimalBlog.Domain/
dotnet build

dotnet tool update --global dotnet-ef
dotnet build

cd MinimalBlog.Dal
dotnet ef migrations add $(date +%s) --startup-project ../MinimalBlog.Api --context MinimalBlogDbContext
dotnet ef --startup-project ../MinimalBlog.Api/ database update --context MinimalBlogDbContext

‫۲ سال و ۳ ماه قبل، پنجشنبه ۱۵ اردیبهشت ۱۴۰۱، ساعت ۰۸:۲۴
امکان تهیه Custom Elements در NET 6 Blazor.

در آخرین نسخه Blazor این امکان فراهم شده است که بتوانیم از کامپوننت‌های Blazor درون پروژه‌های React/Vue, Angular, ... استفاده کنیم (+). البته این فیچر هنوز به صورت آزمایشی می‌باشد و ممکن است API آن تغییر کند. 
در ادامه یک مثال از این قابلیت را مشاهده خواهید کرد. 
ایجاد پروژه Blazor
یک دایرکتوری ایجاد کرده و درون آن یک پروژه blazorwasm با نام blazor_wasm ایجاد کنید:
dotnet new blazorwasm blazor_wasm
برای استفاده از این فیچر میبایست پکیج  Microsoft.AspNetCore.Components.CustomElements را نصب کنیم:
dotnet add package Microsoft.AspNetCore.Components.CustomElements --version 0.1.0-alpha.21466.1
در ادامه یک کامپوننت Todo ایجاد خواهیم کرد:
@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" @bind="todo.IsDone" />
            <input @bind="todo.Title" />
        </li>
    }
</ul>

<input placeholder="Something todo" @bind="newTodo" />
<button @onclick="AddTodo">Add todo</button>

@code {

    public class TodoItem
    {
        public string? Title { get; set; }
        public bool IsDone { get; set; }
    }

    private List<TodoItem> todos = new();
    private string? newTodo;

    private async void AddTodo(MouseEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
}
برای تبدیل کامپوننت فوق به یک Custom Element درون فایل Program.cs خط زیر را اضافه میکنیم:
builder.RootComponents.RegisterAsCustomElement<Todo>("todo-element");

استفاده از کامپوننت فوق درون یک پروژه React
npx create-react-app blazor_react && cd blazor_react
برای استفاده از Custom Element موردنظر دو خط زیر را به فایل public/index.html اضافه میکنیم:
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
همچنین لازم است یک پراکسی نیز درون پروژه ایجاد کنیم (درون فایل package.json)؛ با اینکار اسکریپت‌های موردنیاز فوق از سمت سرور دریافت خواهند شد:
"proxy": "BLAZOR_APP_ADDRESS", // for example: http://localhost:5269 
در نهایت درون فایل App.js می‌توانیم از کامپوننت Todo استفاده کنیم:
function App() {
  return (
    <div className="App">
      <todo-element />
    </div>
  );
}

export default App;
 

‫۲ سال و ۴ ماه قبل، چهارشنبه ۳۱ فروردین ۱۴۰۱، ساعت ۱۹:۳۶
پوشه node_modules رو بهتر است به فایل dockerignore. اضافه کنید تا وابستگی‌ها داخل کانتینر نصب شوند؛ یکسری پکیج‌ها مشکل cross platform بودن دارند که بهتر است در سیستم مقصد npm i صورت بگیرد تا وابستگی متناسب با سیستم‌عامل نصب شود.
یک کاربرد دیگر در رابطه با مثال آخر میتواند جنریت کردن یک فایل ویدئویی باشد؛ این مورد رو من همیشه بهش نیاز داشتم مثلاً موقع تست یک اندپوینت آپلود فایل ویدئویی:

docker run --rm -v $(pwd):$(pwd) -w $(pwd)\
        jrottenberg/ffmpeg \
        -f lavfi -i testsrc=size=1280x720 -t 60 -pix_fmt yuv420p testsrc.mp4
در اینجا توسط فیلتر testsrc یک فایل ویدئویی یک دقیقه‌ایی (عدد ۶۰ مدت زمان ویدئو را تعیین میکند) با رزلوشن 1280x720 تولید خواهد شد: