نظرات مطالب
طراحی گزارش در Stimulsoft Reports.Net – بخش 2
مشکلی که روش درخواستی شما داره شبیه به حالت lazy loading اشتباه استفاده شده در ORMها است (اصطلاحا بهش می‌گن n+1 select). اینطوری هزاران هزار کوئری به بانک اطلاعاتی بجای فقط یک کوئری و یک رفت و برگشت خواهید داشت.
مطالب
استفاده از افزونه‌ی jQuery Autocomplete در ASP.NET

با استفاده از AutoComplete TextBoxes می‌توان گوشه‌ای از زندگی روزمره‌ی کاربران یک برنامه را ساده‌تر کرد. مشکل مهم dropDownList ها دریک برنامه‌ی وب، عدم امکان تایپ قسمتی از متن مورد نظر و سپس نمایان شدن آیتم‌های متناظر با آن در اسرع وقت می‌باشد. همچنین با تعداد بالای آیتم‌ها هم حجم صفحه و زمان بارگذاری را افزایش می‌دهند. راه حل‌های بسیار زیادی برای حل این مشکل وجود دارند و یکی از آن‌ها ایجاد AutoComplete TextBoxes است. پلاگین‌های متعددی هم جهت پیاده سازی این قابلیت نوشته‌ شده‌اند منجمله jQuery Autocomplete . این پلاگین دیگر توسط نویسنده‌ی اصلی آن نگهداری نمی‌شود اما توسط برنامه نویسی دیگر در github ادامه یافته است. در ادامه نحوه‌ی استفاده از این افزونه‌ را در ASP.NET Webforms بررسی خواهیم کرد.

الف) دریافت افزونه

لطفا به آدرس GitHub ذکر شده مراجعه نمائید.

سپس برای مثال پوشه‌ی js را به پروژه افزوده و فایل‌های jquery-1.5.min.js ، jquery.autocomplete.js ، jquery.autocomplete.css و indicator.gif را در آن کپی کنید. فایل indicator.gif به همراه مجموعه‌ی دریافتی ارائه نمی‌شود و یک آیکن loading معروف می‌تواند باشد.
علاوه بر آن یک فایل جدید custom.js را نیز جهت تعاریف سفارشی خودمان اضافه خواهیم کرد.


ب) افزودن تعاریف افزونه به صفحه

در ذیل نحوه‌ی افزودن فایل‌های فوق به یک master page نمایش داده شده است.
در اینجا از قابلیت‌های جدید ScriptManager (موجود در سرویس پک یک دات نت سه و نیم و یا دات نت چهار) جهت یکی کردن اسکریپت‌ها کمک گرفته شده است. به این صورت تعداد رفت و برگشت‌ها به سرور به‌جای سه مورد (تعداد فایل‌های اسکریپت مورد استفاده)، یک مورد (نهایی یکی شده) خواهد بود و همچنین حاصل نهایی به صورت خودکار به شکلی فشرده شده به مرورگر تحویل داده شده، سرآیندهای کش شدن اطلاعات به آن اضافه می‌گردد (که در سایر حالات متداول اینگونه نیست)؛ به علاوه Url نهایی آن هم بر اساس hash فایل‌ها تولید می‌شود. یعنی اگر محتوای یکی از این فایل‌ها تغییر کرد، چون Url نهایی تغییر می‌کند، دیگر لازم نیست نگران کش شدن و به روز نشدن اسکریپت‌ها در سمت کاربر باشیم.

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="AspNetjQueryAutocompleteTest.Site" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:PlaceHolder Runat="server">
<link href="<%= ResolveClientUrl("~/js/jquery.autocomplete.css")%>" rel="stylesheet" type="text/css" />
</asp:PlaceHolder>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="~/js/jquery-1.5.min.js" />
<asp:ScriptReference Path="~/js/jquery.autocomplete.js" />
<asp:ScriptReference Path="~/js/custom.js" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
علت استفاده از ResolveClientUrl در حین تعریف فایل css در اینجا به عدم مجاز بودن استفاده از ~ جهت مسیر دهی فایل‌های css در header صفحه بر می‌گردد.


ج) افزودن یک صفحه‌ی ساده به برنامه
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="default.aspx.cs" Inherits="AspNetjQueryAutocompleteTest._default" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:TextBox ID="txtShenas" runat="server" />
</asp:Content>

فرض کنید می‌خواهیم افزونه‌ی ذکر شده را به TextBox استاندارد فوق اعمال کنیم. ID این TextBox در نهایت به شکل ContentPlaceHolder1_txtShenas رندر خواهد شد. البته در ASP.NET 4.0 با تنظیم ClientIDMode=Static می‌توان ID انتخابی خود را به جای این ID خودکار درنظر گرفت و اعمال کرد. اهمیت این مساله در قسمت (ه) مشخص می‌گردد.


د) فراهم آوردن اطلاعات مورد استفاده توسط افزونه‌ی AutoComplete به صورت پویا

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

using System;
using System.Data.SqlClient;
using System.Text;
using System.Web;

namespace AspNetjQueryAutocompleteTest
{
public class AutoComplete : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string prefixText = context.Request.QueryString["q"];
var sb = new StringBuilder();

using (var conn = new SqlConnection())
{
//todo: این مورد باید از فایل کانفیگ خوانده شود
conn.ConnectionString = "Data Source=(local);Initial Catalog=MyDB;Integrated Security = true";
using (var cmd = new SqlCommand())
{
cmd.CommandText = @" select Field1 ,Field2 from tblData where Field1 like @SearchText + '%' ";
cmd.Parameters.AddWithValue("@SearchText", prefixText);
cmd.Connection = conn;
conn.Open();
using (var sdr = cmd.ExecuteReader())
{
if (sdr != null)
while (sdr.Read())
{
string field1 = sdr.GetValue(0) == DBNull.Value ? string.Empty : sdr.GetValue(0).ToString().Trim();
string field2 = sdr.GetValue(1) == DBNull.Value ? string.Empty : sdr.GetValue(1).ToString().Trim();
sb.AppendLine(field1 + "|" + field2);
}
}
}
}

context.Response.Write(sb.ToString());
}

public bool IsReusable
{
get
{
return false;
}
}
}
}

در این مثال از ADO.NET کلاسیک استفاده شده است تا به عمد نحوه‌ی تعریف پارامترها یکبار دیگر مرور گردند. اگر از LINQ to SQL یا Entity framework یا NHibernate و موارد مشابه استفاده می‌کنید، جای نگرانی نیست؛ زیرا کوئری‌های SQL تولیدی توسط این ORMs به صورت پیش فرض از نوع پارامتری هستند (+).
در این مثال اطلاعات دو فیلد یک و دوی فرضی از جدولی با توجه به استفاده از like تعریف شده دریافت می‌گردد. به عبارتی همان متد StartsWith معروف LINQ بکارگرفته شده است.
به صورت خلاصه افزونه، کوئری استرینگ q را به این فایل ashx ارسال می‌کند. سپس کلیه آیتم‌های شروع شده با مقدار دریافتی، از بانک اطلاعاتی دریافت شده و هر کدام قرارگرفته در یک سطر جدید بازگشت داده می‌شوند.
اگر دقت کرده باشید در قسمت sb.AppendLine ، با استفاده از "|" دو مقدار دریافتی از هم جدا شده‌اند. عموما یک مقدار کفایت می‌کند (در 98 درصد موارد) ولی اگر نیاز بود تا توضیحاتی نیز نمایش داده شود از این روش نیز می‌توان استفاده کرد. برای مثال یک مقدار خاص به همراه توضیحات آن به عنوان یک آیتم نمایش داده شده مد نظر است.


ه) اعمال نهایی افزونه به TextBox

در ادامه پیاده سازی فایل custom.js برای استفاده از امکانات فراهم شده در قسمت‌های قبل ارائه گردیده است:

function formatItem(row) {
return row[0] + "<br/><span style='text-align:justify;' dir='rtl'>" + row[1] + "</span>";
}

$(document).ready(function () {
$("#ContentPlaceHolder1_txtShenas").autocomplete('AutoComplete.ashx', {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
delay: 5,
//Only suggested values are valid
mustMatch: true,
//The number of items in the select box
max: 20,
//Fill the input while still selecting a value
autoFill: false,
//The comparison doesn't looks inside
matchContains: false,
formatItem: formatItem
});
});

پس از این مقدمات، اعمال افزونه‌ی autocomplete به textBox ایی با id مساوی ContentPlaceHolder1_txtShenas ساده است. اطلاعات از فایل AutoComplete.ashx دریافت می‌گردد و تعدادی از خواص پیش فرض این افزونه در اینجا مقدار دهی شده‌اند. لیست کامل آن‌ها را در فایل jquery.autocomplete.js می‌توان مشاهده کرد.
تنها نکته‌ی مهم آن استفاده از پارامتر اختیاری formatItem است. اگر در حین تهیه‌ی AutoComplete.ashx خود تنها یک آیتم را در هر سطر نمایش می‌دهید و از "|" استفاده نکرده‌اید، نیازی به ذکر آن نیست. در این مثال ویژه، فیلد یک در یک سطر و فیلد دو در سطر دوم یک آیتم نمایش داده می‌شوند:



نظرات مطالب
سری بررسی SQL Smell در EF Core - استفاده از مدل Entity Attribute Value - بخش دوم
مطالب
خلاصه‌ای از روش‌های ارسال داده‌های سمت سرور به کدهای جاوا اسکریپتی در ASP.NET MVC
روش‌های زیادی برای ارسال داده‌های سمت سرور تهیه شده در یک برنامه‌ی ASP.NET به کدهای سمت کاربر JavaScript ایی وجود دارند که تعدادی از مهم‌ترین‌های آن‌ها را در این مطلب بررسی خواهیم کرد.

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

استفاده از Ajax یکی از روش‌های کلاسیک دریافت اطلاعات سمت سرور در کدهای جاوا اسکریپتی است.
<script type="text/javascript">
var products = [];
$(function() {
    $.getJSON("/home/products", function(response) {
        products = response.products;
    });
});
</script>
برای نمونه در اینجا با استفاده از امکانات jQuery، درخواست Ajax ایی به سرور ارسال شده و سپس نتیجه‌ی دریافتی، به آرایه‌ی جاوا اسکریپتی products نسبت داده شده‌است.
- مزایا: استفاده از Ajax، روشی بسیار متداول و شناخته شده‌است و به کمک انواع و اقسام روش‌های بازگشت JSON از سرور، می‌توان با آن کار کرد.
- معایب: درخواست Ajax، صرفا پس از بارگذاری اولیه‌ی صفحه به سمت سرور ارسال خواهد شد و در این بین، کاربر وقفه‌ای را مشاهده خواهد کرد. همچنین در اینجا بجای یک درخواست از سرور، حداقل دو درخواست باید ارسال شوند؛ یکی برای بارگذاری صفحه‌ی اصلی و دیگری برای دریافت اطلاعات Ajax ایی از سرور به صورت غیرهمزمان.


روش دوم: دریافت اطلاعات از یک فایل جاوا اسکریپتی خارجی

اطلاعات سمت کاربر را از یک فایل جاوا اسکریپتی خارجی الحاق شده‌ی به صفحه‌ی جاری نیز می‌توان تهیه کرد:
 <script src="/file.js"></script>
یک چنین فایلی را می‌توان توسط کدهای سمت سرور نیز بازگشت داد و اطلاعات آن‌را تهیه و پر کرد. در این حالت فقط کافی است که ContentType شیء Response را از نوع application/javascript مشخص کنیم و سپس یک خروجی متنی جاوا اسکریپتی را از سمت سرور بازگشت دهیم.
این روش نیز تقریبا مانند حالت یک درخواست Ajax ایی کار می‌کند و اطلاعات مورد نیاز را در طی یک درخواست جداگانه، پس از بارگذاری صفحه‌ی اصلی، از سرور دریافت خواهد کرد. البته در حالت کار با Ajax، می‌توان در طی یک callback، نتیجه را دریافت کرد و سپس عکس العمل نشان داد؛ اما در اینجا callback ایی وجود ندارد.


روش سوم: استفاده از SignalR

در SignalR ابتدا سعی می‌شود تا با استفاده از Web Sockets ارتباطی ماندگار بین کلاینت و سرور برقرار شود و سپس در این حالت، سرور می‌تواند مدام اطلاعاتی، مانند تغییرات داده‌های خود را به سمت کاربر، جهت نمایش و یا محاسبات خاص خود ارسال کند. اگر حالت Web Socket میسر نباشد (توسط سرور یا کلاینت پشتیبانی نشود)، به حالت‌های دیگری مانند server events, forever frames, long polling سوئیچ خواهد کرد. اطلاعات بیشتر


روش چهارم: قرار دادن اطلاعات سمت سرور در کدهای HTML صفحه

روش متداول دیگری جهت تامین اطلاعات جاوا اسکریپتی سمت کاربر، قرار دادن آن‌ها در ویژگی‌های data-* ارائه شده در HTML5 است.
<ul>
@foreach (var product in products)
{
  <li id="product@product.Id" data-rank="@product.Rank">@product.Name</li>
}
</ul>
در اینجا لیستی از محصولات، به صورت متداولی از کنترلر دریافت گردیده و سپس ویژگی data-rank هر ردیف نمایش داده شده نیز توسط این اطلاعات سمت سرور مقدار دهی شده‌اند.
اکنون برای دسترسی به مقدار data-rank سطری مانند product1، در کدهای جاوا اسکریپتی صفحه می‌توان نوشت:
<script type="text/javascript">
var product1Rank = $("#product1").data("rank");
</script>
این روش برای قرار دادن اطلاعات ثابت اشیاء، روش مرسومی است.


روش پنجم: قرار دادن اطلاعات سمت سرور در کدهای جاوا اسکریپتی صفحه

این روش همانند روش چهارم است، با این تفاوت که اینبار اطلاعات مورد نیاز، مستقیما به یک متغیر جاوا اسکریپتی انتساب داده شده‌است:
 <script type="text/javascript">
var product1Name = "@product1.Name";
</script>
مزیت این روش، عدم ارسال درخواست اضافه‌تری به سرور برای دریافت اطلاعات مورد نیاز است. مقدار product1Name در همان بار اول رندر صفحه از سمت سرور، مشخص می‌گردد.


روش ششم: انتساب یک شیء دات نتی به یک متغیر جاوا اسکریپتی

این روش همانند روش پنجم است، با این تفاوت که اینبار قصد داریم بجای یک مقدار ثابت رشته‌ای یا عددی، برای مثال، آرایه‌ای از اشیاء را به یک متغیر جاوا اسکریپتی انتساب دهیم. در اینجا ابتدا اطلاعات مورد نظر را به فرمت JSON تبدیل می‌کنیم:
//سمت سرور
[HttpGet]
public ActionResult Index()
{
    var array = new[]
    {
        "Afghanistan",
        "Albania",
        "Algeria",
        "American Samoa",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarctica",
        "Antigua and/or Barbuda"
     };
     ViewBag.JsonString = new JavaScriptSerializer().Serialize(array);
     return View();
}
سپس توسط متد Html.Raw می‌توان این رشته‌ی JSON را که اکنون حاوی آرایه جاوا اسکریپتی سمت سرور است، به یک متغیر جاوا اسکریپتی نسبت داد:
 //سمت کلاینت
<script type="text/javascript">
var jsonArray = @Html.Raw(@ViewBag.JsonString);
</script>
استفاده از Html.Raw در این حالت از این جهت ضروری است که اطلاعاتی مانند [] به صورت encode شده در سمت کاربر نمایش داده نشوند؛ چون Razor به صورت پیش فرض اطلاعات را Encode می‌کند.
و یا اینکار را به صورت خلاصه به شکل زیر نیز می‌توان در سمت کاربر انجام داد:
 <script type="text/javascript">
  var model = @Html.Raw(Json.Encode(Model));
  // your js code here
</script>
در اینجا کار تبدیل اطلاعات مدل به JSON، در همان سمت RazorView انجام شده‌است.
مطالب
تست کردن متدهای یک Controller به کمک PowerShell

ابزارهای زیادی جهت تست کردن API ‌های برنامه‌های وب موجود است. یکی از معروفترین آنها  Fiddler است که ابزاری مستقل جهت دیباگ تحت پروتکل HTTP می‌باشد. یکی دیگر از این نرم افزارها Swashbuckle است که از Nuget قابل دریافت است و صفحه‌ای را به برنامه اضافه می‌کند که به اختصار API ‌های برنامه را نمایش داده و امکان اجرای آنها را فراهم میکند.

اما ساده‌ترین راه جهت کردن تست کردن API ‌های یک برنامه، استفاده از PowerShell است که عمل ایجاد درخواست‌های HTTP را به راحتی از طریق Command line فراهم میکند و به شما اجازه میدهد بدون وارد شدن به جزئیات، بر روی نتیجه API مورد نظر تمرکز کنید. PowerShell ابتدا فقط برای محیط ویندوز طراحی شده بود ولی در حال حاضر قابلیت اجرای تحت Linux و Mac را نیز دارد.

در این بخش به شما نشان خواهم داد که چگونه متدهای یک Controller را با استفاده از PowerShell تست نمایید.

بدین منظور ابتدا کلاسی را به نام Reservation با مشخصات زیر، در یک پروژه ASP.NET Core Web Application  ایجاد نمایید.
public class Reservation
{
        public int ReservationId { get; set; }
        public string ClientName { get; set; }
        public string Location { get; set; }
}
و سپس Interface زیر را جهت انجام عملیات CRUD اضافه نمائید:
public interface IRepository
{
        IEnumerable<Reservation> Reservations { get; }
        Reservation this[int id] { get; }
        Reservation AddReservation(Reservation reservation);
        Reservation UpdateReservation(Reservation reservation);
        void DeleteReservation(int id);
}
و کلاسی که Interface فوق را پیاده سازی خواهد کرد:
public class MemoryRepository : IRepository
 {
        private Dictionary<int, Reservation> items;
        public MemoryRepository()
        {
            items = new Dictionary<int, Reservation>();
            new List<Reservation> {
                new Reservation { ClientName = "Alice", Location = "Board Room" },
                new Reservation { ClientName = "Bob", Location = "Lecture Hall" },
                new Reservation { ClientName = "Joe", Location = "Meeting Room 1" }
            }.ForEach(r => AddReservation(r));
        }

        public Reservation this[int id] => items.ContainsKey(id) ? items[id] : null;
        public IEnumerable<Reservation> Reservations => items.Values;

        public Reservation AddReservation(Reservation reservation)
        {
            if (reservation.ReservationId == 0)
            {
                int key = items.Count;
                while (items.ContainsKey(key)) { key++; };
                reservation.ReservationId = key;
            }
            items[reservation.ReservationId] = reservation;
            return reservation;
        }

        public void DeleteReservation(int id) => items.Remove(id);
        public Reservation UpdateReservation(Reservation reservation) => AddReservation(reservation);
}
در کلاس فوق به جهت سهولت در کار، از یک لیست، برای نگهداری تعدادی رکورد اطلاعاتی جهت نمایش استفاده شده است.

سپس کنترلری را به نام ReservationController به پروژه اضافه کنید که شامل متدهای زیر باشد:
[Route("api/[controller]")]
public class ReservationController : Controller
    {
        private IRepository repository;
        public ReservationController(IRepository repo) => repository = repo;

        [HttpGet]
        public IEnumerable<Reservation> Get() => repository.Reservations;

        [HttpGet("{id}")]
        public Reservation Get(int id) => repository[id];

        [HttpPost]
        public Reservation Post([FromBody] Reservation res) =>
                    repository.AddReservation(new Reservation
                    {
                        ClientName = res.ClientName,
                        Location = res.Location
                    });
        [HttpPut]
        public Reservation Put([FromBody] Reservation res) => repository.UpdateReservation(res);

        [HttpPatch("{id}")]
        public StatusCodeResult Patch(int id, [FromBody] JsonPatchDocument<Reservation> patch)
        {
            Reservation res = Get(id);
            if (res != null)
            {
                patch.ApplyTo(res);
                return Ok();
            }
            return NotFound();
        }

        [HttpDelete("{id}")]
        public void Delete(int id) => repository.DeleteReservation(id);
}
حالا جهت تست صحت این API‌ها توسط PowerShell لازم است ابتدا برنامه را اجرا کرده و منتظر بمانید تا صفحه Home ظاهر شود. پس از آن لازم است یک پنجره PowerShell را از طریق کلیک راست در یکی از فولدرهای نمایش داده شده در FileExplorer باز کنید.

تست کردن درخواست از نوع GET

حالا دستور زیر را در پنجره PowerShell وارد کنید:
 Invoke-RestMethod http://localhost:7000/api/reservation -Method GET
لازم است شماره پورت را مطابق با پورتی که برنامه شما بر روی آن اجرا شده تغییر دهید. این فرمان به کمک دستور Invoke-RestMethod درخواستی از نوع GET را به api/reservation ارسال می‌کند و نتیجه را پس از تجزیه و تحلیل و فرمت بندی، جهت سهولت در خوانده شدن، به شکل زیر نمایش میدهد:

location  clientName  reservationId 
Board Room 
Alice 
0                  
Lecture Hall 
Bob
1
Meeting Room 1 
Joe
2
فرمت اطلاعات دریافت شده از درخواست GET، اشیایی از نوع کلاس Reservation است و به فرمت Json می‌باشد؛ ولی Invoke-RestMethod آنرا به صورت جدول نمایش میدهد.
حال جهت نمایش فقط یک رکورد از اطلاعات فوق، دستور زیر را وارد نمایید:
Invoke-RestMethod http://localhost:7000/api/reservation/1 -Method GET
این فرمان درخواستی جهت دریافت اطلاعات شیء Reservation است که کد داخلی آن برابر 1 است. پاسخ این درخواست به شکل زیر نمایش داده خواهد شد:

location  clientName   reservationId   
Lecture Hall  
        Bob
1                  


تست درخواست از نوع Post

تمامی متدهای ارائه شده توسط یک API را می‌توان به کمک PowerShell تست کرد. اگرچه در این حالت فرمت بعضی از درخواستهای ارسالی ممکن است کمی به هم ریخته به نظر برسد. در اینجا درخواستی جهت اضافه کردن یک رکورد را به لیست Reservation ارسال می‌کنیم و نتیجه را در پاسخ ارسال شده از سمت سرور خواهیم دید:

Invoke-RestMethod http://localhost:7000/api/reservation -Method POST -Body
(@{clientName="Anne"; location="Meeting Room 4"} | ConvertTo-Json) -ContentType "application/json"
این دستور با استفاده از آرگومان Body- محتویات درخواست را که با فرمت Json است تعیین می‌کند. آرگومان Content-Type- هم نوع محتویات درخواستی را در هدر مشخص میکند. دستور فوق در صورت موفقیت نتیجه زیر را نمایش خواهد داد:

location  clientName   reservationId   
 Meeting Room 4
Anne      
1                
           
دستور Post ارسالی، با استفاده از دو فیلد clientName و location، یک رکورد جدید از نوع Reservation را به لیست موجود اضافه کرده و نتیجه را در قالب Json به سمت کلاینت برگشت خواهد داد که شامل ReservationId می‌باشد که به رکورد جدید اختصاص داده شده‌است. ممکن است چنین به نظر برسد که نتیجه برگشت داده شده، همان درخواست ارسالی است که به شکل جدول فوق نمایش داده می‌شود؛ ولی چنین نیست. جهت مشاهده تاثیر درخواست POST ارسالی بر روی منبع داده، کافی است یک بار دیگر دستور زیر را در command line وارد کنید:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId   
 Board Room           Alice
0              
 Lecture Hall  Bob 1
 Meeting Room 1  Joe 2
 Meeting Room 4  Anne 3
داده‌هایی که به سمت کلاینت ارسال شده، نشان دهنده اضافه شدن رکورد جدیدی به منبع داده ماست.

تست درخواست از نوع PUT

درخواست از نوع PUT جهت جایگزینی داده‌ای موجود در لیست، با داده‌ی جدید است. بدین منظور کد داخلی شیء مورد نظر (در اینجا ReservationId) باید در URL گنجانده شود و مقادیر فیلدهای کلاس، در متن درخواست. مثالی جهت درخواست اصلاح داده موجود از طریق فرمان PowerShell :

Invoke-RestMethod http://localhost:7000/api/reservation -Method PUT -Body
(@{reservationId="1"; clientName="Bob"; location="Media Room"} | ConvertTo-Json)
-ContentType "application/json"
درخواست فوق، فیلد Location رکوردی با کد داخلی 1 را به مقدار جدیدی تغییر خواهد داد و نتیجه تغییر انجام شده را در پاسخ ارسال خواهد کرد:

location  clientName   reservationId   
Media Room
Bob        
1                

و باز با ارسال درخواست زیر می‌توان نتیجه کلی را مشاهده کرد:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId   
Board Room
Alice  
0  
 Media Room
 Bob  1
 Meeting Room 1
Joe 2
 Meeting Room 4
Anne
3


استفاده از دستور PATCH

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

ASP.NET Core MVC از استاندارد Json PATCH پشتیبانی می‌کند و این کار اجازه میدهد تا بتوان تغییرات را بطور یکنواختی انجام داد. جهت مشاهده جزئیات این دستور می‌توانید به این لینک مراجعه کنید. اما برای مثال فرض کنید می‌خواهید چنین درخواستی را که به فرمت Json است از طریق HTTP PATCH به سمت سرور ارسال کنید:

[
  { "op": "replace", "path": "clientName", "value": "Bob"},
  { "op": "replace", "path": "location", "value": "Lecture Hall"}
]

دربسیاری از مواقع فقط از دستور replace درخواست PATCH استفاده می‌شود و کد داخلی رکورد مورد نظر، جهت تغییر در Url درخواست ارسالی، فرستاده خواهد شد. ASP.NET Core MVC به طور اتوماتیک این دستور را پردازش کرده و آنرا به صورت آبجکتی از نوع <JsonPatchDocument<T به اکشن کنترلر قید شده، پاس خواهد داد که در اینجا نوع T، از نوع کلاس مورد نظر ما می‌باشد. فرمت دستور ارسالی از طریق Powershell به شکل زیر خواهد بود:

Invoke-RestMethod http://localhost:7000/api/reservation/2 -Method PATCH -Body (@
{ op="replace"; path="clientName"; value="Bob"},@{ op="replace"; path="location";
value="Lecture Hall"} | ConvertTo-Json) -ContentType "application/json"
دستور فوق درخواست تغییر دو فیلد clientname و location، با کد داخلی 2 می‌باشد.
جهت مشاهده تغییر ایجاد شده، دستور زیر را مجددا اجرا نمایید:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId  
Board Room
Alice  
0            
Media Room
Bob
 1
Lecture Hall
 Bob 2
Meeting Room 4
 Anne 3


استفاده از دستور Delete

استفاده از دستور Delete هم به شکل زیر خواهد بود:

Invoke-RestMethod http://localhost:7000/api/reservation/2 -Method DELETE
توضیح اینکه در این حالت پاسخی از سمت سرور ارسال نخواهد شد. اما جهت مشاهده تاثیر این دستور بر روی اطلاعات موجود می‌توان مجددا دستور زیر را جهت نمایش داده‌ها بکار برد:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

کدهای این مقاله به پیوست موجود است: ApiControllers.zip
مطالب
معرفی Actor Based Programming و توسعه نرم افزار های مقیاس پذیر و دارای عملیات همزمان بسیار زیاد - قسمت دوم
در  قسمت قبل توضیحاتی راجع به مقدمات Actor Based Programming و کاربرد آن داده شد و چند framework نیز برای توسعه به این روش معرفی گردید. در این قسمت جزئیات بیشتری را از این روش توسعه، ارائه خواهیم داد.
خط تولید کارخانه‌ای را فرض کنید که در آن یک قطعه از ابتدای خط حرکت نموده و کارگران مستقر در خط تولید نیز هر کدام بنا به وظیفه‌ی خود، کاری را بر روی قطعه‌ی مورد نظر انجام می‌دهند؛ به طوریکه در انتهای خط تولید، آن قطعه‌ی اولیه، به یک محصول کامل تبدیل می‌شود.

 ایده‌ی Actor Based نیز هم از همین روش الهام گرفته است. با این تفاوت که بجای کارگران، Thread داریم و بجای قطعه نیز یک پیام یا object و بجای خط تولید نیز خط لوله یا pipeline را داریم. همانطور که در قسمت قبل اشاره کردم، وظیفه‌ی توسعه دهنده در این روش، طراحی یک خط لوله و نوشتن کد مربوط به هر thread است. به همین سادگی!
یعنی تمام پیچیدگی‌های مربوط به concurrency و مسائل فنی توسط یک framework مثل TPL DataFlow یا Akka کنترل و مدیریت می‌شود و توسعه دهنده با تمرکز بر روی مسئله‌ی خود، شروع به طراحی (کانفیگ) خط لوله و نوشتن کد مربوط به هر کدام از thread‌ها می‌نماید.

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

خوب حالا که با مفاهیم خط لوله و اکتور آشنا شدیم، یک مسئله‌ی بسیار ساده را در نظر می‌گیریم و آن را با این روش حل می‌کنیم. فرض کنید یک رشته (string) داریم و می‌خواهیم عملیات زیر را بر روی آن به ترتیب انجام دهیم:

1- فاصله‌های اضافی ابتدا و انتهای رشته حذف شود.

2- اگر رشته یک کلمه‌ای است lowerCase شود.

3- اگر رشته بیش از یک کلمه است، تمام کلمات، به جز کلمه‌ی اول، حذف شوند و سپس مرحله‌ی 2 بر روی آن انجام شود.

4- نتیجه‌ی کار در خروجی نمایش داده شود.

حالا می‌خواهیم انجام هر یک از عملیات فوق را به یک اکتور سپرده و یک خط لوله را برای حل این مسئله طراحی کنیم. در قسمت بعدی به صورت عملی و با TPL DataFlow مایکروسافت این کار را انجام می‌دهیم.

مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت پنجم
در قسمت قبل پیاده سازی change-tracking در سمت کلاینت توسط Web API را بررسی کردیم. در این قسمت نگاهی به حذف موجودیت‌های منفصل یا disconnected خواهیم داشت.


حذف موجودیت‌های منفصل

فرض کنید موجودیتی را از یک سرویس WCF دریافت کرده اید و می‌خواهید آن را برای حذف علامت گذاری کنید. مدل زیر را در نظر بگیرید.

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

  • در ویژوال استودیو پروژه جدیدی از نوع WCF Service Library بسازید و نام آن را به Recipe5 تغییر دهید.
  • روی پروژه کلیک راست کنید و گزینه Add New Item را انتخاب کنید. سپس گزینه‌های Data -> ADO.NET Entity Data Model را برگزینید.
  • از ویزارد ویژوال استودیو برای اضافه کردن یک مدل با جداول Invoice و Payment استفاده کنید. برای ساده نگه داشتن مثال جاری، فیلد پیمایشی Payments را از موجودیت Invoice حذف کرده ایم (برای این کار روی خاصیت پیمایشی Payments کلیک راست کنید و گزینه Delete From Model را انتخاب کنید.) روی خاصیت TimeStamp موجودیت Payment کلیک راست کنید و گزینه Properties را انتخاب کنید. سپس مقدار Concurrency Mode آن را به Fixed تغییر دهید. این کار باعث می‌شود که مقدار این فیلد برای کنترل همزمانی بررسی شود. بنابراین مقدار TimeStamp در عبارت WHERE تمام دستورات بروز رسانی و حذف درج خواهد شد.
  • فایل IService1.cs را باز کنید و تعریف سرویس را مانند لیست زیر تغییر دهید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    Payment InsertPayment();
    [OperationContract]
    void DeletePayment(Payment payment);
}
  • فایل Service1.cs را باز کنید و پیاده سازی سرویس را مانند لیست زیر تغییر دهید.
public class Service1 : IService1
{
    public Payment InsertPayment()
    {
        using (var context = new EFRecipesEntities())
        {
            // delete the previous test data
            context.Database.ExecuteSqlCommand("delete from [payments]");
            context.Database.ExecuteSqlCommand("delete from [invoices]");
            var payment = new Payment { Amount = 99.95M, Invoice =
                new Invoice { Description = "Auto Repair" } };
            context.Payments.Add(payment);
            context.SaveChanges();
            return payment;
        }
    }

    public void DeletePayment(Payment payment)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(payment).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }
}
  • برای تست این سرویس به یک کلاینت نیاز داریم. یک پروژه جدید از نوع Console Application به راه حل جاری اضافه کنید و کد آن را مطابق لیست زیر تغییر دهید. فراموش نکنید که ارجاعی به سرویس هم اضافه کنید. روی پروژه کلاینت کلیک راست کرده و Add Service Reference را انتخاب نمایید. ممکن است پیش از آنکه بتوانید سرویس را ارجاع کنید، نیاز باشد پروژه سرویس را ابتدا اجرا کنید (کلیک راست روی پروژه سرویس و انتخاب گزینه Debug -> Start Instance).
class Program
{
    static void Main()
    {
        var client = new Service1Client();
        var payment = client.InsertPayment();
        client.DeletePayment(payment);
    }
}
اگر روی خط اول متد ()Main یک breakpoint قرار دهید می‌توانید مراحل ایجاد و حذف یک موجودیت Payment را دنبال کنید.


شرح مثال جاری

در مثال جاری برای بروز رسانی و حذف موجودیت‌های منفصل از الگویی رایج استفاده کرده ایم که در سرویس‌های WCF و Web API استفاده می‌شود.

در کلاینت با فراخوانی متد InsertPayment یک پرداخت جدید در دیتابیس ذخیره می‌کنیم. این متد، موجودیت Payment ایجاد شده را باز می‌گرداند. موجودیتی که به کلاینت باز می‌گردد از DbContext منفصل (disconnected) است، در واقع در چنین وضعیتی آبجکت context ممکن است در فضای پروسس دیگری قرار داشته باشد، یا حتی روی کامپیوتر دیگری باشد.

برای حذف موجودیت Payment از متد DeletePayment استفاده می‌کنیم. این متد به نوبه خود با فراخوانی متد Entry روی آبجکت context و پاس دادن موجودیت پرداخت بعنوان آرگومان، موجودیت را پیدا می‌کند. سپس وضعیت موجودیت را به EntityState.Deleted تغییر می‌دهیم که این کار آبجکت را برای حذف علامت گذاری می‌کند. فراخوانی‌های بعدی متد ()SaveChanges موجودیت را از دیتابیس حذف خواهد کرد.

آبجکت پرداختی که برای حذف به context الحاق کرده ایم تمام خاصیت هایش مقدار دهی شده اند، درست مانند هنگامی که این موجودیت به دیتابیس اضافه شده بود. اما از آنجا که از foreign key association استفاده می‌کنیم، تنها فیلدهای کلید موجودیت، خاصیت همزمانی (concurrency) و TimeStamp برای تولید عبارت where مناسب لازم هستند که نهایتا منجر به حذف موجودیت خواهد شد. تنها استثنا درباره این قاعده هنگامی است که موجودیت شما یک یا چند خاصیت از نوع پیچیده یا Complex Type داشته باشد. از آنجا که خاصیت‌های پیچیده، اجزای ساختاری یک موجودیت محسوب می‌شوند نمی‌توانند مقادیر null بپذیرند. یک راه حل ساده این است که هنگامی که EF مشغول ساختن عبارت SQL Delete لازم برای حذف موجودیت بر اساس کلید و خاصیت همزمانی آن است، وهله جدیدی از نوع داده پیچیده خود بسازید. اگر فیلدهای complex type را با مقادیر null رها کنید، فراخوانی متد ()SaveChanges با خطا مواجه خواهد شد.

اگر از یک independent association استفاده می‌کنید که در آن کثرت (multiplicity) موجودیت مربوطه یک، یا صفر به یک است، EF انتظار دارد که کلید‌های موجودیت‌ها بدرستی مقدار دهی شوند تا بتواند عبارت where مناسب را برای دستورات بروز رسانی و حذف تولید کند. اگر در مثال جاری از یک رابطه independent association بین موجودیت‌های Invoice و Payment استفاده می‌کردیم، لازم بود تا خاصیت پیمایشی Invoice را با وهله ای از صورت حساب مقدار دهی کنیم که خاصیت InvoiceId آن نیز بدرستی مقدار دهی شده باشد. در این صورت عبارت where نهایی شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.

نکته: هنگام پیاده سازی معماری‌های n-Tier با Entity Framework، استفاده از رویکرد Foreign Key Association برای موجودیت‌های مرتبط باید با ملاحظات جدی انجام شود. پیاده سازی رویکرد Independent Association مشکل است و می‌تواند کد شما را بسیار پیچیده کند. برای مطالعه بیشتر درباره این رویکردها و مزایا و معایب آنها به این لینک مراجعه کنید که توسط یکی از برنامه نویسان تیم EF نوشته شده است.

اگر موجودیت شما تعداد متعددی Independent Association دارد، مقدار دهی تمام آنها می‌تواند خسته کننده شود. رویکردی ساده‌تر این است که وهله مورد نظر را از دیتابیس دریافت کنید و آن را برای حذف علامت گذاری نمایید. این روش کد شما را ساده‌تر می‌کند، اما هنگامی که آبجکت را از دیتابیس دریافت می‌کنید EF کوئری جاری را بازنویسی می‌کند تا تمام روابط یک، یا صفر به یک بارگذاری شوند. مگر آنکه از گزینه NoTracking روی context خود استفاده کنید. اگر در مثال جاری رویکرد Independent Association را پیاده سازی کرده بودیم، هنگامی که موجودیت Payment را از دیتابیس دریافت می‌کنیم (قبل از علامت گذاری برای حذف) EF یک Object state entry برای موجودیت پرداخت و یک Relationship entry برای رابطه بین Payment و Invoice می‌ساخت. سپس وقتی که موجودیت پرداخت را برای حذف علامت گذاری می‌کنیم، EF رابطه بین پرداخت و صورت حساب را هم برای حذف علامت گذاری می‌کند. در اینجا عبارت where تولید شده مانند قبل، شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.

یک گزینه دیگر برای حذف موجودیت‌ها در Independent Associations این است که تمام موجودیت‌های مرتبط را مشخصا بارگذاری کنیم (eager loading) و کل Object graph را برای حذف به سرویس WCF یا Web API بفرستیم. در مثال جاری می‌توانستیم موجودیت صورتحساب مرتبط با موجودیت پرداخت را مشخصا بارگذاری کنیم. اگر می‌خواستیم موجودیت Payment را حذف کنیم، می‌توانستیم کل گراف را که شامل هر دو موجودیت می‌شود به سرویس ارسال کنیم. اما هنگام استفاده از چنین روشی باید بسیار دقت کنید، چرا که این رویکرد پهنای باند بیشتری مصرف می‌کند و زمان پردازش بیشتری هم برای مرتب سازی (serialization) صرف می‌کند. بنابراین هزینه این رویکرد نسبت به سادگی کدی که بدست می‌آید به مراتب بیشتر است.

مطالب
ObservableCollection در Entity Framework
در مبحث استفاده از خاصیت Local در Entity Framework  ملاحظه نمودید که خاصیت Local به راحتی می‌تواند از رفت و آمدهای بی جهت به دیتابیس جلوگیری کند.
حال قصد معرفی یک collection  را به نام ObservableCollection دارم.   
همانطور که از نامش پیداست برای مشاهده و تحت نظر قرار دادن داده‌های اضافه شده یا پاک شده کاربرد دارد. به کد زیر دقت کنید.
    private static void ListenToLocalChanges()
    {
        using (var context = new BreakAwayContext())
        {
            context.Destinations.Local.CollectionChanged += (sender, args) =>
            {
                if (args.NewItems != null)
                {
                    foreach (Destination item in args.NewItems)
                    {
                        Console.WriteLine("Added: " + item.Name);
                    }
                }
                if (args.OldItems != null)
                {
                    foreach (Destination item in args.OldItems)
                    {
                        Console.WriteLine("Removed: " + item.Name);
                    }
                }
            };
            context.Destinations.Load();
        }
    }
در بالا به وسیله یک event handler جدید به collection محلی ما (Local) نظر می‌اندازد و در صورت اضافه شدن یا حذف موجودیتی، آن را به ما نشان می‌دهد. فقط توجه کنید که اگر نیاز دارید در صفحه‌ای این تغییرات را مشاهده کنید باید عمل Refresh کردن صفحه را چه به صورت دستی یا با نوشتن کد خودتان مدیریت کنید. البته با استفاده از WPF میتوان (استفاده  از کنترل‌های مانند ListBox ) این کار را به صورت خودکار انجام داد.