مطالب
مایکرو سرویس‌ها - قسمت 2 - بررسی ویژگی‌ها
در قسمت قبل با مفهوم مایکرو سرویس‌ها آشنا شدیم. سرویس‌های کوچک و مجزایی که بصورت مستقل، قابلیت توسعه و استقرار دارند و در راستای انجام یک قابلیت کسب و کار در اختیار دیگران قرار می‌گیرند.

ویژگی‌های یک مایکرو سرویس چیست؟

بعد از آشنایی با معماری مایکرو سرویس‌ها می‌خواهیم با ویژگی‌های آن آشنا شویم. البته باید به این نکته توجه داشت که همه‌ی معماری‌های مایکروسرویس‌ها این ویژگی‌ها را ندارند؛ ولی میتوان انتظار داشت اکثر آن‌ها این ویژگی‌ها را از خود به نمایش بگذارند.

Componentization via Services 

تعریف ما از component، واحدی از نرم افزار می‌باشد که به تنهایی قابل جایگزینی و به‌روز رسانی می‌باشد.
همانطور که گفته شد، مایکرو سرویس‌ها یک نرم افزار را به سرویس‌های (business component) کوچکتری تقسیم میکنند که به تنهایی قابل توسعه و استقرار می‌باشند. ما کتابخانه‌ها را به عنوان component در نظر میگیریم که به نرم افزار ما پیوند خورده‌اند و به وسیله فراخوانی توابع در پروسه نرم افزار قابل استفاده می‌باشند. در حالیکه سرویس‌ها componentهایی هستند که خارج از پروسه نرم افزار ما می‌باشند و به وسیله مکانیزم‌های ارتباطی مانند Web Service Request و Remote Procedure Call قابل دسترسی هستند.
در سیستم های یکپارچه که از چندین  component تشکیل شده‌اند و این componentها در جریان یک پروسه با هم در ارتباط هستند، اگر بخواهیم در یک component تغییر ایجاد کنیم، این تغییر نیازمند استقرار مجدد کل سیستم می‌باشد. در حالیکه در معماری مایکرسرویس، کافی است شما  component (سرویس) ای را که تغییر کرده‌است، دوباره مستقر کنید و سایر بخشهای سیستم از این تغییر تاثیر نمی‌پذیرند.

Technology Heterogeneity 

یک سیستم را در نظر بگیرید که از همکاری  چندین سرویس تشکیل شده‌است. ما میتوانیم تصمیم بگیریم که برای هر کدام از سرویسها از تکنولوژی متفاوتی استفاده کنیم. این امر به ما اجازه می‌دهد برای حل هر مشکل، از بهترین ابزار و تکنولوژی استفاده کنیم. این امر بر خلاف سیستم‌هایی که تنها باید از تکنولوژی بکار رفته در سیستم استفاده کنند، انعطاف پذیری سیستم را بسیار بالا می‌برد.
برای روشن شدن موضوع فرض کنید می‌خواهیم در بخشی از سیستم، performance را افزایش دهیم. برای این امر میتوانیم از هر تکنولوژی که به ما کمک میکند، استفاده کنیم. برای نمونه در یک سیستم شبکه اجتماعی، می‌توانیم اطلاعات مربوط به کاربران و ارتباطات آن‌ها را در یک دیتابیس مبتنی بر گراف و اطلاعات مربوط به پست‌ها و نظرات کاربران را در یک دیتابیس سندگرا ذخیره کنیم. به این ترتیب مشاهده میکنیم که وابسته‌ی به تکنولوژی خاصی نمی‌باشیم و می‌توانیم بر اساس نیاز خود، از تکنولوژی مورد نظر بهره ببریم.


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

Resilience 

چنانچه یکی ازسرویسها دچار اشکال شود و این مسئله بصورت زنجیروار برای تمام سرویس‌ها رخ ندهد، با جدا شدن آن سرویس، درروند کار سیستم خللی بوجود نمی‌آید و سیستم بدون کمترین مشکلی به ادامه کار خود می‌پردازد. یعنی محدوده یک سرویس، دیوار حائلی می‌شود تا سایر بخشها تاثیر نپذیرند. در سیستم‌های یکپارچه چنانچه یک سرویس دچار اشکال شود، سایر بخش‌های سیستم نیز غیر قابل استفاده می‌شوند. البته می‌توان با نصب نسخه‌های متفاوتی بر روی ماشین‌های متفاوت (Scale out)، تاثیر اینگونه اختلالات را تا حدودی کاهش داد. اما بوسیله مایکرو سرویسها می‌توانیم سیستم‌هایی را بسازیم که قادرند با خطاهای بوجود آمده در سرویس‌ها به‌درستی رفتار کنند؛ تا خللی در کار سیستم ایجاد نشود.

Scaling

فرض کنید در بخشی ازیک سیستم، دغدغه Performance بوجود می‌آید. چنانچه ما یک معماری یکپارچه را داشته باشیم، مجبوریم کل آن را scale کنیم. درحالیکه این مشکل تنها در بخش کوچکی از نرم افزار ایجاد شده‌است. اما اگرمعماری ما مایکرو سرویس باشد، فقط همان سرویس مربوطه را که بر روی آن دغدغه داریم، scale می‌کنیم و سایر بخش‌های نرم افزار بدون نیاز به تغییر و بزرگ شدن، به کار خود ادامه می‌دهند.


Gilt  یک خرده فروش آنلاین در صنعت مد می‌باشد که  بخاطر مسائل Performance به معماری مایکروسرویس‌ها روی آورد. آنها در سال 2007 با یک نرم افزار یکپارچه شروع به کار کردند. اما بعد از دوسال سیستم آنها قادر به مقابله با ترافیک سایت نبود. آنها با تقسیم بندی قسمت‌های اصلی سیستم خود به مایکرو سرویسها، توانستند خیلی بهتر و موثرتر با ترافیک رسیده مقابله کنند و قابلیت دسترسی پذیری سیستم را افزایش دهند. در حال حاضر سیستم  آنها از حدود 450 مایکرو سرویس تشکیل شده که هر کدام روی چندین ماشین مختلف در حال اجرا می‌باشند.


Ease of Deployment 

تغییر حتی یک خط کد، در برنامه‌ای که از چندین میلیون خط تشکیل شده است، ما را ملزم به استقرار مجدد کل سیستم  و انتشار نسخه جدید می‌کند. این استقرار می‌تواند تاثیر و ریسک بالایی را داشته باشد و ما را مجبور به تغییرات بیشتر و انتشار نسخه‌های دیگر کند که این حجم زیاد تغییرات ممکن است به محصول ما ضربه بزند.
اما در مایکرو سرویس‌ها یک تغییر کوچک، تمام سیستم را تحت تاثیر قرار نمی‌دهد. بلکه تنها تغییرات مربوط به سرویس مورد نظر می‌باشد که به صورت مستقل قابلیت استقرار را دارد و چنانچه سیستم دچار مشکل شود، منشاء تغییرات کاملا مشخصی می‌باشد و می‌توان سریعتر سیستم را به وضعیت قابل اطمینان بازگرداند. این ویژگی یکی از مهمترین دلایلی می‌باشد که شرکت‌هایی مانند Netflix و Amazon  به پیاده سازی این معماری روی آورده‌اند.

Organizational Alignment 

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

Composability

یکی از ویژگی‌های کلیدی که در سیستم‌های توزیع شده و سرویس گرا به آن وعده داده می‌شود، قابلیت استفاده مجدد از functionality‌های سیستم می‌باشد. مایکرو سرویس‌ها این امکان را برای ما فراهم می‌کنند تا functionality ‌های سیستم، به روش‌های مختلف و با اهداف گوناگونی مورد استفاده قرار گیرند. اینکه استفاده کنندگان نرم‌افزار ما چگونه از آن استفاده می‌کنند، برای ما مهم است. در حال حاضر ما باید درباره راه‌های مختلفی که می‌توانند قابلیت‌های سیستم را باهم ترکیب کنند تا در برنامه‌های وب، موبایل و یا ابزار‌های پوشیدنی مورد استفاده قرار گیرند، فکر کنیم. در مایکرو سرویس تفکر ما این است که بخش‌های مختلف سیستم خود را از هم جدا کنیم و این بخشها توسط استفاده کننده‌های بیرونی قابل دسترسی باشند و با تغییر شرایط بتوانیم  بخش‌های گوناگونی را بسازیم.


Optimizing for Replaceability

اگر شما در یک سازمان متوسط و یا بزرگ، مشغول به کار باشید، ممکن است با یک سیستم بزرگ و قدیمی مواجه شده باشید که در گوشه‌ای از سازمان در حال استفاده می‌باشد و هیچ کسی رغبت نزدیک شدن به آن و دست بردن در آن را ندارد و اگر کسی از شما بپرسد «چرا نمی‌توان آن را تغییر داد؟» خواهید گفت این کار، بسیار پرریسک و پردردسر است.
با استفاده از معماری مایکروسرویس، هزینه این تغییرات به مراتب کمتر و تغییرات با ضریب اطمینان بالاتری انجام می‌شوند.
مطالب
Lazy Loading در AngularJS
وقتی پروژه انگیولاری‌تان کمی گسترش پیدا کند، تعداد زیادی فایل شامل کنترلرها، سرویس‌ها، دایرکتیوها و ... خواهید داشت. واضح است که همه این اجزا همراه با هم مورد نیاز نیستند و برای افزایش سرعت بارگذاری سایت و صرفه جویی در مصرف پهنای باند بهتر است هرکدام از آن‌ها را در هنگام نیاز بارگذاری کنیم. این یعنی همان lazy loading خودمان!
در AngularJS امکانی برای lazy loading فایل‌ها پیشبینی نشده است، پس باید از ابزارهای دیگری که این امکان را فراهم می‌کنند استفاده کرد. من در ادامه از Script.js برای این کار استفاده خواهم کرد، ولی شما می‌توانید از هر کتابخانه دیگری استفاده کنید.
اما مسئله دیگری که پیش از lazy loading  فایل‌ها باید تکلیفش را معلوم کنیم، این است که چطور می‌توانیم اجزایی را به ماژولی که قبلا راه‌اندازی (bootstrap) شده اضافه کنیم. اگر بخواهیم برای مثال کنترلری را در یک فایل مجزا تعریف کنیم، باید آن را به شکلی در ماژول برنامه‌مان ثبت کنیم. فرض کنید این کار را به این ترتیب انجام دهیم: 
angular.module('app').controller('SomeLazyController', function($scope)
{
    $scope.key = '...';
});
در این صورت اگر این کنترلر را در قسمتی از برنامه به صورت ng-controller='SomeLazyController' استفاده کنیم با این خطا مواجه خواهیم شد:
Error: Argument ‘SomeLazyController’ is not a function, got undefined
برای این کار (افزودن اجزایی به ماژولی که قبلا راه اندازی شده) می‌توانیم بجای استفاده از API‌های ماژول، از provider های AngularJS استفاده کنیم. به این ترتیب برای ثبت یک کنترلر باید از  controllerProvider$، برای ثبت یک directive از  compileProvider$، برای ثبت فیلترها از filterProvider$ و برای ثبت سایر اجزا در ماژول از provide$ استفاده کنیم:
// Registering a controller after app bootstrap
$controllerProvider.register('SomeLazyController', function($scope)
{
   $scope.key = '...'; 
});
 
// Registering a directive after app bootstrap
$compileProvider.directive('SomeLazyDirective', function()
{
    return {
        restrict: 'A',
        templateUrl: 'templates/some-lazy-directive.html'
    }
})
 
// etc
اما نکته‌ای که درباره providerها وجود دارد این است که آن‌ها تنها در روال config یک ماژول در دسترس هستند. بنا بر این برای دسترسی به آن‌ها پس از اجرای این روال، ارجاعی به آنها را باید نگهداری کنیم: 
(function () {
    app = angular.module("app", []);

    app.config([
        '$controllerProvider',
        '$compileProvider',
        '$filterProvider',
        '$provide',

    function ($controllerProvider, $compileProvider, $filterProvider, $provide) {
        //برای رجیستر کردن غیر همروند اجزای انگیولاری در آینده
        app.lazy =
        {
            controller: $controllerProvider.register,
            directive: $compileProvider.directive,
            filter: $filterProvider.register,
            factory: $provide.factory,
            service: $provide.service
        };
    }]);
})();
اکنون SomeLazyController  را به این ترتیب می‌توانیم ثبت کنیم: 
angular.module('app').lazy.controller('SomeLazyController', function($scope)
{
    $scope.key = '...';
});
نکته دیگر این است که کجا باید lazy loadign را انجام دهیم. به نظر می‌رسد مناسب‌ترین محل برای انجام این کار خصوصیت resolve مسیریابی است. در  این مطلب و این مطلب resolve در route$ و UI-Router معرفی شده است:
$stateProvider
            .state('state1', {
                url: '/state1',
                template: '<div>{{st1Ctrl.msg}}</div>',
                controller: 'state1Controller as st1Ctrl',
                resolve: {
                    fileDeps: ['$q', '$rootScope', function ($q, $rootScope) {
                        var deferred = $q.defer();
                        var deps = [
                            'app/messageService.js',
                            'app/state1Controller.js'];
                        $script(deps, function () {
                            $rootScope.$apply(function () {
                                deferred.resolve();
                            });
                        });
                        return deferred.promise;
                    }]
                }
            })
            .state('state2', {
                url: '/state2',
                template: '<div>{{st2Ctrl.msg}}</div>',
                controller: 'state2Controller as st2Ctrl',
                resolve: {
                    fileDeps: ['$q', '$rootScope', function ($q, $rootScope) {
                        var deferred = $q.defer();
                        var deps = [
                            'app/messageService.js',
                            'app/state2Controller.js'];
                        $script(deps, function () {
                            $rootScope.$apply(function () {
                                deferred.resolve();
                            });
                        });
                        return deferred.promise;
                    }]
                }
            });
    }]);
کنترلر state1Controller که در فایلی با همین نام پیاده‌سازی شده است تنها در مسیر state1/ مورد نیاز است، و state2Controller تنها در مسیر state2/ لازم است بارگذاری شود. هردوی این کنترلرها به messageService وابستگی دارند که در messageService.js پیاده سازی شده است (همانطور که در این مطلب   اشاره شده می‌توانیم یک حالت انتزاعی به عنوان پدر دو حالت موجود تعریف کرده و وابستگی مشترک را به آن منتقل کنیم).
 برای بارگذاری فایل‌های مورد نیاز در ابتدای کار و راه اندازی اولیه برنامه هم می‌توان به این ترتیب عمل کرد:  
<script type="text/javascript">
        // ----Script.js----
        !function (a, b, c) { function t(a, c) { var e = b.createElement("script"), f = j; e.onload = e.onerror = e[o] = function () { e[m] && !/^c|loade/.test(e[m]) || f || (e.onload = e[o] = null, f = 1, c()) }, e.async = 1, e.src = a, d.insertBefore(e, d.firstChild) } function q(a, b) { p(a, function (a) { return !b(a) }) } var d = b.getElementsByTagName("head")[0], e = {}, f = {}, g = {}, h = {}, i = "string", j = !1, k = "push", l = "DOMContentLoaded", m = "readyState", n = "addEventListener", o = "onreadystatechange", p = function (a, b) { for (var c = 0, d = a.length; c < d; ++c) if (!b(a[c])) return j; return 1 }; !b[m] && b[n] && (b[n](l, function r() { b.removeEventListener(l, r, j), b[m] = "complete" }, j), b[m] = "loading"); var s = function (a, b, d) { function o() { if (!--m) { e[l] = 1, j && j(); for (var a in g) p(a.split("|"), n) && !q(g[a], n) && (g[a] = []) } } function n(a) { return a.call ? a() : e[a] } a = a[k] ? a : [a]; var i = b && b.call, j = i ? b : d, l = i ? a.join("") : b, m = a.length; c(function () { q(a, function (a) { h[a] ? (l && (f[l] = 1), o()) : (h[a] = 1, l && (f[l] = 1), t(s.path ? s.path + a + ".js" : a, o)) }) }, 0); return s }; s.get = t, s.ready = function (a, b, c) { a = a[k] ? a : [a]; var d = []; !q(a, function (a) { e[a] || d[k](a) }) && p(a, function (a) { return e[a] }) ? b() : !function (a) { g[a] = g[a] || [], g[a][k](b), c && c(d) }(a.join("|")); return s }; var u = a.$script; s.noConflict = function () { a.$script = u; return this }, typeof module != "undefined" && module.exports ? module.exports = s : a.$script = s }(this, document, setTimeout)

        $script('Scripts/angular.js', function () {
            $script('Scripts/angular-ui-router.js', function () {
                $script('app/app.js', function () {
                    angular.bootstrap(document, ['app']);
                });
            });
        });
    </script>
توجه داشته باشید که لازم نیست بارگذاری فایل‌ها حتما یکی پس از دیگری باشد. ترتیب بارگذاری فایل‌ها تنها در آن‌هایی که وابستگی به هم دارند باید رعایت شود. همچنین، می‌توانید همه فایل‌های مورد نیاز در این مرحله را Bundle کنید. 
از اینجا می‌توانید پروژه بسیار ساده‌ای که در آن lazy loading پیاده شده است را دانلود کرده و مطالب توضیح داده شده را مشاهده کنید. 
نظرات مطالب
Blazor 5x - قسمت 34 - توزیع برنامه‌های Blazor بر روی IIS
روش درست کردن دمو برای پروژه‌های blazor در Github (یا روش توزیع پروژه‌های Blazor WASM در Github-Pages)

ابتدا فایل yml زیر را در پوشه‌ی github\workflows\deploy.yml. قرار دهید (پوشه‌ای را به این نام، در ریشه‌ی پروژه‌ی خود ایجاد کنید):
name: Deploy to GitHub Pages

# Run workflow on every push to the main branch
on:
  push:
    branches: [ main ]

jobs:
  deploy-to-github-pages:
    # use ubuntu-latest image to run steps on
    runs-on: ubuntu-latest
    steps:
    # uses GitHub's checkout action to checkout code form the main branch
    - uses: actions/checkout@v2
    
    # sets up .NET Core SDK
    - name: Setup .NET Core SDK
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.302

    # publishes Blazor project to the release-folder
    - name: Publish .NET Core Project
      run: dotnet publish ./src/DNTPersianComponents.Blazor.WasmSample/Server/DNTPersianComponents.Blazor.WasmSample.Server.csproj -c Release -o release --nologo
    
    # changes the base-tag in index.html from '/' to 'DNTPersianComponents.Blazor' to match GitHub Pages repository subdirectory
    - name: Change base-tag in index.html from / to DNTPersianComponents.Blazor
      run: sed -i 's/<base href="\/" \/>/<base href="\/DNTPersianComponents.Blazor\/" \/>/g' release/wwwroot/index.html
    
    # copy index.html to 404.html to serve the same file when a file is not found
    - name: copy index.html to 404.html
      run: cp release/wwwroot/index.html release/wwwroot/404.html

    # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore)
    - name: Add .nojekyll file
      run: touch release/wwwroot/.nojekyll
      
    - name: Commit wwwroot to GitHub Pages
      uses: JamesIves/github-pages-deploy-action@3.7.1
      with:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        BRANCH: github-pages
        FOLDER: release/wwwroot
در این قالب، چهار مورد را باید ویرایش کنید:
- نام شاخه‌ی اصلی پروژه؛ که یا main است و یا master.
- شماره نگارش دات نت مورد استفاده.
- مسیر فایل csproj پروژه‌ی wasm.
- نام اصلی مخزن کد.


سپس آن‌را به مخزن کد خود commit کنید. بعد به قسمت settings->pages در github مراجعه کرده و source را بر روی نام شاخه‌ی جدید github-pages (فوق در قسمت آخر کار) قرار داده و آن‌را ذخیره کنید. الان سایت دموی شما در مسیری که در همین قسمت pages پس از ذخیره سازی، نمایش می‌دهد، آماده‌است.


یک نکته‌ی مهم

چون base href، توسط action فوق اصلاح می‌شود تا به پوشه‌ی نسبی محل قرارگیری برنامه اشاره کند، نیاز است navlink‌ها با href شروع شده‌ی با / نباشند؛ چون به ریشه‌ی سایت اشاره می‌کنند و نه مسیر نسبی محل قرارگیری برنامه. کلا در هر قسمتی از برنامه، این نکته باید رعایت شود. مثلا اگر فونت وبی را در فایل app.css تعریف کرده‌اید، مسیر آن نباید با / شروع شود؛ وگرنه یافت نخواهد شد. یک مثال:
فایل app.css برنامه در مسیر wwwroot\css\app.css قرار دارد و داخل آن فایل، فونت‌های پوشه‌ی دیگر wwwroot\lib\samim-font را به صورت زیر تعریف کرده‌ایم؛ که یعنی مسیر فونت را از ریشه‌ی سایت پیدا کن:
src: url('/lib/samim-font/Samim-Bold.eot?v=4.0.5');
این مسیر، باید به مسیر نسبی زیر که به یک پوشه‌ی بالاتر (از محل قرار گیری app.css) اشاره می‌کند، اصلاح شود:
src: url('../lib/samim-font/Samim-Bold.eot?v=4.0.5');
مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت اول - نصب و راه اندازی
VS Code یک محیط توسعه‌ی یکپارچه است که توسط مایکروسافت توسعه پیدا می‌کند و دارای مزایای ذیل است:
 - سبک وزن است
 - بسیار سریع است
 - به صورت سورس باز توسعه پیدا می‌کند
 - رایگان است
 - چندسکویی است
 - انواع و اقسام زبان‌های برنامه نویسی را پشتیبانی می‌کند
 - پشتیبانی بسیار مناسبی را از طرف جامعه‌ی برنامه نویسان به همراه دارد
 - به همراه تعداد زیادی افزونه است

هدف اصلی از توسعه‌ی آن نیز ارائه‌ی تجربه‌ی کاربری یکسانی در سکوهای کاری مختلف و زبان‌های متفاوت برنامه نویسی است. در اینجا مهم نیست که از ویندوز، مک یا لینوکس استفاده می‌کنید. نحوه‌ی کار کردن با آن در این سکوهای کاری تفاوتی نداشته و یکسان است. همچنین برای آن تفاوتی نمی‌کند که با PHP کار می‌کنید یا ASP.NET. تمام گروه‌های مختلف برنامه نویسان دسترسی به یک IDE بسیار سریع و سبک وزن را خواهند داشت.
برخلاف نگارش کامل ویژوال استودیو که این روزها حجم دریافت آن به بالای 20 گیگابایت رسیده‌است، VS Code با هدف سبک وزن بودن و سادگی دریافت و نصب، طراحی و توسعه پیدا می‌کند. این مورد، مزیت دریافت به روز رسانی‌های منظم را بدون نگرانی از دریافت حجم‌های بالا، برای بسیاری از علاقمندان مسیر می‌کند.
همچنین برای کار با نگارش‌های جدیدتر ASP.NET Core، دیگر نیازی به دریافت آخرین به روز رسانی‌های چندگیگابایتی ویژوال استودیوی کامل نبوده و می‌توان کاملا مستقل از آن، از آخرین نگارش NET Core. و ASP.NET Core به سادگی در VSCode استفاده کرد.


نصب VS Code بر روی ویندوز

آخرین نگارش این محصول را از آدرس https://code.visualstudio.com می‌توانید دریافت کنید. نصب آن نیز بسیار ساده‌است؛ فقط گزینه‌ی Add to PATH را نیز در حین نصب حتما انتخاب نمائید (هرچند به صورت پیش فرض نیز انتخاب شده‌است). به این ترتیب امکان استفاده‌ی از آن در کنسول‌های متفاوتی مسیر خواهد شد.
در ادامه فرض کنید که مسیر D:\vs-code-examples\sample01 حاوی اولین برنامه‌ی ما خواهد بود. برای اینکه در اینجا بتوانیم، تجربه‌ی کاربری یکسانی را مشاهده کنیم، از طریق خط فرمان به این پوشه وارد شده و دستور ذیل را صادر می‌کنیم:
 D:\vs-code-examples\sample01>code .
به این ترتیب کل پوشه‌ی sample01 در VS Code باز خواهد شد.


نصب VS Code بر روی Mac

نصب VS Code بر روی مک یا لینوکس نیز به همین ترتیب است و زمانیکه به آدرس فوق مراجعه می‌کنید، به صورت خودکار نوع سیستم عامل را تشخیص داده و بسته‌ی متناسبی را به شما پیشنهاد می‌کند. پس از دریافت بسته‌ی آن برای مک، یک application را دریافت خواهید کرد که آن‌را می‌توان به مجموعه‌ی Applications سیستم اضافه کرد. تنها تفاوت تجربه‌ی نصب آن با ویندوز، انتخاب گزینه‌ی Add to PATH آن است و به صورت پیش فرض نمی‌توان آن‌را از طریق ترمینال در هر مکانی اجرا کرد. برای این منظور، پس از اجرای اولیه‌ی VS Code، دکمه‌های Ctrl/Command+Shift+P را در VS Code فشرده و سپس path را جستجو کنید (در دستور یاد شده، Ctrl برای ویندوز و لینوکس است و Command برای Mac):


در اینجا گزینه‌ی install 'code' command path را انتخاب کنید تا بتوان VS Code را از طریق ترمینال نیز به سادگی اجرا کرد. به این ترتیب امکان اجرای دستور . code که بر روی ویندوز نیز ذکر شد، در اینجا نیز میسر خواهد بود.


نصب VS Code بر روی لینوکس

در اینجا نیز با مراجعه‌ی به آدرس https://code.visualstudio.com، بسته‌ی متناسب با لینوکس، جهت دریافت پیشنهاد خواهد شد؛ برای مثال بسته‌های deb. برای توزیع‌هایی مانند اوبونتو و یا rpm. برای ردهت. به علاوه اگر بر روی علامت ^ کنار بسته‌های دانلود کلیک کنید، یک بسته‌ی tar.gz. نیز قابل دریافت خواهد بود. تجربه‌ی نصب آن نیز همانند نمونه‌ی ویندوز است و Add to PATH آن به صورت خودکار انجام خواهد شد.


بررسی ابتدایی محیط VS Code

VS Code بر اساس فایل‌های قرار گرفته‌ی در یک پوشه و زیر پوشه‌های آن کار می‌کند. به همین جهت پس از صدور دستور . code، آن پوشه را در IDE خود نمایش خواهد داد. در اینجا برخلاف نگارش کامل ویژوال استودیو، روش کار، مبتنی بر یک فایل پروژه نیست و اگر خارج از VS Code نیز فایلی را به پوشه‌ی باز شده اضافه کنید، بلافاصله تشخیص داده شده و در اینجا لیست می‌شود. هرچند یک چنین تجربه‌ی کاربری با پروژه‌های ASP.NET Core نیز در نگارش‌های جدیدتر ویژوال استودیوی کامل، سعی شده‌است شبیه سازی شود؛ برخلاف سایر پروژه‌های ویژوال استودیو که اگر فایلی در فایل پروژه‌ی آن مدخلی نداشته باشد، به صورت پیش فرض نمایش داده نشده و درنظر گرفته نمی‌شود.

در ادامه برای نمونه از طریق منوی File->New File، یک فایل جدید را اضافه می‌کنیم. هرچند می‌توان اشاره‌گر ماوس را بر روی نام پوشه نیز برده و از دکمه‌های نوار ابزار آن نیز برای ایجاد یک فایل و یا پوشه‌ی جدید نیز استفاده کرد:


در اینجا فرمت ابتدایی فایل جدید را plain text تشخیص می‌دهد:


برای تغییر این حالت یا می‌توان فایل را ذخیره کرد و پسوند مناسبی را برای آن انتخاب نمود و یا در همان status bar پایین صفحه، بر روی plain text کلیک کنید تا منوی انتخاب زبان ظاهر شود:


به این ترتیب پیش از ذخیره‌ی فایل با پسوندی مناسب نیز می‌توان زبان مدنظر را تنظیم کرد. پس از آن، intellisense و syntax highlighting متناسب با آن زبان در دسترس خواهند بود.


بررسی تنظیمات VS Code

از طریق منوی File->Preferences->Settings می‌توان به تنظیمات VS Code دسترسی یافت.


در اینجا در سمت چپ، لیست تنظیمات مهیا و پیش فرض این محیط قرار دارند و در سمت راست می‌توان این پیش فرض‌ها را (پس از بررسی و جستجوی آن‌ها در پنل سمت چپ) بازنویسی و سفارشی سازی کرد.
تنظیمات انجام شده‌ی در اینجا را می‌توان به پوشه‌ی جاری نیز محدود کرد. برای این منظور بر روی لینک work space settings در کنار لینک user settings در تصویر فوق کلیک کنید. در این حالت یک فایل json را در پوشه‌ی vscode. نمای جاری VSCode، ایجاد خواهد کرد (sample01\.vscode\settings.json) که می‌تواند در برگیرنده‌ی تنظیمات سفارشی محدود و مختص به این پروژه و یا نما باشد.

یک نکته: تمام گزینه‌های منوی VS Code را و حتی مواردی را که در منوها لیست نشده‌اند، می‌توانید در Command Pallet آن با فشردن دکمه‌های Ctrl/Command+Shift+P نیز مشاهده کنید و به علاوه جستجوی آن نیز بسیار سریع‌تر است از دسترسی و کار مستقیم با منوها.


همچنین در اینجا اگر قصد یافتن سریع فایلی را داشته باشید، می‌توانید دکمه‌های Ctrl/Command+P را فشرده و سپس نام فایل را جستجو کرد:


این دو دستور، جزو دستورات پایه‌ای این IDE هستند و مدام از آن‌ها استفاده می‌شود.


نصب افزونه‌ی #C

اولین افزونه‌ای را که جهت کار با ASP.NET Core نیاز خواهیم داشت، افزونه‌ی #C است. برای این منظور در نوار ابزار عمودی سمت چپ صفحه، گزینه‌ی Extensions را انتخاب کنید:


در اینجا افزونه‌ی #C مایکروسافت را جستجو کرده و نصب کنید. نصب آن نیز بسیار ساده است. با حرکت اشاره‌گر ماوس بر روی آن، دکمه‌ی install ظاهر می‌شود یا حتی اگر آن‌را در لیست انتخاب کنیم، در سمت راست صفحه علاوه بر مشاهده‌ی جزئیات آن، دکمه‌های نصب و عزل نیز ظاهر خواهند شد.
تجربه‌ی کاربری محیط نصب افزونه‌های آن نیز نسبت به نگارش کامل ویژوال استودیو، بسیار بهتر است. برای نمونه اگر به تصویر فوق دقت کنید، در همینجا می‌توان جزئیات کامل افزونه، نویسنده یا نویسندگان آن و یا لیست تغییرات و وابستگی‌های آن‌را نیز بدون خروج از VSCode مشاهده و بررسی کرد. همچنین در دفعات بعدی اجرای VSCode، کار بررسی و نصب به روز رسانی‌های این افزونه‌ها نیز خودکار بوده و نیازی به بررسی دستی آن‌ها نیست.

پس از نصب، دکمه‌ی reload را ظاهر کرده و با کلیک بر روی آن، محیط جاری به صورت خودکار بارگذاری مجدد شده و بلافاصله قابل استفاده‌است.


در قسمت بعد، اولین پروژه‌ی ASP.NET Core خود را در VS Code ایجاد خواهیم کرد.
اشتراک‌ها
Visual Studio 2019 RC منتشر شد
Visual Studio 2019 RC منتشر شد
مطالب
معرفی واژه‌ی کلیدی جدید required در C# 11
واژه‌ی کلیدی جدید required در C# 11.0، همانند خواص init-only که پیشتر معرفی شدند، با هدف آغاز و نمونه سازی دقیق‌تر و ساده‌تر اشیایی است که برای اینکار، به تعاریف ویژه‌ی سازنده‌ی کلاس‌ها وابسته نیستند.


امکان نمونه سازی بدون قید و شرط کلاس‌ها

تعریف کلاس Article1 را به صورت زیر درنظر بگیرید:
public class Article1
{
    public string Title { get; set; }
    public string? Subtitle { get; set; }
    public string Author { get; set; }
    public DateTime Published { get; set; }
}
ساختار پروژه‌های دات نت 7 نیز به صورت پیش‌فرض به صورت زیر است:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
یعنی nullable reference types در آن‌ها فعال است. با این فعال بودن، به اخطارهای زیر می‌رسیم:
Non-nullable property 'Title' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618)
Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618)
عنوان می‌کند که خاصیت‌های Title و Author، به صورت غیرنال‌پذیر تعریف شده‌اند (و همانند Subtitle نال‌پذیر نیستند)؛ اما تعریف این کلاس به نحوی است که این مساله را الزامی نمی‌کند. یعنی می‌توان نمونه‌ای از Article1 را ایجاد کرد که در آن، هر دوی این خواص نال باشند؛ هرچند در این حالت مشکلی از لحاظ کامپایل وجود نخواهد داشت، اما ممکن است به علت اشتباه استفاده‌ی از آن‌ها، به null reference exceptions برسیم. چون یکی از مهم‌ترین اهداف استفاده از یک چنین تعاریفی و فعال سازی nullable reference type در یک پروژه، ارائه‌ی متادیتای بهتری جهت خواص و پارامترها و خروجی‌های متدهاست تا استفاده کننده دقیقا بداند که آیا این خواص می‌توانند نال باشد یا خیر. اگر  public string ای تعریف شده، یعنی این خاصیت قطعا نال نخواهد بود و می‌توان بدون مشکل و بدون بررسی مقدار آن، از آن استفاده کرد و اگر ?public string ای تعریف شده، یعنی ممکن است مقدار آن نال نیز باشد و بهتر است پیش از استفاده‌ی از آن، حتما مقدار آن بررسی شود. اکنون مشکل اینجا است که هیچگونه قیدی، جهت اجبار به مقدار دهی خواص غیرنال پذیر در اینجا وجود ندارند و می‌توان نمونه‌ای از شیء Article1 را ایجاد کرد که در آن متادیتای خواص غیرنال پذیر تعریفی در آن، نقض شوند.


مدیریت کردن نحوه‌ی نمونه سازی کلاس‌ها، با وابستگی به سازنده‌های آن

یکی از روش‌های مدیریت مشکلی که تا اینجا بررسی شد، تعریف سازنده‌های متعددی برای یک کلاس است؛ تا توسط آن بتوان مقدار دهی یک سری از خواص را اجباری کرد:
public class Article2
{
    public Article2(string title, string subtitle, string author, DateTime published)
    {
        Title = title;
        Subtitle = subtitle;
        Author = author;
        Published = published;
    }

    public Article2(string title, string author, DateTime published)
    {
        Title = title;
        Author = author;
        Published = published;
    }

    public string Title { get; set; }
    public string? Subtitle { get; set; }
    public string Author { get; set; }
    public DateTime Published { get; set; }
}
در این کلاس، نمونه‌ی بهبود یافته‌ی Article1 را مشاهده می‌کنید که استفاده کننده را وادار به مقدار دهی title و author می‌کند. در این حالت اخطارهای کامپایلری را که مشاهده کردید، رفع می‌شوند؛ اما به همراه این مسایل است:
- تعداد سطرهای تعریف این کلاس، به شدت افزایش یافته‌است.
- با اضافه شدن خواص بیشتری به کلاس، به تعاریف بیشتری نیاز خواهد بود.
- سازنده‌ها کار خاصی را بجز نگاشت مقادیر ارائه شده، به خواص کلاس، انجام نمی‌دهند.
- نمونه سازی این کلاس‌ها، شکل طولانی و غیرواضح زیر را پیدا می‌کند و زیبایی inline object initializers را ندارند:
 Article2 article = new("C# 11 Required Keyword", "A new language feature", "Name",  new DateTime(2022, 11, 12));

البته روش دیگر مدیریت یک چنین اخطارهایی، استفاده از مقدار ویژه‌ی !default است که سبب محو اخطارهای یاد شده می‌شود؛ اما باز هم مقدار دهی آن‌را الزامی نمی‌کند. فقط به این معنا است که قول می‌دهیم این خاصیت را در جای دیگری مقدار دهی کنیم و هیچگاه نال نباشد!
 public string Title { get; set; } = default!;


مدیریت کردن نحوه‌ی نمونه سازی کلاس‌ها، بدون وابستگی به سازنده‌های آن در C# 11.0

C# 11 به همراه واژه‌ی کلیدی جدیدی به نام required است تا دیگر نیازی نباشد همانند راه حل فوق، سازنده‌های متعددی را جهت اجبار به مقدار دهی خواص یک شیء، تعریف کنیم. در این حالت تعریف کلاس Article به صورت زیر خلاصه می‌شود و دیگر به همراه اخطارهای کامپایلر نمایش داده شده نیز نیست:
public class Article3
{
    public required string Title { get; set; }
    public string? Subtitle { get; set; }
    public required string Author { get; set; }
    public DateTime Published { get; set; }
}
به این ترتیب هنوز می‌توان از زیبایی و خوانایی به همراه نمونه سازی توسط روش inline object initializers بهره‌مند شد و همچنین مطمئن بود که اگر استفاده کننده خاصیت غیرنال‌پذیر Title را مقدار دهی نکند، اینبار با یک خطای کامپایلر متوقف خواهد شد:



معرفی ویژگی جدید SetsRequiredMembers

کلاس Book زیر را که به همراه یک خاصیت required و دو سازنده‌است، درنظر بگیرید:
public class Book
{
    public Book() => Name = string.Empty;

    public Book(string name) => Name = name;

    public required string Name { get; set; }
}
اکنون فرض کنید که بر این اساس، شیء‌ای را به صورت زیر نمونه سازی کرده‌ایم:
Book book = new("Book's Name");
این قطعه کد با خطای زیر کامپایل نمی‌شود:
Required member 'Book.Name' must be set in the object initializer or attribute constructor. [CS11Tests]csharp(CS9035)
عنوان می‌کند که باید خاصیت Name را حتما مقدار دهی کرد؛ چون از نوع required است. هرچند سازنده‌‌ای که از آن استفاده شده، این مقدار دهی را انجام داده‌است و مشکلی از لحاظ عدم مقدار دهی خاصیت Name در اینجا وجود ندارد. برای رفع این مشکل، باید تغییر زیر را اعمال کرد:
public class Book
{
    [SetsRequiredMembers]
    public Book() => Name = string.Empty;

    [SetsRequiredMembers]
    public Book(string name) => Name = name;

    public required string Name { get; set; }
}
با استفاده از ویژگی جدید SetsRequiredMembers عنوان می‌کنیم که این سازنده‌ی خاص، حتما خواص از نوع required را نیز مقدار دهی می‌کند و نیازی به صدور خطای یاد شده نیست. در این حالت بررسی خواص required توسط کامپایلر غیرفعال می‌شود.


محدودیت‌های کار با خواص required

- واژه‌ی کلیدی required را می‌توان تنها به خواص و فیلدهای نوع‌های class, record, record struct اعمال کرد. امکان اعمال این واژه‌ی کلیدی به اجزای یک اینترفیس وجود ندارد.
- میدان دید اعضای required باید حداقل در حد نوع‌های دربرگیرنده‌ی آن‌ها باشند. برای مثال اگر کلاسی public است، نمی‌توان در آن یک فیلد required با میدان دید protected را تعریف کرد.
- نوع‌های مشتق شده‌ی از یک نوع پایه، نمی‌توانند اعضای required آن‌را مخفی کنند و اگر قصد بازنویسی آن‌را دارند، باید حتما واژه‌ی کلیدی required را لحاظ کنند.
- اگر سازنده‌ای به سازنده‌ی دیگری از طریق ذکر ()base و یا ()this زنجیر شده باشد نیز باید ویژگی SetsRequiredMembers مرتبط را تکرار کند.