مطالب
Asp.Net Identity #3
در مقاله‌ی  پیشین  نگاهی داشتیم به نحوه‌ی برپایی سیستم Identity. در این مقاله به نحوه‌ی استفاده از این سیستم به منظور طراحی یک سیستم مدیریت کاربران خواهیم پرداخت و انشالله در مقاله‌های بعدی این سیستم را تکمیل خواهیم نمود. کار را با اضافه کردن یک کنترلر جدید به پروژه آغاز می‌کنیم.
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity.Owin;
using Users.Infrastructure;

namespace Users.Controllers
{
    public class HomeController : Controller
    {
        private AppUserManager UserManager
        {
            get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); }
        }
        // GET: Home
        public ActionResult Index()
        {
            return View(UserManager.Users);

        }

}
در خط 10 یک پروپرتی از نوع AppUserManager (کلاسی که مدیریت کاربران را برعهده دارد) ایجاد می‌کنیم. اسمبلی Microsoft.Owin.Host.SystemWeb یک سری متدهای الحاقی را به کلاس HttpContext اضافه می‌کند که یکی از آنها متد GetOwinContext می‌باشد. این متد یک شیء Per-Request Context را از طریق رابط IOwinContext به OwinApi ارسال می‌کند؛ با استفاده از متد الحاقی <GetUserManager<T که T همان کلاس AppUserManager می‌باشد. حال که نمونه‌ای از کلاس AppUserManager را بدست آوردیم، می‌توانیم درخواستهایی را به جداول کاربران بدهیم. مثلا در خط 17 با استفاده از پروپرتی Users میتوانیم لیست کاربران موجود را بدست آورده و آن را به ویو پاس دهیم.
@using Users.Models
@model IEnumerable<AppUser>
@{
    ViewBag.Title = "Index";
}
<div class="panel panel-primary">
    <div class="panel-heading">
        User Accounts
    </div>
    <table class="table table-striped">
        <tr><th>ID</th><th>Name</th><th>Email</th></tr>
        @if (!Model.Any())
        {
            <tr><td colspan="3" class="text-center">No User Accounts</td></tr>
        }
        else
        {
            foreach (AppUser user in Model)
            {
                <tr>
                    <td>@user.Id</td>
                    <td>@user.UserName</td>
                    <td>@user.Email</td>
                </tr>
            }
        }
    </table>
</div>
@Html.ActionLink("Create", "CreateUser", null, new { @class = "btn btn-primary" })

نحوه‌ی ساخت یک کاربر جدید
ابتدا در پوشه Models یک کلاس ایجاد کنید : 
 namespace Users.Models
    {
        public class CreateModel
        {
            [Required]
            public string Name { get; set; }
            [Required]
            public string Email { get; set; }
            [Required]
            public string Password { get; set; }
        }
    }
فقط دوستان توجه داشته باشید که در پروژه‌های حرفه‌ای و تجاری هرگز اطلاعات مهم مربوط به مدل‌ها را در پوشه‌ی Models قرار ندهید. ما در اینجا صرف آموزش و برای جلوگیری از پیچیدگی مثال این کار را انجام میدهیم. برای اطلاعات بیشتر به این مقاله مراجعه کنید.
حال در کنترلر برنامه کدهای زیر را اضافه می‌کنیم:
 public ActionResult CreateUser()
        {
            return View();
        }

        [HttpPost]
        public async Task<ActionResult> CreateUser(CreateModel model)
        {
            if (!ModelState.IsValid)
                return View(model);

            var user = new AppUser { UserName = model.Name, Email = model.Email };
            var result = await UserManager.CreateAsync(user, model.Password);

            if (result.Succeeded)
            {
                return RedirectToAction("Index");
            }

            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
            return View(model);
        }
در اکشن CreateUser ابتدا یک شیء از کلاس AppUser ساخته و پروپرتی‌های مدل را به پروپرتی‌های کلاس AppUser انتساب می‌دهیم. در مرحله‌ی بعد یک شیء از کلاس IdentityResult به نام result ایجاد کرده و نتیجه‌ی متد CreateAsync را درون آن قرار می‌دهیم. متد CreateAsync از طریق پروپرتی از نوع AppUserManager قابل دسترسی است و دو پارامتر را دریافت می‌کند. پارامتر اول یک شیء از کلاس AppUser و پارامتر دوم یک رشته‌ی حاوی Password می‌باشد و خروجی متد یک شیء از کلاس IdentityResult است. در مرحله‌ی بعد چک می‌کنیم اگر Result، مقدار Succeeded را داشته باشد (یعنی نتیجه موفقیت آمیز بود) آن‌وقت ... در غیر اینصورت خطاهای موجود را به ModelState اضافه نموده و به View می‌فرستیم.
@model Users.ViewModels.CreateModel

@Html.ValidationSummary(false)

@using (Html.BeginForm())
{
    <div class="form-group">
        <label>Name</label>
        @Html.TextBoxFor(x => x.UserName, new { @class = "form-control" })
    </div>
    <div class="form-group">
        <label>Email</label>
        @Html.TextBoxFor(x => x.Email, new { @class = "form-control" })
    </div>

    <div class="form-group">
        <label>Password</label>
        @Html.PasswordFor(x => x.Password, new { @class = "form-control" })
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
    @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default" })
}

اعتبار سنجی رمز
عمومی‌ترین و مهمترین نیازمندی برای هر برنامه‌ای، اجرای سیاست رمزگذاری می‌باشد؛ یعنی ایجاد یک سری محدودیتها برای ایجاد رمز است. مثلا رمز نمی‌تواند از 6 کاراکتر کمتر باشد و یا باید حاوی حروف بزرگ و کوچک باشد و ... . برای اجرای سیاست‌های رمزگذاری از کلاس PasswordValidator استفاده میشود. کلاس PasswordValidator برای اجرای سیاستهای رمزگذاری از پروپرتی‌های زیر استفاده می‌کند.

var manager = new AppUserManager(new UserStore<AppUser>(db))
            {
                PasswordValidator = new PasswordValidator
                {
                    RequiredLength = 6,
                    RequireNonLetterOrDigit = false,
                    RequireDigit = false,
                    RequireLowercase = true,
                    RequireUppercase = true
                }
            };

فقط دوستان توجه داشته باشید که کد بالا را در متد Create از کلاس AppUserManager استفاده کنید.


اعتبار سنجی نام کاربری

برای اعبارسنجی نام کاربری از کلاس UserValidator به صورت زیر استفاده می‌کنیم:

manager.UserValidator = new UserValidator<AppUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = true,
                RequireUniqueEmail = true
            };

کد بالا را نیز در متد Create  از کلاس AppUserManager قرار می‌دهیم.

مطالب
آشنایی با Refactoring - قسمت 11

قسمت یازدهم آشنایی با Refactoring به توصیه‌هایی جهت بالا بردن خوانایی تعاریف مرتبط با اعمال شرطی می‌پردازد.

الف) شرط‌های ترکیبی را کپسوله کنید

عموما حین تعریف شرط‌های ترکیبی، هدف اصلی از تعریف آن‌ها پشت انبوهی از && و || گم می‌شود و برای بیان مقصود، نیاز به نوشتن کامنت خواهند داشت. مانند:

using System;

namespace Refactoring.Day11.EncapsulateConditional.Before
{
public class Element
{
private string[] Data { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }

public string FindElement()
{
if (Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1)
return "Element1";

if (Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2)
return "Element2";

return string.Empty;
}
}
}

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

using System;

namespace Refactoring.Day11.EncapsulateConditional.After
{
public class Element
{
private string[] Data { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }

public string FindElement()
{
if (hasOneYearOldElement)
return "Element1";

if (hasTwoYearsOldElement)
return "Element2";

return string.Empty;
}

private bool hasTwoYearsOldElement
{
get { return Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2; }
}

private bool hasOneYearOldElement
{
get { return Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1; }
}
}
}


همانطور که ملاحظه می‌کنید پس از این جایگزینی، خوانایی متد FindElement بهبود یافته است و برنامه نویس اگر 6 ماه بعد به این کدها مراجعه کند نخواهد گفت: «من این کدها رو نوشتم؟!»؛ چه برسد به سایرینی که احتمالا قرار است با این کدها کار کرده و یا آن‌ها را نگهداری کنند.


ب) از تعریف خواص Boolean با نام‌های منفی پرهیز کنید

یکی از مواردی که عموما علت اصلی بروز بسیاری از خطاها در برنامه است، استفاده از نام‌های منفی جهت تعریف خواص است. برای مثال در کلاس مشتری زیر ابتدا باید فکر کنیم که مشتری‌های علامتگذاری شده کدام‌ها هستند که حالا علامتگذاری نشده‌ها به این ترتیب تعریف شده‌اند.

namespace Refactoring.Day11.RemoveDoubleNegative.Before
{
public class Customer
{
public decimal Balance { get; set; }

public bool IsNotFlagged
{
get { return Balance > 30m; }
}
}
}

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

namespace Refactoring.Day11.RemoveDoubleNegative.After
{
public class Customer
{
public decimal Balance { get; set; }

public bool IsFlagged
{
get { return Balance <= 30m; }
}
}
}

مطالب
ساخت ActionResult سفارشی
پیشتر با انواع ActionResult آشنا شدید. حال فرض کنید می‌خواهید نوعی رو برگردونید که براش ActionResult موجود نباشه مثلا RSS و یا فایل از نوع Excel و...
خوب، فرض کنید می‌خواهید اکشن متدی رو بنویسید که قراره نام یک فایل متنی رو بگیره و انو تو مروگر به کاربر نمایش بده.
برای اینکار از کلاس ActionResult، کلاس دیگه‌ی رو بنام TextResult به ارث می‌بریم و از این ActionResult سفارشی شده، در اکشن متد مربوطه استفاده می‌کنیم:
public class TextResult : ActionResult
{
    public string FileName { get; set; }
    public override void ExecuteResult(ControllerContext context)
    {
        var filePath = Path.Combine(context.HttpContext.Server.MapPath(@"~/Files/"), FileName);
        var data = File.ReadAllText(filePath);
        context.HttpContext.Response.Write(data);
    }
}
نحوه استفاده
    public ActionResult DownloadTextFile(string fileName)
    {
        return new TextResult { FileName = fileName };
    }
در واقع متد اصلی اینجا ExecuteResult هست که نتیجه‌ی کار یک اکشن رو می‌تونیم پردازش کنیم.
خوب، سوالی که اینجا پیش میاد اینه که چرا این همه کار اضافی، چرا از Return File  استفاده نمی‌کنی؟
    public ActionResult DownloadTextFile(string fileName)
    {
        var filePath = Path.Combine(HttpContext.Server.MapPath(@"~/Files/"), fileName);
        return File(filePath, "text");
    }
 یا کلا دلیل استفاده از ActionResult سفارشی چیه؟

  • جلوگیری از پیچیدگی و  تکرار کد
همیشه کار مثل مورد بالا راحت و کم کد! نیست.
به مثال زیر توجه کنید که قراره خروجی CSV  بهمون بده.
public class CsvActionResult : ActionResult
{
    public IEnumerable ModelListing { get; set; }

    public CsvActionResult(IEnumerable modelListing)
    {
        ModelListing = modelListing;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        byte[] data = new CsvFileCreator().AsBytes(ModelListing);
        var fileResult = new FileContentResult(data, "text/csv")
        {
            FileDownloadName = "CsvFile.csv"
        };
        fileResult.ExecuteResult(context);
    }
}
و نحوه استفاده:
    public ActionResult ExportUsers()
    {
        IEnumerable<User> model = UserRepository.GetUsers();
        return new CsvActionResult(model);
    }
حال فرض کنید بخواهیم همه این کدها رو داخل اکشن متد داشته باشیم، یکم پیچیده میشه و یا فرض کنید کنترلر دیگه‌‌ای نیاز به خروجی CSV  داشته باشه، تکرار کد زیاد میشه.

  • راحت کردن گرفتن تست واحد از اکشن‌ها متدها
کاربرد ActionResult سفارشی تو تست واحد اینه که وابستگی‌های یک اکشن رو که Mock کردنش سخته می‌بریم داخل ActionResult و هنگام نوشتن تست واحد درگیر کار با اون وابستگی نمی‌شیم.
به مثال زیر توجه کنید که قراره برای اکشن Logout  تست واحد بنویسیم
ابتدا بردن وابستگی‌ها به خارج از اکشن به کمک ActionResult سفارشی
public class LogoutActionResult : ActionResult
{
    public RedirectToRouteResult ActionAfterLogout { get; set; }
    public LogoutActionResult(RedirectToRouteResult actionAfterLogout)
    {
        ActionAfterLogout = actionAfterLogout;
    }
    public override void ExecuteResult(ControllerContext context)
    {
        FormsAuthentication.SignOut();
        ActionAfterLogout.ExecuteResult(context);
    }
}
نحوه استفاده از ActionResult سفارشی
    public ActionResult Logout()
    {
        var redirect = RedirectToAction("Index", "Home");
        return new LogoutActionResult(redirect);
    }
و سپس نحوه تست واحد نوشتن
    [TestMethod]
    public void The_Logout_Action_Returns_LogoutActionResult()
    {
        //arrange
        var account = new AccountController();

        //act
        var result = account.Logout() as LogoutActionResult;

        //assert
        Assert.AreEqual(result.ActionAfterLogout.RouteValues["Controller"], "Home");
    }
خوب به راحتی ما میایم فراخوانی متد SignOut رو از داخل اکشن می‌کشیم بیرون و این کار از اجرای متد SignOut  از داخل اکشن متد جلوگیری می‌کنه و همچنین با این کار هنگام تست واحد نوشتن نیاز نیست با Mock  کردن کلاس FormsAuthentication سروکار داشته باشیم و فقط کافیه چک کنیم خروجی از نوع LogoutActionResult هست یا خیر و یا می‌تونیم ActionAfterLogout رو چک کنیم.

منابع و مراجع: + و +
 
مطالب
مبانی TypeScript؛ اینترفیس‌ها
اینترفیس، مانند قراردادی است که یک نوع را تعریف می‌کند. کامپایلر از اینترفیس‌ها جهت بررسی نوع‌ها و اجبار به رعایت قرارداد استفاده می‌کند. در این حالت اگر متدها یا خواص معرفی شده‌ی در نوع اینترفیس، توسط استفاده کننده بکار گرفته نشوند، خطایی توسط کامپایلر گزارش خواهد شد.
از آنجائیکه اینترفیس‌ها به معنای نوع‌های سفارشی هستند و جاوا اسکریپت از آن‌ها پشتیبانی نمی‌کند، توسط کامپایلر TypeScript، به هیچ نوع کد معادلی در جاوا اسکریپت، ترجمه و تبدیل نخواهند شد. کامپایلر TypeScript تنها از آن‌ها جهت بررسی نوع‌ها استفاده می‌کند.
اینترفیس‌ها به صورت مجموعه‌ای از تعاریف خواص و متدها، بدون پیاده سازی آن‌ها تعریف می‌شوند. پیاده سازی این اینترفیس‌ها، توسط کلاس‌ها و یا سایر اشیاء صورت خواهند گرفت. برای مثال یک قرارداد اجاره، مشخص می‌کند که آخر هر ماه چه مقداری را باید پرداخت کرد. اما این قرار داد مشخص نمی‌کند که چگونه باید این پرداخت صورت گیرد و از هر شخصی به شخص دیگری می‌تواند متفاوت باشد. به این حالت duck typing هم می‌گویند. به این معنا که قرار داد، شکل یک شیء را مشخص می‌کند و تا زمانیکه پیاده سازی کننده‌ی آن بتواند این قرارداد را تامین کند، می‌تواند بجای نوع اصلی نیز بکار گرفته شود.


Duck typing چیست؟

duck typing به این معنا است که اگر پرنده‌ای بتواند مانند یک اردک راه برود، شنا کند و صدا در بیاورد، یک اردک نامیده می‌شود. بنابراین همینقدر که یک شیء بتواند قراردادی را پیاده سازی کند، نوع آن با نوع اینترفیس یکی درنظر گرفته می‌شود. برای نمونه به مثال ذیل دقت کنید:
interface Duck {
    walk: () => void;
    swim: () => void;
    quack: () => void;
}

let probablyADuck = {
    walk: () => console.log('walking like a duck'),
    swim: () => console.log('swimming like a duck'),
    quack: () => console.log('quacking like a duck')
}

function FlyOverWater(bird: Duck) { }

FlyOverWater(probablyADuck); // works!
در این مثال اینترفیس Duck، متدهایی را تعریف کرده‌است که یک Duck می‌تواند انجام دهد.
در ادامه متغیر و شیءایی بدون تعریف نوع آن ایجاد شده‌است که همان متدهای اینترفیس Duck را پیاده سازی می‌کند و امضای آن‌ها با امضای متدهای اینترفیس Duck یکی هستند.
سپس متد FlyOverWater تعریف شده که در آن، نوع پارامتر ورودی آن به صورت صریحی به نوع اینترفیس Duck مقید شده‌است.
در سطر بعدی، این متد با دریافت شیء probablyADuck فراخوانی شده‌است و چون این شیء تمام اجزای قرارداد Duck را پیاده سازی کرده‌است، مشکلی در اجرای آن نخواهد بود. به این حالت duck typing می‌گویند.


نحوه‌ی تعریف یک اینترفیس در TypeScript

تعریف یک اینترفیس با واژه‌ی کلیدی interface شروع شده و سپس خواص و متدهای مدنظر این قرارداد، به همراه نوع آن‌ها تعریف خواهند شد:
interface Book {
    id: number;
    title: string;
    author: string;
    pages?: number;
    markDamaged: (reason: string) => void;
}
در این مثال خواص id، title و author اجباری هستند و پیاده سازی کننده موظف است آن‌ها را به همراه داشته باشد.
در اینترفیس‌های TypeScript می‌توان خواص اختیاری و optional را نیز تعریف کرد. نمونه‌ی آن خاصیت pages در این مثال است که با ? مشخص شده‌است و نمونه‌ی آن‌را در حین تعریف پارامترهای اختیاری متدها نیز پیشتر ملاحظه کرده بودید.
تعریف متدها در یک اینترفیس، با مشخص سازی نام آن متد و ذکر یک کولن و سپس مشخص سازی امضای پارامترهای دریافتی  انجام می‌شود. نوع خروجی متد، در سمت راست علامت <= قرار خواهد گرفت.


استفاده از اینترفیس‌ها برای تعریف نوع خروجی توابع

در مثال زیر، متد CreateCustomerID دارای دو پارامتر ورودی از نوع‌های رشته‌ای و عددی است و خروجی آن نیز از نوع رشته‌ای تعریف شده‌است:
function CreateCustomerID(name: string, id: number): string {
    return name + id;
}
در ادامه تعریف متغیری را مشاهده می‌کنید که نوع آن، متدی است که با امضای متد CreateCustomerID یکسان است:
 let IdGenerator: (chars: string, nums: number) => string;
به این ترتیب امکان انتساب متد CreateCustomerID به متغیر IdGenerator وجود خواهد داشت:
IdGenerator = CreateCustomerID;
جهت مدیریت بهتر یک چنین تعریف‌هایی و همچنین امکان استفاده‌ی مجدد از آن‌ها، می‌توان از اینترفیس‌ها کمک گرفت:
interface StringGenerator {
     (chars: string, nums: number): string;
}
اینترفیس StringGenerator نام بهتر و با قابلیت استفاده‌ی مجددی را به نوع متدی که قابل انتساب است به متغیر IdGenerator، تعریف می‌کند. در اینجا syntax تعریف نوع متد، در اینترفیس StringGenerator اندکی با حالت‌های قبلی متفاوت است. در اینجا بجای استفاده از <= جهت مشخص کردن نوع خروجی متد، از کولن استفاده شده‌است.
اکنون می‌توان نحوه‌ی تعریف متغیر IdGenerator را به صورت زیر Refactor کرد و تغییر داد:
 let IdGenerator: StringGenerator;
به عنوان نمونه می‌توان یک چنین تغییری را در نحوه‌ی تعریف اینترفیس Book ابتدای بحث و تغییر متد markDamaged آن نیز اعمال کرد.


بسط و توسعه‌ی اینترفیس‌ها

بسط و توسعه‌ی اینترفیس‌ها شبیه به مباحث ارث بری هستند. به این ترتیب که با بسط یک اینترفیس از طریق اینترفیسی دیگر، می‌توان به نوعی مرکب رسید:
interface LibraryResource {
   catalogNumber: number;
}

interface LibraryBook {
   title: string;
}

interface Encyclopedia extends LibraryResource, LibraryBook {
   volume: number;
}
در این مثال، ابتدا دو اینترفیس منابع و کتاب‌های یک کتابخانه تعریف شده‌اند. سپس اینترفیس جدیدی به نام Encyclopedia با بسط این دو اینترفیس توسط واژه‌ی کلیدی extends ایجاد شده‌است.
این نوع مرکب، علاوه بر دارا بودن خاصیت volume مختص به خودش، اکنون حاوی دو خاصیت موجود در سایر اینترفیس‌های ذکر شده‌ی در قسمت extends نیز هست.
حال اگر متغیر جدیدی را از نوع Encyclopedia تعریف کنیم، جهت برآورده شده تمام اجزای قرارداد، لازم است هر سه خاصیت را مقدار دهی نمائیم:
let refBook: Encyclopedia = {
   catalogNumber: 1234,
   title: 'The Book of Everything',
   volume: 1
}


نوع کلاس‌ها

مبحث کلاس‌ها به صورت جداگانه‌ای در این سری بررسی خواهند شد. اما جهت تکمیل بحث جاری نیاز است اشاره‌ی کوتاهی به آنها شود.
همانطور که عنوان شد، اینترفیس‌ها تنها شکل و قرارداد پیاده سازی یک شیء را تعریف می‌کنند؛ بدون ارائه‌ی پیاده سازی خاصی از آن‌‌ها. تا اینجا در بحث جاری، اشیاء را توسط object literals داخل {} تعریف کردیم (مانند متغیر refBook مثال قبل). اما کلاس‌ها روش بهتری برای انجام این‌کار و تعریف اشیاء هستند.
در ذیل تعریف اینترفیس کتابدار را با تک متد doWork آن ملاحظه می‌کنید:
interface Librarian {
   doWork: () => void;
}
متد doWork دارای پارامتری نیست و خروجی نیز ندارد. سپس با استفاده از واژه‌ی کلیدی class، یک کلاس جدید را ایجاد کرده‌ایم که با استفاده‌ی واژه‌ی کلیدی implements، یک پیاده سازی مشخص از اینترفیس Librarian را ارائه می‌دهد:
class ElementarySchoolLibrarian implements Librarian {
   doWork() {
     console.log('Reading to and teaching children...');
   }
}
اکنون داخل این کلاس، پیاده سازی خاصی از متد doWork مشخص شده‌ی در قرارداد و اینترفیس Librarian را مشاهده می‌کنید.
در ادامه برای ایجاد شیءایی از روی این تعریف، به نحو ذیل عمل می‌کنیم:
 let kidsLibrarian: Librarian = new ElementarySchoolLibrarian();
kidsLibrarian.doWork();
در اینجا متغیر kidsLibrarian از نوع اینترفیس کتابدار تعریف شده‌است. به این معنا که شیءایی که به آن انتساب داده می‌شود باید این اینترفیس را پیاده سازی کند. این شیء نیز توسط واژه‌ی کلیدی new، نمونه سازی/وهله سازی می‌شود. در ادامه می‌توان به متدها و خواص شیء kidsLibrarian دسترسی یافت و آن‌ها را فراخوانی کرد.
مطالب
C# 7 - More Expression-Bodied Members
یکی از امکانات جالب سی‌شارپ که در نسخه 6 معرفی شد، قابلیت Expression-Bodied Members بود. در نسخه 7 سی‌شارپ، امکانات جدیدتری اضافه شده است؛ به عنوان مثال اکنون می‌توان برای constructors, finalizers و همچنین get and set برای پراپرتی‌ها و ایندکسرها نیز از این قابلیت استفاده کرد.

 
استفاده از expression body برای constructors 
public class Person
{
    public string FirstName { get; set; }
    public Person(string firstName)
    {
        this.FirstName = firstName;
    }
}
به عنوان مثال اکنون سازنده‌ی کلاس فوق را می‌توانیم از روش block body متداول، به روش expression body، به صورت خلاصه‌تری بنویسیم:
public class Person
{
     public string FirstName { get; set; }
     public Person(string firstName) => this.FirstName = firstName;
}
البته محدودیت این روش این است که تنها برای یک پارامتر می‌توانیم به اینصورت عمل کنیم؛ اما در نسخه‌ 7.1  قرار است قابلیت استفاده از expression body برای بیشتر از یک پارامتر نیز اضافه شود:
public class Person
{
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age) => (Name, Age) = (name, age);
}

اما اگر نیاز داشتید برای بیشتر از دو متغیر از expression body استفاده کنید می‌توانید از Tuple برای شبیه‌سازی آن استفاده کنید(+):
public class Person
{
    private readonly (string name, int age) _tuple;    

    public string Name => _tuple.name;
    public int Age => _tuple.age;

    public Person(string name, int age) => _tuple = (name, age);
}

استفاده از expression body برای destructors 
public class Resource
{
    ~Resource() => Console.WriteLine("destructor");
}


 استفاده از expression body در get / set accessors 
 در سی‌شارپ 7 برای accessors نیز می‌توانیم از سینتکس جدید expression body استفاده کنیم. به عنوان مثال کد زیر را در نظر بگیرید:
private int _x;
public int X 
{
    get
    {
        return _x;
    }
    set
    {
        _x = value;
    }
}
کد فوق را می‌توانیم در سی‌شارپ 7 به صورت خلاصه‌تری بنویسیم:
private int _x;
public int X 
{
    get => _x;
    set => _x = value;
}

در ویژوال‌استودیوی 2017 نیز با قرار دادن ماوس بر روی پراپرتی x_، استفاده‌ی از سینتکس expression body به شما پیشنهاد داده خواهد شد:


همچنین برای Event Accessors نیز می‌توانیم از این قابلیت استفاده کنیم:

private EventHandler _someEvent;
public event EventHandler SomeEvent
{
    add => _someEvent += value;
    remove => _someEvent -= value;
}


مطالب
آشنایی با Refactoring - قسمت 7


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

using System;
using System.Collections.Generic;

namespace Refactoring.Day7.IntroduceParameterObject.Before
{
public class Registration
{
public void Create(string name, DateTime date, DateTime validUntil,
IEnumerable<string> courses, decimal credits)
{
// do work
}
}
}

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

using System;
using System.Collections.Generic;

namespace Refactoring.Day7.IntroduceParameterObject.After
{
public class RegistrationContext
{
public string Name {set;get;}
public DateTime Date {set;get;}
public DateTime ValidUntil {set;get;}
public IEnumerable<string> Courses {set;get;}
public decimal Credits { set; get; }
}
}

namespace Refactoring.Day7.IntroduceParameterObject.After
{
public class Registration
{
public void Create(RegistrationContext registrationContext)
{
// do work
}
}
}

یکی از مزایای این روش، منعطف شدن معرفی متدها است؛ به این صورت که اگر نیاز به افزودن پارامتر دیگری باشد، تنها کافی است یک خاصیت جدید به کلاس RegistrationContext اضافه شود و امضای متد Create،‌ ثابت باقی خواهد ماند.

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

public int GetIndex(int pageSize, int pageNumber, ...) { ...

همانطور که ملاحظه می‌کنید تعدادی از پارامترها در اینجا با کلمه page شروع شده‌اند. بهتر است این‌ پارامترهای مرتبط را به یک کلاس مجزا به نام Page انتقال داد.


مطالب دوره‌ها
استفاده از RavenDB در ASP.NET MVC به همراه تزریق وابستگی‌ها
جهت تکمیل مباحث این دوره می‌توان به نحوه مدیریت سشن‌ها و document store بانک اطلاعاتی RavenDB با استفاده از یک IoC Container مانند StructureMap در ASP.NET MVC پرداخت. اصول کلی آن به تمام فناوری‌های دات نتی دیگر مانند وب فرم‌ها، WPF و غیره نیز قابل بسط است. تنها پیشنیاز آن مطالعه «کامل» دوره «بررسی مفاهیم معکوس سازی وابستگی‌ها و ابزارهای مرتبط با آن » می‌باشد.


هدف از بحث
ارائه راه حلی جهت تزریق یک وهله از واحد کار تشکیل شده (همان شیء سشن در RavenDB) به کلیه کلاس‌های لایه سرویس برنامه و همچنین زنده نگه داشتن شیء document store آن در طول عمر برنامه است. ایجاد شیء document store که کار اتصال به بانک اطلاعاتی را مدیریت می‌کند، بسیار پرهزینه است. به همین جهت این شیء تنها یکبار باید در طول عمر برنامه ایجاد شود.


ابزارها و پیشنیازهای لازم
ابتدا یک برنامه جدید ASP.NET MVC را آغاز کنید. سپس ارجاعات لازم را به کلاینت RavenDB، سرور درون پروسه‌ای آن (RavenDB.Embedded) و همچنین StructureMap با استفاده از نیوگت، اضافه نمائید:
 PM> Install-Package RavenDB.Client
PM> Install-Package RavenDB.Embedded -Pre
PM> Install-Package structuremap

دریافت کدهای کامل این مثال

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


الف) لایه سرویس‌های برنامه
using RavenDB25Mvc4Sample.Models;
using System.Collections.Generic;

namespace RavenDB25Mvc4Sample.Services.Contracts
{
    public interface IUsersService
    {
        User AddUser(User user);
        IList<User> GetUsers(int page, int count = 20);
    }
}

using System.Collections.Generic;
using System.Linq;
using Raven.Client;
using RavenDB25Mvc4Sample.Models;
using RavenDB25Mvc4Sample.Services.Contracts;

namespace RavenDB25Mvc4Sample.Services
{
    public class UsersService : IUsersService
    {
        private readonly IDocumentStore _documentStore;
        private readonly IDocumentSession _documentSession;
        public UsersService(IDocumentStore documentStore, IDocumentSession documentSession)
        {
            _documentStore = documentStore;
            _documentSession = documentSession;
        }

        public User AddUser(User user)
        {
            _documentSession.Store(user);
            return user;
        }

        public IList<User> GetUsers(int page, int count = 20)
        {
            return _documentSession.Query<User>()
                                   .Skip(page * count)
                                   .Take(count)
                                   .ToList();
        }

        //todo: سایر متدهای مورد نیاز در اینجا

    }
}
نکته مهمی که در اینجا وجود دارد، استفاده از اینترفیس‌های خود RavenDB است. به عبارتی IDocumentSession، تشکیل دهنده الگوی واحد کار در RavenDB است و نیازی به تعاریف اضافه‌تری در اینجا وجود ندارد.
هر کلاس لایه سرویس با یک اینترفیس مشخص شده و اعمال آن‌ها از طریق این اینترفیس‌ها در اختیار کنترلرهای برنامه قرار می‌گیرند.

ب) لایه Infrastructure برنامه
در این لایه کدهای اتصالات IoC Container مورد استفاده قرار می‌گیرند. کدهایی که به برنامه جاری وابسته‌اند، اما حالت عمومی و مشترکی ندارند تا در سایر پروژه‌های مشابه استفاده شوند.
using Raven.Client;
using Raven.Client.Embedded;
using RavenDB25Mvc4Sample.Services;
using RavenDB25Mvc4Sample.Services.Contracts;
using StructureMap;

namespace RavenDB25Mvc4Sample.Infrastructure
{
    public static class IoCConfig
    {
        public static void ApplicationStart()
        {
            ObjectFactory.Initialize(x =>
            {
                // داکیومنت استور سینگلتون تعریف شده چون باید در طول عمر برنامه زنده نگه داشته شود
                x.ForSingletonOf<IDocumentStore>().Use(() =>
                       {
                           return new EmbeddableDocumentStore
                           {
                               DataDirectory = "App_Data"
                           }.Initialize();
                       });

                // سشن در برنامه وب هیبرید تعریف شده تا در طول عمر یک درخواست زنده نگه داشته شود
                // در برنامه‌های ویندوزی حالت هیبرید را حذف کنید
                x.For<IDocumentSession>().HybridHttpOrThreadLocalScoped().Use(context =>
                    {
                        return context.GetInstance<IDocumentStore>().OpenSession();
                    });

                // اتصالات لایه سرویس در اینجا
                x.For<IUsersService>().Use<UsersService>();
                // ...
            });
        }

        public static void ApplicationEndRequest()
        {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
        }
    }
}
تعاریف اتصالات StructureMap را در اینجا ملاحظه می‌کنید.
IDocumentStore و IDocumentSession، دو اینترفیس تعریف شده در کلاینت RavenDB هستند. اولی کار اتصال به بانک اطلاعاتی را مدیریت خواهد کرد و دومی کار مدیریت الگوی واحد کار را انجام می‌دهد. IDocumentStore به صورت Singleton تعریف شده است؛ چون باید در طول عمر برنامه زنده نگه داشته شود. اما IDocumentStore در ابتدای هر درخواست رسیده، وهله سازی شده و سپس در پایان هر درخواست در متد ApplicationEndRequest به صورت خودکار Dispose خواهد شد.
اگر به فایل Global.asax.cs پروژه وب برنامه مراجعه کنید، نحوه استفاده از این کلاس را مشاهده خواهید کرد:
using System;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Routing;
using RavenDB25Mvc4Sample.Infrastructure;
using StructureMap;

namespace RavenDB25Mvc4Sample
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            IoCConfig.ApplicationStart();

            AreaRegistration.RegisterAllAreas();

            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            //Set current Controller factory as StructureMapControllerFactory  
            ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
        }

        protected void Application_EndRequest(object sender, EventArgs e)
        {
            IoCConfig.ApplicationEndRequest();
        }
    }

    public class StructureMapControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
                throw new InvalidOperationException(string.Format("Page not found: {0}", 
                    requestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture)));
            return ObjectFactory.GetInstance(controllerType) as Controller;
        }
    }
}
در ابتدای کار برنامه، متد IoCConfig.ApplicationStart جهت برقراری اتصالات، فراخوانی می‌شود. در پایان هر درخواست نیز شیء سشن جاری تخریب خواهد شد. همچنین کلاس StructureMapControllerFactory نیز جهت وهله سازی خودکار کنترلرهای برنامه به همراه تزریق وابستگی‌های مورد نیاز، تعریف گشته است.

ج) استفاده از کلاس‌های لایه سرویس در کنترلرهای برنامه

using System.Web.Mvc;
using Raven.Client;
using RavenDB25Mvc4Sample.Models;
using RavenDB25Mvc4Sample.Services.Contracts;

namespace RavenDB25Mvc4Sample.Controllers
{
    public class HomeController : Controller
    {
        private readonly IDocumentSession _documentSession;
        private readonly IUsersService _usersService;
        public HomeController(IDocumentSession documentSession, IUsersService usersService)
        {
            _documentSession = documentSession;
            _usersService = usersService;
        }

        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش صفحه ثبت
        }

        [HttpPost]
        public ActionResult Index(User user)
        {
            if (this.ModelState.IsValid)
            {
                _usersService.AddUser(user);
                _documentSession.SaveChanges();

                return RedirectToAction("Index");
            }
            return View(user);
        }
    }
}
پس از این مقدمات و طراحی اولیه، استفاده از کلاس‌های لایه سرویس در کنترل‌ها، ساده خواهند بود. تنها کافی است اینترفیس‌های مورد نیاز را از طریق روش تزریق در سازنده کلاس‌ها تعریف کنیم. سایر مسایل وهله سازی آن خودکار خواهند بود.
نظرات مطالب
C# 7 - Ref Returns and Ref Locals
یک نکته‌ی تکمیلی: امکان تعریف خروجی از نوع ref readonly در C# 7.2

modifier جدیدی در C# 7.2 به نام ref readonly جهت تعریف نوع خروجی متدها نیز معرفی شده‌است. به این ترتیب یک متد می‌تواند بازگشت ارجاعی به اطلاعاتی موجود را بیان و همچنین فراخوان را از تغییر آن منع کند.
البته فراخوان می‌تواند تصمیم گیری کند که آیا یک کپی و یا یک ارجاع فقط خواندنی را از این متد ویژه دریافت کند. به این معنا که خروجی از نوع ref readonly، فراخوان را ملزم به تعریف یک متغیر محلی از نوع ref readonly نمی‌کند.

در مثال زیر، متد ReturnBiggestA یک خروجی کپی را باز می‌گرداند و متد ReturnBiggestARefReadonly دقیقا ارجاعی را به DataInfo اصلی بازگشت می‌دهد و با آن یکی است:
namespace CS72Tests
{
    public struct DataInfo
    {
        public double A;
    }

    public class RefReadonlyExamples
    {
        public DataInfo ReturnBiggestA(in DataInfo data1, in DataInfo data2)
        {
            return data1.A > data2.A ? data1 : data2;
        }

        public ref readonly DataInfo ReturnBiggestARefReadonly(in DataInfo data1, in DataInfo data2)
        {
            if (data1.A > data2.A)
            {
                return ref data1;
            }
            return ref data2;
        }

        public void TestingRefReadonly()
        {
            var data1 = new DataInfo { A = 0 };
            var data2 = new DataInfo { A = 100 };

            var biggest = ReturnBiggestA(data1, data2);
            biggest.A = 42;


            var biggest2 = ReturnBiggestARefReadonly(data1, data2);
            biggest2.A = 99;


            ref readonly var biggest3 = ref ReturnBiggestARefReadonly(data1, data2);
            biggest3.A = 99; // ERROR: The left-hand side of an assignment must be a variable, property or indexer
        }
    }
}
- در این فراخوانی‌ها، biggest یک کپی از data2 را باز می‌گرداند. به همین جهت می‌توان A آن‌را تغییر داد.
- در اولین فراخوانی ReturnBiggestARefReadonly، با تعریف خروجی به صورت var biggest2، یک کپی از data2 را دریافت کرده‌ایم. به همین جهت A آن قابل تغییر است.
- اما در دومین فراخوانی ReturnBiggestARefReadonly، چون خروجی آن‌را از نوع ref readonly var دریافت کرده‌ایم، این خروجی به data2 اصلی اشاره می‌کند و همچنین فقط خواندنی است. بنابراین سطر بعدی آن که A را تغییر می‌دهد، مجاز نیست.

پ.ن
در ابتدا قصد داشتند ref readonly را برای تعریف پارامترهای value type نیز بکار برند، اما این تصمیم با معرفی پارامترهای از نوع in جایگزین شد. به همین جهت ممکن است مقالات قدیمی‌تر C# 7.2 را با تعریف متدهایی مانند ذیل نیز مشاهده کنید که در نگارش آخر C# 7.2، تمام این‌ها به in تغییر کرده‌اند:
public static void Add(ref readonly int x, ref readonly int y, ref int z)
{
   z = x + y + z;
}
نظرات مطالب
آشنایی با Gridify
فرض کنید مدل زیر رو داریم
public class Order : AggregateRoot<int>
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    public int? GetBuyerId => _buyerId;
    public int? _buyerId;
    public OrderStatus OrderStatus { get; private set; }
    public  int _orderStatusId;
    private string _description;
    private bool _isDraft;


    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;

    protected Order()
    {
        _orderItems = new List<OrderItem>();
        _isDraft = false;
    }
    public Order(string userId, string userName, Address address,
        int? buyerId = null) : this()
    {
        _buyerId = buyerId;
        _orderStatusId = OrderStatus.Submitted.Id;
        _orderDate = DateTime.UtcNow;
        Address = address;
        //AddOrderStartedDomainEvent(userId, userName);
    }
}

public class OrderStatus : Enumeration
{
    public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
    public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
    public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
    public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
    public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
    public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());
    public OrderStatus(int id, string name)
        : base(id, name)
    {
    }
}
حال چگونه میتوانیم از Order بر اساس OrderStatus.Id فیلترینگ انجام بدیم ؟
var query = _orderQueryRepository.GetAll(x => x._buyerId == 52).AsNoTracking();
var s = await query.GridifyAsync(request.queryFilter);
return s.Adapt<Paging<OrderQuery>>();
{
  "buyerid": 0,
  "queryFilter": {
    "page": 1,
    "pageSize": 5,
    "orderBy": "id",
    "filter": "Order_OrderStatus_Id==1"
  }
}
خروجی
 "message": "Property 'Order_OrderStatus_Id' not found.",

مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #1
نگاشت اشیاء امری مفید و لذت بخش است. ولی بخاطر تنظیمات خاص آن و افزایش کدها، همیشه کمی دردسر ساز بوده است. استفاده از کلاس Profile راه کار مناسبی است؛ اما در این حالت کلاس مقصد (ViewModel) از تنظیمات نگاشت‌ها بی اطلاع می‌ماند و فقط حاوی داده خواهد بود. برای ادغام کلاس و تنظیمات نگاشت در اینجا راهکاری ارائه گردید که در ادامه و با الگو گیری از همین ایده، اقدام به ارائه‌ی روشی جدید می‌کنم که با استفاده از Attribute‌ها تنظیمات نگاشت اشیاء را در AutoMapper انجام می‌دهد.
در نهایت می‌خواهیم نگاشت‌ها را اینچنین تنظیم کنیم:
 [MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
 public class AdminStudentViewModel
 {
     // [IgnoreMap]
     public int Id { set; get; }

     [MapForMember("Name")]
     public string FirstName { set; get; }

     [MapForMember("Family")]
     public string LastName { set; get; }

     public string Email { set; get; }

     [MapForMember("RegisterDateTime")]
     public string RegisterDateTimePersian { set; get; }

     [UseValueResolver(typeof (BookCountValueResolver))]
     public int BookCounts { set; get; }

     [UseValueResolver(typeof (BookPriceValueResolver))]
     public decimal BookPrice { set; get; }
 };
  این سبک تنظیم کردن نگاشت‌های اشیاء به نظر بهتر از روش‌های دیگر است؛ چون کلاس‌های ویوومدل را معنادار کرده و همچنین برای برنامه نویسان EF و ASP.NET MVC استفاده‌ی از ویژگی‌ها، یک شیوه‌ی کاری معمول به حساب می‌آید. 
به تعریف و توضیح صفت‌های (ویژگی‌ها یا Attributes) مورد نیاز می‌پردازم:

صفت MapFromAttribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MapFromAttribute : Attribute
{
    public Type SourceType { get; private set; }
    public bool IgnoreAllNonExistingProperty { get; private set; }
    public bool AlsoCopyMetadata { get; private set; }    //Go to: https://www.dntips.ir/courses/topic/16/cb36bc2e-4263-431e-86a5-236322cb5576

    public MapFromAttribute(Type sourceType, bool ignoreAllNonExistingProperty = false,
        bool alsoCopyMetadata = false)
    {
        SourceType = sourceType;
        IgnoreAllNonExistingProperty = ignoreAllNonExistingProperty;
        AlsoCopyMetadata = alsoCopyMetadata;
    }
};
این صفت روی کلاس‌ها می‌نشیند و توسط آرگومان sourceType آن، نوع مبدأ را برای automapper مشخص می‌کند. در واقع همه چیز از اینجا شروع می‌شود. همچنین آرگومان ignoreAllNonExistingProperty مشخص می‌کند کلیه‌ی صفاتی که در مقصد هستند ولی معادل اسمی در مبدأ ندارند، بصورت خودکار رد (Ignore) شده و از آن‌ها صرف نظر شود تا از شکست متد AutoMapper.Mapper.AssertConfigurationIsValid جلوگیری کند (پرداخته شده در اینجا). آرگومان alsoCopyMetadata پیاده سازی نمی‌شود؛ ولی می‌تواند پرچمی باشد تا اجازه دهد  Data Annotations از مدل‌های ef به ViewModel انتقال یابند.


صفت IgnoreMapAttribute
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreMapAttribute : Attribute {};
از این صفت برای رد کردن خصیصه‌‌ای در نگاشت‌ها استفاده می‌کنیم. لازم به ذکر است که صفتی مشابه در Automapper.IgnoreAttribute وجود دارد که می‌تواند به جای این صفت مورد استفاده قرار گیرد. «نگارنده جهت همخوانی با سایر صفات، اقدام به استفاده‌ی از این صفت می‌کند»


صفت MapForMemberAttribute
[AttributeUsage(AttributeTargets.Property)]
public class MapForMemberAttribute : Attribute
{
    public string MemberToMap { get; private set; }
    public MapForMemberAttribute(string memberToMap)
    {
        MemberToMap = memberToMap;
    }
};
اگر نام خصیصه‌ها در مبدأ و مقصد یکی نباشند، از این صفت برای همگام سازی این دو استفاده می‌کنیم.


صفت UseValueResolverAttribute
[AttributeUsage(AttributeTargets.Property)]
public class UseValueResolverAttribute : Attribute
{
    public IValueResolver ValueResolver { get; private set; }
    public UseValueResolverAttribute(Type valueResolver)
    {
        ValueResolver = valueResolver.GetConstructors()[0].Invoke(new object[] {}) as IValueResolver;
    }
};
استفاده از ValueResolver‌ها در اینجا ذکر شده است. از این صفت برای تنظیم این مقدار برای یک خصیصه استفاده می‌شود. برای مثال فیلد FullName را در مقصد درنظر بگیرد که از دو فیلد Name و Family در مبدأ تشکیل می‌شود.

تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفت‌ها را به نگاشت متناسبی در automapper تبدیل کنیم.
دریافت کدها
ادامه دارد...