مطالب دوره‌ها
استفاده از Async و Await در برنامه‌های دسکتاپ
امکان استفاده از قابلیت‌های غیرهمزمان دات نت 4.5 در برنامه‌های WPF نیز به روش‌های مختلفی میسر است که در ادامه دو روش مرسوم آن‌را بررسی خواهیم کرد.

تهیه مقدمات بحث

ابتدا یک برنامه‌ی WPF جدید را آغاز کنید. سپس کدهای MainWindow.xaml آن‌را به نحو ذیل تغییر دهید.
<Window x:Class="Async10.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <DockPanel Dock="Top">
            <Button Name="BtnGo" Content="Go" Click="BtnGo_OnClick" />
            <ProgressBar Name="ProgressBar" IsIndeterminate="True" Visibility="Collapsed"/>
        </DockPanel>
        <TextBox Name="Results"/>
    </DockPanel>
</Window>
قصد داریم اطلاعاتی را از وب دریافت و سپس در TextBox قرار گرفته در صفحه نمایش دهیم.
در این مثال از کلاس جدید HttpClient نیز استفاده خواهیم کرد. برای استفاده از آن نیاز است ارجاعی را به اسمبلی استاندارد System.Net.Http.dll نیز به پروژه اضافه کنید.


روش اول

در ادامه کدهای فایل MainWindow.xaml.cs را به نحو ذیل تغییر داده و سپس برنامه را اجرا کنید.
using System.Net.Http;
using System.Windows;

namespace Async10
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void BtnGo_OnClick(object sender, RoutedEventArgs e)
        {
            BtnGo.IsEnabled = false;
            ProgressBar.Visibility = Visibility.Visible;

            var url = "https://www.dntips.ir";
            var client = new HttpClient(); // make sure you have an assembly reference to System.Net.Http.dll
            client.DefaultRequestHeaders.UserAgent.ParseAdd("Test Async");
            var task = client.GetStringAsync(url);
            task.ContinueWith(t =>
            {
                Results.Text = t.Result;

                BtnGo.IsEnabled = true;
                ProgressBar.Visibility = Visibility.Collapsed;
            });
        }
    }
}
روال رخدادگردان BtnGo_OnClick به نحو مرسوم آن نوشته شده است. بنابراین جهت دریافت نتیجه‌ی متد GetStringAsync می‌توان از متد ContinueWith بر روی task دریافت اطلاعات از وب، استفاده کرد. همچنین در اینجا مستقیما اطلاعات و نتیجه‌ی دریافتی را به عناصر UI انتساب داده‌ایم.
اگر پروژه را اجرا کنید، برنامه با استثنای زیر متوقف می‌شود:
 The calling thread cannot access this object because a different thread owns it.
چون task آغاز شده در ترد دیگری نسبت به ترد UI اجرا می‌شود، مجوز تغییری را در کدهای UI ندارد. برای حل این مشکل می‌توان از دو روش ذیل استفاده کرد:

الف) با استفاده از SynchronizationContext.Current و متد Post آن
 var context = SynchronizationContext.Current;
task.ContinueWith(t => context.Post(state =>
{
   Results.Text = t.Result;

   BtnGo.IsEnabled = true;
   ProgressBar.Visibility = Visibility.Collapsed;
}, null));
با این روش در قسمت اول آشنا شدید. SynchronizationContext.Current در اینجا چون در ابتدای متد و خارج از ContinueWith دریافت اطلاعات، اجرا می‌شود، به ترد UI یا ترد اصلی برنامه اشاره می‌کند. سپس همانطور که ملاحظه می‌کنید، توسط متد Post آن می‌توان اطلاعات را در زمینه‌ی تردی که SynchronizationContext به آن اشاره می‌کند اجرا کرد.

ب) با استفاده از امکانات TaskScheduler
 var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(t =>
{
   Results.Text = t.Result;

   BtnGo.IsEnabled = true;
   ProgressBar.Visibility = Visibility.Collapsed;
}, taskScheduler);
وقتی یک task اجرا می‌شود، TPL یا task parallel library نیاز دارد بداند، این task بر روی چه تردی و چه زمانی قرار است اجرا شود. به صورت پیش فرض از thread pool استفاده می‌کند، اما الزامی به آن نیست. با استفاده از TaskScheduler می‌توان بر روی نحوه‌ی رفتار تردهای TPL تاثیر گذاشت و یا حتی آن‌ها را سفارشی سازی کرد. متد FromCurrentSynchronizationContext، یک TaskScheduler جدید را در اختیار ما قرار می‌دهد که کدهای آن بر اساس SynchronizationContext.Current کار می‌کند؛ در اینجا Context به UI اشاره می‌کند و در یک برنامه‌ی وب، به یک درخواست رسیده.
برای مثال اگر در برنامه‌های وب یک Task جدید را اجرا کنید شاید اینطور به نظر برسد که به HttpContext دسترسی ندارید. این نقیصه را می‌توان توسط کار با SynchronizationContext جاری برطرف کرد.
در مثال فوق، چون taskScheduler پیش از فراخوانی متد ContinueWith ایجاد شده‌است، به ترد UI اشاره می‌کند. در این حالت برای نمایش اطلاعات در همان ترد اصلی برنامه کافی است این taskScheduler را به عنوان پارامتر متد ContinueWith معرفی کنیم.


روش دوم

در دات نت 4.5 می‌توان روال رخدادگردان تعریف شده را به صورت async نیز معرفی کرد (یعنی مجاز هستیم امضای متد پیش فرض تولید شده را تغییر دهیم):
 private async void BtnGo_OnClick(object sender, RoutedEventArgs e)
سپس استفاده از await در کدهای برنامه میسر خواهد شد:
        private async void BtnGo_OnClick(object sender, RoutedEventArgs e)
        {
            BtnGo.IsEnabled = false;
            ProgressBar.Visibility = Visibility.Visible;

            var url = "https://www.dntips.ir";
            var client = new HttpClient(); // make sure you have an assembly reference to System.Net.Http.dll
            client.DefaultRequestHeaders.UserAgent.ParseAdd("Test Async");
            Results.Text = await client.GetStringAsync(url);
            BtnGo.IsEnabled = true;
            ProgressBar.Visibility = Visibility.Collapsed;
        }
در این حالت دیگر نیازی به استفاده از ContinueWith و مباحث SynchronizationContext نیست. زیرا تمام آن‌ها به صورت توکار اعمال می‌شوند. به علاوه کدنهایی نیز بسیار خواناتر شده‌است.
مطالب
روش دیگر نوشتن Model binderهای سفارشی در ASP.NET Core 7x با معرفی IParseable
ASP.NET MVC از روش بکارگیری binding providerها برای تدارک زیرساخت model binding استفاده می‌کند که در این روش، داده‌های پارامترهای یک action method از طریق هدرها، کوئری استرینگ‌ها، بدنه‌ی درخواست و غیره تهیه می‌شوند. در حالت پیش‌فرض اگر این پارامترها از نوع‌های ساده‌ای مانند اعداد و یا DateTime تشکیل شده باشند و یا به همراه یک TypeConverter باشند که امکان تبدیل این رشته را به آن نوع خاص بدهد، به صورت خودکار bind خواهند شد و هر نوع دیگری، به صورت یک نوع پیچیده درنظر گرفته می‌شود. نوع پیچیده یعنی bind برای مثال اطلاعات بدنه‌ی درخواست به تک تک خواص آن نوع. برای نمونه در کنترلر زیر:
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries =
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy",
        "Hot", "Sweltering", "Scorching",
    };

    // /WeatherForecast/GetForecast2?from=1&to=3
    [HttpGet("[action]")]
    public IEnumerable<WeatherForecast> GetForecast2(Duration days)
    {
        return Enumerable.Range(days.From, days.To)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = Summaries[Random.Shared.Next(Summaries.Length)],
                                          })
                         .ToArray();
    }
}
که از دو مدل زیر استفاده می‌کند:
public class Duration
{
    public int From { get; set; }
    public int To { get; set; }
}

public class WeatherForecast
{
    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }
}
می‌توان خواص پارامتر days را از طریق کوئری استرینگ‌های HttpGet، برای مثال با ارائه‌ی آدرس WeatherForecast/GetForecast2?from=1&to=3 به صورت خودکار تامین کرد. زمانیکه اطلاعات رسیده چنین شکلی را داشته باشند، کار پردازش و bind آن‌ها در حالت HttpGet، خودکار است.


روش دیگر پردازش اطلاعات رشته‌ای رسیده و تشکیل یک Model Binder سفارشی در ASP.NET Core 7x

اکنون فرض کنید بجای آدرس WeatherForecast/GetForecast2?from=1&to=3 که اطلاعات را از طریق کوئری استرینگ مشخص و استانداردی دریافت می‌کند، می‌خواهیم اطلاعات آن‌را از طریق یک قالب سفارشی و غیراستاندارد مانند WeatherForecast/GetForecast3/1-3 دریافت کنیم:
// /WeatherForecast/GetForecast3/1-3
[HttpGet("[action]/{days}")]
public IEnumerable<WeatherForecast> GetForecast3(Days days)
    {
        return Enumerable.Range(days.From, days.To)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = Summaries[Random.Shared.Next(Summaries.Length)],
                                          })
                         .ToArray();
    }
یکی از راه‌های انجام اینکار، نوشتن model binderهای سفارشی مخصوص است و یا اکنون در ASP.NET Core 7x می‌توان با پیاده سازی اینترفیس IParsable به صورت خودکار و با روشی دیگر به این مقصود رسید:
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace NET7Mvc.Models;

public class Days : IParsable<Days>
{
    public Days(int from, int to)
    {
        From = from;
        To = to;
    }

    public int From { get; }
    public int To { get; }

    public static bool TryParse([NotNullWhen(true)] string? value,
                                IFormatProvider? provider,
                                [MaybeNullWhen(false)] out Days result)
    {
        var items = value?.Split('-', StringSplitOptions.RemoveEmptyEntries);
        if (items is { Length: 2 })
        {
            if (int.TryParse(items[0], NumberStyles.None, provider, out var from)
                && int.TryParse(items[1], NumberStyles.None, provider, out var to))
            {
                result = new Days(from, to);
                return true;
            }
        }

        result = default;
        return false;
    }

    public static Days Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
            throw new ArgumentException("Could not parse the given value.", nameof(value));
        }

        return result;
    }
}
- برای پیاده سازی این اینترفیس باید دو متد TryParse و Parse آن‌را به صورت فوق پیاده سازی کرد و توسط آن، روش تبدیل رشته‌ی دریافتی از کاربر را به شیء مدنظر، مشخص کرد.
- همینقدر که مدلی IParsable را پیاده سازی کرده باشد، از امکانات آن به صورت خودکار استفاده خواهد شد و نیازی به معرفی و یا تنظیمات خاص دیگری ندارد.
- البته این قابلیت جدید نیست و پشتیبانی از IParsable، پیشتر در Minimal API دات نت 6 ارائه شده بود؛ اما در دات نت 7 توسط ASP.NET Core MVC نیز قابل استفاده شده‌است.
مطالب
Tuple در دات نت 4

نوع جدیدی در دات نت 4 به نام Tuple اضافه شده است که در این مطلب به بررسی آن خواهیم پرداخت.
در ریاضیات، Tuple به معنای لیست مرتبی از اعضاء با تعداد مشخص است. Tuple در زبان‌های برنامه نویسی Dynamic مانند اف شارپ، Perl ، LISP و بسیاری موارد دیگر مطلب جدیدی نیست. در زبان‌های dynamic برنامه نویس‌ها می‌توانند متغیرها را بدون معرفی نوع آن‌ها تعریف کنند. اما در زبان‌های Static مانند سی شارپ، برنامه نویس‌ها موظفند نوع متغیرها را پیش از کامپایل آن‌ها معرفی کنند که هر چند کار کد نویسی را اندکی بیشتر می‌کند اما به این صورت شاهد خطاهای کمتری نیز خواهیم بود (البته سی شارپ 4 این مورد را با معرفی واژه‌ی کلیدی dynamic تغییر داده است).
برای مثال در اف شارپ داریم:
let data = (“John Doe”, 42)

که سبب ایجاد یک tuple که المان اول آن یک رشته و المان دوم آن یک عدد صحیح است می‌شود. اگر data را بخواهیم نمایش دهیم خروجی آن به صورت زیر خواهد بود:
printf “%A” data
// Output: (“John Doe”,42)

در دات نت 4 فضای نام جدیدی به نام System.Tuple معرفی شده است که در حقیقت ارائه دهنده‌ی نوعی جنریک می‌باشد که توانایی در برگیری انواع مختلفی را دارا است :
public class Tuple<T1>
up to:
public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>

همانند آرایه‌ها، اندازه‌ی Tuples نیز پس از تعریف قابل تغییر نیستند (immutable). اما تفاوت مهم آن با یک آرایه در این است که اعضای آن می‌توانند نوع‌های کاملا متفاوتی داشته باشند. همچنین تفاوت مهم آن با یک ArrayList یا آرایه‌ای از نوع Object، مشخص بودن نوع هر یک از اعضاء آن است که type safety بیشتری را به همراه خواهد داشت و کامپایلر می‌تواند در حین کامپایل دقیقا مشخص نماید که اطلاعات دریافتی از نوع صحیحی هستند یا خیر.

یک مثال کامل از Tuples را در کلاس زیر ملاحظه خواهید نمود:

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

namespace TupleTest
{
class TupleCS4
{
#region Methods (4)

// Public Methods (4)

public static Tuple<string, string> GetFNameLName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new NullReferenceException("name is empty.");

var nameParts = name.Split(',');

if (nameParts.Length != 2)
throw new FormatException("name must contain ','");

return Tuple.Create(nameParts[0], nameParts[1]);
}

public static void PrintSelectedTuple()
{
var list = new List<Tuple<string, int>>
{
new Tuple<string, int>("A", 1),
new Tuple<string, int>("B", 2),
new Tuple<string, int>("C", 3)
};

var item = list.Where(x => x.Item2 == 2).SingleOrDefault();
if (item != null)
Console.WriteLine("Selected Item1: {0}, Item2: {1}",
item.Item1, item.Item2);
}

public static void PrintTuples()
{
var tuple1 = new Tuple<int>(12);
Console.WriteLine("tuple1 contains: item1:{0}", tuple1.Item1);

var tuple2 = Tuple.Create("Item1", 12);
Console.WriteLine("tuple2 contains: item1:{0}, item2:{1}",
tuple2.Item1, tuple2.Item2);

var tuple3 = Tuple.Create(new DateTime(2010, 5, 6), "Item2", 20);
Console.WriteLine("tuple3 contains: item1:{0}, item2:{1}, item3:{2}",
tuple3.Item1, tuple3.Item2, tuple3.Item3);
}

public static void Tuple8()
{
var tup =
new Tuple<int, int, int, int, int, int, int, Tuple<int, int>>
(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int>(8, 9));

Console.WriteLine("tup.Rest Item1: {0}, Item2: {1}",
tup.Rest.Item1,tup.Rest.Item2);
}

#endregion Methods
}
}

using System;

namespace TupleTest
{
class Program
{
static void Main()
{
var data = TupleCS4.GetFNameLName("Vahid, Nasiri");
Console.WriteLine("Data Item1:{0} & Item2:{1}",
data.Item1, data.Item2);

TupleCS4.PrintTuples();

TupleCS4.PrintSelectedTuple();

TupleCS4.Tuple8();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

توضیحات :
- روش‌های متفاوت ایجاد Tuples را در متد PrintTuples می‌توانید ملاحظه نمائید. همچنین نحوه‌ی دسترسی به مقادیر هر کدام از اعضاء نیز مشخص شده است.
- کاربرد مهم Tuples در متد GetFNameLName نمایش داده شده است؛ زمانیکه نیاز است تا چندین خروجی از یک تابع داشته باشیم. به این صورت دیگر نیازی به تعریف آرگومان‌هایی به همراه واژه کلیدی out نخواهد بود یا دیگر نیازی نیست تا یک شیء جدید را ایجاد کرده و خروجی را به آن نسبت دهیم. به همان سادگی زبان‌های dynamic در اینجا نیز می‌توان یک tuple را ایجاد و استفاده کرد.
- بدیهی است از Tuples در یک لیست جنریک و یا حالات دیگر نیز می‌توان استفاده کرد. مثالی از این دست را در متد PrintSelectedTuple ملاحظه خواهید نمود. ابتدا یک لیست جنریک از Tuple ایی با دو عضو تشکیل شده است. سپس با استفاده از امکانات LINQ ، عضوی که آیتم دوم آن مساوی 2 است یافت شده و سپس المان‌های آن نمایش داده می‌شود.
- نکته‌ی دیگری را که حین کار با Tuples می‌توان در نظر داشت این است که اعضای آن حداکثر شامل 8 عضو می‌توانند باشند که عضو آخر باید یک Tuple تعریف گردد و بدیهی است این Tuple‌ نیز می‌تواند شامل 8 عضو دیگر باشد و الی آخر که نمونه‌ای از آن را در متد Tuple8 می‌توان مشاهده کرد.

مطالب
تهیه خروجی XML از یک بانک اطلاعاتی، توسط EF Code first
نگارش کامل SQL Server امکان تهیه خروجی XML از یک بانک اطلاعاتی را دارد. اما اگر بخواهیم از سایر بانک‌های اطلاعاتی که چنین توابع توکاری ندارند، استفاده کنیم چطور؟ برای تهیه خروجی XML توسط Entity framework و مستقل از نوع بانک اطلاعاتی در حال استفاده، حداقل دو روش وجود دارد:

الف) استفاده از امکانات Serialization توکار دات نت

using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace DNTViewer.Common.Toolkit
{
    public static class Serializer
    {
        public static string Serialize<T>(T type)
        {
            var serializer = new XmlSerializer(type.GetType());
            using (var stream = new MemoryStream())
            {
                serializer.Serialize(stream, type);
                stream.Seek(0, SeekOrigin.Begin);
                using (var reader = new StreamReader(stream))
                {
                    return reader.ReadToEnd();
                }
            }
        }
    }
}
در اینجا برای نمونه، لیستی از اشیاء مدنظر خود را تهیه کرده و به متد Serialize فوق ارسال کنید. نتیجه کار، تهیه معادل XML آن است.
امکانات سفارشی سازی محدودی نیز برای XmlSerializer درنظر گرفته شده است؛ برای نمونه قرار دادن ویژگی‌هایی مانند XmlIgnore بالای خواصی که نیازی به حضور آن‌ها در خروجی نهایی XML نمی‌باشد.


ب) استفاده از امکانات LINQ to XML دات نت

روش فوق بدون مشکل کار می‌کند، اما اگر بخواهیم قسمت Reflection خودکار ثانویه آن‌را (برای نمونه جهت استخراج مقادیر از لیست دریافتی) حذف کنیم، می‌توان از LINQ to XML استفاده کرد که قابلیت سفارشی سازی بیشتری را نیز در اختیار ما قرار می‌دهد (کاری که در سایت جاری برای تهیه خروجی XML از بانک اطلاعاتی آن انجام می‌شود).

        private string createXmlFile(string dir)
        {
            var xLinq = new XElement("ArrayOfPost",
                        _blogPosts
                            .AsNoTracking()
                            .Include(x => x.Comments)
                            .Include(x => x.User)
                            .Include(x => x.Tags)
                            .OrderBy(x => x.Id)
                            .ToList()
                            .Select(x => new XElement("Post", postXElement(x)))
                            );

            var xmlFile = Path.Combine(dir, "dot-net-tips-database.xml");
            xLinq.Save(xmlFile);
            return xmlFile;
        }

        private static XElement[] postXElement(BlogPost x)
        {
            return new XElement[]
            {
                new XElement("Id", x.Id),
                new XElement("Title", x.Title),
                new XElement("Body", x.Body),
                new XElement("CreatedOn", x.CreatedOn),
                tagElement(x),
                new XElement("User",
                                new XElement("Id", x.UserId.Value),
                                new XElement("FriendlyName", x.User.FriendlyName))
            }.Where(item => item != null).ToArray();
        }

        private static XElement tagElement(BlogPost x)
        {
            var tags = x.Tags.Any() ?
                            x.Tags.Select(y =>
                                        new XElement("Tag",
                                                new XElement("Id", y.Id),
                                                new XElement("Name", y.Name)))
                                  .ToArray() : null;
            if (tags == null)
                return null;

            return new XElement("Tags", tags);
        }
خلاصه‌ای از نحوه تبدیل اطلاعات لیستی از مطالب را به معادل XML آن در کدهای فوق مشاهده می‌کنید. یک سری نکات ریز نیز باید در اینجا رعایت شوند:
1) کار با یک new XElement که دارای متد Save با فرمت XML نیز هست، شروع می‌شود. مقدار آن‌را مساوی یک کوئری از بانک اطلاعاتی قرار می‌دهیم. این کوئری چون قرار است تنها اطلاعاتی را از بانک اطلاعاتی دریافت کند و نیازی به تغییر در آن‌ها نیست، با استفاده از متد AsNoTracking، حالت فقط خواندنی پیدا کرده است.
2) اطلاعاتی را که نیاز است در فایل نهایی XML وجود داشته باشند، تنها کافی است در قسمت Select این کوئری با فرمت new XElement‌های تو در تو قرار دهیم. به این ترتیب قسمت Relection خودکار XmlSerializer روش مطرح شده در ابتدای بحث دیگر وجود نداشته و عملیات نهایی بسیار سریعتر خواهد بود.
3) چون در این حالت، کار انجام شده دستی است، باید نام‌های گره‌های صحیحی را انتخاب کنیم تا اگر قرار است توسط همان XmlSerializer مجددا کار serializer.Deserialize صورت گیرد، عملیات با شکست مواجه نشود. بهترین کار برای کم شدن سعی و خطاها، تهیه یک لیست اطلاعات آزمایشی و سپس ارسال آن به روش ابتدای بحث است. سپس می‌توان با بررسی خروجی آن مثلا دریافت که روش serializer.Deserialize به صورت پیش فرض به دنبال ریشه‌ای به نام ArrayOfPost برای دریافت لیستی از مطالب می‌گردد و نه Posts یا هر نام دیگری.
4) در کوئری LINQ to Entites نوشته شده، پیش از Select، یک ToList قرار دارد. متاسفانه EF اجازه استفاده مستقیم از Select هایی از نوع XElement را نمی‌دهد و باید ابتدا اطلاعات را تبدیل به LINQ to Objects کرد.
5) در حین تهیه XElement‌ها اگر قرار است عنصری نال باشد، باید آن‌را در خروجی نهایی ذکر نکرد. به این ترتیب serializer.Deserialize بدون نیاز به تنظیمات اضافه‌تری بدون مشکل کار خواهد کرد. در غیراینصورت باید وارد مباحثی مانند تعریف یک فضای نام جدید برای خروجی XML به نام XSI رفت و سپس به کمک ویژگی‌ها، xsi:nil را به true مقدار دهی کرد. اما همانطور که در متد postXElement ملاحظه می‌کنید، برای وارد نشدن به مبحث فضای نام xsi، مواردی که null بوده‌اند، اصلا در آرایه نهایی ظاهر نمی‌شوند و نهایتا در خروجی، حضور نخواهند داشت. به این ترتیب متد ذیل، بدون مشکل و بدون نیاز به تنظیمات اضافه‌تری قادر است فایل XML نهایی را تبدیل به معادل اشیاء دات نتی آن کند.

using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace DNTViewer.Common.Toolkit
{
    public static class Serializer
    {
        public static T DeserializePath<T>(string xmlAddress)
        {
            using (var xmlReader = new XmlTextReader(xmlAddress))
                {
                    var serializer = new XmlSerializer(typeof(T));
                    return (T)serializer.Deserialize(xmlReader);
                }
        }
    }
}
مطالب
آشنایی با Refactoring - قسمت 14

در بسیاری از زبان‌های برنامه نویسی امکان null بودن Reference types وجود دارد. به همین جهت مرسوم است پیش از استفاده از آن‌ها، بررسی شود آیا شیء مورد استفاده نال است یا خیر و سپس برای مثال متد یا خاصیت مرتبط با آن فراخوانی گردد؛ در غیر اینصورت برنامه با یک استثناء خاتمه خواهد یافت.
مشکلی هم که با این نوع بررسی‌ها وجود دارد این است که پس از مدتی کد موجود را تبدیل به مخزنی از انبوهی از if و else ها خواهند کرد که هم درجه‌ی پیچیدگی متدها را افزایش می‌دهند و هم نگهداری ‌آن‌ها را در طول زمان مشکل می‌سازند. برای حل این مساله، الگوی استانداردی وجود دارد به نام null object pattern؛ به این معنا که بجای بازگشت دادن null و یا سبب بروز یک exception شدن، بهتر است واقعا مطابق شرایط آن متد یا خاصیت، «هیچ‌کاری» را انجام نداد. در ادامه، توضیحاتی در مورد نحوه‌ی پیاده سازی این «هیچ‌کاری» را انجام ندادن، ارائه خواهد شد.


الف) حین معرفی خاصیت‌ها از محافظ استفاده کنید.

برای مثال اگر قرار است خاصیتی به نام Name را تعریف کنید که از نوع رشته‌ است، حالت امن آن رشته بجای null بودن، «خالی» بودن است. به این ترتیب مصرف کننده مدام نگران این نخواهد بود که آیا الان این Name نال است یا خیر. مدام نیاز نخواهد داشت تا if و else بنویسد تا این مساله را چک کند. نحوه پیاده سازی آن هم ساده است و در ادامه بیان شده است:

private string name = string.Empty;
public string Name
{
    get { return this.name; }
    set
    {
        if (value == null)
        {
            this.name = "";
            return;
        }
        this.name = value;
    }
}

دقیقا در زمان انتساب مقداری به این خاصیت، بررسی می‌شود که آیا مثلا null است یا خیر. اگر بود، همینجا و نه در کل برنامه، مقدار آن «خالی» قرار داده می‌شود.

ب) سعی کنید در متدها تا حد امکان null بازگشت ندهید.

برای نمونه اگر متدی قرار است لیستی را بازگشت دهد:

public IList<string> GetCultures()
{
//...
}

و حین تهیه این لیست، عضوی مطابق منطق پیاده سازی آن یافت نشد، null را بازگشت ندهید؛ یک new List خالی را بازگشت دهید. به این ترتیب مصرف کننده دیگری نیازی به بررسی نال بودن خروجی این متد نخواهد داشت.


ج) از متدهای الحاقی بجای if و else استفاده کنید.

پیاده سازی حالت الف زمانی میسر خواهد بود که طراح اصلی ما باشیم و کدهای برنامه کاملا در اختیار ما باشند. اما در شرایطی که امکان دستکاری کدهای یک کتابخانه پایه را نداریم چه باید کرد؟ مثلا دسترسی به تعاریف کلاس XElement دات نت فریم ورک را نداریم (یا حتی اگر هم داریم، تغییر آن تا زمانیکه در کدهای پایه اعمال نشده باشد، منطقی نیست). در این حالت می‌شود یک یا چند extension method را طراحی کرد:

public static class LanguageExtender
{
public static string GetSafeStringValue(this XElement input)
{
return (input == null) ? string.Empty : input.Value;
}

public static DateTime GetSafeDateValue(this XElement input)
{
return (input == null) ? DateTime.MinValue : DateTime.Parse(input.Value);
}
}

به این ترتیب می‌توان امکانات کلاس پایه‌‌ای را بدون نیاز به دسترسی به کدهای اصلی آن مطابق نیاز‌های خود تغییر و توسعه داد.


پاسخ به بازخورد‌های پروژه‌ها
توضیح گام های اجرا شده در پروژه
سلام؛
هدف از انجام این پروژه برای من چسباندن قطعات مختلف یک پازل به هم بودند تا بتوان به یک تصویر خوب رسید.منظور من این است که entity framework و ASP.NET MVC و bootstrap و best practice‌های آن‌ها به تنهایی و جدا از هم به نظر ساده و راحت و خوب بیایند، اما درگیر شدن همه‌ی آن‌ها در یک پروژه‌ی واقعی، واقعا چالش بر انگیز است.
من دانشجو هستم و تقریبا استارت این پروژه را از آبان ماه زدم، اما به دلیل یک سری مشکلات از جمله همین دانشجو بودن، کار به کندی پیش رفت و حتی وقفه‌های چند ماهه در آن پیش اومد. هدف من این بود که اساسا یک سیستم با کیفیت بنویسم و در ابتدای کار هم، کار به خوبی پیش می‌رفت، اما با توجه به مشکلات ذکر شده، عمده کار کدنویسی در تعطیلات عید نوروز صورت گرفت، و کاملا از کدنویسی انجام شده مشهود است ک ههمان قسمت هایی که در عید نوروز کدنویسی شده اند، اصطلاحا سرهم بندی شده اند( به خصوص در کدهای سمت کلاینت)
در مورد گام‌های انجام شده؛ پروژه به این منوال انجام شد:
- تحلیل ساختار بانک اطلاعاتی مورد نیاز
- شروع به تحقیق در مورد امکانات مورد نیاز 
- دعوت همکای برای کار گروهی توسط دوستان ( کسی قبول نکرد البته دی:)
- با توجه به محدودیت‌های یافت شده در تحقیقات، ساختار بانک اطلاعاتی نهایی می‌شود.
- انتخاب فریم ورک‌های مناسب( که در اینجا Entity Framework برای orm و ASP.NET MVC برای کدنویسی سمت سرور و bootstrap برای css و jquery هم برای جاوا اسکریپت)
- تحقیق در مورد best practice‌های موجود در مورد هر یک از فریم ورک‌های فوق
-شروع کدنویسی
در مورد قسمت مدیریت کاربران، هدف طراحی یک سیستم خیلی منعطف بود که قطعا با memebrship خود دات نت امکان پذیر نبود. متاسفانه به دلیل مشکلات پیش اومده این قسمت از پروژه هم سرهم بندی کردم و به یک سیستم ساده اکتفا کردم.
برای پیاده سازی آن هم شما کافیست در گوگل عبارت implement custom membership in asp.net mvc را سرچ کنید. مطمئن باشید کلی مطلب پیدا خوهید کرد که با جمع بندی آن یک سیستم خوب می‌توانید پیاده سازی کنید.
الان همین سیستم پیاده سازی شده در سایت یک باگ دارد که بعد از مدتی remember me آ از کار می‌افتد.کوکی کاربر اعتبار دارد، اما رویداد متناظر آن برای اعتبار سنجی اتفاق نمی‌افتد!
الان هم در حال تحقیق برای پیاده سازی یک سیستم اعتبارسنجی  کامل‌تر و اصولی‌تر و یک پارچه‌تر با ASP.NET MVC  هستم که مقاله‌ی زیر خیلی به من کمک کرد.(امیدوارم برای شما هم مفید باشد)
الان هم برنامه ای برای ارتقا این سیستم دارم و مهمترین تغییر آن را می‌توان به استفاده از angularjs برای نوشتن بخش مدیریتی و پیاده سازی آن به صورت single page دانست.( البته اگر این کمردرد بزاره دی:)
امیدوارم دوستان با بازخوردهای خوب خودشون، در ارتقای سطح کیفی کار کمک کنند.
اشتراک‌ها
واچک

«... مرجعی برای برگردان‌های فارسی در حوزه نرم‌افزار ...»

واچک
مطالب
بررسی تغییرات Blazor 8x - قسمت سوم - روش ارتقاء برنامه‌های Blazor Server قدیمی به دات نت 8
در قسمت قبل، با نحوه‌ی رندر سمت سرور و روش فعالسازی قابلیت‌های تعاملی در این حالت، آشنا شدیم. از این نکات می‌توان جهت ارتقاء ساختار پروژه‌های قدیمی Blazor Server به Blazor Server 8x استفاده کرد. البته همانطور که پیشتر نیز عنوان شد، در دات نت 8 دیگر خبری از قالب‌های قدیمی پروژه‌های blazor server و blazor wasm نیست و اگر دقیقا همین موارد مدنظر هستند، آن‌ها را می‌توان با تنظیم سطح رندر و میزان تعاملی که مدنظر است، شبیه سازی کرد و یا حتی هر دو را هم با هم در یک پروژه داشت.


1) به‌روز رسانی شماره نگارش دات‌نت

اولین قدم در جهت ارتقاء پروژه‌های قدیمی، تغییر شماره نگارش TargetFramework موجود در فایل csproj. به net8.0 است. پس از اینکار نیاز است تمام بسته‌های نیوگت موجود را نیز به نگارش‌های جدیدتر آن‌ها ارتقاء دهید.


2) فعالسازی حالت SSR تعاملی سمت سرور

پایه‌ی تمام تغییرات انجام شده‌ی در Blazor 8x، قابلیت SSR است و تمام امکانات دیگر برفراز آن اجرا می‌شوند. به همین جهت پس از ارتقاء شماره نگارش دات‌نت، نیاز است SSR را فعال کنیم و برای اینکار باید به هاست ASP.NET Core بگوئیم که درخواست‌های رسیده را به کامپوننت‌های Razor هدایت کند. بنابراین، به فایل Program.cs مراجعه کرده و دو تغییر زیر را به آن اعمال کنید:
// ...
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
// ...
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
یک نمونه‌ی کامل از فایل Program.cs را در قسمت قبل مشاهده کردید و یا حتی می‌توانید دستور dotnet new blazor --interactivity Server را جهت ساخت یک پروژه‌ی آزمایشی جدید بر اساس SDK دات نت 8 و ایده گیری از آن، اجرا کنید.

در اینجا ترکیب کامپوننت‌های تعاملی سمت سرور (AddInteractiveServerComponents) و رندر تعاملی سمت سرور (AddInteractiveServerRenderMode)، دقیقا همان Blazor Server قدیمی است که ما با آن آشنا هستیم.

یک نکته: اگر از قالب جدید dotnet new blazor --interactivity None استفاده کنیم، یعنی حالت تعاملی بودن آن‌را به None تنظیم کنیم، کلیات ساختار پروژه‌ای را که مشاهده خواهیم کرد، با حالت تعاملی Server آن یکی است؛ فقط در تنظیمات Program.cs آن، گزینه‌های فوق را نداریم و به صورت زیر ساده شده‌است:
// ...

builder.Services.AddRazorComponents();

// ...

app.MapRazorComponents<App>();
در این نوع برنامه‌ها نمی‌توان جزایر/قسمت‌های تعاملی Blazor Server را در صفحات و کامپوننت‌های SSR، تعریف کرد. در مورد جزایر تعاملی، در مطالب بعدی بیشتر بحث خواهیم کرد.


3) ایجاد فایل جدید App.razor

در دات نت 8، دیگر خبری از فایل آغازین Host.cshtml_ پروژه‌های Blazor Server قدیمی نیست و کدهای آن با تغییراتی، به فایل جدید App.razor منتقل شده‌اند. در این قسمت، کار هدایت درخواست‌های رسیده به کامپوننت‌های برنامه رخ می‌دهد و از این پس، صفحه‌ی ریشه‌ی برنامه خواهد بود.


در این تصویر، مقایسه‌ای را بین جریان پردازش یک درخواست رسیده در دات نت 8، با نگارش قبلی Blazor Server مشاهده می‌کنید. در دات نت 8، فایل Host.cshtml_ (یک Razor Page آغازین برنامه) با یک کامپوننت Razor به نام App.razor جایگزین شده‌است و فایل قدیمی App.razor این پروژه‌ها به Routes.razor، تغییر نام یافته‌است.

نمونه‌ای از فایل 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>
در این فایل جدید تغییرات زیر رخ داده‌اند:
- تمام دایرکتیوهای تعریف شده مانند page ،@addTagHelper@ و غیره حذف شده‌اند.
- base href تعریف شده اینبار فقط با یک / شروع می‌شود و نه با /~. این مورد خیلی مهم است! اگر به آن دقت نکنید، هیچکدام از فایل‌های استاتیک برنامه مانند فایل‌های css. و js.، بارگذاری نخواهند شد!
- پیشتر برای رندر HeadOutlet، از یک تگ‌هلپر استفاده می‌شد. این مورد در نگارش جدید با یک کامپوننت ساده جایگزین شده‌است.
- تمام component tag helper‌های پیشین حذف شده‌اند و نیازی به آن‌ها نیست.
- ارجاع پیشین فایل blazor.server.js با فایل جدید blazor.web.js جایگزین شده‌است.

یک نکته: همانطور که مشاهده می‌کنید، فایل App.razor یک کامپوننت است و اینبار به همراه تگ <script> نیز شده‌است. یعنی در این نگارش از Blazor می‌توان اسکریپت‌ها را در کامپوننت‌ها نیز ذکر کرد؛ فقط با یک شرط! این کامپوننت حتما باید SSR باشد. اگر این تگ اسکریپتی را در یک کامپوننت تعاملی ذکر کنید، همانند قابل (و نگارش‌های پیشین Blazor) با خطا مواجه خواهید شد.


4) ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده

همانطور که عنوان شد، فایل قدیمی App.razor این پروژه‌ها به Routes.razor تغییر نام یافته‌است که درج آن‌را در قسمت body مشاهده می‌کنید. محتوای این فایل نیز به صورت زیر است:
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
</Router>
این محتوای جدید، فاقد ذکر کامپوننت NotFound قبلی است؛ از این جهت که سیستم مسیریابی جدید Blazor 8x با ASP.NET Core 8x یکپارچه است و نیازی به این کامپوننت نیست. یعنی اگر قصد مدیریت آدرس‌های یافت نشده‌ی برنامه را دارید، باید همانند ASP.NET Core به صورت زیر در فایل Program.cs عمل کرده:
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
و سپس کامپوننت جدید StatusCode.razor را برای مدیریت آن به نحو زیر به برنامه اضافه کنید و بر اساس responseCode دریافتی، واکنش‌های متفاوتی را ارائه دهید:
@page "/StatusCode/{responseCode}"

<h3>StatusCode @ResponseCode</h3>

@code {
    [Parameter] public string? ResponseCode { get; set; }
}

یک نکته: اگر پروژه‌ای را بر اساس قالب dotnet new blazor --interactivity Server ایجاد کنیم، در فایل Program.cs آن، چنین تنظیمی اضافه شده‌است:
if (!app.Environment.IsDevelopment())
{
   app.UseExceptionHandler("/Error", createScopeForErrors: true);
}
که دقیقا معادل رفتاری است که در برنامه‌های ASP.NET Core قابل مشاهده‌است. این مسیر Error/، به کامپوننت جدید Components\Pages\Error.razor نگاشت می‌شود. بنابراین اگر در برنامه‌های جدید Blazor Server، استثنائی رخ دهد، با استفاده از میان‌افزار ExceptionHandler فوق، کامپوننت Error.razor نمایش داده خواهد شد.  باید دقت داشت که این کامپوننت ویژه، تحت هر شرایطی در حالت یک static server component رندر می‌شود.

سؤال: در اینجا (برنامه‌های Blazor Server) چه تفاوتی بین UseExceptionHandler و UseStatusCodePagesWithRedirects وجود دارد؟
میان‌افزار UseExceptionHandler برای مدیریت استثناءهای آغازین برنامه، پیش از تشکیل اتصال دائم SignalR وارد عمل می‌شود. پس از آن و تشکیل اتصال وب‌سوکت مورد نیاز، فقط از میان‌افزار UseStatusCodePagesWithRedirects استفاده می‌کند.
اگر علاقمند نیستید تا تمام خطاهای رسیده را همانند مثال فوق در یک صفحه مدیریت کنید، می‌توانید حداقل سه فایل زیر را به برنامه اضافه کنید تا خطاهای متداول یافت نشدن آدرسی، بروز خطایی و یا عدم دسترسی را مدیریت کنند:

404.razor
@page "/StatusCode/404"

<PageTitle>Not found</PageTitle>

<h1>Not found</h1>
<p role="alert">Sorry, there's nothing at this address.</p>

500.razor
@page "/StatusCode/500"

<PageTitle>Unexpected error</PageTitle>

<h1>Unexpected error</h1>
<p role="alert">There was an unexpected error.</p>

401.razor
@page "/StatusCode/401"

<PageTitle>Not Authorized</PageTitle>

<h1>Not Authorized</h1>
<p role="alert">Sorry, you are not authorized to access this page.</p>


5) تعاملی کردن سراسری برنامه

پس از این تغییرات اگر برنامه را اجرا کنید، بر اساس روش جدید static server-side rendering کار می‌کند و تعاملی نیست. یعنی تمام کامپوننت‌های آن به صورت پیش‌فرض، یکبار بر روی سرور رندر شده و خروجی آن‌ها به مرورگر کاربر ارسال می‌شوند و هیچ اتصال دائم SignalR ای برقرار نخواهد شد. برای فعالسازی سراسری قابلیت‌های تعاملی برنامه و بازگشت به حالت Blazor Server قبلی، به فایل App.razor مراجعه کرده و دو تغییر زیر را اعمال کنید تا به صورت خودکار به تمام زیرکامپوننت‌ها، یعنی کل برنامه، اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" />
...
<Routes @rendermode="@InteractiveServer" />

نکته 1: اجرای دستور زیر در دات‌نت 8، قالب پروژه‌ای را ایجاد می‌کند که رفتار آن همانند پروژه‌های Blazor Server نگارش‌های قبلی دات‌نت است (این مورد را در قسمت قبل بررسی کردیم)؛ یعنی همه‌جای آن به صورت پیش‌فرض، تعاملی است:
dotnet new blazor --interactivity Server --all-interactive

نکته 2: البته ...  InteractiveServer، دقیقا همان حالت پیش‌فرض برنامه‌های Blazor Server قبلی نیست! این حالت رندر، به صورت پیش‌فرض به همراه پیش‌رندر (pre-rendering) هم هست. یعنی در این حالت، روال رویدادگردان OnInitializedAsync یک کامپوننت، دوبار فراخوانی می‌شود (که باید به آن دقت داشت و عدم توجه به آن می‌تواند سبب انجام دوباره‌ی کارهای سنگین آغازین یک کامپوننت شود)؛ یکبار برای پیش‌رندر صفحه به صورت یک HTML استاتیک (بدون فعال سازی هیچ قابلیت تعاملی) که برای موتورهای جستجو و بهبود SEO مفید است و بار دیگر برای فعالسازی قسمت‌های تعاملی آن، درست پس از زمانیکه اتصال SignalR صفحه، برقرار شد (البته امکان فعالسازی حالت پیش‌رندر در Blazor Server قبلی هم وجود داشت؛ ولی مانند Blazor 8x، به صورت پیش‌فرض فعال نبود). در صورت نیاز، برای سفارشی سازی و لغو آن می‌توان به صورت زیر عمل کرد:
@rendermode InteractiveServerRenderModeWithoutPrerendering

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

در مورد پیش‌رندر و روش مدیریت دوبار فراخوانی شدن روال رویدادگردان OnInitializedAsync یک کامپوننت در این حالت، در قسمت‌های بعدی این سری بیشتر بحث خواهد شد.
 
مطالب
بررسی تغییرات HttpClient در NET 5.0.
پیشتر بسته‌ی نیوگتی به نام Microsoft.AspNet.WebApi.Client وجود داشت/دارد که کار آن ارائه‌ی یک سری متد الحاقی کار با JSON، جهت HttpClient است. در نگارش 5 دات نت، تمام این متدهای الحاقی جزئی از دات نت استاندارد شده‌اند و برای کار با آن‌ها دیگر نیازی به استفاده‌ی از بسته‌های نیوگت خاصی نیست.


تغییرات API دات نت 5 از دیدگاه افزونه‌های HttpClient

در اینجا لیست کامل متدهای الحاقی اضافه شده‌ی به فضای نام جدید و استاندارد System.Net.Http.Json را مشاهده می‌کنید:
namespace System.Net.Http.Json {
    public static class HttpClientJsonExtensions {
        public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken);
    }

    public static class HttpContentJsonExtensions {
        public static Task<object> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<T> ReadFromJsonAsync<T>(this HttpContent content, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
    }

    public sealed class JsonContent : HttpContent {
        public Type ObjectType { get; }
        public object Value { get; }
        public static JsonContent Create(object inputValue, Type inputType, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null);
        public static JsonContent Create<T>(T inputValue, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null);
        protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context);
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken);
        protected override bool TryComputeLength(out long length);
    }
}


متدهای الحاقی جدید کلاس HttpClientJsonExtensions

این متدها به صورت خلاصه شامل سه متد زیر می‌شوند:
- GetFromJsonAsync : یک درخواست Get را به آدرسی خاص ارسال کرده و خروجی JSON دریافتی را به کمک امکانات توکار System.Text.Json، پردازش و deserialize می‌کند.
- PostAsJsonAsync : یک درخواست POST را به آدرسی خاص، ارسال می‌کند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال می‌گردد.
- PutAsJsonAsync : یک درخواست PUT را به آدرسی خاص، ارسال می‌کند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال می‌گردد.

در ذیل چند مثال را در مورد نحوه‌ی کار با این متدهای الحاقی جدید فضای نام استاندارد System.Net.Http.Json، مشاهده می‌کنید:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:5000");

var profiles = await httpClient.GetFromJsonAsync<Profile[]>("api/users/profiles");

var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 };
using var response1 = await httpClient.PostAsJsonAsync("api/users/profiles", profile);
response1.EnsureSuccessStatusCode();


var updatedProfile = new Profile { FirstName = "User 2", LastName = "Name 2", Age = 40 };
using var response2 = await httpClient.PutAsJsonAsync("api/users/profiles", profile);
response2.EnsureSuccessStatusCode();

اگر می‌خواستیم یک چنین کارهایی را پیش از دات نت 5 انجام دهیم، می‌بایستی قسمت Serialize کردن و همچنین تنظیم content-type را دستی انجام می‌دادیم:
var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 };
var json = JsonSerializer.Serialize(profile);
var stringContent = new StringContent(json, Encoding.UTF8, "application/json");
using var response4 = await httpClient.PostAsync("api/users/profiles", stringContent);
response4.EnsureSuccessStatusCode();


متدهای الحاقی جدید کلاس HttpContentJsonExtensions

این کلاس، متد الحاقی جدید ReadFromJsonAsync را ارائه می‌دهد که کار آن، خواندن یک محتوای HTTP از نوع HttpContent و deserialize آن به صورت JSON است. یک مثال:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:5000");

var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles");
using var response1 = await httpClient.SendAsync(request);
if (response1.IsSuccessStatusCode)
{
  var profiles = await response1.Content.ReadFromJsonAsync<Profile[]>();
}

انجام اینکار در نگارش‌های پیشین دات نت، نیاز به فراخوانی دستی JsonSerializer.DeserializeAsync را دارد:
var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles");
using var response2 = await httpClient.SendAsync(request);
if (response2.IsSuccessStatusCode)
{
   using var streamResult = await response2.Content.ReadAsStreamAsync();
   var profiles = JsonSerializer.DeserializeAsync<Profile[]>(streamResult);
}


متدهای جدید کلاس JsonContent

روش‌های زیادی برای کار با HttpClient وجود دارند. یک روش آن، ساخت دستی HttpRequestMessage و سپس ارسال آن توسط متد SendAsync است؛ بجای استفاده از متد PostAsJsonAsync که بررسی شد. در این حالت با استفاده از متد جدید JsonContent.Create، می‌توان کار تبدیل یک شیء را به JSON و همچنین تنظیم content-type را به صورت خودکار انجام داد:
var httpClient = new HttpClient();
var uri = "https://localhost:5000";
httpClient.BaseAddress = new Uri(uri);

var requestMessage = new HttpRequestMessage(HttpMethod.Post, "https://localhost:5000")
{
   Content = JsonContent.Create(new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 })
};
using var reponse1 = await httpClient.SendAsync(requestMessage);
reponse1.EnsureSuccessStatusCode();
مطالب
پشتیبانی از SIMD در دات نت 4.6
SIMD مخفف «Single Instruction, Multiple Data» است و متشکل است از تعدادی instruction پردازنده‌ها که بجای مقادیر عددی، بر روی بردارها کار می‌کنند. به این ترتیب امکان کار موازی بر روی مقادیر عددی، در سطح CPU میسر می‌شود. برای نمونه به تصویر ذیل دقت کنید:


در اینجا قرار است تک تک عناصر آرایه‌ای از اعداد، با عدد 6 جمع شوند. روش متداول آن به این صورت است که حلقه‌ای تشکیل شده و سپس تک تک عناصر این آرایه دریافت و با عدد 6 جمع می‌شوند. اما در حالت استفاده‌ی از SIMD، هربار گروهی از عناصر این آرایه به صورت یک بردار درنظر گرفته می‌شوند (Multiple Data) و سپس با برداری حاوی مقدار 6 جمع می‌شوند (Single Instruction). اینبار این عملیات به صورت موازی، بر روی گروهی از اعداد انجام می‌شود و به همین دلیل نسبت به حالت کار بر روی یک المان از آرایه در هر مرحله، سرعت بیشتری دارد.


تفاوت چندریسمانی با SIMD چیست؟

شاید عنوان کنید که با وجود امکانات چندریسمانی چه نیازی به SIMD است؟ در حالت پردازش‌های چند ریسمانی، یک یا چند کار بر روی چندین هسته‌ی CPU به صورت موازی پردازش می‌شوند، اما SIMD امکان پردازش موازی را در یک هسته‌ی CPU میسر می‌کند.


آیا CPU من از SIMD پشتیبانی می‌کند؟

SIMD instruction sets شامل افزونه‌ها‌ی ذیل است:
• MMX - MultiMedia eXtensions
• SSE - Streaming SIMD Extensions
• SSE2 - Streaming SIMD Extensions 2
• SSE3 - Streaming SIMD Extensions 3
• SSSE3 - Supplemental Streaming SIMD Extensions 3
• SSE4.1 - Streaming SIMD Extensions 4.1
• SSE4.2 - Streaming SIMD Extensions 4.2
• AES-NI - Advanced Encryption Standard New Instructions
• AVX - Advanced Vector eXtensions
اگر CPU شما حداقل یکی از این قابلیت‌ها را داشته باشد، امکان استفاده‌ی از SIMD را دارید. برای مشخص سازی آن نیز می‌توانید از برنامه‌ی معروف CPU-Z استفاده کنید:


در این برنامه، در برگه‌ی CPU آن به قسمت instructions آن دقت کنید و موارد لیست شده‌ی در آن را با افزونه‌ها‌ی فوق مقایسه نمائید.


پشتیبانی از SIMD در دات نت

با ارائه‌ی دات نت 4.6 و RyuJIT جدید آن، امکان کار با دستورات SIMD در فضای نام System.Numerics.Vectors پیش بینی شده‌است. برای کار با آن باید بسته‌ی نیوگت زیر را نصب کنید:
 PM> Install-Package System.Numerics.Vectors
در ابتدای کار باید بررسی کنید که آیا سخت افزار شما از SIMD پشتیبانی می‌کند یا خیر. خاصیت Vector.IsHardwareAccelerated بیانگر این موضوع است. اما ... این خاصیت در حال دیباگ ممکن است مساوی false باشد. برای استفاده‌ی از SIMD ، طی این مراحل ضروری است:
الف) نصب دات نت 4.6.x (دریافت دات نت 4.6.1 مخصوص یکپارچه شدن با ویژوال استودیو)
ب) به خواص پروژه‌ی جاری مراجعه کرده و platform target را بر روی x64 قرار دهید. باید دقت داشت که RyuJIT جدید، برای سیستم‌های 64 بیتی طراحی شده‌است.
ج) RyuJIT، در حالت release و انتخاب گزینه‌ی optimize code (در همان برگه‌ی خواص پروژه) است که کدهای ویژه‌ی SIMD را تولید می‌کند.
د) نصب بسته‌ی نیوگت System.Numerics.Vectors

در کل اگر برنامه را داخل دیباگر VS.NET اجرا کنید، مقدار Vector.IsHardwareAccelerated مساوی false خواهد بود. به همین جهت برنامه را در حالت release و 64 بیتی کامپایل کرده و خارج از محیط VS.NET اجرا کنید.


بررسی فضای نام جدید System.Numerics.Vectors

پشتیبانی از SIMD در دات نت به این معنا نیست که هر نوع کدی توسط RyuJIT به صورت خودکار تبدیل به SIMD instruction sets خواهد شد. برای این منظور نیاز است از نوع‌های داده‌ای خاصی به همراه متدهای مرتبط با آن‌ها استفاده کرد.
سری اول این نوع‌های جدید برداری، به شرح زیر هستند:
var vector01 = new Vector2(x: 5F, y: 15F);
var vector11 = new Vector3(x: 5F, y: 15F, z: 25F);
var vector12 = new Vector3(x: 3F, y: 5F, z: 8F);
var vector13 = new Vector4(x: 3F, y: 5F, z: 8F, w:1F);
کلاس‌های وکتور 2، 3 و 4، بردارهایی از نوع float را با اندازه‌هایی ثابت تعریف می‌کنند و بر روی 128bit SIMD registers کار می‌کنند. بر روی این کلاس‌ها، با توجه به operators overloading صورت گرفته، امکان جمع، منها، ضرب و تقسیم نیز وجود دارد و یا می‌توان از متدهای متناظر موجود در کلاس‌های آن‌ها استفاده کرد. نمونه‌ای از این عملیات را در مثال‌های ذیل مشاهده می‌کنید:
var vector3 = vector11 - vector12; //استفاده از سربارگذاری عملگرها
var vector4 = Vector3.Subtract(vector12, vector11);//ویا استفاده از متدهای متناظر
 
vector3 = vector11 * vector12;
vector4 = Vector3.Multiply(vector11, vector12);
 
vector3 = vector11 / vector12;
vector4 = Vector3.Divide(vector11, vector12);
 
vector3 = vector11 + vector12;
vector4 = Vector3.Add(vector11, vector12);
 
var areEqual = (vector11 == vector12);
 
var areNotEqual = (vector11 != vector12);
 
var array = new float[3];
vector11.CopyTo(array);
در مثال آخر مطرح شده، روشی کپی و تبدیل یک بردار، به یک آرایه‌ی هم نوع آن، ارائه شده‌است.
علاوه بر اعمال متداول ریاضی، هر کدام از کلاس‌های Vector دارای متدهای اضافی ویژه‌ای مانند محاسبه‌ی حداقل، حداکثر، جذر و غیره نیز می‌باشند:
vector3 = Vector3.Max(vector11, vector12);
vector3 = Vector3.Min(vector11, vector12);
vector3 = Vector3.SquareRoot(vector11);
vector3 = Vector3.Abs(vector11);
var dotProduct = Vector3.Dot(vector11, vector12);
برای مثال متد Max در اینجا به MAXPS instruction مخصوص پردازشگر ترجمه می‌شود.

سری دوم بردارهای قابل تعریف، از نوع <Vector<T هستند. برای مثال CPUهایی که از SSE2 پشتیبانی می‌کنند، قابلیت کار با نوع‌های داده‌ای زیر را نیز دارا هستند:
Vector<double>.Length: 2
Vector<int>.Length: 4
Vector<long>.Length: 2
Vector<float>.Length: 4
برای نمونه همان مثال ابتدای بحث را در نظر بگیرید. نسخه‌ی متداول انجام افزودن مقداری به تک تک اعضای یک آرایه به صورت زیر است:
private static int[] simpleIncrement(int[] values, int inc)
{
    var results = new int[values.Length];
    for (var i = 0; i < results.Length; i++)
    {
        results[i] = values[i] + inc;
    }
    return results;
}
بازنویسی این متد برای کار با SIMD به صورت ذیل خواهد بود:
private static int[] simdIncrement(int[] values, int inc)
{
    var vector = new Vector<int>(values);
    var vectorAddResults = vector + new Vector<int>(inc);
 
    var results = new int[values.Length];
    vectorAddResults.CopyTo(results);
    return results;
}
در اینجا یک Vector از نوع int تعریف شده و سپس بجای تشکیل یک حلقه، فقط کافی است بردار دیگری را حاوی عدد مشخص شده، به آن اضافه کنیم. در پایان برای تبدیل این بردار به آرایه‌ای از نوع int (در صورت نیاز) می‌توان از متد Copy استفاده کرد.

در مثال ذیل، نحوه‌ی انتخاب Multiple data (گروهی از اعداد، بجای تک عدد) و سپس اعمال یک تک instruction را ملاحظه می‌کنید:
var valuesIn = new float[] { 4f, 16f, 36f, 64f, 9f, 81f, 49f, 25f, 100f, 121f, 144f, 16f, 36f, 4f, 9f, 81f };
var valuesOut = new float[valuesIn.Length];
for (var i = 0; i < valuesIn.Length; i += Vector<float>.Count)
{
    var vectorIn = new Vector<float>(valuesIn, i);
    
    var vectorOut = Vector.SquareRoot(vectorIn);
    vectorOut.CopyTo(valuesOut, i);
}
در مثال فوق قصد داریم جذر تک تک عناصر آرایه‌ای را محاسبه کرده و سپس در آرایه‌ی دومی ثبت کنیم. بجای روش متداول مراجعه‌ی به تک تک عناصر آرایه‌ی ورودی، اینبار با استفاده از کلاس بردار، به اندازه‌ی طول بردار float، اطلاعات را در vectorIn ذخیره کرده و سپس به صورت یکجا به تک متد SquareRoot ارسال می‌کنیم. این متد در سمت CPU به معادل SQRTPS instruction ترجمه می‌شود و تنها یک instruction است.

یک مثال تکمیلی