Build Real App in Angular 10 and ASP.Net Web API
These are 2 of the hottest frameworks right now for the ‘back-end’ (Microsoft’s ASP.NET Core) and the ‘front-end’ (Google’s Angular) and are well worth spending the time to learn.
This course starts from scratch, you neither need to know Angular 1 nor Angular 2
We will start from nothing and incrementally build this property dealing application front-end using Angular 10.
And then we will connect our front-end with the Web-API until we have a fully functional Web Application that we will publish to Firebase and then on IIS.
اشتراکها
Git for Windows 2.28.0 منتشر شد
New Features
- Comes with Git v2.28.0.
- Comes with subversion v1.14.0.
- Comes with the designated successor of Git Credential Manager for Windows (GCM for Windows), the cross-platform Git Credential Manager Core. For now, this is opt-in, with the idea of eventually retiring GCM for Windows.
- Comes with cURL v7.71.1.
- Comes with Perl v5.32.0.
- Comes with MSYS2 runtime (Git for Windows flavor) based on Cygwin 3.1.6 (including the improvements of Cygwin 3.1.5).
- Comes with GNU Privacy Guard v2.2.21.
اشتراکها
معرفی رسمی C# 7.1
اشتراکها
دریافت نسخهی نمایشی ویندوز 10
اشتراکها
C# در مرورگر با Blazor
Blazor is the new Microsoft experimental framework that brings C# into any browser without a plug-in. It holds the promise of modern single-page applications, combined with the ability to use C# and its vast base-class library. Blazor takes C# development to a new level. It’s the final piece necessary to make the language a full-stack development tool. It will have all the power of the popular JavaScript frameworks, but based on the familiar languages, APIs and tooling of the Microsoft .NET Framework.
در این مطلب، روش ساخت یک برنامهی دسکتاپ چندسکویی 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 را اندکی ویرایش کرد تا بتواند فایلهای اضافه شده را کامپایل کند:
- چون برنامه از نوع Blazor Server است، ارجاعی به AspNetCore را نیاز دارد و همچنین برای فایلهای cshtml آن نیز باید AddRazorSupportForMvc را به true تنظیم کرد.
- به علاوه فایل Error.cshtml.cs انتقالی، نیاز به افزودن فضای نام using Microsoft.Extensions.Logging را خواهد داشت.
- در فایل Imports.razor_ انتقالی نیاز است دو using آخر آنرا که به BlazorServerApp قبلی اشاره میکنند، به BlazorClassLibrary جدید ویرایش کنیم:
- این تغییر فضای نام جدید، شامل ابتدای فایل BlazorClassLibrary\Pages\_Host.cshtml انتقالی هم میشود:
- چون wwwroot را نیز به class library منتقل کردهایم، جهت اصلاح مسیر فایلهای css استفاده شدهی در برنامه، فایل BlazorClassLibrary\Pages\_Layout.cshtml را گشوده و تغییر زیر را اعمال میکنیم:
در مورد این مسیر ویژه، در مطلب «روش ایجاد پروژههای کتابخانهای کامپوننتهای Blazor» بیشتر بحث شدهاست.
پس از این تغییرات، برای اینکه برنامهی BlazorServerApp موجود، به کار خود ادامه دهد، نیاز است ارجاعی از پروژهی class lib را به فایل csproj آن اضافه کنیم:
اکنون جهت آزمایش برنامهی Blazor Server، یکبار دستور dotnet run را در ریشهی آن اجرا میکنیم تا مطمئن شویم انتقالات صورت گرفته، سبب کار افتادن آن نشدهاند.
ویرایش برنامهی WinForms جهت اجرای کدهای Blazor
تا اینجا برنامهی Blazor Server ما تمام فایلهای مورد نیاز خود را از BlazorClassLibrary دریافت میکند و بدون مشکل اجرا میشود. در ادامه میخواهیم کار هاست این class lib را در برنامهی WinForms نیز انجام دهیم. به همین جهت در ابتدا ارجاعی را به class lib به آن اضافه میکنیم:
سپس کامپوننت جدید WebView را به پروژهی WinForms اضافه میکنیم:
در ادامه نیاز است فایل Form1.Designer.cs را به صورت دستی جهت افزودن این WebView اضافه شده، تغییر داد:
کامپوننت WebView را نمیتوان از طریق toolbox به فرم اضافه کرد؛ به همین جهت باید فایل فوق را به نحوی که مشاهده میکنید، اندکی ویرایش نمود.
هاست برنامهی Blazor در برنامهی WinForm
پس از تغییرات فوق، نیاز است فایلهای wwwroot را از پروژهی class lib به پروژهی WinForms کپی کرد. از این جهت که این فایلها از طریق index.html جدیدی خوانده خواهند شد. پس از کپی کردن این پوشه، نیاز است فایل csproj پروژهی WinForm را به صورت زیر اصلاح کرد:
Sdk این فایل تغییر کردهاست تا بتواند از wwwroot ذکر شده استفاده کند. همچنین به ازای هر Build، فایلهای واقع در wwwroot به خروجی کپی خواهند شد.
در ادامه داخل این پوشهی wwwroot که از پروژهی class lib کپی کردیم، نیاز است فایل index.html جدیدی را که قرار است blazor.webview.js را اجرا کند، به صورت زیر ایجاد کنیم:
- ساختار این فایل بسیار شبیه به ساختار فایل برنامههای Blazor WASM است؛ با این تفاوت که در انتهای آن از blazor.webview.js کامپوننت webview استفاده میشود.
- همچنین در این فایل باید مداخل css.های مورد نیاز را هم مجددا ذکر کرد.
مرحلهی آخر کار، استفاده از کامپوننت webview جهت نمایش فایل index.html فوق است:
نکتهی مهم! حتما نیاز است WebView2 Runtime را جداگانه دریافت و نصب کرد. در غیر اینصورت در حین اجرای برنامه، با خطای نامفهوم زیر مواجه خواهید شد:
در اینجا یک ServiceCollection را ایجاد کرده و توسط آن سرویسهای مورد نیاز کامپوننت WebView را تامین میکنیم. همچنین مسیر فایل index.html نیز توسط آن مشخص شدهاست. این تنظیمات شبیه به فایل Program.cs برنامهی Blazor هستند.
تا اینجا اگر برنامه را اجرا کنیم، چنین خروجی قابل مشاهدهاست:
اکنون برنامهی کامل Blazor Server ما توسط یک WinForms هاست شدهاست و کاربر برای کار با آن، نیاز به نصب IIS یا هیچ وب سرور خاصی ندارد.
تعامل بین برنامهی WinForm و برنامهی Blazor
میخواهیم یک دکمه را بر روی WinForm قرار داده و با کلیک بر روی آن، مقدار شمارشگر حاصل در برنامهی Blazor را نمایش دهیم؛ مانند تصویر فوق.
برای اینکار در کدهای فوق، ثبت سرویس جدید AppState را هم مشاهده میکنید:
که چنین محتوایی را دارد:
این سرویس را به نحو زیر نیز به فایل Program.cs پروژهی Blazor Server اضافه میکنیم:
سپس در فایل Counter.razor آنرا تزریق کرده و به نحو زیر به ازای هر بار کلیک بر روی دکمهی افزایش مقدار شمارشگر، مقدار آنرا اضافه میکنیم:
با توجه به Singleton بودن آن و هاست برنامهی Blazor توسط WinForms، یک وهله از این سرویس، هم در برنامهی Blazor و هم در برنامهی WinForms قابل دسترسی است. برای نمونه یک دکمه را به فرم برنامهی WinForm اضافه کرده و در روال رویدادگردان کلیک آن، کد زیر را اضافه میکنیم:
در اینجا میتوان با استفاده از وهلهی سرویس به اشتراک گذاشته شده، به مقدار تنظیم شدهی در برنامهی Blazor دسترسی یافت.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorDesktopHybrid.zip
ایجاد برنامههای ابتدایی مورد نیاز
در ابتدا دو پوشهی جدید 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>
- به علاوه فایل Error.cshtml.cs انتقالی، نیاز به افزودن فضای نام using Microsoft.Extensions.Logging را خواهد داشت.
- در فایل Imports.razor_ انتقالی نیاز است دو using آخر آنرا که به BlazorServerApp قبلی اشاره میکنند، به BlazorClassLibrary جدید ویرایش کنیم:
@using BlazorClassLibrary @using BlazorClassLibrary.Shared
@namespace BlazorClassLibrary.Pages
<link rel="stylesheet" href="_content/BlazorClassLibrary/css/bootstrap/bootstrap.min.css" /> <link href="_content/BlazorClassLibrary/css/site.css" rel="stylesheet" />
پس از این تغییرات، برای اینکه برنامهی BlazorServerApp موجود، به کار خود ادامه دهد، نیاز است ارجاعی از پروژهی class lib را به فایل csproj آن اضافه کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" /> </ItemGroup> </Project>
ویرایش برنامهی WinForms جهت اجرای کدهای Blazor
تا اینجا برنامهی Blazor Server ما تمام فایلهای مورد نیاز خود را از BlazorClassLibrary دریافت میکند و بدون مشکل اجرا میشود. در ادامه میخواهیم کار هاست این class lib را در برنامهی WinForms نیز انجام دهیم. به همین جهت در ابتدا ارجاعی را به class lib به آن اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" /> </ItemGroup> </Project>
<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; }
هاست برنامهی 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>
در ادامه داخل این پوشهی 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>
- همچنین در این فایل باید مداخل 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; } }
builder.Services.AddSingleton<AppState>();
@inject BlazorServerApp.Data.AppState AppState // ... @code { private void IncrementCount() { // ... AppState.Counter++; } }
private void button1_Click(object sender, EventArgs e) { MessageBox.Show( owner: this, text: $"Current counter value is: {_appState.Counter}", caption: "Counter"); }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorDesktopHybrid.zip
نظرات مطالب
پیاده سازی UnitOfWork به وسیله MEF
من کلاسهام به این شکله:
کلاس کانتکسهای من
در تعاریف کلاسهایی که از IDBContext ارث میبرن اکسپورت شدن (این یک نمونه از کلاسهای منه)
در طرف دیگر برای لود کردن کلاس زیر نوشتم
و در کانتکس اصلی برنامه این پلاگین هارو لود میکنم
کلاس کانتکسهای من
public class VegaContext : DbContext, IUnitOfWork, IDbContext { #region Constructors (2) /// <summary> /// Initializes the <see cref="VegaContext" /> class. /// </summary> static VegaContext() { Database.SetInitializer<VegaContext>(null); } /// <summary> /// Initializes a new instance of the <see cref="VegaContext" /> class. /// </summary> public VegaContext() : base("LocalSqlServer") { } #endregion Constructors #region Properties (2) /// <summary> /// Gets or sets the languages. /// </summary> /// <value> /// The languages. /// </value> public DbSet<Language> Languages { get; set; } /// <summary> /// Gets or sets the resources. /// </summary> /// <value> /// The resources. /// </value> public DbSet<Resource> Resources { get; set; } #endregion Properties #region Methods (2) // Public Methods (1) /// <summary> /// Setups the specified model builder. /// </summary> /// <param name="modelBuilder">The model builder.</param> public void Setup(DbModelBuilder modelBuilder) { //todo modelBuilder.Configurations.Add(new ResourceMap()); modelBuilder.Configurations.Add(new LanguageMap()); modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources"); modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages"); //base.OnModelCreating(modelBuilder); } // Protected Methods (1) /// <summary> /// This method is called when the model for a derived context has been initialized, but /// before the model has been locked down and used to initialize the context. The default /// implementation of this method does nothing, but it can be overridden in a derived class /// such that the model can be further configured before it is locked down. /// </summary> /// <param name="modelBuilder">The builder that defines the model for the context being created.</param> /// <remarks> /// Typically, this method is called only once when the first instance of a derived context /// is created. The model for that context is then cached and is for all further instances of /// the context in the app domain. This caching can be disabled by setting the ModelCaching /// property on the given ModelBuidler, but note that this can seriously degrade performance. /// More control over caching is provided through use of the DbModelBuilder and DbContextFactory /// classes directly. /// </remarks> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new ResourceMap()); modelBuilder.Configurations.Add(new LanguageMap()); modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources"); modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages"); base.OnModelCreating(modelBuilder); } #endregion Methods #region IUnitOfWork Members /// <summary> /// Sets this instance. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <returns></returns> public new IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } #endregion }
در طرف دیگر برای لود کردن کلاس زیر نوشتم
public class LoadContexts { public LoadContexts() { var directoryPath = HttpRuntime.BinDirectory;//AppDomain.CurrentDomain.BaseDirectory; //"Dll folder path"; var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll"); var aggregateCatalog = new AggregateCatalog(); aggregateCatalog.Catalogs.Add(directoryCatalog); var container = new CompositionContainer(aggregateCatalog); container.ComposeParts(this); } //[Import] //public IPlugin Plugin { get; set; } [ImportMany] public IEnumerable<IDbContext> Contexts { get; set; } }
public class MainContext : DbContext, IUnitOfWork { public MainContext() : base("LocalSqlServer") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var contextList = new LoadContexts(); //ObjectFactory.GetAllInstances<IDbContext>(); foreach (var context in contextList.Contexts) context.Setup(modelBuilder); Database.SetInitializer(new MigrateDatabaseToLatestVersion<MainContext, Configuration>()); //Database.SetInitializer(new DropCreateDatabaseAlways<MainContext>()); } /// <summary> /// Sets this instance. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <returns></returns> public IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } }
با موفقیت همه پلاگینها لود میشه و مشکلی در عملیات نیست. اما Attributeهای کلاس هارو نمیشناسه. مثلا پیام خطا تعریف شده در MVC نمایش داده نمیشه چون وجود نداره ولی وقتی کلاس مورد نظر از IValidatableObject ارث میبره خطایهای من نمایش داده میشه. میخوام از خود متادیتاهای استاندارد استفاده کنم.
اشتراکها
معرفی OpenJDK ساخت مایکروسافت
اشتراکها