نظرات مطالب
شروع به کار با DNTFrameworkCore - قسمت 6 - پیاده‌سازی عملیات CRUD موجودیت‌ها با استفاده از ASP.NET Core MVC
نکته تکمیلی
در راستای تکمیل مطلب جاری و مطلب «پیاده سازی Conventional UI در ASP.NET MVC» برای رسیدن به یک قالب مشخص و جلوگیری از تکرار، می‌توان به شکل زیر عمل کرد:
1- انتقال قسمت‌های مشترک فرم‌ها به یک پارشال‌ویو به عنوان Layout فرم‌ها
//_EntityFormLayout.cshtml

@inherits EntityFormRazorPage<dynamic>
@{
    Layout = null;
}
<div class="modal-header">
    <h4 class="modal-title" asp-if="IsNew">Create New @EntityDisplayName</h4>
    <h4 class="modal-title" asp-if="!IsNew">Edit @EntityDisplayName</h4>
    <button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<form asp-action="@(IsNew ? CreateActionName : EditActionName)" asp-modal-form="@FormId">
    <div class="modal-body">
        <input type="hidden" name="continue-editing" value="true" asp-permission="@EditPermission"/>
        <input asp-for="@Version" type="hidden"/>
        <input asp-for="@Id" type="hidden"/>
        @RenderBody()
    </div>
    <div class="modal-footer">

        <a class="btn btn-light btn-circle" asp-modal-delete-link asp-model-id="@Id" asp-modal-toggle="false"
           asp-action="@DeleteActionName" asp-if="!IsNew" asp-permission="@DeletePermission"
           title="Delete Role">
            <i class="fa fa-trash text-danger"></i>
        </a>

        <a class="btn btn-light btn-circle" title="Refresh Role" asp-if="!IsNew" asp-modal-link asp-modal-toggle="false"
           asp-action="@EditActionName" asp-route-id="@Id">
            <i class="fa fa-repeat"></i>
        </a>
        <a class="btn btn-light btn-circle mr-auto" title="New Role" asp-modal-link asp-modal-toggle="false"
           asp-permission="@CreatePermission"
           asp-action="@CreateActionName">
            <i class="fa fa-plus"></i>
        </a>
        <button type="button" class="btn btn-light" data-dismiss="modal">
            <i class="fa fa-ban"></i>&nbsp; Cancel
        </button>
        <button type="submit" class="btn btn-outline-primary">
            <i class="fa fa-save"></i>&nbsp;Save Changes
        </button>
    </div>
</form>

با توجه به اینکه مدل متناظر با یک ویو در Layout آن نیز قابل دسترس می‌باشد. بدین ترتیب امکان دسترسی به خصوصیاتی مانند Id و Version یا متد IsNew وجود دارد؛ این خصوصیات در کلاس MasterModel به عنوان پایه مدل/DTO/ویومدل‌های ثبت/ویرایش، تعریف شده‌اند.
قراداد ما استفاده از همان مدل/DTO‌ها به عنوان ویومدل می‌باشد که در سناریوهای خاص پیشنهاد شد که از مدلی با نام موجودیت + کلمه ModalViewModel یا FormViewModel استفاده شود. برای انتقال سایر دیتا و متادیتای مورد نیاز برای ساخت فرم می‌توان از ViewBag و ViewData پس از امکان تعریف ویومدل پایه (دارای خصوصیات مورد نیاز Layout) که در این طراحی ممکن نیست، استفاده کرد. 
2- طراحی یک EntityFormRazorPage پایه
برای رسیدن به کدی با خوانایی بالا کلاسی را به عنوان پایه ویو‌های فرم‌ها و پارشال‌ویو EntityFormLayout، به شکل زیر طراحی می‌کنیم. در اینجا فرم ما یکسری خصوصیات موجود در کلاس پایه خود را مقداردهی خواهد کرد و در ادامه به دلیل ذخیره شدن این اطلاعات در ViewData، در Layout نیز قابل دسترس خواهند بود. 
    public abstract class EntityFormRazorPage<T> : RazorPage<T>
    {
        protected string EntityName
        {
            get => ViewData[nameof(EntityName)].ToString();
            set => ViewData[nameof(EntityName)] = value;
        }

        protected string EntityDisplayName
        {
            get => ViewData[nameof(EntityDisplayName)].ToString();
            set => ViewData[nameof(EntityDisplayName)] = value;
        }

        protected string DeletePermission
        {
            get => ViewData[nameof(DeletePermission)].ToString();
            set => ViewData[nameof(DeletePermission)] = value;
        }

        protected string CreatePermission
        {
            get => ViewData[nameof(CreatePermission)].ToString();
            set => ViewData[nameof(CreatePermission)] = value;
        }

        protected string EditPermission
        {
            get => ViewData[nameof(EditPermission)].ToString();
            set => ViewData[nameof(EditPermission)] = value;
        }

        protected string CreateActionName
        {
            get => ViewData.TryGetValue(nameof(CreateActionName), out var value) ? value.ToString() : "Create";
            set => ViewData[nameof(CreateActionName)] = value;
        }

        protected string EditActionName
        {
            get => ViewData.TryGetValue(nameof(EditActionName), out var value) ? value.ToString() : "Edit";
            set => ViewData[nameof(EditActionName)] = value;
        }

        protected string DeleteActionName
        {
            get => ViewData.TryGetValue(nameof(DeleteActionName), out var value) ? value.ToString() : "Delete";
            set => ViewData[nameof(DeleteActionName)] = value;
        }

        protected string FormId => $"{EntityName}Form";
        protected bool IsNew => (Model as dynamic).IsNew();
        protected string Id => (Model as dynamic).Id.ToString(CultureInfo.InvariantCulture);
        protected byte[] Version => (Model as dynamic).Version;
    }
3- تنظیم خصوصیات موجود در کلاس پایه
برای این منظور لازم است کلاس پایه را با دایرکتیو inherits مشخص کرده و سپس کار تنظیم Layout و سایر خصوصیات مورد نیاز را انجام دهید:
//_BlogPartial.cshtml

@inherits EntityFormRazorPage<BlogModel>
@{
    Layout = "_EntityFormLayout";
    EntityName = "Blog";
    DeletePermission = PermissionNames.Blogs_Delete;
    CreatePermission = PermissionNames.Blogs_Create;
    EditPermission = PermissionNames.Blogs_Edit;
    EntityDisplayName = "Blog";
}

4 - فرم ثبت و ویرایش متناظر با یک موجودیت
//_BlogPartial.cshtml

@inherits EntityFormRazorPage<BlogModel>
@{
    Layout = "_EntityFormLayout";
    ...
}

<div class="form-group row">
    <div class="col col-md-8">
        <label asp-for="Title" class="col-form-label text-md-left"></label>
        <input asp-for="Title" autocomplete="off" class="form-control"/>
        <span asp-validation-for="Title" class="text-danger"></span>
    </div>
</div>
<div class="form-group row">
    <div class="col">
        <label asp-for="Url" class="col-form-label text-md-left"></label>
        <input asp-for="Url" class="form-control" type="url"/>
        <span asp-validation-for="Url" class="text-danger"></span>
    </div>
</div>

و یا اگر از EditorTemplates استفاده می‌کنید:
//_BlogPartial.cshtml

@inherits EntityFormRazorPage<BlogModel>
@{
    Layout = "_EntityFormLayout";
    EntityName = "Blog";
    DeletePermission = PermissionNames.Blogs_Delete;
    CreatePermission = PermissionNames.Blogs_Create;
    EditPermission = PermissionNames.Blogs_Edit;
    EntityDisplayName = "Blog";
}

@Html.EditorForModel()

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

مطالب
طراحی گردش کاری با استفاده از 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 بتواند بر این اساس تصمیم‌گیری کند که آیا مجاز است عملیاتی را انجام دهد یا خیر.
مطالب دوره‌ها
تولید پویای کد در زمان اجرا توسط Reflection.Emit
در ادامه قصد داریم توسط امکانات Reflection به همراه کدهای IL، اشیایی را در زمان اجرا ایجاد کنیم.


Reflection چیست؟

Reflection چیزهایی هستند که با نگاه در یک آینه قابل مشاهده‌اند. در این حالت شخص می‌تواند قسمت‌های مختلف ظاهر خود را برانداز کرده یا قسمتی را تغییر دهید. اما این مساله چه ربطی به دنیای دات نت دارد؟ در دات نت با استفاده از Reflection می‌توان به اطلاعات اشیاء یک برنامه‌ی در حال اجرا دسترسی یافت. برای مثال نام کلاس‌های مختلف آن چیست یا درون کلاسی خاص، چه متدهایی قرار دارند. همچنین با استفاده از Reflection می‌توان رفتارهای جدیدی را نیز به کلاس‌ها و اشیاء افزود یا آن‌ها را تغییر داد.
همواره عنوان می‌شود که از Reflection به دلیل سربار بالای آن پرهیز کنید و تنها از آن به عنوان آخرین راه حل موجود استفاده نمائید و این دقیقا موردی است که در مباحث جاری بیشتر از آن استفاده خواهد شد: ساخت اشیاء جدید در زمان اجرا به کمک کدهای IL و امکانات Reflection


نگاهی به امکانات متداول Reflection

در مثال بعد، نگاهی خواهیم داشت به امکانات متداول Reflection، مانند دسترسی به متدها و خواص یک کلاس و تعویض مقدار یا فراخوانی آن‌ها:
using System;

namespace FastReflectionTests
{
    class Person
    {
        public string Name { set; get; }

        public string Speak()
        {
            return string.Format("Hello, my name is {0}.", this.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //روش متداول
            var vahid = new Person { Name = "Vahid" };
            Console.WriteLine(vahid.Speak());

            var type = vahid.GetType();

            //نمایش متدهای یک کلاس
            var methods = type.GetMethods();
            foreach (var method in methods)
            {
                Console.WriteLine(method.Name);
            }

            //تغییر مقدار یک خاصیت
            var setNameMethod = type.GetMethod("set_Name");
            setNameMethod.Invoke(obj: vahid, parameters: new[] { "Ali" });

            //فراخوانی یک متد
            var speakMethod = type.GetMethod("Speak");
            var result = speakMethod.Invoke(obj: vahid, parameters: null);
            Console.WriteLine(result);
        }
    }
}
با خروجی ذیل
 Hello, my name is Vahid.
set_Name
get_Name
Speak
ToString
Equals
GetHashCode
GetType
Hello, my name is Ali.
توضیحات:
در اینجا یک کلاس شخص با خاصیت نام او تعریف شده است؛ به همراه متدی که رشته‌ای را نمایش خواهد داد.
در متد Main برنامه، ابتدا یک وهله جدید از این شخص ایجاد شده و سپس به روش متداول، متد Speak آن فراخوانی گردیده است. در ادامه کار از امکانات Reflection برای انجام همین امور کمک گرفته شده است.
کار با دریافت نوع یک وهله شروع می‌شود. برای نمونه در اینجا توسط vahid.GetType به نوع وهله ساخته شده دسترسی یافته‌ایم. سپس با داشتن این type، می‌توان به کلیه امکانات Reflection دسترسی یافت. برای مثال توسط GetMethods، لیست کلیه متدهای موجود در کلاس شخص بازگشت داده می‌شود.
اگر به خروجی فوق دقت کنید، پس از سطر اول، 7 سطر بعدی نمایانگر متدهای موجود در کلاس شخص هستند. شاید عنوان کنید که این کلاس به نظر یک متد بیشتر ندارد. اما در دات نت اشیاء از شیء Object مشتق می‌شوند و چهار متد ToString، Equals، GetHashCode و GetType متعلق به آن هستند. همچنین خواص تعریف شده نیز در اصل به دو متد set و get به صورت خودکار در کدهای IL برنامه ترجمه خواهند شد. از همین متد set_Name در ادامه برای مقدار دهی خاصیت نام وهله ایجاد شده استفاده شده است.
همانطور که ملاحظه می‌کنید برای فراخوانی یک وهله از طریق Reflection، ابتدا توسط متد type.GetMethod می‌توان به آن دسترسی یافت و سپس با فراخوانی متد Invoke، می‌توان متد مدنظر را بر روی یک شیء مهیا با پارامترهایی که ذکر می‌کنیم، فراخوانی کرد. اگر این متد پارامتری ندارد، آن‌را نال قرار خواهیم داد.

تا اینجا مقدمه‌ای را ملاحظه نمودید که بیشتر جهت تکمیل بحث، حفظ روابط منطقی قسمت‌های مختلف آن و یادآوری مباحث مرتبط با Reflection ذکر شدند.


ایجاد اشیاء در زمان اجرای برنامه

یکی از کلاس‌های مهم Reflection که در منابع مختلف کمتر به آن پرداخته شده است، کلاس DynamicMethod آن است که از آن می‌توان برای ایجاد اشیاء و یا متدهایی پویا در زمان اجرا استفاده کرد. این کلاس قرار گرفته در فضای نام System.Reflection.Emit، دارای یک ILGenerator است که می‌توان به آن OpCodeهایی را اضافه کرد. زمانیکه کار ایجاد این متدپویا به پایان رسید، با استفاده از Delegates امکان دسترسی و اجرای این متد پویا وجود خواهد داشت.
یک مثال کامل را در این زمینه در ادامه ملاحظه می‌نمائید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static double Divider(int a, int b)
        {
            return a / b;
        }

        delegate double DividerDelegate(int a, int b);
        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Divider(10, 2));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "DividerMethod",
                                        returnType: typeof(double),
                                        parameterTypes: new[] { typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(opcode: OpCodes.Ldarg_0); //بارگذاری پارامتر اول بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Ldarg_1); //بارگذاری پارامتر دوم بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Div); // دو پارامتر از پشته ارزیابی دریافت و تقسیم خواهند شد
            il.Emit(opcode: OpCodes.Ret); // دریافت نتیجه نهایی از پشته ارزیابی و بازگشت آن

            //فراخوانی متد پویا
            //روش اول
            var result = myMethod.Invoke(obj: null, parameters: new object[] { 10, 2 });
            Console.WriteLine(result);

            //روش دوم
            var method = (DividerDelegate)myMethod.CreateDelegate(delegateType: typeof(DividerDelegate));
            Console.WriteLine(method(10, 2));
        }
    }
}
توضیحات
در ابتدای این مثال جدید یک متد متداول تقسیم کننده دو عدد را ملاحظه می‌کنید. در ادامه قصد داریم overload دیگری از این متد را توسط کدهای MSIL در زمان اجرا ایجاد کنیم که دو پارامتر int را قبول می‌کند.
کار با وهله سازی کلاس DynamicMethod موجود در فضای نام System.Reflection.Emit شروع می‌شود. در اینجا کار تعریف امضای متد جدید باید صورت گیرد. برای مثال نام آن چیست، نوع خروجی آن کدام است. نوع پارامترهای آن چیست و نهایتا این متدی که قرار است به صورت پویا به برنامه اضافه شود، باید در کجا قرار گیرد. برای اینکار از Module خود کلاس Program برنامه استفاده شده است.
پس از تعریف امضای متد پویا، نوبت به تعریف بدنه‌ی آن می‌رسد. کار با دریافت یک ILGenerator که می‌توان در آن کدهای IL را وارد کرد شروع می‌شود. مابقی آن تعریف کدهای IL توسط متد Emit است و پیشتر با مقدمات اسمبلی دات نت در قسمت‌های قبلی مبحث جاری آشنا شده‌ایم. ابتدا دو Ldarg فراخوانی شده‌اند تا دو پارامتر ورودی متد را دریافت کنند. سپس Div بر روی آن‌ها صورت گرفته و نهایتا نتیجه بازگشت داده شده است.
خوب؛ تا اینجا موفق شدیم اولین متد پویای خود را ایجاد نمائیم. برای اجرا آن حداقل دو روش وجود دارد:
الف) فراخوانی متد Invoke بر روی آن. با توجه به اینکه قرار نیست این متد بر روی وهله‌ی خاصی اجرا شود، اولین پارامتر آن null وارد شده است و سپس پارامترهای این متد پویا توسط آرگومان دوم متد Invoke وارد شده‌اند.
ب) می‌توان این عملیات را اندکی شکیل‌تر کرد. برای اینکار پیش از متد Main برنامه یک delegate به نام DividerDelegate تعریف شده است. سپس با استفاده از متد CreateDelegate، خروجی این متد پویا را تبدیل به یک delegate کرده‌ایم. اینبار فراخوانی متد پویا بسیار شبیه به متدهای معمولی می‌شود.
نظرات اشتراک‌ها
قلم (فونت) فارسی صمیم
اعداد انگلیسی، خوب، انگلیسی نمایش داده می‌شوند. برای نمایش فارسی اعداد، خودتان باید این تغییر را دستی اعمال کنید:
        public static string ToPersianNumbers(this string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return string.Empty;
            return
               data.Replace("0", "۰")
                .Replace("1", "۱")
                .Replace("2", "۲")
                .Replace("3", "۳")
                .Replace("4", "۴")
                .Replace("5", "۵")
                .Replace("6", "۶")
                .Replace("7", "۷")
                .Replace("8", "۸")
                .Replace("9", "۹")
                .Replace(".", ",");
        }
نظرات مطالب
معرفی پروژه فروشگاهی Iris Store
حذف تگ‌های htlml و normalization توسط متد   RemoveHtmlTags  به درستی انجام نمی‌شود و در برخی موارد کلمات جدا از هم به یکدیگر متصل می‌شوند.  

راه حل:
        public static string RemoveHtmlTags(this string str)
        {
            if (string.IsNullOrWhiteSpace(str))
            {
                return string.Empty;
            }
          
           //remove html tags by HtmlAgilityPack
            HtmlDocument htmlDoc = new HtmlDocument();
            htmlDoc.LoadHtml(str);
           
           // normalization
            return Regex.Replace(htmlDoc.DocumentNode.InnerText, @"&nbsp;|&zwnj;|(\n)\s*", " ").Trim();
        }
مطالب
MongoDb در سی شارپ (بخش دهم)

ابتدا بسته زیر را از طریق  nuget نصب نمایید:

dotnet add package MongoDB.Driver


سپس مدل‌های زیر را ایجاد نمایید:

public class BaseModel
{
    public BaseModel()
    {
        CreationDate=DateTime.Now;
    }
    public string Id { get; set; }
    public DateTime CreationDate { get; set; }
    public bool IsRemoved { get; set; }
    public DateTime? ModificationDate { get; set; }

}


 این مدل شامل یک کلاس پایه برای id,CreationDate,ModificationDate,IsRemoved میباشد که بسیار شبیه مدل‌هایی است که عموما در EntityFramework تعریف می‌کنیم.

برای اینکه فیلد Id به صورت objectId ایجاد شود ولی به صورت رشته‌ای استفاده شود ابتدا ویژگی BsonId را در بالای آن تعریف کرده تا به عنوان شناسه یکتا سند شناخته شود و سپس با استفاده از ویژگی BsonRepresentation  اعلام میکنیم که کار تبدیل به رشته و بلعکس آن به صورت خودکار در پشت صحنه صورت بگیرد:

public class BaseModel
    {
        [BsonId]
        [BsonRepresentation((BsonType.ObjectId))]
        public string Id { get; set; }
    }

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

BsonClassMap.RegisterClassMap<BaseModel>(map =>
{
    map.SetIdMember(map.GetMemberMap(x=>x.Id));
    map.GetMemberMap(x => x.Id)
        .SetSerializer(new StringSerializer(BsonType.ObjectId));
});


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

public class Employee : BaseModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
//=================
BsonClassMap.RegisterClassMap<Employee >(map =>
{
    map.SetIdMember(map.GetMemberMap(x=>x.Id));
    map.GetMemberMap(x => x.Id)
        .SetSerializer(new StringSerializer(BsonType.ObjectId));
});


روش استفاده از مونگو در asp.net core  به صورت زیر بسیار متداول میباشد که در قسمت‌های پیشین هم در این مورد نوشته بودیم:

MongoDbContext

  public interface IMongoDbContext
    {
        IMongoCollection<TEntity> GetCollection<TEntity>();
    }

  public class MongoDbContext : IMongoDbContext
    {

        private readonly IMongoClient _client;
        private readonly IMongoDatabase _database;

        public MongoDbContext(string databaseName,string connectionString)
        {
            var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));
            _client = new MongoClient(settings);
            _database = _client.GetDatabase(databaseName);
        }

        public IMongoCollection<TEntity> GetCollection<TEntity>()
        {
            return _database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s");
        }
    }

سپس از طریق کد زیر IMongoDbContext را به سیستم تزریق وابستگی‌ها معرفی میکنیم. الگوی استفاده شده‌ی در اینجا بر خلاف نسخه‌های sql که عموما به صورت AddScoped تعریف میشدند، در اینجا به صورت AddSingleton تعریف کردیم و نحوه پیاده سازی آن را نیز در طرف سمت راست به صورت صریح اعلام کردیم:

public static class MongoDbContextService
{
    public static void AddMongoDbContext(this IServiceCollection services,string databaseName,string connectionString)
    {
        services.AddSingleton<IMongoDbContext>(serviceProvider => new MongoDbContext(databaseName, connectionString));
    }
}

//===============
Program.cs

builder.Services.AddMongoDbContext("bookstore", "mongodb://localhost:27017");


پیاده سازی SoftDelete در مونگو

در مونگو چیزی تحت عنوان Global Query Filter نداریم که تمام کوئری هایی که به سمت دیتابیس ارسال میشوند، توسط کانتکس اطلاح شوند؛ بدین جهت برای پیاده سازی این خصوصیت میتوان اینترفیسی با نام <IRepository<T را به شکل زیر طراحی نماییم:

public interface IRepository<T> where T : BaseModel
{

    IMongoCollection<T> GetCollection();
    IMongoQueryable<T> GetFilteredCollection();
}

public class Repository<T> : IRepository<T> where T:BaseModel
{
    private IMongoDbContext _mongoDbContext;

    public Repository(IMongoDbContext mongoDbContext)
    {
        _mongoDbContext = mongoDbContext;
    }

    public IMongoCollection<T> GetCollection()
    {
        return _mongoDbContext.GetCollection<T>();
    }
    
    public IMongoQueryable<T> GetFilteredCollection()
    {
        var query= _mongoDbContext.GetCollection<T>().AsQueryable();
        
        //================= Global Query Filters ====================
        
        //Filter 1
        query=query.Where(x => x.RemovedAt.HasValue == false);
        
        //==============================================================
        
        return query;
    }
}

این کلاس یا اینترفیس شامل دو متد هستند که کلاس جنریک آنها باید از BaseModel ارث بری کرده باشد و اولین متد، تنها یک کالکشن بدون هیچگونه فیلتری است که میتواند نقش متد IgnoreQueryFilters  را بازی کند و دیگری GetFilteredCollection است که در این متد ابتدا کالکشنی دریافت شده و سپس آن را به حالت کوئری تغییر داده و فیلترهای مورد نظر، مانند حذف منطقی را پیاده سازی میکنیم:

public interface IRepository<T> where T : BaseModel
{

    IMongoCollection<T> GetCollection();
    IMongoQueryable<T> GetFilteredCollection();
}

public class Repository<T> : IRepository<T> where T:BaseModel
{
    private IMongoDbContext _mongoDbContext;

    public Repository(IMongoDbContext mongoDbContext)
    {
        _mongoDbContext = mongoDbContext;
    }

    public IMongoCollection<T> GetCollection()
    {
        return _mongoDbContext.GetCollection<T>();
    }
    
    public IMongoQueryable<T> GetFilteredCollection()
    {
        var query= _mongoDbContext.GetCollection<T>().AsQueryable();
        
        //================= Global Query Filters ====================
        
        //Filter 1
        query=query.Where(x => x.RemovedAt.HasValue == false);
        
        //==============================================================
        
        return query;
    }
}


اصلاح تاریخ ویرایش در مدل

در EF به لطف dbset و همچنین ChangeTracking امکان شناسایی حالت‌ها وجود دارد و میتوانید در متدی مانند saveChanges مقدار تاریخ ویرایش را تنظیم نمود. برای مدل‌های منگو چنین چیزی وجود ندارد و به همین دلیل چند روش زیر پیشنهاد میگردد:

یک. استفاده از اینترفیس INotifyPropertyChanged یا جهت حذف کدهای تکراری نیز از الگوی AOP بهره بگیرید.

دو. استفاده از یک <Repository<T همانند بالا که شامل متدهای داخلی Update و Delete هستند که در آنجا میتوانید این مقادیر را به صورت مستقیم تغییر دهید.

نظرات مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت چهارم - پرهیز از الگوی Service Locator در برنامه‌های وب
یک نکته‌ی تکمیلی: امکان تزریق وابستگی‌های سرویس‌های سفارشی، در سازنده‌ی کلاس Startup برنامه‌های وب


اگر به سازنده‌ی پیش‌فرض کلاس Startup یک برنامه‌ی وب دقت کنید، چنین تزریق وابستگی در قالب ابتدایی آن وجود دارد:
public class Startup 
{ 
   public Startup(IConfiguration configuration) 
   { 
       Configuration = configuration; 
   }
در اینجا ممکن است چند سؤال مطرح شوند:
الف) چه سرویس‌های پیش‌فرض دیگری را نیز می‌توان در اینجا تزریق کرد؟
ب) آیا می‌توان سرویس‌های سفارشی تهیه شده‌ی توسط خودمان را نیز در اینجا تزریق کرد؟

الف) بر روی ابتدای متد ConfigureServices کلاس Startup یک break-point را قرار دهید. لیست پارامتر services آن، شامل سرویس‌های پیش‌فرضی است که قابلیت تزریق وابستگی‌ها را در سازنده‌ی این کلاس دارند و بیش از 40 کلاس هستند.

ب) برای این منظور به فایل Program.cs مراجعه کرده و سرویس سفارشی خود را به صورت زیر، توسط متد ConfigureServices آن، اضافه کنید:
using CoreIocServices;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace CoreIocSample02
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .ConfigureServices(serviceCollection =>
            {
                serviceCollection.AddScoped<ISomeService, SomeService>();
            })
            .UseStartup<Startup>();
    }
}
اکنون ISomeService سفارشی ما قابلیت تزریق در سازنده‌ی کلاس Startup را نیز پیدا کرده‌است (علاوه بر سایر نقاط برنامه):
namespace CoreIocSample02
{
    public class Startup
    {
        private readonly ISomeService _someService;

        public Startup(IConfiguration configuration, ISomeService someService)
        {
            Configuration = configuration;
            _someService = someService;
        }

        public IConfiguration Configuration { get; }
مطالب
فراخوانی GraphQL API در یک کلاینت ASP.NET Core
در قسمت قبل، ایجاد کردن Mutation‌ها را در GraphQL تمام کردیم. در این قسمت تصمیم بر این است که از GraphQL API در یک برنامه ASP.NET Core استفاده کنیم. برای فراخوانی GraphQL API در یک برنامه ASP.NET Core، از یک کتابخانه ثالث که به ما در این فرآیند کمک می‌کند استفاده خواهیم کرد.

Preparing the ASP.NET Core Client Project 

کار را با ایجاد کردن یک پروژه ASP.NET Core Web API شروع می‌کنیم :
dotnet new api -n DotNetGraphQLClient
 
به محض اینکه پروژه را ایجاد کردیم فایل launchsettings.json را باز می‌کنیم و مقدار خصوصیت launchBrowser را به false  و خصوصیت applicationUrl  را به
https://localhost:5003;http://localhost:5004
تغییر می‌دهیم. در ادامه فایل appsettings.json را مطابق زیر ویرایش می‌کنیم (اضافه کردن کلید GraphQLURI ):
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "GraphQLURI": "https://localhost:5001/graphql",
  "AllowedHosts": "*"
}

اکنون می‌توانیم کتابخانه مورد نیاز را نصب کنیم. در ترمینال مربوط به VS Code دستور زیر را وارد کنید:
dotnet add package GraphQL.Client
بعد از نصب، برای ثبت آن، کلاس Startup را مطابق زیر ویرایش کنید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(x => new GraphQL.Client.GraphQLClient(Configuration["GraphQLURI"]));
   ...
}

در سازنده کلاس GraphQLClient، آدرس endpoint را معرفی می‌کنیم (که در فایل appsettings.json می باشد) .
قدم بعد، ایجاد کردن یک کلاس به نام OwnerConsumer است که عملیات مربوط به فراخوانی API، در این کلاس می‌باشد. 
public class OwnerConsumer
{
    private readonly GraphQL.Client.GraphQLClient _client;
 
    public OwnerConsumer(GraphQL.Client.GraphQLClient client)
    {
        _client = client;
    }
}
سپس کلاس ایجاد شده را در متد ConfigureServices از کلاس Startup  ثبت می‌کنیم: 
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(x => new GraphQL.Client.GraphQLClient(Configuration["GraphQLURI"]));
    services.AddScoped<OwnerConsumer>();
    ...
}


Creating Model Classes 
 
در قسمت اول تعدادی کلاس را در پوشه Models  ایجاد کردیم. همه آن کلاس‌ها را برای این client application نیاز داریم؛ پس آن‌ها را ایجاد می‌کنیم: 
public enum TypeOfAccount
{
    Cash,
    Savings,
    Expense,
    Income
}
public class Account
{
    public Guid Id { get; set; }
    public TypeOfAccount Type { get; set; }
    public string Description { get; set; }
}
public class Owner
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
 
    public ICollection<Account> Accounts { get; set; }
}

در قسمت سوم ( GraphQL Mutation ) یک کلاس Input type را برای اکشن‌های Mutation ایجاد کردیم. این کلاس هم مورد نیاز می‌باشد. پس آن را ایجاد می‌کنیم:
public class OwnerInput
{
    public string Name { get; set; }
    public string Address { get; set; }
}
اکنون همه چیز آماده است تا Query و Mutation ‌ها را ایجاد کنیم. 

Creating Queries and Mutations to Consume GraphQL API 

کلاس OwnerConsumer را باز می‌کنیم و متد GetAllOwners  را به آن اضافه می‌کنیم:
public async Task<List<Owner>> GetAllOwners()
{
    var query = new GraphQLRequest
    {
        Query = @"
                query ownersQuery{
                  owners {
                    id
                    name
                    address
                    accounts {
                      id
                      type
                      description
                    }
                  }
                }"
    };
 
    var response = await _client.PostAsync(query);
    return response.GetDataFieldAs<List<Owner>>("owners");
}

در ابتدا یک شیء جدید از نوع GraphQLRequest را ایجاد می‌کنیم که شامل خصوصیت Query که برای ارسال queryی که می‌خواهیم به GraphQL API ارسال کنیم، می‌باشد. این query مثل همانی که در ابزار UI.Playground اجرا کردیم، می‌باشد. جهت اجرای این query، متد PostAsync را فراخوانی می‌کنیم که یک پاسخ را برگشت می‌دهد. در نهایت می‌توان نتیجه را تبدیل به نوع مورد نیاز، با استفاده از متد GetDataFieldsAs کرد. 
توجه کنید که آرگومان متد GetDataFieldAs باید با نام query مطابقت داشته باشد .


Implementing a Controller 
یک کنترلر جدید را به نام OwnerController ایجاد می‌کنیم و آن را مطابق زیر ویرایش می‌کنیم: 
[Route("api/owners")]
public class OwnerController: Controller
{
    private readonly OwnerConsumer _consumer;
 
    public OwnerController(OwnerConsumer consumer)
    {
        _consumer = consumer;
    }
 
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var owners = await _consumer.GetAllOwners();
        return Ok(owners);
    }
}

مثال ضمیمه شده در پایان قسمت قبل را اجرا کنید که بر روی پورت 5001 می‌باشد و سپس پروژه را اجرا کنید ( بر روی پورت 5003 است ).
اکنون می‌توانیم آن را در Postman تست کنیم.
جهت بازیابی تمامی owner ‌ها، در خواست زیر را در Postman صادر کنید:


جهت ایجاد یک owner جدید، کلاس OwnerConsumer را مطابق زیر ویرایش می‌کنیم ( اضافه کردن متد  CreateOwner ) :
 public async Task<Owner> CreateOwner(OwnerInput ownerToCreate)
 {
     var query = new GraphQLRequest
     {
         Query = @"
         mutation($owner: ownerInput!){
           createOwner(owner: $owner){
             id,
             name,
             address
           }
         }",
         Variables = new { owner = ownerToCreate }
     };

     var response = await _client.PostAsync(query);
     return response.GetDataFieldAs<Owner>("createOwner");
 }
و سپس ، کلاس OwnerController را باز می‌کنیم و متد PostItem را به آن اضافه می‌کنیم :
[HttpPost]
public async Task<IActionResult> PostItem([FromBody]OwnerInput model)
{
    var result = await _consumer.CreateOwner(model);
    return Json(result);
}

 اکنون، در خواست ایجاد یک owner جدید را در Postman به صورت زیر صادر می‌کنیم: 


همانطور که مشخص است، همه چیز به درستی کار می‌کند. شما می‌توانید مابقی Query و Mutation ‌ها را با استفاده از Postman تست کنید.

نتیجه گیری 
در این قسمت یادگرفتیم که چگونه :
  1. یک کلاینت ASP.NET Core را برای فراخوانی GraphQL API  آماده سازی کنیم. 
  2. چه کتابخانه‌هایی مورد نیاز است که می‌توانند به ما در انجام فرآیند فراخوانی GraphQL API کمک کنند.
  3. ایجاد کردن query‌ها و mutation‌ها در یک کلاینت ASP.NET Core. 

کد‌های کامل این قسمت را از اینجا دریافت کنید: DotNetGraphQLClient_4.zip 
کد‌های کامل قسمت قبل را می‌توانید از اینجا دریافت کنید:  ASPCoreGraphQL_3.rar

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config
نکته تکمیلی: نحوه بایند کردن دو Section مختلف در asppsettings.json به یک کلاس
مثلا دو Object متفاوت در appsettings.json داریم و میخواهیم آن را به یک کلاس بایند کنیم:
"SiteSettingsA": {
    "DefaultUserPicture": "http://site.com/users/defaultavatar.png"
},
"SiteSettingsB": {
    "DefaultUserPicture": "http://site.com/users/defaultavatar2.png"
}
و این کلاس برای بایند کردن تنظیمات به آن:
public class SiteSettings
{
    public string DefaultUserPicture { get; set; }
}
اگر به روش انجام شده در این مطلب فراخوانی کنیم Object ـی که بالاتر است به کلاس بایند میشود که در این اینجا SiteSettingsA میباشد. برای حل این مشکل به این صورت عمل میکنیم:
services.Configure<SiteSettings>("FirstSettings", Configuration.GetSection("SiteSettingsA"));
services.Configure<SiteSettings>("SecondSettings", Configuration.GetSection("SiteSettingsB"));
و فراخوانی آن در یک کنترلر:
private readonly SiteSettings _firstSiteSettings;
private readonly SiteSettings _secondSiteSettings;

public HomeController(IOptionsSnapshot<SiteSettings> siteSettings)
{
    _firstSiteSettings = siteSettings.Get("FirstSettings");
    _secondSiteSettings = siteSettings.Get("SecondSettings");
}

مطالب
مثالی از الگوی Delegate Dictionary
این الگو چیز جدیدی نیست و قبلا تو سری مطالب «مروری بر کاربردهای Action و Func» دربارش مطلب نوشته شده و...
البته با توجه به جدید بودن این الگو اسم واحدی براش مشخص نشده ولی تو این مطلب «الگوی Delegate Dictionary» معرفی شده که بنظرم از بقیه بهتره.
به طور خلاصه این الگو میگه اگه قراره براساس شرایط (ورودی) خاصی کار خاصی انجام بشه بجای استفاده از IF و Switch از DictionaryوFunc یا Action استفاده کنیم.

برای مثال فرض کنید مدلی به شکل زیر داریم
public class Person
{
    public int Id { get; set; }
    public Gender Gender { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
قراره براساس جنسیت(شرایط) شخص اعتبارسنجی متفاوتی(کار خاص) رو انجام بدیدم.مثلا در اینجا قراره چک کنیم اگه شخص مرد بود اسم زنونه انتخاب نکرده باشه و...
خب روش معمول به این شکل میتونه باشه
switch (person.Gender)
{
    case Gender.Male:
        if (IsMale(person.FirstName))
        {
            //Isvalid
        }
        break;
    case Gender.Female:
        if (IsFemale(person.FirstName))
        {
            //Isvalid
        }
        break;
}
خب این روش خوب جواب میده ولی باید در حد توان استفاده از IF و Switch رو کم کرد.مثلا تو همین مثال ما اصل Open/Closed رو نقض کردیم فکر کنید قرار باشه اعتبارسنجی دیگه ای از همین دست به این کد(کلاس) اضافه بشه باید تغیرش بدیم پس این کد(کلاس) برای تغییر بسته نیست.در اینجور موارد «الگوی Delegate Dictionary» به کار ما میاد.
ما میایم توابع مورد نظرمون رو داخل یک Dictionary ذخیره میکنیم.
var genderFuncs = new Dictionary<Gender, Func<string, bool>>
                {
                    {Gender.Male , (x) => IsMale(x)},
                    {Gender.Female , (x) => IsFemale(x)}
                };
فرض کنید پیاده سازی توابع به شکل زیر باشه
public static bool IsMale(string name)
{
    //check...
    return true;
}
public static bool IsFemale(string name)
{
    //check...
    if (name == "Farzad")
    {
        return false;    
    }
    return true;
}
نحوه استفاده
var dummyPerson = new List<Person>
                {
                    new Person
                        {Id = 1, Gender = Gender.Male, FirstName = "Mohammad", LastName = "Saheb"},
                    new Person
                        {Id = 2, Gender = Gender.Female, FirstName = "Farzad", LastName = "Mojidi"}
                };

foreach (var person in dummyPerson)
{
    bool isValid = genderFuncs[person.Gender].Invoke(person.FirstName);          
}
با همین روش میشه قسمت آخر مقاله ی خوب آقای کیاست رو هم Refactor کرد.
var query = context.Students.AsQueryable();
  if (searchByName)
  {
      query= query.FindStudentsByName(name);
  }
  if (orderByAge)
  {
      query = query.OrderByAge();
  }
  if (paging)
  {
     query =  query.SkipAndTake(skip, take);
  }
  return query.ToList();
توابع رو داخل یک دیکشنری ذخیره میکنیم
var searchTypeFuncs = new Dictionary<SearchType, Func<IQueryable<Student>, string, IQueryable<Student>>>
                    {
                        {SearchType.FirstName, (x, y) => x.FindStudentsByName(y)},
                        {SearchType.LastName, (x, y) => x.FindStudentsByLastName(y)}
                    };
نحوه استفاده
public static IList<Student> SearchStudents(IQueryable<Student> students, SearchType type, string keyword)
{
    var result = searchTypeFuncs[type].Invoke(students, keyword);
    return result.ToList();
}