مطالب
آماده سازی Jasmine برای پروژه های Asp.Net MVC
با گسترش روز افزون برنامه‌های تحت وب، نیاز به یک سری ابزار برای تست و اطمینان از نحوه عملکرد صحیح کدهای نوشته شده احساس می‌شود. Jasmine یکی از این ابزار‌های قدرتمند برای تست کد‌های JavaScript است.
چندی پیش در سایت جاری چند مقاله خوب توسط یکی از دوستان درباره Qunit منتشر شد. Qunit یک ابزار قدرتمند و مناسب برای تست کد‌های جاوااسکریپت است و در اثبات صحت این گفته همین کافیست که بدانیم برای تست کد‌های نوشته شده در پروژه‌های متن بازی هم چون Backbone.Js و JQuery از این فریم ورک استفاده شده است. اما به احتمال قوی در ذهن شما این سوال مطرح شده است که خب! در صورت آشنایی با Qunit چه نیاز به یادگیری Jasmine یا خدای نکرده Mocha و FuncUnit است؟ هدف صرفا معرفی یک ابزار غیر برای تست کد است نه مقایسه و نتیجه گیری برای تعیین میزان برتری این ابزارها. اصولا مهم‌ترین دلیل برای انتخاب، علاوه بر امکانات و انعطاف پذیری، فاکتور راحتی و آسان بودن در هنگام استفاده است که به صورت مستقیم به شما و تیم توسعه نرم افزار بستگی دارد.

اما به عنوان توسعه دهنده نرم افزار که قرار است از این ابزار استفاده کنیم بهتر است با تفاوت‌ها و شباهت‌های مهم این دو فریم ورک آشنا باشیم:

»Jasmine یک فریم ورک تست کدهای جاوا اسکریپ بر مبنای Behavior-Driven Development است در حالی که Qunit بر مبنای Test-Driven Development است و همین مسئله مهم‌ترین تفاوت بین این دو فریم ورک می‌باشد.
»اگر قصد دارید که از Qunit نیز به روش BDD استفاده نمایید باید از ترکیب Pavlov به همراه Qunit استفاده کنید.
»Jasmine از مباحث مربوط به Spies و Mocking به خوبی پشتیبانی می‌کند ولی این امکان به صورت توکار در Qunit فراهم نیست. برای اینکه بتوانیم این مفاهیم را در Qunit پیاده سازی کنیم باید از فریم ورک‌های دیگر نظیر SinonJS به همراه Qunit استفاده کنیم.
»هر دو فریم ورک بالا به سادگی و راحتی کار معروف هستند 
»تمام موارد مربوط به الگوهای Matching در هر دو فریم ورک به خوبی تعبیه شده است
» هر دو فریم ورک بالا از مباحث مربوط به Asynchronous Testing  برای تست کد‌های Ajax ای به خوبی پشتیبانی می‌کنند.

بررسی چند مفهوم
قبل از شروع، بهتر است که با چند مفهوم کلی و در عین حال مهم این فریم ورک آشنا شویم
describe('JavaScript addition operator', function () {
 it('adds two numbers together', function () {
  expect(1 + 2).toEqual(3);
 });
});


در کد بالا یک نمونه از تست نوشته شده با استفاده از Jasmine را مشاهده می‌کنید. دستور describe  برای تعریف یک تابع تست مورد استفاده قرار می‌گیرد که دارای دو پارامتر ورودی است. ابتدا یک نام را به این تست اختصاص دهید(بهتر است که این عنوان به صورت یک جمله قابل فهم باشد). سپس یک تابع به عنوان بدنه تست نوشته می‌شود. به این تابع Spec گفته می‌شود.
در تابع it کد بالا شما می‌توانید کد‌های مربوط بدنه توابع تست خود را بنویسید. برای پیاده سازی Assert در توابع تست مفهوم expectation‌ها وجود دارد. در واقع expect برای بررسی مقادیر حقیقی با مقادیر مورد انتظار مورد استفاده قرار می‌گیرد و شامل مقادیر true یا false خواهد بود.
برای Setup و Teardown توابع تست خود باید از توابع beforeEach و afterEach که بدین منظور تعبیه شده اند استفاده کنید.
describe("A spec (with setup and tear-down)", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  afterEach(function() {
    foo = 0;
  });

  it("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });

  it("can have more than one expectation", function() {
    expect(foo).toEqual(1);
    expect(true).toEqual(true);
  });
});
کاملا واضح است که در تابع beforeEach مجموعه دستورالعمل‌های مربوط به setup تست وجود دارد. سپس دو تابع it برای پیاده سازی عملیات Assertion نوشته شده است. در پایان هم دستورات تابع afterEach ایجاد می‌شوند.

اگر در کد تست خود قصد دارید که یک تابع describe یا it را غیر فعال کنید کافیست یک x به ابتدای آن‌ها اضافه کنید و دیگر نیاز به هیچ کار اضافه دیگری برای comment کردن کد نیست.
xdescribe("A spec", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  xit("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });
});
توابع describe و it بالا در هنگام تست نادیده گرفته می‌شوند و خروجی آن‌ها مشاهده نخواهد شد.

درادامه قصد پیاده سازی یک مثال  را با استفاده از Jasmine و RequireJs در پروژه Asp.Net MVC دارم.
برای شروع آخرین نسخه Jasmine را از اینجا دریافت نمایید. یک پروژه Asp.Net MVC به همراه پروژه تست به صورت Empty ایجاد کنید(در هنگام ایجاد پروژه، گزینه create unit test را انتخاب نمایید). فایل دانلود شده را unzip نمایید و دو پوشه lib و spec ،به همراه فایل specRunner.html را در پروژه تست خود کپی نمایید. 
  • فولدر lib شامل فایل‌ها کد‌های Jasmine برای setup و tear down و spice و تست کد‌های شما می‌باشد.
  • فایل specRunner.html به واقع یک فایل برای نمایش فایل‌های تست و همچنین نمایش نتیجه تست است.
  • فولدر spec نیز شامل کد‌های Jasmine برای کمک به نوشتن تست می‌باشد.

در این مثال قصد داریم فایل‌های player.js و song.js که به عنوان نمونه به همراه این فریم ورک قرار دارد را در قالب یک پروژه MVC به همراه RequireJs، تست نماییم. در نتیجه این فایل‌ها را از فولدر src انتخاب نمایید و آن‌ها را در قسمت Scripts پروژه اصلی خود کپی کنید(ابتدا بک پوشه به نام App بسازید و فایل‌ها را در آن قرار دهید)

برای استفاده از requireJs باید دستور define را در ابتدا این فایل‌ها اضافه نماییم. در نتیجه فایل‌های Player.js و Song.js را باز کنید و تغییرات زیر را در ابتدای این فایل‌ها اعمال نمایید.

Song.js

define(function () {
    function Song() {
    }

    Song.prototype.persistFavoriteStatus = function (value) {
        // something complicated
        throw new Error("not yet implemented");
    };
});
Player.js
define(function () {
    function Player() {
    }
    Player.prototype.play = function (song) {
        this.currentlyPlayingSong = song;
        this.isPlaying = true;
    };

    Player.prototype.pause = function () {
        this.isPlaying = false;
    };

    Player.prototype.resume = function () {
        if (this.isPlaying) {
            throw new Error("song is already playing");
        }

        this.isPlaying = true;
    };

    Player.prototype.makeFavorite = function () {
        this.currentlyPlayingSong.persistFavoriteStatus(true);
    };
});
حال فایل SpecRunner.html را بازکنید و کد‌های مربوط به تگ script که به مسیر اصلی فایل‌های تست  اشاره می‌کند را Comment نمایید و به جای آن تگ Script مربوط به RequireJs را اضافه نمایید. برای پیکر بندی RequireJs باید از baseUrl و paths استفاده کرد.

baseUrl در پیکر بندی requireJs به مسیر فایل‌های پروژه که در پروژه اصلی MVC قرار دارد اشاره می‌کند. paths برای تعیین مسیر فایل‌های تست که در پوشه spec در پروژه تست قرار دارد اشاره می‌کند. اگر دقت کرده باشید به دلیل اینگه تگ‌های script مربوط به لود فایل‌های SpecHelper.js و PlayerSpec.js به صورت comment در آمده اند در نتیجه این فایل‌ها لود نخواهند شد و خروجی مورد نظر مشاهده نمی‌شود. در این جا باید از مکانیزم AMD موجود در RequireJs استفاده نماییم و فایل‌های مربوطه را لود کنیم. برای این کار نیاز به اضافه کردن دستور require در ابتدای تگ script به صورت زیر در این فایل است. در نتیجه فایل‌های PlayerSpec و SpecHelper نیز توسط RequireJs لود خواهند شد.



نیاز به یک تغییر کوچک دیگر نیز وجود دارد. فایل PlayerSpec را باز نمایید و وابستگی فایل‌های آن را تعیین نمایید. از آن جا که این فایل برای تست فایل‌های Player , Song ایجاد شده است در نتیجه باید از define برای تعیین این وابستگی‌ها استفاده نماییم.

یادآوری:

»دستور describe در فایل بالا برای تعریف تابع تست است. همان طور که می‌بینید بک نام به آن داده می‌شود به همراه بدنه تابع تست. 

»دستور beforeEach برای آماده سازی مواردی است که قصد داریم در تست مورد استفاده قرار گیرند. همانند متد‌های Setup در UnitTest.

» دستور expect نیز معادل Assert در UnitTest است و برای بررسی صحت عملکرد تست نوشته می‌شود.

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


 

 
نظرات اشتراک‌ها
فرم ساز JQuery
برای پیاده سازی آنچنان دانشی نیاز ندارد
اول مثال این صفحه‌ها را پیاده سازی کنید (بعد از کلیک بر روی دکمه ذخیره. پیش نمایش فرم را به شما نمایش میده)
json خروجی این المان را در جایی ذخیره کنید.
و در زمان نمایش فرم این مثال استفاده کنید(مثال از xml به JSON تغییر بدید) و دیتا ذخیره شده در قسمت قبل در این جا ست کنید.
زمانی هم که کاربر دکمه سابمیت(شما باید این دکمه را درست کنید به همراه تگ form) زد. شما مقدار Request در Action باید چک کنید آن هم به صورت داینامیک(با زدن حلقه For و...)
بعد خروجی حاصل برای کاربر ادمین به شکل
name="ابراهیم حمزه" - age="24"- و به همین ترتیب تا به آخر.
حداقل این روش برای فرم‌های ارتباط با ما جواب میدهد(در وردپرس هم پلاگین‌های بسیاری از این روش بهره میبرند و نتیجه را برای کاربر ادمین ایمیل میکنند- با همین فرمتی که من نمایش دادم.)
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت یازدهم- استفاده از تامین کننده‌های هویت خارجی
همیشه نمی‌توان کاربران را وادار به استفاده‌ی از صفحه‌ی لاگین برنامه‌ی IDP کرد. ممکن است کاربران بخواهند توسط سطوح دسترسی خود در یک شبکه‌ی ویندوزی به سیستم وارد شوند و یا از Social identity providers مانند تلگرام، گوگل، فیس‌بوک، توئیتر و امثال آن‌ها برای ورود به سیستم استفاده کنند. برای مثال شاید کاربری بخواهد توسط اکانت گوگل خود به سیستم وارد شود. همچنین مباحث two-factor authentication را نیز باید مدنظر داشت؛ برای مثال ارسال یک کد موقت از طریق ایمیل و یا SMS و ترکیب آن با روش فعلی ورود به سیستم جهت بالا بردن میزان امنیت برنامه.
در این مطلب نحوه‌ی یکپارچه سازی Windows Authentication دومین‌های ویندوزی را با IdentityServer بررسی می‌کنیم.


کار با تامین کننده‌های هویت خارجی

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


نگاهی به شیوه‌ی پشتیبانی از تامین کننده‌های هویت خارجی توسط Quick Start UI

Quick Start UI ای را که در «قسمت چهارم - نصب و راه اندازی IdentityServer» به IDP اضافه کردیم، دارای کدهای کار با تامین کننده‌های هویت خارجی نیز می‌باشد. برای بررسی آن، کنترلر DNT.IDP\Controllers\Account\ExternalController.cs را باز کنید:
[HttpGet]
public async Task<IActionResult> Challenge(string provider, string returnUrl)

[HttpGet]
public async Task<IActionResult> Callback()
زمانیکه کاربر بر روی یکی از تامین کننده‌های لاگین خارجی در صفحه‌ی لاگین کلیک می‌کند، اکشن Challenge، نام provider مدنظر را دریافت کرده و پس از آن returnUrl را به اکشن متد Callback به صورت query string ارسال می‌کند. اینجا است که کاربر به تامین کننده‌ی هویت خارجی مانند گوگل منتقل می‌شود. البته مدیریت حالت Windows Authentication و استفاده از اکانت ویندوزی در اینجا متفاوت است؛ از این جهت که از returnUrl پشتیبانی نمی‌کند. در اینجا اطلاعات کاربر از اکانت ویندوزی او به صورت خودکار استخراج شده و به لیست Claims او اضافه می‌شود. سپس یک کوکی رمزنگاری شده از این اطلاعات تولید می‌شود تا در ادامه از محتویات آن استفاده شود.
در اکشن متد Callback، اطلاعات کاربر از کوکی رمزنگاری شده‌ی متد Challenge استخراج می‌شود و بر اساس آن هویت کاربر در سطح IDP شکل می‌گیرد.


فعالسازی Windows Authentication برای ورود به IDP

در ادامه می‌خواهیم برنامه را جهت استفاده‌ی از اکانت ویندوزی کاربران جهت ورود به IDP تنظیم کنیم. برای این منظور باید نکات مطلب «فعالسازی Windows Authentication در برنامه‌های ASP.NET Core 2.0» را پیشتر مطالعه کرده باشید.
پس از فعالسازی Windows Authentication در برنامه، اگر برنامه‌ی IDP را توسط IIS و یا IIS Express و یا HttpSys اجرا کنید، دکمه‌ی جدید Windows را در قسمت External Login مشاهده خواهید کرد:


یک نکته: برچسب این دکمه را در حالت استفاده‌ی از مشتقات IIS، به صورت زیر می‌توان تغییر داد:
namespace DNT.IDP
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<IISOptions>(iis =>
            {
                iis.AuthenticationDisplayName = "Windows Account";
                iis.AutomaticAuthentication = false;
            });

اتصال کاربر وارد شده‌ی از یک تامین کننده‌ی هویت خارجی به کاربران بانک اطلاعاتی برنامه

سازنده‌ی کنترلر DNT.IDP\Controllers\Account\ExternalController.cs نیز همانند کنترلر Account که آن‌را در قسمت قبل تغییر دادیم، از TestUserStore استفاده می‌کند:
        public ExternalController(
            IIdentityServerInteractionService interaction,
            IClientStore clientStore,
            IEventService events,
            TestUserStore users = null)
        {
            _users = users ?? new TestUserStore(TestUsers.Users);

            _interaction = interaction;
            _clientStore = clientStore;
            _events = events;
        }
بنابراین در ابتدا آن‌را با IUsersService تعویض خواهیم کرد:
        private readonly IUsersService _usersService;
        public ExternalController(
    // ...
            IUsersService usersService)
        {
    // ...
            _usersService = usersService;
        }
و سپس تمام ارجاعات قبلی به users_ را نیز توسط امکانات این سرویس اصلاح می‌کنیم:
الف) در متد FindUserFromExternalProvider
سطر قدیمی
 var user = _users.FindByExternalProvider(provider, providerUserId);
به این صورت تغییر می‌کند:
 var user = await _usersService.GetUserByProviderAsync(provider, providerUserId);
در این حالت امضای این متد نیز باید اصلاح شود تا async شده و همچنین User را بجای TestUser بازگشت دهد:
 private async Task<(User user, string provider, string providerUserId, IEnumerable<Claim> claims)> FindUserFromExternalProvider(AuthenticateResult result)
ب) متد AutoProvisionUser قبلی
private TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable<Claim> claims)
{
   var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList());
   return user;
}
نیز باید حذف شود؛ زیرا در ادامه آن‌را با صفحه‌ی ثبت نام کاربر، جایگزین می‌کنیم.
مفهوم «Provisioning a user» در اینجا به معنای درخواست از کاربر، جهت ورود اطلاعاتی مانند نام و نام خانوادگی او است که پیشتر صفحه‌ی ثبت کاربر جدید را برای این منظور در قسمت قبل ایجاد کرده‌ایم و از آن می‌شود در اینجا استفاده‌ی مجدد کرد. بنابراین در ادامه، گردش کاری ورود کاربر از طریق تامین کننده‌ی هویت خارجی را به نحوی اصلاح می‌کنیم که کاربر جدید، ابتدا به صفحه‌ی ثبت نام وارد شود و اطلاعات تکمیلی خود را وارد کند؛ سپس به صورت خودکار به متد Callback بازگشته و ادامه‌ی مراحل را طی نماید:
در اکشن متد نمایش صفحه‌ی ثبت نام کاربر جدید، متد RegisterUser تنها آدرس بازگشت به صفحه‌ی قبلی را دریافت می‌کند:
[HttpGet]
public IActionResult RegisterUser(string returnUrl)
اکنون نیاز است اطلاعات Provider و ProviderUserId را نیز در اینجا دریافت کرد. به همین جهت ViewModel زیر را به برنامه اضافه می‌کنیم:
namespace DNT.IDP.Controllers.UserRegistration
{
    public class RegistrationInputModel
    {
        public string ReturnUrl { get; set; }
        public string Provider { get; set; }
        public string ProviderUserId { get; set; }

        public bool IsProvisioningFromExternal => !string.IsNullOrWhiteSpace(Provider);
    }
}
سپس با داشتن اطلاعات FindUserFromExternalProvider که آن‌را در قسمت الف اصلاح کردیم، اگر خروجی آن null باشد، یعنی کاربری که از سمت تامین کننده‌ی هویت خارجی به برنامه‌ی ما وارد شده‌است، دارای اکانتی در سمت IDP نیست. به همین جهت او را به صفحه‌ی ثبت نام کاربر هدایت می‌کنیم. همچنین پس از پایان کار ثبت نام نیاز است مجددا به همینجا، یعنی متد Callback که فراخوان FindUserFromExternalProvider است، بازگشت:
namespace DNT.IDP.Controllers.Account
{
    [SecurityHeaders]
    [AllowAnonymous]
    public class ExternalController : Controller
    {
        public async Task<IActionResult> Callback()
        {
            var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
            var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";

            var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
            if (user == null)
            {
                // user = AutoProvisionUser(provider, providerUserId, claims);
                
                var returnUrlAfterRegistration = Url.Action("Callback", new { returnUrl = returnUrl });
                var continueWithUrl = Url.Action("RegisterUser", "UserRegistration" ,
                    new { returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId });
                return Redirect(continueWithUrl);
            }
در اینجا نحوه‌ی اصلاح اکشن متد Callback را جهت هدایت یک کاربر جدید به صفحه‌ی ثبت نام و تکمیل اطلاعات مورد نیاز IDP را مشاهده می‌کنید.
returnUrl ارسالی به اکشن متد RegisterUser، به همین اکشن متد جاری اشاره می‌کند. یعنی کاربر پس از تکمیل اطلاعات و اینبار نال نبودن user او، گردش کاری جاری را ادامه خواهد داد.

در ادامه نیاز است امضای متد نمایش صفحه‌ی ثبت نام را نیز بر این اساس اصلاح کنیم:
namespace DNT.IDP.Controllers.UserRegistration
{
    public class UserRegistrationController : Controller
    {
        [HttpGet]
        public IActionResult RegisterUser(RegistrationInputModel registrationInputModel)
        {
            var vm = new RegisterUserViewModel
            {
                ReturnUrl = registrationInputModel.ReturnUrl,
                Provider = registrationInputModel.Provider,
                ProviderUserId = registrationInputModel.ProviderUserId
            };

            return View(vm);
        }
به این ترتیب اطلاعات provider نیز علاوه بر ReturnUrl در اختیار View آن قرار خواهد گرفت. البته RegisterUserViewModel هنوز شامل این خواص اضافی نیست. به همین جهت با ارث بری از RegistrationInputModel، این خواص در اختیار RegisterUserViewModel نیز قرار می‌گیرند:
namespace DNT.IDP.Controllers.UserRegistration
{
    public class RegisterUserViewModel : RegistrationInputModel
    {

اکنون نیاز است RegisterUser.cshtml را اصلاح کنیم:
- ابتدا دو فیلد مخفی دیگر Provider و ProviderUserId را نیز به این فرم اضافه می‌کنیم؛ از این جهت که در حین postback به سمت سرور به مقادیر آن‌ها نیاز داریم:
<inputtype="hidden"asp-for="ReturnUrl"/>
<inputtype="hidden"asp-for="Provider"/>
<inputtype="hidden"asp-for="ProviderUserId"/>
- با توجه به اینکه کاربر از طریق یک تامین کننده‌ی هویت خارجی وارد شده‌است، دیگر نیازی به ورود کلمه‌ی عبور ندارد. به همین جهت خاصیت آن‌را در ViewModel مربوطه به صورت Required تعریف نکرده‌ایم:
@if (!Model.IsProvisioningFromExternal)
{
    <div>
        <label asp-for="Password"></label>
        <input type="password" placeholder="Password"
               asp-for="Password" autocomplete="off">
    </div>
}
مابقی این فرم ثبت نام مانند قبل خواهد بود.

پس از آن نیاز است اطلاعات اکانت خارجی این کاربر را در حین postback و ارسال اطلاعات به اکشن متد RegisterUser، ثبت کنیم:
namespace DNT.IDP.Controllers.UserRegistration
{
    public class UserRegistrationController : Controller
    {
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> RegisterUser(RegisterUserViewModel model)
        {
    // ...
            
            if (model.IsProvisioningFromExternal)
            {
                userToCreate.UserLogins.Add(new UserLogin
                {
                    LoginProvider = model.Provider,
                    ProviderKey = model.ProviderUserId
                });
            }

            // add it through the repository
            await _usersService.AddUserAsync(userToCreate);

// ...
        }
    }
که اینکار را با مقدار دهی UserLogins کاربر در حال ثبت، انجام داده‌ایم.
همچنین در ادامه‌ی این اکشن متد، کار لاگین خودکار کاربر نیز انجام می‌شود. با توجه به اینکه پس از ثبت اطلاعات کاربر نیاز است مجددا گردش کاری اکشن متد Callback طی شود، این لاگین خودکار را نیز برای حالت ورود از طریق تامین کننده‌ی خارجی، غیرفعال می‌کنیم:
if (!model.IsProvisioningFromExternal)
{
    // log the user in
    // issue authentication cookie with subject ID and username
    var props = new AuthenticationProperties
    {
        IsPersistent = false,
        ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
    };
    await HttpContext.SignInAsync(userToCreate.SubjectId, userToCreate.Username, props);
}

بررسی ورود به سیستم توسط دکمه‌ی External Login -> Windows

پس از این تغییرات، اکنون در حین ورود به سیستم (تصویر ابتدای بحث در قسمت فعالسازی اعتبارسنجی ویندوزی)، گزینه‌ی External Login -> Windows را انتخاب می‌کنیم. بلافاصله به صفحه‌ی ثبت‌نام کاربر هدایت خواهیم شد:


همانطور که مشاهده می‌کنید، IDP اکانت ویندوزی جاری را تشخیص داده و فعال کرده‌است. همچنین در اینجا خبری از ورود کلمه‌ی عبور هم نیست.
پس از تکمیل این فرم، بلافاصله کار ثبت اطلاعات کاربر و هدایت خودکار به برنامه‌ی MVC Client انجام می‌شود.
در ادامه از برنامه‌ی کلاینت logout کنید. اکنون در صفحه‌ی login مجددا بر روی دکمه‌ی Windows کلیک نمائید. اینبار بدون پرسیدن سؤالی، لاگین شده و وارد برنامه‌ی کلاینت خواهید شد؛ چون پیشتر کار اتصال اکانت ویندوزی به اکانتی در سمت IDP انجام شده‌است.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.

یک نکته: برای آزمایش برنامه جهت فعالسازی Windows Authentication بهتر است برنامه‌ی IDP را توسط IIS Express اجرا کنید و یا اگر از IIS Express استفاده نمی‌کنید، نیاز است UseHttpSys فایل program.cs را مطابق توضیحات «یک نکته‌ی تکمیلی: UseHttpSys و استفاده‌ی از HTTPS»  فعال کنید.
مطالب
کنترل دسترسی‌ها در Angular با استفاده از Ng2Permission
سناریویی را در نظر بگیرید که در آن بعد از احراز هویت کاربر، لیست دسترسی‌هایی را که کاربر به بخش‌های مختلف خواهد داشت، از سرور دریافت می‌کند. به عنوان مثال کل دسترسی‌های موجود در سیستم به شرح زیر است:
  1. ViewUsers 
  2. CreateUser 
  3. EditUser 
  4. DeleteUser 
حالا فرض کنید، کاربر X بعد از احراز هویت، از لیست دسترسی‌های موجود، تنها دسترسی ViewUsers و EditUser را دریافت می‌کند. یعنی تنها مجاز به مشاهده‌ی لیست کاربران و ویرایش کردن آنها می‌باشد.
در اینجا جهت جلوگیری از دسترسی به ویرایش کاربر، با استفاده از یک Router guard سفارشی می‌توان مسیر users/edit را برای کاربر غیر قابل استفاده کرد؛ به نحوی که اگر کاربر وارد شده مجوز EditUser را نداشت، این مسیر غیر قابل دسترسی باشد. 
از طرفی صفحه‌ی ViewUsers، برای کاربری با تمامی دسترسی‌ها، به شکل زیر خواهد بود: 


همانطور که مشاهده می‌کنید، المنت‌هایی در صفحه وجود دارند که کاربر X نباید آنها را مشاهده کند. از جمله دکمه حذف کاربر و دکمه ایجاد کاربر. برای مخفی کردن آنها چه راه‌حلی را می‌توان ارائه داد؟ شاید بخواهید برای اینکار از ngIf* استفاده کنید. برای اینکار کافی است دست بکار شوید تا مشکلاتی را که در این روش به آنها بر می‌خورید، متوجه شوید. از جمله این مشکلات می‌توان به پیچیدگی بسیار زیاد و وجود کدهای تکراری در هر کامپوننت اشاره کرد (در بهترین حالت هر کامپوننت باید سرویس حاوی دسترسی‌ها را در خود تزریق کرده و با استفاده از توابعی، وجود یا عدم وجود دسترسی را بررسی کند). 

راه‌حل بهتر، استفاده از یک Directive سفارشی است. همچنین ماژول  Ng2Permission  علاوه بر فراهم کردن Directive جهت مدیریت المنت‌های روی صفحه، امکاناتی را جهت نگهداری و تعریف دسترسی‌های جدید و همچنین محافظت از Routeها فراهم کرده است. این ماژول الهام گرفته از ماژول  angular-permission می‌باشد.

کافی است با استفاده از دستور زیر این ماژول را نصب کنید: 

npm install angular2-permission --save

بعد از نصب، ماژول Ng2Permission را در قسمت imports در ماژول اصلی برنامه، اضافه کنید. 

import { Ng2Permission } from 'angular2-permission';
@NgModule({
  imports: [
    Ng2Permission
  ]
})


مدیریت دسترسی‌ها

 برای مدیریت دسترسی‌های کاربر، از سرویس PermissionService، در هرجایی از برنامه می‌توانید استفاده کنید. این سرویس دارای متدهای زیر است.:
توضیحات  امضاء 
 تعریف دسترسی‌های جدید   define(permissions: Array<string>): void
 افزودن دسترسی جدید   add(permission: string ) : void 
 حذف دسترسی مشخص شده   remove(permission: string ) : void 
 برسی اینکه دسترسی قبلا تعریف شده است؟   hasDefined( permission : string ) : boolean 
 برسی اینکه حداقل یکی از دسترسی‌های ورودی قبلا تعریف شده است؟   hasOneDefined(permissions: Array < string > ) : boolean 
 حذف تمامی دسترسی‌ها   clearStore( ) : void 
 دریافت تمامی دسترسی‌های تعریف شده   get store ( ) : Array < string>
 Emitter جهت تغییر در دسترسی‌ها  get permissionStoreChangeEmitter ( ) : EventEmitter< an y>

برای مثال جهت تعریف دسترسی‌های جدید، کافی است سرویس PermissionService را تزریق کرده و با استفاده از متدهای define اقدام به اینکار کنید: 

import { PermissionService } from 'angular2-permission';
@Component({
    […]
})
export class LoginComponent implements OnInit {
    constructor(private _permissionService: PermissionService) { 
        this._permissionService.define(['ViewUsers', 'CreateUser', 'EditUser', 'DeleteUser']);
    }
}

آنچه که واضح است، این است که لیست دسترسی‌ها می‌توانند از سمت سرور تامین شوند.

 

محافظت از مسیرهای تعریف شده

 بعد از تعریف دسترسی‌ها، برای محافظت از مسیر‌های غیر مجاز برای کاربر می‌توانید از PermissionGuard به شکل زیر استفاده کنید: 
import { PermissionGuard, IPermissionGuardModel } from 'angular2-permission';
[…]
const routes: Routes = [
    {
        path: 'login',
        component: LoginComponent,
        children: []
    },
    {
        path: 'users',
        component: UserListComponent,
        canActivate: [PermissionGuard],
        data: {
            Permission: {
                Only: ['ViewUsers'],
                RedirectTo: '403'
            } as IPermissionGuardModel
        },
        children: []
    },
    {
        path: 'users/create',
        component: UserCreateComponent,
        canActivate: [PermissionGuard],
        data: {
            Permission: {
                Only: ['CreateUser'],
                RedirectTo: '403'
            } as IPermissionGuardModel
        },
        children: []
    },
    {
        path: '403',
        component: AccessDeniedComponent,
        children: []
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }
همانطور که مشاهده می‌کنید کافی است canActivate در هر Route را به PermissionGuard تنظیم کنید. همچنین در data از طریق کلید Permission، تنظیمات این Guard را مشخص کنید. کلید Permission در data یک شیء از نوع IPermissionGuardModel را دریافت خواهد کرد که حاوی خصوصیات زیر است:
 
توضیحات     خصوصیت 
 فقط دسترسی‌های تعریف شده در این قسمت، امکان پیمایش به این مسیر را خواهند داشت.   Only 
 تمامی دسترسی‌های تعریف شده، به جز دسترسی‌های تعریف شده در این قسمت، امکان پیمایش به این مسیر را خواهند داشت.   Except 
 در صورتیکه تقاضای غیر مجازی جهت پیمایش به این مسیر صادر شد، کاربر را به این مسیر هدایت می‌کند.   RedirectTo 

نکته ۱: مقدار دهی همزمان Only و Except مجاز نیست.
نکته ۲: ذکر چند دسترسی در هر یک از خصوصیت‌های Only و Except معنی «یا منطقی» دارد. مثلا در قطعه کد زیر، اگر دسترسی Admin باشد «یا» دسترسی CreateUser باشد امکان مشاهده پیمایش صفحه وجود خواهد داشت. 
{
    path: 'users/create',
    component: UserCreateComponent,
    canActivate: [PermissionGuard],
    data: {
        Permission: {
            Only: ['Admin', 'CreateUser'],
            RedirectTo: '403'
        } as IPermissionGuardModel
    },
    children: []
}

محافظت از المنت‌های صفحه

 جهت محافظت از المنت‌های موجود در صفحه، ماژول Ng2Permission دایرکتیوهایی را برای این منظور تدارک دیده است: 
 توضیحات  Input type     Directive 
 فقط دسترسی‌های تعریف شده در این دایرکتیو امکان مشاهده (به صورت پیش فرض) المنت را دارند.   Arrayy<string>   hasPermission 
 تمامی دسترسی‌های موجود، به جز دسترسی‌های تعریف شده در این دایرکتیو امکان مشاهده (به صورت پیش فرض) المنت را دارند.   Array<string>   exceptPermission
 استراتژی برخورد با المنت، هنگامیکه کاربر دسترسی به المنت دارد.   string | Function   onAuthorizedPermission 
 استراتژی برخورد با المنت، هنگامیکه کاربر دسترسی به المنت را ندارد.  string | Function 
 onUnauthorizedPermission 

در مثال زیر فقط کاربرانی که دسترسی DeleteUser را داشته باشند، امکان مشاهده دکمه حذف را خواهند داشت.
<button type="button" [hasPermission]="['DeleteUser']">
  <span aria-hidden="true"></span>
  Delete
</button>

در صورتیکه بیش از یک دسترسی مد نظر باشد، با کاما از هم جدا خواهند شد.

<button type="button" [hasPermission]="[ 'Admin', 'DeleteUser']">
  <span aria-hidden="true"></span>
  Delete
</button>
در مثال بالا درصورتیکه کاربر دسترسی Admin « یا » دسترسی DeleteUser را داشته باشد، امکان مشاهده (به صورت پیش فرض) المنت را خواهد داشت. مثال زیر یعنی تمامی کاربران با هر دسترسی امکان مشاهده المنت را خواهند داشت؛ بجز دسترسی GeustUser. 
<button type="button" [exceptPermission]="['GeustUser']">
  <span aria-hidden="true"></span>
  Delete
</button>
به صورت پیش فرض هنگامیکه کاربری دسترسی به المنتی را نداشته باشد، دایرکتیو، این المنت را مخفی خواهد کرد (با تنظیم استایل display به none). شاید شما بخواهید بجای مخفی/نمایش المنت، مثلا از فعال/غیرفعال کردن المنت استفاده کنید. برای این کار از خصوصیت onAuthorizedPermission و onUnauthorizedPermission به شکل زیر استفاده کنید:
<button type="button" 
  [hasPermission]="['GeustUser']"
  onAuthorizedPermission="enable"
  onUnauthorizedPermission="disable">
  <span aria-hidden="true"></span>
  Delete
</button>
تمامی استراتژی‌های از قبل تعریف شده و قابل استفاده برای خصوصیت onAuthorizedPermission و onUnauthorizedPermission به شرح زیر است:
 رفتار   مقدار 
 حذف خصوصیت disabled از المنت   enable 
افزودن خصوصیت disabled به المنت     disable 
 تنظیم استایل display به inherit   show 
 تنظیم استایل display به none   hide 

در صورتیکه هیچ‌کدام از این استراتژی‌ها، پاسخگوی نیاز شما جهت برخورد با المنت نبود، می‌توانید بجای ارسال یک رشته ثابت به خصوصیت onAuthorizedPermission و onUnauthorizedPermission، یک تابع را ارسال کنید تا عملی که می‌خواهید، بر روی المنت انجام دهد. به عنوان مثال می‌خواهیم در صورتیکه کاربر مجاز به استفاده از المنتی نبود، المنت را از طریق تنظیم استایل visibility به hidden، مخفی کنیم و در صورتیکه کاربر مجاز به استفاده از المنت بود، استایل visibility به inherit تنظیم شود. 
با این فرض، کدهای کامپوننت به شکل زیر:
@Component({
    selector: 'app-user-list',
    templateUrl: './user-list.component.html',
    styleUrls: ['./user-list.component.css']
})
export class UserListComponent {

  constructor() { }

  OnAuthorizedPermission(element: ElementRef) {
    element.nativeElement.style.visibility ="inherit";
  }

  OnUnauthorizedPermission(element: ElementRef) {
    element.nativeElement.style.visibility = "hidden";    
  }
}
و تگهای Html زیر: 
<button 
  [hasPermission]="['CreateUser']"
  [onAuthorizedPermission]="OnAuthorizedPermission"
  [onUnauthorizedPermission]="OnUnauthorizedPermission">
  <span aria-hidden="true"></span>
  Add New User
</button>
نکته:  هر تغییر در لیست دسترسی‌ها در لحظه سبب اجرای دوباره دایرکتیوها خواهد شد و تغییرات، در همان لحظه در لایه نمایش قابل مشاهده خواهند بود. 
مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت دوم
در قسمت اول در مورد ابزار انتخابی برای توسعه برنامه‌های Cross Platform صحبت کردیم. در این قسمت به آموزش نصب و راه اندازی محیط توسعه می‌پردازیم.

شما می‌توانید در هر یک از سیستم عامل‌های Mac - Windows - Linux و با هر یک از IDE‌های Visual Studio - Visual Studio for mac - Rider کار کنید. برای این که بتوانیم آموزش را کاملا عملی پیش ببریم و وارد جزئیات شویم، در عمل باید یکی را انتخاب و آموزش دهیم و آن Windows - Visual Studio است؛ اگرچه باقی تفاوت خیلی زیادی ندارند.

با توجه به این که کد نوشته شده برای UI و Logic برای هر سه پلتفرم Windows - Android - iOS یکی است و کدهای منحصر به هر پلتفرم، سهم اندکی از پروژه را تشکیل می‌دهد و همچنین تست برنامه برای Windows آسان‌تر و سریع‌تر بوده و امکانات بیشتری را دارد، توصیه اکید می‌کنم برنامه را روی نسخه Windows توسعه دهید و تست کنید و پس از انجام کارهای اصلی پروژه، آن را بر روی Android و iOS نیز تست کنید. این مورد شباهت به برنامه نویسی وب برای مرورگرها را دارد. خیلی از افراد، سایت را بر روی یک مرورگر مثل Chrome یا Firefox توسعه می‌دهند و در نهایت کار را بر روی مرورگرهای موبایل و IE - Edge - Safari و ... تست می‌کنند. همانطور که می‌شود در مرورگر Chrome هم Touch را تست کرد و هم سایزهای مختلف را، همین کارها را در تست نسخه ویندوزی نیز می‌توانید انجام دهید. در کنار این با توجه به رشد فروش تبلت‌های ویندوزی، برای خیلی از برنامه‌ها، ارائه نسخه ویندوزی می‌تواند مفید نیز باشد.

برای شروع بهتر است نسخه‌ای به روز از ویندوز 10 را داشته باشید، یا Pro یا Enterprise. برای بررسی، ابتدا Command Line را باز کنید و دستور ver (مخفف version) را اجرا کنید. چیزی مشابه مقدار
Microsoft Windows Version 10.0.17134.345
را مشاهده خواهید نمود که باید عدد پنج رقمی آن (در این مثال 17134) از 16299 کمتر نباشد. اگر فرض کنیم که فقط یک سیستم داریم که بدون سیستم عامل است، تا این جا یک ISO نصب ویندوز دانلود کرده‌ایم، به حجم 3.7 گیگ که بعد از نصب، 9.5 گیگ از فضای هارد را می‌گیرد. کمی حوصله به خرج دهید (!) و اگر می‌خواهید همه چیز را تمیز انجام دهید، با یک ویندوز تمیز شروع کنید!

آخرین نسخه پایدار ویژوال استودیو در زمان نگارش این مقاله، 2017 - 15.8.7 هست که ما نیاز به نصب Workload های زیر داریم:
Universal Windows Platform development 
Mobile development with .NET 
نصب این دو احتیاج به 5 گیگ دانلود و 14 گیگ فضای روی هارد را دارد که علاوه بر خود Visual Studio و محیط توسعه آن، موارد زیر را نیز برای شما نصب می‌کند:
 Android SDK - Android NDK - JDK (Java) - Windows SDK - iOS SDK
نکات مهم:
۱- اگر قبلا یکی از SDK‌های ذکر شده را دانلود کرده‌اید، لطفا بی خیال آن شوید! اجازه دهید تا ویژوال استودیو همه چیز را دانلود و نصب و کانفیگ کند. Android SDK، برای مثال، بالغ بر 70 گیگ فایل، برای 28 ورژن اندروید است که اگر یکی از آن‌ها را داشته‌اید که برای تست کد نویسی با Java و Android Studio جواب می‌داده، هیچ دلیلی ندارد دقیقا همان نسخه به درد Xamarin هم بخورد!
۲- ترجیحا نسخه Enterprise را نصب کنید.
۳- قسمت‌های عمده فایل‌های دانلودی از سرورهای مایکروسافت دانلود می‌شوند که محدودیتی برای کاربران ایرانی ندارد، ولی قسمت هایی نیز مستقیما از سرورهای گوگل دانلود می‌شوند که متاسفانه روی کاربرهای ایرانی بسته است. با توجه به این که ممکن است استفاده از روش‌های دور زدن تحریم مانند VPN باعث کندی سرعت اینترنت و دانلود شوند، توصیه می‌کنم که ابتدا "Universal Windows Platform development" را نصب کنید (زیرا تماما از سرورهای مایکروسافت دانلود می‌شود) و سپس مجدد Installer را باز کرده و "Mobile development with .NET" را انتخاب کنید و این بار از ابزارهای دور زدن تحریم استفاده کنید.
۴- در سمت راست گزینه‌های قابل نصب، تیک موارد "Google Android Emulator API Level 27" و "Intel Hardware Accelerated Execution Manager (HAXM) global install" را بردارید. در پستی جداگانه آپشن‌های متنوع Emulator‌های اندرویدی را بررسی خواهیم نمود.
۵- بهتر است Administrator سیستم خود باشید.

بعد از اتمام نصب باید Developer mode را فعال کنید که نحوه انجام آن در این لینک شرح داده شده است. به صورت خلاصه به Settings بروید، سپس Update & Security، سپس For developers و در نهایت انتخاب Developer mode از بین گزینه‌های موجود.
ضمن استفاده از ابزارهای دور زدن تحریم (فقط برای ساختن و بیلد کردن اولین پروژه)، ویژوال استودیو را به صورت Run as admin باز کنید و از منوی File > New > Project قسمت Cross-Platform برای CSharp، یک Mobile app Xamarin Forms بسازید که ضمن انگلیسی بودن نام پروژه و فاقد Space بودن آن، ترجیحا در فولدری باشد که مسیر آن فولدر نیز طولانی نباشد، Space و کارکترهای فارسی نیز نداشته باشد.

تنظیماتی که در پنجره New Cross Platform App هستند مناسب بوده و Ok را بزنید! اولین بیلد به علت نیاز به دانلود طول می‌کشد و در صورت بیلد شدن موفقیت آمیز پروژه شما دومین قسمت را با موفقیت طی کرده اید. در قسمت بعدی ساختار پروژه‌های Xamarin Forms را بررسی می‌کنیم و یک مثال ساده می‌نویسیم که لااقل روی ویندوز قابلیت تست را داشته باشد. دقت کنید که همان کد روی Android / iOS نیز کار می‌کند، ولی در پست هایی جداگانه باید در مورد راه اندازی Emulator‌های Android و iOS آموزش هایی را ببینید. در صورت وجود هر گونه مشکل یا سوال نیز در قسمت نظرات همین صفحه در خدمت شما عزیزان هستیم. 
مطالب
PowerShell 7.x - قسمت پنجم - اسکریپت بلاک و توابع
همانطور که در قسمت قبل اشاره شد، توابع نیز یکی از ویژگی‌های اصلی PowerShell هستند. قبل از بررسی بیشتر توابع بهتر است ابتدا با مفهوم script block آشنا شویم. script blocks به مجموعه‌ایی از دستورات گفته میشود که داخل یک بلاک قرار میگیرند. در واقع هر چیزی داخل {} یک script block محسوب میشود (البته به جز hash tables). به عنوان مثال در کد زیر از یک script block مخصوص، با نام فیلتر استفاده شده است که یک ورودی برای پارامتر FilterScript مربوط به دستور Where-Object میباشد. چیزی که این script block را متمایز میکند، خروجی آن است. به این معنا که خروجی آن باید یک مقدار بولین باشد: 
Get-Process | Where-Object { $_.Name -eq 'Dropbox' }
script blocks را به صورت مستقیم درون command line هم میتوانیم استفاده کنیم. به محض تایپ کردن } و زدن کلید enter، امکان نوشتن اسکریپت‌های چندخطی را درون ترمینال خواهیم داشت. در نهایت با بستن script block و زدن کلید enter، از بلاک خارج خواهیم شد: 
PS /Users/sirwanafifi/Desktop> $block = {
>> $newVar = 10
>> Write-Host $newVar
>> }
با اینکار یک بلاک از کد را داخل متغیری با اسم block ذخیره کرده‌ایم. برای فراخوانی این قطعه کد میتوانیم از یک عملگر مخصوص با نام invocation operator یا call operator استفاده کنیم: 
PS /Users/sirwanafifi/Desktop> & $block
یا حتی میتوانیم از Invoke-Command نیز برای اجرای بلاک استفاده کنیم. همچنین از عملگر & برای فراخوانی یک expression رشته‌ایی نیز میتوان استفاده کرد: 
PS /Users/sirwanafifi/Desktop> & "Get-Process"
البته این نکته را در نظر داشته باشید که & قادر به پارز کردن (parse) یک expression نیست. به عنوان مثال اجرای کد زیر با خطا مواجه خواهد شد (برای حل این مشکل میتوانید بجای آن از Invoke-Expression استفاده کنید که امکان پارز کردن پارامترها را نیز دارد):
PS /Users/sirwanafifi/Desktop> & "1 + 1"
or
PS /Users/sirwanafifi/Desktop> & "Get-Process -Name Slack"

توابع
در قسمت قبل با نحوه ایجاد توابع آشنا شدیم. به این نوع توابع، basic functions گفته میشود و ساده‌ترین نوع توابع در PowerShell هستند. همچنین خیلی محدود نیز میباشند؛ یکسری ورودی/خروجی دارند. برای کنترل بیشتر روی نحوه فراخوانی توابع (به عنوان مثال دریافت ورودی از pipeline و…) باید از advanced functions یا توابع پیشرفته استفاده کنیم. در واقع به محض استفاده از اتریبیوتی با نام [()CmdletBinding] تابع ما تبدیل به یک advanced function خواهد شد. منظور از دریافت ورودی از pipeline این است که بتوانیم خروجی دستورات را به تابع‌مان pipe کنیم اینکار در basic function امکانپذیر نیست: 
Function Add-Something {
    Write-Host "$_ World"
}

"Hello" | Add-Something
اما با کمک advanced functions میتوانیم چنین قابلیتی را داشته باشیم: 
Function Add-Something {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$Name
    )

    Write-Host "$Name World"
}

"Hello" | Add-Something
یکی دیگر از ویژگی‌های advanced functions امکان استفاده فلگ Verbose حین فراخوانی دستورات میباشد. به عنوان مثال قطعه کد زیر را در نظر بگیرید: 
$API_KEY = "...."

Function Read-WeatherData {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$CityName
    )

    $Url = "https://api.openweathermap.org/data/2.5/forecast?q=$CityName&cnt=40&appid=$API_KEY&units=metric"
    Try {
        Write-Verbose "Reading weather data for $CityName"
        $Response = Invoke-RestMethod -Uri $Url
        $Response.list | ForEach-Object {
            Write-Verbose "Processing $($_.dt_txt)"
            [PSCustomObject]@{
                City               = $Response.city.name
                DateTime           = [DateTime]::Parse($_.dt_txt)
                Temperature        = $_.main.temp
                Humidity           = $_.main.humidity
                Pressure           = $_.main.pressure
                WindSpeed          = $_.wind.speed
                WindDirection      = $_.wind.deg
                Cloudiness         = $_.clouds.all
                Weather            = $_.weather.main
                WeatherDescription = $_.weather.description
            }
        } | Where-Object { $_.DateTime.Date -eq (Get-Date).Date }
        Write-Verbose "Done processing $CityName"
    }
    Catch {
        Write-Error $_.Exception.Message
    }
}
کاری که تابع فوق انجام میدهد، دریافت دیتای پیش‌بینی وضعیت آب‌وهوای یک شهر است. در حالت عادی فراخوانی تابع فوق پیام‌های Verbose را نمایش نمیدهد. از آنجائیکه تابع فوق یک advanced function است، میتوانیم فلگ Verbose را نیز وارد کنیم. با اینکار به صورت صریح گفته‌ایم که پیام‌های از نوع Verbose را نیز نمایش دهد: 
Read-WeatherData -CityName "London" -Verbose
هر چند این مقدار را همانطور که در قسمت‌های قبلی عنوان شد میتوانیم تغییر دهیم که دیگر مجبور نباشیم با فراخوانی هر تابع، این فلگ را نیز ارسال کنیم. بیشتر دستورات native نیز قابلیت نمایش پیام‌های Verbose را با ارسال همین فلگ در اختیارمان قرار میدهند. بنابراین بهتر است برای امکان مشاهده جزئیات بیشتر حین فراخوانی توابع‌مان از Write-Verbose استفاده کنیم. در ادامه اجزای دیگر توابع را بررسی خواهیم کرد (بیشتر این اجزا درون یک script block نیز قابل استفاده هستند)

کنترل کامل بر روی ورودی‌های توابع
بر روی ورودی‌های یک تابع میتوانیم کنترل نسبتاً کاملی داشتیم باشیم. PowerShell یک مجموعه وسیع از قابلیت‌ها را برای هندل کردن پارامترها و همچنین اعتبارسنجی ورودی‌ها ارائه میدهد. به عنوان مثال میتوانیم یک پارامتر را mandatory کنیم یا اینکه امکان positional binding و غیره را تعیین کنیم. اتریبیوت Parameter در واقع یک وهله از System.Management.Automation.ParameterAttribute میباشد. میتوانید با نوشتن دستور زیر لیستی از خواصی را که میتوانید همراه با این اتریبیوت تعیین کنید، مشاهده کنید: 
PS /> [Parameter]::new()

ExperimentName                  :
ExperimentAction                : None
Position                        : -2147483648
ParameterSetName                : __AllParameterSets
Mandatory                       : False
ValueFromPipeline               : False
ValueFromPipelineByPropertyName : False
ValueFromRemainingArguments     : False
HelpMessage                     :
HelpMessageBaseName             :
HelpMessageResourceId           :
DontShow                        : False
TypeId                          : System.Management.Automation.ParameterAttribute
در ادامه یک مثال از نحوه هندل کردن ورودی‌های یک تابع را بررسی خواهیم کرد. تابع زیر یک لیست از URLها را از کاربر دریافت کرده و یک health check توسط دستور Test-Connection انجام میدهد. در کد زیر پارامتر Websites را با تعدادی اتریبیوت مزین کرده‌ایم. توسط اتریبیوت Parameter تعیین کرده‌ایم که ورودی الزامی است و همچنین مقدار آن میتواند از pipeline نیز دریافت شود. در ادامه توسط ValidatePattern یک عبارت باقاعده را برای بررسی صحیح بودن URL دریافتی نوشته‌ایم. از آنجائیکه ورودی از نوع آرایه‌ایی از string تعریف شده است، این تست برای هر آیتم از آرایه بررسی خواهد شد. برای پارامتر دوم یعنی Count نیز رنج مقداری را که کاربر وارد میکند، حداقل ۳ و حداکثر ۳ انتخاب کرده‌ایم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidatePattern('^www\..*')]
        [string[]]$Websites,
        [ValidateRange(1, 3)]
        [int]$Count = 3
    )
    $Results = @()
    $Websites | ForEach-Object {
        $Website = $_
        $Result = Test-Connection -ComputerName $Website -Count $Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Results += @{
            Website = $Website
            Result  = $ResultText
        }
        Write-Verbose "The result of pinging $Website is $ResultText"
    }
    $Results | ForEach-Object { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $Count }; } 
    }
}
یکی دیگر از اعتبارسنجی‌هایی که میتوانیم برای پارامترهای یک تابع انتخاب کنیم، ValidateScript است. توسط این اتریبیوت میتوانیم یک منطق سفارشی برای اعتبارسنجی مقادیر پارامترها بنویسیم. به عنوان مثال تابع فوق را به گونه‌ایی تغییر خواهیم داد که لیست وب‌سایت‌ها را از طریق یک فایل JSON دریافت کند. میخواهیم قبل از دریافت فایل مطمئن شویم که فایل، به صورت فیزیکی روی دیسک وجود دارد، در غیراینصورت باید یک خطا را به کاربر نمایش دهیم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                If (-Not ($_ | Test-Path) ) {
                    Throw "File or folder does not exist" 
                }
                If (-Not ($_ | Test-Path -PathType Leaf) ) {
                    Throw "The Path argument must be a file. Folder paths are not allowed."
                }
                If ($_ -NotMatch "(\.json)$") {
                    throw "The file specified in the path argument must be either of type json"
                }
                Return $true
            })]
        [Alias("src", "source", "file")]
        [System.IO.FileInfo]$Path,
        [int]$Count = 1
    )
    $Results = [System.Collections.ArrayList]@()
    $Urls = Get-Content -Path $Path | ConvertFrom-Json
    $Urls | ForEach-Object -Parallel {
        $Website = $_.url
        $Result = Test-Connection -ComputerName $Website -Count $using:Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Item = @{
            Website = $Website
            Result  = $ResultText
        }
        $null = ($using:Results).Add($Item)
    }
    
    $Results | ForEach-Object -Parallel { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $using:Count }; } 
    }
}
تابع Ping-Website را جهت بررسی فیچر جدیدی که همراه با دستور ForEach-Object استفاده میشود، تغییر داده‌ایم تا به صورت Parallel عمل کند؛ این قابلیت از نسخه ۷ به بعد به PowerShell اضافه شده است. از آنجائیکه این قابلیت باعث میشود script block مربوط به ForEach-Object درون یک context دیگر با نام runspace اجرا شود. در نتیجه برای دسترسی به متغیرهای بیرون از script block نیاز خواهیم داشت از یک متغیر خودکار تحت‌عنوان using قبل از نام متغیر و بعد از علامت $ استفاده کنیم. همچنین آرایه مثال قبل را نیز به ArrayList تغییر داده‌ایم. زیرا در حالت قبلی امکان تغییر سایز یک آرایه با سایز ثابت را نخواهیم داشت. نکته دیگری که در مورد کد فوق میتوان به آن توجه کرد، نال کردن خروجی متد Add مربوط به آرایه‌ی Results است. همانطور که در قسمت قبل توضیح دادیم، از این تکنیک برای suppress کردن خروجی استفاده میکنیم و چون در اینجا خروجی متد Add یک عدد میباشد، با تکنیک فوق، خروجی را دیگر درون کنسول مشاهده نخواهیم کرد. توسط اتریبیوت Alias نیز نام‌های دیگری را که میتوان برای پارامتر Path حین فراخوانی تابع استفاده کرد، تعیین کرده‌ایم. لیست کامل اتریبیوت‌هایی را که میتوان برای پارامترهای یک تابع تعیین کرد، میتوانید در مستندات PowerShell ببینید. 
نکته: اگر تابع فوق را همراه با فلگ Verbose فراخوانی کنیم، لاگ‌های موردنظر را درون کنسول مشاهده نخواهیم کرد؛ زیرا همانطور که اشاره شد script block درون یک context جدا اجرا میشود و باید متغیرهای خودکار مربوط به Output را مجدداً مقداردهی کنیم:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    # As before
    $Urls | ForEach-Object -Parallel {
        $DebugPreference = $using:DebugPreference 
        $VerbosePreference = $using:VerbosePreference 
        $InformationPreference = $using:InformationPreference 
        
        # As before
    }
    
    # As before
}

قابلیت تعریف بلاک‌ها/توابع، به صورت تودرتو  
درون توابع و script block امکان نوشتن بلاک‌های تودرتو را نیز داریم:
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }
    $someVariable = 20

    & $doSomeWork
}
خروجی بلاک فوق  Some variable value: 20 خواهد بود؛ زیرا قبل از فراخوانی doSomeWork مقدار متغیر عددی someVariable را به ۲۰ تغییر داده‌ایم. برای script blocks این امکان را داریم که دقیقاً در همان جایی که بلاک را تعریف میکنیم، یک snapshot تهیه کنیم. در اینحالت خروجی، مقدار Some variable value: 10 خواهد شد: 
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }.GetNewClosure()
    $someVariable = 20

    & $doSomeWork
}
یکسری بلاک‌های ویژه نیز درون توابع و script blockها میتوانیم بنویسیم که اصطلاحاً به name blocks معروف هستند:
begin
process
end
dynamicparam
درون یک تابع اگر هیچکدام از بلاک‌های فوق استفاده نشود، به صورت پیش‌فرض بدنه تابع، درون بلاک end قرار خواهد گرفت. بلاک begin قبل از شروع pipeline اجرا میشود. process به ازای هر آیتم pipe شده اجرا خواهد شد. end نیز در پایان اجرا میشود. به عنوان مثال تابع زیر را در نظر بگیرید:
function Show-Pipeline {
    begin { 
        Write-Host "Pipeline start" 
    }
    process { 
        Write-Host  "Pipeline process $_" 
    }
    end { 
        Write-Host  "Pipeline end $_" 
    }
}
در ادامه یکسری آیتم را به ورودی این تابع pipe خواهیم کرد:
PS /> 1..2 | Show-Pipeline                                   
Pipeline start 
Pipeline process 1
Pipeline process 2
Pipeline end 2
همانطور که مشاهده میکنید، به ازای هر آیتم pipe شده، یکبار بلاک process اجرا شده است. همچنین برای دسترسی به مقدار آیتم pipe شده نیز از متغیر خودکار _$ استفاده کرده‌ایم (PSItem$ نیز به همین متغیر اشاره دارد).

با توجه به توضیحات named blockهای فوق، اکنون اگر بخواهیم نسخه اول تابع Ping-Website را با pipe کردن یک آرایه فراخوانی کنیم، خروجی که در کنسول نمایش داده خواهد شد، تنها آیتم آخر از آرایه خواهد بود:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website                 

Website       Result  Number Of Attempts
-------       ------  ------------------
www.yahoo.com Success                  3
دلیل آن نیز این است که به صورت صریح کدها را درون بلاک process ننوشته بودیم. همانطور که عنوان شد، در حالت پیش‌فرض، بدنه توابع درون بلاک end قرار خواهند گرفت و تنها یکبار اجرا خواهند شد. بنابراین:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    process {
        # As before
    }
}
اینبار اگر تابع را مجدداً فراخوانی کنیم، خروجی مطلوب را نمایش خواهد داد:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website

Website        Result  Number Of Attempts
-------        ------  ------------------
www.google.com Success                  3
www.yahoo.com  Success                  3

بلاک dynamicparam
از این بلاک برای تعریف پارامترهای داینامیک که به صورت on the fly نیاز هست ایجاد شوند، استفاده میشود. برای درک بهتر آن فرض کنید میخواهیم تابعی را بنویسیم که امکان خواندن یک فایل CSV را به ما میدهد. تا اینجای کار توسط Import-CSV به یک خط دستور قابل انجام است. اما فرض کنید میخواهیم به کاربر این امکان را بدهیم که یک ستون موردنظر از فایل را مشاهده کند. همچنین میخواهیم یک اعتبارسنجی هم روی نام ستونی که کاربر قرار است وارد کند نیز داشته باشیم. به عنوان مثال یک فایل CSV با ستون‌های name, lname, age داریم و کاربر میخواهد تنها ستون اول یک name را واکشی کند:
PS /> Read-Csv ./users.csv -Columns name
برای اینکار میتوانیم با کمک dynamic param یک پارامتر را در زمان اجرا ایجاده کرده و مقادیری را که کاربر برای ستون‌ها مجاز است وارد کند، براساس هدر فایل CSV تنظیم کنیم:
using namespace System.Management.Automation
Function Read-Csv {
    Param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path
    )
    DynamicParam {
        $firstLine = Get-Content $Path | Select-Object -First 1
        [String[]]$headers = $firstLine -split ', '
        $parameters = [RuntimeDefinedParameterDictionary]::new()
        $parameter = [RuntimeDefinedParameter]::new(
            'Columns', [String[]], [Attribute[]]@(
                [Parameter]@{ Mandatory = $false; Position = 1 }
                [ValidateSet]::new($headers)
            )
        )
        $parameters.Add($parameter.Name, $parameter) 
        Return $parameters
    }
    Begin {
        $csvContent = Import-Csv $Path
        If ($PSBoundParameters.ContainsKey('Columns')) {
            $columns = $PSBoundParameters['Columns']
            $csvContent | Select-Object -Property $columns
        }
        Else {
            $csvContent
        }
    }
}
درون کنسول PowerShell هم یک IntelliSense برای مقادیر مجاز نمایش داده خواهد شد:

مطالب
آشنایی با JS Link در شیرپوینت 2013
شیرپوینت 2013 تغییرات محسوسی در ظاهر خود و در واسط کاربریش اعمال کرده است . یکی از این تغییرات JS Link است که به کاربر امکان مدیریت روی Render کردن موجودیت‌های روی صفحه مانند فیلد‌ها ، آیتم‌ها و وب پارت‌ها را به کمک جاوااسکریپت می‌دهد. در این پست نحوه استفاده از این ویژگی جدید را بیان می‌کنم .

وارد سایت شده و یک لیست ایجاد کنید . (در اینجا از Custom List استفاده می‌کنیم .)


و ان را داده آمایی می‌کنیم . هدف این است که بر مبنای عدد موجود در لیست ، رنگ زمینه آن ایتم تغییر کند .


یک فایل جاوااسکریپت ایجاد کنید و کد زیر را در آن ذخیره کنید (از اینجا دانلود کنید) :
(function () {
    var itemCtx = {};
    itemCtx.Templates = {};
    itemCtx.Templates.Header = "<div><b title=\"اطلاعات فیلم ها\">Movie Data</b></div><ul>";
    itemCtx.Templates.Item = MyOverrideTemplate;
    itemCtx.Templates.Footer = "</ul>";
    itemCtx.BaseViewID = 1;
    itemCtx.ListTemplateType = 100; 
//For Generic List (More : http://msdn.microsoft.com/en-us/library/ms462947(v=office.12).aspx)

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(itemCtx);

})();



function GT(val , index)
{
    // example of val : 60 %
    var temp = val.split(' ')[0];
    var v = Number(temp);
    return v > index;
}

function LT(val, index) {
    var temp = val.split(' ')[0];
    var v = Number(temp);
    return v < index;
}

function EQ(val, index) {
    var temp = val.split(' ')[0];
    var v = Number(temp);
    return v == index;
}



function MyOverrideTemplate(ctx) {

   

    if (LT(ctx.CurrentItem.PopularityPercent ,25))
    {
        return "<li title='خیلی کم بازدید' style='color:white;background-color: red;width: 300px;height: 24px;'>" +
            ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>";
    }
    else
    if (LT(ctx.CurrentItem.PopularityPercent ,50))
    {
        return "<li title='کم بازدید'  style='color:maroon;background-color: #ffcc00;width: 300px;height: 24px;'>" +
            ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>";
    }
    else
    if (LT(ctx.CurrentItem.PopularityPercent ,75))
    {
        return "<li title='بازدید معمولی'  style='color:#ffcc00;background-color: maroon;width: 300px;height: 24px;'>" +
            ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>";
    }
    else
    if (LT(ctx.CurrentItem.PopularityPercent ,95))
    {
        return "<li title='پر بازدید'  style='color:yellow;background-color: blue;width: 300px;height: 24px;'>" +
            ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>";
    }
    else
        if (EQ(ctx.CurrentItem.PopularityPercent, 100)) {
            return "<li  title='بالاترین بازدید'  style='color:black;background-color: green;width: 300px;height: 24px;'>" +
                ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>";
        }
        else {
            return "<li title='نامعلوم'  style='color:navy;background-color: yellow;width: 300px;height: 24px;'>" +
                ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>";
        }
}

حال وارد Site Setting شده و وارد Master Pages شوید

   
 فایل جاوااسکریپت فوق را از قسمت ریبون و تب Document آپلود کنید و منتظر بمانید تا پس از بارگذاری پنجره ویژگی‌های فایل نمایش داده شود



هنگلام پر کردن فیلد‌ها به این نکات دقت کنید :
در قسمت Content Type گزینه جدیدی که در این نسخه از شیرپوینت اضافه شده یعنی JavaScript Display Template را انتخاب کنید


در قسمت Target Control Type یکی از سه گزینه view یا Form ویا Field باید انتخاب شوند که در اینجا View را انتخاب میکنیم
standalone را روی override تنظیم می‌کنیم . همچنین گزینه Target Scope را که مسیر اعمال فایل است به رووت تنظیم می‌کنیم
در نهایت شناسه List template را به توجه به لیست مورد نظر که در اینجا Custome list است مقدار دهی می‌کنیم . (بیشتر )
سپس اطلاعات را ذخیره می‌کنیم .

برای آزمایش این تغییرات بک صفحه می‌سازیم و وب پارت لیست مورد نظر را به آن اضافه می‌کنیم


سپس وارد تنظیمات وب پارت شده و وارد قسمت Miscellaneous می‌شویم


در قسمت JS Link مسیر فایل خود را به صور نسبی وارد کنید

~site/_catalogs/masterpage/MyJsLinkSample.js
و نتیجه نهایی :

در صورت بروز Exception در فایل جاوااسکریپت ، خطا به صورت زیر نمایش داده خواهد شد :


   
مطالب
بررسی Bad code smell ها: زنجیره پیام یا Message chain
این کد بد بو در دسته «جلوگیری کنندگان از تغییر» قرار می‌گیرد. معمولا زمانیکه فراخوانی‌هایی مانند تکه کد زیر را در بخشی از کد مشاهده کردید، با چنین کد بد بویی مواجه هستید.  
MethodA().MethodB().MethodC();
فراخوانی هر یک از این متدها در خطی مجزا از کد نیز تشکیل دهنده‌ی این الگوی بد است. استفاده کننده‌ی از این زنجیره پیام، برای استفاده‌ی درست از آن، باید در جریان هریک از حلقه‌های زنجیره و ترتیب فراخوانی آنها باشد. در صورتیکه هر یک از حلقه‌های زنجیره تغییری داشتند، استفاده کننده  نیز باید تغییر کنند. 
به طور مثال:
public class RepresentativeEmployeeQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
} 
public class RepresentativeQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
} 
public class CustomerQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
}
public static class Programm 
{ 
    static void Main(string[] args) 
    { 
        var customer = new CustomerQuery().GetById(1); 
        var representativeId = customer.RepresentativeId; 
        var representative = new RepresentativeQuery().GetById(representativeId); 
        var managerId = representative.ManagerId; 
        var manager  = new RepresentativeEmployeeQuery().GetById(managerId); 
        var managerName = manager.FullName; 
    } 
}
  • کلاس CustomerQuery پرس و جوهای مربوط به مشتری را مدیریت می‌کند.
  • کلاس RepresentativeQuery پرس و جوهای مربوط به نمایندگی را مدیریت می‌کند .
  • کلاس RepresentativeEmployeeQuery پرس و جوهای مربوط به کارمندان نمایندگی را مدیریت می‌کند.
در مثال ذکر شده می‌خواهیم نام مدیریت نمایندگی ای را که یک مشتری از آن خرید کرده است، بدانیم. صرفا جهت نمایش مثال، این کار را در متد main انجام داده‌ایم.  
مشاهده می‌کنید که زنجیره‌ای از پیام‌ها از CustomerQuery تا پایین‌ترین قسمت یعنی RepresentativeEmployeeQuery ارسال شده است. هر یک از مراحل زنجیره بخشی از منطق دریافت نام را مدیریت می‌کنند. مانند دریافت مشتری، دریافت نمایندگی آن و دریافت مدیریت نمایندگی. 

مشکلات این کد بد بو 

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


روش‌ها اصلاح این کد بد بو 

روش‌های اصلاح این کد بد بو حول مخفی کردن زنجیره فراخوانی‌ها هستند. اما در مواردی مانند مثال ذکر شده در این مطلب امکان ادغام یک یا چند متد نیز وجود دارد. به شرطی که این کار ناقض اصل Single responsibility یا دیگر اصول شیء گرایی نباشد. دو نمونه از روش‌های اصلاح این کد بد بو به صورت زیر هستند: 
  • مخفی کردن زنجیره فراخوانی و مستقل سازی استفاده کننده از زنجیره فرخوانی (Hide delegate)  
  • ادغام بخشی از متدها در زنجیره فراخوانی و از بین بردن زنجیره فراخوانی 


چه کدهایی Message Chain نیستند؟ 

معمولا کدهایی که از الگوی domain specific language پیروی می‌کنند، ممکن است شباهت بسیاری به مثال مطرح شده داشته باشند. اما از نظر مفهومی با الگوی مطرح شده متفاوت هستند؛ به این صورت که در کد بد بوی زنجیره پیام‌ها، استفاده کننده از متد نیاز به دانستن تمامی حلقه‌ها و ترتیب آنها را دارد، ولی در domain specific language‌ها معمولا نحوه استفاده از متدها به صورت شبه زبانی گویا هدایت شده و معمولا ترتیب به صورت مخفی مدیریت می‌شود.