اشتراک‌ها
تغییرات ASP.NET Core در NET 7 Release Candidate 1.

.NET 7 Release Candidate 1 (RC1) is now available and includes many great new improvements to ASP.NET Core.

Here’s a summary of what’s new in this preview release:

  • Dynamic authentication requests in Blazor WebAssembly
  • Handle location changing events
  • Blazor WebAssembly debugging improvements
  • .NET WebAssembly build tools for .NET 6 projects
  • .NET JavaScript interop on WebAssembly
  • Kestrel full certificate chain improvements
  • Faster HTTP/2 uploads
  • HTTP/3 improvements
  • Experimental Kestrel support for WebTransport over HTTP/3
  • Experimental OpenAPI support for gRPC JSON transcoding
  • Rate limiting middleware improvements
  • macOS dev-certs improvements 
تغییرات ASP.NET Core در NET 7 Release Candidate 1.
مطالب دوره‌ها
اصول طراحی یک سیستم افزونه پذیر به کمک StructureMap
قصد داریم سیستمی را طراحی کنیم که افزونه‌های خود را در زمان اجرا از مسیری خاص خوانده و سپس وهله‌های آن‌ها‌را جهت استفاده در دسترس قرار دهد. برنامه‌ای که در اینجا مورد بررسی قرار می‌گیرد، یک برنامه‌ی WinForms ساده است؛ به نام WinFormsWithPluginSupport. اما اصول کلی مطرح شده، در تمام فناوری‌های دیگر دات نتی نیز کاربرد دارد و یکسان است.


تهیه قرارداد

یک پروژه‌ی Class library به نام PluginsBase را به Solution جاری اضافه کنید. به آن اینترفیس قرار داد پلاگین‌های برنامه خود را اضافه نمائید. برای مثال:
namespace PluginsBase
{
    public interface IPlugin
    {
        string Name { get; }
        void Run();
    }
}
هر پلاگین دارای یک نام یا توضیح خاص خود خواهد بود به همراه متدی برای اجرای منطق مرتبط با آن.


تهیه سه پلاگین جدید

به Solution جاری سه پروژه‌ی مجزای Class library با نام‌های plugin1 تا 3 را اضافه کنید. در ادامه به هر پلاگین، ارجاعی را به اسمبلی PluginsBase، برای دریافت قرارداد پیاده سازی منطق پلاگین، اضافه نمائید. هدف این است که اینترفیس IPlugin، در این اسمبلی‌ها قابل دسترسی شود.
هر پلاگین هم دارای برای مثال کدهایی مانند کد ذیل خواهد بود که در آن صرفا نام آن‌ها به 2 و 3 تنظیم می‌شود.
using PluginsBase;

namespace Plugin1
{
    public class Plugin1Main : IPlugin
    {
        public string Name
        {
            get { return "Test 1"; }
        }

        public void Run()
        {
            // todo: ...
        }
    }
}


کپی خودکار پلاگین‌ها به پوشه‌ی مخصوص آن‌ها

به پروژه‌ی WinFormsWithPluginSupport مراجعه کنید. در پوشه‌ی bin\debug آن یک پوشه‌ی جدید به نام Plugins ایجاد نمائید. بدیهی است هربار که پلاگین‌های برنامه تغییر کنند نیاز است اسمبلی‌های نهایی آن‌ها را به این پوشه کپی نمائیم. اما راه بهتری نیز وجود دارد. به خواص هر کدام از پروژه‌های پلاگین مراجعه کرده و برگه‌ی Build events را باز کنید.


در اینجا قسمت post-build event را به نحو ذیل تغییر دهید:
 Copy "$(ProjectDir)$(OutDir)$(TargetName).*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"
این کار را برای هر سه پلاگین تکرار کنید.
به این ترتیب هربار که پلاگین جاری کامپایل شود، پس از آن به صورت خودکار به پوشه‌ی plugins تعیین شده، کپی می‌شود و دیگر نیازی به کپی دستی نخواهد بود.
تنظیم فوق، تنها اسمبلی اصلی پروژه را به پوشه‌ی bin\debug\plugins کپی می‌کند. اگر می‌خواهید تمام فایل‌ها کپی شوند، از تنظیم ذیل استفاده کنید:
 Copy "$(ProjectDir)$(OutDir)*.*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"


اضافه کردن وابستگی‌های اصلی پروژه‌ی WinForms

در ادامه بسته‌ی نیوگت StructureMap را به پروژه‌ی WinForms از طریق دستور ذیل اضافه کنید:
 PM> install-package structuremap
همچنین این پروژه تنها نیاز دارد ارجاع مستقیمی را به اسمبلی PluginsBase ابتدای مطلب داشته باشد. از آن، جهت تنظیمات اولیه یافتن افزونه‌ها استفاده می‌کنیم.


تعریف محل ثبت پلاگین‌ها

روش‌های متفاوتی برای کار با StructureMap وجود دارد. یکی از آن‌ها تعریف کلاسی است مشتق شده از کلاس Registry آن به نحو ذیل:
using System.IO;
using System.Windows.Forms;
using PluginsBase;
using StructureMap.Configuration.DSL;
using StructureMap.Graph;

namespace WinFormsWithPluginSupport.Core
{
    public class PluginsRegistry : Registry
    {
        public PluginsRegistry()
        {
            this.Scan(scanner =>
            {
                scanner.AssembliesFromPath(
                    path: Path.Combine(Application.StartupPath, "plugins"),
                    // یک اسمبلی نباید دوبار بارگذاری شود
                    assemblyFilter: assembly =>
                    {
                        return !assembly.FullName.Equals(typeof(IPlugin).Assembly.FullName);
                    });
                scanner.AddAllTypesOf<IPlugin>().NameBy(item => item.FullName);
            });
        }
    }
}
در اینجا مشخص کرده‌ایم که اسمبلی‌های پوشه plugins را که یک سطح پایین‌تر از پوشه‌ی اجرایی برنامه قرار می‌گیرند، خوانده و در این بین آن‌هایی را که پیاده سازی از اینترفیس IPlugin دارند، در دسترس قرار دهد.

یک نکته‌ی مهم
در قسمت assemblyFilter تعیین کرده‌ایم که اسمبلی تکراری PluginBase بارگذاری نشود. چون این اسمبلی هم اکنون به برنامه‌ی WinForms ارجاع دارد. رعایت این نکته جهت رفع تداخلات آتی بسیار مهم است. همچنین این فایل در پوشه‌ی Plugins نیز نباید حضور داشته باشد وگرنه شاهد بارگذاری افزونه‌ها نخواهید بود.


سپس نیاز به وهله سازی Container آن و معرفی این کلاس PluginsRegistry می‌باشد:
using System;
using System.Threading;
using StructureMap;

namespace WinFormsWithPluginSupport
{
    public static class IocConfig
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);

        public static IContainer Container
        {
            get { return _containerBuilder.Value; }
        }

        private static Container defaultContainer()
        {
            return new Container(x =>
            {
                x.AddRegistry<PluginsRegistry>();
            });
        }
    }
}


تنظیمات ابتدایی WinForms برای دسترسی به امکانات StructureMap

به فرم اصلی برنامه مراجعه کرده و به سازنده‌ی آن IContainer را اضافه کنید. از این اینترفیس جهت دسترسی به پلاگین‌های برنامه استفاده خواهیم کرد.
using System.Windows.Forms;
using StructureMap;

namespace WinFormsWithPluginSupport
{
    public partial class FrmMain : Form
    {
        private readonly IContainer _container;

        public FrmMain(IContainer container)
        {
            _container = container;
            InitializeComponent();
        }
    }
}
اکنون برنامه دیگر کامپایل نخواهد شد؛ چون در فایل Program.cs وهله سازی مستقیمی از FrmMain وجود دارد. این وهله سازی را اکنون به StructureMap محول می‌کنیم تا مشکل برطرف شود:
using System;
using System.Windows.Forms;

namespace WinFormsWithPluginSupport
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(IocConfig.Container.GetInstance<FrmMain>());
        }
    }
}
زمانیکه از متد IocConfig.Container.GetInstance استفاده می‌شود، تا هر تعداد سطحی که تعریف شده، سازنده‌های کلاس‌های مرتبط وهله سازی می‌شوند. در اینجا نیاز است سازنده‌ی کلاس FrmMain وهله سازی شود. چون IContainer اینترفیس اصلی خود StructureMap است، آن‌را شناخته و به صورت خودکار وهله سازی می‌کند. اگر اینترفیس دیگری را ذکر کنید، نیاز است مطابق معمول آن‌را در کلاس IocConfig و متد defaultContainer آن معرفی نمائید.


بارگذاری و اجرای افزونه‌ها

دو دکمه‌ی Run و ReLoad را به فرم اصلی برنامه با کدهای ذیل اضافه کنید:
using System.Linq;
using System.Windows.Forms;
using PluginsBase;
using StructureMap;
using WinFormsWithPluginSupport.Core;

namespace WinFormsWithPluginSupport
{
    public partial class FrmMain : Form
    {
        private readonly IContainer _container;

        public FrmMain(IContainer container)
        {
            _container = container;
            InitializeComponent();
        }

        private void BtnRun_Click(object sender, System.EventArgs e)
        {
            var plugins = _container.GetAllInstances<IPlugin>().ToList();
            foreach (var plugin in plugins)
            {
                plugin.Run();
            }
        }

        private void BtnReload_Click(object sender, System.EventArgs e)
        {
            _container.EjectAllInstancesOf<IPlugin>();
            _container.Configure(x =>
                x.AddRegistry<PluginsRegistry>()
            );
        }
    }
}
در اینجا توسط متد container.GetAllInstances می‌توان به تمامی وهله‌های پلاگین‌های بارگذاری شده، دسترسی یافت و سپس آن‌ها را اجرا کرد.
همچنین در متد ReLoad نحوه‌ی بارگذاری مجدد این پلاگین‌ها را در صورت نیاز مشاهده می‌کنید.
اگر برنامه را اجرا کردید و پلاگینی بارگذاری نشد، به دنبال اسمبلی‌های تکراری بگردید. برای مثال PluginsBase نباید هم در پوشه‌ی اصلی اجرایی برنامه حضور داشته باشد و هم در پوشه‌ی پلاگین‌ها.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
WinFormsWithPluginSupport.zip
 
نظرات مطالب
Blazor 5x - قسمت هفتم - مبانی Blazor - بخش 4 - انتقال اطلاعات از کامپوننت‌های فرزند به کامپوننت والد
یک نکته‌ی تکمیلی: نیاز به دقت در ویژگی «captured into the closure» در حلقه‌های Blazor

برای مثال حلقه‌ی زیر را در نظر بگیرید:
@for( int c = 0; c < 10; c++ )
{
   <li>
       <a href="#" @onclick="@(_=> OnLinkClicked(c))">@c</a>
   </li>
}
فکر می‌کنید پس از پایان این حلقه و رندر UI، اگر بر روی لینکی کلیک شد، چه مقداری به متد OnLinkClicked ارسال می‌شود؟
برخلاف تصور، با کلیک بر روی تمام لینک‌ها، فقط عدد ثابت 10 به متد  OnLinkClicked ارسال می‌شود. علت آن، همان نکات مطلب «بررسی مفهوم Captured Variable در زبان سی شارپ» است که در حین تشکیل حلقه‌های Blazor هم صادق هستند.
برای رفع این مشکل، از یکی از دو روش زیر می‌توان استفاده کرد:
Capture متغیر داخل حلقه:
@for( int c = 0; c < 10; c++ )
{
   var current = c;
   <li>
       <a href="#" @onclick="@(_=> OnLinkClicked(current))">@current</a>
   </li>
}
و یا ایجاد یک حلقه‌ی foreach بر روی یک Enumerable:
@foreach(var c in Enumerable.Range(0,10))
{
   <li>
       <a href="#" @onclick="@(_=> OnLinkClicked(c))">@c</a>
   </li>
}
نظرات مطالب
Blazor 5x - قسمت 21 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 1 - افزودن قالب ابتدایی Identity
معرفی دو پروژه‌ی تکمیلی
اگر علاقمند به استفاده‌ی از ASP.NET Core Identity نیستید، دو پروژه بر پایه‌ی مطالب «اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core بدون استفاده از سیستم Identity» و «اعتبارسنجی مبتنی بر JWT در ASP.NET Core بدون استفاده از سیستم Identity» در ذیل توسعه یافته‌اند:
- BlazorServer CookieAuthentication (مخصوص Blazor Server)
- JWT WebApi Blazor (مخصوص Blazor WASM)
نظرات مطالب
Blazor 5x - قسمت دهم - مبانی Blazor - بخش 7 - مسیریابی
یک نکته‌ی تکمیلی: امکان تزریق وابستگی‌ها در سازنده‌ی کلاس‌های کامپوننت‌ها در Blazor 7x

اگر از روش code-behind جهت توسعه‌ی کامپوننت‌های Blazor استفاده می‌کنید، در دات نت 7 و Blazor 7x می‌توانید علاوه بر بکارگیری ویژگی [Inject]، از تزریق مستقیم در سازنده‌ی کلاس‌ها نیز استفاده کنید:
public class MyComponent : ComponentBase
{
   public MyComponent(IMyService myService) { ... }
}
اطلاعات بیشتر
نظرات مطالب
Blazor 5x - قسمت 21 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 1 - افزودن قالب ابتدایی Identity
- خارج از موضوع بحث نکنید.
- ساختار ابتدایی پروژه‌های Blazor Server را در مطلب «Blazor 5x - قسمت دوم - بررسی ساختار اولیه‌ی پروژه‌های Blazor» بررسی کردیم که در آن AddServerSideBlazor و غیره بحث شده‌اند که شامل موارد دیگری مانند مشخص سازی روش رندر کامپوننت‌ها و ... سیم‌کشی‌های دیگری هم هست برای اجرای بدون خطا:
<component type="typeof(App)" render-mode="ServerPrerendered" />
نظرات مطالب
روش ایجاد پروژه‌ها‌ی کتابخانه‌ای کامپوننت‌های Blazor
یک نکته‌ی تکمیلی: استفاده از فقط یک فضای نام برای کل پروژه‌ی کتابخانه‌ای
فرض کنید که یک کتابخانه از کامپوننت‌های Blazor را با پوشه‌بندی‌های مختلفی ایجاد کرده‌اید. هر کدام از این پوشه‌ها به صورت خودکار به همراه فضای نام متناظر با نام آن پوشه هم خواهند بود. برای مثال اگر در پوشه‌ی C:\Prog\BlazorLib\Folder1 کامپوننت Component1.razor قرار گیرد، فضای نام پیش‌فرض آن BlazorLib.Folder1 خواهد بود. این مساله توزیع و استفاده‌ی از کامپوننت‌های پوشه‌بندی شده را برای استفاده کنندگان ثالث مشکل می‌کند؛ چون باید به ازای هر پوشه، یک using را تعریف کنند. برای استفاده از تنها یک فضای نام در کل پروژه می‌توان از روش
@namespace BlazorLib
در ابتدای تمام فایل‌های razor. استفاده کرد. این تنظیم، فضای نام پیش‌فرض پوشه‌ها را بازنویسی می‌کند.
مطالب
فارسی کردن اعداد در صفحات blazor
برای فارسی کردن اعداد در صفحات  HTML قبلا از  کتابخانه‌های  jquery  یا javascript استفاده می‌کردیم. در این مقاله قصد دارم فارسی کردن اعداد را به کمک کامپوننت‌های  blazor انجام دهم. البته بهتر است از این روش برای وقتی استفاده کنیم که قرار است متن ما فقط شامل اعداد باشد؛ مثلا فیلدهای عددی یک جدول.

یک کامپوننت جدید را به نام PersianNumber به صورت زیر ایجاد می‌کنیم. در این کامپوننت یک پارامتر را به نام Number داریم که کاراکتر به کاراکتر آن را پیمایش کرده و اعداد انگلیسی را با اعداد فارسی جایگزین می‌کنیم:
@Number

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

    protected override Task OnInitializedAsync()
    {
        var persianDic = new Dictionary<char, char>
        {
            {'0','۰'},
            {'1','۱'},
            {'2','۲'},
            {'3','۳'},
            {'4','۴'},
            {'5','۵'},
            {'6','۶'},
            {'7','۷'},
            {'8','۸'},
            {'9','۹'},

        };
        var number = Number.ToString();
        var ech = number.ToCharArray();
        for (int i = 0; i < ech.Length; i++)
        {
            persianDic.TryGetValue(ech[i], out char pch);
            if (pch == null)
                continue;
            ech[i] = pch;
        }
        Number = new string(ech);
        return base.OnInitializedAsync();
    }
}
حالا از این کامپوننت در هر جای صفحه که مثلا عددی را از دیتابیس (api) دریافت کرده و می‌خواهیم نمایش دهیم، استفاده می‌کنیم:
... 
@foreach (var item in _items)
                {
                    <tr>
                        <td class="h6 text-color-1">@item.Title</td>
                        <td> <PersianNumber Number="@item.Price.ToString()"/> ریال</td>
                    </tr>
                }
...

اشتراک‌ها
منتشر شد Blazor WebAssembly 3.2.0 Preview 4
A new preview update of Blazor WebAssembly is now available! Here’s what’s new in this release:

  • Access host environment during startup
  • Logging improvements
  • Brotli precompression
  • Load assemblies and runtime in parallel
  • Simplify IL linker config for apps
  • Localization support
  • API docs in IntelliSense 
منتشر شد Blazor WebAssembly 3.2.0 Preview 4