this.categoryBindingSource.DataSource = _context.Categories.Local.ToBindingList();
اما ... چقدر خوب میشد که امکان ترکیب هردوی اینها با هم در یک برنامه وجود میداشت؛ یعنی داشتن یک آغاز سریع، به همراه تعاملات سریع با DOM. به همین جهت Auto Render Mode به Blazor 8x اضافه شدهاست.
نحوهی عملکرد حالت رندر تعاملی خودکار در Blazor 8x
زمانیکه از قرار است از Auto Render Mode استفاده شود، یعنی در نهایت به سراغ حالت رندر وباسمبلی رفتن؛ اما به شرطیکه که فریمورک، مطمئن شود میتواند تمام فایلهای مرتبط را خیلی سریع و در کمتر از 100 میلیثانیه تامین کند که عموما یک چنین حالتی به معنای از پیش دریافت کردن این فایلها و کش شده بودن آنها در مرورگر است. اما اگر یک چنین تضمینی وجود نداشته باشد، از همان ابتدای کار تصمیم میگیرد که باید کامپوننت را از طریق نگارش Blazor Server آن ارائه دهد، تا آغاز سریعی را سبب شود. در این بین هم در پشت صحنه (یعنی زمانیکه کاربر مشغول به کار با نگارش Blazor Server کامپوننت است)، شروع به دریافت فایلهای مرتبط با نگارش وباسمبلی کامپوننت و برنامه میشود تا آنها را کش کرده و برای بار بعدی بارگذاری صفحه و نمایش اطلاعات آن، به سرعت از آنها استفاده کند.
یک چنین حالتی برای کاربران به این معنا است که به محض گشودن برنامه و صفحهای، قادر به استفادهی از آن هستند و برای بارهای بعدی استفاده، دیگر نیازی به اتصال دائم SignalR یک جزیرهی تعاملی Blazor Server نداشته و در نتیجه بار کمتری به سرور تحمیل خواهد شد (مقیاس پذیری بیشتر) و همچنین پردازش DOM بسیار سریعتری را نیز شاهد خواهند بود (کار با نگارش Blazor WASM درون مرورگر).
همانطور که در این تصویر هم مشخص است، برای بار اول نمایش یک چنین جزیرههایی، یک اتصال وبسوکت برقرار میشود که به معنای فعال شدن حالت جزیرهای Blazor Server است که در قسمت پنجم بررسی کردیم. در این بین فایلهای Blazor WASM این جزیره هم دریافت و کش میشوند که در کنسول توسعه دهندههای مرورگر، لاگ شدهاست. این اتصال وبسوکت، در بار اول نمایش این کامپوننت، بسته نخواهد شد؛ تا زمانیکه کاربر به صفحهای دیگر مراجعه کند. در دفعهی بعدی که درخواست نمایش این صفحه را داشته باشیم، چون اطلاعات نگارش وباسمبلی آن کش شدهاست، از همان ابتدای کار نگارش وب اسمبلی را بارگذاری و راهاندازی میکند.
تفاوت قالب پروژههای Auto Render Mode با سایر حالتهای رندر در Blazor 8x
برای ایجاد قالب ابتدایی پروژهی یک چنین حالت رندری، از دستور dotnet new blazor --interactivity Auto استفاده میشود که حالت تعاملی آن به Auto تنظیم شدهاست. در نگاه اول، Solution ایجاد شدهی آن، بسیار شبیه به Solution جزیرههای تعاملی Blazor WASM است که در قسمت هفتم به همراه یک مثال کامل بررسی کردیم؛ یعنی از دو پروژهی سمت سرور و سمت کلاینت تشکیل میشود و دارای این تفاوتها است:
در فایل Program.cs پروژهی سمت سرور آن، افزوده شدن هر دو حالت جزایر تعاملی Blazor Server و همچنین Blazor WASM را مشاهده میکنیم:
// ... builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); // ... app.MapRazorComponents<App>() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(Counter).Assembly);
الف) امکان تعریف صفحات فقط SSR در پروژهی سمت سرور
ب) امکان داشتن جزیرههای تعاملی فقط Blazor Server در پروژهی سمت سرور
ج) امکان داشتن جزیرههای تعاملی فقط Blazor Wasm در پروژهی سمت کلاینت
د) به همراه امکان تعریف جزیرهای تعاملی Auto Render Mode در پروژهی سمت کلاینت
یک نکته: در این تنظیمات، متد AddAdditionalAssemblies، امکان استفاده از کامپوننتهای قرار گرفتهی در سایر اسمبلیها و پروژهها را میسر میکند.
نحوهی تعریف کامپوننتهایی که قرار است توسط Auto Render Mode ارائه شوند
باتوجه به اینکه این نوع کامپوننتها در نهایت قرار است به صورت وباسمبلی رندر شوند، آنها را باید در پروژهی سمت کلاینت قرار داد و به نکات مرتبط با توسعهی آنها که در قسمت هفتم پرداختیم، توجه داشت.
همچنین مانند سایر حالتهای رندر، به دو طریق میتوان مشخص کرد که یک کامپوننت باید به چه صورتی رندر شود:
الف) استفاده از دایرکتیو حالت رندر با مقدار InteractiveAuto در ابتدای تعریف یک کامپوننت
@rendermode InteractiveAuto
<Banner @rendermode="@InteractiveAuto" Text="Hello"/>
@using static Microsoft.AspNetCore.Components.Web.RenderMode
ایجاد یک برنامهی Minimal-API جدید در دات نت 8
پروژهای را که در اینجا پیگیری میکنیم، بر اساس قالب استاندارد تولید شدهی توسط دستور dotnet new webapi تکمیل میشود.
ایجاد یک صفحهی Blazor 8x به همراه مسیریابی و دریافت پارامتر
در ادامه قصد داریم که یک کامپوننت جدید را به نام SsrTest.razor در پوشهی جدید Components\Tests ایجاد کرده و برای آن مسیریابی از نوع page@ هم تعریف کنیم. یعنی نهفقط قصد داریم آنرا توسط RazorComponentResult رندر کنیم، بلکه میخواهیم اگر آدرس آنرا در مرورگر هم وارد کردیم، قابل دسترسی باشد.
به همین جهت یک پوشهی جدید را به نام Components در ریشهی پروژهی Web API جاری ایجاد میکنیم، با این محتوا:
برای ایده گرفتن از محتوای مورد نیاز، به «معرفی قالبهای جدید شروع پروژههای Blazor در دات نت 8» قسمت دوم این سری مراجعه کرده و برای مثال قالب سادهترین حالت ممکن را توسط دستور زیر تولید میکنیم (در یک پروژهی مجزا، خارج از پروژهی جاری):
dotnet new blazor --interactivity None
- فایل Imports.razor_ ساده شده برای سهولت کار با فضاهای نام در کامپوننتهای Blazor (فضاهای نامی را که در آن وجود ندارند و مرتبط با پروژهی دوم هستند، حذف میکنیم).
- فایل App.razor، برای تشکیل نقطهی آغازین برنامهی Blazor.
- فایل Routes.razor برای معرفی مسیریابی صفحات Blazor تعریف شده.
- پوشهی Layout برای معرفی فایل MainLayout.razor که در Routes.razor استفاده شدهاست.
و ... یک فایل آزمایشی جدید به نام Components\Tests\SsrTest.razor با محتوای زیر:
@page "/ssr-page/{Data:int}" <PageTitle>An SSR component</PageTitle> <h1>An SSR component rendered by a Minimal-API!</h1> <div> Data: @Data </div> @code { [Parameter] public int Data { get; set; } }
تغییرات مورد نیاز در فایل Program.cs برنامهی Web-API برای فعالسازی رندر سمت سرور Blazor
در ادامه کل تغییرات مورد نیاز جهت اجرای این برنامه را مشاهده میکنید:
var builder = WebApplication.CreateBuilder(args); // ... builder.Services.AddRazorComponents(); // ... // http://localhost:5227/ssr-component?data=2 // or it can be called directly http://localhost:5227/ssr-page/2 app.MapGet("/ssr-component", (int data = 1) => { var parameters = new Dictionary<string, object?> { { nameof(SsrTest.Data), data }, }; return new RazorComponentResult<SsrTest>(parameters); }); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>(); app.Run(); // ...
- همین اندازه تغییر در جهت فعالسازی رندر سمت سرور کامپوننتهای Blazor در یک برنامهی ASP.NET Core کفایت میکند. یعنی اضافه شدن:
AddRazorComponents ،UseAntiforgery و MapRazorComponents
- در اینجا نحوهی ارسال پارامترها را به یک RazorComponentResult نیز مشاهده میکنید.
- در حالت فراخوانی از طریق مسیر endpoint (یعنی فراخوانی مسیر http://localhost:5227/ssr-component در مثال فوق)، خود کامپوننت فراخوانی شده، بدون layout تعریف شدهی در فایل App.razor، رندر میشود. علت اینجا است که layout برنامه به همراه کامپوننت Router و RouteView آن فعال میشود که این دو هم مختص به صفحات دارای مسیریابی Blazor هستند و برای رندر کامپوننتهای خالص آن بکار گرفته نمیشوند. خروجی RazorComponentResult تنها یک static SSR خالص است؛ مگر اینکه فایل blazor.web.js را نیز بارگذاری کند.
یک نکته: اگر در حالت رندر توسط RazorComponentResult، علاقمند به استفادهی از layout هستید، میتوان از کامپوننت LayoutView داخل یک کامپوننت فرضی به صورت زیر استفاده کرد؛ اما این مورد هم شامل اطلاعات فایل App.razor نمیشود:
<LayoutView Layout="@typeof(MainLayout)"> <PageTitle>Home</PageTitle> <h2>Welcome to your new app.</h2> </LayoutView>
سؤال: آیا در این حالت کامپوننتهای تعاملی هم کار میکنند؟
پاسخ: بله. فقط برای ایده گرفتن، یک نمونه پروژهی تعاملی Blazor 8x را در ابتدا ایجاد کنید و قسمتهای اضافی AddRazorComponents و MapRazorComponents آنرا در اینجا کپی کنید؛ یعنی برای مثال جهت فعالسازی کامپوننتهای تعاملی Blazor Server، به این دو تغییر زیر نیاز است:
// ... builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // ... app.MapRazorComponents<App>().AddInteractiveServerRenderMode(); // ...
<script src="_framework/blazor.web.js"></script>
@rendermode InteractiveServer <h1>Counter</h1> <p role="status">Current count: @_currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int _currentCount; private void IncrementCount() { _currentCount++; } }
// http://localhost:5227/server-interactive-component app.MapGet("/server-interactive-component", () => new RazorComponentResult<Counter>());
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Interactive server component</title> <base href="/"/> </head> <body> <h1>Interactive server component</h1> <Counter/> <script src="_framework/blazor.web.js"></script> </body> </html>
سپس تعریف endpoint متناظر را به صورت زیر تغییر میدهیم:
// http://localhost:5227/server-interactive-component app.MapGet("/server-interactive-component", () => new RazorComponentResult<CounterInteractive>());
سؤال: آیا میتوان این خروجی static SSR کامپوننتهای بلیزر را در سرویسهای یک برنامه ASP.NET Core هم دریافت کرد؟
منظور این است که آیا میتوان از یک کامپوننت Blazor، به همراه تمام پیشرفتهای Razor در آن که در Viewهای MVC قابل دسترسی نیستند، بهشکل یک رشتهی خالص، خروجی گرفت و برای مثال از آن بهعنوان قالب پویای محتوای ایمیلها استفاده کرد؟
پاسخ: بله! زیر ساخت RazorComponentResult که از سرویس HtmlRenderer استفاده میکند، بدون نیاز به برپایی یک endpoint هم قابل دسترسی است:
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; namespace WebApi8x.Services; public class BlazorStaticRendererService { private readonly HtmlRenderer _htmlRenderer; public BlazorStaticRendererService(HtmlRenderer htmlRenderer) => _htmlRenderer = htmlRenderer; public Task<string> StaticRenderComponentAsync<T>() where T : IComponent => RenderComponentAsync<T>(ParameterView.Empty); public Task<string> StaticRenderComponentAsync<T>(Dictionary<string, object?> dictionary) where T : IComponent => RenderComponentAsync<T>(ParameterView.FromDictionary(dictionary)); private Task<string> RenderComponentAsync<T>(ParameterView parameters) where T : IComponent => _htmlRenderer.Dispatcher.InvokeAsync(async () => { var output = await _htmlRenderer.RenderComponentAsync<T>(parameters); return output.ToHtmlString(); }); }
builder.Services.AddScoped<HtmlRenderer>(); builder.Services.AddScoped<BlazorStaticRendererService>();
app.MapGet("/static-renderer-service-test", async (BlazorStaticRendererService renderer, int data = 1) => { var parameters = new Dictionary<string, object?> { { nameof(SsrTest.Data), data }, }; var html = await renderer.StaticRenderComponentAsync<SsrTest>(parameters); return Results.Content(html, "text/html"); });
کدهای کامل این مطلب را میتوانید از اینجا دریافت کنید: WebApi8x.zip
در Blazor میتوان مسیریابیهای پارامتری را به صورت زیر نیز تعریف کرد:
@page "/post/edit/{EditId:int}"
که در اینجا EditId، یک پارامتر مسیریابی از نوع int تعریف شده و به صورت زیر در کدهای صفحهی مرتبط، قابل دسترسی است:
[Parameter] public int? EditId { set; get; }
int تعریف شدهی در این مسیریابی، یک routing constraint و یا یک قید مسیریابی محسوب میشود و استفادهی از آن، چنین مزایایی را به همراه دارد:
- در این حالت فقط EditId های عددی پردازش میشوند و اگر رشتهای دریافت شود، کاربر با خروجی از نوع 404 و یا «یافت نشد»، مواجه خواهد شد.
- امکان اعتبارسنجی مقادیر دریافتی، پیش از ارسال آنها به صفحه و پردازش صفحه.
قیود پیشفرض تعریف شدهی در Blazor
اگر به مستندات مسیریابی Blazor مراجعه کنیم، بهنظر فقط این موارد را میتوان بهعنوان قیود پارامترهای مسیریابی تعریف کرد:
bool, datetime, decimal, double, float, guid, int, long, nonfile
و ... توضیحاتی در مورد اینکه آیا امکان بسط آنها وجود دارند یا خیر، فعلا در مستندات رسمی آن، ذکر نشدهاند.
در Blazor 8x میتوان از قیود مسیریابی سفارشی ASP.NET Core نیز استفاده کرد!
ASP.NET Core سمت سرور، به همراه امکان سفارشی سازی قیودمسیریابی خود نیز هست که آنرا میتوان به کمک اینترفیسIRouteConstraint پیاده سازی کرد:
namespace Microsoft.AspNetCore.Routing { public interface IRouteConstraint { bool Match( HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection); } }
جالب اینجا است که میتوان این نمونههای سفارشی را حداقل در نگارش جدید Blazor 8x SSR نیز استفاده کرد؛ هرچند در مستندات رسمی Blazor هنوز به آن اشارهای نشدهاست.
در امضای متد Match فوق، دو پارامتر routeKey و values آن بیش از مابقی مهم هستند:
- routeKey مشخص میکند که الان کدام پارامتر مسیریابی (مانند EditId در این مطلب) در حال پردازش است.
- values، یک دیکشنری است که کلید هر عضو آن، پارامتر مسیریابی و مقدار آن، مقدار دریافتی از URL جاری است.
- اگر این متد مقدار true را برگرداند، یعنی مسیریابی وارد شدهی به آن، با موفقیت پردازش و اعتبارسنجی شده و میتوان صفحهی مرتبط را نمایش داد؛ در غیراینصورت، کاربر پیام یافت نشدن آن صفحه و مسیر درخواستی را مشاهده میکند.
پیاده سازی یک قید سفارشی رمزگشایی پارامترهای مسیریابی
فرض کنید قصد ندارید که پارامترهای مسیریابی ویرایش رکوردهای خاصی را دقیقا بر اساس Id متناظر عددی آنها در بانک اطلاعاتی، نمایش دهید؛ برای مثال نمیخواهید دقیقا آدرس post/edit/1 را به کاربر نمایش دهید؛ چون نمایش این اعداد عموما ساده و ترتیبی، حدس زدن آنها را ساده کرده و ممکن است در آینده مشکلات امنیتی را به همراه داشته باشد.
میخواهیم از آدرسهای متداول و سادهی عددی زیر:
@page "/post/edit/{EditId:int}"
به آدرس رمزنگاری شدهی زیر برسیم:
@page "/post/edit/{EditId:encrypt}"
اگر به این آدرس جدید دقت کنید، در اینجا از نام قید جدیدی به نام encrypt استفاده شدهاست که جزو قیود پیشفرض سیستم مسیریابی Blazor نیست. روش تعریف آن به صورت زیر است:
using System.Globalization; using DNTCommon.Web.Core; namespace Blazor8xSsrComponents.Utils; public class EncryptedRouteConstraint(IProtectionProviderService protectionProvider) : IRouteConstraint { public const string Name = "encrypt"; public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { ArgumentNullException.ThrowIfNull(routeKey); ArgumentNullException.ThrowIfNull(values); if (!values.TryGetValue(routeKey, out var routeValue)) { return false; } var valueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture); values[routeKey] = string.IsNullOrEmpty(valueString) ? null : protectionProvider.Decrypt(valueString); return true; } }
توضیحات:
- در قیود سفارشی میتوان سرویسها را به سازندهی کلاس تزریق کرد و برای مثال از سرویس IProtectionProviderService که در کتابخانهی DNTCommon.Web.Core تعریف شده، برای رمزگشایی اطلاعات رسیده، استفاده کردهایم.
- یک نام را هم برای آن درنظر گرفتهایم که از این نام در فایل Program.cs به صورت زیر استفاده میشود:
builder.Services.Configure<RouteOptions>(opt => { opt.ConstraintMap.Add(EncryptedRouteConstraint.Name, typeof(EncryptedRouteConstraint)); });
یعنی زمانیکه سیستم مسیریابی به قید جدیدی به نام encrypt میرسد:
@page "/post/edit/{EditId:encrypt}"
آنرا در لیست ConstraintMap ای که به نحو فوق به سیستم معرفی شده، جستجو میکند. اگر این نام یافت شد، سپس کلاس EncryptedRouteConstraint متناظر را نمونه سازی کرده و در جهت پردازش مسیر رسیده، مورد استفاده قرار میدهد.
- در کلاس EncryptedRouteConstraint و متد Match آن، مقدار رشتهای EditId دریافت شدهی از طریق آدرس جاری درخواستی، رمزگشایی شده و بجای مقدار فعلی رمزنگاری شدهی آن درج میشود. همین اندازه برای مقدار دهی خودکار پارامتر EditId ذیل در صفحات مرتبط، کفایت میکند:
[Parameter] public string? EditId { set; get; }
یعنی دیگر نیازی نیست تا در صفحات مرتبط، کار رمزگشایی EditId، به صورت دستی انجام شود.
Wisej 3 has shipped, described as an "alternative for Blazor developers" for building enterprise-level ASP.NET web applications with specialized Visual Studio templates.
گاهی از اوقات شاید نیاز شود تا از یک کنترل Active-X در WPF استفاده شود؛ مثلا هیچ نمایش دهندهی PDF ایی را در ویندوز نمیتوان یافت که امکانات و کیفیت آن در حد Acrobat reader و Active-X آن باشد. یک روش استفاده از آنرا به کمک کنترل WebBrowser در WPF پیشتر در این سایت مطالعه کردهاید. روش معرفی شده برای WinForm هم در WPF قابل استفاده است که در ادامه شرح آن خواهد آمد.
الف) بجای اضافه کردن یک User control مخصوص WPF یک user control از نوع WinForms را به یک پروژه WPF اضافه کنید.
سپس مراحل مشابهی را مانند حالت WinForms، باید طی کرد:
ب) در VS.NET از طریق منوی Tools گزینهی Choose toolbox items ، برگهی Com components را انتخاب کنید.
ج) سپس گزینهی Adobe PDF reader را انتخاب نمائید و بر روی دکمهی OK کلیک کنید.
د) اکنون این کنترل جدید را بر روی فرم user control قسمت الف برنامه قرار دهید. به صورت خودکار COMReference های متناظر هم به پروژه اضافه میشوند.
پس از اینکه کنترل بر روی فرم قرار گرفت بهتر است به خواص آن مراجعه کرده و خاصیت Dock آنرا با Fill مقدار دهی کرد تا کنترل به صورت خودکار در هر اندازهای کل ناحیهی متناظر را پوشش دهد.
کدهای مرتبط با نمایش فایل PDF این کنترل هم به شرح زیر است:
using System.Windows.Forms;
namespace WpfPdfViewer.Controls
{
public partial class AcroReader : UserControl
{
public AcroReader(string fileName)
{
InitializeComponent();
ShowPdf(fileName);
}
public void ShowPdf(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName)) return;
axAcroPDF1.LoadFile(fileName);
axAcroPDF1.setShowToolbar(true);
axAcroPDF1.Show();
}
}
}
خوب، ما تا اینجا یک کنترل Active-X را از طریق یک User controls مخصوص WinForms به پروژهی WPF جاری اضافه کردهایم. برای اینکه بتوانیم این کنترل را درون مثلا یک User control از جنس WPF و XAML نمایش دهیم باید از کنترل WindowsFormsHost استفاده کرد. برای این منظور نیاز است تا ارجاعی را به اسمبلی WindowsFormsIntegration اضافه کنیم. پس از آن کنترل یاد شده قابل استفاده خواهد بود.
برای نمونه کدهای XAML پنجره اصلی برنامه میتواند به صورت زیر باشد:
<Window x:Class="WpfPdfViewer.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">
<Grid>
<WindowsFormsHost x:Name="WindowsFormsHost1" />
</Grid>
</Window>
سپس جهت استفاده از کنترل WindowsFormsHost خواهیم داشت:
using WpfPdfViewer.Controls;
namespace WpfPdfViewer
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
WindowsFormsHost1.Child = new AcroReader(@"PageSummary.pdf");
}
}
}
فقط کافی است شیء Child این کنترل را با وهلهای از یوزرکنترل AcroReader اضافه شده به برنامه مقدار دهی کنیم.
سؤال: این روش زیاد MVVM friendly نیست. به عبارتی Child را نمیتوان از طریق Binding مقدار دهی کرد. آیا راهی برای آن وجود دارد؟
پاسخ: بله. روش متداول برای حل این نوع مشکلات، نوشتن یک DependencyObject و Attached property مناسب میباشد که به آنها Behaviors هم میگویند. برای مثال یک نمونه از این پیاده سازی را در ذیل مشاهده میکنید:
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
namespace WpfPdfViewer.Behaviors
{
public class WindowsFormsHostBehavior : DependencyObject
{
public static readonly DependencyProperty BindableChildProperty =
DependencyProperty.RegisterAttached("BindableChild",
typeof(Control),
typeof(WindowsFormsHostBehavior),
new UIPropertyMetadata(null, BindableChildPropertyChanged));
public static Control GetBindableChild(DependencyObject obj)
{
return (Control)obj.GetValue(BindableChildProperty);
}
public static void SetBindableChild(DependencyObject obj, Control value)
{
obj.SetValue(BindableChildProperty, value);
}
public static void BindableChildPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var windowsFormsHost = o as WindowsFormsHost;
if (windowsFormsHost == null)
throw new InvalidOperationException("This behavior can only be attached to a WindowsFormsHost.");
var control = (Control)e.NewValue;
windowsFormsHost.Child = control;
}
}
}
که نهایتا برای استفاده از آن خواهیم داشت:
<WindowsFormsHost
Behaviors:WindowsFormsHostBehavior.BindableChild="{Binding ...}" />
و در ViewModel برنامه هم مانند مثال فوق، فقط کافی است یک وهله از new AcroReader به این خاصیت قابل انقیاد از نوع Control، انتساب داده شود.
یا حتی میتوان بجای نوشتن یک BindableChild، برای مثال مسیر فایل pdf را به DependencyObject تعریف شده ارسال کرد و سپس در همانجا این وهله سازی و انتسابات صورت گیرد (بجای ViewModel برنامه که اینبار فقط مسیر را تنظیم میکند).
Learn how to harness the power of the Syncfusion UI components from within a Blazor server application. We’ll also integrate the Microsoft Identity technology into our Blazor application to leverage login, registration, authorization and authentication functionality. Syncfusion provides a UI component suite for building powerful web, desktop, and mobile apps.
⭐️ Course Contents ⭐️
⌨️ (0:00:13) Introduction
⌨️ (0:00:49) Course Overview
⌨️ (0:10:25) Technologies used to Develop the Sales Management Application
⌨️ (0:13:20) Getting Started - Create the Blazor Project through Visual Studio 2022
⌨️ (0:15:02) Introduction to the Syncfusion DataGrid Component
⌨️ (0:43:39) Create the Database using Ef Core Code First Migrations
⌨️ (1:22:02) Integrate the Syncfusion DataGrid Component into the Application
⌨️ (3:02:44) Integrate the Syncfusion ListView component into the Sale Management Application
⌨️ (4:25:23) Integration of the Syncfusion Charts into the Sales Management Application to Display Sales Order Analytical Data
⌨️ (5:11:04) Create Dashboards for Employees
⌨️ (6:03:51) Integrate the Syncfusion Diagram into the Sales Management Application
⌨️ (6:22:25) Integrate the Syncfusion Scheduler into the Sales Management Application
⌨️ (6:52:53) Integrate Microsoft Identity into the Sales Management Application
⌨️ (7:40:34) Wrapping up