مطالب
نمایش ای‌جکسی یک partial view در popover بوت استرپ 3
فرض کنید بخواهیم نمایش رای دهنده‌های یک مطلب را با popover بوت استرپ 3 نمایش دهیم:

Popover بوت استرپ برای کار با منابع remote طراحی نشده‌است و نیاز است توابع API آن‌را به همراه jQuery Ajax ترکیب کرد تا به تصویر فوق رسید.


مرحله‌ی اول: اکشن متدی که یک partial view را باز می‌گرداند

فرض کنید اکشن متدی که لیست کاربران رای داده‌ی به یک مطلب را باز می‌گرداند، چنین شکلی را دارد:
        public ActionResult RenderResults(string param1)
        {
            var users = new[]
            {
                new User{ Id = 1, Name = "Test 1", Rating = 3},
                new User{ Id = 2, Name = "Test 2", Rating = 4},
                new User{ Id = 3, Name = "Test 3", Rating = 5}
            };
            return PartialView("_RenderResults", model: users);
        }
در اینجا لیستی دریافت و سپس به partial view ارسال می‌شود. در ادامه این Partial view نیز به صورت ذیل اطلاعات را رندر می‌کند:
@using RemotePopOver.Models
@model IList<User>

<ul id="ratings1" data-title="Ratings" class="list-unstyled">
    @foreach (var user in Model)
    {
        <li>
            @user.Name
            <span class="badge pull-right">@user.Rating</span>
        </li>
    }
</ul>
در اینجا از یک ویژگی سفارشی data-* به نام data-title نیز استفاده کرده‌ایم. متنی که در آن قرار می‌گیرد، به صورت عنوان popover ظاهر خواهد شد.


مرحله‌ی دوم: دریافت اطلاعات partial view با استفاده از jQuery Ajax و سپس درج آن در یک popover

می‌خواهیم با حرکت ماوس بر روی دکمه‌ی سفارشی ذیل، یک popover ظاهر شده و محتوای خودش را از اکشن متد فوق تامین کند.
<span id="remotePopover1"
      aria-hidden="true"
      data-param1="test"
      data-popover-content-url="@Url.Action("RenderResults", "Home")"
      class="glyphicon glyphicon-info-sign btn btn-info"></span>
در این مثال، چند ویژگی سفارشی data-* دیگر را نیز تعریف کرده‌ایم تا نیازی به تعریف سراسری متغیرهای جاوا اسکریپتی نباشد. برای مثال data-param1 یک پارامتر دلخواه است و data-popover-content-url به آدرس اکشن متدی اشاره می‌کند که قرار است partial view مدنظر را رندر کند.
در ادامه نحوه‌ی استفاده‌ی از این ویژگی‌ها را در jQuery Ajax مشاهده می‌کنید:
@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('body').on('mouseenter', 'span[data-popover-content-url]', function () {
                var el = $(this);
                $.ajax({
                    type: "POST",
                    url: $(this).data("popover-content-url"),
                    data: JSON.stringify({ param1: $(this).data("param1") }),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    // controller is returning a simple text, not json
                    complete: function (xhr, status) {
                        var data = xhr.responseText;
                        if (status === 'error' || !data) {
                            el.popover({
                                content: 'Error connecting server!',
                                trigger: 'focus',
                                html: true,
                                container: 'body',
                                placement: 'auto',
                                title: 'Error!'
                            }).popover('show');
                        }
                        else {
                            el.popover({
                                content: data,
                                trigger: 'focus',
                                html: true,
                                container: 'body',
                                placement: 'auto',
                                title: $('<html />').html(data).find('#ratings1:first').data('title')
                            }).popover('show');
                        }
                    }
                });
            }).on('mouseleave', 'span[data-popover-content-url]', function () {
                $(this).popover('hide');
            });
        });
    </script>
}
در این مثال تمام المان‌هایی که دارای data-popover-content-url هستند، تحت نظر قرار می‌گیرند. سپس اگر ماوس به محدوده‌ی آن‌ها وارد شد، مقدار ("this).data("popover-content-url)$ مساوی آدرسی است که قرار است اطلاعات را از سرور دریافت کند. به همین جهت از آن برای استفاده‌ی در متد ajax کمک گرفته شده‌است.
خروجی partial view به صورت json نیست. بنابراین باید اطلاعات نهایی آن‌را در callback ویژه‌ی complete دریافت کرد. مقدار data دریافتی، معادل اطلاعات رندر شده‌ی partial view است. به همین جهت آن‌را به خاصیت content متد popver ارسال می‌کنیم. همچنین چون خروجی patrtial view به همراه html است، نیاز است خاصیت html متد popover نیز به true تنظیم شود. در خاصیت title، نحوه‌ی دسترسی به مقدار data-title تنظیم شده‌ی در partial view را مشاهده می‌کنید.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
RemotePopOver.zip
مطالب
استفاده از Interop.word برای جایگزین کردن مقادیر در تمامی فایل (Footer - Header - ... )
یکی از متداول‌ترین کارهایی که با اسناد می‌توان انجام داد، تهیه خروجی pdf از word و پر کردن یک فایل word با مقادیر ورودی است که سعی داریم یک نمونه از آن را اینجا بررسی کنیم. کد عمومی برای جایگزین کردن:
public void MsInteropReplace(Microsoft.Office.Interop.Word.Application doc, object findText, object replaceWithText)
        {
            object matchCase = false;
            object matchWholeWord = true;
            object matchWildCards = false;
            object matchSoundsLike = false;
            object matchAllWordForms = false;
            object forward = true;
            object format = false;
            object matchKashida = false;
            object matchDiacritics = false;
            object matchAlefHamza = false;
            object matchControl = false;
            object read_only = false;
            object visible = true;
            object replace = 2;
            object wrap = 1;
            //execute find and replace
            doc.Selection.Find.Execute(ref findText, ref matchCase, ref matchWholeWord,
                ref matchWildCards, ref matchSoundsLike, ref matchAllWordForms, ref forward, ref wrap, ref format, ref replaceWithText, ref replace,
                ref matchKashida, ref matchDiacritics, ref matchAlefHamza, ref matchControl);
        }
 و یا این مورد:
private static void MsInteropReplace2()
        {
            var doc = new Microsoft.Office.Interop.Word.Application().Documents.Open(@"D:\temp\te1.docx");

            doc.Content.Find.Execute("@levelOrder", false, true, false, false, false, true, 1, false, "12345", 2,
            false, false, false, false);
            object missing = System.Reflection.Missing.Value;
            doc.SaveAs(@"D:\temp\out.docx", ref missing, ref missing, ref missing, ref missing
                , ref missing, ref missing, ref missing, ref missing, ref missing, ref missing
                , ref missing, ref missing, ref missing, ref missing, ref missing);
}

که هر دو مورد را در stackoverflow میتوانید پیدا کنید. به شخصه از این مورد برای replace کردن مقادیر در یک فایل template.docx استفاده میکردم؛ ولی بعد از مدتی فهمیدم که Footer‌ها و Header را نمیتواند پردازش کند. کد زیر در تمامی قسمت‌هایی که در یک فایل word می‌توان متغیر تعریف کرد را گشته و عمل پر کردن مقادیر را بر روی فایل نمونه، انجام می‌دهد و شامل سه متد ذیل است:
private static void repAll()
        {
            object Missing = System.Reflection.Missing.Value;

            Application app = null;
            Microsoft.Office.Interop.Word.Document doc = null;
            try
            {
                app = new Microsoft.Office.Interop.Word.Application();

                doc = app.Documents.Open(@"D:\temp\te1.docx", Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing);

                FindReplaceAnywhere(app, "@levelOrder", "محرمانه");

                doc.SaveAs(@"D:\temp\out.docx", Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing);
            }
            finally
            {
                try
                {
                    if (doc != null) ((Microsoft.Office.Interop.Word._Document)doc).Close(true, Missing, Missing);
                }
                finally { }
                if (app != null) ((Microsoft.Office.Interop.Word._Application)app).Quit(true, Missing, Missing);
            }
        }

        private static void searchAndReplaceInStory(Microsoft.Office.Interop.Word.Range rngStory, string strSearch, string strReplace)
        {
            rngStory.Find.ClearFormatting();
            rngStory.Find.Replacement.ClearFormatting();
            rngStory.Find.Text = strSearch;
            rngStory.Find.Replacement.Text = strReplace;
            rngStory.Find.Wrap = WdFindWrap.wdFindContinue;
            object Missing = System.Reflection.Missing.Value;

            object arg1 = Missing; // Find Pattern
            object arg2 = Missing; //MatchCase
            object arg3 = Missing; //MatchWholeWord
            object arg4 = Missing; //MatchWildcards
            object arg5 = Missing; //MatchSoundsLike
            object arg6 = Missing; //MatchAllWordForms
            object arg7 = Missing; //Forward
            object arg8 = Missing; //Wrap
            object arg9 = Missing; //Format
            object arg10 = Missing; //ReplaceWith
            object arg11 = WdReplace.wdReplaceAll; //Replace
            object arg12 = Missing; //MatchKashida
            object arg13 = Missing; //MatchDiacritics
            object arg14 = Missing; //MatchAlefHamza
            object arg15 = Missing; //MatchControl

            rngStory.Find.Execute(ref arg1, ref arg2, ref arg3, ref arg4, ref arg5, ref arg6, ref arg7, ref arg8, ref arg9, ref arg10, ref arg11, ref arg12, ref arg13, ref arg14, ref arg15);
        }

        // Main routine to find text and replace it,
        //   var app = new Microsoft.Office.Interop.Word.Application();
        public static void FindReplaceAnywhere(Microsoft.Office.Interop.Word.Application app, string findText, string replaceText)
        {
            // http://forums.asp.net/p/1501791/3739871.aspx
            var doc = app.ActiveDocument;

            // Fix the skipped blank Header/Footer problem
            //    http://msdn.microsoft.com/en-us/library/aa211923(office.11).aspx
            Microsoft.Office.Interop.Word.WdStoryType lngJunk = doc.Sections[1].Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range.StoryType;

            // Iterate through all story types in the current document
            foreach (Microsoft.Office.Interop.Word.Range rngStory in doc.StoryRanges)
            {

                // Iterate through all linked stories
                var internalRangeStory = rngStory;

                do
                {
                    searchAndReplaceInStory(internalRangeStory, findText, replaceText);

                    try
                    {
                        //   6 , 7 , 8 , 9 , 10 , 11 -- http://msdn.microsoft.com/en-us/library/aa211923(office.11).aspx
                        switch (internalRangeStory.StoryType)
                        {
                            case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesHeaderStory: // 6
                            case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryHeaderStory:   // 7
                            case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesFooterStory: // 8
                            case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryFooterStory:   // 9
                            case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageHeaderStory: // 10
                            case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageFooterStory: // 11

                                if (internalRangeStory.ShapeRange.Count > 0)
                                {
                                    foreach (Microsoft.Office.Interop.Word.Shape oShp in internalRangeStory.ShapeRange)
                                    {
                                        if (oShp.TextFrame.HasText != 0)
                                        {
                                            searchAndReplaceInStory(oShp.TextFrame.TextRange, findText, replaceText);
                                        }
                                    }
                                }
                                break;

                            default:
                                break;
                        }
                    }
                    catch
                    {
                        // On Error Resume Next
                    }

                    // ON ERROR GOTO 0 -- http://www.harding.edu/fmccown/vbnet_csharp_comparison.html

                    // Get next linked story (if any)
                    internalRangeStory = internalRangeStory.NextStoryRange;
                } while (internalRangeStory != null); // http://www.harding.edu/fmccown/vbnet_csharp_comparison.html
            }

        }

برای تهیه pdf نیز می‌توانید به کد زیر مراجعه کنید:
public static void getFileDocxInPdf()
        {
            // Create a new Microsoft Word application object
            Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application();

            // C# doesn't have optional arguments so we'll need a dummy value
            object oMissing = System.Reflection.Missing.Value;

            // Get list of Word files in specified directory
            DirectoryInfo dirInfo = new DirectoryInfo(@"D:\temp");
            FileInfo[] wordFiles = dirInfo.GetFiles("*.docx");

            word.Visible = false;
            word.ScreenUpdating = false;

            foreach (FileInfo wordFile in wordFiles)
            {
                // Cast as Object for word Open method
                Object filename = (Object)wordFile.FullName;

                // Use the dummy value as a placeholder for optional arguments
                Microsoft.Office.Interop.Word.Document doc = word.Documents.Open(ref filename, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing);
                doc.Activate();

                object outputFileName = wordFile.FullName.Replace(".docx", ".pdf");
                object fileFormat = WdSaveFormat.wdFormatPDF;

                // Save document into PDF Format
                doc.SaveAs(ref outputFileName,
                    ref fileFormat, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing);

                // Close the Word document, but leave the Word application open.
                // doc has to be cast to type _Document so that it will find the
                // correct Close method.                
                object saveChanges = WdSaveOptions.wdDoNotSaveChanges;
                ((_Document)doc).Close(ref saveChanges, ref oMissing, ref oMissing);
                doc = null;
            }

            // word has to be cast to type _Application so that it will find
            // the correct Quit method.
            ((_Application)word).Quit(ref oMissing, ref oMissing, ref oMissing);
            word = null;
        }
نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
سلام خدمت دوستان
بنده PersianDatePicker رو در پروژه خودم به کار گرفتم اما ظاهرا زمانی که بجای استفاده از Post Model از پیاده سازی PostViewModel استفاده می‌کنیم، datepicker به درستی عمل نمیکنه، البته در نمایش تقویم و انتخاب تاریخ مشکلی نیست بلکه در تبدیل شمسی به میلادی و ارسال به سرور مشکل داره و نهایتا فیلد addDate مقدار {01/01/0001 12:00:00 ق.ظ} رو میگیره.

Post ViewModel به صورت:
namespace MvcAppPersianDatePicker.ViewModels
{
    public class PostViewModel
    {
        public Post post { get; set; }
    }
}

و Home کنترلر:

namespace MvcAppPersianDatePicker.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            var model = new PostViewModel();
            model.post = new Post() { AddDate = DateTime.Now.AddDays(-1), Title = "تست" };
            return View(model);
        }

        [HttpPost]
        public ActionResult Index(PostViewModel model)
        {
            if (ModelState.IsValid)
            {
                // todo: save data
                return RedirectToAction("Index");
            }

            return View(model);
        }
    }
}


مطالب دوره‌ها
آشنایی با AOP IL Weaving
IL Weaving در AOP به معنای اتصال Aspects تعریف شده، پس از کامپایل برنامه به فایل‌های باینری نهایی است. اینکار با ویرایش اسمبلی‌ها در سطح IL یا کد میانی صورت می‌گیرد. بنابراین در این حالت دیگر یک محصور کننده و پروکسی، در این بین جهت مزین سازی اشیاء، در زمان اجرای برنامه تشکیل نمی‌شود. بلکه فراخوانی Aspects به معنای فراخوانی واقعی قطعه کدهایی است که به اسمبلی‌های برنامه پس از کامپایل آن‌ها تزریق شده‌اند.
در دنیای دات نت، ابزارهای چندی امکان انجام IL Weaving را فراهم ساخته‌اند که تعدادی از آن‌ها به قرار ذیل هستند:
- PostSharp
- LOOM.NET
- Wicca
و ...

در بین این‌ها، PostSharp معروفترین فریم ورک AOP بوده و در ادامه از آن استفاده خواهیم کرد.


پیشنیاز ادامه بحث

ابتدا یک پروژه کنسول جدید را آغاز کرده و سپس در خط فرمان پاور شل نوگت در VS.NET دستور ذیل را اجرا کنید:
 PM> Install-Package PostSharp
به این ترتیب ارجاعی به PostSharp به پروژه جاری اضافه خواهد شد. البته حجم آن نسبتا بالا است؛ نزدیک به 20 مگ به همراه ابزارهای تزریق کد همراه با آن. مجوز استفاده از آن نیز تجاری و مدت دار است.


مراحل ایجاد یک Aspect برای پروسه IL Code Weaving

ابتدا یک کلاس پایه مشتق شده از کلاسی ویژه موجود در یکی از فریم ورک‌های AOP باید تعریف شود. مرحله بعد، کار اتصال این Aspect می‌باشد که توسط پردازشگر ثانویه IL Code Weaving انجام می‌شود.
در ادامه قصد داریم همان مثال LoggingInterceptor قسمت دوم این سری را با استفاده از IL Code Weaving پیاده سازی کنیم.
using System;

namespace AOP03
{
    public class MyType
    {
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new MyType().DoSomething("Test", 1);
        }
    }
}
کدهای برنامه همانند قبل است. اما اینبار بجای استفاده از Interceptors، با ارث بری از کلاس OnMethodBoundaryAspect کتابخانه PostSharp شروع خواهیم کرد:
using System;
using PostSharp.Aspects;

namespace AOP03
{
    [Serializable]
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            Console.WriteLine("On Entry");
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            Console.WriteLine("On Exit");
        }

        public override void OnSuccess(MethodExecutionArgs args)
        {
            Console.WriteLine("On Success");
        }

        public override void OnException(MethodExecutionArgs args)
        {
            Console.WriteLine("On Exception");
        }
    }
}
نیاز است این کلاس توسط ویژگی Serializable مزین شود تا توسط PostSharp قابل استفاده گردد. همانطور که ملاحظه می‌کنید، مراحل مختلف اجرای یک Aspcet در اینجا با override متدهای کلاس پایه OnMethodBoundaryAspect پیاده سازی شده‌اند. این مراحل را پیشتر در زمان استفاده از Interceptors توسط try/finally/catch بررسی کرده بودیم.
اکنون اگر برنامه را اجرا کنیم، اتفاق خاصی رخ نداده و همان خروجی معمول متد DoSomething در کنسول نمایش داده خواهد شد. بنابراین در مرحله بعد نیاز است تا این Aspect را به کدهای برنامه متصل کنیم.
کلاس OnMethodBoundaryAspect در کتابخانه PostSharp، از کلاس MulticastAttribute مشتق می‌شود. بنابراین LoggingAspect ایی را که ایجاد کرده‌ایم نیز می‌توان به صورت یک ویژگی به متد‌های مورد نظر خود افزود:
    public class MyType
    {
        [LoggingAspect]
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }
اکنون اگر برنامه را اجرا کنیم، با خروجی زیر مواجه خواهیم شد:
 On Entry
DoSomething(Test, 1);
On Success
On Exit
برای اینکه بتوان عملیات رخ داده را بهتر توضیح داد می‌تواند از یک دی‌کامپایلر مانند برنامه معروف Reflector استفاده کرد:
public void DoSomething(string data, int i)
{
    <>z__Aspects.a0.OnEntry(null);
    try
    {
        Console.WriteLine("DoSomething({0}, {1});", data, i);
        <>z__Aspects.a0.OnSuccess(null);
    }
    catch (Exception)
    {
        <>z__Aspects.a0.OnException(null);
        throw;
    }
    finally
    {
        <>z__Aspects.a0.OnExit(null);
    }
}
این کدی است که به صورت پویا توسط PostSharp به اسمبلی نهایی فایل اجرایی برنامه تزریق شده است.

خوب! این یک روش اتصال Aspects به برنامه است. اما اگر همانند Interceptors بخواهیم Aspect تعریف شده را سراسری اعمال کنیم چکار باید کرد (بدون نیاز به قرار دادن ویژگی بر روی تک تک متدها)؟
برای اینکار ابتدا نیاز است میدان عملکرد Aspect تعریف شده را توسط ویژگی MulticastAttributeUsage محدود کنیم تا برای مثال به خواص اعمال نشوند:
 [Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, TargetMemberAttributes = MulticastAttributes.Instance)]
public class LoggingAspect : OnMethodBoundaryAspect
سپس فایل AssemblyInfo.cs استاندارد پروژه را گشوده و سطر زیر را به آن اضافه کنید:
 [assembly: LoggingAspect(AttributeTargetTypes = "AOP03.*")]
توسط AttributeTargetTypes می‌توان اعمال این Aspect را به یک فضای نام خاص نیز محدود کرد.

مزیت روش IL Code Weaving نسبت به Interceptors، کارآیی و سرعت بالاتر است. از این جهت که کدهایی که قرار است اجرا شوند، پیشتر در اسمبلی برنامه قرار گرفته‌اند و نیازی نیست تا در زمان اجرا، کدی به برنامه به صورت پویا تزریق گردد.
مطالب
Feature Toggle
در بسیاری از پروژه‌های نرم افزاری ما ممکن است یک امکان (Feature) را برای بازه‌ی زمانی خاصی بنا به درخواست مشتری یا ضوابط خودمان نیاز داشته باشیم و در زمان دیگری یا برای مشتری دیگری نیاز نداشته باشیم و باید قابلیت مورد نظر غیر فعال باشد. یا حتی ممکن است قابلیتی را به تازگی افزوده باشیم، ولی در زمان اجرا خطایی داشته باشد و مجبور باشیم فورا آن را از دسترش خارج کنیم. به این فرایند در اصلاح Feature Toggle میگویند که البته نام‌های دیگری از جمله (feature switch, feature flag, feature flipper, conditional feature ) هم دارد. مارتین فاولر آن را این چنین تعریف میکند:
"Feature Toggling" is a set of patterns which can help a team to deliver new functionality to users rapidly but safely
"Feature Toggling" تکنیک قدرتمندی است که به ما این اجازه را میدهد تا رفتار سیستم را بدون تغییر کد عوض کنیم.
ساده‌ترین الگوی پیاده سازی Feature Toggling چیزی شبیه به نمونه زیر می‌باشد. یک اینترفیس که باید مشخصه یا متدی برای بررسی فعال بودن و نبودن داشته باشد.
 public interface IFeatureToggle {
   bool FeatureEnabled {get;}  
}
برای اینکه اصل قابل تنظیم بودن (Configurable) را هم رعایت کرده باشیم، بررسی فعال بودن کامپوننت را از طریق وب کانفیگ انجام میدهیم.
class ShowMessageToggle : IFeatureToggle  
 {   
    public bool FeatureEnabled {
     get{
           return  bool.Parse(ConfigurationManager.AppSettings["ShowMessageEnabled"]);      
        }
 }
و حالا کافی است در هر جایی که قصد استفاده از آن کلاس را داشته باشیم، فعال بودن و نبودنش را بررسی کنیم.
class Program
 {
 static void Main(string[] args)
   {
     var toggle = new ShowMessageToggle();
     if (toggle.FeatureEnabled)
     {
        Console.WriteLine("This feature is enabled")
     }
     else
     {  
         Console.WriteLine("This feature is disabled");            
     }
   }  
 }
مثال بالا ساده‌ترین نحوه‌ی استفاده از Feature Toggling بود. اما شبیه الگوی IOC که ابزارهای زیادی برای پیاده سازی آن عرضه شده است، برای این الگو هم ابزارهای جالبی تولید شده است که به‌راحتی این قابلیت را در پروژه‌های ما ایجاد و نگهداری میکند. لیستی از این ابزارها و پکیج‌ها را از اینجا میتوانید ببینید.
بطور مثال برای کار با FeatureToggle ابتدا آنرا با دستور زیر نصب میکنیم:
Install-Package FeatureToggle
سپس کلاس مورد نظر را از کلاس پایه SimpleFeatureToggle ارث بری میکنیم.
MyAwesomeFeature : SimpleFeatureToggle {}
در  فایل کانفیگ برنامه یک تنظیم جدید را با نام کلاس مذکور ایجاد میکنیم:
<add key="MyAwesomeFeature " value="true" />
حالا هرجای برنامه نیاز داشتید میتوانید فعال بودن و نبودن قابلیت‌های مختلف را بررسی کنید.
if (!myAwesomeFeature.FeatureEnabled)
{ // code to disable stuff (e.g. UI buttons, etc) }
شما به همین سادگی و سرعت، میتوانید قابلیت Feature Toggle را در پروژه‌هایتان راه اندازی کنید.

لیست منابع
 http://nugetmusthaves.com/Tag/toggle 
http://featureflags.io/dotnet-feature-flags/ 
http://martinfowler.com/articles/feature-toggles.html
نظرات مطالب
استفاده از Interop.word برای جایگزین کردن مقادیر در تمامی فایل (Footer - Header - ... )
سلام؛ من ازهمین روش شما استفاده کردم چند وقت پیش به وسیله  bookmark:
        private Document oDoc;
        public void createdoc1()
        {
            var realpath="~/template";
            var filePath = Path.Combine(HttpContext.Current.Server.MapPath("~/template"), Lcourseid.Text + ".doc");
            var oWordApplication = new Application();
            DirectoryInfo dir = new DirectoryInfo(Server.MapPath(realpath));
            foreach (FileInfo files in dir.GetFiles())
            {
                files.Delete();
            }
            // To invoke MyMethod with the default argument value, pass  
            // Missing.Value for the optional parameter.
            object missing = System.Reflection.Missing.Value;

            //object fileName = ConfigurationManager.AppSettings["DocxPath"];@"C:\DocXExample.docx";
            string fileName = @"D:\template1.dot";
            //string fileName1 = @"D:\sss.doc";
            object newTemplate = false;

 object docType = 0;
            object isVisible = true;

            //System.Reflection.Missing.Value is used here for telling that method to use default parameter values when method execution
            oDoc = oWordApplication.Documents.Open(fileName, newTemplate, docType, isVisible, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
                ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
           // usable in earlier versions of Microsoft Word v2003  v11
          //  if(Convert.ToInt16(oWordApplication.Version) >=11)
            {
             //Sets or returns a Boolean that represents whether a document is being viewed in reading layout view.
              oDoc.ActiveWindow.View.ReadingLayout = false;
           }

            //The active window is the window that currently has focus.If there are no windows open, an exception is thrown.
            //microsoft.office.tools.word.
            oDoc.Activate();

            if (oDoc.Bookmarks.Exists("Title"))
            {
                oDoc.Bookmarks["Title"].Range.Text = "Test Field Entry from webform";
                oDoc.Bookmarks["Address"].Range.Text = "Address Field Entry from webform";
            }

            oDoc.SaveAs(filePath, ref missing);
            oWordApplication.Documents.Close(ref missing, ref missing, ref missing);
            //oWordApplication.Quit(ref SaveChanges, ref missing, ref missing, ref missing);
            ProcessRequest(filePath, Lcourseid.Text);
ولی این روش مشکلاتی هم داره. اول اینکه باید روی سرور تنظیمات خاصی رو انجام بدی. البته از تنظیمات منظور تنظیمات دسترسی کاربران هست. ولی استفاده از داک ایکس بیشتر استقبال میشه چون دردسرش کمتره.
مطالب
ASP.NET Web API - قسمت پنجم
مدیریت کدهای وضعیت در Web API
تمامی پاسخ‌های دریافتی از Web API توسط Client، باید در قالب کدهای وضعیت HTTP باشند. دو کلاس جدید با نام‌های HttpResponseMessage و HttpResponseException همراه با ASP.NET MVC 4 معرفی شده اند که ارسال کدهای وضعیت پردازش درخواست به Client را آسان می‌سازند. به عنوان مثال، ارسال وضعیت برای چهار عمل اصلی بازیابی، ایجاد، آپدیت و حذف رکورد را بررسی می‌کنیم.

بازیابی رکورد
بر اساس مستندات پروتوکل HTTP، در صورتی که منبع درخواستی Client پیدا نشد، باید کد وضعیت 404 برگشت داده شود. این حالت را در متد ذیل پیاده سازی کرده ایم.
public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); 
    }
    return item;
}
در صورتی که رکوردی با مشخصه‌ی درخواستی پیدا نشد، با استفاده از کلاس HttpResponseException، خطایی به Client ارسال خواهد شد. پارامتر سازنده‌ی این کلاس، شی ای از نوع کلاس HttpResponseMessage است. سازنده‌ی کلاس HttpResponseMessage، مقداری از یک enum با نام HttpStatusCode را می‌پذیرد. مقدار NotFound، نشان از خطای 404 است و زمانی به کار می‌رود که منبع درخواستی وجود نداشته باشد. اگر محصول درخواست شده یافت شد، در قالب JSON برگشت داده می‌شود. در شکل ذیل، پاسخ دریافتی در زمان درخواست محصولی که وجود ندارد را ملاحظه می‌کنید.

ایجاد رکورد
برای ایجاد رکورد، Client درخواستی از نوع POST را همراه با داده‌های رکورد در بدنه‌ی درخواست به Server ارسال می‌کند. در ذیل، پیاده سازی ساده ای از این حالت را مشاهده می‌کنید.
public Product PostProduct(Product item)
{
    item = repository.Add(item);
    return item;
}
 این پیاده سازی کار می‌کند اما کمبودهایی دارد:
  • کد وضعیت پردازش درخواست: به طور پیش فرض، Web API، کد 200 را در پاسخ ارسال می‌کند، اما بر اساس مستندات پروتوکل HTTP، زمانی که یک درخواست از نوع POST منجر به تولید منبعی می-شود، Server باید کد وضعیت 201 را به Client برگشت بدهد.
  • آدرس منبع جدید ایجاد شده: بر اساس مستندات پروتوکل HTTP، زمانی که منبعی بر روی Server ایجاد می‌شود، باید آدرس منبع جدید ایجاد شده از طریق هدر Location به Client ارسال شود.
با توجه به این توضیحات، متد قبل به صورت ذیل در خواهد آمد.
public HttpResponseMessage PostProduct(Product item)
{
    item = repository.Add(item);
    var response = Request.CreateResponse(HttpStatusCode.Created, item);

    string uri = Url.Link("DefaultApi", new { id = item.Id });
    response.Headers.Location = new Uri(uri);
    return response;
}
همان طور که ملاحظه می‌کنید، خروجی متد از نوع کلاس HttpResponseMessage است، چون با استفاده از این نوع می‌توانیم جزئیات مورد نیاز را در مورد نتیجه‌ی پردازش درخواست به مرورگر ارسال کنیم. همچنین، داده‌های رکورد جدید نیز در بدنه‌ی پاسخ، با یک فرمت مناسب مانند XML یا JSON برگشت داده می‌شوند. با استفاده از متد CreateResponse کلاس Request و پاس دادن کد وضعیت و شی ای که قصد داریم به Client ارسال شود به این متد، شی ای از نوع کلاس HttpResponseMessage ایجاد می‌کنیم. آدرس منبع جدید نیز با استفاده از response.Headers.Location مشخص شده است. نمونه ای از پاسخ دریافت شده در سمت Client به صورت ذیل است.


آپدیت رکورد
آپدیت با استفاده از درخواست‌های از نوع PUT انجام می‌گیرد. یک مثال ساده در این مورد.

public void PutProduct(int id, Product product)
{
    product.Id = id;
    if (!repository.Update(product))
    {
        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
    }
}
نام متد با عبارت Put آغاز شده است. بنابراین توسط Web API برای پردازش درخواست‌های از نوع PUT در نظر گرفته می‌شود. متد قبل، دو پارامتر ورودی دارد. id برای مشخصه‌ی محصول، و محصول آپدیت شده که در پارامتر دوم قرار می‌گیرد. مقدار پارامتر id از آدرس دریافت می‌شود و مقدار پارامتر product از بدنه‌ی درخواست. به طور پیش فرض، Web API، مقدار داده هایی با نوع ساده مانند int، string و bool را از طریق route، و مقدار نوع‌های پیچیده‌تر مانند داده‌های یک کلاس را از بدنه‌ی درخواست می‌خواند.

حذف یک رکورد
حذف یک رکورد، با استفاده از درخواست‌های از نوع DELETE انجام می‌گیرد. یک مثال ساده در این مورد.
public HttpResponseMessage DeleteProduct(int id)
{
    repository.Remove(id);
    return new HttpResponseMessage(HttpStatusCode.NoContent);
}
بر اساس مستندات پروتکل HTTP، اگر منبعی که Client قصد حذف آن را دارد از پیش حذف شده است، نباید خطایی به وی گزارش شود. معمولاً در متدهایی که وظیفه‌ی حذف منبع را بر عهده دارند، کد 204 مبنی بر پردازش کامل درخواست و پاسخ خالی برگشت داده می‌شود. این کد با استفاد از مقدار NoContent برای HttpStatusCode مشخص می‌شود.

فراخوانی متدها و مدیریت کدهای وضعیت HTTP در سمت Client
حال ببینیم چگونه می‌توان از متدهای قبل در سمت Client استفاده و خطاهای احتمالی آنها را مدیریت کرد.
بهتر است مثال را برای حالتی که در آن رکوردی آپدیت می‌شود بررسی کنیم. کدهای مورد نیاز برای فراخوانی متد PutProduct در سمت Client به صورت ذیل است.
var id = $("#myTextBox").val();

$.ajax({
  url: "/api/Test/" + id,
   type: 'PUT',
   data: { Id: "1", Name: "Tomato Soup", Category: "Groceries", Price: "1.39M" },
   cache: false,
   statusCode: {
       200: function (data) {
           alert("آپدیت انجام شد");
        },
       404:
            function () {
                alert("خطا در آپدیت");
            }
      }
});
از متدهای get، getJson یا post در jQuery نمی‌توان برای عمل آپدیت استفاده نمود، چون Web API انتظار دارد تا نام فعل درخواستی، PUT باشد. اما با استفاده از متد ajax و ذکر نام فعل در پارامتر type آن می‌توان نوع درخواست را PUT تعریف کرد. خط 5 بدین منظور است. از طریق خصیصه‌ی statusCode نیز می‌توان کدهای وضعیت مختلف HTTP را بررسی کرد. دو کد 200 و 404 که به ترتیب نشان از موفقیت و عدم موفقیت در آپدیت رکورد هستند تعریف شده و پیغام مناسب به کاربر نمایش داده می‌شود.
در حالتی که آپدیت با موفقیت همراه باشد، بدنه‌ی پاسخ به شکل ذیل است.


  و در صورتی که خطایی رخ دهد، بدنه‌ی پاسخ دریافتی به صورت ذیل خواهد بود.


مطالب
MongoDb در سی شارپ (بخش نهم)
سال‌های مدیدی است که به طراحی پایگاه‌های sql پرداخته و تجاربی آموخته‌ایم. کتاب‌ها و مقالات زیادی در اینباره منتشر شده‌اند. از این‌رو در نحوه طراحی دیتابیس‌های رابطه‌ای اطلاعات زیادی کسب و مسائل زیادی را از این راه حل نموده‌ایم؛ ولی با ورود دیتابیس‌های NoSql و تنوع زیاد آن‌ها و روش‌های متنوعی که هر کدام از آن‌ها به طور جداگانه دارند باعث شد تجربه سال‌ها فعالیت و مدل ذهنی که داشتیم به یکباره تغییر کند و گاها بیشتر باعث گیج شدن می‌گردد. از این‌رو در این مقاله سعی داریم تکنیک‌ها مدل سازی اسناد را در دیتابیس مونگو، بررسی کنیم و مزایا و معایب هر یک را برشماریم.
در دیتابیس‌های قدیم، تمرکز بر روی نوشتن بود تا با کمترین افزونگی و تکرار و رعایت اصول ACID، اطلاعات را ذخیره نماییم. ولی در حال حاضر به دلیل دسترسی به فضاهای ذخیره سازی بزرگتر و همچنین افزایش ترافیک شبکه در واکشی دیتاها، قضیه عکس شده و تمرکز دیتابیس‌های NoSql بر روی خواندن میباشد. پس باید فاکتورهای مدل سازی طوری باشد تا خواندن در سریعترین حد امکان قرار بگیرد. البته مواردی چون حذف و به روزرسانی هم باید در این مورد بررسی شوند.
ارتباط اسناد با یکدیگر:
ارتباط اسناد از دو طریق امکان پذیر است:
  • حالت ارجاع  : شماره سند یا Object Id را شامل شده و در صورتیکه به اطلاعاتی نیاز داشتید، باید اطلاعات آن را در یک درخواست جداگانه واکشی نمایید. چون مونگو شامل جوین نبوده و جوین‌ها باید در سطح اپلیکیشن مدیریت شوند.
{
fname:'ali',
lname:'yeganeh',
accounts:[454354353,3455435]
}

  • حالت جاسازی سند (یا اسناد تو در تو) Embed :  در این حالت سند مورد نظر اطلاعات سند دیگری را در درون خود نگه میدارد. در این حالت به هیچ جوینی نیازی نیست و اطلاعات وابسته، به همراه خود سند اصلی واکشی می‌شوند. این نکته باید مورد توجه قرار بگیرد که مونگو یک دیتابیس غیر اتمیک هست و در صورتیکه اصل دیتا تغییر کند، تغییر یا به روزرسانی در سندهای Embed انجام نخواهد شد و در صورت نیاز باید خودتان به طور دستی آن را کنترل نمایید.
{
fname:'ali',
lname:'yeganeh',
accounts:[
{
  username:"ali",
  password:"123"
},
{
  username:"reza",
  password:"456"
}
]
}

مدل هایی با ارتباط یک به یک : 
در این نوع مدل سازی، دو سند داریم که یکی از آن‌ها Principle و دیگری Dependent محسوب می‌شود. برای ذخیره سازی آن‌ها عموما از حالت Embed استفاده میشود. در این حالت چون ارتباط بین دو سند به صورت یک به یک میباشد، در واقع این امکان وجود دارد تا سند مادری به طور جداگانه وجود نداشته باشد و همان سند به صورت Embed ذخیره میشود. در این حالت مشکلی از لحاظ اتمیک نبودن مونگو پیش نمیاید و  ویرایش راحت‌تری خواهد داشت.
مدل‌هایی با ارتباط یک به چند:
این اسناد را می‌توان به دو حالت بالا بر حسب نیازمندی سیستم ذخیره کرد. فرض کنید مثال زیر را که در سایت مونگو هم عنوان شده‌‌است، داریم:
book
{
     name:'Scarlet Letter",
     Language:"English",
     Pages:124,
...
}

publisher
{
   name : "Orielly",
   ...

}
در این حالت هر کتاب باید ارتباطی با ناشر خود داشته باشد. در صورتیکه به صورت Embed داخل سند قرار بگیرد و هر کتابی شامل اطلاعات ناشر خود باشد، نکات زیر مورد بررسی قرار میگیرند:
book
{
     name:'Scarlet Letter",
     Language:"English",
     Pages:124,
...,
publisher:
{
   name : "Orielly",
   ...

}
}

نکات مثبت:
  1. در این حالت در صورتیکه واکشی هر کتاب به همراه اطلاعات ناشر را نیاز داشته باشیم و یا پرس وجوهای ترکیبی نیاز باشد، در سریعترین زمان ممکن واکشی انجام خواهد شد.
  2. درج و مدیریت آن راحت‌تر خواهد بود.
نکات منفی:
  1. در صورتیکه اطلاعات ناشر نیاز به تغییرات اساسی داشته باشد و باید در تمامی سندها اصلاح گردد، باید تمامی اسناد مربوط به اطلاعات کتاب به روزرسانی شوند که هزینه سنگین‌تری را خواهد داشت.
  2. دیتای تکراری زیادی ذخیره خواهد شد و در نتیجه حافظه بیشتری را میطلبد.
  3. در صورتیکه تنها به اطلاعات ناشر نیاز باشد و اطلاعات ناشر در سند دیگری وجود نداشته باشد و فقط در سند کتاب وجود داشته باشد، واکشی آن هزینه سنگین‌تری را خواهد طلبید. به همین جهت توصیه میشود در صورتیکه دیتای شما می‌تواند به صورت یک موجودیت مستقل هم عمل کند، اطلاعات آن در سند دیگری که من به آن سند اصلی میگویم ذخیره شوند تا نمونه‌ها از روی آخرین ویرایش آن ساخته شوند و موقعی‌که تنها به واکشی آن اطلاعات نیاز است، همان‌ها بیرون کشیده شوند.
در روشی دیگری میتوان ارجاعی از ناشر را به شکل زیر در کتاب نگهداری کرد:
book
{
     name:'Scarlet Letter",
     Language:"English",
     Pages:124,
...,
publisher:1212121
}
نکات مثبت:
  1. عدم وجود تکرار اطلاعات
  2. چون تنها یک سند برای ویرایش وجود دارد، نیازی به اصلاح اسناد توکار نیست و ویرایش، هزینه کمتری خواهد داشت.
نکات منفی:
  1. عدم وجود جوین: در صورتیکه نیاز به جوین بزرگی باشد، این نوع جوین باید در سطح برنامه شما انجام شود و هزینه بر خواهد بود.

نگهداری نام کتاب‌ها در ناشر
انعطاف مونگو برای ایجاد مدل، گزینه‌های زیادی را پیش رو میگذارد و واقعا مدلسازی را بیشتر از قبل، چالش برانگیز میکند. در حالت دیگر میتوان اطلاعات کتاب را به صورت ارجاع، در سند ناشر نگهداری کرد. به عنوان مثال زمانیکه نیاز داریم کتب منتشرشده یک ناشر را ببینیم، شاید این گزینه بهتر باشد. البته در این حالت باید بتوان ارجاعات به کتاب را در تعداد محدودی نگهداری کرد؛ در غیر این صورت با تعداد زیادی ارجاع که شاید هیچگاه نیازی هم به آن‌ها نیست، خواهیم رسید و در این حالت شاید ارجاع به ناشر در سند کتاب بسیار بهتر به نظر برسد. البته میتوان در این حالت ناشر تنها به تعداد معدودی از آخرین کتابهایش دسترسی داشته باشد تا کاربر بتواند آخرین کتاب‌های منتشر شده‌ی ناشر را ببیند. 
حال با اطلاعات بالا چگونه مدلسازی کنیم؟
همانطور که گفتیم ابتدا تمرکز شما باید برای خواندن اطلاعات باشد و سپس معیارهایی چون به روزرسانی نیز بررسی گردند. به عنوان نمونه اطلاعات یک پست در وبلاگ را در نظر بگیرید. این سند شامل سندهای توکاری چون دسته بندی، اطلاعات نویسنده، معیارهایی چون امتیازدهی و بخش نظرات میباشد. در این حالت چون همه عناصر قرار است با یکدیگر بیرون کشیده شوند و در واقع تنها با یک سند سروکار داریم، کار بسیار سریعتر و راحت‌تر است. پس این ساختار گزینه مناسبی برای نمایش است:
Post
{
title:"C#",
body:"About C#",
tags:['C#','.Net','microsoft'],
Categories:[{name:'Programming'}],
votes:[{rate:3,user:42342},{rate:5,user:423445},...],
comments:[
{
text:"my comment1",
time:"10/2/1396",...},
...

]
}

حال این تصور را داشته باشید که ما تنها یک پست را نشان نمیدهیم و بلکه پست‌ها به صورت یک لیست قرار است نمایش داده شوند و با گزینه‌ی مشاهده‌ی مطلب می‌توانیم یک پست را به صورت کامل ببینیم. در این صورت همه اطلاعات همانند قبل هستند، بجز بخش نظرات که دیگر در این حالت کاربردی ندارد و دیتای اضافی است که به ناچار باید خوانده شود. پس در این حالت میگوییم این مدل برای خواندن مناسب نیست، چون باید تمام نظرات اسنادی که در لیست قرار دارند هم خوانده شوند. پس باید بخش نظرات را از سند پست وبلاگ جدا کنیم.
{
POST:45453,
count:35,
comments:[...]
}
سپس میگوییم هر سند نهایتا 16 مگابایت اطلاعات را نگهداری میکند و هم اینکه تعداد نظرات ممکن است بسیار زیاد باشند. پس هر سند را به تعدادی نظر محدود میکنیم به این حالت میگویند داریم یک Bucket میسازیم و مثلا هر باکت را به 100 کامنت محدود میکنیم. تا به الان وضعیت طراحی بهتری نسبت به قبل پیدا کردیم:
{
post:345345,
capacity:100,
count:35,
bucket:2,
comments:[...]
}
در این حالت حتی میتوانیم کامنتها را صفحه بندی کرده و در هر صفحه یک باکت را بخوانیم. برای نمایش این دو مورد آخری برای جداسازی دیتا بسیار خوب است. حتی میتوان یک کامنت را به همراه پاسخ‌های آن که به صورت درخت واره قرار گرفته اند نیز در یک سند جداگانه ذخیره کرد.
نکاتی که باید در حین طراحی در نظر بگیرید:
  1. همیشه به این نکته توجه داشته باشید که نباید بگذارید تعداد آرایه‌های یک سند خیلی بزرگ شوند. در غیر اینصورت کارآیی مونگو به خصوص در حین ویرایش سند پایین خواهد آمد. در حین ویرایش، اگر سندی از اندازه‌ی خود بزرگتر نشود، مشکلی پیش نمیاید ولی اگر فضایی بیش از آنچه که  قبلا داشته به آن اضافه شود، سند نیاز به جابجایی و گسترش فضا خواهد داشت. در این حالت باید مونگو سند را به جای دیگری که فضای کافی برای آن وجود دارد، انتقال بدهد و میزان Disk Fragment به طبع بالا خواهد رفت. همچنین اندیس‌های آرایه‌ای هم با جابجا شدن دیتا نیاز به، به روزرسانی خواهند داشت و زمانی هم صرف به روزرسانی اندیس‌ها خواهد شد.
  2. مدیر محصول مونگو اظهار نظر صریحی در این مورد نکرد‌ه‌است، ولی به نظر می‌رسد نوع فرمت BSON از یک اسکن خطی در حافظه استفاده میکند و زمان بیشتری صرف پیدا کردن المان‌های انتهایی در آرایه خواهد شد؛ پس بیشتر عملیات در این نوع سند، با کندی مواجه خواهند شد. با توجه به کامنت‌هایی که در سایت‌ها و شبکه‌های اجتماعی یافت شده‌است، آرایه ای با بیش از صدهزار آیتم ساده میتواند آسیب زا باشد؛ به همین دلیل توصیه میشود که اگر بیش از صدهزار آیتم نیاز است، از همان حالت Bucket استفاده شود.
  3. استفاده از اندیس‌ها هم سابقه‌ی دیرینه‌ای داشته و سعی کنید کوئری هایی بزنید که بر اساس اندیس‌های تعریف شده باشند تا واکشی دیتا سریعتر شود. پس نحوه کوئری نویسی و انتخاب فیلدی که اندیس میشود بسیار مهم است.
  4. استفاده از Projection تاثیری بر خواندن اسناد ندارد و هر سند به طور کامل واکشی می‌شود. projection تنها در بار‌ه‌ی ترافیک یا انتقال حجم کمتری از اطلاعات به سمت کلاینت تاثیرگذار میباشد. پس استفاده از projection بجای جدا سازی اسناد را دنبال نکنید.
مطالب
GraphQL Mutations در ASP.NET Core ( عملیات POST, PUT, DELETE )
Mutation‌ها در GraphQL، اکشن‌های می‌باشند که با استفاده از آن‌ها Add ، Delete و Update را انجام می‌دهیم. تا کنون ما query ‌های بازیابی اطلاعات را اجرا کرده‌ایم. در این قسمت می‌خواهیم در رابطه با Mutation داده‌ها صحبت کنیم.


Input Types and Schema Enhancing for the GraphQL Mutations 

کار را با ایجاد کردن یک کلاس جدید به نام OwnerInputType در پوشه Types، شروع می‌کنیم:
public class OwnerInputType : InputObjectGraphType
{
    public OwnerInputType()
    {
        Name = "ownerInput";
        Field<NonNullGraphType<StringGraphType>>("name");
        Field<NonNullGraphType<StringGraphType>>("address");
    }
}
این همان typeی می‌باشد که قرار است به عنوان یک آرگومان برای Mutation از طرف کلاینت ارسال شود. این کلاس از InputObjectGraphType مشتق شده است؛ نه از ObjectGraphType همانند Typeهای تعریف شده قبل.
در سازنده کلاس، خصوصیت Name را مقدار دهی و دو فیلد را ایجاد می‌کنیم. همانطور که می‌بینیم دو خصوصیت Id و Accounts را نداریم؛ به دلیل اینکه نیازی به آن‌ها نیست. 
اگر قسمت قبل را دنبال کرده باشید متوجه هستیم برای ایجاد کردن query ‌ها مجبور بودیم که یک کلاس را به نام AppQuery ایجاد کنیم. برای Mutation هم همین گونه‌است. یک کلاس به نام AppMutation را در پوشه GraphQLQueries ایجاد می‌کنیم:
public class AppMutation : ObjectGraphType
{
    public AppMutation()
    {
    }
}

در نهایت نیاز است کلاس AppSchema را باز کرده و خصوصیت Mutation را به آن اضافه می‌کنیم:
public class AppSchema : Schema
{
    public AppSchema(IDependencyResolver resolver)
        :base(resolver)
    {
        Query = resolver.Resolve<AppQuery>();
        Mutation = resolver.Resolve<AppMutation>();
    }
}
اکنون همه چیز آماده است تا تعدادی Mutation، در پروژه ایجاد کنیم. 

Create Mutation 
  در ابتدا واسط IOwnerRepository  و کلاس OwnerRepository را همانند زیر ویرایش می‌کنیم (اضافه کردن متد CreateOwner):
public interface IOwnerRepository
{
   ...
    Owner CreateOwner(Owner owner);
}

public class OwnerRepository : IOwnerRepository
{
   ...
    public Owner CreateOwner(Owner owner)
     {
         owner.Id = Guid.NewGuid();
         _context.Add(owner);
         _context.SaveChanges();
         return owner;
     }
}

متد CreateOwner یک شیء Owner جدید ایجاد شده را بازگشت می‌دهد. سپس در کلاس AppMutation: 
public class AppMutation : ObjectGraphType
{
   // Add
    public AppMutation(IOwnerRepository repository)
    {  
        Field<OwnerType>(
            "createOwner",
            arguments: new QueryArguments(new QueryArgument<NonNullGraphType<OwnerInputType>> { Name = "owner" }),
            resolve: context =>
            {
                var owner = context.GetArgument<Owner>("owner");
                return repository.CreateOwner(owner);
            }
        );
    }
}
 در ابتدا یک فیلد را به منظور بازگشت دادن شیء OwnerType ایجاد می‌کنیم. در بخش نام، createOwner را وارد می‌کنیم و در بخش arguments، یک آرگومان از نوع OwnerInputType را داریم (نشان دهنده ورودی‌ها می‌باشند) و در نهایت در بخش resolve، که قرار است متد CreateOwner را از IOwnerRepository اجرا کند. 
  اکنون پروژه را اجرا کرده و سپس درخواست را در UI.Playground به صورت زیر ارسال کنید:
mutation($owner:ownerInput!){
  createOwner(owner:$owner){
    id,
    name,
    address
  }
}
سپس در قسمت Query Variables : 
{
  "owner":{
    "name":"Abolfazl-Roshanzamir",
    "address":"Address - User 4"
  }
}
و در نهایت، نتیجه اجرا، در بخش سمت راست تصویر زیر قابل مشاهده‌است:
 


بجای کلمه کلیدی query، از کلمه کلیدی mutation برای mutations‌ها استفاده می‌کنیم. این تنها یک تفاوت جدید است.

Update Mutation  

در ابتدا واسط IOwnerRepository و کلاس OwnerRepository را مطابق زیر ویرایش می‌کنیم: 
public interface IOwnerRepository
{
    ...
    Owner UpdateOwner(Owner dbOwner, Owner owner);
}

public class OwnerRepository : IOwnerRepository
{
   ...
   public Owner UpdateOwner(Owner dbOwner, Owner owner)
   {
         dbOwner.Name = owner.Name;
         dbOwner.Address = owner.Address;

         _context.SaveChanges();
         return dbOwner;
     }
}

در نهایت همانند بخش (Create Mutation) یک فیلد را در سازنده کلاس AppMutation برای Update، اضافه می‌کنیم:
    public class AppMutation : ObjectGraphType
    {
        public AppMutation(IOwnerRepository repository)
        {
            ...
            // Update
            Field<OwnerType>(
                "updateOwner",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<OwnerInputType>> { Name = "owner" },
                    new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "ownerId" }),
                resolve: context =>
                {
                    var owner = context.GetArgument<Owner>("owner");
                    var ownerId = context.GetArgument<Guid>("ownerId");

                    var dbOwner = repository.GetById(ownerId);
                    if (dbOwner == null)
                    {
                        context.Errors.Add(new ExecutionError("Couldn't find owner in db."));
                        return null;
                    }

                    return repository.UpdateOwner(dbOwner, owner);
                }
            );

        }
    }

  اکنون پروژه را اجرا کرده و سپس درخواست را در UI.Playground به صورت زیر ارسال کنید:
mutation($owner:ownerInput!,$ownerId:ID!){
  updateOwner(owner:$owner,ownerId:$ownerId){
    id,
    name,
    address
  }
}

سپس در قسمت Query Variables:
{
  "owner":{
    "name":"Andy Madaidan",
    "address":"Address - User 1"
  },
  "ownerId": "53270061-3ba1-4aa6-b937-1f6bc57d04d2"
}


Delete Mutation   
همان الگوی Create / Update را دوباره دنبال می‌کنیم. در ابتدا واسط IOwnerRepository و کلاس OwnerRepository را همانند زیر ویرایش می‌کنیم:
public interface IOwnerRepository
{
   ...
    void DeleteOwner(Owner owner);
}

public class OwnerRepository : IOwnerRepository
{
   ...
   public void DeleteOwner(Owner owner)
   {
        _context.Remove(owner);
        _context.SaveChanges();
    }
}

و آخرین کاری که نیاز است انجام شود، ویرایش کلاس AppMutation می‌باشد:
 public class AppMutation : ObjectGraphType
    {
        public AppMutation(IOwnerRepository repository)
        {
            ...
           
            //Delete
            Field<StringGraphType>(
                "deleteOwner",
                arguments: new QueryArguments(new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "ownerId" }),
                resolve: context =>
                {
                    var ownerId = context.GetArgument<Guid>("ownerId");
                    var owner = repository.GetById(ownerId);
                    if (owner == null)
                    {
                        context.Errors.Add(new ExecutionError("Couldn't find owner in db."));
                        return null;
                    }
            
                    repository.DeleteOwner(owner);
                    return $"The owner with the id: {ownerId} has been successfully deleted from db.";
                }
            );
        }
    }

 اکنون پروژه را اجرا کنید و سپس درخواست را در UI.Playground به صورت زیر ارسال کنید:
mutation($ownerId:ID!){
  deleteOwner(ownerId:$ownerId)
}

سپس در قسمت Query Variables : 
{
  "ownerId": "6f513773-be46-4001-8adc-2e7f17d52d83"
}
 نتیجه اجرا در بخش سمت راست تصویر زیر قابل مشاهده است : 


نتیجه گیری
اکنون ما می‌دانیم که چگونه از Input Type ها برای Mutation ها استفاده کنیم. چگونه mutation action های متفاوتی را ایجاد کنیم و هم چنین چگونه در خواست‌های  mutation را در سمت کلاینت ایجاد کنیم.

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