نظرات مطالب
بررسی متد های یک طرفه در WCF
زمانی که ارتباط بین سرور و کلاینت به هر دلیلی قطع شود یا به بنا به دلایلی طی انجام عملیات سمت سرور Exception رخ دهد، وضعیت ChannelFactory برای آن سرویس به حالت Faulted  تغییر پیدا می‌کند. در نتیجه دیگر امکان استفاده از این کانال ارتباطی میسر نیست و باید یک کانال ارتباطی جدید تهیه نمایید. اما برای اینکه بعد از متوقف سازی عملیات سمت سرور حافظه مصرفی دوباره بازگردانده شود باید از مفهوم UnitOfWork بهره جست البته با مقداری تغییرات برای همگام سازی با درخواست‌های WCF. روش مورد استفاده من به صورت زیر است(با فرض اینکه از EntityFramework به عنوان ORM استفاده میکنید):
» ابتدا یک Extension برای OperationContext تعریف می‌کنیم(با فرض اینکه IDatabaseContext نماینده کلاس DbContext پروژه است):
private class OperationContainerExtension : IExtension<OperationContext>
        {
            public OperationContainerExtension( IDatabaseContext dbContext, string contextKey )
            {
                this.CurrentDbContext = dbContext;
                this.ContextKey = contextKey;
            }

            public IDatabaseContext CurrentDbContext
            {
                get;
                private set;
            }

            public string ContextKey
            {
                get;
                private set;
            }

            public void Attach( OperationContext owner )
            {
            }

            public void Detach( OperationContext owner )
            {
            }
        }
بعد در هنگام نمونه سازی از UnitOfWork در لایه سرویس Extension بالا به OperationContext جاری اضافه خواهد شد(اگر از IOC Container خاصی استفاده می‌کنید باید کد عملیات وهله سازی کلاس UnitOfWork را با کد‌های زیر مزین کنید):
 if ( OperationContext.Current != null )
            {
                OperationContext.Current.Extensions.Add( new OperationContainerExtension( dbContext , CONTEXTKEY ) );
                OperationContext.Current.OperationCompleted += CurrentOperationContext_OperationCompleted;
                 OperationContext.Current.Channel.Faulted += Channel_Faulted; 
             }
می بینید که برای رویداد OperationCompleted و رویداد Fault در Channel نیز کد‌های Dispose کردن UnitOfWork و همچنین DbContext را قرار دادم. به صورت زیر:
void Channel_Faulted( object sender, EventArgs e )
        {
            IDatabaseContext dbContext = GetDbContext();
            if ( dbContext != null )
            {
                dbContext.Dispose();
                GC.Collect();
            }
        }

        private void CurrentOperationContext_OperationCompleted( object sender, EventArgs e )
        {
            IDatabaseContext dbContext = GetDbContext();
            if ( dbContext != null )
            {
                dbContext.Dispose();
                GC.Collect();
            }
        }
اگر به کد‌های بالا دقت کنید متد GetDbContext نوشته شده برای به دست آوردن DbContext جاری در Session مربوطه است. کد آن نیز به صورت زیر می‌باشد
protected override IDatabaseContext GetDbContext()
        {
            if ( OperationContext.Current != null )
            {
                var operationContainerExtension = OperationContext.Current.Extensions.OfType<OperationContainerExtension>().FirstOrDefault( e => e.ContextKey == CONTEXTKEY );
                if ( operationContainerExtension != null )
                {
                    return operationContainerExtension.CurrentDbContext;
                }
                return staticDbContext;
            }
            else
                return staticDbContext;
        }
نکته آخر هم این است که CONTEXTKEY صرفا یک فیلد از نوع string ولی با مقدار Guid برای به دست آوردن Extension مربوطه می‌باشد و تعریف آن در سازنده کلاس خواهد بود.
private string CONTEXTKEY = Guid.NewGuid().ToString();
در این صورت به طور قطع تمام منابع مورد استفاده سرویس‌های سمت سرور بعد از اتمام عملیات یا حتی وقوع هر خطا آزاد خواهند شد. اما اگر NHibernate را به عنوان ORM ترجیح می‌دهید به جای  IDatabaseContext باید از ISession استفاده نمایید.
مطالب
React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1
برای کار با React، نیاز است با ES6 آشنایی داشته باشید که در این سایت، یک سری کامل بررسی مقدمات آن‌را پیشتر مرور کرده‌ایم. علاوه بر توصیه‌ی مطالعه‌ی این سری (اینکار الزامی است)، در این قسمت خلاصه‌ی بسیار سریع و کاربردی آن‌را که بیشتر در برنامه‌های مبتنی بر React مورد استفاده قرار می‌گیرند، با هم مرور خواهیم کرد. در قسمت‌های بعدی، اهمیت ذکر این خلاصه بیشتر مشخص می‌شود.

برای بررسی ویژگی‌های جاوا اسکریپت مدرن، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-02
> cd sample-02
> npm start
سپس تمام کدهای داخل index.js را نیز حذف می‌کنیم. اکنون تمام کدهای خالص جاوا اسکریپتی خود را داخل این فایل خواهیم نوشت.
به علاوه چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود می‌توانید مشاهده کنید (فشردن دکمه‌ی F12).


var، let و const

در اکثر زبان‌های برنامه نویسی، متغیرها در محدوده‌ی دید قطعه کدی که تعریف شده‌اند (scope)، قابل دسترسی هستند. برای نمونه محتوای فایل index.js پروژه را به صورت زیر تغییر داده و با فرض اجرای دستور npm start، خروجی آن‌را می‌توان در کنسول مرورگر مشاهده کرد.
function sayHello() {
  for (var i = 0; i < 5; i++) {
    console.log(i);
  }
  console.log(i);
}

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


در آخرین پیمایش حلقه، i مساوی 5 شده و از حلقه خارج می‌شود. اما چون در اینجا برای تعریف متغیر از واژه‌ی کلیدی var استفاده شده‌است، محدوده‌ی دید آن به کل تابعی که در آن تعریف شده‌است، بسط پیدا می‌کند. به همین جهت در این خروجی، عدد 5 را نیز مشاهده می‌کند که حاصل دسترسی به i، خارج از حلقه‌است.
برای یک دست سازی این رفتار با سایر زبان‌های برنامه نویسی، در ES6، واژه‌ی کلیدی جدیدی به نام let تعریف شده‌است که میدان دید متغیر را به قطعه کدی که در آن تعریف شده‌است، محدود می‌کند. اکنون اگر در حلقه‌ی فوق بجای var از let استفاده شود، یک چنین خطایی در مرورگر ظاهر خواهد شد که عنوان می‌کند، i استفاده شده‌ی در خارج از حلقه، تعریف نشده‌است.
./src/index.js
  Line 14:15:  'i' is not defined  no-undef

Search for the keywords to learn more about each error.

علاوه بر let، واژه‌ی کلیدی جدید const نیز به ES6 اضافه شده‌است که از آن برای تعریف ثوابت استفاده می‌شود. constها نیز همانند let، میدان دید محدود شده‌ای به قطعه کد تعریف شده‌ی در آن دارند؛ اما قابلیت انتساب مجدد را ندارند:
 const x = 1;
x = 2; // Attempting to override 'x' which is a constant.
اگر یک چنین قطعه کدی را اجرا کنیم، خطای x is const را در مرورگر می‌توان مشاهده کرد.

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


اشیاء در جاوا اسکریپت

اشیاء در جاوا اسکریپت به صورت مجموعه‌ای از key/value‌ها تعریف می‌شوند:
const person = {
  name: "User 1",
  walk: function() {}, // method
  talk() {} // concise method
};
در اینجا امکان تعریف یک تابع نیز وجود دارد که چون درون یک شیء قرار می‌گیرد، اینبار «متد» نامیده می‌شود. همچنین در ES6 می‌توان این متدها را به صورت معمولی، مانند متد talk نیز تعریف کرد که به آن‌ها concise method می‌گویند. بنابراین نحوه‌ی تعریف فوق را به نحو زیر نیز می‌توان خلاصه کرد:
const person = {
  name: "User 1",
  walk() {},
  talk() {}
};
پس از تعریف این شیء، روش دسترسی به اجزای آن به صورت زیر است:
person.talk();
person.name = "User 3";
person["name"] = "User 2";
به دو مورد اول، روش dot notation می‌گویند که از همان ابتدا دقیقا مشخص است کدامیک از خواص و متدهای شیء تعریف شده، مورد استفاده قرار می‌گیرند.
مورد آخر همان روش استفاده از key/valueها است که اساس اشیاء جاوا اسکریپتی را تشکیل می‌دهد. البته از این روش فقط زمانی استفاده کنید که قرار است یکسری کار پویا صورت گیرند (مقدار key به صورت متغیر دریافت شود) و از ابتدا مشخص نیست که کدام خاصیت یا متد قرار است تعریف و استفاده شود:
const targetMember = "name";
person[targetMember] = "User 2";


واژه‌ی کلیدی this در جاوا اسکریپت

از واژه‌ی کلیدی this، در قسمت‌های بعدی زیاد استفاده خواهیم کرد. به همین جهت نیاز است تفاوت‌های اساسی آن‌را با سایر زبان‌های برنامه نویسی بررسی کنیم.
همان شیء person را که پیشتر تعریف کردیم درنظر بگیرید. در متد walk آن، مقدار this را لاگ می‌کنیم:
const person = {
  name: "User 1",
  walk() {
    console.log(this);
  },
  talk() {}
};

person.walk();
خروجی این قطعه، به صورت زیر است:


شیء this در جاوا اسکریپت، همانند سایر زبان‌های برنامه نویسی مانند سی‌شارپ و یا جاوا رفتار نمی‌کند. در سایر زبان‌های نامبرده شده، this همواره ارجاعی را به وهله‌ای از شیء جاری، باز می‌گرداند؛ دقیقا همانند تصویری که در بالا مشاهده می‌کنید. در اینجا نیز this جاوا اسکریپتی لاگ شده، ارجاعی را به وهله‌ی جاری شیء person، بازگشت داده‌است. اما مشکل اینجا است که this در جاوا اسکریپت، همیشه به این صورت رفتار نمی‌کند!
برای نمونه در ادامه یک ثابت را به نام walk تعریف کرده و آن‌را به person.walk مقدار دهی می‌کنیم:
const walk = person.walk;
console.log(walk);
دقت داشته باشید که در اینجا از () استفاده نشده‌است (متد walk اجرا نشده‌است). یعنی صرفا «ارجاعی» از متد walk شیء person را به ثابت walk نسبت داده‌ایم. بنابراین اکنون ثابت walk نیز یک function است که حاصل console.log آن به صورت زیر است:


سؤال: اکنون اگر این function را با فراخوانی ()walk اجرا کنیم، چه خروجی را می‌توان مشاهده کرد؟


اینبار this لاگ شده، به شیء person اشاره نمی‌کند و شیء استاندارد window مرورگر را بازگشت داده‌است!
اگر یک function به صورت متدی از یک شیء فراخوانی شود، مقدار this همواره اشاره‌گری به وهله‌ای از آن شیء خواهد بود. اما اگر این تابع به صورت متکی به خود و به صورت یک function و نه متد یک شیء، فراخوانی شود، اینبار this، شیء سراسری جاوا اسکریپت یا همان شیء window را بازگشت می‌دهد.

یک نکته: اگر strict mode جاوا اسکریپت را در پروژه‌ی جاری فعال کنیم، بجای شیء window، مقدار undefined را در خروجی فوق شاهد خواهیم بود.


اتصال مجدد this به شیء اصلی در جاوا اسکریپت

تا اینجا دریافتیم که اگر یک function را به صورت متکی به خود و نه جزئی از یک شیء فراخوانی کنیم، شیء this در این حالت به شیء window سراسری مرورگر اشاره می‌کند و اگر strict mode فعال باشد، فقط undefined را بازگشت می‌هد. اکنون می‌خواهیم بررسی کنیم که چگونه می‌توان این مشکل را برطرف کرد؛ یعنی صرف‌نظر از نحوه‌ی فراخوانی متدها یا تابع‌ها، this همواره ارجاعی را به شیء person بازگشت دهد.
در جاوا اسکریپت، تابع‌ها نیز شیء هستند. برای مثال person.walk نوشته شده نیز یک شیء است. برای اثبات ساده‌ی آن فقط یک دات را پس از person.walk قرار دهید:


همانطور که مشاهده می‌کنید، شیء person.walk مانند تمام اشیاء دیگر جاوا اسکریپت، به همراه متد bind نیز هست. کار آن، انقیاد یک تابع، به یک شیء است. یعنی هرچیزی را که به عنوان آرگومان آن، به آن ارسال کنیم، به عنوان مقدار شیء this درنظر می‌گیرد:
const walk2 = person.walk.bind(person);
console.log(walk2);
walk2();
در اینجا متد bind، یک وهله‌ی جدید از person.walk را بازگشت می‌دهد که در آن شیء person را به عنوان شیء this، تنظیم کرده‌است. به همین جهت اینبار فراخوانی walk2 که به شیء person متصل شده‌است، به this صحیحی بجای window سراسری اشاره می‌کند. از این روش در برنامه‌های مبتنی بر React زیاد استفاده می‌شود.


Arrow functions

تابع زیر را درنظر بگیرید که به یک ثابت انتساب داده شده‌است:
const square = function(number) {
  return number * number;
};
در ES6، روش ساده‌تر و تمیزتری برای این نوع تعاریف، ذیل ویژگی جدید Arrow functions اضافه شده‌است. برای تبدیل قطعه کد فوق به یک arrow function، ابتدا واژه‌ی کلیدی function را حذف می‌کنیم. سپس بین پارامتر تابع و {}، یک علامت <= (که به آن fat arrow هم می‌گویند!) قرار می‌دهیم:
const square2 = (number) => {
  return number * number;
};
اگر مانند اینجا تنها یک تک پارامتر وجود داشته باشد، می‌توان پرانتزهای ذکر شده را نیز حذف کرد:
const square2 = number => {
  return number * number;
};
و اگر این متد پارامتری نداشت، از () استفاده می‌شود.
در ادامه اگر بدنه‌ی این تابع، فقط حاوی یک return بود، می‌توان آن‌را به صورت زیر نیز خلاصه کرد (در اینجا {} به همراه واژه‌ی کلیدی return حذف می‌شوند):
const square3 = number => number * number;
console.log(square3(5));
این یک سطر ساده شده، دقیقا معادل اولین const square ای است که نوشتیم. نحوه‌ی فراخوانی آن نیز مانند قبل است.

اکنون مثال مفید دیگری را در مورد Arrow functions بررسی می‌کنیم که بیشتر شبیه به عبارات LINQ در #C است:
const jobs = [
  { id: 1, isActive: true },
  { id: 2, isActive: true },
  { id: 3, isActive: true },
  { id: 4, isActive: true },
  { id: 5, isActive: false }
];
در اینجا آرایه‌ای از اشیاء job را مشاهده می‌کنید که مورد آخر آن، فعال نیست. اکنون می‌خواهیم لیست کارهای فعال را گزارشگیری کنیم:
var activeJobs = jobs.filter(function(job) {
  return job.isActive;
});
متد filter در جاوا اسکریپت، بر روی تک تک عناصر آرایه‌ی jobs حرکت می‌کند. سپس هر job را به پارامتر متد ارسالی آن که predicate نام دارد، جهت دریافت یک خروجی true و یا false، ارائه می‌دهد. اگر خروجی این متد true باشد، آن job انتخاب خواهد شد و در لیست نهایی گزارش، ظاهر می‌شود.
در ادامه می‌توان این تابع را توسط arrow functions به صورت ساده‌تر زیر نیز نوشت:
var activeJobs2 = jobs.filter(job => job.isActive);
ابتدا واژه‌ی کلیدی function را حذف می‌کنیم. سپس چون یک تک پارامتر را دریافت می‌کند، نیازی به ذکر پرانتزهای آن نیز نیست. در ادامه چون یک تک return را داریم، { return }  را با یک <= جایگزین خواهیم کرد. در اینجا نیازی به ذکر سمی‌کالن انتهای return هم نیست. نوشتن یک چنین کدی تمیزتر و خواندن آن، ساده‌تر است.


ارتباط بین arrow functions و شیء this

نکته‌ی مهمی را که باید در مورد arrow functions دانست این است که شیء this را rebind نمی‌کنند (rebind: مقدار دهی مجدد؛ ریست کردن).
در مثال زیر، ابتدا شیء user با متد talk که در آن شیء this، لاگ شده، ایجاد شده و سپس این متد فراخوانی گردیده‌است:
const user = {
  name: "User 1",
  talk() {
    console.log(this);
  }
};
user.talk();
همانطور که انتظار می‌رود، this ای که در اینجا لاگ می‌شود، دقیقا ارجاعی را به وهله‌ی جاری شیء user دارد.
اکنون اگر متد لاگ کردن را داخل یک تایمر قرار دهیم چه اتفاقی رخ می‌دهد؟
const user = {
  name: "User 1",
  talk() {
    setTimeout(function() {
      console.log(this);
    }, 1000);
  }
};
user.talk();
متد setTimeout، متدی را که به عنوان آرگومان اول آن دریافت کرده، پس از 1 ثانیه اجرا می‌کند.
در این حالت خروجی console.log، مجددا همان شیء سراسری window مرورگر است و دیگر به وهله‌ی جاری شیء user اشاره نمی‌کند. علت اینجا است که پارامتر اول متد setTimeout که یک callback function نام دارد، جزئی از هیچ شیءای نیست. بنابراین دیگر مانند فراخوانی متد ()user.talk در مثال قبلی کار نمی‌کند؛ چون متکی به خود است. هر زمان که یک متد متکی به خود غیر وابسته‌ی به یک شیء را اجرا کنیم، به صورت پیش‌فرض this آن، به شیء window مرورگر اشاره می‌کند.

سؤال: چگونه می‌توان درون یک callback function متکی به خود، به this همان شیء user جاری دسترسی یافت؟
یک روش حل این مساله، ذخیره this شیء user در یک متغیر و سپس ارسال آن به متد متکی به خود setTimeout است:
const user2 = {
  name: "User 2",
  talk() {
    var self = this;
    setTimeout(function() {
      console.log(self);
    }, 1000);
  }
};
user2.talk();
این روشی است که سال‌ها است وجود دارد؛ با ارائه‌ی arrow functions، دیگر نیازی به اینکار نیست و می‌توان از روش زیر استفاده کرد:
const user3 = {
  name: "User 3",
  talk() {
    setTimeout(() => console.log(this), 1000);
  }
};
user3.talk();
در اینجا callback function را تبدیل به یک arrow function کرده‌ایم و چون arrow functions شیء this را rebind نمی‌کنند، یعنی شیء this را به ارث می‌برند. بنابراین console.log مثال فوق، دقیقا به this شیء user اشاره می‌کند و دوباره آن‌را مقدار دهی مجدد نمی‌کند و از همان نمونه‌ی موجود قطعه کد تعریف شده، استفاده خواهد کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-02.zip

در قسمت بعد نیز بررسی پیشنیازهای جاوا اسکریپتی شروع به کار با React را ادامه خواهیم داد.
مطالب
آشنایی با SharePoint Sandboxed Solutions
از Sandbox برای ساخت و انتشار وب پارت هایی استفاده می‌شود که نیاز به مداخله مدیریتی (administrative intervention) وجود ندارد. به این معنی که شما می‌توانید وب پارت خود را در محل شیرپوینت و هاست آن یا با استفاده از زیر ساخت‌های Cloud و دور از هاست آن ، بارگذاری کرده و انتشار دهید.
هر Solution که برای Sandbox ایجاد می‌شود ، در یک گالری خاص به نام Solution Gallery در Site Collection مربوطه ایجاد می‌شود و تمام این solution‌ها می‌توانند قعال یا غیر فعال شوند .


برای استفاده از این مزایا باید سرویس Microsoft SharePoint Foundation User Code Service فعال باشد .

 



استفاده از Sandbox Solution نیازمند 3 چیز است :

1 - User Code Service یا SPUCHostService.exe : این سرویس وظیفه مدیریت کدهای Sandbox روی سرور و میزبانی درخواست‌های Sandbox به سرور را دارد.

2 - Sandbox Worker Process یا SPUCWorkerProcess.exe : این سرویس برای مدیریت پردازش کدهای اجرایی Sandbox استفاده می‌شود خصوصا در مورارد امنیتی (فرض کنید درون کد حلقه بی نهایتی وجود داشته باشد. اگر بار اجرایی آن بر روی w3wp.exe باشد ، باعث متوقف شدن کارکرد تمام پروسه می‌شود. بار این مسئولیت بر دوش SPUCWorkerProcess.exe می‌باشد ) 

3 - Sandbox Worker Process Proxy یا SPUCWorkerProcessProxy.exe : این سرویس برای اجرای کدهای Client Object Model در sandbox استفاده می‌شود. 


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



با توجه به این توضیحات به دلیل استفاده از Sandbox می‌پردازیم :

1 - sandbox Solution‌ها امن هستند. 

2 - sandbox Solution‌ها می‌توانند مانیتور شوند. 

3 - sandbox Solution‌ها حداقل تاثیر را روی هم دارند. 

4 - sandbox Solution‌ها عملیات I/O کمی روی فایل‌های سیستمی دارند 

5 - sandbox Solution‌ها امکان debugging دارند 

6 - sandbox Solution‌ها امکان توسعه ، انتشار و به روز رسانی دارند 

7 - sandbox Solution‌ها امکان تعریف Policy برای CAS ایجاد می‌کند (Code Access Security برای قابل اطمینان ساخت کد ها) 

8 - sandbox Solution‌ها امکان اعتبار سنجی به کمک کلاس SPSolutionValidator را می‌دهد. 

9 - sandbox Solution‌ها امکان تعریف SLA های مختلف را می‌دهد. 



چه قسمت هایی توسط Sandbox Solution پشتیبانی می‌شود :

 

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

 اسفاده محدود از لیست‌ها ، کار با Web Template‌ها و ویژگی‌های آنها ، Content Type‌ها و فیلد‌ها ، ماژول‌ها و فایل‌ها ، وب پارت‌های مشتق شده از کلاس WebPart ، برخی Event Receiver‌ها ، بعضی از Custom Action و چرخه‌های کاری . و از مواردی که نمی‌توان در sandbox استفاده کرد می‌توان به ویژگی‌های موجود در Web Application ، ویژگی‌های موجود در Farm و Timer job ، استفاده از عملیات I/O و استفاده از ADO و برخی کلاس‌های دیگر در دات نت اشاره کرد .
مدیران سایت می‌توانند Solution‌ها را مونیتور کنند و برای Resource Point هایی که در یک روز مصرف می‌کنند Quota تعریف کنند .پیش فرض این مقدار 300 Resource Point در روز است .
 هر solution در هنگام deploy شدن جهت معتبر بودن چک می‌شود و سپس منتشر شود. در صورتی که Valid نباشد ، خطایی به برنامه نویس نشان می‌دهد.
 در انتها اشاره ای هم به استفاده از STSADM یا Powershell برای deploy کردن solution می‌کنم که توسط دستورات زیر قابل اجراست :
 
Add-SPSolution c:\code\SharePointProject2\bin\debug\SharePointProject2.wsp
stsadm –o addsolution –name SharePointProject2.wsp
همانطور که پسوند فایل‌های solution را مشاهده می‌کند wsp هستند که اگر آنها را به cab تغییر نام دهید میتوانید محتویات آنها را مشاهده کنید
 
مطالب
نحوه استخراج آیکون‌های یک قلم در WPF
مطلب «نحوه نمایش تمام آیکون‌های تعریف شده در یک قلم در WPF» را در نظر بگیرید. سؤال: اگر در یک برنامه تنها به تعدادی از این آیکون‌ها یا گلیف‌ها نیاز بود آیا می‌توان این‌ها را به صورت مجزا استخراج و استفاده کرد؟
پاسخ: بلی. همان کلاس  FontFamily موجود در اسمبلی PresentationCore.dll، امکان تبدیل یک گلیف را به معادل هندسی آن نیز دارد. در ادامه کدهای آن‌را مرور خواهیم کرد:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Media;
using CrMap.Models;

namespace CrMap.ViewModels
{
    public class CrMapViewModel
    {
        public IList<Symbol> Symbols { set; get; }
        public int GridRows { set; get; }
        public int GridCols { set; get; }

        public CrMapViewModel()
        {
            fillDataSource();
        }

        private void fillDataSource()
        {
            Symbols = new List<Symbol>();
            GridCols = 15;

            var fontFamily = new FontFamily(new Uri("pack://application:,,,/"), "/Fonts/#whhglyphs");

            GlyphTypeface glyph = null;
            Typeface glyphTypeface = null;
            foreach (var typeface in fontFamily.GetTypefaces())
            {
                if (typeface.TryGetGlyphTypeface(out glyph) && (glyph != null))
                {
                    glyphTypeface = typeface;
                    break;
                }
            }

            if (glyph == null)
                throw new InvalidOperationException("Couldn't find a GlyphTypeface.");

            GridRows = (glyph.CharacterToGlyphMap.Count / GridCols) + 1;

            foreach (var item in glyph.CharacterToGlyphMap)
            {
                var index = item.Key;
                Symbols.Add(new Symbol
                {
                    Character = Convert.ToChar(index),
                    CharacterCode = string.Format("&#x{0:X}", index)
                });

                saveToFile(glyphTypeface, index);
            }
        }

        private static void saveToFile(Typeface glyphTypeface, int index)
        {
            var formattedText = new FormattedText(
                                        textToFormat: Convert.ToChar(index).ToString(),
                                        culture: new CultureInfo("en-us"),
                                        flowDirection: FlowDirection.LeftToRight,
                                        typeface: glyphTypeface,
                                        emSize: 20,
                                        foreground: Brushes.Black);
            var geometry = formattedText.BuildGeometry(new Point(0, 0));
            var path = geometry.GetFlattenedPathGeometry();
            File.WriteAllText(index + ".path", path.ToString());
        }
    }
}
در اینجا تنها متد saveToFile در مقایسه با قسمت قبل افزوده شده است.
شیء FormattedText دارای متدی است به نام BuildGeometry که اطلاعات یک گلیف را تبدیل به معادل هندسی آن می‌کند. سپس توسط GetFlattenedPathGeometry معادل Path آن‌را می‌توان بدست آورد. برای مثال اگر پس از اجرای این مثال، به فایل 48.path تولیدی آن مراجعه کنیم، چنین خروجی را می‌توان مشاهده کرد:
 F1M5,7.47150993347168L5,17.2566661834717 5.732421875,19.0242443084717 7.5,19.7566661834717
12.5,19.7566661834717 13.69140625,19.4441661834717 5,7.47150993347168z
M7.5,4.75666618347168L6.30859375,5.06916618347168 15,17.0418224334717
15,7.25666618347168 14.267578125,5.48908805847168 12.5,4.75666618347168
7.5,4.75666618347168z M7.5,2.25666618347168L12.5,2.25666618347168
14.4189453125,2.62287712097168 16.03515625,3.72150993347168 17.1337890625,5.33772134780884
17.5,7.25666618347168 17.5,17.2566661834717 17.1337890625,19.1756114959717
16.03515625,20.7918224334717 14.4189453125,21.8904552459717 12.5,22.2566661834717
7.5,22.2566661834717 5.5810546875,21.8904552459717 3.96484375,20.7918224334717
2.8662109375,19.1756114959717 2.5,17.2566661834717 2.5,7.25666618347168 2.8662109375,5.33772134780884
3.96484375,3.72150993347168 5.5810546875,2.62287712097168 7.5,2.25666618347168z
که برای استفاده از اطلاعات آن در WPF می‌توان نوشت:
<Path Stroke="DarkRed" Fill="Black" Data="F1M5,7.47150993347168L5,17.2566661834717
              5.732421875,19.0242443084717 7.5,19.7566661834717 12.5,19.7566661834717 13.69140625,19.4441661834717 
              5,7.47150993347168z M7.5,4.75666618347168L6.30859375,5.06916618347168 15,17.0418224334717 
              15,7.25666618347168 14.267578125,5.48908805847168 12.5,4.75666618347168 7.5,4.75666618347168z 
              M7.5,2.25666618347168L12.5,2.25666618347168 14.4189453125,2.62287712097168 
              16.03515625,3.72150993347168 17.1337890625,5.33772134780884 17.5,7.25666618347168 
              17.5,17.2566661834717 17.1337890625,19.1756114959717 16.03515625,20.7918224334717 
              14.4189453125,21.8904552459717 12.5,22.2566661834717 7.5,22.2566661834717 
              5.5810546875,21.8904552459717 3.96484375,20.7918224334717 2.8662109375,19.1756114959717 
              2.5,17.2566661834717 2.5,7.25666618347168 2.8662109375,5.33772134780884 
              3.96484375,3.72150993347168 
              5.5810546875,2.62287712097168 7.5,2.25666618347168z" />
نظرات مطالب
آشنایی با ویژگی DebuggerDisplay در VS.Net
با سلام و تشکر از شما که با این وبلاگ فوق العاده کمک زیادی حداقل به من در خصوص یادگیری JQUERY کردید در مورد attribute ها یک راهنمایی کنید که داریم برنامه می نویسیم کجا باید از attribute ها استفاده کرد و مجبوریم فقط از آن استفاده کنیم
مطالب
طراحی گردش کاری با استفاده از State machines - قسمت دوم
معرفی کتابخانه stateless به عنوان جایگزین سبک وزنی برای Windows workflow foundation

کتابخانه سورس باز Stateless، برای طراحی و پیاده سازی «ماشین‌های حالت گردش کاری مانند» تهیه شده و مزایای زیر را نسبت به Windows workflow foundation دارا است:
- جمعا  30 کیلوبایت است!
- تمام اجزای آن سورس باز است.
- دارای API روان و ساده‌ای است.
- امکان تبدیل UML state diagrams، به نمونه معادل Stateless بسیار ساده و سریع است.
- به دلیل code first بودن، کار کردن با آن برای برنامه نویس‌ها ساده‌تر بوده و افزودن یا تغییر اجزای آن با کدنویسی به سادگی میسر است.

دریافت کتابخانه Stateless از Google code و یا از NuGet


پیاده سازی مثال کلید برق با Stateless

در ادامه همان مثال ساده  کلید برق قسمت قبل را با Stateless پیاده سازی خواهیم کرد:
using System;
using Stateless;

namespace StatelessTests
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string on = "On", off = "Off";
                var space = ' ';

                var onOffSwitch = new StateMachine<string, char>(initialState: off);

                onOffSwitch.Configure(state: off).Permit(trigger: space, destinationState: on);
                onOffSwitch.Configure(state: on).Permit(trigger: space, destinationState: off);

                Console.WriteLine("Press <space> to toggle the switch. Any other key will raise an error.");

                while (true)
                {
                    Console.WriteLine("Switch is in state: " + onOffSwitch.State);
                    var pressed = Console.ReadKey(true).KeyChar;
                    onOffSwitch.Fire(trigger: pressed);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: " + ex.Message);
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey(true);
            }
        }
    }
}
کار با ایجاد یک وهله از ماشین حالت (new StateMachine) آغاز می‌شود. حالت آغازین آن (initialState) مطابق مثال قسمت قبل، مساوی off است.
امضای کلاس StateMachine را در ذیل مشاهده می‌کنید؛ جهت توضیح آرگومان‌های جنریک string و char معرفی شده در مثال:
public class StateMachine<TState, TTrigger>
که اولی بیانگر نوع حالات قابل تعریف است و دومی نوع رویداد قابل دریافت را مشخص می‌کند.
برای مثال در اینجا حالات روشن و خاموش، با رشته‌های on و off مشخص شده‌اند و رویداد قابل قبول دریافتی، کاراکتر فاصله است.
سپس نیاز است این ماشین حالت را برای معرفی رویدادهایی (trigger در اینجا) که سبب تغییر حالت آن می‌شوند، تنظیم کنیم. اینکار توسط متدهای Configure و Permit انجام خواهد شد. متد Configure، یکی از حالات از پیش تعیین شده را جهت تنظیم، مشخص می‌کند و سپس در متد Permit تعیین خواهیم کرد که بر اساس رخدادی مشخص (برای مثال در اینجا فشرده شدن کلید space) وضعیت حالت جاری، به وضعیت جدیدی (destinationState) منتقل شود.
نهایتا این ماشین حالت در یک حلقه بی‌نهایت مشغول به کار خواهد شد. برای نمونه یک Thread پس زمینه (BackgroundWorker) نیز می‌تواند همین کار را در برنامه‌های ویندوزی انجام دهد.


یک نکته
علاوه بر روش‌های یاد شده‌ی تشخیص الگوی ماشین حالت که در قسمت قبل بررسی شدند، مورد refactoring انبوهی از if و elseها و یا switchهای بسیار طولانی را نیز می‌توان به این لیست افزود.



استفاده از Stateless Designer برای تولید کدهای ماشین حالت

کتابخانه Stateless دارای یک طراح و Code generator بصری سورس باز است که آن‌را به شکل افزونه‌ای برای VS.NET می‌توانید در سایت Codeplex دریافت کنید. این طراح از کتابخانه GLEE برای رسم گراف استفاده می‌کند.

کار مقدماتی با آن به نحو زیر است:
الف) فایل StatelessDesignerPackage.vsix را از سایت کدپلکس دریافت و نصب کنید. البته نگارش فعلی آن فقط با VS 2012 سازگار است.
ب) ارجاعی را به اسمبلی stateless به پروژه خود اضافه نمائید (به یک پروژه جدید یا از پیش موجود).
ج) از منوی پروژه، گزینه Add new item را انتخاب کرده و سپس در صفحه ظاهر شده، گزینه جدید Stateless state machine را انتخاب و به پروژه اضافه نمائید.
کار با این طراح، با ادیت XML آن شروع می‌شود. برای مثال گردش کاری ارسال و تائید یک مطلب جدید را در بلاگی فرضی، به نحو زیر وارد نمائید:
<statemachine xmlns="http://statelessdesigner.codeplex.com/Schema">
  <settings>
    <itemname>BlogPostStateMachine</itemname>
    <namespace>StatelessTests</namespace>
    <class>public</class>
  </settings>
  <triggers>     
    <trigger>Save</trigger>
    <trigger>RequireEdit</trigger>
    <trigger>Accept</trigger>
    <trigger>Reject</trigger>
  </triggers>
  <states>     
    <state start="yes">Begin</state>
    <state>InProgress</state>     
    <state>Published</state>      
    <state>Rejected</state>      
  </states>
  <transitions>
    <transition trigger="Save" from="Begin" to="InProgress" />

    <transition trigger="Accept" from="InProgress" to="Published" />
    <transition trigger="Reject" from="InProgress" to="Rejected" />

    <transition trigger="Save" from="InProgress" to="InProgress" />

    <transition trigger="RequireEdit" from="Published" to="InProgress" />
    <transition trigger="RequireEdit" from="Rejected" to="InProgress" />
  </transitions>
</statemachine>
حاصل آن گراف زیر خواهد بود:


به علاوه کدهای زیر که به صورت خودکار تولید شده‌اند:
using Stateless;

namespace StatelessTests
{
  public class BlogPostStateMachine
  {
    public delegate void UnhandledTriggerDelegate(State state, Trigger trigger);
    public delegate void EntryExitDelegate();
    public delegate bool GuardClauseDelegate();

    public enum Trigger
    {
      Save,
      RequireEdit,
      Accept,
      Reject,
    }

    public enum State
    {
      Begin,
      InProgress,
      Published,
      Rejected,
    }

    private readonly StateMachine<State, Trigger> stateMachine = null;

    public EntryExitDelegate OnBeginEntry = null;
    public EntryExitDelegate OnBeginExit = null;
    public EntryExitDelegate OnInProgressEntry = null;
    public EntryExitDelegate OnInProgressExit = null;
    public EntryExitDelegate OnPublishedEntry = null;
    public EntryExitDelegate OnPublishedExit = null;
    public EntryExitDelegate OnRejectedEntry = null;
    public EntryExitDelegate OnRejectedExit = null;
    public GuardClauseDelegate GuardClauseFromBeginToInProgressUsingTriggerSave = null;
    public GuardClauseDelegate GuardClauseFromInProgressToPublishedUsingTriggerAccept = null;
    public GuardClauseDelegate GuardClauseFromInProgressToRejectedUsingTriggerReject = null;
    public GuardClauseDelegate GuardClauseFromInProgressToInProgressUsingTriggerSave = null;
    public GuardClauseDelegate GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit = null;
    public GuardClauseDelegate GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit = null;
    public UnhandledTriggerDelegate OnUnhandledTrigger = null;

    public BlogPost()
    {
      stateMachine = new StateMachine<State, Trigger>(State.Begin);
      stateMachine.Configure(State.Begin)
        .OnEntry(() => { if (OnBeginEntry != null) OnBeginEntry(); })
        .OnExit(() => { if (OnBeginExit != null) OnBeginExit(); })
        .PermitIf(Trigger.Save, State.InProgress , () => { if (GuardClauseFromBeginToInProgressUsingTriggerSave != null) return GuardClauseFromBeginToInProgressUsingTriggerSave(); return true; } )
      ;
      stateMachine.Configure(State.InProgress)
        .OnEntry(() => { if (OnInProgressEntry != null) OnInProgressEntry(); })
        .OnExit(() => { if (OnInProgressExit != null) OnInProgressExit(); })
        .PermitIf(Trigger.Accept, State.Published , () => { if (GuardClauseFromInProgressToPublishedUsingTriggerAccept != null) return GuardClauseFromInProgressToPublishedUsingTriggerAccept(); return true; } )
        .PermitIf(Trigger.Reject, State.Rejected , () => { if (GuardClauseFromInProgressToRejectedUsingTriggerReject != null) return GuardClauseFromInProgressToRejectedUsingTriggerReject(); return true; } )
        .PermitReentryIf(Trigger.Save , () => { if (GuardClauseFromInProgressToInProgressUsingTriggerSave != null) return GuardClauseFromInProgressToInProgressUsingTriggerSave(); return true; } )
      ;
      stateMachine.Configure(State.Published)
        .OnEntry(() => { if (OnPublishedEntry != null) OnPublishedEntry(); })
        .OnExit(() => { if (OnPublishedExit != null) OnPublishedExit(); })
        .PermitIf(Trigger.RequireEdit, State.InProgress , () => { if (GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit != null) return GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit(); return true; } )
      ;
      stateMachine.Configure(State.Rejected)
        .OnEntry(() => { if (OnRejectedEntry != null) OnRejectedEntry(); })
        .OnExit(() => { if (OnRejectedExit != null) OnRejectedExit(); })
        .PermitIf(Trigger.RequireEdit, State.InProgress , () => { if (GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit != null) return GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit(); return true; } )
      ;
      stateMachine.OnUnhandledTrigger((state, trigger) => { if (OnUnhandledTrigger != null) OnUnhandledTrigger(state, trigger); });
    }

    public bool TryFireTrigger(Trigger trigger)
    {
      if (!stateMachine.CanFire(trigger))
      {
        return false;
      }
      stateMachine.Fire(trigger);
      return true;
    }

    public State GetState
    {
      get
      {
        return stateMachine.State;
      }
    }
  }
}
توضیحات:

ماشین حالت فوق دارای چهار حالت شروع، در حال بررسی، منتشر شده و رد شده است. معمول است که این چهار حالت را به شکل یک enum معرفی کنند که در کدهای تولیدی فوق نیز به همین نحو عمل گردیده و public enum State معرف چهار حالت ذکر شده است. همچنین رویدادهای ذخیره، نیاز به ویرایش، ویرایش، تائید و رد نیز توسط public enum Trigger معرفی شده‌اند.
در قسمت Transitions، بر اساس یک رویداد (Trigger در اینجا)، انتقال از یک حالت به حالتی دیگر را سبب خواهیم شد.
تعاریف اصلی تنظیمات ماشین حالت، در سازنده کلاس BlogPostStateMachine انجام شده است. این تعاریف نیز بسیار ساده هستند. به ازای هر حالت، یک Configure داریم. در متدهای OnEntry و OnExit هر حالت، یک سری callback function فراخوانی خواهند شد. برای مثال در حالت Rejected یا Approved می‌توان ایمیلی را به ارسال کننده مطلب جهت یادآوری وضعیت رخ داده، ارسال نمود.
متدهای PermitIf سبب انتقال شرطی، به حالتی دیگر خواهند شد. برای مثال رد یا تائید یک مطلب نیاز به دسترسی مدیریتی خواهد داشت. این نوع موارد را توسط delgateهای Guard ایی که برای مدیریت شرط‌ها ایجاد کرده است، می‌توان تنظیم کرد. PermitReentryIf سبب بازگشت مجدد به همان حالت می‌گردد. برای مثال ویرایش و ذخیره یک مطلب در حال انتشار، سبب تائید یا رد آن نخواهد شد؛ صرفا عملیات ذخیره صورت گرفته و ماشین حالت مجددا در همان مرحله باقی خواهد ماند.

نحوه استفاده از ماشین حالت تولیدی:
همانطور که عنوان شد، حداقل استفاده از ماشین‌های حالت، refactoing انبوهی از if و else‌ها است که در حالت مدیریت یک چنین گردش‌های کاری باید تدارک دید.
namespace StatelessTests
{
    public class BlogPostManager
    {
        private BlogPostStateMachine _stateMachine;
        public BlogPostManager()
        {
            configureWorkflow();
        }

        private void configureWorkflow()
        {
            _stateMachine = new BlogPostStateMachine();

            _stateMachine.GuardClauseFromBeginToInProgressUsingTriggerSave = () => { return UserCanPost; };
            _stateMachine.OnBeginExit = () => { /* save data + save state + send an email to admin */ };

            _stateMachine.GuardClauseFromInProgressToPublishedUsingTriggerAccept = () => { return UserIsAdmin; };
            _stateMachine.GuardClauseFromInProgressToRejectedUsingTriggerReject = () => { return UserIsAdmin; };
            _stateMachine.GuardClauseFromInProgressToInProgressUsingTriggerSave = () => { return UserHasEditRights; };
            _stateMachine.OnInProgressExit = () => { /* save data + save state + send an email to user */ };

            _stateMachine.OnPublishedExit = () => { /* save data + save state + send an email to admin */ };
            _stateMachine.GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit = () => { return UserHasEditRights; };

            _stateMachine.OnRejectedExit = () => { /* save data + save state + send an email to admin */ };
            _stateMachine.GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit = () => { return UserHasEditRights; };
        }

        public bool UserIsAdmin
        {
            get
            {
                return true; // TODO: Evaluate if user is an admin.
            }
        }

        public bool UserCanPost
        {
            get
            {
                return true; // TODO: Evaluate if user is authenticated
            }
        }

        public bool UserHasEditRights
        {
            get
            {
                return true; // TODO: Evaluate if user is owner or admin
            }
        }

        // User actions
        public void Save() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Save); }
        public void RequireEdit() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.RequireEdit); }

        // Admin actions        
        public void Accept() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Accept); }
        public void Reject() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Reject); }
    }
}
در کلاس فوق، نحوه استفاده از ماشین حالت تولیدی را مشاهده می‌کنید. در delegateهای Guard، سطوح دسترسی انجام عملیات بررسی خواهند شد. برای مثال، از بانک اطلاعاتی بر اساس اطلاعات کاربر جاری وارد شده به سیستم اخذ می‌گردند. در متدهای Exit هر مرحله، کارهای ذخیره سازی اطلاعات در بانک اطلاعاتی، ذخیره سازی حالت (مثلا در یک فیلد که بعدا قابل بازیابی باشد) صورت می‌گیرد و در صورت نیاز ایمیلی به اشخاص مختلف ارسال خواهد شد.
برای به حرکت درآوردن این ماشین، نیاز به یک سری اکشن متد نیز می‌باشد. تعدادی از این موارد را در انتهای کلاس فوق ملاحظه می‌کنید. کد نویسی آن‌ها در حد فراخوانی متد TryFireTrigger ماشین حالت است.

یک نکته:
ماشین حالت تولیدی به صورت پیش فرض در حالت State.Begin قرار دارد. می‌توان این مورد را از بانک اطلاعاتی خواند و سپس مقدار دهی نمود تا با هر بار وهله سازی ماشین حالت دقیقا مشخص باشد که در چه مرحله‌ای قرار داریم و TryFireTrigger بتواند بر این اساس تصمیم‌گیری کند که آیا مجاز است عملیاتی را انجام دهد یا خیر.
مطالب
Blazor 5x - قسمت 23 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 3 - کار با نقش‌های کاربران
در قسمت قبل، روش یکپارچه سازی context مربوط به ASP.NET Core Identity را با یک برنامه‌ی Blazor Server، بررسی کردیم. در این قسمت می‌خواهیم محدود کردن دسترسی‌ها را بر اساس نقش‌های کاربران و همچنین کدنویسی مستقیم، بررسی کنیم.


کار با Authentication State از طریق کدنویسی

فرض کنید در کامپوننت HotelRoomUpsert.razor نمی‌خواهیم دسترسی‌ها را به کمک اعمال ویژگی attribute [Authorize]@ محدود کنیم؛ می‌خواهیم اینکار را از طریق کدنویسی مستقیم انجام دهیم:
// ...

@*@attribute [Authorize]*@


@code
{
    [CascadingParameter] public Task<AuthenticationState> AuthenticationState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationState;
        if (!authenticationState.User.Identity.IsAuthenticated)
        {
            var uri = new Uri(NavigationManager.Uri);
            NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}");
        }
        // ...
- در اینجا در ابتدا اعمال ویژگی Authorize را کامنت کردیم.
- سپس یک پارامتر ویژه را از نوع CascadingParameter، به نام AuthenticationState تعریف کردیم. این خاصیت از طریق کامپوننت CascadingAuthenticationState که در قسمت قبل به فایل BlazorServer.App\App.razor اضافه کردیم، تامین می‌شود.
- در آخر در روال رویدادگردان OnInitializedAsync، بر اساس آن می‌توان به اطلاعات User جاری وارد شده‌ی به سیستم دسترسی یافت و برای مثال اگر اعتبارسنجی نشده بود، با استفاده از NavigationManager، او را به صفحه‌ی لاگین هدایت می‌کنیم.
- در اینجا روش ارسال آدرس صفحه‌ی فعلی را نیز مشاهده می‌کنید. این امر سبب می‌شود تا پس از لاگین، کاربر مجددا به همین صفحه هدایت شود.

authenticationState، امکانات بیشتری را نیز در اختیار ما قرار می‌دهد؛ برای مثال با استفاده از متد ()authenticationState.User.IsInRole آن می‌توان دسترسی به قسمتی را بر اساس نقش‌های خاصی محدود کرد.


ثبت کاربر ادمین Identity

در ادامه می‌خواهیم دسترسی به کامپوننت‌های مختلف را بر اساس نقش‌ها، محدود کنیم. به همین جهت نیاز است تعدادی نقش و یک کاربر ادمین را به بانک اطلاعاتی برنامه اضافه کنیم. برای اینکار به پروژه‌ی BlazorServer.Common مراجعه کرده و تعدادی نقش ثابت را تعریف می‌کنیم:
namespace BlazorServer.Common
{
    public static class ConstantRoles
    {
        public const string Admin = nameof(Admin);
        public const string Customer = nameof(Customer);
        public const string Employee = nameof(Employee);
    }
}
علت قرار دادن این کلاس در پروژه‌ی Common، نیاز به دسترسی به آن در پروژه‌ی اصلی Blazor Server و همچنین در پروژه‌ی سرویس‌های برنامه‌است. فضای نام این کلاس را نیز در فایل imports.razor_ قرار می‌دهیم.

سپس در فایل BlazorServer.App\appsettings.json، مشخصات ابتدایی کاربر ادمین را ثبت می‌کنیم:
{
  "AdminUserSeed": {
    "UserName": "vahid@dntips.ir",
    "Password": "123@456#Pass",
    "Email": "vahid@dntips.ir"
  }
}
جهت دریافت strongly typed این تنظیمات در برنامه، کلاس معادل AdminUserSeed را به پروژه‌ی Models اضافه می‌کنیم:
namespace BlazorServer.Models
{
    public class AdminUserSeed
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }
}
که به صورت زیر در فایل BlazorServer.App\Startup.cs به سیستم تزریق وابستگی‌های برنامه معرفی می‌شود:
namespace BlazorServer.App
{
    public class Startup
    {

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions<AdminUserSeed>().Bind(Configuration.GetSection("AdminUserSeed"));
            // ...
اکنون می‌توان سرویس افزودن نقش‌ها و کاربر ادمین را در پروژه‌ی BlazorServer.Services تکمیل کرد:
using System;
using System.Linq;
using System.Threading.Tasks;
using BlazorServer.Common;
using BlazorServer.DataAccess;
using BlazorServer.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace BlazorServer.Services
{
    public class IdentityDbInitializer : IIdentityDbInitializer
    {
        private readonly ApplicationDbContext _dbContext;
        private readonly UserManager<IdentityUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly IOptions<AdminUserSeed> _adminUserSeedOptions;

        public IdentityDbInitializer(
            ApplicationDbContext dbContext,
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager,
            IOptions<AdminUserSeed> adminUserSeedOptions)
        {
            _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
            _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager));
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _adminUserSeedOptions = adminUserSeedOptions ?? throw new ArgumentNullException(nameof(adminUserSeedOptions));
        }

        public async Task SeedDatabaseWithAdminUserAsync()
        {
            if (_dbContext.Roles.Any(role => role.Name == ConstantRoles.Admin))
            {
                return;
            }

            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Admin));
            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Customer));
            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Employee));

            await _userManager.CreateAsync(
                new IdentityUser
                {
                    UserName = _adminUserSeedOptions.Value.UserName,
                    Email = _adminUserSeedOptions.Value.Email,
                    EmailConfirmed = true
                },
                _adminUserSeedOptions.Value.Password);

            var user = await _dbContext.Users.FirstAsync(u => u.Email == _adminUserSeedOptions.Value.Email);
            await _userManager.AddToRoleAsync(user, ConstantRoles.Admin);
        }
    }
}
این سرویس، با استفاده از دو سرویس توکار UserManager و RoleManager کتابخانه‌ی Identity، ابتدا سه نقش ادمین، مشتری و کارمند را ثبت می‌کند. سپس بر اساس اطلاعات AdminUserSeed تعریف شده، کاربر ادمین را ثبت می‌کند. البته این کاربر در این مرحله، یک کاربر معمولی بیشتر نیست. در مرحله‌ی بعد است که با انتساب نقش ادمین به او، می‌توان کاربر او را بر اساس این نقش ویژه، شناسایی کرد. کلاس‌های IdentityRole و IdentityUser، کلاس‌های پایه‌ی نقش‌ها و کاربران کتابخانه‌ی Identity هستند.

پس از تعریف این سرویس، نیاز است آن‌را به سیستم تزریق وابستگی‌های برنامه اضافه کرد:
namespace BlazorServer.App
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IIdentityDbInitializer, IdentityDbInitializer>();
            // ...
مرحله‌ی آخر، اعمال و اجرای سرویس IIdentityDbInitializer، در زمان آغاز برنامه‌است و محل توصیه شده‌ی آن، در متد Main برنامه‌ی اصلی، پیش از اجرای برنامه‌است. به همین جهت، نیاز است BlazorServer.DataAccess\Utils\MigrationHelpers.cs را به صورت زیر ایجاد کرد:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Polly;

namespace BlazorServer.DataAccess.Utils
{
    public static class MigrationHelpers
    {
        public static void MigrateDbContext<TContext>(
                this IServiceProvider serviceProvider,
                Action<IServiceProvider> postMigrationAction
                ) where TContext : DbContext
        {
            using var scope = serviceProvider.CreateScope();
            var scopedServiceProvider = scope.ServiceProvider;
            var logger = scopedServiceProvider.GetRequiredService<ILogger<TContext>>();
            using var context = scopedServiceProvider.GetService<TContext>();

            logger.LogInformation($"Migrating the DB associated with the context {typeof(TContext).Name}");

            var retry = Policy.Handle<Exception>().WaitAndRetry(new[]
                {
                    TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)
                });

            retry.Execute(() =>
                {
                    context.Database.Migrate();
                    postMigrationAction(scopedServiceProvider);
                });

            logger.LogInformation($"Migrated the DB associated with the context {typeof(TContext).Name}");
        }
    }
}
در مورد این متد و استفاده از Polly جهت تکرار عملیات شکست خورده پیشتر در مطلب «اضافه کردن سعی مجدد به اجرای عملیات Migration در EF Core» بحث شده‌است.
کار متد الحاقی فوق، دریافت یک IServiceProvider است که به سرویس‌های اصلی برنامه اشاره می‌کند. سپس بر اساس آن، یک Scoped ServiceProvider را ایجاد می‌کند تا درون آن بتوان با Context برنامه در طی مدت کوتاهی کار کرد و در پایان آن، سرویس‌های ایجاد شده را Dispose کرد.
در این متد ابتدا Database.Migrate فراخوانی می‌شود تا اگر مرحله‌ای از Migrations برنامه هنوز به بانک اطلاعاتی اعمال نشده، کار اجرا و اعمال آن انجام شود. سپس یک متد سفارشی را از فراخوان دریافت کرده و اجرا می‌کند. برای مثال توسط آن می‌توان IIdentityDbInitializer در فایل BlazorServer.App\Program.cs به صوت زیر فراخوانی کرد:
public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    host.Services.MigrateDbContext<ApplicationDbContext>(
     scopedServiceProvider =>
            scopedServiceProvider.GetRequiredService<IIdentityDbInitializer>()
                                 .SeedDatabaseWithAdminUserAsync()
                                 .GetAwaiter()
                                 .GetResult()
    );
    host.Run();
}
تا اینجا اگر برنامه را اجرا کنیم، سه نقش پیش‌فرض، به بانک اطلاعاتی برنامه اضافه شده‌اند:


و همچنین کاربر پیش‌فرض سیستم را نیز می‌توان مشاهده کرد:


که نقش ادمین و کاربر پیش‌فرض، به این صورت به هم مرتبط شده‌اند (یک رابطه‌ی many-to-many برقرار است):



محدود کردن دسترسی کاربران بر اساس نقش‌ها

پس از ایجاد کاربر ادمین و تعریف نقش‌های پیش‌فرض، اکنون محدود کردن دسترسی به کامپوننت‌های برنامه بر اساس نقش‌ها، ساده‌است. برای این منظور فقط کافی است لیست نقش‌های مدنظر را که می‌توانند توسط کاما از هم جدا شوند، به ویژگی Authorize کامپوننت‌ها معرفی کرد:
@attribute [Authorize(Roles = ConstantRoles.Admin)]
و یا از طریق کدنویسی به صورت زیر نیز قابل اعمال است:
    protected override async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationState;
        if (!authenticationState.User.Identity.IsAuthenticated ||
            !authenticationState.User.IsInRole(ConstantRoles.Admin))
        {
            var uri = new Uri(NavigationManager.Uri);
            NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}");
        }


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-23.zip
مطالب
آشنایی با WPF قسمت ششم : DataContext بخش سوم
در قسمت قبلی با مبدل‌ها آشنا شدیم و با استفاده از این ویژگی، دو کنترل Radio Button و CheckBox را بایند کردیم. الان تنها دو کنترل مانده تا آن‌ها را متصل کنیم؛ کنترل ListBox و تقویم، که در این قسمت لیست را بررسی می‌کنیم.

ListBox
در مورد لیست، ما قبلا نام کشورها را با استفاده از تگ ListBoxItem به طور دستی اضافه می‌کردیم و هر گونه ویرایش و اضافه کردن عکس و دیگر اشیاء را داخل این تگ برای هر آیتم جداگانه انجام می‌دادیم؛ مثل تصویر زیر که هر آیتم شامل یک تگ تصویر و دو تگ TextBlock است که یکی از آن‌ها رنگی شده است. کد هر آیتم به طور جداگانه و دستی اضافه شده است.


 ولی در روش بایندینگ چنین چیزی ممکن نیست و تنها با استفاده از یک Template موارد بالا را ایجاد می‌کنیم. پس محتویات سابق ListBox را حذف کرده و تگهای زیر را جهت افزودن یک قالب داده Data Template به شیء لیست اضافه می‌کنیم. حال اگر داده‌های لیست شده خود را روانه  DataContext کنید باید این اطلاعات نمایش داده شوند.
 <ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" >
               <ListBox.ItemTemplate>
                    <DataTemplate>
                        <WrapPanel>
                            <Image Width="24" Height="24" Source="{Binding Flag}"></Image>
                            <TextBlock Padding="5 5 0 0" Text="{Binding Name}"></TextBlock>
                        </WrapPanel>
                    </DataTemplate>
               </ListBox.ItemTemplate>
            </ListBox>
در برنامه ما مشکلی که هست، کد بالا جهت اتصال به DataContext ای است که قبلا پر شده است (DataContext کل View اصلی یا والد تمامی اشیاء مشتق از آن). حتما به یاد دارید که ما این شیء را با مدل یک رکورد ذخیره شده (مدل Person) در منبع داده‌ها پر کرده بودیم. پس استفاده از این روش در حال حاضر منتفی است. ممکن است شما در طول ساخت یک پنجره چندین و چند جا نیاز به منابع داده مختلفی داشته باشید ولی عموما DataContext با یک مدل جهت نمایش یا ذخیره یک رکورد بایند شده است. پس چکار کنیم؟

ارائه این نکته ضروری است که همه اشیاء خصوصیت DataContext را دارند و ما در مثال قبلی DataContext ریشه یا والد اشیاء را پر کردیم. اگر مقاله "ساختار سلسله مراتبی " را به یاد بیاورید، گفتیم که هر شیء در صورتیکه خصوصیت وابسته‌ای برایش تعریف نشده باشد، به سمت اشیاء والد حرکت می‌کند، به این جهت بود که همه‌ی کنترل‌ها به منبع داده‌ها دسترسی داشتند. پس ما اگر DataContext لیست را پر کنیم، لیست دلیلی برای دسترسی به DataContext اشیاء والد ندارد و خصوصیت پر شده‌ی خودش را در نظر می‌گیرد. پس بیایید این مورد را امتحان کنیم:
من کلاس زیر را جهت ارسال لیستی از کشورها به همراه آدرس پرچمشان، بر می‌گردانم:
دلیل استفاده از کلاس ObservableCollection در کد زیر به جای استفاده از اشیایی چون Ilist و ... این بود که این کلاس به اینترفیس هایی چون INotifyPropertyChanged مزین گشته و هر گونه تغییری در این مجموعه، از قبیل حذف و اضافه را اطلاع رسانی کرده و مدل تغییر یافته را به سمت ویو هدایت می‌کند.
using System.Collections.ObjectModel;

namespace test
{
    public class Country
    {
        public string Flag {
            get { return "Images/flags/" + Name + ".png"; }
        }
        public string Name { get; set; }

        public int Id { get; set; }

        public ObservableCollection<Country> GetCountries()
        {
            var countries = new ObservableCollection<Country>();
            countries.Add(new Country(){Id =1,Name = "Afghanistan"});
            countries.Add(new Country() { Id = 2, Name = "Albania" });
            countries.Add(new Country() { Id = 3, Name = "Angola" });

            countries.Add(new Country() { Id = 4, Name = "Bahrain" });
            countries.Add(new Country() { Id = 5, Name = "Bermuda" });
            countries.Add(new Country() { Id =6, Name = "Iran" });

            return countries;
        }
    }
}
برنامه را اجرا کرده و انتظار داریم که بتوانیم لیست پر شده‌ای از داده‌ها را ببینیم؛ ولی در کمال تعجب لیست خالی است. خطایی هم برگردانده نمی‌شود.

دلیل این مشکل این است که DataContext برای نمایش یک Object تهیه شده است و در مورد داده‌های لیستی باید از خصوصیتی به نام ItemsSource استفاده کرد که برای داده‌های لیستی IEnumerables، بهینه شده است.
پس به این ترتیب می‌نویسیم :
   public MainWindow()
        {
            InitializeComponent();
            person = Person.GetPerson();
            DataContext = person;

            //خط جدید
            MyListBox.ItemsSource = new Country().GetCountries();
        }
حال برنامه را اجرا کرده تا نتیجه را مشاهده کنید.

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

شکل زیر همان نمودار بالا را ترسیم میکند ولی دیگر از مبدل پیش فرض WPF خبری نیست و مبدل اختصاصی به اسم ColorBrush جایگزین آن شده است:

نمودار زیر هم دسترسی به مجموعه ای از داده‌های لیستی است که از طریق ItemsSource خوانده می‌شوند:

کد زیر همچنین برای اتصال به کار می‌رود:
        public MainWindow()
        {
            InitializeComponent();
            person = Person.GetPerson();
            DataContext = person;

            //خط جدید
            MyListBox.DataContext = new Country().GetCountries();
            MyListBox.SetBinding(ItemsControl.ItemsSourceProperty, new Binding());
        }
روش بالا اتصال را برقرار می‌کند ولی من توصیه چندانی در استفاده از آن نمی‌کنم. آزاد گذاشتن DataContext یک لیست، یک مزیت هم دارد و آن این است که خارج از تگ Item‌ها یعنی همان تگ لیست، موقعی که  از بایندینگ استفاده می‌کنید، در واقع از DataContext کمک گرفته می‌شود؛ چون خود ListBox یک آیتم نیست که بخواهد با آیتمی در یک لیست سر و کله بزند. بلکه می‌تواند به راحتی به یک شیء، خود را بایند کند؛ مثال زیر نمونه‌ای از آن است.

پی نوشت : روش‌های دیگر بایند کردن همچون استفاده از منابع یا ریسورس‌ها یا استفاده از ViewModel‌ها هم هستند که در آینده در مورد آن‌ها بیشتر صحبت خواهیم کرد.

حال که توانستیم لیست را پر کنیم باید کشوری را که در رکورد واکشی شده آمده است، در لیست انتخاب کنیم.
توجه داشته باشید که باید لیست را از طریق خصوصیت ItemsSource پر کرده باشید و DataContext را دستکاری نکرده باشید.
خصوصیت Country در کلاس Person می‌تواند به دو صورت زیر باشد:
 public int Country { get; set; }
 public Country Country { get; set; }

که در هر دو حال از خصوصیت SelectedValue شی ListBox استفاده می‌شود. هر دو خط زیر به ترتیب برای استفاده از مقادیر بالا به کار می‌روند:
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country}"  >               
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country.Id}"  >
خصوصیت SelectedValuePath برای مشخص کردن اینکه کدام فیلد را باید در آیتم‌های لیست، جست و جو کند به کار می‌رود که ما در اینجا فیلد Id را که در کلاس Country قرار دارد، معرفی کرده‌ایم.
خصوصیت‌های دیگر یک شیء لیستی چون ListBox و ComboBox و ... SelectedIndex است که اندیس یک آیتم انتخابی را بازگردانده یا جهت انتخاب یک آیتم، اندیس آن را دریافت می‌کند. SelectedItem و SelectedItems هم شیء یا شیء‌هایی از مدل را (در اینجا Country) که در لیست انتخاب شده‌اند، بر می‌گرداند (فقط خواندنی).
 نتیجه اینکه اگر روش بالا با دستکاری DataContext انجام می‌گرفت دیگر استفاده از فیلد Country در مدل Peron ممکن نبود.