نظرات مطالب
استفاده از Kendo UI templates
سورس HTML نهایی رندر شده‌ی صفحه را بررسی کنید (کلیک راست روی صفحه، انتخاب view source). ترکیب @ و متد ToHtmlString یک چنین خروجی encode شده‌ای را تولید می‌کند:
<a href="/">Test</a>
اشتراک‌ها
روش های استفاده از ng-class در AngularJs

چند روش استفاده از ng-class در anuglarjs بررسی میکند

String syntax

<div ng-class="textType">Look! I'm Words!</div>
Array syntax
<div ng-class="[styleOne, styleTwo]">Look! I'm Words!</div>
Evaluted Expression
<div ng-class="{ 'text-success': awesome, 'text-large': giant }">
Ternary Operator
ng-class="$even ? 'even-row' : 'odd-row'">

و .......
روش های استفاده از ng-class در AngularJs
مطالب
بررسی Source Generators در #C - قسمت پنجم - نوشتن آزمون‌های واحد
تا اینجا روش آزمایش تولید کننده‌های کد، صرفا بر اساس کامپایل برنامه و مشاهده‌ی خروجی نهایی آن بود و یا حتی با ترفندهایی امکان دیباگ آن‌ها نیز وجود دارد که البته هنوز در تمام IDEها پشتیبانی نمی‌شود. در این قسمت می‌خواهیم این وضعیت را بهبود بخشیده و برای تولید کننده‌های کد، آزمون واحد بنویسیم که یکی از مزایای آن، فراهم بودن امکان دیباگ یک چنین پروژه‌هایی در تمام IDEهای موجود است و برای انجام اینکار، نیاز به هیچ ترفند خاصی وجود ندارد و پروسه‌ی کاری آن یکدست و هماهنگ با سایر آزمون‌های واحد است.


آماده سازی مقدمات پروژه‌ی آزمون واحد

در ادامه‌ی مثال این سری، پروژه‌ی جدید NotifyPropertyChangedGenerator.Tests را از نوع class library با تنظیمات فایل csproj. زیر ایجاد می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
            <PrivateAssets>all</PrivateAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.2.0" />
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />

        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
        <PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
        <PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
    </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj" />
  </ItemGroup>
</Project>
در اینجا وابستگی‌های مورد نیاز برای دسترسی به امکانات Roslyn و همچنین برای نمونه MSTest را مشاهده می‌کنید. به علاوه مسیر پروژه‌ی Source Generator مورد استفاده به نحو متداولی تعریف شده‌است.


ایجاد یک کلاس کمکی برای اجرای Source Generators در پروژه‌های آزمون واحد

در اینجا می‌خواهیم همان کاری را که کامپایلر سی‌شارپ در پشت صحنه انجام می‌دهد، شبیه سازی کنیم تا بتوانیم یک تولید کننده‌ی کد را به مراحل کامپایل کد، معرفی و سپس آن‌را اجرا کنیم:
internal static class SourceGeneratorTestsExtensions
{
    public static (GeneratorDriver Driver, Compilation OutputCompilation, ImmutableArray<Diagnostic> Diagnostics)
        RunGenerators(this string source, params ISourceGenerator[] generators)
    {
        var references =
            AppDomain.CurrentDomain.GetAssemblies()
                .Where(assembly => !assembly.IsDynamic)
                .Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
                .Cast<MetadataReference>();

        var inputCompilation = CSharpCompilation.Create("compilation",
            new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Latest)) },
            references,
            new CSharpCompilationOptions(OutputKind.ConsoleApplication));

        GeneratorDriver driver = CSharpGeneratorDriver.Create(generators);
        driver = driver.RunGeneratorsAndUpdateCompilation(
            inputCompilation,
            out var outputCompilation,
            out var diagnostics);

        return (driver, outputCompilation, diagnostics);
    }
}
این متد، یک قطعه کد ابتدایی را دریافت کرده و سپس آن‌را به همراه Source Generatorهای مدنظر، به کامپایلر سی‌شارپ معرفی می‌کند، تا کامپایلر تمام این موارد را در کنار هم پردازش کرده و اسمبلی درون حافظه‌ای را به نام compilation تولید کند. خروجی‌های این متد، اطلاعات غنی هستند از نحوه‌ی کامپایل داده‌های ارسالی به کامپایلر که در ادامه می‌توان از آن‌ها جهت نوشتن آزمون‌های واحد متکی به خودی استفاده کرد.


نوشتن اولین آزمون واحد مخصوص یک تولید کننده‌ی کد

پس از تهیه‌ی متدی که می‌تواند یک قطعه کد و تعدادی Source Generator را به کامپایلر سی‌شارپ، جهت پردازش معرفی کند، یک نمونه نحوه‌ی استفاده‌ی از آن جهت نوشتن آزمون‌های واحد کاملا مستقل و متکی به خود، به صورت زیر است:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PropertyChangedGenerator = NotifyPropertyChangedGenerator.NotifyPropertyChangedGenerator;

namespace NotifyPropertyChangedGenerator.Tests;

[TestClass]
public class GeneratorTest
{
    [TestMethod]
    public void SimpleGeneratorTest()
    {
        var userSource = @"
using System;
using System.ComponentModel;
namespace NotifyPropertyChangedDemo
{
  public class Test : INotifyPropertyChanged
  {
    private int regularField;
    private int IndexBackingField;
  }
}
";
        var (driver, outputCompilation, diagnostics) =
            userSource.RunGenerators(new PropertyChangedGenerator());

        var newFile = outputCompilation.SyntaxTrees
            .Single(x => Path.GetFileName(x.FilePath).EndsWith(".Test.cs"));

        Assert.IsNotNull(newFile);
        Assert.IsTrue(newFile.FilePath.EndsWith("Test.Notify.Test.cs"));

        var generatedSource = newFile.GetText().ToString();
        Assert.IsTrue(generatedSource.Contains("namespace NotifyPropertyChangedDemo"));

        // We can now assert things about the resulting compilation:
        Assert.IsTrue(diagnostics.IsEmpty); // there were no diagnostics created by the generators
        // we have two syntax trees, the original 'user' provided one, and the one added by the generator
        Assert.IsTrue(outputCompilation.SyntaxTrees.Count() == 2);
        // verify the compilation with the added source has no diagnostics
        Assert.IsTrue(outputCompilation.GetDiagnostics().IsEmpty);
    }
}
 - در این مثال ابتدا یک قطعه کد سی‌شارپ را که قرار است کدهای آن توسط تولید کننده‌ی کد توسعه داده شده تکمیل شوند، تعریف کرده‌ایم.
 - سپس این قطعه کد و نمونه‌ای از تولید کننده‌ی کد را به کامپایلر ارسال و اجرا کرده‌ایم.
 - اکنون بر اساس خروجی کامپایلر برای مثال می‌توان به فایل تولید شده و SyntaxTrees آن دسترسی پیدا کرد و یا با کمک متد GetText، به کل محتوای این فایل تولید شده دسترسی یافت و برای مثال آن‌را با مقداری که انتظار داریم مقایسه کرد تا به این ترتیب بتوان از صحت عملکرد تولید کننده‌ی کد، اطمینان حاصل نمود.
 - همانطور که عنوان شد، اکنون قرار دادن break-point در قسمت‌های مختلف آزمون واحد تهیه شده بسیار ساده‌است و به این ترتیب می‌توان یک چنین پروژه‌هایی را در تمام IDEها دیباگ کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: SourceGeneratorTests-part5.zip
اشتراک‌ها
انتقال WebAssembly به سرور یا WASI

Bringing WebAssembly to the .NET Mainstream - Steve Sanderson, Microsoft

Many developers still consider WebAssembly to be a leading-edge, niche technology tied to low-level systems programming languages. However, C# and .NET (open-source, cross-platform technologies used by nearly one-third of all professional developers [1]) have run on WebAssembly since 2017. Blazor WebAssembly brought .NET into the browser on open standards, and is now one of the fastest-growing parts of .NET across enterprises, startups, and hobbyists. Next, with WASI we could let you run .NET in even more places, introducing cloud-native tools and techniques to a wider segment of the global developer community. This is a technical talk showing how we bring .NET to WebAssembly. Steve will demonstrate how it runs both interpreted and AOT-compiled, how an IDE debugger can attach, performance tradeoffs, and how a move from Emscripten to WASI SDK lets it run in Wasmtime/Wasmer or higher-level runtimes like wasmCloud. Secondly, you'll hear lessons learned from Blazor as an open-source project - challenges and misconceptions faced bringing WebAssembly beyond early adopters. [1] StackOverflow survey 2021 

انتقال WebAssembly به سرور یا WASI
مطالب
شروع به کار با EF Core 1.0 - قسمت 2 - به روز رسانی ساختار بانک اطلاعاتی
پس از برپایی تنظیمات اولیه‌ی کار با EF Core در ASP.NET Core، اکنون نوبت به تبدیل کلاس Person، به جدول معادل آن در بانک اطلاعاتی برنامه است. در EF Core نیز همانند EF Code First 6.x، برای انجام یک چنین اعمالی از مفهومی به نام Migrations استفاده می‌شود که در ادامه به آن خواهیم پرداخت.


پیشنیازهای کار با EF Core Migrations

در قسمت قبل در حین بررسی «برپایی تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0»، چهار مدخل جدید را به فایل project.json برنامه اضافه کردیم. مدخل جدید Microsoft.EntityFrameworkCore.Tools که به قسمت tools آن اضافه شد، پیشنیاز اصلی کار با EF Core Migrations است.


بررسی ابزارهای خط فرمان EF Core و تشکیل ساختار بانک اطلاعاتی بر اساس کلاس‌های برنامه

پس از تکمیل پیشنیازهای کار با EF Core، از طریق خط فرمان به پوشه‌ی جاری پروژه وارد شده و دستور dotnet ef را صادر کنید.
یک نکته: در ویندوز اگر در پوشه‌ای، کلید shift را نگه دارید و در آن پوشه کلیک راست کنید، در منوی باز شده، گزینه‌ی جدیدی را به نام Open command window here مشاهده خواهید کرد که می‌تواند به سرعت خط فرمان را از پوشه‌ی جاری شروع کند.

خروجی صدور فرمان dotnet ef را در ذیل مشاهده می‌کنید:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef
                     _/\__
               ---==/    \\
         ___  ___   |.    \|\
        | __|| __|  |  )   \\\
        | _| | _|   \_/ |  //|\\
        |___||_|       /   \\\/\\
Entity Framework .NET Core CLI Commands 1.0.0-preview2-21431
Usage: dotnet ef [options] [command]
Options:
  -h|--help                      Show help information
  -v|--verbose                   Enable verbose output
  --version                      Show version information
  --assembly <ASSEMBLY>          The assembly file to load.
  --startup-assembly <ASSEMBLY>  The assembly file containing the startup class.
  --data-dir <DIR>               The folder used as the data directory (defaults to current working directory).
  --project-dir <DIR>            The folder used as the project directory (defaults to current working directory).
  --content-root-path <DIR>      The folder used as the content root path for the application (defaults to application base directory).
  --root-namespace <NAMESPACE>   The root namespace of the target project (defaults to the project assembly name).
Commands:
  database    Commands to manage your database
  dbcontext   Commands to manage your DbContext types
  migrations  Commands to manage your migrations
Use "dotnet ef [command] --help" for more information about a command.
در قسمت Commands آن در انتهای لیست، از فرمان migrations آن استفاده خواهیم کرد. برای این منظور در همین پوشه‌ی جاری، دستور ذیل را صادر کنید:
 D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations add InitialDatabase
دستورات migrations با dotnet ef migrations شروع شده و سپس یک سری پارامتر را دریافت می‌کنند برای مثال در اینجا سوئیچ add، به همراه یک نام دلخواه ذکر شده‌است (نام این مرحله را InitialDatabase گذاشته‌ایم). پس از فراخوانی این دستور، اگر به Solution explorer مراجعه کنید، پوشه‌ی جدید Migrations، قابل مشاهده است:


نام دلخواه InitialDatabase را در انتهای نام فایل 13950526050417_InitialDatabase مشاهده می‌کنید.
اگر قصد حذف این مرحله را داشته باشیم، می‌توان دستور dotnet ef migrations remove را مجددا صادر کرد.

فایل 13950526050417_InitialDatabase به همراه کلاسی است که در آن دو متد Up و Down قابل مشاهده هستند. متد Up نحوه‌ی ایجاد جدول جدیدی را از کلاس Person بیان می‌کند و متد Down نحوه‌ی Drop این جدول را پیاده سازی کرده‌است.
فایل ApplicationDbContextModelSnapshot.cs دارای کلاسی است که خلاصه‌ای از تعاریف موجودیت‌های ذکر شده‌ی در DB Context برنامه را به همراه دارد و تفسیر آن‌ها را از دیدگاه  EF در اینجا می‌توان مشاهده کرد.

پس از مرحله‌ی افزودن migrations، نوبت به اعمال آن به بانک اطلاعاتی است. تا اینجا EF تنها متدهای Up و Down مربوط به ساخت و حذف ساختار جداول را ایجاد کرده‌است. اما هنوز آن‌ها را به بانک اطلاعاتی برنامه اعمال نکرده‌است. برای اینکار در پوشه‌ی جاری دستور ذیل را صادر کنید:
 D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef database update
Applying migration '13950526050417_InitialDatabase'.
Done.
همانطور که ملاحظه می‌کنید، دستور dotnet ef database update سبب اعمال اطلاعات فایل 13950526050417_InitialDatabase به بانک اطلاعاتی شده‌است.
اکنون اگر به لیست بانک‌های اطلاعاتی مراجعه کنیم، بانک اطلاعاتی جدید TestDbCore2016 را به همراه جدول متناظر کلاس Person می‌توان مشاهده کرد:


در اینجا جدول دیگری به نام __EFMigrationsHistory نیز قابل مشاهده‌است که کار آن ذخیره سازی وضعیت فعلی Migrations در بانک اطلاعاتی، جهت مقایسه‌های آتی است. این جدول صرفا توسط ابزارهای EF استفاده می‌شود و نباید به صورت مستقیم تغییری در آن ایجاد کنید.


مقدار دهی اولیه‌ی جداول بانک‌های اطلاعاتی در EF Core

در همین حالت اگر کنترلر TestDB مطرح شده‌ی در انتهای بحث قسمت قبل را اجرا کنیم، به این استثناء خواهیم رسید:


این تصویر بدین معنا است که کار Migrations موفقیت آمیز بوده‌است و اینبار امکان اتصال و کار با بانک اطلاعاتی وجود دارد، اما این جدول حاوی اطلاعات اولیه‌ای برای نمایش نیست.
در نگارش قبلی EF Code First، امکانات Migrations به همراه یک متد Seed نیز بود که توسط آن کار مقدار دهی اولیه‌ی جداول را می‌توان انجام داد (زمانیکه جدولی ایجاد می‌شود، در همان هنگام، چند رکورد خاص نیز به آن اضافه شوند. برای مثال به جدول کاربران، رکورد اولین کاربر یا همان Admin اضافه شود). این متد در EF Core 1.0 وجود ندارد.
برای این منظور کلاس جدیدی را به نام ApplicationDbContextSeedData به همان پوشه‌ی جدید Migrations اضافه کنید؛ با این محتوا:
using System.Collections.Generic;
using System.Linq;
using Core1RtmEmptyTest.Entities;
using Microsoft.Extensions.DependencyInjection;

namespace Core1RtmEmptyTest.Migrations
{
    public static class ApplicationDbContextSeedData
    {
        public static void SeedData(this IServiceScopeFactory scopeFactory)
        {
            using (var serviceScope = scopeFactory.CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
                if (!context.Persons.Any())
                {
                    var persons = new List<Person>
                    {
                        new Person
                        {
                            FirstName = "Admin",
                            LastName = "User"
                        }
                    };
                    context.AddRange(persons);
                    context.SaveChanges();
                }
            }
        }
    }
}
و سپس نحوه‌ی فراخوانی آن در متد Configure کلاس آغازین برنامه به صورت زیر است:
public void Configure(IServiceScopeFactory scopeFactory)
{
    scopeFactory.SeedData();
به همراه این تغییر در نحوه‌ی معرفی Db Context برنامه:
public void ConfigureServices(IServiceCollection services)
{
   services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
توضیحات:
- برای پیاده سازی الگوی واحد کار، اولین قدم، مشخص سازی طول عمر Db Context برنامه است. برای اینکه تنها یک Context در طول یک درخواست وهله سازی شود، نیاز است به نحو صریحی طول عمر آن‌را به حالت Scoped تنظیم کرد. متد AddDbContext دارای پارامتری است که این طول عمر را دریافت می‌کند. بنابراین در اینجا ServiceLifetime.Scoped ذکر شده‌است. همچنین در این مثال از نمونه‌ای که IConfigurationRoot به سازنده‌ی کلاس ApplicationDbContext تزریق شده (نکته‌ی انتهای بحث قسمت قبل)، استفاده شده‌است. به همین جهت تنظیمات options آن‌را ملاحظه نمی‌کنید.
- مرحله‌ی بعد نحوه‌ی دسترسی به این سرویس ثبت شده در یک کلاس static دارای متدی الحاقی است. در اینجا دیگر دسترسی مستقیمی به تزریق وابستگی‌ها نداریم و باید کار را با  IServiceScopeFactory شروع کنیم. در اینجا می‌توانیم به صورت دستی یک Scope را ایجاد کرده، سپس توسط ServiceProvider آن، به سرویس ApplicationDbContext دسترسی پیدا کنیم و در ادامه از آن به نحو متداولی استفاده نمائیم. IServiceScopeFactory جزو سرویس‌های توکار ASP.NET Core است و در صورت ذکر آن به عنوان پارامتر جدیدی در متد Configure، به صورت خودکار وهله سازی شده و در اختیار ما قرار می‌گیرد.
- نکته‌ی مهمی که در اینجا بکار رفته‌است، ایجاد Scope و dispose خودکار آن توسط عبارت using است. باید دقت داشت که ایجاد Scope و تخریب آن به صورت خودکار در ابتدا و انتهای درخواست‌ها توسط ASP.NET Core انجام می‌شود. اما چون شروع کار ما از متد Configure است، در اینجا خارج از Scope قرار داریم و باید مدیریت ایجاد و تخریب آن‌را به صورت دستی انجام دهیم که نمونه‌ای از آن‌را در متد SeedData کلاس ApplicationDbContextSeedData ملاحظه می‌کنید. در اینجا Scope ایی ایجاد شده‌است. سپس داده‌های اولیه‌ی مدنظر به بانک اطلاعاتی اضافه گردیده و در آخر این Scope تخریب شده‌است.
- اگر کار ایجاد و تخریب scope، به نحوی که مشخص شده‌است انجام نگیرد، طول عمر درخواستی خارج از Scope، همواره Singleton خواهد بود. چون خارج از طول عمر درخواست جاری قرار داریم و هنوز کار به سرویس دهی درخواست‌ها نرسیده‌است. بنابراین مدیریت Scopeها هنوز شروع نشده‌است و باید به صورت دستی انجام شود.

در این حالت اگر برنامه را اجرا کنیم، این خروجی قابل مشاهده است:


که به معنای کار کردن متد SeedData و ثبت اطلاعات اولیه‌ای در بانک اطلاعاتی است.


اعمال تغییرات به مدل‌های برنامه و به روز رسانی ساختار بانک اطلاعاتی

فرض کنید به کلاس Person قسمت قبل، خاصیت Age را هم اضافه کرده‌ایم:
namespace Core1RtmEmptyTest.Entities
{
    public class Person
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public int Age { get; set; }
    }
}
در این حالت اگر برنامه را اجرا کنیم، به استثنای ذیل خواهیم رسید:
 An unhandled exception occurred while processing the request.
SqlException: Invalid column name 'Age'.
برای رفع این مشکل نیاز است مجددا مراحل Migrations را اجرا کرد:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations add v2
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef database update
در اینجا همان دستورات قبل را مجددا اجرا می‌کنیم. با این تفاوت که اینبار نام دلخواه این مرحله را مثلا v2، به معنای نگارش دوم وارد کرده‌ایم.
با اجرا این دستورات، فایل جدید 13950526073248_v2 به پوشه‌ی Migrations اضافه می‌شود. این فایل حاوی نحوه‌ی به روز رسانی بانک اطلاعاتی، بر اساس خاصیت جدید Age است. سپس با اجرای دستور dotnet ef database update، کار به روز رسانی بانک اطلاعاتی بر اساس مرحله‌ی v2 انجام می‌شود.


بنابراین هر بار که تغییری را در مدل‌های خود ایجاد می‌کنید، یکبار باید کلاس مهاجرت آن‌را ایجاد کنید و سپس آن‌را به بانک اطلاعاتی اعمال نمائید.


تهیه اسکریپت تغییرات بجای اعمال تغییرات توسط ابزارهای EF

شاید علاقمند باشید که پیش از اعمال تغییرات به بانک اطلاعاتی، یک اسکریپت SQL از آن تهیه کنید (جهت مطالعه و یا اعمال دستی آن توسط خودتان). برای اینکار می‌توانید دستور ذیل را در پوشه‌ی جاری پروژه اجرا کنید:
 D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations script -o v2.sql
در این حالت اسکریپت SQL تغییرات، در فایلی به نام v2.sql، در ریشه‌ی جاری پروژه تولید می‌شود.


تغییرات ساختار جدول __EFMigrationsHistory در EF Core 1.0


در EF 6.x، ساختار اطلاعات جدول نگهداری تاریخچه‌ی تغییرات، بسیار پیچیده بود و شامل رشته‌ای gzip شده‌ی حاوی یک snapshot از کل ساختار دیتابیس در هر مرحله‌ی migration بود. در این نگارش، این snapshot حذف شده‌است و بجای آن فایل ApplicationDbContextModelSnapshot.cs را مشاهده می‌کنید (تنها یک snapshot به ازای کل context برنامه). همچنین در اینجا کاملا مشخص است که چه مراحلی به بانک اطلاعاتی اعمال شده‌اند و دیگر خبری از رشته‌ی gzip شده‌ی قبلی نیست (تصویر فوق).

در شکل زیر ساختار قبلی این جدول را در EF 6.x مشاهده می‌کنید. در EF 6.x حتی فضای نام کلاس‌های موجودیت‌های برنامه هم مهم هستند و در صورت تغییر، مشکل ایجاد می‌شود:



مهاجرت خودکار از EF Core حذف شده‌است

در EF 6.x در کنار کلاس Db Context یک کلاس Configuration هم وجود داشت که برای مثال امکان چنین تعریفی در آن میسر هست:
public Configuration()
{
   AutomaticMigrationsEnabled = true;
}
کار آن مهاجرت خودکار اطلاعات context به بانک اطلاعاتی بود؛ بدون نیازی به استفاده از دستورات خط فرمان مرتبط. تمام این موارد از EF Core حذف شده‌اند و علت آن‌را می‌توانید در توضیحات یکی از اعضای تیم EF Core در اینجا مطالعه کنید و خلاصه‌ی آن به این شرح است:
با حذف مهاجرت خودکار:
- دیگر نیازی نیست تا model snapshots در بانک اطلاعاتی ذخیره شوند (همان ساده شدن ساختار جدول ذخیره سازی تاریخچه‌ی مهاجرت‌های فوق).
- در حالت افزودن یک مرحله‌ی مهاجرت، دیگر نیازی به کوئری گرفتن از بانک اطلاعاتی نخواهد بود (سرعت بیشتر).
- می‌توان چندین مرحله‌ی مهاجرت را افزود بدون اینکه الزاما مجبور به اعمال آن‌ها به بانک اطلاعاتی باشیم.
- کاهش کدهای مدیریت ساختار بانک اطلاعاتی.
- تیم‌ها برای یکی کردن تغییرات خود مشکلی نخواهند داشت چون دیگر snapshot مدل‌ها در جدول __EFMigrationsHistory ذخیره نمی‌شود.

بنابراین در EF Core می‌توان مهاجرت v1 را اضافه کرد. سپس تغییراتی را در کدها اعمال کرد. در ادامه مهاجرت v2 را تولید کرد و در آخر کار اعمال یکجای این‌ها را به بانک اطلاعاتی انجام داد.

هرچند در اینجا اگر می‌خواهید مرحله‌ی اجرای دستور dotnet ef database update را حذف کنید، می‌توانید از کدهای ذیل نیز استفاده نمائید:
using Core1RtmEmptyTest.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Core1RtmEmptyTest.Migrations
{
    public static class DbInitialization
    {
        public static void Initialize(this IServiceScopeFactory scopeFactory)
        {
            using (var serviceScope = scopeFactory.CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
                // Applies any pending migrations for the context to the database.
                // Will create the database if it does not already exist.
                context.Database.Migrate();
            }
        }
    }
}
روش فراخوانی آن نیز همانند روش فراخوانی متد SeedData است که پیشتر بحث شد.
کار متد Migrate، ایجاد بانک اطلاعاتی در صورت عدم وجود و سپس اعمال تمام مراحل migration ایی است که در جدول __EFMigrationsHistory ثبت نشده‌اند (دقیقا همان کار دستور dotnet ef database update را انجام می‌دهد).


تفاوت متد Database.EnsureCreated با متد Database.Migrate

اگر به متدهای context.Database دقت کنید، یکی از آن‌ها EnsureCreated نام دارد. این متد نیز سبب تولید بانک اطلاعاتی بر اساس ساختار Context برنامه می‌شود. اما هدف آن صرفا استفاده‌ی از آن در آزمون‌های واحد سریع است. از این جهت که جدول __EFMigrationsHistory را تولید نمی‌کند (برخلاف متد Migrate). بنابراین بجز آزمون‌های واحد، در جای دیگری از آن استفاده نکنید چون به دلیل عدم تولید جدول __EFMigrationsHistory توسط آن، قابلیت استفاده‌ی از بانک اطلاعاتی تولید شده‌ی توسط آن با امکانات migrations وجود ندارد. در پایان آزمون واحد نیز می‌توان از متد EnsureDeleted برای حذف این بانک اطلاعاتی موقتی استفاده کرد.



در قسمت بعد، مطالب تکمیلی مهاجرت‌ها را بررسی خواهیم کرد. برای مثال چگونه می‌توان کلاس‌های موجودیت‌ها را به اسمبلی‌های دیگری منتقل کرد.
اشتراک‌ها
Visual Studio 2017 version 15.3.2 منتشر شد

These are the customer-reported issues addressed in this version:

Visual Studio crashes when you open a solution with a test project.
Visual Studio Freezes in Debug with Chrome.
Failure to install HelpViewer.
UI delay while typing R code.
C# 7.0 Regression in Tuples.
Xamarin - Dynamic Type Platform not supported exception.
Xamarin – Dynamic object is not supported.
Xamarin - Xamarin.iOS: ArgumentNullException for instruction parameter in Mono.Linker's MarkException() method.

Visual Studio 2017 version 15.3.2 منتشر شد
مطالب
ارسال ویدیو بصورت Async توسط Web Api
فریم ورک ASP.NET Web API صرفا برای ساخت سرویس‌های ساده‌ای که می‌شناسیم، نیست و در واقع مدل جدیدی برای برنامه نویسی HTTP است. کارهای بسیار زیادی را می‌توان توسط این فریم ورک انجام داد که در این مقاله به یکی از آنها می‌پردازم. فرض کنید می‌خواهیم یک فایل ویدیو را بصورت Asynchronous به کلاینت ارسال کنیم.

ابتدا پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب آن را MVC + Web API انتخاب کنید.


ابتدا به فایل WebApiConfig.cs در پوشه App_Start مراجعه کنید و مسیر پیش فرض را حذف کنید. برای مسیریابی سرویس‌ها از قابلیت جدید Attribute Routing استفاده خواهیم کرد. فایل مذکور باید مانند لیست زیر باشد.
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();
    }
}
حال در مسیر ریشه پروژه، پوشه جدیدی با نام Videos ایجاد کنید و یک فایل ویدیو نمونه بنام sample.mp4 در آن کپی کنید. دقت کنید که فرمت فایل ویدیو در مثال جاری mp4 در نظر گرفته شده اما به سادگی می‌توانید آن را تغییر دهید.
سپس در پوشه Models کلاس جدیدی بنام VideoStream ایجاد کنید. این کلاس مسئول نوشتن داده فایل‌های ویدیویی در OutputStream خواهد بود. کد کامل این کلاس را در لیست زیر مشاهده می‌کنید.
public class VideoStream
{
    private readonly string _filename;
    private long _contentLength;

    public long FileLength
    {
        get { return _contentLength; }
    }

    public VideoStream(string videoPath)
    {
        _filename = videoPath;
        using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            _contentLength = video.Length;
        }
    }

    public async void WriteToStream(Stream outputStream,
        HttpContent content, TransportContext context)
    {
        try
        {
            var buffer = new byte[65536];

            using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var length = (int)video.Length;
                var bytesRead = 1;

                while (length > 0 && bytesRead > 0)
                {
                    bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));
                    await outputStream.WriteAsync(buffer, 0, bytesRead);
                    length -= bytesRead;
                }
            }
        }
        catch (HttpException)
        {
            return;
        }
        finally
        {
            outputStream.Close();
        }
    }
}

شرح کلاس VideoStream
این کلاس ابتدا دو فیلد خصوصی تعریف می‌کند. یکی filename_ که فقط-خواندنی است و نام فایل ویدیو درخواستی را نگهداری می‌کند. و دیگری contentLength_ که سایز فایل ویدیو درخواستی را نگهداری می‌کند.

یک خاصیت عمومی بنام FileLength نیز تعریف شده که مقدار خاصیت contentLength_ را بر می‌گرداند.

متد سازنده این کلاس پارامتری از نوع رشته بنام videoPath را می‌پذیرد که مسیر کامل فایل ویدیوی مورد نظر است. در این متد، متغیر‌های filename_ و contentLength_ مقدار دهی می‌شوند. نکته‌ی قابل توجه در این متد استفاده از پارامتر FileShare.Read است که باعث می‌شود فایل مورد نظر هنگام باز شدن قفل نشود و برای پروسه‌های دیگر قابل دسترسی باشد.

در آخر متد WriteToStream را داریم که مسئول نوشتن داده فایل‌ها به OutputStream است. اول از همه دقت کنید که این متد از کلمه کلیدی async استفاده می‌کند بنابراین بصورت asynchronous اجرا خواهد شد. در بدنه این متد متغیری بنام buffer داریم که یک آرایه بایت با سایز 64KB را تعریف می‌کند. به بیان دیگر اطلاعات فایل‌ها را در پکیج‌های 64 کیلوبایتی برای کلاینت ارسال خواهیم کرد. در ادامه فایل مورد نظر را باز می‌کنیم (مجددا با استفاده از FileShare.Read) و شروع به خواندن اطلاعات آن می‌کنیم. هر 64 کیلوبایت خوانده شده بصورت async در جریان خروجی نوشته می‌شود و تا هنگامی که به آخر فایل نرسیده ایم این روند ادامه پیدا می‌کند.
while (length > 0 && bytesRead > 0)
{
    bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));
    await outputStream.WriteAsync(buffer, 0, bytesRead);
    length -= bytesRead;
}
اگر دقت کنید تمام کد بدنه این متد در یک بلاک try/catch قرار گرفته است. در صورتی که با خطایی از نوع HttpException مواجه شویم (مثلا هنگام قطع شدن کاربر) عملیات متوقف می‌شود و در آخر نیز جریان خروجی (outputStream) بسته خواهد شد. نکته دیگری که باید بدان اشاره کرد این است که کاربر حتی پس از قطع شدن از سرور می‌تواند ویدیو را تا جایی که دریافت کرده مشاهده کند. مثلا ممکن است 10 پکیج از اطلاعات را دریافت کرده باشد و هنگام مشاهده پکیج دوم از سرور قطع شود. در این صورت امکان مشاهده ویدیو تا انتهای پکیج دهم وجود خواهد داشت.

حال که کلاس VideoStream را در اختیار داریم می‌توانیم پروژه را تکمیل کنیم. در پوشه کنترلر‌ها کلاسی بنام VideoControllerبسازید. کد کامل این کلاس را در لیست زیر مشاهده می‌کنید.
public class VideoController : ApiController
{
    [Route("api/video/{ext}/{fileName}")]
    public HttpResponseMessage Get(string ext, string fileName)
    {
        string videoPath = HostingEnvironment.MapPath(string.Format("~/Videos/{0}.{1}", fileName, ext));
        if (File.Exists(videoPath))
        {
            FileInfo fi = new FileInfo(videoPath);
            var video = new VideoStream(videoPath);

            var response = Request.CreateResponse();

            response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)video.WriteToStream,
                new MediaTypeHeaderValue("video/" + ext));

            response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + fi.Name.Replace(" ", ""));
            response.Content.Headers.Add("Content-Length", video.FileLength.ToString());

            return response;
        }
        else
        {
            return Request.CreateResponse(HttpStatusCode.NotFound);
        }
    }
}

شرح کلاس VideoController
همانطور که می‌بینید مسیر دستیابی به این کنترلر با استفاده از قابلیت Attribute Routing تعریف شده است.

[Route("api/video/{ext}/{fileName}")]
نمونه ای از یک درخواست که به این مسیر نگاشت می‌شود:
api/video/mp4/sample
بنابراین این مسیر فرمت و نام فایل مورد نظر را بدین شکل می‌پذیرد. در نمونه جاری ما فایل sample.mp4 را درخواست کرده ایم.
متد Get این کنترلر دو پارامتر با نام‌های ext و fileName را می‌پذیرد که همان فرمت و نام فایل هستند. سپس با استفاده از کلاس HostingEnvironment سعی می‌کنیم مسیر کامل فایل درخواست شده را بدست آوریم.
string videoPath = HostingEnvironment.MapPath(string.Format("~/Videos/{0}.{1}", fileName, ext));
استفاده از این کلاس با Server.MapPath تفاوتی نمی‌کند. در واقع خود Server.MapPath نهایتا همین کلاس HostingEnvironment را فراخوانی می‌کند. اما در کنترلر‌های Web Api به کلاس Server دسترسی نداریم. همانطور که مشاهده می‌کنید فایل مورد نظر در پوشه Videos جستجو می‌شود، که در ریشه سایت هم قرار دارد. در ادامه اگر فایل درخواست شده وجود داشت وهله جدیدی از کلاس VideoStream می‌سازیم و مسیر کامل فایل را به آن پاس می‌دهیم.
var video = new VideoStream(videoPath);
سپس آبجکت پاسخ را وهله سازی می‌کنیم و با استفاده از کلاس PushStreamContent اطلاعات را به کلاینت می‌فرستیم.
var response = Request.CreateResponse();

response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)video.WriteToStream, new MediaTypeHeaderValue("video/" + ext));

کلاس PushStreamContent در فضای نام System.Net.Http وجود دارد. همانطور که می‌بینید امضای Action پاس داده شده، با امضای متد WriteToStream در کلاس VideoStream مطابقت دارد.

در آخر دو Header به پاسخ ارسالی اضافه می‌کنیم تا نوع داده ارسالی و سایز آن را مشخص کنیم.
response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + fileName);
response.Content.Headers.Add("Content-Length", video.FileLength.ToString());
افزودن این دو مقدار مهم است. در صورتی که این Header‌‌ها را تعریف نکنید سایز فایل دریافتی و مدت زمان آن نامعلوم خواهد بود که تجربه کاربری خوبی بدست نمی‌دهد. نهایتا هم آبجکت پاسخ را به کلاینت ارسال می‌کنیم. در صورتی هم که فایل مورد نظر در پوشه Videos پیدا نشود پاسخ NotFound را بر می‌گردانیم.
if(File.Exists(videoPath))
{
    // removed for bravity
}
else
{
    return Request.CreateResponse(HttpStatusCode.NotFound);
}
خوب، برای تست این مکانیزم نیاز به یک کنترلر MVC و یک View داریم. در پوشه کنترلر‌ها کلاسی بنام HomeController ایجاد کنید که با لیست زیر مطابقت داشته باشد.
public class HomeController : Controller
{
    // GET: Home
    public ActionResult Index()
    {
        return View();
    }
}
نمای این متد را بسازید (با کلیک راست روی متد Index و انتخاب گزینه Add View) و کد آن را مطابق لیست زیر تکمیل کنید.
<div>
    <div>
        <video width="480" height="270" controls="controls" preload="auto">
            <source src="/api/video/mp4/sample" type="video/mp4" />
            Your browser does not support the video tag.
        </video>
    </div>
</div>
همانطور که مشاهده می‌کنید یک المنت ویدیو تعریف کرده ایم که خواص طول، عرض و غیره آن نیز مقدار دهی شده اند. زیر تگ source متنی درج شده که در صورت لزوم به کاربر نشان داده می‌شود. گرچه اکثر مرورگرهای مدرن از المنت ویدیو پشتیبانی می‌کنند. تگ سورس فایلی با مشخصات sample.mp4 را درخواست می‌کند و نوع آن را نیز video/mp4 مشخص کرده ایم.

اگر پروژه را اجرا کنید می‌بینید که ویدیو مورد نظر آماده پخش است. برای اینکه ببینید چطور داده‌های ویدیو در قالب پکیج‌های 64 کیلو بایتی دریافت می‌شوند از ابزار مرورگرتان استفاده کنید. مثلا در گوگل کروم F12 را بزنید و به قسمت Network بروید. صفحه را یکبار مجددا بارگذاری کنید تا ارتباطات شبکه مانیتور شود. اگر به المنت sample دقت کنید می‌بینید که با شروع پخش ویدیو پکیج‌های اطلاعات یکی پس از دیگری دریافت می‌شوند و اطلاعات ریز آن را می‌توانید مشاهده کنید.

پروژه نمونه به این مقاله ضمیمه شده است. قابلیت Package Restore فعال شده و برای صرفه جویی در حجم فایل، تمام پکیج‌ها و محتویات پوشه bin حذف شده اند. برای تست بیشتر می‌توانید فایل sample.mp4 را با فایلی حجیم‌تر جایگزین کنید تا نحوه دریافت اطلاعات را با روشی که در بالا بدان اشاره شد مشاهده کنید.

AsyncVideoStreaming.rar  
مطالب
مراحل ارسال یک پروژه‌ی Visual Studio به GitHub
از نگارش 2012 ویژوال استودیو، امکان کار با مخازن Git، به صورت یکپارچه و توکار و بدون نیاز به ابزارهای جانبی، توسط آن فراهم شده‌است. در ادامه قصد داریم به کمک این ویژگی توکار، نحوه‌ی ارسال یک پروژه‌ی از پیش موجود VS.NET را برای اولین بار به GitHub بررسی کنیم.


تنظیمات مقدماتی GitHub

در ابتدا نیاز است یک مخزن کد خالی را در GitHub ایجاد کنید. برای این منظور به برگه‌ی Repositories در اکانت GitHub خود مراجعه کرده و بر روی دکمه‌ی New کلیک کنید:


سپس در صفحه‌ی بعدی، نام پروژه را به همراه توضیحاتی وارد نمائید و بر روی دکمه‌ی Create repository کلیک کنید. در اینجا سایر گزینه‌ها را انتخاب نکنید. نیازی به انتخاب گزینه‌ی READ ME و یا انتخاب مجوز و غیره نیست. تمام این کارها را در سمت پروژه‌ی اصلی می‌توان انجام داد و یا VS.NET فایل‌های ignore را به صورت خودکار ایجاد می‌کند. در اینجا صرفا هدف، ایجاد یک مخزن کد خالی است.


از اطلاعات صفحه‌ی بعدی، تنها به آدرس مخصوص GitHub آن نیاز داریم. از این آدرس در VS.NET برای ارسال اطلاعات به سرور استفاده خواهیم کرد:



تنظیمات VS.NET برای ارسال پروژه به مخزن GitHub

پس از ایجاد یک مخزن کد خالی در GitHub، اکنون می‌توانیم پروژه‌ی خود را به آن ارسال کنیم. برای این منظور از منوی File، گزینه‌ی Add to source control را انتخاب کنید و در صفحه‌ی باز شده، گزینه‌ی Git را انتخاب نمائید:



سپس در کنار برگه‌ی Solution Explorer، برگه‌ی Team Explorer را انتخاب کنید. در اینجا بر روی دکمه‌ی Home در نوار ابزار آن کلیک کرده و سپس بر روی دکمه‌ی Unsynced commits کلیک نمائید.


در ادامه در صفحه‌ی باز شده، همان آدرس مخصوص مخزن کد جدید را در GitHub وارد کرده و بر روی دکمه‌ی Publish کلیک کنید:


در اینجا بلافاصله صفحه‌ی لاگینی ظاهر می‌شود که باید در آن مشخصات اکانت GitHub خود را وارد نمائید:


به این ترتیب عملیات Publish اولیه انجام شده و تصویر ذیل نمایان خواهد شد:


در اینجا بر روی دکمه‌ی Sync کلیک کنید. به این ترتیب مخزن کد GitHub به پروژه‌ی جاری متصل خواهد شد:


سپس نیاز است فایل‌های موجود را به مخزن کد GitHub ارسال کرد. بنابراین پس از مشاهده‌ی پیام موفقیت آمیز بودن عملیات همگام سازی، بر روی دکمه‌ی Home در نوار ابزار کلیک کرده و اینبار گزینه‌ی Changes را انتخاب کنید:


در اینجا پیام اولین ارسال را وارد کرده و سپس بر روی دکمه‌ی Commit کلیک کنید:


پس از مشاهده‌ی پیام موفقیت آمیز بودن commit محلی، نیاز است تا آن‌را با سرور نیز هماهنگ کرد. به همین جهت در اینجا بر روی لینک Sync کلیک کرده و در صفحه‌ی بعدی بر روی دکمه‌ی Sync کلیک کنید:



اندکی صبر کنید تا فایل‌ها به سرور ارسال شوند. اکنون اگر به GitHub مراجعه کنید، فایل‌های ارسالی قابل مشاهده هستند:



اعمال تغییرات بر روی پروژه‌ی محلی و ارسال به سرور

در ادامه می‌خواهیم دو فایل README.md و LICENSE.md را به پروژه اضافه کنیم. پس از افزودن آن‌ها، یا هر تغییر دیگری در پروژه، اینبار برای ارسال تغییرات به سرور، تنها کافی است به برگه‌ی Team explorer مراجعه کرده و ابتدا بر روی دکمه‌ی Home کلیک کرد تا منوی انتخاب گزینه‌‌های آن ظاهر شود. در اینجا تنها کافی است گزینه‌ی Changes را انتخاب و دقیقا همان مراحل عنوان شده‌ی پیشین را تکرار کرد. ابتدا ورود پیام Commit و سپس Commit. در ادامه Sync محلی و سپس Sync با سرور.
مطالب
یک سرویس (میکروسرویس) چیست؟ و چگونه آن را مستند کنیم؟ (قسمت دوم)
در قسمت اول این مقاله ، مشخصات کلیدی یک سرویس مورد بررسی قرار گرفت و  API‌ها و وابستگی‌ها یا Dependencies هر سرویس نیز مورد بررسی قرار گرفتند. همانطور که مشخص است با زیاد شدن این سرویس‌ها و وابستگی هایی که به یکدیگر پیدا میکنند، سردرگمی‌ها نیز برای اعضای تیم‌های مختلف، زیاد میگردد. چرا که افراد هر تیم دائما باید از API‌های ارائه شده توسط تیم‌های دیگر مطلع باشند. به همین جهت زمانیکه شما یک سبک معماری مانند میکروسرویس را انتخاب می‌نمایید، باید یک روش مستند سازی مناسب را نیز انتخاب نمایید تا مانع از پیچیدگی و سردرگمی ناشی از نبود مستندات مناسب و سربار هماهنگی بین تیمی شوید.

در این مطلب، 2 روش زیر، جهت مستند سازی سرویس‌ها بررسی می‌شوند:
 روش اول - کارت طراحی میکروسرویس (microservice design card)
 روش دوم - بوم میکروسرویس ( microservice canvas)

لازم به ذکر است که دو روش فوق می‌توانند مکمل یکدیگر باشند؛ همچنین این اسناد (علاوه بر مفید بودن برای مصرف کنندگان سرویس) حتی جهت شناسایی سرویس‌ها و ارتباطات بین آنها در زمان تحلیل (در جلساتی مانند event storming) نیز می‌توانند کاربردی باشند.


روش اول - کارت طراحی میکروسرویس (microservice design card) 
روش کارت طراحی میکروسرویس بر اساس کارت‌های CRC که گاهی اوقات در Object-oriented design استفاده می‌شوند، مدل سازی شده‌است و می‌توان از آن جهت ثبت خدمات سرویس و همچنین تعاملات سرویس، با سایر سرویس ها، استفاده نمود (این اطلاعات نسبت به اطلاعات قابل درج در microservice canvas که در ادامه بررسی می‌شود، کمتر است).
می‌توانید این کارت‌ها را در ابعاد کوچک و به تعداد کافی پرینت و در هنگام تحلیل و طراحی سرویس(ها)، از آنها استفاده نمایید
در نهایت جهت کشیدن نقشه وابستگی سرویس‌ها، کارت‌ها روی یک برد نصب و با کشیدن خطوط بین سرویس‌ها، وابستگی‌های هر یک را مشخص نمایید. مزیت اصلی این روش در طراحی، همکاری بیشتر تیم‌ها با یکدیگر می‌باشد.

(نمونه ای از کارت طراحی ‌های مربوط سرویس‌های project و archive)



روش دوم  - بوم میکروسرویس (microservice canvas)
یکی دیگر از روش‌های مناسب برای مستند سازی یک سرویس و ساختار درونی آن، استفاده از روش بوم میکروسرویس می‌باشد. بوم میکروسرویس نیز تا حدودی شبیه به کارت‌های CRC و همچنین روش microservice design card که پیش‌تر بررسی گردید، می‌باشد؛ با این تفاوت که اطلاعات بیشتری را نگهداری می‌نماید.
روش طراحی روی بوم جهت مستند سازی، از گذشته در صنایع مختلفی از جمله صنعت نرم افزار رایج بوده است؛ ولی ظاهرا برای اولین بار در سال 2017 و در این مقاله  (از سایت DZone که توسط Matt McLarty و Irakli Nadareishvili منتشر شده‌است) از این روش به عنوان روشی برای مستند سازی سرویس‌ها (در معماری میکروسرویس) استفاده شده‌است . پس از آن و در طول زمان، نمونه‌های مختلف و نسبتا مشابهی از این بوم توسط افراد و شرکت‌های مختلف ارائه شد (که با جستجوی عبارت microservice canvas در بخش تصاویر سایت گوگل می‌توانید نمونه‌های متفاوت آن را بررسی نمایید). در ادامه این مقاله، بوم میکروسرویسی که آقای ریچاردسون در سال 2019 معرفی نمودند، بررسی می‌گردد.

تصویر فوق بوم مربوط به سرویس Order می‌باشد

با توجه به تصویر فوق و مفاهیم بررسی شده در قسمت قبلی این مقاله، به بررسی بخش‌های مختلف بوم میکروسرویس می‌پردازیم.

نمای بیرونی یک سرویس (A service’s external view) در بوم میکروسرویس توسط بخش‌های زیر معرفی می‌گردد
• Name – نام سرویس
• Description – ارائه یک توضیح مختصر در مورد سرویس
• Capabilities – معرفی بخش‌هایی از منطق کسب و کار که در این سرویس پیاده سازی شده‌است.
• Service APIs – معرفی عملیات یا operations (شامل commands  و queries) که در این سرویس پیاده سازی شده‌اند و همچنین معرفی وقایع یا همان domain event هایی که توسط سرویس منتشر می‌شوند.
• Quality attributes – معرفی non-functional requirements‌های سرویس
• Observability (قابلیت‌های مشاهده و بررسی  سرویس) – معرفی health check endpoints و key metrics و ... .

وابستگی‌های یک سرویس (A service’s dependencies)  که لزوما استفاده بیرونی ندارد و بیشتر منبعی برای خود اعضای تیم خواهد بود، در بخشی تحت عنوان dependencies مشخص می‌شوند که خود شامل دو قسمت به شرح زیر می‌باشد: 
• Invokes – عملیاتی که در سایر سرویس‌ها پیاده سازی شده‌اند و در این سرویس فراخوانی می‌گردند.
• Subscribes – اشتراک در کانال پیام‌هایی که شامل وقایع سایر سرویس‌ها می‌باشند.

موارد مربوط به پیاده سازی یک سرویس (A service’s implementation)
در بوم میکروسرویس علاوه بر تمام موارد فوق، شما میتوانید مدل پیاده سازی لایه دامنه را نیز معرفی نمایید. همچنین نام bounded context‌ها و aggregateهای پیاده سازی شده در این سرویس، در این بخش نوشته می‌شود (که در یک حالت ایده آل، تنها یک agg از یک bc در یک سرویس پیاده سازی خواهد شد).

تولید بوم میکروسرویس 
با توجه به دغدغه ناشی از به روز نگه داشتن بوم میکروسرویس (و به طور کلی مستندات پروژه) همراه با تغییرات پروژه، تکنیک‌ها و ابزارهایی جهت تولید خودکار فایل json بوم میکروسرویس (microservice-canvas-tools) و همچنین جهت به تصویر کشیدن فایل json تولید شده (microservices-design-canvas-editor ) وجود دارد. اما اگر ابزار مناسبی را با توجه به پلتفرم مورد نظر، نتواستید پیدا کنید و یا فرصت توسعه یک ابزار اختصاصی نبود، همواره می‌توان این فایل را به صورت دستی نیز ایجاد نمود و در مخزن کد مربوط به پروژه و در کنار سورس اصلی نگه داری کرد تا همراه با سایر مستندات پروژه، این سند نیز پس از هر تغییر به روز شود. نسخه‌ای از آن را نیز می‌توان در محلی مناسب با سایر تیم‌ها به اشتراک گذاشت.

در صورتیکه قصد تولید و توسعه دستی این سند را دارید، نسخه‌ای از آن را در قالب فایل ورد، می‌توانید در این مخزن در گیت هاب پیدا نمایید.