در این مطلب، روش ساخت یک برنامهی دسکتاپ چندسکویی Blazor 6x را که امکان به اشتراک گذاری کدهای خود را با یک برنامهی WinForms دارد، بررسی خواهیم کرد.
ایجاد برنامههای ابتدایی مورد نیاز
در ابتدا دو پوشهی جدید BlazorServerApp و WinFormsApp را ایجاد میکنیم. سپس از طریق خط فرمان در اولی دستور dotnet new blazorserver و در دومی دستور dotnet new winforms را اجرا میکنیم تا دو برنامهی خالی Blazor Server و همچنین Windows Forms، ایجاد شوند. برنامهی WinForms ایجاد شده مبتنی بر NET Core. و یا همان NET 6x. است؛ بجای اینکه مبتنی بر دات نت فریمورک 4x باشد.
ایجاد یک پروژهی کتابخانهی Razor
چون میخواهیم کدهای برنامهی BlazorServerApp ما در برنامهی WinForms قابل استفاده باشد، نیاز است فایلهای اصلی آنرا به یک پروژهی razor class library منتقل کنیم. به همین جهت برای این پروژه، یک پوشهی جدید را به نام BlazorClassLibrary ایجاد کرده و درون آن دستور dotnet new razorclasslib را اجرا میکنیم.
انتقال فایلهای پروژهی Blazor به پروژهی کتابخانهی Razor
در ادامه این فایلها را از پروژهی BlazorServerApp به پروژهی BlazorClassLibrary منتقل میکنیم:
- کل پوشهی Data
- کل پوشهی Pages
- کل پوشهی Shared
- فایل App.razor
- فایل Imports.razor_
- کل پوشهی wwwroot
پس از اینکار، نیاز است فایل csproj کتابخانهی class lib را اندکی ویرایش کرد تا بتواند فایلهای اضافه شده را کامپایل کند:
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
- چون برنامه از نوع Blazor Server است، ارجاعی به AspNetCore را نیاز دارد و همچنین برای فایلهای cshtml آن نیز باید AddRazorSupportForMvc را به true تنظیم کرد.
- به علاوه فایل Error.cshtml.cs انتقالی، نیاز به افزودن فضای نام using Microsoft.Extensions.Logging را خواهد داشت.
- در فایل Imports.razor_ انتقالی نیاز است دو using آخر آنرا که به BlazorServerApp قبلی اشاره میکنند، به BlazorClassLibrary جدید ویرایش کنیم:
@using BlazorClassLibrary
@using BlazorClassLibrary.Shared
- این تغییر فضای نام جدید، شامل ابتدای فایل BlazorClassLibrary\Pages\_Host.cshtml انتقالی هم میشود:
@namespace BlazorClassLibrary.Pages
- چون wwwroot را نیز به class library منتقل کردهایم، جهت اصلاح مسیر فایلهای css استفاده شدهی در برنامه، فایل BlazorClassLibrary\Pages\_Layout.cshtml را گشوده و تغییر زیر را اعمال میکنیم:
<link rel="stylesheet" href="_content/BlazorClassLibrary/css/bootstrap/bootstrap.min.css" />
<link href="_content/BlazorClassLibrary/css/site.css" rel="stylesheet" />
در مورد این مسیر ویژه، در مطلب «
روش ایجاد پروژههای کتابخانهای کامپوننتهای Blazor» بیشتر بحث شدهاست.
پس از این تغییرات، برای اینکه برنامهی BlazorServerApp موجود، به کار خود ادامه دهد، نیاز است ارجاعی از پروژهی class lib را به فایل csproj آن اضافه کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" />
</ItemGroup>
</Project>
اکنون جهت آزمایش برنامهی Blazor Server، یکبار دستور dotnet run را در ریشهی آن اجرا میکنیم تا مطمئن شویم انتقالات صورت گرفته، سبب کار افتادن آن نشدهاند.
ویرایش برنامهی WinForms جهت اجرای کدهای Blazor
تا اینجا برنامهی Blazor Server ما تمام فایلهای مورد نیاز خود را از BlazorClassLibrary دریافت میکند و بدون مشکل اجرا میشود. در ادامه میخواهیم کار هاست این class lib را در برنامهی WinForms نیز انجام دهیم. به همین جهت در ابتدا ارجاعی را به class lib به آن اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" />
</ItemGroup>
</Project>
سپس کامپوننت جدید WebView را به پروژهی WinForms اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.WindowsForms" Version="6.0.101-preview.11.2349" />
</ItemGroup>
</Project>
در ادامه نیاز است فایل Form1.Designer.cs را به صورت دستی جهت افزودن این WebView اضافه شده، تغییر داد:
namespace WinFormsApp;
partial class Form1
{
private void InitializeComponent()
{
this.blazorWebView1 = new Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView();
this.SuspendLayout();
this.blazorWebView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.blazorWebView1.Location = new System.Drawing.Point(13, 181);
this.blazorWebView1.Name = "blazorWebView1";
this.blazorWebView1.Size = new System.Drawing.Size(775, 257);
this.blazorWebView1.TabIndex = 20;
this.Controls.Add(this.blazorWebView1);
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "Form1";
this.ResumeLayout(false);
}
private Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView blazorWebView1;
}
کامپوننت WebView را نمیتوان از طریق toolbox به فرم اضافه کرد؛ به همین جهت باید فایل فوق را به نحوی که مشاهده میکنید، اندکی ویرایش نمود.
هاست برنامهی Blazor در برنامهی WinForm
پس از تغییرات فوق، نیاز است فایلهای wwwroot را از پروژهی class lib به پروژهی WinForms کپی کرد. از این جهت که این فایلها از طریق index.html جدیدی خوانده خواهند شد. پس از کپی کردن این پوشه، نیاز است فایل csproj پروژهی WinForm را به صورت زیر اصلاح کرد:
<Project Sdk="Microsoft.NET.Sdk.Razor">
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.WindowsForms" Version="6.0.101-preview.11.2349" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
Sdk این فایل تغییر کردهاست تا بتواند از wwwroot ذکر شده استفاده کند. همچنین به ازای هر Build، فایلهای واقع در wwwroot به خروجی کپی خواهند شد.
در ادامه داخل این پوشهی wwwroot که از پروژهی class lib کپی کردیم، نیاز است فایل index.html جدیدی را که قرار است blazor.webview.js را اجرا کند، به صورت زیر ایجاد کنیم:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Blazor WinForms app</title>
<base href="/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="WinFormsApp.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app"></div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="">Reload</a>
<a>🗙</a>
</div>
<script src="_framework/blazor.webview.js"></script>
</body>
</html>
- ساختار این فایل بسیار شبیه به ساختار فایل برنامههای Blazor WASM است؛ با این تفاوت که در انتهای آن از blazor.webview.js کامپوننت webview استفاده میشود.
- همچنین در این فایل باید مداخل css.های مورد نیاز را هم مجددا ذکر کرد.
مرحلهی آخر کار، استفاده از کامپوننت webview جهت نمایش فایل index.html فوق است:
using System;
using System.Windows.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;
using BlazorServerApp.Data;
using BlazorClassLibrary;
namespace WinFormsApp;
public partial class Form1 : Form
{
private readonly AppState _appState = new();
public Form1()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddBlazorWebView();
serviceCollection.AddSingleton<AppState>(_appState);
serviceCollection.AddSingleton<WeatherForecastService>();
InitializeComponent();
blazorWebView1.HostPage = @"wwwroot\index.html";
blazorWebView1.Services = serviceCollection.BuildServiceProvider();
blazorWebView1.RootComponents.Add<App>("#app");
//blazorWebView1.Dock = DockStyle.Fill;
}
}
نکتهی مهم! حتما نیاز است
WebView2 Runtime را جداگانه دریافت و نصب کرد. در غیر اینصورت در حین اجرای برنامه، با خطای نامفهوم زیر مواجه خواهید شد:
System.IO.FileNotFoundException: The system cannot find the file specified. (0x80070002)
در اینجا یک ServiceCollection را ایجاد کرده و توسط آن سرویسهای مورد نیاز کامپوننت WebView را تامین میکنیم. همچنین مسیر فایل index.html نیز توسط آن مشخص شدهاست. این تنظیمات شبیه به فایل Program.cs برنامهی Blazor هستند.
تا اینجا اگر برنامه را اجرا کنیم، چنین خروجی قابل مشاهدهاست:
اکنون برنامهی کامل Blazor Server ما توسط یک WinForms هاست شدهاست و کاربر برای کار با آن، نیاز به نصب IIS یا هیچ وب سرور خاصی ندارد.
تعامل بین برنامهی WinForm و برنامهی Blazor
میخواهیم یک دکمه را بر روی WinForm قرار داده و با کلیک بر روی آن، مقدار شمارشگر حاصل در برنامهی Blazor را نمایش دهیم؛ مانند تصویر فوق.
برای اینکار در کدهای فوق، ثبت سرویس جدید AppState را هم مشاهده میکنید:
serviceCollection.AddSingleton<AppState>(_appState);
که چنین محتوایی را دارد:
namespace BlazorServerApp.Data;
public class AppState
{
public int Counter { get; set; }
}
این سرویس را به نحو زیر نیز به فایل Program.cs پروژهی Blazor Server اضافه میکنیم:
builder.Services.AddSingleton<AppState>();
سپس در فایل Counter.razor آنرا تزریق کرده و به نحو زیر به ازای هر بار کلیک بر روی دکمهی افزایش مقدار شمارشگر، مقدار آنرا اضافه میکنیم:
@inject BlazorServerApp.Data.AppState AppState
// ...
@code {
private void IncrementCount()
{
// ...
AppState.Counter++;
}
}
با توجه به Singleton بودن آن و هاست برنامهی Blazor توسط WinForms، یک وهله از این سرویس، هم در برنامهی Blazor و هم در برنامهی WinForms قابل دسترسی است. برای نمونه یک دکمه را به فرم برنامهی WinForm اضافه کرده و در روال رویدادگردان کلیک آن، کد زیر را اضافه میکنیم:
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(
owner: this,
text: $"Current counter value is: {_appState.Counter}",
caption: "Counter");
}
در اینجا میتوان با استفاده از وهلهی سرویس به اشتراک گذاشته شده، به مقدار تنظیم شدهی در برنامهی Blazor دسترسی یافت.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorDesktopHybrid.zip