مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
در مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانک‌های اطلاعاتی از پیش موجود»، نحوه‌ی مهندسی معکوس ساختار جداول و ارتباطات یک بانک اطلاعاتی از پیش موجود را به روش Code First بررسی کردیم. با توجه به رسمی بودن این ابزار، می‌توان از آن برای یافتن معادل‌های سمت بانک اطلاعاتی، در EF Core نیز استفاده کرد. برای مثال بررسی کرد، درک EF Core از بانک اطلاعاتی طراحی شده چیست و هر چند در آن مطلب عنوان شد که می‌توان با پارامتر data-annotations-- ، خروجی نهایی را بر اساس روش data-annotations، بجای Fluent API به دست آورد، اما در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها» مشاهده کردیم که بسیاری از تنظیمات پیشرفته‌ی EF Core، اساسا معادل data-annotation ایی ندارند. بنابراین بهتر است این پارامتر را فعال سازی نکنید.


تنظیمات روابط یک به چند در EF Core

همان اسکریپت ابتدای مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانک‌های اطلاعاتی از پیش موجود» را درنظر بگیرید. رابطه‌ی تعریف شده‌ی در آن از نوع one-to-many است: یک بلاگ که می‌تواند چندین مطلب را داشته باشد.


اگر EF Core را وادار به تولید نگاشت‌های Code First معادل آن کنیم، به این خروجی‌ها خواهیم رسید:
الف) با استفاده از روش Fluent API
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
 dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose
با خروجی:
using System;
using System.Collections.Generic;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Blog
    {
        public Blog()
        {
            Post = new HashSet<Post>();
        }

        public int BlogId { get; set; }
        public string Url { get; set; }

        public virtual ICollection<Post> Post { get; set; }
    }
}

using System;
using System.Collections.Generic;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Post
    {
        public int PostId { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        public virtual Blog Blog { get; set; }
        public int BlogId { get; set; }
    }
}

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Core1RtmEmptyTest.Entities
{
    public partial class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(entity =>
            {
                entity.Property(e => e.Url).IsRequired();
            });

            modelBuilder.Entity<Post>(entity =>
            {
                entity.HasOne(d => d.Blog)
                    .WithMany(p => p.Post)
                    .HasForeignKey(d => d.BlogId);
            });
        }

        public virtual DbSet<Blog> Blog { get; set; }
        public virtual DbSet<Post> Post { get; set; }
    }
}

نحوه‌ی تشخیص خودکار روابط

EF Core به صورت پیش فرض، روابط را بر اساس ارجاعات بین کلاس‌ها تشخیص می‌دهد. در اینجا به خاصیت Blog نام navigation property را می‌دهند:
 public virtual Blog Blog { get; set; }
و به خاصیت Post نیز Collection navigation property می‌گویند:
 public virtual ICollection<Post> Post { get; set; }
در اینجا اگر تنها دو navigation property، در کلاس‌های به هم مرتبط شده، یافت شوند، به صورت خودکار به عنوان دو سر رابطه تنظیم می‌شوند. اگر بیشتر از یک navigation property در کلاسی وجود داشت، هیچ رابطه‌ای به صورت خودکار تشکیل نشده و باید ابتدا و انتهای روابط را به صورت دستی مشخص نمود.


نحوه‌ی تشخیص خودکار کلیدهای خارجی
اگر در یک طرف رابطه‌ی تشخیص داده شده، خاصیتی با یکی از سه نام زیر وجود داشت:
<primary key property name>
<navigation property name><primary key property name>
<principal entity name><primary key property name>
آنگاه این خاصیت به صورت خودکار به عنوان کلید خارجی تنظیم می‌شود. در رابطه‌ی فوق Blog از نوع principal است (پدر رابطه) و Post از نوع dependent (فرزند رابطه).
برای مثال در رابطه‌ی فوق، نام خاصیت BlogId دقیقا بر اساس همان الگوی <primary key property name> طرف دیگر رابطه‌است:
  public virtual Blog Blog { get; set; }
  public int BlogId { get; set; }
بنابراین به صورت خودکار به عنوان کلید خارجی درنظر گرفته می‌شود.

تا اینجا اگر مطلب را دنبال کرده باشید به این نتیجه خواهید رسید که دو کلاس فوق، اساسا نیازی به هیچ نوع تنظیم Fluent و یا Data annotations ایی برای برقراری ارتباط یک به چند ندارند. چون روابط بین آن‌ها بر اساس خواص راهبری (navigation property) و همچنین الگوی <primary key property name>، به صورت خودکار قابل تشخیص و تنظیم است. به علاوه ... در هر طرف رابطه، فقط یک navigation property وجود دارد و نیازی به تنظیم دستی سر دیگر رابطه نیست.


استفاده از Fluent API برای تنظیم رابطه‌ی One-to-Many

در تنظیمات فوق، در متد OnModelCreating، ذکر صریح این روابط را صرفا جهت از بین بردن هرگونه ابهامی مشاهده می‌کنید:
modelBuilder.Entity<Post>(entity =>
{
    entity.HasOne(d => d.Blog)
             .WithMany(p => p.Post)
             .HasForeignKey(d => d.BlogId);
});
از هر طرفی که شروع می‌کنید، متدهای HasOne و یا HasMany، مشخص کننده‌ی navigation property هستند که در سمت موجودیت معرفی شده قرار دارند. در اینجا چون کار با موجودیت Post شروع شده‌است، متد HasOne به خاصیت راهبری در همان سمت و به خاصیت Blog آن اشاره می‌کند.
مرحله‌ی بعد، مشخص کردن سر دیگر رابطه (inverse navigation) است. این‌کار توسط یکی از متدهای WithOne و یا WithMany انجام می‌شود.
متدهایی که اسامی فرد دارند مانند HasOne/WithOne به یک navigation property ساده اشاره می‌کنند.
متدهایی که اسامی جمع دارند مانند HasMany/WithMany به collection navigation properties اشاره خواهند کرد.
متد HasForeignKey نیز برای ذکر صریح کلید خارجی بکار رفته‌است.


ب) با استفاده از روش data-annotations
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
 dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose -a
با خروجی:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Blog
    {
        public Blog()
        {
            Post = new HashSet<Post>();
        }

        public int BlogId { get; set; }

        [Required]
        public string Url { get; set; }

        [InverseProperty("Blog")]
        public virtual ICollection<Post> Post { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Post
    {
        public int PostId { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        [ForeignKey("BlogId")]
        [InverseProperty("Post")]
        public virtual Blog Blog { get; set; }
        public int BlogId { get; set; }
    }
}

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Core1RtmEmptyTest.Entities
{
    public partial class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        }

        public virtual DbSet<Blog> Blog { get; set; }
        public virtual DbSet<Post> Post { get; set; }
    }
}
همانطور که در توضیحات روش Fluent API عنوان شد، این مدل خاص، چون دقیقا بر اساس پیش فرض‌های EF Core طراحی شده‌است، نیازی به هیچگونه تنظیم اضافه‌تری ندارد. اما اگر کلید خارجی، مطابق سه الگویی که عنوان شد، قابل تشخیص نباشد، باید آن‌را در روش data annotations توسط ویژگی ForeignKey، به نحو صریحی مشخص کرد:
  [ForeignKey("BlogId")]
  [InverseProperty("Post")]
  public virtual Blog Blog { get; set; }
  public int BlogId { get; set; }
همچنین اگر بیش از یک خاصیت راهبری (navigation property) وجود داشت، ذکر InverseProperty نیز ضروری است تا مشخص شود سر دیگر این رابطه دقیقا کدام است.
در این حالت (داشتن بیش از یک خاصیت راهبری)، باید ویژگی InverseProperty را نیز به سر دوم رابطه، اعمال کرد.
   [InverseProperty("Blog")]
  public virtual ICollection<Post> Post { get; set; }

مطالب تکمیلی

علت virtual بودن خواص راهبری تولید شده

اگر دقت کنید، EF Core کدی را که تولید کرده‌است، به همراه خاصیت‌هایی virtual است:
public virtual Blog Blog { get; set; }
در اینجا تمام خاصیت‌های راهبری virtual تعریف شده‌اند. علت آن، به پیاده سازی مباحث AOP بر می‌گردد. زمانیکه خاصیتی به صورت virtual تعریف می‌شود، EF core می‌تواند آن‌را توسط یک شیء پروکسی شفاف احاطه کند. این پروکسی‌ها دو هدف را دنبال می‌کند:
الف) پیاده سازی lazy loading (بارگذاری خودکار اعضای مرتبط (همان خواص راهبری) با اولین دسترسی به آن‌ها)
ب) پیاده سازی change tracking

مبحث lazy loading فعلا در EF Core 1.0 پشتیبانی نمی‌شود. اما change tracking آن فعال است.
بنابراین اگر مشاهده کردید خواص راهبری به صورت virtual تعریف شده‌اند، علت آن فعال سازی lazy loading است و اگر سایر خواص به صورت virtual تعریف شده‌اند، هدف اصلی آن بهبود عملکرد سیستم change tracking است.
همچنین اگر دقت کرده باشید، نوع مجموعه‌ها نیز ICollection ذکر شده‌است. این مورد نیز یکی دیگر از پیش فرض‌های توکار EF Core است؛ در جهت تشکیل پروکسی‌ها بر روی خواص راهبری مجموعه‌ای (علاوه بر virtual تعریف کردن آن‌ها). عنوان شده‌است که اگر برای مثال از List استفاده کنید (پیاده سازی اینترفیس) یا هر اینترفیس دیگری که از ICollection  مشتق شده‌است، این پروکسی‌ها تشکیل نخواهند شد.


واکشی اعضای به هم مرتبط

همانطور که عنوان شد، نگارش اول EF Core برخلاف EF 6.x از Lazy loading پشتیبانی نمی‌کند. البته این مساله در کل مورد مثبتی است؛ خصوصا در برنامه‌های وب! چون استفاده‌ی نادرست از Lazy loading که به select n+1 نیز مشهور است، سبب رفت و برگشت‌های بی‌شماری به بانک اطلاعاتی می‌شود و عموم برنامه نویس‌های وب باید مدام توسط برنامه‌های Profiler بررسی کنند که آیا این مساله رخ داده‌است یا خیر. فعلا EF Core از این مشکل در امان است!
اما ... اگر به روش کار EF 6.x عادت کرده باشید، قطعه کد ذیل:
 var firstPost = context.Post.First();
Console.WriteLine(firstPost.Blog.Url);
چنین خطایی را صادر می‌کند:
 System.NullReferenceException
Object reference not set to an instance of an object.
علت اینجا است که چون Lazy loading غیرفعال است (هنوز در EF Core 1.0 پیاده سازی نشده‌است)، اولین دسترسی به شیء Blog، سبب وهله سازی خودکار آن نشده و این شیء نال است. به همین جهت استثنای فوق را مشاهده می‌کنیم.
برای رفع این مشکل باید توسط متد Include، سبب لغو عملیات Lazy loading و واکشی صریح Blog مرتبط شویم که اصطلاحا به آن eager loading می‌گویند:
 var firstPost = context.Post.Include(x => x.Blog).First();
Console.WriteLine(firstPost.Blog.Url);

نکته‌ای در مورد سطوح بارگذاری اعضای به هم مرتبط در EF Core

متد Include ایی را که تا اینجا مشاهده کردید، با EF 6.x تفاوتی ندارد. برای مثال اگر شیء Blog حاوی خواص راهبری Posts و همچنین Owner باشد، برای بارگذاری این اعضای مرتبط، می‌توان همانند قبل، متدهای Include را پشت سر هم ذکر کرد:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                              .Include(blog => blog.Owner)
                              .ToList();
اما فرض کنید خاصیت Post، دارای یک خاصیت راهبری دیگری به نام Author نیز باشد و می‌خواهیم این خاصیت هم بارگذاری شود:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                      .ThenInclude(post => post.Author)
                              .ToList();
روش انجام چنین کاری در EF Core، توسط متد الحاقی جدید ThenInclude است. ابتدا لیست Blogها عنوان شده‌است. سپس در این لیست علاقمند به واکشی تمام مطالب این بلاگ‌ها هم بوده‌ایم. به علاوه در این مطالب، نیاز است خاصیت Author آن‌ها نیز از پیش مقدار دهی شده و قابل دسترسی باشد. به همین جهت برای دسترسی به چندین سطح مختلف از متد ThenInclude کمک گرفته شده‌است.
همچنین در اینجا امکان ذکر زنجیروار متدهای ThenInclude هم هست:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                 .ThenInclude(post => post.Author)
                                        .ThenInclude(author => author.Photo)
                              .ToList();
در این مثال یک سطح دیگر جلو رفته و شیء Photo مربوط به شیء Author را هم واکشی کرده‌ایم.
به علاوه امکان ذکر چندین ریشه و چندین زیر ریشه هم وجود دارند:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                  .ThenInclude(post => post.Author)
                                      .ThenInclude(author => author.Photo)
                              .Include(blog => blog.Owner)
                                    .ThenInclude(owner => owner.Photo)
                              .ToList();

یک نکته: متد Include تنها زمانی درنظر گرفته خواهد شد که نوع خروجی نهایی کوئری، دقیقا از نوع موجودیتی باشد که با آن شروع به کار کرده‌ایم. برای مثال اگر در این بین یک Select اضافه شود و فقط تنها تعدادی از خواص Blog واکشی شوند، از تمام Includeهای ذکر شده صرفنظر می‌شود؛ مانند کوئری ذیل:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                              .Select(blog => new
                               {
                                  Id = blog.BlogId,
                                  Url = blog.Url
                               })
                               .ToList();


تنظیمات حذف آبشاری در رابطه‌ی one-to-many

زمانیکه در رابطه‌ی one-to-many قسمت principal (والد رابطه) و یا همان Blog در مثال جاری حذف می‌شود، سه اتفاق برای فرزندان آن میسر خواهند بود:
الف) Cascade : در این حالت ردیف‌های فرزندان وابسته نیز حذف خواهند شد.
باید دقت داشت که حالت Cascade فقط برای موجودیت‌هایی اعمال می‌شود که توسط Context بارگذاری شده و در آن وجود دارند. اگر می‌خواهید سایر موجودیت‌های مرتبط نیز با این روش حذف شوند، باید در سمت دیتابیس نیز تنظیماتی مانند ON DELETE CASCADE زیر نیز وجود داشته باشند:
 CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([BlogId]) ON DELETE CASCADE
و اگر با EF Core بانک اطلاعاتی خود را ایجاد می‌کنید (مباحث مهاجرت‌ها)، این تنظیم به صورت خودکار اعمال خواهد شد؛ اگر DeleteBehavior را به نحو ذیل مشخص کرده باشید:
modelBuilder.Entity<Post>()
                    .HasOne(p => p.Blog)
                    .WithMany(b => b.Posts)
                    .OnDelete(DeleteBehavior.Cascade);
ب) SetNull: در این حالت فرزندان وابسته حذف نمی‌شوند و تنها کلید خارجی آن‌ها به نال تنظیم می‌شود.
ج) Restrict: هیچ تغییری بر روی فرزندان رابطه رخ نمی‌دهد.

یک نکته: به صورت پیش فرض اگر رابطه‌ی one-to-many، به Required تنظیم شود، حالت حذف آن cascade خواهد بود. در غیراینصورت برای حالت‌های Optional، حالت SetNull تنظیم می‌گردد:
modelBuilder.Entity<Post>()
                    .HasOne(p => p.Blog)
                    .WithMany(b => b.Posts)
                    .IsRequired();
در اینجا ذکر صریح متد IsRequired به این معنا است که مقدار دهی کلید خارجی سر دیگر رابطه، اجباری است.
به علاوه باید دقت داشت، همان مباحث «تعیین اجباری بودن یا نبودن ستون‌ها در EF Core» در قسمت قبل، در اینجا هم صادق است. برای مثال چون BlogId (کلید خارجی در کلاس Post) از نوع int است و نال پذیر نیست، بنابراین از دیدگاه EF Core یک فیلد اجباری درنظر گرفته می‌شود. به همین جهت است که در کدهای تولید شده‌ی توسط EF Core در ابتدای بحث، ذکر متد IsRequired و یا OnDelete را مشاهده نمی‌کنید.
بنابراین اگر می‌خواهید حالت SetNull را فعال کنید، باید این کلید خارجی را نیز نال پذیر و به صورت int? BlogId ذکر کنید تا optional درنظر گرفته شود.
نظرات مطالب
PersianDateTime جایگزینی برای System.DateTime
این تمام کد برنامه است :
using System;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //  این قسمت برای ورود اطلاعات به بانک است و با کمک کتابخانه پرژن دات نت  تاریخ شمسی را به میلادی تبدیل و ذخیره میکنم
            using(var db = new h7Entities())
            {
                var t = new test
                {
                    ResponseDate = PersianDateTime.Parse(textBox1.Text).ToDateTime()
                };
                db.test.Add(t);
                db.SaveChanges();
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // این قسمت هم فقط اطلاعات واکشی شده را در گرید نمایش میدهد . بدون هیچ شرطی ، یک سلکت ساده . . فقط از پرژن دانت نت برای تبدیل میلادی به شمسی کمک میگیرم
            using (var db = new h7Entities())
            {
                dataGridView1.DataSource =(from t in db.test
                                              select  new
                                              { 
                                                  Id= t.Id,
                                                  time = new PersianDateTime(DateTime.Parse(t.ResponseDate.ToString())).ToString(PersianDateTimeFormat.DateShortTime)
                                              }).ToList();                
            }

        }
    }
}

کتابخانه PersianDateTime  را از نیوگت دریافت کردم .
ولی چیزی در گرید نمایش نمیدهد .

مدل برنامه هم :

 public partial class test
    {
        public int Id { get; set; }
        public Nullable<System.DateTime> ResponseDate { get; set; }
    }


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


حالا چطور از فیلدی که تاریخ را نمایش میدهد فقط آن را تبدیل به شمسی و نمایش دهد ؟ شبیه این  1365/02/02    ؟
تشکر
مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
در حین کار با برنامه‌های وب، چشم‌پوشی از جاوا اسکریپت عملا ممکن نیست؛ هرچند با Blazor، امکان انجام کارهایی را یافته‌ایم که پیشتر با MVC و یا Razor pages میسر نبودند، اما هیچگاه به تنهایی نمی‌تواند جایگزین کامل جاوا اسکریپت، در تولید برنامه‌های وب باشد. بنابراین ضروری است که نحوه‌ی یکپارچگی جاوا اسکریپت را با برنامه‌های مبتنی بر Blazor، بررسی کنیم.


ایجاد کامپوننت جدید BlazorJS

برای بررسی نحوه‌ی تعامل جاوا اسکریپت و Blazor، در ابتدا کامپوننت جدید Pages\LearnBlazor\BlazorJS.razor را ایجاد کرده:
@page "/BlazorJS"

<h3>BlazorJS</h3>

@code
{
}
و همچنین مدخل منوی آن‌را نیز بر اساس مسیریابی ابتدای فایل این کامپوننت، به فایل Shared\NavMenu.razor اضافه می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="BlazorJS">
        <span class="oi oi-list-rich" aria-hidden="true"></span> BlazorJS
    </NavLink>
</li>


روش فراخوانی کدهای جاوا اسکریپتی از طریق کدهای سی‌شارپ Blazor

فرض کنید می‌خواهیم در حین کلیک بر روی دکمه‌ای مانند دکمه‌ی حذف، ابتدا تائیدیه‌ای را توسط تابع confirm جاوا اسکریپتی، از کاربر اخذ کنیم. روش انجام چنین کاری در برنامه‌های مبتنی بر Blazor به صورت زیر است:
@page "/BlazorJS"

@inject IJSRuntime JsRuntime

<h3>BlazorJS</h3>

<div class="row">
    <button class="btn btn-secondary" @onclick="TestConfirmBox">Test Confirm Button</button>
</div>
<div class="row">
    @if (ConfirmResult)
    {
        <p>Confirmation has been made!</p>
    }
    else
    {
        <p>Confirmation Pending!</p>
    }
</div>

@code {
    string ConfirmMessage = "Are you sure you want to click?";
    bool ConfirmResult;

    async Task TestConfirmBox()
    {
        ConfirmResult = await JsRuntime.InvokeAsync<bool>("confirm", ConfirmMessage);
    }
}
توضیحات:
- در اینجا می‌خواهیم تابع استاندارد confirm جاوا اسکریپتی را از طریق کدهای سی‌شارپ، با کلیک بر روی دکمه‌ی Test Confirm Button، فراخوانی کنیم. به همین جهت onclick@ این دکمه، به متد TestConfirmBox کدهای UI سی‌شارپ این کامپوننت، متصل شده‌است.
- برای دسترسی به توابع جاوا اسکریپتی، نیاز است سرویس توکار IJSRuntime را به کدهای کامپوننت تزریق کنیم که روش انجام آن‌را توسط دایرکتیو inject@ مشاهده می‌کنید. برای دسترسی به این سرویس توکار، نیاز به تنظیمات ابتدایی خاصی نیست و اینکار پیشتر انجام شده‌است.
- سرویس JsRuntime تزریق شده، دو متد مهم InvokeVoidAsync و InvokeAsync را جهت فراخوانی توابع جاوا اسکریپتی به همراه دارد. اگر تابعی، خروجی غیر void داشته باشد، باید از متد InvokeAsync استفاده کرد. برای مثال خروجی تابع استاندارد confirm، از نوع boolean است. بنابراین نوع این خروجی را به صورت یک آرگومان جنریک متد InvokeAsync مشخص کرده‌ایم.
- اولین پارامتر متد InvokeAsync، نام رشته‌ای تابع جاوا اسکریپتی است که قرار است صدا زده شود. پارامترهای اختیاری بعدی که به صورت params object?[]? args تعریف شده‌اند، لیست نامحدود آرگومان‌های ورودی این متد هستند.
- فیلد ConfirmMessage، پیامی را جهت اخذ تائید، تعریف می‌کند که به عنوان پارامتر متد confirm، توسط JsRuntime.InvokeAsync فراخوانی خواهد شد.
- فیلد ConfirmResult، نتیجه‌ی فراخوانی متد confirm جاوا اسکریپتی را به همراه دارد.
- در اینجا روش عکس العمل نشان دادن به خروجی دریافتی از متد جاوااسکریپتی را نیز مشاهده می‌کنید. پس از پایان متد TestConfirmBox که یک متد رویدادگران است، همانطور که در مطلب بررسی «چرخه‌ی حیات کامپوننت‌ها» نیز بررسی کردیم، متد StateHasChanged، در پشت صحنه فراخوانی می‌شود که سبب رندر مجدد UI خواهد شد. بنابراین در حین رندر مجدد UI، بر اساس مقدار جدید ConfirmResult دریافت شده‌ی از کاربر، با تشکیل یک if/else@، می‌توان به نتیجه‌ی تائید یا عدم تائید کاربر، واکنش نشان داد. با این توضیحات در اولین بار نمایش کامپوننت جاری چون مقدار ConfirmResult مساوی false است، پیام زیر را مشاهده می‌کنیم:


اما در ادامه با کلیک بر روی دکمه و تائید پیام ظاهر شده، عبارت زیر ظاهر می‌شود:



روش افزودن یک کتابخانه‌ی خارجی جاوا اسکریپتی به پروژه‌های Blazor

فرض کنید می‌خواهیم پیام‌های برنامه را توسط کتابخانه‌ی معروف جاوا اسکریپتی Toastr نمایش دهیم؛ با این دمو.
مرحله‌ی اول کار با این کتابخانه، دریافت فایل‌های CSS و JS آن است. برای این منظور قصد داریم از برنامه‌ی مدیریت بسته‌های LibMan استفاده کنیم:
dotnet tool install -g Microsoft.Web.LibraryManager.Cli
libman init
libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap
libman install jquery --provider unpkg --destination wwwroot/lib/jquery
libman install toastr --provider unpkg --destination wwwroot/lib/toastr
بنابراین خط فرمان را در ریشه‌ی پروژه گشوده و پنج دستور فوق را اجرا می‌کنیم. دستور اول، ابزار خط فرمان LibMan را نصب می‌کند. دستور دوم، یک فایل libman.json خالی را در این پوشه ایجاد می‌کند و سه دستور بعدی، جی‌کوئری، بوت استرپ و toastr را دریافت و در پوشه‌ی wwwroot/lib قرار می‌دهند. Toastr برای اجرا، نیاز به jQuery نیز دارد.
البته تعاریف مداخل آن‌ها به فایل libman.json نیز اضافه می‌شوند. مزیت آن، اجرای دستور libman restore برای بازیابی و نصب مجدد تمام بسته‌های ذکر شده‌ی در فایل libman.json است.

پس از دریافت بسته‌های سمت کلاینت آن، مداخل مرتبط را به فایل Pages\_Host.cshtml برنامه‌ی Blazor Server اضافه خواهیم کرد (و یا در فایل wwwroot/index.html برنامه‌های Blazor WASM).
<head>
    <base href="~/" />
    <link rel="stylesheet" href="lib/toastr/build/toastr.min.css" />

</head>
<body>
 
    <script src="lib/jquery/dist/jquery.min.js"></script>
    <script src="lib/toastr/build/toastr.min.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>
مدخل فایل css آن‌را در قسمت head و فایل js آن‌را پیش از بسته شدن تگ body تعریف می‌کنیم. در اینجا نیازی به ذکر پوشه‌ی آغازین wwwroot نیست؛ چون base href تعریف شده، به این پوشه اشاره می‌کند.

یک نکته: می‌توان فایل csproj برنامه را به صورت زیر تغییر داد تا کار اجرای دستور libman restore را قبل از build، به صورت خودکار انجام دهد:
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <Target Name="DebugEnsureLibManEnv" BeforeTargets="BeforeBuild" Condition=" '$(Configuration)' == 'Debug' ">
    <!-- Ensure libman is installed -->
    <Exec Command="libman --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="libman is required to build and run this project. To continue, please run `dotnet tool install -g Microsoft.Web.LibraryManager.Cli`, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'libman'. This may take several minutes..." />
    <Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="libman restore" />
  </Target>
</Project>


روش فراخوانی یک کتابخانه‌ی خارجی جاوا اسکریپتی در پروژه‌های Blazor

پس از افزودن فایل‌های سمت کلاینت toastr و تعریف مداخل آن در فایل Pages\_Host.cshtml برنامه‌ی Blazor Server جاری، اکنون می‌خواهیم از این کتابخانه استفاده کنیم. یک روش کار با این نوع کتابخانه‌های عمومی و سراسری به صورت زیر است:
- ابتدا فایل خالی جدید wwwroot\js\common.js را ایجاد می‌کنیم.
- سپس تابع عمومی و سراسری ShowToastr را بر اساس امکانات کتابخانه‌ی toastr و مستندات آن، به صورت زیر ایجاد می‌کنیم:
window.ShowToastr = (type, message) => {
  // Toastr don't work with Bootstrap 4.2
  toastr.options.toastClass = "toastr"; // https://github.com/CodeSeven/toastr/issues/599

  if (type === "success") {
    toastr.success(message, "Operation Successful", { timeOut: 20000 });
  }
  if (type === "error") {
    toastr.error(message, "Operation Failed", { timeOut: 20000 });
  }
};
چون تابع ShowToastr به شیء window انتساب داده شده‌است، در سراسر برنامه‌ی جاری قابل دسترسی است.
سطر اول آن هم برای رفع عدم تداخل با بوت استرپ 4x اضافه شده‌است. بوت استرپ 4x به همراه کلاس‌های CSS مشابهی است که نیاز است با تنظیم toastClass به مقداری دیگر، این تداخل را برطرف کرد.

- در ادامه مدخل تعریف فایل wwwroot\js\common.js را به انتهای تگ body فایل Pages\_Host.cshtml اضافه می‌کنیم:
    <script src="lib/jquery/dist/jquery.min.js"></script>
    <script src="lib/toastr/build/toastr.min.js"></script>
    <script src="js/common.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>

در آخر برای آزمایش آن به کامپوننت Pages\LearnBlazor\BlazorJS.razor مراجعه کرده و تابع سراسری ShowToastr را دقیقا مانند روشی که در مورد تابع confirm بکار بردیم، توسط سرویس JsRuntime، فراخوانی می‌کنیم:
@page "/BlazorJS"

@inject IJSRuntime JsRuntime


<div class="row">
    <button class="btn btn-success" @onclick="@(()=>TestSuccess("Success Message"))">Test Toastr Success</button>
    <button class="btn btn-danger" @onclick="@(()=>TestFailure("Error Message"))">Test Toastr Failure</button>
</div>

@code {
    async Task TestSuccess(string message)
    {
        await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message);
    }

    async Task TestFailure(string message)
    {
        await JsRuntime.InvokeVoidAsync("ShowToastr", "error", message);
    }
}
در اینجا دو دکمه، جهت فراخوانی متد ShowToastr با پارامترهای مختلفی تعریف شده‌اند. چون تابع ShowToastr خروجی ندارد، به همین جهت اینبار از متد InvokeVoidAsync استفاده کرده‌ایم. پارامتر اول آن، نام متد ShowToastr است. پارامتر‌های دوم و سوم آن با آرگومان‌های (type, message) تعریف شده‌ی تابع ShowToastr تطابق دارند. به علاوه در این مثال، روش ارسال پارامترها را نیز در onlick@ توسط arrow functions مشاهده می‌کنید.



کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی

می‌توان جهت کاهش تکرار کدهای استفاده از تابع ShowToastr، متدهای الحاقی زیر را برای سرویس IJSRuntime تهیه کرد:
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace BlazorServerSample.Utils
{
    public static class JSRuntimeExtensions
    {
        public static ValueTask ToastrSuccess(this IJSRuntime JSRuntime, string message)
        {
            return JSRuntime.InvokeVoidAsync("ShowToastr", "success", message);
        }

        public static ValueTask ToastrError(this IJSRuntime JSRuntime, string message)
        {
            return JSRuntime.InvokeVoidAsync("ShowToastr", "error", message);
        }
    }
}
و سپس فضای نام آن‌را به فایل Imports.razor_ معرفی نمود تا در تمام کامپوننت‌های برنامه قابل استفاده شوند.
@using BlazorServerSample.Utils
به این ترتیب به فراخوانی‌های ساده شده‌ی زیر خواهیم رسید:
async Task TestSuccess(string message)
{
   //await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message);
   await JsRuntime.ToastrSuccess(message);
}


فراخوانی یک متد عمومی واقع در کامپوننت فرزند از طریق کامپوننت والد

فرض کنید در کامپوننت فرزند Pages\LearnBlazor\LearnBlazor‍Components\ChildComponent.razor که در قسمت‌های قبل آن‌را تکمیل کردیم، متد عمومی زیر تعریف شده‌است:
@inject IJSRuntime JsRuntime


@code {
    // ...

    public async Task TestSuccess(string message)
    {
        await JsRuntime.ToastrSuccess(message);
    }
}
اکنون اگر بخواهیم این متد عمومی را از طریق کامپوننت والد یا دربرگیرنده‌ی آن فراخوانی کنیم، نیاز است از مفهوم جدیدی به نام ref استفاده کرد. برای این منظور به کامپوننت Pages\LearnBlazor\ParentComponent.razor مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
@page "/ParentComponent"

<ChildComponent
    OnClickBtnMethod="ShowMessage"
    @ref="ChildComp"
    Title="This title is passed as a parameter from the Parent Component">
    <ChildContent>
        A `Render Fragment` from the parent!
    </ChildContent>
    <DangerChildContent>
        A danger content from the parent!
    </DangerChildContent>
</ChildComponent>

<div class="row">
    <button class="btn btn-success" @onclick="@(()=>ChildComp.TestSuccess("Done!"))">Show Alert</button>
</div>


@code {
    ChildComponent ChildComp;
    // ...
}
با استفاده از ref@ که به فیلد ChildComp انتساب داده شده‌است، می‌توان ارجاعی از کامپوننت فرزند را (وهله‌ای از کلاس مرتبط با آن‌را) در کامپوننت جاری بدست آورد و سپس از آن جهت فراخوانی متدهای عمومی کامپوننت فرزند استفاده کرد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-11.zip
مطالب
Blazor 5x - قسمت 21 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 1 - افزودن قالب ابتدایی Identity
در ادامه‌ی مثال این سری، می‌خواهیم امکان ثبت و ویرایش اتاق‌ها را (و یا امکانات رفاهی یک هتل را که به صورت تمرینی دقیقا مشابه افزودن مشخصات اتاق‌ها، اضافه شده و کدهای آن از فایل پیوستی انتهای بحث قابل دریافت است) به کاربران اعتبارسنجی شده‌ی دارای نقش مدیریتی، محدودیت کنیم و نمی‌خواهیم عموم کاربران برنامه بتوانند در این قسمت‌ها، تغییری را ایجاد کنند. برای این منظور از امکانات توکار و استاندارد ASP.NET Core Identity استفاده خواهیم کرد و این کتابخانه را از صفر و بدون تغییری، به پروژه‌ی جاری از نوع Blazor Server، به همان نحوی که طراحی شده، اضافه می‌کنیم و در مراحل بعدی، بر اساس نیازهای برنامه، قسمت‌های مختلف آن‌را سفارشی سازی خواهیم کرد.


تغییر نوع DbContext برنامه

پیش از شروع به یکپارچه کردن ASP.NET Core Identity با برنامه‌ی جاری، نیاز است نوع DbContext آن‌را به صورت زیر تغییر داد:
using BlazorServer.Entities;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace BlazorServer.DataAccess
{
    public class ApplicationDbContext : IdentityDbContext
    {
      // ...
- بنابراین به فایل BlazorServer\BlazorServer.DataAccess\ApplicationDbContext.cs مراجعه کرده و برای شروع، بجای DbContext، از IdentityDbContext استفاده می‌کنیم.
- این تغییر، نیاز به نصب بسته‌ی نیوگت Microsoft.AspNetCore.Identity.EntityFrameworkCore را نیز در پروژه‌ی جاری دارد تا IdentityDbContext آن شناسایی شده و قابل استفاده شود.


نصب ابزار تولید کدهای ASP.NET Core Identity

اگر از ویژوال استودیوی کامل استفاده می‌کنید، گزینه‌ی افزودن کدهای ASP.NET Core Identity به صورت زیر قابل دسترسی است:
project -> right-click > Add > New Scaffolded Item -> select Identity > Add
اما از آنجائیکه قصد داریم این مطلب، برای کاربران VSCode و همچنین سایر سیستم عامل‌ها نیز قابل استفاده باشد، از NET Core CLI. استفاده خواهیم کرد. برای این منظور، ابتدا ابزار سراسری dotnet-aspnet-codegenerator را نصب می‌کنیم:
dotnet tool install -g dotnet-aspnet-codegenerator
سپس به پروژه‌ی اصلی Blazor Server مراجعه کرده (BlazorServer.App.csproj در این مثال) و در پوشه‌ی آن، دستورات زیر را اجرا می‌کنیم تا بسته‌های نیوگت مورد نیاز ASP.NET Core Identity و UI آن، نصب شوند:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
پس از نصب این وابستگی‌ها، اکنون در همین ریشه‌ی پروژه‌ی اصلی، دستور زیر را اجرا می‌کنیم تا فایل‌های ASP.NET Core Identity اضافه شوند:
dotnet aspnet-codegenerator identity --dbContext BlazorServer.DataAccess.ApplicationDbContext --force
در اینجا ذکر فضای نام کامل کلاس ApplicationDbContext ضروری است.
حال اگر به پروژه دقت کنیم، پوشه‌ی جدید Areas که به همراه فایل‌های مدیریتی ASP.NET Core Identity است، اضافه شده و حاوی کدهای صفحات لاگین، ثبت نام کاربر و غیره است.


اعمال تغییرات ابتدایی مورد نیاز جهت استفاده از ASP.NET Core Identity

تا اینجا کدهای پیش‌فرض مدیریتی ASP.NET Core Identity را به پروژه اضافه کردیم. در ادامه نیاز است تغییرات ذیل را به پروژه‌ی اصلی Blazor Server اعمال کنیم تا بتوان از این فایل‌ها استفاده کرد:
- به فایل BlazorServer.App\Startup.cs مراجعه کرده و UseAuthentication و UseAuthorization را دقیقا در محلی که مشاهده می‌کنید، اضافه می‌کنیم. همچنین در اینجا نیاز است مسیریابی‌های razor pages را نیز فعال کرد.
namespace BlazorServer.App
{
    public class Startup
    {
        // ...
 
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            // ...

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                // ...
            });
        }
    }
}
- سپس به پوشه‌ی BlazorServer.DataAccess برنامه وارد شده و دستورات Migrations را یکبار دیگر اجرا می‌کنیم، تا جداول پیش‌فرض Identity، بر اساس Context جدید آن ایجاد شوند:
dotnet tool update --global dotnet-ef --version 5.0.4
dotnet build
dotnet ef migrations --startup-project ../BlazorServer.App/ add AddIdentity --context ApplicationDbContext
dotnet ef --startup-project ../BlazorServer.App/ database update --context ApplicationDbContext
پس از اجرای این دستورات، جداول جدید زیر به بانک اطلاعاتی برنامه اضافه می‌شوند:



افزودن گزینه‌ی منوی لاگین به برنامه‌ی Blazor Server


پس از این تغییرات، به برنامه‌ای رسیده‌ایم که مدیریت قسمت Identity آن، توسط قالب استاندارد مایکروسافت که در پوشه‌ی Areas\Identity\Pages\Account نصب شده و بر اساس فناوری ASP.NET Core Razor Pages کار می‌کند، انجام می‌شود.
اکنون می‌خواهیم در منوی برنامه‌ی Blazor Server خود که با صفحات Identity یکی شده‌است، لینکی را به صفحه‌ی لاگین این Area اضافه کنیم. اگر به فایل Shared\MainLayout.razor آن مراجعه کنیم، به صورت پیش‌فرض، لینکی به صفحه‌ی About، قرار دارد. به همین جهت این مورد را به صورت زیر اصلاح می‌کنیم:
ابتدا کامپوننت جدید BlazorServer.App\Shared\LoginDisplay.razor را با محتوای زیر ایجاد می‌کنیم:
<a href="Identity/Account/Register">Register</a>
<a href="Identity/Account/Login">Login</a>

@code {

}
که لینک‌هایی را به صفحات لاگین و ثبت نام یک کاربر جدید، تعریف می‌کند.
سپس از این کامپوننت در فایل BlazorServer.App\Shared\MainLayout.razor استفاده می‌کنیم:
<div class="top-row px-4">
    <LoginDisplay></LoginDisplay>
    <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>


ثبت و فعالسازی سرویس‌های کار با ASP.NET Core Identity

البته اگر در این حال برنامه را اجرا کنیم، با کلیک بر روی لینک‌های فوق، استثنائی را مانند یافت نشدن سرویس UserManager، مشاهده خواهیم کرد. برای رفع این مشکل، به فایل BlazorServer.App\Startup.cs مراجعه کرده و سرویس‌های Identity را ثبت می‌کنیم:
namespace BlazorServer.App
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
           // ...

            services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders()
                .AddDefaultUI();

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


همانطور که مشاهده می‌کنید، قالب این قسمت Identity، با قالب قسمت Blazor Server یکی نیست؛ چون توسط Razor Pages و Area آن تامین می‌شود که master page خاص خودش را دارد. زمانیکه قالب Identity را اضافه می‌کنیم، علاوه بر Area خاص خودش، پوشه‌ی جدید Pages\Shared را نیز ایجاد می‌کند که قالب صفحات Identity را به کمک فایل Pages\Shared\_Layout.cshtml تامین می‌کند:


بنابراین سفارشی سازی قالب این قسمت، شبیه به قالبی که برای کامپوننت‌های Blazor مورد استفاده قرار می‌گیرد، باید در اینجا انجام شود و سفارشی سازی قالب کامپوننت‌های Blazor، در پوشه‌ی Shared ای که در ریشه‌ی پروژه‌است (BlazorServer.App\Shared\MainLayout.razor) انجام می‌شود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-21.zip
نظرات مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
ممنون از راهنمایی شما
ولی متاسفانه بعد از دیباگ شی attachFile رو null میفرسته سمت سرور( اکشن متد داخل کنترلر ) و فقط بقیه input‌ها رو در قالب مدل ارسال میکنه که کل عملیات با شکست مواجه میشه چون تصویری برای ذخیره شدن موجود نیست و NullRefrenceException اعلام میشه.
تو بخش client : 
<form asp-controller="Admin" asp-action="CreateBlog" enctype="multipart/form-data" data-ajax="true" data-ajax-method="post" data-ajax-loading="#Progress" data-ajax-complete="onComplete"
      data-ajax-failure="onFailed" data-ajax-success="onSuccess">
 در سمت سرور :
public async Task<IActionResult> CreateBlog(MyViewModel vm, IFormFile attachFile)

یکی از دوستان در نظرات این لینک مقاله شما رو پیشنهاد کرده بودن. منتها گفتم شاید در دات نت core بشه بدون افزونه خاصی متد ارسال فایل رو با بقیه اطلاعات فرم بصورت UnObtrusive پیاده سازی کرد.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
سلام معادل 
@using Menu.Helper
@model IEnumerable<.Models.Entities.Category>
@ShowTree(Model)

@helper ShowTree(IEnumerable<Menu.Models.Entities.Category> categories)
{
    foreach (var item in categories)
    {
    <lidropdown-submenu" : "")">

        @Html.ActionLink(item.Name, actionName: "Category", controllerName: "Product", routeValues: new { Id = item.Id, productName = item.Name.ToSeoUrl() }, htmlAttributes: null)
        @if (item.Children.Any())
        {
            <ul>
                @ShowTree(item.Children)
            </ul>
                }
    </li>

        }
}
با استفاده از @function امکان پذیر هست؟  
@model IEnumerable<TR_Mahmodabad.Context.Entities.Navbar>

@functions
{
    public void ShowTree(IEnumerable<Navbar> navbars)
    {
        foreach (var item in navbars)
        {
            <lidropdown-submenu" : "")">
                @Html.ActionLink(item.Title, actionName: "", controllerName: "Product", routeValues: new {Id = item.Id, title = item.Title},htmlAttributes:null)
                @if (item.HasChiled)
                {
                    <ul>
                        @ShowTree(item.Children) // خطا
                    </ul>
                }
            </li>
        }
    }
}
مطالب
امکان تعریف رشته‌های UTF-8 در C# 11
به صورت پیش‌فرض، رشته‌ها در زبان #C، از نوع UTF-16 تعریف شده‌اند؛ درحالیکه اکثر رشته‌ها در دنیای اینترنت و پروتکل‌های آن، از نوع UTF-8 هستند. برای کاهش سربار کارآیی این تبدیل‌ها، اکنون در C# 11 می‌توانید رشته‌ها را با پسوند u8 نیز معرفی کنید تا از ابتدای کار، به صورت UTF-8 پردازش شوند. یک مثال:
// C# 10
string utf16String = "Hello World";
byte[] array1 = Encoding.UTF8.GetBytes(utf16String);

// C# 11
ReadOnlySpan<byte> utf8String = "Hello World"u8;
byte[] array2 = utf8String.ToArray();
در اینجا نحوه‌ی تعریف یک رشته‌ی UTF-8 را توسط پسوند u8 در مثال دوم مشاهده می‌کنید. این رشته با نوع <ReadOnlySpan<byte معرفی می‌شود که به همراه عدم نیاز به استفاده از متد Encoding.UTF8.GetBytes قبلی و همچنین تخصیص حافظه‌ی رشته‌ی UTF-16 آن در مثال اول است. اگر می‌خواهید معادل آرایه‌ی تغییرپذیر آن‌را (mutable array) داشته باشید، باید از متد ()ToArray بر روی آن استفاده کنید.


افزایش خوانایی رشته‌های UTF-8 در C# 11

یکی دیگر از مزیت‌های این روش تعریف، علاوه بر بالا رفتن کارآیی کار با رشته‌های UTF-8، بالا بردن امکان خوانایی و درک آن‌ها است. برای مثال AuthStringLiteral مثال زیر، همان معادل آرایه‌ی AuthWithTrailingSpace است که خوانایی بیشتری دارد:
ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8;
چند مثال دیگر:
var helloUtf8 = "hello"u8;// new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f }
var dogUtf8 = "dog"u8;// new byte[] { 0x64, 0x6f, 0x67 }
var catUtf8 = "cat"u8;// new byte[] { 0x63, 0x61, 0x74 }


محدودیت‌های کار با رشته‌های UTF-8 در C# 11

- UTF-8 string literals از نوع ثوابت زمان کامپایل نیستند و از نوع ثوابت زمان اجرا محسوب می‌شوند. به همین جهت برای مثال نمی‌توان از آن‌ها به عنوان مقادیر ثابت و پیش‌فرض پارامترهای متدها استفاده کرد.
- UTF-8 string literals را نمی‌توان در عملیات string interpolation مورد استفاده قرار داد. همچنین نمی‌توان از $ و u8، بر روی یک رشته‌ی نهایی تولید شده، استفاده کرد.
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 4
برای ادامه‌‏ی کار به لایه‎ی Interface بازمی‏‌گردیم. کلیه‌ی متدهایی که به آن نیاز داریم، نخست در این لایه تعریف می‌شود. در این‏جا نیز از قراردادهایی برای تعریف کلاس و روال‎های آن بهره می‎بریم که در ادامه به آن می‎پردازیم. پیش از آن باید بررسی کنیم، برای استفاده از این دو موجودیت، به چه متدهایی نیاز داریم. من گمان می‎کنم موارد زیر برای کار ما کافی باشد:
1- نمایش کلیه‌ی رکوردهای جدول خبر
2- انتخاب رکوردی از جدول خبر با پارامتر ورودی شناسه‎ی جدول خبر 
3- درج یک رکورد جدید در جدول خبر
4- ویرایش یک رکورد از جدول خبر  
5- حذف یک رکورد از جدول خبر 
6- افزودن یک دسته
7- حذف یک دسته
8- نمایش دسته‏‌ها
هم‎اکنون به صورت زیر آن‎ها را تعریف کنید:
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;

namespace MyNewsWCFLibrary
{
    [ServiceContract]
    interface IMyNewsService
    {
        [OperationContract]
        List<tblNews> GetAllNews();

        [OperationContract]
        tblNews GetNews(int tblNewsId);

        [OperationContract]
        int AddNews(tblNews News);

        [OperationContract]
        bool EditNews(tblNews News);

        [OperationContract]
        bool DeleteNews(int tblNewsId);

        [OperationContract]
        int AddCategory(tblCategory News);

        [OperationContract]
        bool DeleteCategory(int tblCategoryId);

        [OperationContract]
        List<tblCategory> GetAllCategory();
    }
}
 همان‎گونه که مشاهده می‎کنید از دو قرارداد جدید ServiceContract و OperationContract در فضای نام  System.ServiceModel بهره برده ایم.  ServiceContract صفتی است که بر روی Interface اعمال می‌شود و تعیین می‌کند که مشتری چه فعالیت‌هایی را روی سرویس می‌تواند انجام دهد و  OperationContract تعیین می‎کند، چه متدهایی در اختیار قرار خواهند گرفت. برای ادامه‎ی کار نیاز است تا کلاس اجرا را ایجاد کنیم. برای این‎کار از ابزار Resharper بهره خواهم برد:
روی نام interface همانند شکل کلیک کنید و سپس برابر با شکل عمل کنید:

کلاسی به نام MyNewsService با ارث‌بری از IMyNewsService ایجاد می‎شود. زیر حرف I از IMyNewsService یک خط دیده می‎‌شود که با کلیک روی آن برابر با شکل زیر عمل کنید:

ملاحظه خواهید کرد که کلیه‎ی متدها برابر با Interface ساخته خواهد شد. اکنون همانند شکل روی نشان هرم شکلی که هنگامی که روی نام کلاس کلیک می‌کنید، در سمت چپ نشان داده می‌شود کلیک کنید و گزینه Move to another file to match type name را انتخاب کنید:

به صورت خودکار محتوای این کلاس به یک فایل دیگر انتقال می‎یابد. اکنون هر کدام از متدها را به شکل دلخواه ویرایش می‎کنیم. من کد کلاس را این‎گونه تغییر دادم:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;

namespace MyNewsWCFLibrary
{
    class MyNewsService : IMyNewsService
    {
        private dbMyNewsEntities dbMyNews = new dbMyNewsEntities();
        public List<tblNews> GetAllNews()
        {
            return dbMyNews.tblNews.Where(p => p.IsDeleted == false).ToList();
        }

        public tblNews GetNews(int tblNewsId)
        {
            return dbMyNews.tblNews.FirstOrDefault(p => p.tblNewsId == tblNewsId);
        }

        public int AddNews(tblNews News)
        {
            dbMyNews.tblNews.Add(News);
            dbMyNews.SaveChanges();
            return News.tblNewsId;
        }

        public bool EditNews(tblNews News)
        {
            try
            {
                dbMyNews.Entry(News).State = EntityState.Modified;
                dbMyNews.SaveChanges();
                return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

        public bool DeleteNews(int tblNewsId)
        {
            try
            {
                tblNews News = dbMyNews.tblNews.FirstOrDefault(p => p.tblNewsId == tblNewsId);
                News.IsDeleted = true;
                dbMyNews.SaveChanges();
            return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

        public int AddCategory(tblCategory Category)
        {
            dbMyNews.tblCategory.Add(Category);
            dbMyNews.SaveChanges();
            return Category.tblCategoryId;
        }

        public bool DeleteCategory(int tblCategoryId)
        {
            try
            {
                tblCategory Category = dbMyNews.tblCategory.FirstOrDefault(p => p.tblCategoryId == tblCategoryId);
                Category.IsDeleted = true;
                dbMyNews.SaveChanges();
                return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

        public List<tblCategory> GetAllCategory()
        {
            return dbMyNews.tblCategory.Where(p => p.IsDeleted == false).ToList();
        }
    }
}

ولی شما ممکن است درباره‎ی حذف، دوست داشته باشید رکوردها از پایگاه داده حذف شوند و نه این‌که با یک فیلد بولی آن‎ها را مدیریت کنید. در این صورت کد شما می‎تواند این‎گونه نوشته شود:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;

namespace MyNewsWCFLibrary
{
    class MyNewsService : IMyNewsService
    {
        private dbMyNewsEntities dbMyNews = new dbMyNewsEntities();
        public List<tblNews> GetAllNews()
        {
            return dbMyNews.tblNews.ToList();
        }

        public tblNews GetNews(int tblNewsId)
        {
            return dbMyNews.tblNews.FirstOrDefault(p => p.tblNewsId == tblNewsId);
        }

        public int AddNews(tblNews News)
        {
            dbMyNews.tblNews.Add(News);
            dbMyNews.SaveChanges();
            return News.tblNewsId;
        }

        public bool EditNews(tblNews News)
        {
            try
            {
                dbMyNews.Entry(News).State = EntityState.Modified;
                dbMyNews.SaveChanges();
                return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

        public bool DeleteNews(tblNews News)
        {
            try
            {
                dbMyNews.tblNews.Remove(News);
                dbMyNews.SaveChanges();
            return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

        public int AddCategory(tblCategory Category)
        {
            dbMyNews.tblCategory.Add(Category);
            dbMyNews.SaveChanges();
            return Category.tblCategoryId;
        }

        public bool DeleteCategory(tblCategory Category)
        {
            try
            {
                dbMyNews.tblCategory.Remove(Category);
                dbMyNews.SaveChanges();
                return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

        public List<tblCategory> GetAllCategory()
        {
            return dbMyNews.tblCategory.ToList();
        }
    }
}

البته باید در نظر داشته باشید که در صورت هر گونه تغییر در پارامترهای ورودی، لایه‌ی Interface نیز باید تغییر کند. گونه‌ی دیگر نوشتن متد حذف خبر می‌تواند به صورت زیر باشد:

public bool DeleteNews(int tblNewsId)
        {
            try
            {
                tblNews News = dbMyNews.tblNews.FirstOrDefault(p => p.tblNewsId == tblNewsId);
                dbMyNews.tblNews.Remove(News); 
                dbMyNews.SaveChanges();
            return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

در بخش 5 درباره‌ی تغییرات App.Config خواهم نوشت.

مطالب دوره‌ها
آشنایی با AOP IL Weaving
IL Weaving در AOP به معنای اتصال Aspects تعریف شده، پس از کامپایل برنامه به فایل‌های باینری نهایی است. اینکار با ویرایش اسمبلی‌ها در سطح IL یا کد میانی صورت می‌گیرد. بنابراین در این حالت دیگر یک محصور کننده و پروکسی، در این بین جهت مزین سازی اشیاء، در زمان اجرای برنامه تشکیل نمی‌شود. بلکه فراخوانی Aspects به معنای فراخوانی واقعی قطعه کدهایی است که به اسمبلی‌های برنامه پس از کامپایل آن‌ها تزریق شده‌اند.
در دنیای دات نت، ابزارهای چندی امکان انجام IL Weaving را فراهم ساخته‌اند که تعدادی از آن‌ها به قرار ذیل هستند:
- PostSharp
- LOOM.NET
- Wicca
و ...

در بین این‌ها، PostSharp معروفترین فریم ورک AOP بوده و در ادامه از آن استفاده خواهیم کرد.


پیشنیاز ادامه بحث

ابتدا یک پروژه کنسول جدید را آغاز کرده و سپس در خط فرمان پاور شل نوگت در VS.NET دستور ذیل را اجرا کنید:
 PM> Install-Package PostSharp
به این ترتیب ارجاعی به PostSharp به پروژه جاری اضافه خواهد شد. البته حجم آن نسبتا بالا است؛ نزدیک به 20 مگ به همراه ابزارهای تزریق کد همراه با آن. مجوز استفاده از آن نیز تجاری و مدت دار است.


مراحل ایجاد یک Aspect برای پروسه IL Code Weaving

ابتدا یک کلاس پایه مشتق شده از کلاسی ویژه موجود در یکی از فریم ورک‌های AOP باید تعریف شود. مرحله بعد، کار اتصال این Aspect می‌باشد که توسط پردازشگر ثانویه IL Code Weaving انجام می‌شود.
در ادامه قصد داریم همان مثال LoggingInterceptor قسمت دوم این سری را با استفاده از IL Code Weaving پیاده سازی کنیم.
using System;

namespace AOP03
{
    public class MyType
    {
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new MyType().DoSomething("Test", 1);
        }
    }
}
کدهای برنامه همانند قبل است. اما اینبار بجای استفاده از Interceptors، با ارث بری از کلاس OnMethodBoundaryAspect کتابخانه PostSharp شروع خواهیم کرد:
using System;
using PostSharp.Aspects;

namespace AOP03
{
    [Serializable]
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            Console.WriteLine("On Entry");
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            Console.WriteLine("On Exit");
        }

        public override void OnSuccess(MethodExecutionArgs args)
        {
            Console.WriteLine("On Success");
        }

        public override void OnException(MethodExecutionArgs args)
        {
            Console.WriteLine("On Exception");
        }
    }
}
نیاز است این کلاس توسط ویژگی Serializable مزین شود تا توسط PostSharp قابل استفاده گردد. همانطور که ملاحظه می‌کنید، مراحل مختلف اجرای یک Aspcet در اینجا با override متدهای کلاس پایه OnMethodBoundaryAspect پیاده سازی شده‌اند. این مراحل را پیشتر در زمان استفاده از Interceptors توسط try/finally/catch بررسی کرده بودیم.
اکنون اگر برنامه را اجرا کنیم، اتفاق خاصی رخ نداده و همان خروجی معمول متد DoSomething در کنسول نمایش داده خواهد شد. بنابراین در مرحله بعد نیاز است تا این Aspect را به کدهای برنامه متصل کنیم.
کلاس OnMethodBoundaryAspect در کتابخانه PostSharp، از کلاس MulticastAttribute مشتق می‌شود. بنابراین LoggingAspect ایی را که ایجاد کرده‌ایم نیز می‌توان به صورت یک ویژگی به متد‌های مورد نظر خود افزود:
    public class MyType
    {
        [LoggingAspect]
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }
اکنون اگر برنامه را اجرا کنیم، با خروجی زیر مواجه خواهیم شد:
 On Entry
DoSomething(Test, 1);
On Success
On Exit
برای اینکه بتوان عملیات رخ داده را بهتر توضیح داد می‌تواند از یک دی‌کامپایلر مانند برنامه معروف Reflector استفاده کرد:
public void DoSomething(string data, int i)
{
    <>z__Aspects.a0.OnEntry(null);
    try
    {
        Console.WriteLine("DoSomething({0}, {1});", data, i);
        <>z__Aspects.a0.OnSuccess(null);
    }
    catch (Exception)
    {
        <>z__Aspects.a0.OnException(null);
        throw;
    }
    finally
    {
        <>z__Aspects.a0.OnExit(null);
    }
}
این کدی است که به صورت پویا توسط PostSharp به اسمبلی نهایی فایل اجرایی برنامه تزریق شده است.

خوب! این یک روش اتصال Aspects به برنامه است. اما اگر همانند Interceptors بخواهیم Aspect تعریف شده را سراسری اعمال کنیم چکار باید کرد (بدون نیاز به قرار دادن ویژگی بر روی تک تک متدها)؟
برای اینکار ابتدا نیاز است میدان عملکرد Aspect تعریف شده را توسط ویژگی MulticastAttributeUsage محدود کنیم تا برای مثال به خواص اعمال نشوند:
 [Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, TargetMemberAttributes = MulticastAttributes.Instance)]
public class LoggingAspect : OnMethodBoundaryAspect
سپس فایل AssemblyInfo.cs استاندارد پروژه را گشوده و سطر زیر را به آن اضافه کنید:
 [assembly: LoggingAspect(AttributeTargetTypes = "AOP03.*")]
توسط AttributeTargetTypes می‌توان اعمال این Aspect را به یک فضای نام خاص نیز محدود کرد.

مزیت روش IL Code Weaving نسبت به Interceptors، کارآیی و سرعت بالاتر است. از این جهت که کدهایی که قرار است اجرا شوند، پیشتر در اسمبلی برنامه قرار گرفته‌اند و نیازی نیست تا در زمان اجرا، کدی به برنامه به صورت پویا تزریق گردد.
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
یک متد الحاقی لاگ ELMAH را ایجاد کنید:
using System;
using System.Text;
using Elmah;

namespace Common.WebToolkit
{
    public static class ElmahLogEx
    {
        public static void LogException(this string ex)
        {
            if (string.IsNullOrWhiteSpace(ex))
                return;
            LogException(new Exception(ex));
        }

        public static void LogException(this Exception ex)
        {
            if (ex == null) return;
            try
            {
                ErrorSignal.FromCurrentContext().Raise(ex);
            }
            catch
            {
                ErrorLog.GetDefault(null).Log(new Error(ex));
            }
        }
    }
}
سپس
- در کتابخانه‌ی فوق به قسمت ScheduledTasksCoordinator.Current.OnUnexpectedException هم دقت داشته باشد؛ مطابق مثال ارائه شده. این موارد را هم لاگ کنید:
 ScheduledTasksCoordinator.Current.OnUnexpectedException =
                    (exception, scheduledTask) => (scheduledTask.Name + ":" + exception).LogException();
- ابتدا و انتهای هر Task را لاگ کنید (متد الحاقی فوق را به صورت معمولی و با پیام‌هایی مشخص، در ابتدا و انتهای هر Task فراخوانی کنید؛ تا در لاگ‌های ELMAH ظاهر شوند).
- شروع به کار برنامه و خاتمه‌ی آن‌را لاگ کنید (متد الحاقی فوق را با پیام‌هایی مشخص، در متدهای Application_Start و Application_End فایل Global.asax.cs فراخوانی کنید تا مشخص شود که آیا برنامه خاتمه یافته‌است یا خیر).