امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x - قسمت چهاردهم- آماده شدن برای انتشار برنامه
تنظیم مجوز امضای توکنهای IDP
namespace DNT.IDP { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() .AddDeveloperSigningCredential() .AddCustomUserStore() .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients());
مرحلهی بعد، تغییر AddDeveloperSigningCredential به یک نمونهی واقعی است. استفادهی از روش فعلی آن چنین مشکلاتی را ایجاد میکند:
- اگر برنامهی IDP را در سرورهای مختلفی توزیع کنیم و این سرورها توسط یک Load balancer مدیریت شوند، هر درخواست رسیده، به سروری متفاوت هدایت خواهد شد. در این حالت هر برنامه نیز مجوز امضای توکن متفاوتی را پیدا میکند. برای مثال اگر یک توکن دسترسی توسط سرور A امضاء شود، اما در درخواست بعدی رسیده، توسط مجوز سرور B تعیین اعتبار شود، این اعتبارسنجی با شکست مواجه خواهد شد.
- حتی اگر از یک Load balancer استفاده نکنیم، به طور قطع Application pool برنامه در سرور، در زمانی خاص Recycle خواهد شد. این مورد DeveloperSigningCredential تنظیم شده را نیز ریست میکند. یعنی با ریاستارت شدن Application pool، کلیدهای مجوز امضای توکنها تغییر میکنند که در نهایت سبب شکست اعتبارسنجی توکنهای صادر شدهی توسط IDP میشوند.
بنابراین برای انتشار نهایی برنامه نمیتوان از DeveloperSigningCredential فعلی استفاده کرد و نیاز است یک signing certificate را تولید و تنظیم کنیم. برای این منظور از برنامهی makecert.exe مایکروسافت که جزئی از SDK ویندوز است، استفاده میکنیم. این فایل را از پوشهی src\IDP\DNT.IDP\MakeCert نیز میتوانید دریافت کنید.
سپس دستور زیر را با دسترسی admin اجرا کنید:
makecert.exe -r -pe -n "CN=DntIdpSigningCert" -b 01/01/2018 -e 01/01/2025 -eku 1.3.6.1.5.5.7.3.3 -sky signature -a sha256 -len 2048 -ss my -sr LocalMachine
پس از آن در قسمت run ویندوز، دستور mmc را وارد کرده و enter کنید. سپس از منوی File گزینهی Add remove span-in را انتخاب کنید. در اینجا certificate را add کنید. در صفحهی باز شده Computer Account و سپس Local Computer را انتخاب کنید و در نهایت OK. اکنون میتوانید این مجوز جدید را در قسمت «Personal/Certificates»، مشاهده کنید:
در اینجا Thumbprint این مجوز را در حافظه کپی کنید؛ از این جهت که در ادامه از آن استفاده خواهیم کرد.
چون این مجوز از نوع self signed است، در قسمت Trusted Root Certification Authorities قرار نگرفتهاست که باید این انتقال را انجام داد. در غیراینصورت میتوان توسط آن توکنهای صادر شده را امضاء کرد اما به عنوان یک توکن معتبر به نظر نخواهند رسید.
در ادامه این مجوز جدید را انتخاب کرده و بر روی آن کلیک راست کنید. سپس گزینهی All tasks -> export را انتخاب کنید. نکتهی مهمی را که در اینجا باید رعایت کنید، انتخاب گزینهی «yes, export the private key» است. کپی و paste این مجوز از اینجا به جایی دیگر، این private key را export نمیکند. در پایان این عملیات، یک فایل pfx را خواهید داشت.
- در آخر نیاز است این فایل pfx را در مسیر «Trusted Root Certification Authorities/Certificates» قرار دهید. برای اینکار بر روی نود certificate آن کلیک راست کرده و گزینهی All tasks -> import را انتخاب کنید. سپس مسیر فایل pfx خود را داده و این مجوز را import نمائید.
پس از ایجاد مجوز امضای توکنها و انتقال آن به Trusted Root Certification Authorities، نحوهی معرفی آن به IDP به صورت زیر است:
namespace DNT.IDP { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() .AddSigningCredential(loadCertificateFromStore()) .AddCustomUserStore() .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()); } private X509Certificate2 loadCertificateFromStore() { var thumbPrint = Configuration["CertificateThumbPrint"]; using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadOnly); var certCollection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbPrint, true); if (certCollection.Count == 0) { throw new Exception("The specified certificate wasn't found."); } return certCollection[0]; } } private X509Certificate2 loadCertificateFromFile() { // NOTE: // You should check out the identity of your application pool and make sure // that the `Load user profile` option is turned on, otherwise the crypto susbsystem won't work. var certificate = new X509Certificate2( fileName: Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data", Configuration["X509Certificate:FileName"]), password: Configuration["X509Certificate:Password"], keyStorageFlags: X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); return certificate; } } }
پس از این تغییرات، برنامه را اجرا کنید. سپس مسیر discovery document را طی کرده و آدرس jwks_uri آنرا در مرورگر باز کنید. در اینجا خاصیت kid نمایش داده شده با thumbPrint مجوز یکی است.
https://localhost:6001/.well-known/openid-configuration https://localhost:6001/.well-known/openid-configuration/jwks
انتقال سایر قسمتهای فایل Config برنامهی IDP به بانک اطلاعاتی
قسمت آخر آماده سازی برنامه برای انتشار آن، انتقال سایر دادههای فایل Config، مانند Resources و Clients برنامهی IDP، به بانک اطلاعاتی است. البته هیچ الزامی هم به انجام اینکار نیست. چون اگر تعداد برنامههای متفاوتی که در سازمان قرار است از IDP استفاده کنند، کم است، تعریف مستقیم آنها داخل فایل Config برنامهی IDP، مشکلی را ایجاد نمیکند و این تعداد رکورد الزاما نیازی به بانک اطلاعاتی ندارند. اما اگر بخواهیم امکان به روز رسانی این اطلاعات را بدون نیاز به کامپایل مجدد برنامهی IDP توسط یک صفحهی مدیریتی داشته باشیم، نیاز است آنها را به بانک اطلاعاتی منتقل کنیم. این مورد مزیت به اشتراک گذاری یک چنین اطلاعاتی را توسط Load balancers نیز میسر میکند.
البته باید درنظر داشت قسمت دیگر اطلاعات IdentityServer شامل refresh tokens و reference tokens هستند. تمام اینها اکنون در حافظه ذخیره میشوند که با ریاستارت شدن Application pool برنامه از بین خواهند رفت. بنابراین حداقل در این مورد استفادهی از بانک اطلاعاتی اجباری است.
خوشبختانه قسمت عمدهی این کار توسط خود تیم IdentityServer توسط بستهی IdentityServer4.EntityFramework انجام شدهاست که در اینجا از آن استفاده خواهیم کرد. البته در اینجا این بستهی نیوگت را مستقیما مورد استفاده قرار نمیدهیم. از این جهت که نیاز به 2 رشتهی اتصالی جداگانه و دو Context جداگانه را دارد که داخل خود این بسته تعریف شدهاست و ترجیح میدهیم که اطلاعات آنرا با ApplicationContext خود یکی کنیم.
برای این منظور آخرین سورس کد پایدار آنرا از این آدرس دریافت کنید:
https://github.com/IdentityServer/IdentityServer4.EntityFramework/releases
انتقال موجودیتها به پروژهی DNT.IDP.DomainClasses
در این بستهی دریافتی، در پوشهی src\IdentityServer4.EntityFramework\Entities آن، کلاسهای تعاریف موجودیتهای متناظر با منابع IdentityServer قرار دارند. بنابراین همین فایلها را از این پروژه استخراج کرده و به پروژهی DNT.IDP.DomainClasses در پوشهی جدید IdentityServer4Entities اضافه میکنیم.
البته در این حالت پروژهی DNT.IDP.DomainClasses نیاز به این وابستگیها را خواهد داشت:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" /> <PackageReference Include="IdentityServer4" Version="2.2.0" /> </ItemGroup> </Project>
انتقال تنظیمات روابط بین موجودیتها، به پروژهی DNT.IDP.DataLayer
در فایل src\IdentityServer4.EntityFramework\Extensions\ModelBuilderExtensions.cs بستهی دریافتی، تعاریف تنظیمات این موجودیتها به همراه نحوهی برقراری ارتباطات بین آنها قرار دارد. بنابراین این اطلاعات را نیز از این فایل استخراج و به پروژهی DNT.IDP.DataLayer اضافه میکنیم. البته در اینجا از روش IEntityTypeConfiguration برای قرار هر کدام از تعاریف یک در کلاس مجزا استفاده کردهایم.
پس از این انتقال، به کلاس Context برنامه مراجعه کرده و توسط متد builder.ApplyConfiguration، این فایلهای IEntityTypeConfiguration را معرفی میکنیم.
تعاریف DbSetهای متناظر با موجودیتهای منتقل و تنظیم شده در پروژهی DNT.IDP.DataLayer
پس از انتقال موجودیتها و روابط بین آنها، دو فایل DbContext را در این بستهی دریافتی خواهید یافت:
الف) فایل src\IdentityServer4.EntityFramework\DbContexts\ConfigurationDbContext.cs
این فایل، موجودیتهای تنظیمات برنامه مانند Resources و Clients را در معرض دید EF Core قرار میدهد.
سپس فایل src\IdentityServer4.EntityFramework\Interfaces\IConfigurationDbContext.cs نیز جهت استفادهی از این DbContext در سرویسهای این بستهی دریافتی تعریف شدهاست.
ب) فایل src\IdentityServer4.EntityFramework\DbContexts\PersistedGrantDbContext.cs
این فایل، موجودیتهای ذخیره سازی اطلاعات مخصوص IDP را مانند refresh tokens و reference tokens، در معرض دید EF Core قرار میدهد.
همچنین فایل src\IdentityServer4.EntityFramework\Interfaces\IPersistedGrantDbContext.cs نیز جهت استفادهی از این DbContext در سرویسهای این بستهی دریافتی تعریف شدهاست.
ما در اینجا DbSetهای هر دوی این DbContextها را در ApplicationDbContext خود، خلاصه و ادغام میکنیم.
انتقال نگاشتهای AutoMapper بستهی دریافتی به پروژهی جدید DNT.IDP.Mappings
در پوشهی src\IdentityServer4.EntityFramework\Mappers، تعاریف نگاشتهای AutoMapper، برای تبدیلات بین موجودیتهای برنامه و IdentityServer4.Models انجام شدهاست. کل محتویات این پوشه را به یک پروژهی Class library جدید به نام DNT.IDP.Mappings منتقل و فضاهای نام آنرا نیز اصلاح میکنیم.
انتقال src\IdentityServer4.EntityFramework\Options به پروژهی DNT.IDP.Models
در پوشهی Options بستهی دریافتی سه فایل موجود هستند:
الف) Options\ConfigurationStoreOptions.cs
این فایل، به همراه تنظیمات نام جداول متناظر با ذخیره سازی اطلاعات کلاینتها است. نیازی به آن نداریم؛ چون زمانیکه موجودیتها و تنظیمات آنها را به صورت مستقیم در اختیار داریم، نیازی به فایل تنظیمات ثالثی برای انجام اینکار نیست.
ب) Options\OperationalStoreOptions.cs
این فایل، تنظیمات نام جداول مرتبط با ذخیره سازی توکنها را به همراه دارد. به این نام جداول نیز نیازی نداریم. اما این فایل به همراه سه تنظیم زیر جهت پاکسازی دورهای توکنهای قدیمی نیز هست:
namespace IdentityServer4.EntityFramework.Options { public class OperationalStoreOptions { public bool EnableTokenCleanup { get; set; } = false; public int TokenCleanupInterval { get; set; } = 3600; public int TokenCleanupBatchSize { get; set; } = 100; } }
public TokenCleanup( IServiceProvider serviceProvider, ILogger<TokenCleanup> logger, IOptions<OperationalStoreOptions> options)
کلاسی است به همراه خواص نام اسکیمای جداول که در دو کلاس تنظیمات قبلی بکار رفتهاست. نیازی به آن نداریم.
انتقال سرویسهای IdentityServer4.EntityFramework به پروژهی DNT.IDP.Services
بستهی دریافتی، شامل دو پوشهی src\IdentityServer4.EntityFramework\Services و src\IdentityServer4.EntityFramework\Stores است که سرویسهای آنرا تشکیل میدهند (جمعا 5 سرویس TokenCleanup، CorsPolicyService، ClientStore، PersistedGrantStore و ResourceStore). بنابراین این سرویسها را نیز مستقیما از این پوشهها به پروژهی DNT.IDP.Services کپی خواهیم کرد.
همانطور که عنوان شد دو فایل Interfaces\IConfigurationDbContext.cs و Interfaces\IPersistedGrantDbContext.cs برای دسترسی به دو DbContext این بستهی دریافتی در سرویسهای آن، تعریف شدهاند و چون ما در اینجا صرفا یک ApplicationDbContext را داریم که از طریق IUnitOfWork، در دسترس لایهی سرویس قرار میگیرد، ارجاعات به دو اینترفیس یاد شده را با IUnitOfWork تعویض خواهیم کرد تا مجددا قابل استفاده شوند.
انتقال متدهای الحاقی معرفی سرویسهای IdentityServer4.EntityFramework به پروژهی DNT.IDP
پس از انتقال قسمتهای مختلف IdentityServer4.EntityFramework به لایههای مختلف برنامهی جاری، اکنون نیاز است سرویسهای آنرا به برنامه معرفی کرد که در نهایت جایگزین متدهای فعلی درون حافظهای کلاس آغازین برنامهی IDP میشوند. خود این بسته در فایل زیر، به همراه متدهایی الحاقی است که این معرفی را انجام میدهند:
src\IdentityServer4.EntityFramework\Extensions\IdentityServerEntityFrameworkBuilderExtensions.cs
به همین جهت این فایل را به پروژهی وب DNT.IDP ، منتقل خواهیم کرد؛ همانجایی که در قسمت دهم AddCustomUserStore را تعریف کردیم.
این کلاس پس از انتقال، نیاز به تغییرات ذیل را دارد:
الف) چون یکسری از کلاسهای تنظیمات را حذف کردیم و نیازی به آنها نداریم، آنها را نیز به طور کامل از این فایل حذف میکنیم. تنها تنظیم مورد نیاز آن، OperationalStoreOptions است که اینبار آنرا از فایل appsettings.json دریافت میکنیم. بنابراین ذکر این مورد نیز در اینجا اضافی است.
ب) در آن، دو Context موجود در بستهی اصلی IdentityServer4.EntityFramework مورد استفاده قرار گرفتهاند. ما در اینجا آنها را نیز با تک Context برنامهی خود تعویض میکنیم.
ج) در آن سرویسی به نام TokenCleanupHost تعریف شدهاست. آنرا نیز به لایهی سرویسها منتقل میکنیم. همچنین در امضای سازندهی آن بجای تزریق مستقیم OperationalStoreOptions از <IOptions<OperationalStoreOptions استفاده خواهیم کرد.
نگارش نهایی و تمیز شدهی IdentityServerEntityFrameworkBuilderExtensions را در اینجا مشاهده میکنید.
افزودن متدهای الحاقی جدید به فایل آغازین برنامهی IDP
پس از انتقال IdentityServerEntityFrameworkBuilderExtensions به پروژهی DNT.IDP، اکنون نوبت به استفادهی از آن است:
namespace DNT.IDP { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() .AddSigningCredential(loadCertificateFromStore()) .AddCustomUserStore() .AddConfigurationStore() .AddOperationalStore();
اجرای Migrations در پروژهی DNT.IDP.DataLayer
پس از پایان این نقل و انتقالات، اکنون نیاز است ساختار بانک اطلاعاتی برنامه را بر اساس موجودیتها و روابط جدید بین آنها، به روز رسانی کنیم. به همین جهت فایل add_migrations.cmd موجود در پوشهی src\IDP\DNT.IDP.DataLayer را اجرا میکنیم تا کلاسهای Migrations متناظر تولید شوند و سپس فایل update_db.cmd را اجرا میکنیم تا این تغییرات، به بانک اطلاعاتی برنامه نیز اعمال گردند.
انتقال اطلاعات فایل درون حافظهای Config، به بانک اطلاعاتی برنامه
تا اینجا اگر برنامه را اجرا کنیم، دیگر کار نمیکند. چون جداول کلاینتها و منابع آن خالی هستند. به همین جهت نیاز است اطلاعات فایل درون حافظهای Config را به بانک اطلاعاتی منتقل کنیم. برای این منظور سرویس ConfigSeedDataService را به برنامه اضافه کردهایم:
public interface IConfigSeedDataService { void EnsureSeedDataForContext( IEnumerable<IdentityServer4.Models.Client> clients, IEnumerable<IdentityServer4.Models.ApiResource> apiResources, IEnumerable<IdentityServer4.Models.IdentityResource> identityResources); }
برای استفادهی از آن، به کلاس آغازین برنامهی IDP مراجعه میکنیم:
namespace DNT.IDP { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... initializeDb(app); seedDb(app); // ... } private static void seedDb(IApplicationBuilder app) { var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var configSeedDataService = scope.ServiceProvider.GetService<IConfigSeedDataService>(); configSeedDataService.EnsureSeedDataForContext( Config.GetClients(), Config.GetApiResources(), Config.GetIdentityResources() ); } }
آزمایش برنامه
پس از اعمال این تغییرات، برنامهها را اجرا کنید. سپس به بانک اطلاعاتی آن مراجعه نمائید. در اینجا لیست جداول جدیدی را که اضافه شدهاند ملاحظه میکنید:
همچنین برای نمونه، در اینجا اطلاعات منابع منتقل شدهی به بانک اطلاعاتی، از فایل Config، قابل مشاهده هستند:
و یا قسمت ذخیره سازی خودکار توکنهای تولیدی توسط آن نیز به درستی کار میکند:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحهی login نام کاربری را User 1 و کلمهی عبور آنرا password وارد کنید.
Note Taking App .Net MAUI Blazor Hybrid and Blazor WASM - Single Codebase Complete Code & UI Sharing
A Note Taking App for Mobile and Web Browser both Platforms using Single Code base with almost 100% Code Sharing (Complete Code, Logic and UI Sharing)
.Net MAUI Blazor Hybrid for Mobile App and Blazor WebAssembly (Blazor WASM) for Web Browser App sharing code using Razor Class Libraries
A step by step RealWorld app tutorial from scratch
آیا باید NET Core. را یاد بگیرم؟
انتشار PostSharp 5.0 RTM
PostSharp 5.0 adds support for .NET Core 1.1, Visual Studio 2017 and C# 7.0. It also introduces brand new features like the [Cache] aspect, the [Command] and [DependencyProperty] aspects for XAML applications. And the Logging feature has been completely revamped, now fully customizable and faster than ever.
فعالسازی قدم به قدم Roslyn refactoring افزونهی Roslynator
الف) فایل VSIX آنرا از اینجا دریافت کنید و سپس پسوند آنرا به zip تغییر دهید.
ب) این فایل zip را در پوشهای مشخص باز کنید.
ج) پس از باز کردن این فایل zip، دو فایل Roslynator.VisualStudio.Core.dll و Roslynator.VisualStudio.dll آنرا حذف کنید. این فایلها مخصوص نگارش کامل ویژوال استودیو هستند و در صورت وجود، با سیستم بارگذاری افزونههای OmniSharp تداخل میکنند.
د) در آخر مسیر زیر را گشوده:
%USERPROFILE%/.omnisharp
{ "RoslynExtensionsOptions": { "LocationPaths": [ "C:\\lib\\roslynator" ] } }
اکنون اگر VSCode را اجرا کنید، شاهد افزوده شدن امکانات Refactoring مخصوص افزونهی Roslynator به لیست Refactoring پیشفرض OmniSharp خواهید بود:
خودکار سازی دریافت، نصب و فعالسازی Roslyn refactoring افزونهی Roslynator
مراحل فوق را میتوان تبدیل به یک اسکریپت پاورشل کرد که با هر بار اجرای آن، به صورت خودکار کار دریافت و نصب این افزونه صورت گیرد:
Write-Host "Download, unzip and enable Roslynator for Visual Studio Code" $name = "josefpihrt.Roslynator2017" $url = "https://marketplace.visualstudio.com/items?itemName=$name" $currentDir = $PSScriptRoot $file = "$currentDir\Roslynator.zip" $pattern = "<script class=`"vss-extension`" defer=`"defer`" type=`"application\/json`">(.*?)<\/script>" $regex = [regex]"(?m)$pattern" Write-Host "Grab the home page of the $name." $dom = (New-Object Net.WebClient).DownloadString($url); if($dom -and $dom -match $pattern) { $matches = $regex.Match($dom) $jsonText = $matches[0].Groups[1] $json = ConvertFrom-Json $jsonText $version = $Json.versions[0].version # Parse the json in the page for the latest version number $parts = $name.Split(".") $publisher = $parts[0] $package = $parts[1] # Assemble the url for the vsix package $packageUrl = "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/$publisher/vsextensions/$package/$version/vspackage" Write-Host "Download the vsix package: $packageUrl" (New-Object Net.WebClient).DownloadFile($packageUrl, $file) Write-Host "Using $currentDir as the current dir." Write-Host "Unzip $file." $shellApp = new-object -com shell.application $zipFile = $shellApp.namespace($file) $destination = $shellApp.namespace($currentDir) $destination.Copyhere($zipFile.items(), 0x14)# overwrite and be silent Write-Host "Delete VS specific files. Otherwise they will interfere with the MEF services inside OmniSharp." Remove-Item "$currentDir\Roslynator.VisualStudio.Core.dll","$currentDir\Roslynator.VisualStudio.dll", "$currentDir\Roslynator.VisualStudio.pkgdef" $omnisharpJsonFilePath = "$env:USERPROFILE\.omnisharp\omnisharp.json"; Write-Host "Create $omnisharpJsonFilePath file." $omnisharpJson = @" {{ "RoslynExtensionsOptions": {{ "LocationPaths": [ "{0}" ] }} }} "@ -f $currentDir -Replace "\\","\\" $omnisharpJson | Out-File "$omnisharpJsonFilePath" -Confirm Write-Host "Done!" } else { Write-Host "Failed to find the packageUrl!" }
PS D:\Prog\1396\RoslynatorVSCode> .\update.ps1 Download, unzip and enable Roslynator for Visual Studio Code Grab the home page of the josefpihrt.Roslynator2017. Download the vsix package: https://marketplace.visualstudio.com/_apis/public/gallery/publishers/josefpihrt/vsextensions/Roslynator2017/1.7.0/vspackage Using D:\Prog\1396\RoslynatorVSCode as the current dir. Unzip D:\Prog\1396\RoslynatorVSCode\Roslynator.zip. Delete VS specific files. Otherwise they will interfere with the MEF services inside OmniSharp. Create C:\Users\Vahid\.omnisharp\omnisharp.json file. Confirm Are you sure you want to perform this action? Performing the operation "Output to File" on target "C:\Users\Vahid\.omnisharp\omnisharp.json". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): Done!
در ادامه این افزونه دریافت شده و در پوشهی جاری باز خواهد شد. سپس فایل omnisharp.json نیز به صورت خودکار تشکیل و مقدار دهی میشود.
اکنون اگر VSCode را اجرا کنید، همه چیز آماده بوده و امکانات این افزونه در دسترس خواهند بود.
منابع و مآخذ مرتبط با کتابخانهی Angular Material
در اینجا مآخذ اصلی کار با این کتابخانه را ملاحظه میکنید که شامل اصول طراحی متریال و مخازن اصلی توسعهی آن میباشند:
Material Design Specification
- https://material.io/design
Angular Material
- https://material.angular.io
- https://github.com/angular/material2
مفاهیم پایهی طراحی متریال
چرا «زیبایی» رابط کاربری مهم است؟
در ابتدای معرفی کتابخانهی Angular Material عنوان شد که این مجموعه به همراه تعدادی کامپوننت «زیبا» است. بنابراین این سؤال مطرح میشود که چرا و یا تا چه اندازه «زیبایی» رابط کاربری اهمیت دارد؟ مهمترین دلیل آن بهبود تجربهی کاربری است. بر اساس تحقیقاتی که بر روی کاربران بسیاری صورت گرفتهاست، مشخص شدهاست کاربران، با رابطهای کاربری زیبا نتایج بهتری را از لحاظ کاهش زمان اتمام کار و تعداد خطاهای مرتبط دریافت میکنند.
اما ... طراحی برنامههای زیبا مشکل است. به همین جهت استفاده از کتابخانههای غنی مانند طراحی متریال که این امر را سهولت میبخشند، ضروری است. طراحی متریال یک زبان کامل طراحی برنامههای زیبا است. توسط گوگل طراحی شدهاست و دو هدف اصلی را دنبال میکند:
- وفاداری به اصول کلاسیک طراحی رابط کاربری
- ارائهی تجربهی کاربری یکدست و هماهنگ، در بین وسایل و اندازههای صفحات نمایشی مختلف
اصول پایهی طراحی متریال نیز شامل موارد زیر است:
- «متریال» یک متافور است و بر اساس مطالعهی نحوهی کار با کاغذ، مرکب و ارتباط بین اشیاء در دنیای واقعی پدید آمدهاست.
- اشیاء در دنیای واقعی دارای ارتباطهای ابعادی و حجمی هستند. برای مثال دو برگهی کاغذ یک فضا را اشغال نمیکنند. طراحی متریال برای نمایش این ارتباط سه بعدی بین اشیاء، از نور و سایه استفاده میکند.
- در دنیای واقعی، اشیاء از درون یکدیگر رد نمیشوند. این مورد در طراحی متریال نیز صادق است.
- طراحی متریال به همراه جعبهی رنگ مخصوص و بکارگیری فضاهای خالی و عناوین درشت بسیار مشخص، واضح و عمدی است.
- طراحی متریال به همراه حرکت و پویانمایی، جهت ارائهی مفاهیم مختلف به کاربر، جهت درک بهتر او از برنامه است.
برپایی پیشنیازهای ابتدایی کار با Angular Material
پیش از ادامهی بحث فرض بر این است که آخرین نگارش Angular CLI را نصب کردهاید و اگر پیشتر آنرا نصب کردهاید، یکبار دستور ذیل را اجرا کنید تا تمام وابستگیهای سراسری نصب شدهی در سیستم به صورت خودکار به روز رسانی شوند:
npm update -g
ng new MaterialAngularClient --routing
cd MaterialAngularClient ng serve -o
افزودن کتابخانهی Angular Material به برنامه
در طول این سری از سایت https://material.angular.io زیاد استفاده خواهیم کرد. همواره به روزترین روش افزودن کتابخانهی Angular Material به یک برنامهی موجود را در آدرس https://material.angular.io/guide/getting-started میتوانید مشاهده کنید که خلاصهی آن به صورت زیر است:
البته در Angular 6 روش تفصیلی نصب فوق که شامل 6 مرحلهاست، به صورت زیر هم خلاصه شدهاست:
ng add @angular/material
npm install --save @angular/material @angular/cdk npm install --save @angular/animations npm install --save hammerjs
- همانطور که عنوان شد، طراحی متریال مبتنی بر حرکت و پویانمایی است. به همین جهت تعدادی از کامپوننتهای آن نیاز به بستهی angular/animations را دارند که توسط دستور دوم نصب میشود.
- دستور سوم نیز کامپوننتهای slide و slider را پشتیبانی میکند (Gesture Support). البته پس نصب این وابستگی، نیاز است به فایل src/main.ts مراجعه کرده و یک سطر زیر را نیز افزود:
import "hammerjs";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @NgModule({ imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule ] }) export class AppModule { }
مدیریت بهتر import کامپوننتهای Angular Material
در ادامه به ازای هر کامپوننت Angular Material باید ماژول آنرا به لیست imports افزود که پس از مدتی به یک فایل app.module.ts بسیار شلوغ خواهیم رسید. برای مدیریت بهتر این فایل، از روش مطرح شدهی در مطلب «سازماندهی برنامههای Angular» استفاده خواهیم کرد.
به همین جهت دو پوشهی core و shared را درون پوشهی src/app ایجاد میکنیم:
محتویات فایل src\app\core\core.module.ts به صورت زیر است:
import { CommonModule } from "@angular/common"; import { NgModule, Optional, SkipSelf } from "@angular/core"; import { RouterModule } from "@angular/router"; @NgModule({ imports: [CommonModule, RouterModule], exports: [ // components that are used in app.component.ts will be listed here. ], declarations: [ // components that are used in app.component.ts will be listed here. ], providers: [ /* ``No`` global singleton services of the whole app should be listed here anymore! Since they'll be already provided in AppModule using the `tree-shakable providers` of Angular 6.x+ (providedIn: 'root'). This new feature allows cleaning up the providers section from the CoreModule. But if you want to provide something with an InjectionToken other that its class, you still have to use this section. */ ] }) export class CoreModule { constructor(@Optional() @SkipSelf() core: CoreModule) { if (core) { throw new Error("CoreModule should be imported ONLY in AppModule."); } } }
محتویات فایل src\app\shared\shared.module.ts نیز به صورت زیر است:
import { CommonModule } from "@angular/common"; import { HttpClientModule } from "@angular/common/http"; import { ModuleWithProviders, NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; @NgModule({ imports: [ CommonModule, FormsModule, HttpClientModule ], entryComponents: [ // All components about to be loaded "dynamically" need to be declared in the entryComponents section. ], declarations: [ // common and shared components/directives/pipes between more than one module and components will be listed here. ], exports: [ // common and shared components/directives/pipes between more than one module and components will be listed here. CommonModule, FormsModule, HttpClientModule, ] /* No providers here! Since they’ll be already provided in AppModule. */ }) export class SharedModule { static forRoot(): ModuleWithProviders { // Forcing the whole app to use the returned providers from the AppModule only. return { ngModule: SharedModule, providers: [ /* All of your services here. It will hold the services needed by `itself`. */] }; } }
import { CoreModule } from "./core/core.module"; import { SharedModule } from "./shared/shared.module"; @NgModule({ imports: [ BrowserModule, BrowserAnimationsModule, CoreModule, SharedModule.forRoot(), AppRoutingModule ] }) export class AppModule { }
import { CdkTableModule } from "@angular/cdk/table"; import { NgModule } from "@angular/core"; import { MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatSortModule, MatStepperModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, } from "@angular/material"; @NgModule({ imports: [ MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatStepperModule, MatSortModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, CdkTableModule ], exports: [ MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatStepperModule, MatSortModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, CdkTableModule ] }) export class MaterialModule { }
سپس MaterialModule را نیز به قسمتهای imports و exports فایل src\app\shared\shared.module.ts اضافه خواهیم کرد:
import { MaterialModule } from "./material.module"; @NgModule({ imports: [ CommonModule, FormsModule, HttpClientModule, MaterialModule ], exports: [ // common and shared components/directives/pipes between more than one module and components will be listed here. CommonModule, FormsModule, HttpClientModule, MaterialModule ] }) export class SharedModule { }
تا اینجا جهت اطمینان از اجرای برنامه، دستور ng serve -o را از ابتدا اجرا کنید.
افزودن چند کامپوننت مقدماتی متریال به برنامه
بهترین روش کار با این مجموعه، بررسی مستندات آن در سایت https://material.angular.io/components است. برای مثال برای افزودن دکمه، به مستندات آن مراجعه کرده و بر روی دکمهی view source کلیک میکنیم:
سپس کدهای قسمت HTML آنرا به برنامه و فایل app.component.html اضافه خواهیم کرد:
<button mat-button>Click me!</button>
<mat-checkbox>Check me!</mat-checkbox>
البته شکل ظاهری آنها تا اینجا آنچنان مطلوب نیست. برای رفع این مشکل، نیاز است یک قالب را به این کنترلها و کامپوننتها اعمال کرد. به همین جهت فایل styles.css واقع در ریشهی برنامه را گشوده و قالب پیشفرض متریال را به آن اضافه میکنیم:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
پس از اعمال قالب، اکنون است که شکل ظاهری کنترلهای آن بسیار بهتر شدهاند و همچنین کار با آنها به همراه پویانمایی نیز شدهاست:
افزودن آیکنهای متریال به برنامه
مرحلهی آخر این تنظیمات، افزودن آیکنهای متریال به برنامهاست. برای این منظور فایل src\index.html را گشوده و یک سطر ذیل را به head اضافه کنید:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<button mat-button> <mat-icon>face</mat-icon> Click me! </button> <mat-checkbox>Check me!</mat-checkbox>
لیست کامل این آیکنها را به همراه توضیحات تکمیلی آنها، در آدرس ذیل میتوانید ملاحظه کنید:
http://google.github.io/material-design-icons
البته چون ما نمیخواهیم این آیکنها را از وب بارگذاری کنیم، برای نصب محلی آنها ابتدا دستور زیر را در ریشهی پروژه صادر کنید:
npm install material-design-icons --save
همانطور که مشاهده میکنید، برای استفادهی از این فایلهای آیکن فونت محلی، تنها کافی است فایل material-icons.css را به برنامه معرفی کنیم. برای این منظور فایل angular.json را گشوده و قسمت styles آنرا به صورت زیر تکمیل میکنیم:
"styles": [ "node_modules/material-design-icons/iconfont/material-icons.css", "src/styles.css" ],
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-01.zip
برای اجرای آن نیز ابتدا فایل restore.bat و سپس فایل ng-serve.bat را اجرا کنید.