مطالب
تست کردن متدهای یک 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
مطالب
بررسی تغییرات Blazor 8x - قسمت دوم - بررسی حالت رندر سمت سرور
در قسمت قبل، حالت‌های مختلف رندر کامپوننت‌ها را در Blazor 8x معرفی کردیم. در این قسمت می‌خواهیم نحوه‌ی کارکرد دو حالت InteractiveServer و StreamRendering را به همراه چند مثال بررسی کنیم.


معرفی قالب‌های جدید شروع پروژه‌های Blazor در دات نت 8

پس از نصب SDK دات نت 8، دیگر خبری از قالب‌های قدیمی پروژه‌های blazor server و blazor wasm نیست! در اینجا در ابتدا باید مشخص کرد که سطح تعاملی برنامه در چه حدی است. در ادامه 4 روش شروع پروژه‌های Blazor 8x را مشاهده می‌کنید که توسط پرچم interactivity--، نوع رندر برنامه در آن‌ها مشخص شده‌است:

اجرای قسمت‌های تعاملی برنامه بر روی سرور:
dotnet new blazor --interactivity Server

اجرای قسمت‌های تعاملی برنامه در مرورگر، توسط فناوری وب‌اسمبلی:
dotnet new blazor --interactivity WebAssembly

برای اجرای قسمت‌های تعاملی برنامه، ابتدا حالت Server فعالسازی می‌شود تا فایل‌های WebAssembly دریافت شوند، سپس فقط از WebAssembly استفاده می‌کند:
dotnet new blazor --interactivity Auto

فقط از حالت SSR یا همان static server rendering استفاده می‌شود (این نوع برنامه‌ها تعاملی نیستند):
dotnet new blazor --interactivity None

سایر گزینه‌ها را با اجرای دستور dotnet new blazor --help می‌توانید مشاهده کنید.


نکته‌ی مهم! در قالب‌های آماده‌ی Blazor 8x، حالت SSR، پیش‌فرض است.

هرچند در تمام پروژه‌های فوق، انتخاب حالت‌های مختلف رندر را مشاهده می‌کنید، اما این انتخاب‌ها صرفا دو مقصود مهم را دنبال می‌کنند:
الف) تنظیم فایل Program.cs برنامه جهت افزودن وابستگی‌های مورد نیاز، به صورت خودکار.
ب) ایجاد پروژه‌ی کلاینت (علاوه بر پروژه‌ی سرور)، در صورت نیاز. برای مثال حالت‌های وب‌اسمبلی و Auto، هر دو به همراه یک پروژه‌ی کلاینت وب‌اسمبلی هم هستند؛ اما حالت‌های Server و None، خیر.

در تمام این پروژه‌ها هر صفحه و یا کامپوننتی که ایجاد می‌شود، به صورت پیش‌فرض بر اساس SSR رندر و نمایش داده خواهد شد؛ مگر اینکه به صورت صریحی این نحوه‌ی رندر را بازنویسی کنیم. برای مثال مشخص کنیم که قرار است بر اساس Blazor Server اجرا شود و یا وب‌اسمبلی و یا حالت Auto.

اما ... اگر علاقمند بودیم تا به حالت‌های پیش‌از دات نت 8 رجوع کنیم و تمام برنامه را به صورت یک‌دست تعاملی کنیم و حالت SSR پیش‌فرض نباشد، می‌توان از پرچم all-interactive-- که به انتهای دستورات فوق قابل افزوده شدن است، کمک گرفت. این پرچم، فایل App.Razor را جهت تنظیم سراسری حالت‌های رندر، ویرایش می‌کند. این مورد را در ادامه‌ی این مطلب، در قسمت «روشی ساده برای تعاملی کردن کل برنامه» بیشتر بررسی می‌کنیم.


بررسی حالت Server side rendering

برای بررسی این حالت یک پوشه‌ی جدید را ایجاد کرده و توسط خط فرمان، دستور dotnet new blazor --interactivity Server را در ریشه‌ی آن اجرا می‌کنیم. پس از ایجاد ساختار ابتدایی پروژه بر اساس این قالب انتخابی، فایل Program.cs جدید آن، چنین شکلی را دارد:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents().AddInteractiveServerComponents();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

app.Run();
مهم‌ترین قسمت‌های آن، متدهای AddInteractiveServerComponents و AddInteractiveServerRenderMode هستند که server-side rendering را به همراه امکان داشتن کامپوننت‌های تعاملی، میسر می‌کنند.
server-side rendering به این معنا است که برنامه‌ی سمت سرور، کل DOM و HTML نهایی را تولید کرده و به مرورگر کاربر ارائه می‌کند. مرورگر هم این DOM را نمایش می‌دهد. فقط همین! در اینجا هیچ خبری از اتصال دائم SignalR نیست و محتوای ارائه شده، یک محتوای استاتیک است. این حالت رندر، برای ارائه‌ی محتواهای فقط خواندنی غیرتعاملی، فوق العاده‌است؛ امکان از لحظه‌ای که نیاز به کلیک بر روی دکمه‌ای باشد، دیگر پاسخگو نیست. به همین جهت در اینجا امکان تعاملی کردن تعدادی از کامپوننت‌های ویژه و مدنظر نیز پیش‌بینی شده‌اند تا بتوان به ترکیبی از server-side rendering و client-side rendering رسید.
حالت پیش‌فرض در اینجا، ارائه‌ی محتوای استاتیک است. بنابراین هر کامپوننتی در اینجا ابتدا بر روی سرور رندر شده (HTML ابتدایی آن آماده شده) و به سمت مرورگر کاربر ارسال می‌شود. اگر کامپوننتی نیاز به امکانات تعاملی داشت باید آن‌را دقیقا توسط ویژگی InteractiveXYZ مشخص کند؛ مانند مثال زیر:
@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
همانطور که مشاهده می‌کنید، این کامپوننت از روش رندر InteractiveServer استفاده می‌کند. برای درک نحوه‌ی کارکرد آن، همین سطر را حذف، یا کامنت کنید و سپس برنامه را اجرا کنید. در حین مشاهده‌ی این صفحه، همه چیز به خوبی نمایش داده می‌شود و حتی دکمه‌ی Click me هم مشخص است. اما ... با کلیک بر روی آن اتفاقی رخ نمی‌دهد! علت اینجا است که اکنون این صفحه، یک صفحه‌ی کاملا استاتیک است و دیگر تعاملی نیست.
در ادامه، مجددا سطر کامنت شده را به حالت عادی برگردانید و سپس برنامه را اجرا کنید. پیش از باز کردن صفحه‌ی Counter، ابتدا developer tools مرورگر خود را گشوده و برگه‌ی network آن‌را انتخاب و سپس صفحه‌ی Counter را باز کنید. در این لحظه‌است که مشاهده می‌کنید یک اتصال وب‌سوکت برقرار شد. این اتصال است که قابلیت‌های تعاملی صفحه را برقرار کرده و مدیریت می‌کند (این اتصال دائم SignalR است که این صفحه را همانند برنامه‌های Blazor Web Server پیشین مدیریت می‌کند).

یک نکته: در برنامه‌های Blazor Server سنتی، امکان فعالسازی قابلیتی به نام prerender نیز وجود دارد. یعنی سرور، ابتدا صفحه را رندر کرده و محتوای استاتیک آن‌را به سمت مرورگر کاربر ارسال می‌کند و سپس اتصال SignalR برقرار می‌شود. در دات نت 8، این حالت، حالت پیش‌فرض است. اگر آن‌را نمی‌خواهید باید به نحو زیر غیرفعالش کنید:
@rendermode InteractiveServerRenderModeWithoutPrerendering

@code{
  static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = 
        new InteractiveServerRenderMode(false);
}


روشی ساده برای تعاملی کردن کل برنامه

اگر می‌خواهید رفتار برنامه را همانند Blazor Server سابق کنید و نمی‌خواهید به ازای هر کامپوننت، نحوه‌ی رندر آن‌را به صورت سفارشی انتخاب کنید، فقط کافی است فایل App.razor را باز کرده:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="MyApp.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>
و قسمت‌های HeadOutlet و مسیریابی آن‌را به صورت زیر تغییر دهید تا به کل برنامه اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" />
...
<Routes @rendermode="@InteractiveServer" />
این مورد دقیقا همان کاری است که پرچم all-interactive-- در هنگام ایجاد پروژه‌های جدید Blazor 8x به کمک NET CLI.، انجام می‌دهد. یک چنین گزینه‌ای در ویژوال استودیو نیز هنگام ایجاد پروژه‌ها‌ی جدید Blazor وجود دارد و به شما این امکان را می‌دهد که بین حالت‌های تعاملی Per page/component و Global، یکی را انتخاب کنید. در حین استفاده‌ی از CLI، نیازی به ذکر حالت تعاملی Per page/component نیست؛ چون حالت پیش‌فرض، یا همان SSR است. حالت Global هم فقط فایل App.Razor را به صورت فوق، ویرایش و تنظیم می‌کند.


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


بررسی حالت Streaming Rendering

در اینجا مثال پیش‌فرض Weather.razor قالب پیش‌فرض مورد استفاده‌ی جاری را کمی تغییر داده‌ایم که کدهای نهایی آن به صورت زیر است (2 قسمت forecasts.AddRange_ را اضافه‌تر دارد):

@page "/weather"
@attribute [StreamRendering(prerender: true)]

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (_forecasts == null)
{
    <p>
        <em>Loading...</em>
    </p>
}
else
{
    <table class="table">
        <thead>
        <tr>
            <th>Date</th>
            <th>Temp. (C)</th>
            <th>Temp. (F)</th>
            <th>Summary</th>
        </tr>
        </thead>
        <tbody>
        @foreach (var forecast in _forecasts)
        {
            <tr>
                <td>@forecast.Date.ToShortDateString()</td>
                <td>@forecast.TemperatureC</td>
                <td>@forecast.TemperatureF</td>
                <td>@forecast.Summary</td>
            </tr>
        }
        </tbody>
    </table>
}

@code {
    private List<WeatherForecast>? _forecasts;

    protected override async Task OnInitializedAsync()
    {
    // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(500);

        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[]
                        {
                            "Freezing",
                            "Bracing",
                            "Chilly",
                            "Cool",
                            "Mild",
                            "Warm",
                            "Balmy",
                            "Hot",
                            "Sweltering",
                            "Scorching",
                        };
        _forecasts = GetWeatherForecasts(startDate, summaries).ToList();
        StateHasChanged();

    // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(1000);
        _forecasts.AddRange(GetWeatherForecasts(startDate, summaries));
        StateHasChanged();

        await Task.Delay(1000);
        _forecasts.AddRange(GetWeatherForecasts(startDate, summaries));
    }

    private static IEnumerable<WeatherForecast> GetWeatherForecasts(DateOnly startDate, string[] summaries)
    {
        return Enumerable.Range(1, 5)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = startDate.AddDays(index),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = summaries[Random.Shared.Next(summaries.Length)],
                                          });
    }

    private class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }

}
برای بررسی این مثال، ابتدا سطر ویژگی StreamRendering را حذف و یا کامنت کرده و سپس برنامه را اجرا کنید. پس از اجرای برنامه، با انتخاب مشاهده‌ی صفحه‌ی Weather، هیچگاه قسمت loading که در حالت forecasts == null_ قرار است ظاهر شود، نمایش داده نمی‌شود؛ چون در این حالت (حذف نوع رندر)، صفحه‌ی نهایی که به کاربر ارائه خواهد شد، یک صفحه‌ی استاتیک کاملا رندر شده‌ی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحه‌ی نهایی را دریافت و مشاهده کند. در این حالت امکانات تعاملی Blazor server وجود خارجی ندارند.
در ادامه مجددا سطر ویژگی StreamRendering را به حالت قبلی برگردانید و برنامه را اجرا کنید. در این حالت ابتدا قسمت loading ظاهر می‌شود و سپس در طی چند مرحله با توجه به Task.Delay‌های قرار داده شده، صفحه رندر شده و تکمیل می‌شود.
اتفاقی که در اینجا رخ می‌دهد، استفاده از فناوری  HTML Streaming است که مختص به مایکروسافت هم نیست. در حالت Streaming، هربار قطعه‌ای از HTML ای که قرار است به کاربر ارائه شود، به صورت جریانی به سمت مرورگر ارسال می‌شود و مرورگر این قطعه‌ی جدید را بلافاصله نمایش می‌دهد. نکته‌ی جالب این روش، عدم نیاز به اتصال SignalR و یا اجرای WASM درون مرورگر است.

Streaming rendering حالت بینابین رندر کامل در سمت سرور و رندر کامل در سمت کلاینت است. در حالت رندر سمت سرور، کل HTML صفحه ابتدا توسط سرور تهیه و بازگشت داده می‌شود و کاربر باید تا پایان عملیات تهیه‌ی این HTML نهایی، منتظر باقی بماند و در این بین چیزی را مشاهده نخواهد کرد. در حالت Streaming rendering، هنوز هم همان حالت تهیه‌ی HTML استاتیک سمت سرور برقرار است؛ به همراه تعدادی محل جایگذاری اطلاعات جدید. به محض پایان یک عمل async سمت سرور که سه نمونه‌ی آن را در مثال فوق مشاهده می‌کنید، برنامه، جریان قطعه‌ای از اطلاعات استاتیک را به سمت مرورگر کاربر ارسال می‌کند تا در مکان‌هایی از پیش تعیین شده، درج شوند.
در حالت SSR، فقط یکبار شانس ارسال کل اطلاعات به سمت مرورگر کاربر وجود دارد؛ اما در حالت Streaming rendering، ابتدا می‌توان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آن‌را به محض آماده شدن در طی چند مرحله بازگشت داد. در این حالت نمایش گزارشی از اطلاعاتی که ممکن است با تاخیر در سمت سرور تهیه شوند، ساده‌تر می‌شود. یعنی می‌توان هربار قسمتی را که تهیه شده، برای نمایش بازگشت داد و کاربر تا مدت زیادی منتظر نمایش کل صفحه باقی نخواهد ماند.


روش نهایی معرفی نحوه‌ی رندر صفحات

بجای استفاده از ویژگی‌های RenderModeXyz جهت معرفی نحوه‌ی رندر کامپوننت‌ها و صفحات (که تا پیش از نگارش RTM معرفی شده بودند و چندبار هم تغییر کردند)، می‌توان از دایرکتیو جدیدی به نام rendermode@ با سه مقدار InteractiveServer، InteractiveWebAssembly و InteractiveAuto استفاده کرد. برای سهولت تعریف این موارد باید سطر ذیل را به فایل Imports.razor_ اضافه نمود:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
در این حالت تعریف قدیمی سطر ذیل:
@attribute [RenderModeInteractiveServer]
به صورت زیر:
@rendermode InteractiveServer
تبدیل می‌شود.

اگر هم قصد سفارشی سازی آن‌ها را دارید، برای مثال می‌خواهید prerender را در آن‌ها false کنید، روش کار به صورت زیر است:
@rendermode renderMode

@code {
    static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false);
}
نظرات مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
ممنون آقای نصیری

با راهنمایی شما مشکل Duplicate Reader با نوشتن دستور به شکل زیر حل شد.

this._pages.Include(page => page.Children).ToList().Where(page => page.Parent == null).ToList();

الان مشکلی دارم اینه که نمیتونم یه select خوب بنویسم تا فقط مواردی را که میخوام برگردونم.
مثلا من viewmodel را این شکلی تعریف کردم.

public class NavBarModel
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual string Status { get; set; }
        public virtual int? Order { get; set; }
        public virtual NavBarModel Parent { get; set; }
        public virtual ICollection<Page> Children { get; set; }
    }

توی select زدن نمیدونم چه شکلی باید کد بزنم تا parent و children هم یه صورت خودکار پر شوند.

در حقیقت من می‌خوام این موارد را در یک navigation bar به صورت منوی آبشاری نشون بدم.

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

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

کوکی در جاوا اسکریپت 

همانطور که در قسمت قبل اشاره کوتاهی شد، مدیریت کوکی‌های در دسترس در وضعیت جاری، در جاوا اسکریپت ازطریق پراپرتی cookie از شی document امکان‌پذیر است. این پراپرتی کاری همانند هدرهای Set-Cookie و Cookie (که در قسمت قبل درباره آن‌ها بحث شد) انجام می‌دهد. این پراپرتی یک مورد کاملا استثنایی و نسبتا عجیب در زبان جاوا اسکریپت است. در نگاه اول ظاهرا document.cookie از نوع رشته است، اما قضیه کاملا فرق می‌کند. برای روشن شدن مطلب به ادامه بحث توجه کنید.

افزودن کوکی
- برای افزودن یا ویرایش یک کوکی باید از ساختاری مانند ساختار هدر Set-Cookie که چیزی شبیه به عبارت زیر است، پیروی کرد:
document.cookie = "name=value; expires=date; domain=theDomain; path=thePath; secure";
 
نکته: با توجه به توضیحاتی که در قسمت قبل ارائه شد، بدیهی است که امکان ثبت یک کوکی با فلگ HttpOnly در جاوا اسکریپت وجود ندارد!
 
اجرای دستوری شبیه با ساختار نشان داده شده در بالا، موجب حذف کوکی‌های قبلی نمی‌شود. از این دستور برای ایجاد یک کوکی و یا ویرایش یک کوکی موجود استفاده می‌شود. کوکی‌های ایجادشده با این روش تفاوتی با کوکی‌های ایجادشده توسط هدر Set-Cookie ندارند و همانند آنها در درخواست‌های بعدی با توجه به خواص تنظیم شده، به سمت سرور ارسال خواهند شد.
همانطور که مشاهده می‌کنید خاصیت‌های کوکی به صورت جفت‌های نام-مقدار درون یک رشته به document.cookie نسبت داده می‌شوند. این خاصیت‌ها توسط یک کاراکتر ; از یکدیگر جدا می‌شوند. شرح ساختار فوق در  زیر آورده شده است:
1. همیشه اولین جفتِ نام-مقدار همانند مثال بالا باید «عنوان و مقدار» کوکی را مشخص سازد. این قسمت تنها عضو اجباری ساختار فوق است.
2. سپس یک سمی‌کالن و یک فاصله
3. تاریخ انقضا (expires) یا حداکثر طول عمر کوکی (max-age)
4. سپس یک سمی‌کالن و یک فاصله
5. دمین و یا مسیر مربوط به کوکی
6. سپس یک سمی‌کالن و یک فاصله
7. سایر خواص چون Secure
نکته: این ساختار عجیب معرفی شده را عینا رعایت کنید. بقیه کار توسط مرورگر انجام خواهد شد.
نکته: قسمت‌های مختلف این ساختار case-sensitive نیست، البته به‌جز نام کوکی که کاملا case-sensitive است.
مثلا برای ثبت یک کوکی با عنوان myCookie و مقدار myValue و دمین d.com و مسیر test و طول عمر 5 روزه باید از دستور زیر استفاده کرد:
document.cookie = 'myCookie=myValue; max-age=432000; domain=d.com; path=/test';
 
خواندن کوکی
- برای خواندن کوکی‌ها تنها کافی است مقدار پراپرتی document.cookie بررسی شود. با اینکه از دستور نشان داده شده در بالا اینگونه برمی آید که پراپرتی document.cookie به رشته معرفی شده مقداردهی شده است، اما به محض خواندن این پراپرتی چیزی شبیه به عبارت زیر برگردانده میشود:
myCookie=myValue 
از بقیه خواص اثری نیست! این رفتار به دلیل حفط امنیت کوکی‌ها در تمام مرورگرها رعایت می‌شود.
- برای ثبت کوکی دیگری در وضعیت جاری کافی است یکبار دیگر دستور بالا را برای کوکی جدید به کار ببریم. مثلا به صورت زیر:
document.cookie = 'mySecondCookie=mySecondValue; path=/'
اینار یک کوکی سشنی بدون دمین و با مقدار / برای مسیر کوکی ثبت می‌شود! در این حالت کوکی قبلی دوباره نویسی و یا حذف نمی‌شود و تنها یک کوکی جدید به لیست کوکیهای مرورگر اضافه می‌شود! این رفتار عجیب از ویژگی‌های جالب document.cookie است.
- اگر مقدار document.cookie در این حالت خوانده شود مقدار زیر برگشت داده می‌شود:
myCookie=myValue; mySecondCookie=mySecondValue
باز هم خبری از سایر خاصیت‌ها نیست. ولی همانطور که می‌بینید کوکی دوم به لیست کوکی‌های مرورگر اضافه شده است.

نکته: عبارت برگشت داده شده از پراپرتی document.cookie همانند مقداری است که در هدر Cookie هر درخواست توسط مرورگر گنجانده می‌شود، یعنی جفت نام-مقدار کوکی‌ها به همراه یک ; و یک فاصله بین مقادیر هر کوکی. بنابراین برای بدست آوردن مقدار یک کوکی یکسری عملیات جهت Parse کردن داده‌های آن نیاز است!

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

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

افزودن و یا ویرایش کوکی
function setCookie(data, value) {
  if (typeof data === "string") {
    data = { name: data, value: value };
  };
  if (!data.name) throw "Cookie's name can not be null.";

  var cookie = escape(data.name) + "=" + escape(data.value);

  var expDate = null;
  if (data.expDays) {
    expDate = new Date();
    expDate.setDate(expDate.getDate() + data.expDays);
  }
  else if (data.expYear && data.expMonth && data.expDay) {
    expDate = new Date(data.expYear, data.expMonth, data.expDay);
  }
  else if (data.expires) {
    expDate = data.expires;
  }
  else if (data.maxAge) {
    expDate = new Date();
    expDate.setSeconds(expDate.getSeconds() + data.maxAge);
  }
  if (expDate != null) cookie += "; expires=" + expDate.toGMTString();

  if (data.domain)
    cookie += "; domain=" + escape(data.domain);

  if (data.path)
    cookie += "; path=" + escape(data.path);

  if (data.secure)
    cookie += "; secure";

  document.cookie = cookie;
  return document.cookie;
}
در کد فوق برای انکد کردن رشته‌های مورد استفاده از متد escape استفاده شده است. برای آشنایی با این متد به اینجا مراجعه کنید.
هم‌چنین کار کردن با نوع داده تاریخ در جاوا اسکریپت کمی متفاوت است. بنابراین برای آشنایی بیشتر با این نوع داده به اینجا رجوع کنید.
 
نکته: در متد بالا بدلیل عدم پشتیبانی از خاصیت max-age در نسخه‌های قدیمی اینترنت اکسپلورر (نسخه 8 و قبل از آن) تنها از خاصیت expires استفاده شده است.
 
نحوه استفاده از متد بالا به صورت زیر است:
setCookie('cookie1', 'Value1');
setCookie({name:'cookie1', value:'Value1'});
setCookie({name:'cookie2', value:'Value2', expDays:10});
setCookie({name:'cookie3', value:'Value3', expires:new Date()});
setCookie({name:'cookie4', value:'Value4', expYear:2013, expMonth:0, expDay:13});
setCookie({name:'cookie3', value:'Value3', maxAge:365*24*60*60});
setCookie({name:'cookie5', value:'Value5', domain:'d.net', path:'/'});
setCookie({name:'cookie6', value:'Value6', secure:true});
setCookie({name:'cookie7', value:'Value7', expDays:100, domain:'dd.com', path:'/employee', secure:true});
 
حذف کوکی
همانطور که در قسمت قبل هم اشاره شد، برای حذف یک کوکی، کافی است تا تاریخ انقضای آن به تاریخی در گذشته مقداردهی شود. بنابراین برای اینکار می‌توان از متد زیر استفاده کرد:
function delCookie(data) {
  if (typeof data === "string") {
    data = { name: data };
  };
  data.expDays = -1;
  return setCookie(data);
}
در متد فوق از متد setCookie که در بالا معرفی شد، استفاده شده است. نحوه استفاده از این متد هم به صورت زیر است:
delCookie('myCookie');
delCookie({ name: 'myCookie', domain: 'd.com', path: '/test' });
 
خواندن کوکی
برای خواندن مقدار یک کوکی می‌توان از متد زیر استفاده کرد:
function getCookie(name) {
  var cookies = document.cookie.split(";");
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i].split("=");
    if (cookie[0].trim() == escape(name)) {
      return unescape(cookie[1].trim());
    }
  }
  return null;
}
برای آشنایی با متد unescape که در بالا از آن استفاده شده است به اینجا مراجعه کنید. در متد فوق از متد trim زیر استفاده شده است:
String.prototype.trim = function () {
  return this.replace(/^\s+|\s+$/g, "");
};
String.prototype.trimStart = function () {
  return this.replace(/^\s+/, "");
};
String.prototype.trimEnd = function () {
  return this.replace(/\s+$/, "");
};
این متدها از اینجا گرفته شده است.
روش استفاده شده برای خواندن مقادیر کوکی‌ها در متد بالا بسیار ساده و ابتدایی است و صرفا برای آشنایی با نحوه Parse کردن رشته برگشت داده شده توسط document.cookie ارائه شده است. روش‌های مناسب‌تر و مطمئن‌تر با یک جستجوی ساده در دسترس هستند. البته همانطور که قبلا هم اشاره شد، استفاه از کتابخانه‌های موجود راه‌حل بهتری است.
هم‌چنین ازآنجاکه مقدار یک کوکی می‌تواند شامل کاراکتر = نیز باشد، بنابراین قسمت return متد فوق را می‌توان به صورت زیر تغییر داد:
cookie.shift(1);
return unescape(cookie.join('=').trim());
 
نکته: با توجه به مطالب ارائه شده در قسمت قبل  بدست آوردن مقادیر کوکی‌ها کمی پیچیده‌تر از دیگر عملیات‌هاست. ازآنجاکه راه مستقیمی با استفاده از جاوا اسکریپت برای یافتن سایر خواص کوکی وجود ندارد، بنابراین بدست آوردن مقدار دقیق کوکی موردنظر ممکن است غیرممکن باشد! (با توجه به اینکه کوکی‌های متفاوت می‌توانند نام‌های یکسانی داشته باشند).
 
با توجه به نکته بالا، حال اگر با یک نام بخصوص، چندین کوکی ثبت شده باشد (با خواص متفاوت)، یکی از راه‌حل‌ها این است که آرایه‌ای از مقادیر این کوکی‌های همنام برگشت داده شود. بنابراین متد فوق را می‌توان به صورت زیر تکمیل کرد:
function getCookie(name) {
  var foundCookies = [];
  var cookies = document.cookie.split(";");
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i].split("=");
    if (cookie[0].trim() == escape(name) && cookie.length >= 2) {
      cookie.shift(1);
      foundCookies.push(unescape(cookie.join('=').trim()));
    }
  }
  return foundCookies.length > 1 
            ? foundCookies
            : foundCookies.length == 1
                ? foundCookies[0]
                : null;
}

خلاصه‌ای از نحوه استفاده از متدهای بالا در IE8 (برای نمایش اجرای درست در مرورگری قدیمی!) در تصویر زیر  نشان داده شده است:

 
نکته: کار توسعه این متدها را میتوان برای پشتیبانی از SubCookieها نیز ادامه داد، اما به دلیل دورشدن از مبحث اصلی، این موضوع در این مطلب ارائه نمیشود (درباره این نوع از کوکی‌ها در قسمت قبل شرح کوتاهی داده شده است). اگر علاقه‌مند و نیازمند به این نوع کوکی‌ها هستید، کتابخانه YUI پشتیبانی کاملی از آنها ارائه میکند.
 
در قسمت بعدی به نکات کار با کوکی در ASP.NET میپردازیم.

منابع:
مطالب
C# 7.1 - Tuple Name Inference
در مطلب «C# 7 - Tuple return types and deconstruction» با نوع‌های جدید بازگشتی Tuple در C# 7.0 آشنا شدیم. در C# 7.1 تشخیص نام اعضای Tuple تعریف شده بهبود یافته و از این لحاظ شبیه به anonymous types شده‌است. مفهوم «Name Inference» یا «حدس زدن نام‌ها» را با یک مثال بهتر می‌توان توضیح داد.
string name = "User 1";
int age = 20;
var personTuple = (name, age);
Console.WriteLine(personTuple.Item1); // User 1
Console.WriteLine(personTuple.Item2); // 20
در C# 7.0 مفهوم «Name Inference» پیاده سازی نشده‌است. به همین جهت کامپایلر قادر نیست نام اعضای Tuple تعریف شده‌ی فوق را حدس بزند و برای دسترسی به آن‌ها باید تنها از Item1 و Item2 مانند قبل استفاده کرد. البته اگر برای اعضای Tuple نام تعریف کنیم (قسمت «مفهوم Tuple Literals»)، آنگاه می‌توان Item1 و Item2 را با نام‌های این اعضاء جایگزین کرد:
string name = "User 1";
int age = 20;
var personTuple = (name: name, age:age);
Console.WriteLine(personTuple.name); // User 1
Console.WriteLine(personTuple.age); // 20
بنابراین ذکر نام صریح اعضای Tuple در سی‌شارپ 7 الزامی است؛ در غیراینصورت باید با همان نام‌های عمومی Item1 و Item2 جهت دسترسی به این اعضاء، کار کرد.
این وضعیت در C# 7.1 بهبود یافته‌است و اکنون کامپایلر در صورت عدم ذکر صریح نام اعضای Tuple، قادر است این نام‌ها را دقیقا بر اساس نام متغیرها، همانند قابلیتی که در مورد anonymous types وجود دارد، تعیین کند و حدس بزند:
string name = "User 1";
int age = 20;
var personTuple = (name, age);
Console.WriteLine(personTuple.name); // User 1
Console.WriteLine(personTuple.age); // 20
این مثال، شبیه به اولین مثالی است که در مورد C# 7.0 ذکر شد. اما در C# 7.1 نیازی به ذکر Item1 و Item2 جهت دسترسی به اعضای Tuple تعریف شده نیست (هرچند هنوز هم مجاز است) و کامپایلر نام این اعضاء را از نام متغیرهای متناظر با آن‌ها حدس می‌زند.


مثال‌هایی از حدس زدن نام‌های اعضای Tuple در C# 7.1

مثال اول همان حدس زدن نام‌های اعضای Tuple بر اساس نام متغیرهای محلی متناظر با آن‌ها است.

مثال دوم بر اساس نام خواص یک شیء است که توسط یک نوع Tuple بازگشت داده می‌شود:
var p = new Person
{
   Name = "User 1",
   Age = 20
};
var personTuple = (p.Name, p.Age);
Console.WriteLine(personTuple.Name);
Console.WriteLine(personTuple.Age);

در اینجا عملگر .? نیز پشتیبانی می‌شود:
Person p = null;
var personTuple = (p?.Name, p?.Age);
Console.WriteLine(personTuple.Name); // null
Console.WriteLine(personTuple.Age); // null

مثال سوم همان مثال دوم است که در یک عبارت LINQ بکار رفته‌است:
var people = new List<Person>
{
   new Person {Name = "User 1", Age = 42},
   new Person {Name = "User 2", Age = 18},
   new Person {Name = "User 3", Age = 21}
};

var tuples = people
   .Select(person =>
           (
              person.Name,
              person.Age,
              NameAndAge: $"{person.Name} is {person.Age}"
           )
);
var name = tuples.First().Name;
var age = tuples.First().Age; 
var nameAndAge = tuples.First().NameAndAge;
در اینجا نوع خروجی عبارت LINQ نوشته شده، لیستی از Tupleها است. در Tuple خروجی آن، نام دو عضو اول، از نام خواص متناظر با آن‌ها حدس زده می‌شود. نام عنصر سوم به صورت صریح مشخص شده‌است.


نکته 1: حدس زدن نام‌ها در مورد مقادیر خروجی متدها رخ نمی‌دهد.

در مثال ذیل نمی‌توان به personTuple.FirstName بر اساس نام متد ذکر شده دسترسی یافت و تنها می‌توان از Item1 در مورد آن استفاده کرد؛ اما در مورد متغیر محلی age می‌توان:
int age = 42;
var personTuple = (FirstName(), age);
Console.WriteLine(personTuple.Item1);
Console.WriteLine(personTuple.age);


نکته 2: اگر نام اعضای Tuple یکی باشند، عملیات حدس زدن نام‌ها رخ نمی‌دهد.

var p1 = new Person
{
   Name = "User 1",
   Age = 20
};

var p2 = new Person
{
   Name = "User 2",
   Age = 22
};

var personTuple = (p1.Name, p2.Name);
Console.WriteLine(personTuple.Item1); // User 1
Console.WriteLine(personTuple.Item2); // User 2
در این مثال چون Tuple تشکیل شده دارای نام‌های یکسان Name است، امکان حدس زدن نام‌ها میسر نیست و در اینجا نیز باید از طریق Item1 و ... به اعضای Tuple دسترسی یافت (و یا می‌توان به هر عضو Tuple یک نام منحصربفرد را انتساب داد).
اشتراک‌ها
کتابخانه‌ی CalcBinding برای WPF
Before (Automatic inverse example):

<TextBox Text = "{Binding Path=A, Conveter={x:StaticResource MyMathConverter}">

public class MyMathConverter : IValueConverter
{
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
          var source = (int)value;
          return Math.Sin(source*2)-5;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {        
          var res = Double.Parse(value);
          return (int)(Math.Asin(res + 5) / 2);
        }
}

After:

<TextBox Text = "{c:Binding 'Math.Sin(A*2)-5'}">
کتابخانه‌ی CalcBinding برای WPF
نظرات مطالب
آپلود فایل‌ها توسط برنامه‌های React به یک سرور ASP.NET Core به همراه نمایش درصد پیشرفت
با سلام؛ در آپلود فایل در هنگام آپلود، فایل در ریشه پروژه و فولدر wwwroot آپلود میشود، تا اینجا مشکلی ندارم، ولی در هنگام خواندن فایل چون پروژه React در فولدر CientApp قرار دارد نمیتوانم آدرس فایل رو داشته باشم باید چیکار کنم واسه حل این مشکل، و سوال بعدی آیا باید کار خاصی بکنم واسه بعد از publish پروژه که بتونم فایل‌ها رو بخونم؟
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
"اما چون مسیر https://raw.githubusercontent.com قابل دسترسی نیست (از این طرف البته!)  "
میتوان با ایجاد فایل typingsrc. در ریشه پروژه و استفاده از ف.ل.ت.ر ش.ک.ن فری گیت  قادر به دریافت فایل‌های typing شویم.
به جای 8888 از پورتی که با فری گیت به آن متصل شده ایم، استفاده نماییم.
{
"proxy" : "localhost:8888/",
"rejectUnauthorized" : false
}

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 19 - بومی سازی
با انتقال پوشه Resources (بدون تغییر نام پوشه و namespace ها):
پس از بررسی دسترسی به منابع اطلاعات :

کار میکند »  Controller
کار نمیکنید » Views ، SharedResource.fa.resx  ،ViewModels  


ضمنا اگر ViewModels را به class library (مخصوص NET Core. مجزایی به نام Core1RtmTestResources.PresentaionLayer منتقل کنید، حتی در وضعیتی که Resource در ریشه پروژه اصلی Core1RtmTestResources.Web هم قرار داشته باشد منبع اطلاعات را پیدا نمی‌کند (به درستی عمل نمیکند)