بازخوردهای پروژه‌ها
ایراد در نمایش جزییات نمایش مطالب و TreeView Extension
زمانی که روی جزییات نمایش  مطلبی کلیک میکنیم خطای Helper مربوط به  TreeView  رو میگیره بعد از بررسی متوجه شدم کد TreeView ربطی به پروژه نداره و اصلا داخل پوشه  Helper  تعریف نشده.

خطای دریافتی بعد از زدن روی دکمه مشاهده
( به ازای کامنت هر مطلب  از TreeView  استفاده شده، خطا میدهد)


HttpCompileException: d:\IrisCms-master\Iris.Web\Views\Comment\_PostComments.cshtml(7): error CS1061: 'System.Web.Mvc.HtmlHelper<System.Collections.Generic.IEnumerable<Iris.DomainClasses.Entities.Comment>>' does not contain a definition for 'TreeView' and no extension method 'TreeView' accepting a first argument of type 'System.Web.Mvc.HtmlHelper<System.Collections.Generic.IEnumerable<Iris.DomainClasses.Entities.Comment>>' could be found (are you missing a using directive or an assembly reference?) 


کد TreeView
using System.Collections.Generic;
using System.Web.Mvc;
using Iris.Servicelayer.Interfaces;

namespace Iris.Web.Controllers
{
    public partial class TreeViewController : Controller
    {
        private readonly IPageService _pageService;

        public TreeViewController(IPageService pageService)
        {
            _pageService = pageService;
        }

        public virtual ActionResult Index()
        {
            List<TreeViewLocation> locations = GetLocations();

            return View(_pageService.GetNavBarData(x => x.Body.Equals("dsad")));
        }

        public static List<TreeViewLocation> GetLocations()
        {
            var locations = new List<TreeViewLocation>
            {
                new TreeViewLocation
                {
                    Name = "United States",
                    ChildLocations =
                    {
                        new TreeViewLocation
                        {
                            Name = "Chicago",
                            ChildLocations =
                            {
                                new TreeViewLocation {Name = "Rack 1"},
                                new TreeViewLocation {Name = "Rack 2"},
                                new TreeViewLocation {Name = "Rack 3"},
                                new TreeViewLocation {Name = "Rack 3"},
                            }
                        },
                        new TreeViewLocation
                        {
                            Name = "Dallas",
                            ChildLocations =
                            {
                                new TreeViewLocation
                                {
                                    Name = "Rack 1",
                                    ChildLocations =
                                    {
                                        new TreeViewLocation
                                        {
                                            Name = "Rack 1",
                                            ChildLocations =
                                            {
                                                new TreeViewLocation {Name = "Rack 1"},
                                                new TreeViewLocation {Name = "Rack 2"},
                                                new TreeViewLocation {Name = "Rack 3"},
                                                new TreeViewLocation {Name = "Rack 3"},
                                            }
                                        },
                                        new TreeViewLocation {Name = "Rack 2"},
                                        new TreeViewLocation {Name = "Rack 3"},
                                        new TreeViewLocation {Name = "Rack 3"},
                                    }
                                },
                                new TreeViewLocation {Name = "Rack 2"},
                                new TreeViewLocation {Name = "Rack 3"},
                                new TreeViewLocation {Name = "Rack 3"},
                            }
                        },
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                    }
                },
                new TreeViewLocation
                {
                    Name = "Canada",
                    ChildLocations =
                    {
                        new TreeViewLocation {Name = "Ontario"},
                        new TreeViewLocation {Name = "Windsor"}
                    }
                }
            };
            return locations;
        }
    }

    public class TreeViewLocation
    {
        public TreeViewLocation()
        {
            ChildLocations = new HashSet<TreeViewLocation>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public ICollection<TreeViewLocation> ChildLocations { get; set; }
    }
}

مطالب
ASP.NET MVC #7

آشنایی با Razor Views

قبل از اینکه بحث جاری ASP.NET MVC را بتوانیم ادامه دهیم و مثلا مباحث دریافت اطلاعات از کاربر، کار با فرم‌ها و امثال آن‌را بررسی کنیم، نیاز است حداقل به دستور زبان یکی از View Engineهای ASP.NET MVC آشنا باشیم.
MVC3 موتور View جدیدی را به نام Razor معرفی کرده است که به عنوان روش برگزیده ایجاد Viewها در این سیستم به شمار می‌رود و فوق العاده نسبت به ASPX view engine سابق، زیباتر، ساده‌تر و فشرده‌تر طراحی شده است و یکی از اهداف آن تلفیق code و markup می‌باشد. در این حالت دیگر پسوند فایل‌های Viewها همانند سابق ASPX نخواهد بود و به cshtml و یا vbhtml تغییر یافته است. همچنین برخلاف web forms view engine از System.Web.Page مشتق نشده است. و باید دقت داشت که Razor یک زبان برنامه نویسی جدید نیست. در اینجا از مخلوط زبان‌های سی شارپ و یا ویژوال بیسیک به همراه تگ‌های html استفاده می‌شود.
البته این را هم باید عنوان کرد که این مسایل سلیقه‌ای است. اگر با web forms view engine راحت هستید، با همان کار کنید. اگر با هیچکدام از این‌ها راحت نیستید (!) نمونه‌های دیگر هم وجود دارند، مثلا:

Razor Views یک سری قابلیت جالب را هم به همراه دارند:
1) امکان کامپایل آن‌ها به درون یک DLL وجود دارد. مزیت: استفاده مجدد از کد، عدم نیاز به وجود صریح فایل cshtml یا vbhtml بر روی دیسک سخت.
2) آزمون پذیری: از آنجائیکه Razor viewها به صورت یک کلاس کامپایل می‌شوند و همچنین از System.Web.Page مشتق نخواهند شد، امکان بررسی HTML نهایی تولیدی آن‌هابدون نیاز به راه اندازی یک وب سرور وجود دارد.
3) IntelliSense ویژوال استودیو به خوبی آن‌را پوشش می‌دهد.
4) با توجه به مواردی که ذکر شد، یک اتفاق جالب هم رخ داده است: امکان استفاده از Razor engine خارج از ASP.NET MVC هم وجود دارد. برای مثال یک سرویس ویندوز NT طراحی کرده‌اید که قرار است ایمیل فرمت شده‌ای به همراه اطلاعات مدل‌های شما را در فواصل زمانی مشخص ارسال کند؟ می‌توانید برای طراحی آن از Razor engine استفاده کنید و تهیه خروجی نهایی HTML آن نیازی به راه اندازی وب سرور و وهله سازی HttpContext ندارد.


ساختار پروژه مثال جاری

در ادامه مرور سریعی خواهیم داشت بر دستور زبان Razor engine و جهت نمایش این قابلیت‌ها، یک مثال ساده را در ابتدا با مشخصات زیر ایجاد خواهیم کرد:
الف) یک empty ASP.NET MVC 3 project را ایجاد کنید و نوع View engine را هم در ابتدای کار Razor انتخاب نمائید.
ب) دو کلاس زیر را به پوشه مدل‌های برنامه اضافه کنید:
namespace MvcApplication3.Models
{
public class Product
{
public Product(string productNumber, string name, decimal price)
{
Name = name;
Price = price;
ProductNumber = productNumber;
}
public string ProductNumber { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}

using System.Collections.Generic;

namespace MvcApplication3.Models
{
public class Products : List<Product>
{
public Products()
{
this.Add(new Product("D123", "Super Fast Bike", 1000M));
this.Add(new Product("A356", "Durable Helmet", 123.45M));
this.Add(new Product("M924", "Soft Bike Seat", 34.99M));
}
}
}

کلاس Products صرفا یک منبع داده تشکیل شده در حافظه است. بدیهی است هر نوع ORM ایی که یک ToList را بتواند در اختیار شما قرار دهد، توانایی تشکیل لیست جنریکی از محصولات را نیز خواهد داشت و تفاوتی نمی‌کند که کدامیک مورد استفاده قرار گیرد.

ج) سپس یک کنترلر جدید به نام ProductsController را به پوشه Controllers برنامه اضافه می‌کنیم:

using System.Web.Mvc;
using MvcApplication3.Models;

namespace MvcApplication3.Controllers
{
public class ProductsController : Controller
{
public ActionResult Index()
{
var products = new Products();
return View(products);
}
}
}

د) بر روی نام متد Index کلیک راست کرده، گزینه Add view را جهت افزودن View متناظر آن، انتخاب کنید. البته می‌شود همانند قسمت پنجم گزینه Create a strongly typed view را انتخاب کرد و سپس Product را به عنوان کلاس مدل انتخاب نمود و در آخر خیلی سریع یک لیست از محصولات را نمایش داد، اما فعلا از این قسمت صرفنظر نمائید، چون می‌خواهیم آن‌ را دستی ایجاد کرده و توضیحات و نکات بیشتری را بررسی کنیم.

ه) برای اینکه حین اجرای برنامه در VS.NET هربار نخواهیم که آدرس کنترلر Products را دستی در مرورگر وارد کنیم، فایل Global.asax.cs را گشوده و سپس در متد RegisterRoutes، در سطر Parameter defaults، مقدار پیش فرض کنترلر را مساوی Products قرار دهید.


مرجع سریع Razor

ابتدا کدهای View متد Index را به شکل زیر وارد نمائید:

@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>

@:line-2: @data <‪br />
<text>line-3:</text> @data
}
<‪br />
site@(data)
<‪br />
@@name
<‪br />
@(number/10)
<‪br />
First product: @Model.First().Name
<‪br />
@if (@number>10)
{
  <span>@data</span>
}
else
{
  <text>Plain Text</text>
}
<‪br />
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}

@*
A Razor Comment
*@

<‪br />
@("First product: " + Model.First().Name)
<‪br />
<img src="@(number).jpg" />

در ادامه توضیحات مرتبط با این کدها ارائه خواهد شد:

1) نحوه معرفی یک قطعه کد
@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>

@:line-2: @data <‪br />
<text>line-3:</text> @data
}

این کدها متعلق به Viewایی است که در قسمت (د) بررسی ساختار پروژه مثال جاری، ایجاد کردیم. در ابتدای آن هم نوع model مشخص شده تا بتوان ساده‌تر به اطلاعات شیء Model به کمک IntelliSense دسترسی داشت.
برای ایجاد یک قطعه کد در Viewایی از نوع Razor به این نحو عمل می‌شود:
@{  ...Code Block.... }

در اینجا مجاز هستیم کدهای سی شارپ را وارد کنیم. یک نکته جالب را هم باید درنظر داشت: امکان نوشتن تگ‌های html هم در این بین وجود دارد (بدون اینکه مجبور باشیم قطعه کد شروع شده را خاتمه دهیم، به حالت html معمولی سوئیچ کرده و دوباره یک قطعه کد دیگر را شروع نمائیم). مانند line1 مثال فوق. اگر کمی پایین‌تر از این سطر مثلا بنویسیم line2 (به عنوان یک برچسب) کامپایلر ایراد خواهد گرفت، زیرا این مورد نه متغیر است و نه از پیش تعریف شده است. به عبارتی نباید فراموش کنیم که اینجا قرار است کد نوشته شود. برای رفع این مشکل دو راه حل وجود دارد که در سطرهای دو و سه ملاحظه می‌کنید. یا باید از تگی به نام text برای معرفی یک برچسب در این میان استفاده کرد (سطر سه) یا اگر قرار است اطلاعاتی به شکل یک متن معمولی پردازش شود ابتدای آن مانند سطر دوم باید یک @: قرار گیرد.
کمی پایین‌تر از قطعه کد معرفی شده در بالا بنویسید:
<‪br />
site@data

اکنون اگر خروجی این View را در مرورگر بررسی کنید، دقیقا همین site@data خواهد بود. چون در این حالت Razor تصور خواهد کرد که قصد داشته‌اید یک آدرس ایمیل را وارد کنید. برای این حالت خاص باید نوشت:
<‪br />
site@(data)

به این ترتیب data از متغیر data تعریف شده در code block قبلی برنامه دریافت و نمایش داده خواهد شد.
شبیه به همین حالت در مثال زیر هم وجود دارد:
<img src="@(number).jpg" />

در اینجا اگر پرانتزها را حذف کنیم، Razor فرض را بر این خواهد گذاشت که شیء number دارای خاصیت jpg است. بنابراین باید به نحو صریحی، بازه کاری را مشخص نمائیم.

بکار گیری این علامت @ یک نکته جنبی دیگر را هم به همراه دارد. فرض کنید در صفحه قصد دارید آدرس توئیتری شخصی را وارد کنید. مثلا:
<‪br />
@name

در این حالت View کامپایل نخواهد شد و Razor تصور خواهد کرد که قرار است اطلاعات متغیری به نام name را نمایش دهید. برای نمایش این اطلاعات به همین شکل، یک @ دیگر به ابتدای سطر اضافه کنید:
<‪br />
@@name

2) نحوه معرفی عبارات
عبارات پس از علامت @ معرفی می‌شوند و به صورت پیش فرض Html Encoded هستند (در قسمت 5 در اینباره بیشتر توضیح داده شد):
First product: @Model.First().Name

در این مثال با توجه به اینکه نوع مدل در ابتدای View مشخص شده است، شیء Model به لیستی از Products اشاره می‌کند.

یک نکته:
مشخص سازی حد و مرز صریح یک متغیر در مثال زیر نیز کاربرد دارد:
<‪br />
@number/10

اگر خروجی این مثال را بررسی کنید مساوی 12/10 خواهد بود و محاسبه‌ای انجام نخواهد شد. برای حل این مشکل باز هم از پرانتز می‌توان کمک گرفت:
<‪br />
@(number/10)
3) نحوه معرفی عبارات شرطی

@if (@number>10)
{
  <span>@data</span>
}
else
{
  <text>Plain Text</text>
}

یک عبارت شرطی در اینجا با @if شروع می‌شود و سپس نکاتی که در «نحوه معرفی یک قطعه کد» بیان شد، در مورد عبارات داخل {} صادق خواهد بود. یعنی در اینجا نیز می‌توان عبارات سی شارپ مخلوط با تگ‌های html را نوشت.
یک نکته: عبارت شرطی زیر نادرست است. حتما باید سطرهای کدهای سی شارپ بین {} محصور شوند؛‌ حتی اگر یک سطر باشند:
@if( i < 1 ) int myVar=0;


4) نحوه استفاده از حلقه foreach
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}

حلقه foreach نیز مانند عبارات شرطی با یک @ شروع شده و داخل {} بدنه آن نکات «نحوه معرفی یک قطعه کد» برقرار هستند (امکان تلفیق code و markup با هم).
کسانی که پیشتر با web forms کار کرده باشند، احتمالا الان خواهند گفت که این یک پس رفت است و بازگشت به دوران ASP کلاسیک دهه نود! ما به ندرت داخل صفحات aspx وب فرم‌ها کد می‌نوشتیم. مثلا پیشتر یک GridView وجود داشت و یک دیتاسورس که به آن متصل می‌شد؛ مابقی خودکار بود و ما هیچ وقت حلقه‌ای ننوشتیم. در اینجا هم این مساله با نوشتن برای مثال «html helpers» قابل کنترل است که در قسمت‌های بعدی به آن‌ پرداخته خواهد شد. به عبارتی قرار نیست به این نحو با Viewهای Razor رفتار کنیم. این قسمت فقط یک آشنایی کلی با Syntax است.


5) امکان تعریف فضای نام در ابتدای View
@using namespace;

6) نحوه نوشتن توضیحات سمت سرور:
@*
A Razor Comment / Server side Comment
*@

7) نحوه معرفی عبارات چند جزئی:
@("First product: " + Model.First().Name)

همانطور که ملاحظه می‌کنید، ذکر یک پرانتز برای معرفی عبارات چندجزئی کفایت می‌کند.


استفاده از موتور Razor خارج از ASP.NET MVC

پیشتر مطلبی را در مورد «تهیه قالب برای ایمیل‌های ارسالی یک برنامه ASP.Net» در این سایت مطالعه کرده‌اید. اولین سؤالی هم که در ذیل آن مطلب مطرح شده این است: «در برنامه‌های ویندوز چطور؟» پاسخ این است که کل آن مثال بر مبنای HttpContext.Current.Server.Execute کار می‌کند. یعنی باید مراحل وهله سازی HttpContext و شیء Server توسط یک وب سرور و درخواست رسیده طی شود و ... شبیه سازی آن آنچنان مرسوم و کار ساده‌ای نیست.
اما این مشکل با Razor وجود ندارد. به عبارتی در اینجا برای رندر کردن یک Razor View به html نهایی، نیازی به HttpContext نیست. بنابراین از این امکانات مثلا در یک سرویس ویندوز ان تی یا یک برنامه کنسول، WinForms، WPF و غیره هم می‌توان استفاده کرد.
برای اینکه بتوان از Razor خارج از ASP.NET MVC استفاده کرد، نیاز به اندکی کدنویسی هست مثلا استفاده از کامپایلر سی شارپ یا وی بی و کامپایل پویای کد و یک سری ست آپ دیگر. پروژه‌ای به نام RazorEngine این‌ کپسوله سازی رو انجام داده و از اینجا http://razorengine.codeplex.com/ قابل دریافت است.


مطالب
استفاده از Web API در ASP.NET Web Forms
گرچه ASP.NET Web API بهمراه ASP.NET MVC بسته بندی شده و استفاده می‌شود، اما اضافه کردن آن به اپلیکیشن‌های ASP.NET Web Forms کار ساده ای است. در این مقاله مراحل لازم را بررسی می‌کنیم.

برای استفاده از Web API در یک اپلیکیشن ASP.NET Web Forms دو قدم اصلی باید برداشته شود:

  • اضافه کردن یک کنترلر Web API که از کلاس ApiController مشتق می‌شود.
  • اضافه کردن مسیرهای جدید به متد Application_Start.


یک پروژه Web Forms بسازید

ویژوال استودیو را اجرا کنید و پروژه جدیدی از نوع ASP.NET Web Forms Application ایجاد کنید.


کنترلر و مدل اپلیکیشن را ایجاد کنید

کلاس جدیدی با نام Product بسازید و خواص زیر را به آن اضافه کنید.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}
همانطور که مشاهده می‌کنید مدل مثال جاری نمایانگر یک محصول است. حال یک کنترلر Web API به پروژه اضافه کنید. کنترلر‌های Web API درخواست‌های HTTP را به اکشن متدها نگاشت می‌کنند. در پنجره Solution Explorer روی نام پروژه کلیک راست کنید و گزینه Add, New Item را انتخاب کنید.

در دیالوگ باز شده گزینه Web را از پانل سمت چپ کلیک کنید و نوع آیتم جدید را Web API Controller Class انتخاب نمایید. نام این کنترلر را به "ProductsController" تغییر دهید و OK کنید.

کنترلر ایجاد شده شامل یک سری متد است که بصورت خودکار برای شما اضافه شده اند، آنها را حذف کنید و کد زیر را به کنترلر خود اضافه کنید.

namespace WebForms
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;

    public class ProductsController : ApiController
    {

        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public Product GetProductById(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return product;
        }

        public IEnumerable<Product> GetProductsByCategory(string category)
        {
            return products.Where(
                (p) => string.Equals(p.Category, category,
                    StringComparison.OrdinalIgnoreCase));
        }
    }
}
کنترلر جاری لیستی از محصولات را بصورت استاتیک در حافظه محلی نگهداری می‌کند. متدهایی هم برای دریافت لیست محصولات تعریف شده اند.


اطلاعات مسیریابی را اضافه کنید

مرحله بعدی اضافه کردن اطلاعات مسیریابی (routing) است. در مثال جاری می‌خواهیم آدرس هایی مانند "api/products/" به کنترلر Web API نگاشت شوند. فایل Global.asax را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.

using System.Web.Http;
حال کد زیر را به متد Application_Start اضافه کنید.
RouteTable.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = System.Web.Http.RouteParameter.Optional }
    );

برای اطلاعات بیشتر درباره مسیریابی در Web API به این لینک مراجعه کنید.


دریافت اطلاعات بصورت آژاکسی در کلاینت

تا اینجا شما یک API دارید که کلاینت‌ها می‌توانند به آن دسترسی داشته باشند. حال یک صفحهHTML خواهیم ساخت که با استفاده از jQuery سرویس را فراخوانی می‌کند. صفحه Default.aspx را باز کنید و کدی که بصورت خودکار در قسمت Content تولید شده است را حذف کرده و کد زیر را به این قسمت اضافه کنید:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" 
    AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebForms._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>Products</h2>
    <table>
    <thead>
        <tr><th>Name</th><th>Price</th></tr>
    </thead>
    <tbody id="products">
    </tbody>
    </table>
</asp:Content>
حال در قسمت HeaderContent کتابخانه jQuery را ارجاع دهید.
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
</asp:Content>

همانطور که می‌بینید در مثال جاری از فایل محلی استفاده شده است اما در اپلیکیشن‌های واقعی بهتر است از CDN‌‌ها استفاده کنید.

نکته: برای ارجاع دادن اسکریپت‌ها می‌توانید بسادگی فایل مورد نظر را با drag & drop به کد خود اضافه کنید.

زیر تگ jQuery اسکریپت زیر را اضافه کنید.

<script type="text/javascript">
    function getProducts() {
        $.getJSON("api/products",
            function (data) {
                $('#products').empty(); // Clear the table body.

                // Loop through the list of products.
                $.each(data, function (key, val) {
                    // Add a table row for the product.
                    var row = '<td>' + val.Name + '</td><td>' + val.Price + '</td>';
                    $('<tr/>', { text: row })  // Append the name.
                        .appendTo($('#products'));
                });
            });
        }

        $(document).ready(getProducts);
</script>

هنگامی که سند جاری (document) بارگذاری شد این اسکریپت یک درخواست آژاکسی به آدرس "api/products/" ارسال می‌کند. سرویس ما لیستی از محصولات را با فرمت JSON بر می‌گرداند، سپس این اسکریپت لیست دریافت شده را به جدول HTML اضافه می‌کند.

اگر اپلیکیشن را اجرا کنید باید با نمایی مانند تصویر زیر مواجه شوید:

مطالب
اجرای سرویسهای NodeJS در ASP.NET Core
 این نوشتار در مورد نحوه اجرای سرویس‌های NodeJS در ASP.NET Core می‌باشد؛ زیرا تعداد زیادی از Package‌های سورس باز و با کیفیت بالا به فرم Node package manager یا به اصطلاح NPM موجود و قابل دریافت می‌باشند. NPM بزرگترین مخزن دنیا از لحاظ وجود بسته‌های نرم افزاری سورس باز است. به همین جهت بسته Microsoft.AspNetCore.NodeServices، جهت استفاده از این بسته‌ها در برنامه‌های ASP.NET Core ارائه شده‌است. برای استفاده از سرویسهای Node ابتدا باید ارجاعی را به بسته Microsoft.AspNetCore.NodeServices داشته باشید. برای این منظور میتوانید از دستور dotnet add package Microsoft.AspNetCore.NodeServices استفاده نمایید. البته اگر از آخرین نسخه NET Core. استفاده میکنید، لزومی به ارجاع به بسته فوق نمی‌باشد و به صورت پیشفرض در بسته Microsoft.AspNetCore.All موجود است.
سپس متد ()ConfigurationServices را جهت اضافه کردن میان افزار Node Services به خط لوله درخواستها (request pipeline) ویرایش میکنیم:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddNodeServices();
}

حال میتوانید به وهله‌ای از اینترفیس INodeServices، از طریق تزریق وابستگی‌ها دسترسی داشته باشید. اینترفیس INodeServices یک Api می‌باشد و مشخص می‌کند که کدام قطعه از کد NET. میتواند کد جاوااسکریپتی را که در محیط Node اجرا می‌شود، فراخوانی کند. همچنین میتوانید از خاصیت FromServices برای دریافت وهله‌ای از اینترفیس INodeServices در اکشن خود استفاده نمایید.
public async Task<IActionResult> Add([FromServices] INodeServices nodeServices)
{
    var num1 = 10;
    var num2 = 20;
    var result = await nodeServices.InvokeAsync<int>("AddModule.js", num1, num2);
    ViewData["ResultFromNode"] = $"Result of {num1} + {num2} is {result}";
    return View();
}

سپس کد جاوااسکریپتی متناظر با تابعی را که توسط متد InvokeAsync فراخوانی میشود، به صورت زیر می‌نویسیم:
module.exports = function(callback, num1, num2) { 
  var result = num1 + num2;
  callback(null, result); 
};
دقت کنید که نام فایل جاوااسکریپت باید همنام با پارامتر اول متد InvokeAsync و تعداد پارامترهای تابع باید مساوی با پارامترهایی باشد که به تابع فراخوانی شده جاوااسکریپت ارسال می‌شود.

حال بیاییم مثالی دیگر را مرور کنیم. میخواهیم از صفحه وب درخواستی، عکسی را تهیه کنیم. بدین منظور از کتابخانه url-to-image استفاده میکنیم. برای نصب آن دستور npm install --save url-to-image را در خط فرمان تایپ میکنیم.

بعد از اتمام نصب این بسته، متدی را برای دریافت اطلاعات ارسالی این کتابخانه تدارک میبینیم.

 [HttpPost]
        public async Task<IActionResult> GenerateUrlPreview([FromServices] INodeServices nodeServices)
        {
            var url = Request.Form["Url"].ToString();
            var fileName = System.IO.Path.ChangeExtension(DateTime.UtcNow.Ticks.ToString(), "jpeg");
            var file = await nodeServices.InvokeAsync<string>("UrlPreviewModule.js", url, System.IO.Path.Combine("PreviewImages", fileName));
            return Content($"/Home/Download?img={fileName}");
        }

        public IActionResult Download()
        {
            var image = Request.Query["img"].ToString();
            var fileName = System.IO.Path.Combine("PreviewImages", image);
            var isExists = System.IO.File.Exists(fileName);

            if (isExists)
            {
                Response.Headers.Add($"Content-Disposition", "attachment; filename=\"" + image + "\"");
                var bytes = System.IO.File.ReadAllBytes(fileName);
                return File(bytes, "image/jpeg");
            }
            else
            {
                return NotFound();
            }
        }

سپس متد UrlPreviewModule.js را به صورت زیر مینویسیم:

var urlToImage = require('url-to-image');
module.exports = function (callback, url, imageName) {
    urlToImage(url, imageName).then(function () {
        callback(null, imageName);
    }).catch(function (err) {
        callback(err, imageName);
    });
};


سرویس‌های Node به توسعه دهندگان ASP.NET Core امکان استفاده از اکوسیستم NPM را که دارای قابلیتهای فراوانی می‌باشد، میدهد. 

مطالب
تبدیل شدن زبان C# 9.0 به یک زبان اسکریپتی با معرفی ویژگی Top Level Programs
اگر به قالب ابتدایی یک برنامه‌ی کنسول #C دقت کنیم، همواره به ساختار استاندارد زیر می‌رسیم:
using System;

namespace CS9Features
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
در اینجا یک سری import، به همراه تعریف فضای نام، تعریف کلاس و تعریف متد Main وجود دارند ... تا بتوان یک سطر Hello World را در کنسول نمایش داد. در این حالت اگر تازه شروع به یادگیری زبان #C کرده باشید، مفاهیم زیادی را باید در جهت درک آن فرا بگیرید؛ برای مثال static چیست؟ args چیست؟ کاربرد فضای نام چیست و غیره. کاری که در C# 9.0 انجام شده، امکان حذف تمام این عوامل در جهت نمایش تک سطر Hello World است که به آن top level programs و یا top level statements گفته می‌شود.


تبدیل قالب پیش‌فرض برنامه‌های کنسول به یک Top level program

در C# 9.0 می‌توان تمام سطرهای فوق را به دو سطر زیر تقلیل داد و خلاصه کرد:
using System;

Console.WriteLine("Hello World!");
این قطعه کد بدون هیچگونه مشکلی در C# 9.0 کامپایل می‌شود و به این ترتیب زبان #C را تبدیل و یا شبیه به یک «زبان اسکریپتی» ساده می‌کند.


روش استفاده از متدهای async در Top level programs

زمانیکه نقطه‌ی آغازین برنامه را تبدیل به یک top level program کردیم، دیگر دسترسی مستقیمی را به متد Main نداریم تا آن‌را async Task دار معرفی کنیم و پس از آن بتوانیم به سادگی با متدهای async کار کنیم. برای رفع این مشکل، کامپایلر فقط کافی است یک await را در قطعه کد شما پیدا کند. خودش به صورت خودکار متد Main غیرهمزمانی را جهت اجرای کدها، تشکیل می‌دهد. به همین جهت برای کار با کدهای async در اینجا، نیاز به تنظیم خاصی نیست و قطعه کد زیر که در آن متد MyMethodAsync را اجرا می‌کند، بدون مشکل کامپایل و اجرا خواهد شد:
using System;
using System.Threading.Tasks;

await MyMethodAsync();
Console.WriteLine("Hello World!");

static async Task MyMethodAsync()
{
   await Task.Yield();
}


روش دسترسی به args در Top level programs

همانطور که در قطعه کد ابتدایی این مطلب مشخص است، متد Main به همراه پارامتر string[] args نیز هست. اما اکنون در Top level programs که فاقد متد Main هستند، چگونه می‌توان به این آرگومان‌های ارسالی توسط کاربر دسترسی یافت؟
پاسخ: پارامتر args نیز هنوز در اینجا قابل دسترسی است؛ فقط به ظاهر مخفی است:
using System;

Console.WriteLine(args[0]);


ارائه‌ی return codes به فراخون در Top level programs

بعضی از برنامه‌های کنسول در انتهای متد Main خود برای مثال return 0 و یا return 1 را دارند؛ که اولی به معنای موفقیت عملیات و دومی به معنای شکست عملیات است. در top level programs نیز می‌توان این return‌ها را در انتهای کار قید کرد:
using System;
Console.WriteLine($"Hello world!");
return 1;
که یک چنین خروجی نهایی را توسط کامپایلر تولید می‌کند:
// <Program>$
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
   private static int <Main>$(string[] args)
   {
     Console.WriteLine("Hello world!");
     return 1;
   }
}


امکان تعریف کلاس‌ها و متدها در Top level programs

در تک فایل program.cs برنامه، در حین کار با Top level programs محدودیتی از لحاظ تعریف متدها، کلاس‌ها و غیره نیست؛ یک مثال:
using System;

var greeter = new Greeter();

var helloTeacher = greeter.Greet("teacher");
var helloStudents = SayHello("students");

Console.WriteLine(helloTeacher);
Console.WriteLine(helloStudents);

static string SayHello(string name)
{
    return "Hello, " + name;
}

public class Greeter
{
    public string Greet(string name)
    {
        return "Hello, " + name;
    }
}
همانطور که مشاهده می‌کنید، در حالت کار اسکریپتی با زبان #C، امکان استفاده‌ی از کلاس‌ها و یا متدها نیز وجود دارد؛ اما با یک شرط: این تعاریف باید پس از Top-level statements قرار گیرند. یعنی اگر متد و کلاس تعریف شده را به بالای فایل انتقال دهید، به خطای کامپایلر زیر خواهید رسید:
Top-level statements must precede namespace and type declarations. [CS9Features]csharp(CS8803)


سطوح دسترسی به کلاس‌ها و متدهای تعریف شده‌ی در Top level programs

اگر قطعه کد مثال قبل را کامپایل کنیم، نمونه‌ی دی‌کامپایل شده‌ی آن به صورت زیر است:
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
  private static void <Main>$(string[] args)
  {
   Greeter greeter = new Greeter();
   string helloTeacher = greeter.Greet("teacher");
   string helloStudents = SayHello("students");
   Console.WriteLine(helloTeacher);
   Console.WriteLine(helloStudents);

   static string SayHello(string name)
   {
    return "Hello, " + name;
   }
  }
}
همانطور که مشاهده می‌کنید، کامپایلر نه فقط نام متدها را تغییر داده‌است، بلکه سطوح دسترسی به آن‌ها را یا private و یا internal تعریف کرده‌است. به این معنا که کلاس‌ها و متدهای تعریف شده‌ی در Top level programs در سایر کتابخانه‌ها و یا برنامه‌ها، قابل استفاده و دسترسی نیستند. البته کلاس public class Greeter به همان صورت public باقی می‌ماند و سطح دسترسی آن تغییری نمی‌کند.


نوع متدهای تعریف شده‌ی در Top level programs

مثال زیر را که یک top level program است، درنظر بگیرید:
using System;

Foo();

var x = 3;

int result = AddToX(4);
Console.WriteLine(result);

static void Foo()
{
    Console.WriteLine("Foo");
}

int AddToX(int y)
{
    return x + y;
}
متد AddToX که static نیست، امکان دسترسی به متغیر x را یافته‌است. با توجه به اینکه متد Main هم static است، چطور چنین چیزی ممکن شده‌است؟
پاسخ: متدهایی که در top level programs تعریف می‌شوند در حقیقت از نوع local functions هستند که در ابتدا در C# 7.0 معرفی شدند و سپس در C# 8.0 امکان تعریف نمونه‌های static آن‌ها نیز میسر شد.
قطعه کد فوق در اصل به صورت زیر کامپایل می‌شود که متدهای AddToX و Foo در آن داخل متد Main تشکیل شده، به صورت local function تعریف شده‌اند:
// <Program>$
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
   private static void <Main>$(string[] args)
   {
     Foo();
     int x = 3;
     int result = AddToX(4);
     Console.WriteLine(result);

     int AddToX(int y)
     {
       return x + y;
     }

     static void Foo()
     {
       Console.WriteLine("Foo");
     }
   }
}
فقط یک local function از نوع static، دسترسی به متغیرهای تعریف شده‌ی در متد Main را ندارد.
اشتراک‌ها
NET 8 Preview 2. منتشر شد
Microsoft's second preview of .NET 8 is just "a quick follow-up" to Preview 1, highlighted by a couple of improvements to Blazor, for creating web apps in C# instead of JavaScript.
NET 8 Preview 2. منتشر شد
مطالب
پیاده سازی یک MediaTypeFormatter برای پشتیبانی از MultiPart/form-data در Web API

Media Type یا MIME Type نشان دهنده فرمت یک مجموعه داده است. در HTTP، مدیا تایپ بیان کننده فرمت message body یک درخواست / پاسخ است و به دریافت کننده اعلام می‌کند که چطور باید پیام را بخواند. محل استاندارد تعیین Mime Type در هدر Content-Type است. درخواست کننده می‌تواند با استفاده از هدر Accept لیستی از MimeType‌های قابل قبول را به عنوان پاسخ، به سرور اعلام کند.

Asp.net Web API  از MimeType برای تعیین نحوه serialize یا deserialize کردن محتوای دریافتی / ارسالی استفاده می‌کند


MediaTypeFormatter

Web API برای خواندن/درج پیام در بدنه درخواست/پاسخ از MediaTypeFormmater‌‌ها استفاده می‌کند. اینها کلاس‌هایی هستند که نحوه‌ی Serialize کردن و deserialize کردن اطلاعات به فرمت‌های خاص را تعیین می‌کنند. Web API به صورت توکار دارای formatter هایی برای نوع‌های XML ، JSON، BSON و Form-UrlEncoded می‌باشد. همه این‌ها کلاس پایه MediaTypeFormatter را پیاده سازی می‌کنند. 


مسئله

یک پروژه Web API بسازید و view model زیر را در آن تعریف کنید:

public class NewProduct
    {
        [Required]
        public string Name { get; set; }

        public double Price { get; set; }

        public byte[] Pic { get; set; }
    }

همانطور که می‌بینید یک فیلد از نوع byte[] برای تصویر محصول در نظر گرفته شده است.

حالا یک کنترلر API  ساخته و اکشنی برای دریافت اطلاعات محصول جدید از کاربر می‌نویسیم :
public class ProductsController : ApiController
    {
        [HttpPost]
        public HttpResponseMessage PostProduct(NewProduct model)
        {
            if (ModelState.IsValid)
            {
                //  ثبت محصول 

                return new HttpResponseMessage(HttpStatusCode.Created);
            }

            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }

    }

و یک صفحه html به نام index.html که حاوی یک فرم برای ارسال اطلاعات باشد :

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <h1>ساخت MediaTypeFormatter برای Multipart/form-data</h1>
    <h2>محصول جدید</h2>

    <form id="newProduct" method="post" action="/api/products" enctype="multipart/form-data">
        <div>
            <label for="name">نام محصول : </label>
            <input type="text" id="name" name="name" />
        </div>
        <div>
            <label for="price">قیمت : </label>
            <input type="number" id="price"  name="price" />
        </div>
        <div>
            <label for="pic">تصویر : </label>
            <input type="file" id="pic" name="pic" />
        </div>
        <div>
            <button type="submit">ثبت</button>
        </div>
    </form>
</body>
</html>

زمانی که فرم حاوی فایلی برای آپلود باشد مشخصه encType باید برابر با Multipart/form-data مقداردهی شود تا اطلاعات فایل به درستی کد شوند. در زمان ارسال فرم Content-type درخواست برابر با Multipart/form-data و فرمت اطلاعات درخواست ارسالی به شکل زیر خواهد بود :


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

- Content-Disposition که نام فیلد و نام فایل را شامل می‌شود .

- content-type که mime type مخصوص آن بخش از داده‌ها را مشخص می‌کند.

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

خطای روی داده اعلام می‌کند که Web API فاقد MediaTypeFormatter برای خواندن اطلاعات ارسال شده با فرمتMultiPart/Form-data  است.  Web API برای خواندن و بایند کردن پارامترهای complex Type از درون بدنه پیام یک درخواست از MediaTypeFormatter استفاده می‌کند و همانطور که گفته شد Web API فاقد Formatter توکار برای deserialize کردن داده‌های با فرمت Multipart/form-data است.

راه حل‌ها :

روشی که در سایت asp.net برای آپلود فایل در web api استفاده شده، عدم استفاده از پارامترها و خواندن محتوای Request در درون کنترلر است. که به طبع در صورتی که بخواهیم کنترلرهای تمیز و کوچکی داشته باشیم روش مناسبی نیست. از طرفی امتیاز parameter binding و modelstate را هم از دست خواهیم داد.

روش دیگری که می‌خواهیم در اینجا پیاده سازی کنیم ساختن یک MediaTypeFormatter برای خواندن فرمت Multipart/form-data است. با این روش کد موردنیاز کپسوله شده و امکان استفاده از binding و modelstate را خواهیم داشت.

برای ساختن یک MediaTypeFormatter یکی از 2 کلاس MediaTypeFormatter یا  BufferedMediaTypeFormatter  را باید پیاده سازی کنیم . تفاوت این دو در این است که BufferedMediaTypeFormatter برخلاف MediaTypeFormatter از متدهای synchronous استفاده می‌کند.

 
پیاده سازی :

یک کلاس به نام MultiPartMediaTypeFormatter می‌سازیم و کلاس MediaTypeFormatter را به عنوان کلاس پایه آن قرار می‌دهیم .

public class MultiPartMediaTypeFormatter : MediaTypeFormatter
{
    ...
}

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

        public MultiPartMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
        }
در اینجا Multipart/form-data را به عنوان تنها نوع مجاز تعریف کرده ایم.

سپس با پیاده سازی توابع CanReadType و CanWriteType مربوط به کلاس MediaTypeFormatter مشخص می‌کنیم که چه مدل‌هایی را می‌توان توسط این کلاس  serialize / deserialize کرد. در اینجا چون می‌خواهیم این کلاس محدود به یک مدل خاص نباشد، از یک اینترفیس برای شناسایی کلاس‌های مجاز استفاده می‌کنیم .

    public interface INeedMultiPartMediaTypeFormatter
    {
    }

و آنرا به کلاس newProduct اضافه می‌کنیم :

public class NewProduct : INeedMultiPartMediaTypeFormatter
{
 ...
}
از آنجا که تنها نیاز به خواندن اطلاعات داریم و قصد نوشتن نداریم، در متد CanWriteType مقدار false را برمی گردانیم .
        public override bool CanReadType(Type type)
        {
            return typeof(INeedMultiPartMediaTypeFormatter).IsAssignableFrom(type);
        }

        public override bool CanWriteType(Type type)
        {
            return false;
        }

و اما تابع ReadFromStreamAsync که کار خواندن محتوای ارسال شده و بایند کردن آنها به پارامترها را برعهده دارد 

public async override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
که در آن پارامتر type مربوط به مدل مشخص شده به عنوان پارامتر اکشن (NewProduct)است و پارامتر content محتوای درخواست را در خود دارد.

ابتدا محتوای ارسال شده را خوانده و اطلاعات فرم را استخراج می‌کنیم و از طرف دیگر با استفاده از کلاس Activator یک نمونه از مدل جاری را ساخته و لیست property‌های آنرا استخراج می‌کنیم.

            MultipartMemoryStreamProvider provider = await content.ReadAsMultipartAsync();
            IEnumerable<HttpContent> formData = provider.Contents.AsEnumerable();

            var modelInstance = Activator.CreateInstance(type);
            IEnumerable<PropertyInfo> properties = type.GetProperties();

سپس در یک حلقه به ترتیب برای هر property متعلق به مدل، در میان اطلاعات فرم جستجو می‌کنیم. برای پیدا کردن اطلاعات متناظر با هر property در هدر Content-Disposition که در بالا توضیح داده شد، به دنبال فیلد همنام با property می‌گردیم .

            foreach (PropertyInfo prop in properties)
            {
                var propName = prop.Name.ToLower();
                var propType = prop.PropertyType;

                var data = formData.FirstOrDefault(d => d.Headers.ContentDisposition.Name.ToLower().Contains(propName));
در صورتی که فیلدی وجود داشته باشد کار را ادامه می‌دهیم .

 گفتیم که هر فیلد یک هدر، Content-Type هم می‌تواند داشته باشد. این هدر به صورت پیش فرض معادل text/plain است و برای فیلدهای عادی قرار داده نمی‌شود . در این مثال چون فقط یک فیلد غیر رشته ای داریم فرض را بر این گرفته ایم که در صورت وجود Content-Type، فیلد مربوط به تصویر است. در صورتیکهContentType  وجود داشته باشد، محتوای فیلد را به شکل Stream خوانده به byte[] تبدیل و با استفاده از متد SetValue در property مربوطه قرار می‌دهیم.

                if (data != null)
                {
                    if (data.Headers.ContentType != null)
                    {
                        using (var fileStream = await data.ReadAsStreamAsync())
                        {
                            using (MemoryStream ms = new MemoryStream())
                            {
                                fileStream.CopyTo(ms);
                                prop.SetValue(modelInstance, ms.ToArray());
                            }                            
                        }
                    }

در صورتی که Content-Type غایب باشد بدین معنی است که محتوای فیلد از نوع رشته است ( عدد ، تاریخ ، guid ، رشته ) و باید به نوع مناسب تبدیل شود. ابتدا آن را به صورت یک رشته می‌خوانیم و با استفاده از Convert.ChangeType آنرا به نوع مناسب تبدیل می‌کنیم و در property متناظر قرار می‌دهیم .

                if (data != null)
                {
                    if (data.Headers.ContentType != null)
                    {
                        //...
                    }
                    else
                    {
                        string rawVal = await data.ReadAsStringAsync();
                        object val = Convert.ChangeType(rawVal, propType);

                        prop.SetValue(modelInstance, val);
                    }
                }
و در نهایت نمونه ساخته شده از مدل را برگشت می‌دهیم.
return modelInstance;
برای فعال کردن این Formatter باید آنرا به لیست formmater‌های web api اضافه بکنیم. فایل WebApiConfig در App_Start را باز کرده و خط زیر را به آن اضافه می‌کنیم:
config.Formatters.Add(new MultiPartMediaTypeFormatter());
حال اگر مجددا فرم را به سرور ارسال کنیم، با پیام خطایی، مواجه نشده و عمل binding با موفقیت انجام می‌گیرد.

اشتراک‌ها
بازه‌ها و الگوهای بازگشتی در C# 8
  • C# 8 Adds Ranges and Recursive Patterns
  • Ranges easily define a sequence of data, replacing the Enumberable.Range()
  • Recursive Patterns brings an F#-like construct to C#
  • Recursive Patterns is an awesome feature, it giving you the flexibility to testing the data against a sequence of conditions and performing further computations based on the condition met.
  • Ranges is very useful to generate sequences of numbers in the form of a collection or a list. 
بازه‌ها و الگوهای بازگشتی در C# 8
مطالب
اصول طراحی شیء گرا: OO Design Principles - قسمت پنجم

دانای اطلاعات ( Information Expert )

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

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

public class User
    {
        public ShoppingCart ShoppingCart { get; set; }
        public void AddItem(string name) {
            // User class must know how to create OrderItem
            var item = new OrderItem() { Name = name };

            // User class must know how to add item to shopping cart
            ShoppingCart.Items.Add(item);
            
        }
        public void CheckOut() {
            // User class must know logic behind cost and discount calculations:
            // check for discount
            // check shipping method
            // check promotions
            // calculate total cost of items 
        }
    }
    public class OrderItem
    {
        public int Id { get; set; }
        public string Name { get; set; }

    }
    public class ShoppingCart
    {
        public int Id { get; set; }
        public List<OrderItem> Items { get; set; }
       
    }

بنابراین به جای این طراحی، مسئولیت‌ها را به ShoppingCart منتقل میکنیم:

 public class User
    {
        public ShoppingCart ShoppingCart { get; set; }
        
    }
    public class OrderItem
    {
        public int Id { get; set; }
        public string Name { get; set; }

    }
    public class ShoppingCart
    {
        public int Id { get; set; }
        public List<OrderItem> Items { get; set; }
        public void AddItem(string name)
        {
            // ShoppingCart class know how to create OrderItem
            var item = new OrderItem() { Name = name };

            // ShoppingCart class already know how to add item 
            Items.Add(item);

        }
        public void CheckOut()
        {
            // ShoppingCart class know logic behind cost and discount calculations:
            // check for discount
            // check shipping method
            // check promotions
            // calculate total cost of items 
        }
    }


اتصال ضعیف ( Low Coupling )

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

-  وابستگی بین کلاس‌ها کم است.

-  تغییرات در یک کلاس، اثر کمی بر دیگر کلاس‌ها دارد.

-  پتانسیل استفاده‌ی مجدد از مؤلفه‌ها بالا است.

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


چند ریختی ( Polymorphism )

چند ریختی که از ویژگی‌های اساسی برنامه نویسی و زبان‌های شیء گراست، به منظور بالا بردن قابلیت استفاده‌ی مجدد، استفاده میشود. بر طبق این اصل، مسئولیت تعریف رفتارهای وابسته به نوع کلاس (زیرنوع‌ها در روابط ارث بری) باید به کلاسی واگذار شود که تغییر رفتار در آن اتفاق می­افتد. به عبارت دیگر باید به صورت خودکار رفتار را بر اساس نوع کلاس تصحیح کنیم. این روش در مقابل بررسی نوع داده‌ای برای انجام رفتار مناسب می­باشد.

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

public class ShapeWithoutPolymorphism
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }
        public double Area(string shapeType)
        {
            switch (shapeType)
            {
                case "square":
                    return X * Y;
                case "rectangle":
                    return X * Y;
                case "trapze":
                    return (X + Z) * Y / 2;
                default:
                    return 0;
            }
        }
    }

با استفاده از چندریختی، طراحی به این صورت در خواهد آمد:

  public abstract class Shape
    {
        public double X { get; set; }
        public double Y { get; set; }
        public virtual double Area()
        {
            return X * Y;
        }
    }
    public class Rectangle : Shape
    {
        // No need to override
    }
    public class Square : Shape
    {
        // No need to override
    }
    public class Trapze : Shape
    {
        public double Z { get; set; }
        public override double Area()
        {
            return (X + Z) * Y / 2;

        }
    }


مصنوع خالص ( Pure Fabrication )

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

برای تشخیص زمان استفاده از این اصل میتوان گفت زمانیکه رفتاری را نمیدانیم به کدام کلاس واگذار کنیم، کلاس جدیدی را ایجاد میکنیم. در اینجا بجای آنکه به زور مسئولیتی را به کلاس نامربوطی بچسبانیم، آنرا به کلاس جدیدی که فقط رفتاری را دارد، منتقل میکنیم. با اینکار انسجام کلاس‌ها را حفظ کرده‌ایم و هیچ اشکالی ندارد که کلاسی بدون داده بوده و فقط متد داشته باشد. اگر به یاد داشته باشید، در اصل واسطه گری (Indirection ) کلاس جدیدی برای ایجاد ارتباط ساختیم. در حقیقت مسئولیت برقراری ارتباط بین مؤلفه‌ها را به کلاس دیگری واگذار کردیم که چنانچه میبینید، بدون آنکه بدانیم، برای حل مشکل از اصل مصنوع خالص استفاده کردیم.

در مثال زیر این مساله مشهود است:

public class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }

    public class LibraryManagement
    {
        public User CurrentUser { get; set; }
        public void AddBookToLibrary(int bookId)
        {
            // check for CurrentUser authority:
            // not user's responsibility nor LibraryManagement
        }
        public void RearrangeBook(int bookId, int shelfId)
        {
            // check for CurrentUser authority
            // not user's responsibility nor LibraryManagement
        }
    }
    public class UserManagement
    {
        public User CurrentUser { get; set; }
        public void AddUser(string name)
        {
            // check for CurrentUser authority:
            // not user's responsibility nor UserManagement
        }
        public void ChangeUserRole(int userId, int roleId)
        {
            // check for CurrentUser authority
            // not user's responsibility nor UserManagement
        }
    }
    public class AuthorizationService
    {
        public bool IsAuthorized(int userId, int roleId)
        {
            // get user roles from data base
            // return true if user has the authority
        }
    }

عملیات بررسی مجوز‌ها باید در کلاس جدیدی به نام AuthorizationService ارائه شود. بدین صورت تمام قسمت‌ها، از این کد بدون وابستگی اضافی میتوانند استفاده کنند.


حفاظت از تاثیر تغییرات ( Protected Variations )

این اصل میگوید که کلاس‌ها باید از تغییرات یکدیگر مصون بمانند. در واقع این اصل غایت یک طراحی خوب است. تمام اصولی را که تا به حال بررسی کرده‌ایم، به منظور دستیابی به چنین رفتاری از طراحی بوده‌است. بدین منظور باید از اصول Open/Closed برای واسط­ها، چند ریختی در توارث و ... استفاده کرد تا از تاثیرات زنجیره‌ای تغییرات در امان بمانیم.

نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 11 - بررسی رابطه‌ی Self Referencing

یک نکته‌ی تکمیلی: تاثیر فراخوانی متد AsNoTracking بر روی کوئری‌های خود ارجاعی

همانطور که در مطلب «مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first» هم مشاهده کردید، خود EF، قابلیت تشکیل درخت نهایی خود ارجاع دهنده را دارد و به این ترتیب کوئری گرفتن از نتیجه‌ی آن، بسیار ساده می‌شود. اما ... اگر در این بین، از متد AsNoTracking برای بهینه سازی، کاهش میزان حافظه و حذف پروکسی‌های ردیابی تغییرات EF استفاده شود، دیگر این درخت خودکار، تشکیل نخواهد شد. برای پوشش این حالت می‌توان به صورت زیر عمل کرد:

الف) تشکیل یک کلاس پایه برای تعریف ساده‌تر و مشخص رابطه‌های خود ارجاعی

public abstract class BaseEntity
{
    public int Id { get; set; }
}

public abstract class BaseSelfReferencingEntity<TSelfEntity> : BaseEntity
    where TSelfEntity : BaseEntity
{
    public virtual TSelfEntity? Reply { set; get; }

    public int? ReplyId { get; set; }

    public virtual ICollection<TSelfEntity>? Children { get; set; }
}

که ساختار معرفی شده‌ی در اینجا، با توضیحات موجود در متن، انطباق دارد.

ب) پر کردن درخت نهایی حاصل به صورت دستی:

چون دیگر EF این درخت را برای ما تشکیل نمی‌دهد، اکنون باید خودمان کار تشکیل آن‌را به صورت زیر انجام دهیم:

public static class SelfReferencingExtensions
{ 
    public static List<TEntity> ToSelfReferencingTree<TEntity>(this ICollection<TEntity>? originalList)
        where TEntity : BaseSelfReferencingEntity<TEntity>
    {
        var results = new List<TEntity>();

        if (originalList is null || originalList.Count == 0)
        {
            return results;
        }

        foreach (var rootItem in originalList.Where(x => !x.ReplyId.HasValue))
        {
            results.Add(rootItem);
            AppendChildren(originalList, rootItem);
        }

        return results;
    }

    private static void AppendChildren<TEntity>(ICollection<TEntity> originalList, TEntity parentItem)
        where TEntity : BaseSelfReferencingEntity<TEntity>
    {
        foreach (var kid in originalList.Where(x => x.ReplyId.HasValue && x.ReplyId.Value == parentItem.Id))
        {
            parentItem.Children ??= new List<TEntity>();
            parentItem.Children.Add(kid);
            AppendChildren(originalList, kid);
        }
    }
}

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

پس از این مقدمات، نحوه‌ی استفاده از آن به صورت زیر است:

var comments = await _comments.AsNoTracking()
            .Where(x => x.ParentId == postId)
            .OrderBy(x => x.Id)
            .Take(count)
            .ToListAsync();

var commentsTree = comments.ToSelfReferencingTree();

کوئری نویسی ابتدایی آن، کاملا استاندارد و بدون هیچگونه نکته‌ی خاصی است. ابتدا تمام نظرات یک مطلب (به صورت AsNoTracking) بازگشت داده می‌شوند و سپس متد ToSelfReferencingTree کار اتصالات نهایی درخت پاسخ‌ها را به صورت خودکار انجام می‌دهد.