نظرات مطالب
پشتیبانی از حذف و به‌روز رسانی دسته‌ای رکوردها در EF 7.0

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

در همین مثال مطلب جاری، فرض کنید موجودیت کاربر، به همراه خاصیت محاسباتی تعداد کتاب‌ها (NumberOfBooks) هم هست:

public class User
{
    // ...

    public virtual List<Book> Books { get; set; } = new();

    public int NumberOfBooks { get; set; }
}

اگر سعی کنیم این خاصیت را به صورت زیر به‌روز رسانی کنیم که در آن مقدار تعداد کتاب‌ها به کمک خاصیت راهبری Books محاسبه شود:

await context.Users.Where(x=> x.Id == 1).ExecuteUpdateAsync(s => s.SetProperty(user => user.NumberOfBooks,
                user => user.Books.Count()));

به خطای زیر می‌رسیم:

... could not be translated. Additional information: Translation of member 'Books' on entity type 'User' failed. This commonly occurs when the specified member is unmapped.

عنوان می‌کند که این خاصیت راهبری، نگاشت نشده‌است. برای رفع این مشکل باید به صورت زیر عمل کرد:

await context.Users
             .Where(user => user.Id == 1)
             .Select(user => new
             {
                user.NumberOfBooks,
                BooksCount = user.Books.Count()
             })
             .ExecuteUpdateAsync(s => s.SetProperty(arg => arg.NumberOfBooks, arg => arg.BooksCount));

در اینجا تنها کاری که انجام شده، انجام محاسبات و نگاشت‌های لازم، پیش از فراخوانی متد ExecuteUpdateAsync است.

اشتراک‌ها
اجرای اپلیکیشن های ASP.NET Core بر روی Windows Subsystem for Linux

Windows 10 has something called Windows Subsystem for Linux and this something enables us to run Linux applications on Windows 10 using Linux without need for Hyper-V or other virtual machines. When building multi-platform applications like my open-source TemperatureStation solution then having Linux right there for testing comes very handy. This blog post shows how to get Linux running on Windows, how to install .NET Core and how to run web applications on Linux. 

اجرای اپلیکیشن های ASP.NET Core بر روی Windows Subsystem for Linux
مطالب
عمومی سازی الگوریتم‌ها با استفاده از Reflection
در این مقاله قصد داریم عملیات Reflection را بیشتر در انجام ساده‌تر عملیات ببینیم. عملیاتی که به همراه کار اضافه، تکراری و خسته کننده است و با استفاده از Reflection این کارها حذف شده و تعداد خطوط هم پایین می‌آید. حتی گاها ممکن است موجب استفاده‌ی مجدد از کدها شود که همگی این عوامل موجب بالا رفتن امتیاز Refactoring می‌شوند.
در مثال‌های زیر مجموعه‌ای از Reflection‌های ساده و کاملا کاربردی است که من با آن‌ها روبرو شده ام.


کوتاه سازی کدهای نمایش یک View در ASP.NET MVC با Reflection 

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

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

ساختار اطلاعاتی تصویر فوق به شرح زیر است:
 <div>
                              <div>
                                  <div>
                                      <p><span>First Name </span>: Jonathan</p>
                                  </div>
   </div>
                          </div>
که به دو فایل پارشال تقسیم شده است Bio_ و BioRow_ که محتویات هر پارشال هم به شرح زیر است:

BioRow_
@model System.Web.UI.WebControls.ListItem


<div>
    <p><span>@Model.Text </span>: @Model.Value</p>
</div>
در پارشال بالا ورودی از نوع listItem است که یک متن دارد و یک مقدار.(شاید به نظر شبیه حالت جفت کلید و مقدار باشد ولی در این کلاس خبری از کلید نیست).
پارشال پایینی هم دربرگیرنده‌ی پارشال بالاست که قرار است چندین و چند بار پارشال بالا در خودش نمایش دهد.
Bio_
@using System.Web.UI.WebControls
@using ZekrWepApp.Filters
@model ZekrModel.Admin

    <div>
        <h1>Bio Graph</h1>
        <div>
            
            @{
                ListItemCollection collection = GetCustomProperties.Get(Model,exclude:new string[]{"Poems","Id"});
                foreach (var item in collection)
                {
                    Html.RenderPartial(MVC.Shared.Views._BioRow, item);
                }
            }

                    </div>
    </div>
پارشال بالا یک مدل از کلاس Admin را می‌پذیرد که قرار است اطلاعات شخصی مدیر را نمایش دهد. در ابتدا متدی از یک کلاس ایستا وجود دارد که کدهای Reflection درون آن قرار دارند که یک مجموعه از ListItem‌ها را بر می‌گرداند و سپس با یک حلقه، پارشال BioRow_ را صدا می‌زند.

کد درون این کلاس ایستا را بررسی می‌کنیم؛ این کلاس دو متد دارد یکی عمومی و دیگری خصوصی است:
  public class GetCustomProperties
    {
        private static PropertyInfo[] getObjectsInfos(object obj,string[] inclue,string[] exclude )
        {
            var list = obj.GetType().GetProperties();

            PropertyInfo[] outputPropertyInfos = null;

            if (inclue != null)
            {           
                return list.Where(propertyInfo => inclue.Contains(propertyInfo.Name)).ToArray();
            }
            if (exclude != null)
            {
                return list.Where(propertyInfo => !exclude.Contains(propertyInfo.Name)).ToArray();
            }
            return list;
        }
    }
کد بالا که یک کد خصوصی است، سه پارامتر را می‌پذیرد. اولی مدل یا کلاسی است که به آن پاس کرده‌ایم. دو پارامتر بعدی اختیاری است و در کد پارشال بالا Exclude را تعریف کرده ایم و تنهای یکی از دو پارامتر بالا هم باید مورد استفاده قرار بگیرند و Include ارجحیت دارد. وظیفه‌ی این دو پارمتر این است که آرایه ای از رشته‌ها را دریافت می‌کنند که نام پراپرتی‌ها در آن‌ها ذکر شده است. آرایه Include می‌گوید که فقط این پراپرتی‌ها را برگردان ولی اگر دوست دارید همه‌ی پارامترها را نمایش دهید و تنها یکی یا چندتا از آن‌ها را حذف کنید، از آرایه Exclude استفاده کنید. در صورتی که این دو آرایه خالی باشند، همه‌ی پراپرتی‌ها بازگشت داده می‌شوند و در صورتی که یکی از آن‌ها وارد شده باشد، طبق دستورات Linq بالا بررسی می‌کند که (Include) آیا اسامی مشترکی بین آن‌ها وجود دارد یا خیر؟ اگر وجود دارد آن را در لیست قرار داده و بر می‌گرداند و در حالت Exclude این مقایسه به صورت برعکس انجام می‌گیرد و باید لیستی برگردانده شود که اسامی، نکته مشترکی نداشته باشند.

متد عمومی که در این کلاس قرار دارد به شرح زیر است:
 public static ListItemCollection Get(object obj,string[] inclue=null,string[] exclude=null)
        {
            var propertyInfos = getObjectsInfos(obj, inclue, exclude);
            if (propertyInfos == null) throw new ArgumentNullException("propertyInfos is null");

            var collection = new ListItemCollection();

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {


                string name = propertyInfo.Name;

                foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))
                {
                    DisplayAttribute displayAttribute = attribute as DisplayAttribute;

                    if (displayAttribute != null)
                    {
                        name = displayAttribute.Name;
                        break;
                    }                  
                }

                string value = "";
                object objvalue = propertyInfo.GetValue(obj);
                if (objvalue != null) value = objvalue.ToString();

                collection.Add(new ListItem(name,value));
            }
            return collection;
        }
این متد سه پارامتر را از کاربر دریافت و به سمت متد خصوصی ارسال می‌کند. موقعی‌که پراپرتی‌ها بازگشت داده می‌شوند، دو قسمت آن مهم است؛ یکی عنوان پراپرتی و دیگری مقدار پراپرتی. از آن جا که نام پراپرتی‌ها طبق سلیقه‌ی برنامه نویس و با حروف انگلیسی نوشته می‌شوند، در صورتی که برنامه نویس از متادیتای Display در مدل بهره برده باشد، به جای نام پراپرتی مقداری را که به متادیتای Display داده‌ایم، بر می‌گردانیم.

کد بالا پراپرتی‌ها را دریافت و یک به یک متادیتاهای آن را بررسی کرده و در صورتی که از متادیتای Display استفاده کرده باشند، مقدار آن را جایگزین نام پراپرتی خواهد کرد. در مورد مقدار هم از آنجا که اگر پراپرتی با Null پر شده باشد، تبدیل به رشته‌ای با پیام خطای روبرو خواهد شد. در نتیجه بهتر است یک شرط احتیاط هم روی آن پیاده شود. در آخر هم از متن و مقدار، یک آیتم ساخته و درون Collection اضافه می‌کنیم و بعد از اینکه همه پراپرتی‌ها بررسی شدند، Collection را بر می‌گردانیم.
 [Display(Name = "نام کاربری")]
public string UserName { get; set; }

کد کامل کلاس:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;
using System.Web.Mvc.Html;
using System.Web.UI.WebControls;
using Links;

namespace ZekrWepApp.Filters
{
    
    public class GetCustomProperties
    {
        public static ListItemCollection Get(object obj,string[] inclue=null,string[] exclude=null)
        {
            var propertyInfos = getObjectsInfos(obj, inclue, exclude);
            if (propertyInfos == null) throw new ArgumentNullException("propertyInfos is null");

            var collection = new ListItemCollection();

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                string name = propertyInfo.Name;
                foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))
                {
                    DisplayAttribute displayAttribute = attribute as DisplayAttribute;

                    if (displayAttribute != null)
                    {
                        name = displayAttribute.Name;
                        break;
                    }                  
                }

                string value = "";
                object objvalue = propertyInfo.GetValue(obj);
                if (objvalue != null) value = objvalue.ToString();

                collection.Add(new ListItem(name,value));
            }
            return collection;
        }
        private static PropertyInfo[] getObjectsInfos(object obj,string[] include,string[] exclude )
        {
            var list = obj.GetType().GetProperties();

            PropertyInfo[] outputPropertyInfos = null;

            if (include != null)
            {           
                return list.Where(propertyInfo => include.Contains(propertyInfo.Name)).ToArray();
            }
            if (exclude != null)
            {
                return list.Where(propertyInfo => !exclude.Contains(propertyInfo.Name)).ToArray();
            }
            return list;
        }  
    }   
}

لیستی از پارامترها با Reflection


مورد بعدی که ساده‌تر بوده و از کد بالا مختصرتر هم هست، این است که قرار بود برای یک درگاه، یک سری اطلاعات را با متد Post ارسال کنم که نحوه‌ی ارسال اطلاعات به شکل زیر بود:
amount=1000&orderId=452&Pid=xxx&....
کد زیر را من جهت ساخت قالب‌های این چنینی استفاده می‌کنم:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Utils
{
    public class QueryStringParametersList
    {
        private string Symbol = "&";
        private List<KeyValuePair<string, string>> list { get; set; }

        public QueryStringParametersList()
        {
            list = new List<KeyValuePair<string, string>>();
        }
        public QueryStringParametersList(string symbol)
        {
            Symbol = symbol;
           list = new List<KeyValuePair<string, string>>(); 
        }

        public int Size
        {
            get { return list.Count; }
        }
        public void Add(string key, string value)
        {
            list.Add(new KeyValuePair<string, string>(key, value));
        }

        public string GetQueryStringPostfix()
        {
            return string.Join(Symbol, list.Select(p => Uri.EscapeDataString(p.Key) + "=" + Uri.EscapeDataString(p.Value)));
        }
    }
}



یک متغیر به نام symbol دارد و در صورتی در شرایط متفاوت، قصد چسپاندن چیزی را به یکدیگر با علامتی خاص داشته باشید، این تابع می‌تواند کاربرد داشته باشد. این متد از یک لیست کلید و مقدار استفاده کرده و پارامترهایی را که به آن پاس می‌شود، نگهداری و سپس توسط متد GetQueryStringPostfix آن‌ها را با یکدیگر الحاق کرده و در قالب یک رشته بر می‌گرداند.
کاربرد Reflection در اینجا این است که من باید دوبار به شکل زیر، دو نوع اطلاعات متفاوت را پست کنم. یکی موقع ارسال به درگاه و دیگری موقع بازگشت از درگاه.
QueryStringParametersList queryparamsList = new QueryStringParametersList();

            ueryparamsList.Add("consumer_key", requestPayment.Consumer_Key);
            queryparamsList.Add("amount", requestPayment.Amount.ToString());
            queryparamsList.Add("callback", requestPayment.Callback);
            queryparamsList.Add("description", requestPayment.Description);
            queryparamsList.Add("email", requestPayment.Email);
            queryparamsList.Add("mobile", requestPayment.Mobile);
            queryparamsList.Add("name", requestPayment.Name);
            queryparamsList.Add("irderid", requestPayment.OrderId.ToString());

ولی با استفاده از کد Reflection که در بالاتر عنوان شد، باید نام و مقدار پراپرتی را گرفته و در یک حلقه آن‌ها را اضافه کنیم، بدین شکل:
   private QueryStringParametersList ReadParams(object obj)
        {
            PropertyInfo[] propertyInfos = obj.GetType().GetProperties();

            QueryStringParametersList queryparamsList = new QueryStringParametersList();
            for (int i = 0; i < propertyInfos.Count(); i++)
            {
                queryparamsList.Add(propertyInfos[i].Name.ToLower(),propertyInfos[i].GetValue(obj).ToString() );              
            }
            return queryparamsList;
        }
در کد بالا هر بار پراپرتی‌های کلاس را خوانده و نام و مقدار آن‌ها را گرفته و به کلاس QueryString اضافه می‌کنیم. پارامتر ورودی این متد به این خاطر object در نظر گرفته شده است که تا هر کلاسی را بتوانیم به آن پاس کنیم که خودم در همین کلاس درگاه، دو کلاس را به آن پاس کردم.
مطالب
آشنایی با WebDav و نحوه استفاده از آن

WebDAV  استانداردی است بر روی پروتکل HTTP که Request‌ها و Responseهای مدیریت یک فایل را بر روی سرویس دهنده وب، تشریح می‌کند.

برای درک چرایی وجود این استاندارد بهتر است ذهن خود را معطوف به نحوه‌ی عملکرد سیستم فایل در OS کنیم که شامل API‌های خاص برای دسترسی نرم افزارهای گوناگون به فایل‌های روی یک سیستم است.
حال فکر کنید یک سرور Cloud راه اندازی نموده‌اید که قرار است مدیریت فایل‌ها و پرونده‌های Office را بر عهده داشته باشد و چون امکان ویرایش اسناد Office بر روی وب را ندارید، نیاز است تا اجازه دهید نرم افزار‌های Office مستقیما فایل‌ها را از روی سرور شما باز کنند و بعد از تغییرات، به جای ذخیره در سیستم local، محتوا را به فایل روی سرور ارسال کنند.
در مفهوم web عملا این کار غیر استاندارد و نادرست است. همه درخواست‌ها و جواب‌ها باید بر روی پروتکل Http باشند. خوب حال تصور کنید نرم افزارهای Office قابلیت آن را داشته باشند که به جای تحویل محتوا به سیستم عامل برای ذخیره‌ی آن بر روی سیستم local، محتوا را به یک آدرس ارسال نمایند و پشت آن آدرس، متدی باشد که بتواند به درخواست رسیده، به درستی پاسخ دهد.
این یعنی باید سمت سرور متدی با قابلیت ارسال پاسخ‌های درست و در سمت کلاینت نرم افزاری با قابلیت ارسال درخواست‌های مناسب وجود داشته باشد.
WebDAV استاندارد تشریح محتوای درخواست‌ها و پاسخ‌های مربوط به مدیریت فایل‌ها است.
خوشبختانه نرم افزارهای Office و بسیاری از نرم افزار‌های دیگر، استاندارد WebDAV را پشتیبانی می‌کنند و فقط لازم است برای سرورتان متدی با قابلیت پشتیبانی از درخواست‌های WebDAV پیاده سازی نمایید و البته متاسفانه کتابخانه‌های سورس باز چندانی برای WebDAV در سرور دات نت وجود ندارد. من ماژولی را برای کار با WebDAV نوشتم و سورسش را در Git  قرار دادم. برای این مثال هم از همین کتابخانه  استفاده می‌کنم.
ابتدا یک پروژه‌ی وب MVC ایجاد نمایید و پکیج xDav را از nugget نصب کنید.

PM> Install-Package xDav

اگر به web.config نگاهی بیاندازیم می‌بینیم یک module به نام xDav به وب سرور اضافه شده که بررسی درخواست‌های WebDAV  را به عهده دارد.

<system.webServer>
        <modules>
            <add name="XDav" type="XDav.XDavModule, XDav" />
        </modules>
  </system.webServer>

همچنین یک Section جدید هم به config برای پیکربندی xDav اضافه شده است. 

  <XDavConfig Name="xdav">
    <FileLocation URL="xdav" PathType="Local"></FileLocation>
  </XDavConfig>

خاصیت Name برای xDav نشانگر درخواست‌هایی است که باید توسط این ماژول اجرا شوند. در اینجا یعنی درخواست‌هایی که آدرس آن‌ها شامل "/xdav/" باشد، توسط این ماژول Handle می‌شوند. عبارت بعد از مقدار Name در URL هم طبیعتا نام فایل مورد نظر شماست.

FileLocation آدرس پوشه ای است که فایل‌ها در آن ذخیره و یا بازخوانی می‌شوند. اگر FileType با مقدار Local تنظیم شود، یعنی باید یک پوشه به نام خاصیت URL که در اینجا  xdav است در پوشه‌ی اصلی وب شما وجود داشته باشد و اگر با Server مقدار دهی شود URL باید یک آدرس فیزیکی بر روی سرور داشته باشد . مثل "c:\webdav"URL=

ما در این مثال مقادیر را به صورت پیش‌فرض نگه می‌داریم. یعنی باید در پوشه‌ی وب، یک Folder با نام xdav ایجاد کنیم.

در ادامه چند فایل word را برای تست در این پوشه کپی می‌کنم.

می خواهیم در صفحه Index، لیستی از فایل‌های درون این پوشه را نمایش دهیم طوری که در صورت کلیک بر روی هر کدام از آن‌ها، آدرس WebDav فایل مورد نظر را به Word ارسال کنیم.

بعد از نصب Office، در registry چند نوع Url تعریف می‌شود که معرف اپلیکیشنی است که آدرس به آن فرستاده شود. این دقیقا همان چیزیست که ما به آن نیاز داریم. کافیست آدرس WebDav فایل را بعد از عبارت " ms-word:ofe|u| " در یک لینک قرار دهیم تا آدرس به نرم افزار Word ارسال شود. یعنی آدرس URL باید این شکلی باشد:

  ms-word:ofe|u|http://Webaddress/xdav/filename

Webaddress آدرس وبسایت و filename نام فایل مورد نظرمان است. عبارت /xdav/ هم که نشان می‌دهد درخواست هایی که این آدرس را دارند باید توسط ماژول xDav پردازش شوند.

کلاسی با نام DavFile در پوشه‌ی Model ایجاد می‌کنم: 

public class DavFile
    {
        public string Name { get; set; }
        
        public string Href(string webAddress)
        {
            return string.Format("ms-word:ofe|u|http://{0}/xdav/{1}", webAddress, Name);
        }
    }

اکشن متد Index را در Home Controller، مانند زیر تغییر دهید:

var dir = new DirectoryInfo(XDav.Config.ConfigManager.DavPath);
            var model = dir.GetFiles().ToList()
                                      .Select(f => 
                                          new DavFile() { 
                                            Name = f.Name
                                        });
            return View(model);

یک لیست از فایل هایی که در پوشه‌ی webDav قرار دارند تهیه می‌کنیم و به View ارسال می‌کنیم. View را هم مثل زیر بازنویسی می‌کنیم. 

@model IEnumerable<WebDavServer.Models.DavFile>
<h1>
    File List
</h1>

<ul>
    @foreach (var item in Model)
    {
        <li> <a href="@Html.Raw(item.Href(ViewContext.HttpContext.Request.Url.Authority))">  @Html.Raw(item.Name) </a></li>
    }
</ul>

قرار است به ازای هر فایل، لینکی نمایش داده شود که با کلیک بر روی آن، آدرس فایل به word ارسال می‌شود. بعد از ثبت تغییرات، word محتوا را به همان آدرس ارسال می‌کند و ماژول xDav محتوا را در فایل فیزیکی سرور ذخیره خواهد کرد.

برنامه را اجرا کنید و بر روی فایل‌ها کلیک نمایید. اگر نرم افزار Office روی کامپیوترتان باز باشد با کلیک بر روی هر کدام از فایل‌ها، فایل word باز شده و می‌توایند محتوا را تغییر داده و ذخیره نمایید.

نرم افزار کلاینت (word) درخواست هایی با verb‌های مشخص که در استاندارد WebDav ذکر شده به آدرس مورد نظر می‌فرستد.  سرور WebDav درخواست را بر اساس Verb آن Request پردازش کرده و Response استاندارد را ایجاد میکند.

نرم افزار word پس از دریافت یک URL، به جای فرمت فیزیکی فایل، درخواست هایی را با تایپ‌های Option, Head, lock, get, post و unlock ارسال می‌کند. محتوای درخواست و پاسخ هر کدام از تایپ‌ها در استاندارد webDav تعریف شده و ماژول xDav آن را پیاده سازی نموده است.

  دریافت پروژه مثال

مطالب
مخفی کردن کوئری استرینگ‌ها در ASP.NET MVC توسط امکانات Routing
ابتدا مدل و منبع داده نمونه زیر را در نظر بگیرید:
using System.Collections.Generic;

namespace TestRouting.Models
{
    public class Issue
    {
        public int IssueId { set; get; }
        public int ProjectId { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }

    public static class IssuesDataSource
    {
        public static IList<Issue> CreateDataSource()
        {
            var results = new List<Issue>();
            for (int i = 0; i < 100; i++)
            {
                results.Add(new Issue { IssueId = i, ProjectId = i, Body = "Test...", Title = "Title " + i });
            }
            return results;
        }
    }
}
به همراه کنترلر زیر برای نمایش لیست اطلاعات و همچنین نمایش جزئیات یک issue انتخابی:
using System.Linq;
using System.Web.Mvc;
using TestRouting.Models;

namespace TestRouting.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var issuesList = IssuesDataSource.CreateDataSource();
            return View(issuesList); //show the list
        }

        public ActionResult Details(int issueId, int projectId)
        {
            var issue = IssuesDataSource.CreateDataSource()
                                        .Where(x => x.IssueId == issueId && x.ProjectId == projectId)
                                        .FirstOrDefault();
            return View(issue);
        }
    }
}
و View زیر کار نمایش لیست بازخوردهای یک پروژه را انجام می‌دهد:
@model IEnumerable<TestRouting.Models.Issue>
@{
    ViewBag.Title = "Index";
}
<h2>
    Issues</h2>
<ul>
    @foreach (var item in Model)
    {
   
        <li>
            @Html.ActionLink(linkText: item.Title,
                     actionName: "Details",
                     controllerName: "Home",
                     routeValues: new { issueId = item.IssueId, projectId = item.ProjectId },
                     htmlAttributes: null)
        </li>
    }
</ul>
در این حالت اگر پروژه را اجرا کنیم، هر لینک نمایش داده شده، چنین فرمی را خواهد داشت:
 http://localhost:1036/Home/Details?issueId=0&projectId=0
سؤال: آیا می‌شود این لینک‌ها را کمی زیباتر و SEO Friendly‌تر کرد؟
برای مثال آن‌را به نحو زیر نمایش داد:
 http://localhost:1036/Home/Details/0/0
پاسخ: بلی. برای اینکار تنها کافی است از امکانات مسیریابی استفاده کنیم:
using System.Web.Mvc;
using System.Web.Routing;

namespace TestRouting
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "IssueDetails",
                url: "Details/{issueId}/{projectId}", //تطابق با یک چنین مسیرهایی
                defaults: new 
                          { 
                                controller = "Home", //کنترلری که این نوع مسیرها را پردازش خواهد کرد
                                action = "Details", // اکشن متدی که نهایتا پارامترها را دریافت می‌کند
                                issueId = UrlParameter.Optional, //این خواص نیاز است هم نام پارامترهای اکشن متد تعریف شوند
                                projectId = UrlParameter.Optional
                          }
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}
در اینجا یک route جدید به نام دلخواه IssueDetails پیش از route پیش فرض، تعریف شده است.
این route جدید با مسیرهایی مطابق پارامتر url آن تطابق خواهد یافت. پس از آن کوئری استرینگ متناظر با issueId را به پارامتر issueId اکشن متدی به نام Details و کنترلر Home ارسال خواهد کرد؛ به همین ترتیب در مورد projectId عمل خواهد شد.
ضمنا در url نهایی نمایش داده شده، دیگر اثری از کوئری استرینگ‌ها نبوده و برای نمونه در این حالت، اولین لینک نمایش داده شده شکل زیر را خواهد داشت:
 http://localhost:1036/Home/Details/0/0
 البته باید دقت داشت، یک چنین اصلاح خودکاری تنها در حالت استفاده از متد Html.ActionLink رخ می‌دهد و اگر Urlها را دستی ایجاد کنید، تغییری را مشاهده نخواهید کرد.
مطالب دوره‌ها
بررسی سرعت و کارآیی AutoMapper
AutoMapper تنها کتابخانه‌ی نگاشت اشیاء مخصوص دات نت نیست. در این مطلب قصد داریم سرعت AutoMapper را با حالت نگاشت دستی، نگاشت توسط EmitMapper و نگاشت به کمک ValueInjecter، مقایسه کنیم.


مدل مورد استفاده

در اینجا قصد داریم، شیء User را یک میلیون بار توسط روش‌های مختلف، به خودش نگاشت کنیم و سرعت انجام این‌کار را در حالت‌های مختلف اندازه گیری نمائیم:
public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public DateTime LastLogin { get; set; }
}


روش بررسی سرعت انجام هر روش

برای کاهش کدهای تکراری، می‌توان قسمت تکرار شونده را به صورت یک Action، در بین سایر کدهایی که هر بار نیاز است به یک شکل فراخوانی شوند، قرار داد:
public static void RunActionMeasurePerformance(Action action)
{
    GC.Collect();
    var initMemUsage = Process.GetCurrentProcess().WorkingSet64;
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    action();
    stopwatch.Stop();
    var currentMemUsage = Process.GetCurrentProcess().WorkingSet64;
    var memUsage = currentMemUsage - initMemUsage;
    if (memUsage < 0) memUsage = 0;
    Console.WriteLine("Elapsed time: {0}, Memory Usage: {1:N2} KB", stopwatch.Elapsed, memUsage / 1024);
}


انجام آزمایش

در مثال زیر، ابتدا یک میلیون شیء User ایجاد می‌شوند و سپس هربار توسط روش‌های مختلفی به شیء User دیگری نگاشت می‌شوند:
static void Main(string[] args)
{
    var length = 1000000;
    var users = new List<User>(length);
    for (var i = 0; i < length; i++)
    {
 
        var user = new User
        {
            Id = i,
            UserName = "User" + i,
            Password = "1" + i + "2" + i,
            LastLogin = DateTime.Now
        };
        users.Add(user);
    }
 
    Console.WriteLine("Custom mapping");
    RunActionMeasurePerformance(() =>
    {
        var userList =
            users.Select(
                o =>
                    new User
                    {
                        Id = o.Id,
                        UserName = o.UserName,
                        Password = o.Password,
                        LastLogin = o.LastLogin
                    }).ToList();
    });
 
    Console.WriteLine("EmitMapper mapping");
    RunActionMeasurePerformance(() =>
    {
        var map = EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<User, User>();
        var emitUsers = users.Select(o => map.Map(o)).ToList();
    });
 
    Console.WriteLine("ValueInjecter mapping");
    RunActionMeasurePerformance(() =>
    {
        var valueUsers = users.Select(o => (User)new User().InjectFrom(o)).ToList();
    });
 
    Console.WriteLine("AutoMapper mapping, DynamicMap using List");
    RunActionMeasurePerformance(() =>
    {
        var userMap = Mapper.DynamicMap<List<User>>(users).ToList();
    });
 
    Console.WriteLine("AutoMapper mapping, Map using List");
    RunActionMeasurePerformance(() =>
    {
        var userMap = Mapper.Map<List<User>>(users).ToList();
    });
 
    Console.WriteLine("AutoMapper mapping, Map using IEnumerable");
    RunActionMeasurePerformance(() =>
    {
        var userMap = Mapper.Map<IEnumerable<User>>(users).ToList();
    });
 
 
    Console.ReadKey();
}


خروجی آزمایش

در ادامه یک نمونه‌ی خروجی نهایی را مشاهده می‌کنید:
 Custom mapping
Elapsed time: 00:00:00.4869463, Memory Usage: 58,848.00 KB

EmitMapper mapping
Elapsed time: 00:00:00.6068193, Memory Usage: 62,784.00 KB

ValueInjecter mapping
Elapsed time: 00:00:15.6935578, Memory Usage: 21,140.00 KB

AutoMapper mapping, DynamicMap using List
Elapsed time: 00:00:00.6028971, Memory Usage: 7,164.00 KB

AutoMapper mapping, Map using List
Elapsed time: 00:00:00.0106244, Memory Usage: 680.00 KB

AutoMapper mapping, Map using IEnumerable
Elapsed time: 00:00:01.5954456, Memory Usage: 40,248.00 KB

ValueInjecter از همه کندتر است.
EmitMapper از AutoMapper سریعتر است (البته فقط در بعضی از حالت‌ها).
سرعت AutoMapper زمانیکه نوع آرگومان ورودی به آن به IEnumerable تنظیم شود، نسبت به حالت استفاده از List معمولی، به مقدار قابل توجهی کندتر است. زمانیکه از List استفاده شده، سرعت آن از سرعت حالت نگاشت دستی (مورد اول) هم بیشتر است.
متد DynamicMap اندکی کندتر است از متد Map.

در این بین اگر ValueInjecter را از لیست حذف کنیم، به نمودار ذیل خواهیم رسید (اعداد آن برحسب ثانیه هستند):



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


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample06.zip
نظرات مطالب
طراحی و پیاده سازی زیرساختی برای مدیریت خطاهای حاصل از Business Rule Validationها در ServiceLayer
بنده قبلا در پروژه‌ها در بین لایه‌های Service و Presentation ، از لایه دیگری استفاده میکردم که الگویی به همین صورت داشت.
البته مواردی مثل متدهای OnFailure و OnBoth و اینگونه متدها خب جنبه سلیقه ای در پیاده سازی الگو داره.
ولی در کل ایجاد یک درخواست و پاسخ به اون، به صورت زیر میتونه باشه:
public class Request<T>
{
  public Request(T model)
  {
     Model = model.
  }
public T Model { get; } }
کلاس پاسخ:
public class Response
{
   public bool IsSuccess { get; set; }

   public MessageCollection Messages { get; set; } 
}

public class HttpResponse : Response
{
   public HttpStatusCode StatusCode { get; set; }
}
public class Response<T> : Response { public T Result { get; set; } }
public class HttpResponse<T> : HttpResponse { public T Result { get; set; } } 
این کلاس به ازای هر درخواست برای انجام کاری، مشخص میکنه موفق آمیز بود یا خیر، چه پیام هایی برای کاربر قابل نمایش هست، و در صورتیکه نتیجه ای برای نمایش داشت هم در مشخصه Result میتونست قرار بگیره. و همچنین برای اپلیکیشن‌های WebApi میتونه از HttpResponse به عنوان یک بسته پاسخ استفاده بشه که شامل موارد مورد نیاز به علاوه StatusCode برای بررسی و ارسال به سمت کلاینت هست.
این حالت کلی این الگو هست که میتونه موارد دیگه ای هم بسته به نیاز بهش اضافه بشه.
مطالب
آزمایش Web APIs توسط افزونه Rest Client
اگر تجربه‌ی نوشتن Web API را داشته باشید، قطعا نیاز به ابزارها و یا کتابخانه‌هایی را برای تست API‌ها داشته‌اید، تا بتوانید از صحت عملکرد آنها مطمئن شوید. از جمله روش‌ها و یا ابزارهایی که میتوانید برای تست و بررسی API‌ها استفاده کنید، می‌توان به postman (دوره آشنایی با postman ) ، سواگر  و stress اشاره کرد که پیشتر در سایت جاری مقالات مفیدی درباره‌ی آن‌ها نوشته شده‌است که می‌توانید برای آشنایی به آنها مراجعه کنید.

در این مقاله قصد داریم یک افزونه را معرفی نماییم که بر روی Visual Studio Code نصب می‌شود و این امکان را به ما می‌دهد تا بتوانیم API‌ها را فراخوانی و تست کنیم که در ادامه به تفصیل به آن خواهیم پرداخت.
با توجه به اینکه این افزونه مختص VS Code می‌باشد، باید ابتدا vs code را نصب نمایید.
در قسمت افزونه‌ها در بخش جستجو، عبارت Rest client را وارد نمایید و منتظر بمانید تا نتایج، نمایش داده شوند و سپس آن را نصب نمایید.

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

قبل از اینکه بتوانیم با نحوه‌ی کار این افزونه آشنا شویم، نیاز داریم API هایی باشند تا بتوانیم آن‌ها را فراخوانی کنیم. هرچند سایت‌هایی نیز هستند که چنین امکانی را فراهم کرده‌اند و میتوانیم API‌های آنها را فراخوانی کنیم. در این مقاله، چند API  ساده را با ASP.NET Core پیاده سازی میکنیم و سپس اقدام به فراخوانی هر کدام از آنها خواهیم کرد.
با توجه به اینکه این مقاله در مورد Web API و آموزش آن نیست، از توضیح کدهای زیر خودداری می‌شود.
 public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
 شبیه سازی یک Repository
 public class PeopleRepository
    {
        private List<Person> _people;
        public PeopleRepository()
        {
            _people = new List<Person> {
            new Person{ Id=1,Age=28,Name="Uthman"},
            new Person{ Id=2,Age=27,Name="Vahid"},
            new Person{ Id=3,Age=26,Name="Hadi"},
            new Person{ Id=4,Age=25,Name="Saman"},
            new Person{ Id=5,Age=20,Name="Sirwan"},
            };
        }

        public List<Person> GetAll()
        {
            return _people;
        }

    }

و چند API  برای کار با کلاس Person :
[Route("api/people")]
    public class PeopleController : Controller
    {
        PeopleRepository _repository;
        public PeopleController()
        {
            _repository = new PeopleRepository();
        }

        [HttpGet("")]
        public IActionResult Get()
        {
            return Ok(_repository.GetAll());
        }

        [HttpGet("{id:int}")]
        public IActionResult Get(int id)
        {
            return Ok(_repository.GetAll().FirstOrDefault(p => p.Id == id));
        }

        [HttpPost]
        public IActionResult Post([FromBody]Person person)
        {
            return Ok(person);
        }

        [HttpPut("{id:int}")]
        public IActionResult Put(int id,[FromBody] Person person)
        {
            if (id != person.Id)
                return BadRequest();
            return Ok(person);
        }

        [HttpDelete("{id:int}")]
        public IActionResult Delete(int id)
        {
            return Ok();
        }

        [HttpPost("avatar")]
        public IActionResult Post(IFormFile file)
        {
            return Ok();
        }
    }
تا به اینجا صرفا چند API را تهیه کرده‌ایم که در ادامه میخواهیم هر کدام از آنها را با افزونه‌ی Rest Client فراخوانی کنیم و با نحوه‌ی عملکرد آن‌ها آشنا شویم.

 مراحل انجام کار
 ابتدا نیاز هست یک فایل را ایجاد نماییم؛ با پسوند http و یا اینکه میتوانید یک فایل متنی را ایجاد نمایید و زبان آن را بر روی HTTP تنظیم نمایید.

اجرای درخواست GET برای نمایش لیست افراد
GET http://localhost:59416/api/people
با نوشتن یک خط فوق میتوانیم API مربوط به لیست افراد را فراخوانی کنیم.

برای فراخونی دستور فوق دو راه پیش رو است:
1- بعد از نوشتن آدرس هر API، یک دکمه بالای آدرس ظاهر می‌شود، تحت عنوان Send Request که با کلیک بر روی این دکمه، درخواست ارسال خواهد شد.
2- انتخاب آدرس فوق و فشردن کلید‌های ctrl+alt+r یا بجای فشردن این کلید‌ها میتوانید در Command  Palette،دستور Send Request را جستجو کنید و انتخاب نمایید:


بعد از اجرا به یکی از روش‌های فوق، در یک پنجره‌ی جدید در سمت راست، نتیجه‌ی درخواست ظاهر میشود که در بخش بالا توضیحاتی در مورد درخواست و در بخش پایین، خروجی آن قابل مشاهده می‌باشد


تا به اینجا توانستیم فقط با نوشتن آدرس API مورد نظر، آن را فراخوانی کنیم.

برای ارسال پارامترهایی در هدر درخواست فقط کافیست دقیقا در خط پایین آدرس، به صورت field-name:field-value، هر پارامتری را که میخواهید، به همراه درخواست ارسال کنید. برای نمونه برای API لیست افراد که در بالا تست کردیم، میتوانیم هدر را به صورت زیر تنظیم نماییم :

GET http://localhost:59416/api/people
Content-Type: application/json


اجرای درخواست GET برای دریافت یک شخص خاص

GET http://localhost:59416/api/people/1

خروجی آن به صورت زیر می‌باشد


درخواست POST برای درج کاربر جدید:

POST http://localhost:59416/api/people
content-type: application/json

{
    "id": 10,
    "name": "ali",
    "age":37
}

بعد از هدرهای درخواست، یک خط خالی ایجاد کنید و پایین‌تر از خط خالی، میتوانید مقادیر body درخواست را وارد نمایید.


درخواست PUT برای آپدیت یک شخص :

PUT http://localhost:59416/api/people/3
content-type: application/json

{
    "id": 3,
    "name": "ali",
    "age":37
}

درخواست DELETE برای حذف شخص:

DELETE http://localhost:59416/api/people/3
content-type: application/application/json


ارسال توکن اعتبارسنجی :

در صورتی که یک API نیاز به اعتبار سنجی دارد و باید توکن را به همراه درخواستی ارسال نمایید، می‌توانید در هدر درخواست، همانند زیر، توکن را ارسال نمایید

GET http://localhost:59416/api/people
content-type: application/json
Authorization: Bearer token


آپلود فایل:

یکی از API هایی که در مثال ابتدای مقاله داشتیم، مربوط به آپلود فایل آواتار هست که از ورودی، یک IFormFile را به عنوان ورودی دریافت میکند. برای آپلود فایل به کمک افزونه Rest Client میتوانیم به صورت زیر فایل را ارسال نماییم 

POST http://localhost:59416/api/people/avatar
Content-Type: multipart/form-data; boundary=----MyBoundary

------MyBoundary
Content-Disposition: form-data; name="file";filename="Studio"
content-type: image/png

< C:\Users\rahimi\Downloads\Studio.png

------MyBoundary--

قبل از آدرس فایل، وجود  > ضروری می‌باشد. 


فعال سازی دکمه Send Request به ازای هر آدرس:

اگر در یک فایل، چند آدرس را همانند عکس زیر داشته باشید، فقط یک دکمه‌ی Send Request وجود خواهد داشت که کلیک بر روی آن منجر به فراخوانی اولین url می‌شود.

برای داشتن یک دکمه Send Request به ازای هر API، باید بین هر کدام از API ها، حداقل سه تا # قرار دهید.

### Get All People
GET http://localhost:59416/api/people
content-type: application/json

### Get Person
GET http://localhost:59416/api/people/1


### Create 
POST http://localhost:59416/api/people
content-type: application/json

{
    "id": 10,
    "name": "ali",
    "age":37
}

### Edit person
PUT http://localhost:59416/api/people/3
content-type: application/json

{
    "id": 3,
    "name": "ali",
    "age":37
}

### Delete person
DELETE http://localhost:59416/api/people/3
content-type: application/application/json


### Upload Avatar
POST http://localhost:59416/api/people/avatar 
Content-Type: multipart/form-data; boundary=----MyBoundary

------MyBoundary
Content-Disposition: form-data; name="file";filename="Studio"
content-type: image/png

< C:\Users\rahimi\Downloads\Studio.png

------MyBoundary--

افزونه‌ی Rest Client، فراتر از توضیحات این مقاله می‌باشد. در صورت علاقه و برای مطالعه بیشتر در مورد آن، می‌توانید به لینک صفحه افزونه مراجعه نمایید.

مطالب
Bind کردن Enum به ItemsSource در XAML
شاید تا به حال در یک برنامه سازمانی نیاز به Bind کردن یک Enum به کنترل‌های XAML به چشمتان خورده باشد ، روشی که من برای این کار استفاده می‌کنم توسط یک Markup Extension به صورت زیر است :
public class ByteEnumerationExtention : MarkupExtension
    {
        public ByteEnumerationExtention(Type enumType)
        {
            this.enumType = enumType;
        }

        private Type enumType;

        public Type EnumType
        {
            get { return enumType; }
            private set
            {
                enumType = value;
            }
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return (from x in Enum.GetValues(EnumType).OfType<Enum>()
                    select new EnumerationMember
                    {
                        Value = GetValue(x),
                        Description = GetDescription(x)
                    }).ToArray();
        }

        private byte? GetValue(object enumValue)
        {
            return Convert.ToByte(enumValue.GetType().GetField("value__").GetValue(enumValue));
        }

        public object GetObjectValue(object enumValue)
        {
            return enumValue.GetType().GetField("value__").GetValue(enumValue);
        }

        public string GetDescription(object enumValue)
        {
            var descAttrib = EnumType.GetField(enumValue.ToString())
                .GetCustomAttributes(typeof(DescriptionAttribute), false)
                .FirstOrDefault() as DescriptionAttribute;
            return descAttrib != null ? descAttrib.Description : enumValue.ToString();
        }
    }

    public class EnumerationMember
    {
        public string Description { get; set; }

        public byte? Value { get; set; }
    }
XAML :
<ComboBox ItemsSource="{Binding Source={ Extensions:ByteEnumerationExtention {x:Type type:MyEnum} }}"  DisplayMemberPath="Description"
 SelectedValuePath="Value" SelectedValue="{Binding SelectedItemInViewModel}"/>
Enum :
public enum MyEnum : short
    {
        [Description("گزینه 1")]
        Item1 = 1,

        [Description("گزینه 2")]
        Item1 = 2,

        [Description("گزینه 3")]
        Item1 = 3,

        [Description("گزینه 4")]
        Item1 = 4,

        [Description("گزینه 5")]
        Item1 = 5,
.
.
.
    }
SelectedItem در ViewModel :
        short? selectedItemInViewModel;
        public short? SelectedItemInViewModel
        {
            get
            {
                return selectedItemInViewModel;
            }
            set
            {
                selectedItemInViewModel = value; 
                 RaisePropertyChanged("SelectedItemInViewModel");
               //do calculations if needed
            }
        }