آشنایی با نحوه ایجاد یک IoC Container
Unity
is: it's from and supported by Microsoft (p&p). Unity has very good performance, and great documentation. It is also highly configurable. It doesn't have all the bells and whistles of say Castle / Structure Map نگارش نهایی Bootstrap 5 منتشر شد
Bootstrap 5 has officially landed! After three alphas, three betas, and several months of hard work, we’re shipping the first stable release of our new major version. It’s been a wild ride made possible by our maintainers and the amazing community that uses and contributes to Bootstrap. Thanks to all who have helped us get here!
In this post, I want to compare “DNTFrameworkCore” with “ABP Framework”.
ABP is one of most popular and well documented frameworks with high level abstraction that I learned a lot from it. It has many other features that I don’t list them in this post, because, DNTFrameworkCore has not them actually. Behind of ABP, there is a team. It has 123 contributors in GitHub. Also, all of features have related unit-tests.
In other side, DNTFrameworkCore is lightweight with low level abstraction. It is personal open-source project without any contributor and has unit-tests for small part.
I wrote this post to prove that thinking behind of DNTFrameworkCore completely different from ABP (at least in most sections)
تازه ها در TypeScript
EFCore 5.0 RC1 منتشر شد
Today, the Entity Framework Core team announces the first release candidate (RC1) of EF Core 5.0. This is a feature complete release candidate of EF Core 5.0 and ships with a "go live" license. You are supported using it in production.
ساختار پوشهها و پروژههای قسمت Blazor Server
قسمت Blazor Server مدیریت هتل ما از 7 پروژه و پوشهی زیر تشکیل میشود:
- BlazorServer.App: پروژهی اصلی Blazor Server است که با اجرای دستور dotnet new blazorserver در پوشهی خالی آن آغاز میشود.
- BlazorServer.Common: پروژهای از نوع classlib، جهت قرارگیری کدهای مشترک بین پروژهها است که با اجرای دستور dotnet new classlib در این پوشه آغاز میشود.
- BlazorServer.DataAccess: پروژهای از نوع classlib، برای تعریف DbContext برنامه است که با اجرای دستور dotnet new classlib در این پوشه آغاز میشود.
- BlazorServer.Entities: پروژهای از نوع classlib، جهت تعریف کلاسهای متناظر با جداول بانک اطلاعاتی برنامه است که با اجرای دستور dotnet new classlib در این پوشه آغاز میشود.
- BlazorServer.Models: پروژهای از نوع classlib، برای تعریف کلاسهای data transfer objects برنامه (DTO's) است که با اجرای دستور dotnet new classlib در این پوشه آغاز میشود.
- BlazorServer.Models.Mappings: پروژهای از نوع classlib، برای تعریف نگاشتهای بین DTO's و مجودیتهای برنامه و برعکس است که با اجرای دستور dotnet new classlib در این پوشه آغاز میشود.
- BlazorServer.Services: پروژهای از نوع classlib، جهت تعریف کدهایی که منطق تجاری تعامل با بانک اطلاعاتی را از طریق BlazorServer.DataAccess میسر میکند که با اجرای دستور dotnet new classlib در این پوشه آغاز میشود.
اصلاح پروژهی BlazorServer.App جهت استفاده از LibMan
قالب پیشفرض BlazorServer.App، به همراه پوشهی wwwroot\css است که در آن بوت استرپ و open-iconic به همراه فایل site.css قرار دارند. چون این پروژه به همراه هیچ نوع روشی برای مدیریت نگهداری بستههای سمت کلاینت خود نیست، دو پوشهی بوت استرپ و open-iconic آنرا حذف کرده و از روش مطرح شدهی در مطلب «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» استفاده خواهیم کرد:
dotnet tool update -g Microsoft.Web.LibraryManager.Cli libman init libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap libman install open-iconic --provider unpkg --destination wwwroot/lib/open-iconic
بعد از نصب بستههای ذکر شده، ابتدا سطر زیر را از ابتدای فایل پیشفرض wwwroot\css\site.css حذف میکنیم:
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>BlazorServer.App</title> <base href="~/" /> <link href="lib/open-iconic/font/css/open-iconic-bootstrap.min.css" rel="stylesheet" /> <link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="css/site.css" rel="stylesheet" /> <link href="BlazorServer.App.styles.css" rel="stylesheet" /> </head>
برای bundling & minification این فایلها میتوان از «Bundler Minifier» استفاده کرد.
پروژهی موجودیتهای مدیریت هتل
فایل BlazorServer.Entities.csproj وابستگی خاصی را نداشته و به صورت زیر تعریف شدهاست:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> </Project>
using System; using System.ComponentModel.DataAnnotations; namespace BlazorServer.Entities { public class HotelRoom { [Key] public int Id { get; set; } [Required] public string Name { get; set; } [Required] public int Occupancy { get; set; } [Required] public decimal RegularRate { get; set; } public string Details { get; set; } public string SqFt { get; set; } public string CreatedBy { get; set; } public DateTime CreatedDate { get; set; } = DateTime.Now; public string UpdatedBy { get; set; } public DateTime UpdatedDate { get; set; } } }
پروژهی تعریف DbContext برنامهی مدیریت هتل
فایل BlazorServer.DataAccess.csproj به این صورت تعریف شدهاست:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\BlazorServer.Entities\BlazorServer.Entities.csproj" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> </Project>
- همچنین دو وابستگی مورد نیاز جهت کار با EntityFrameworkCore و اجرای مهاجرتها را نیز به آن افزودهایم.
پس از تامین این وابستگیها، اکنون میتوان DbContext ابتدایی برنامه را به صورت زیر تعریف کرد که کار آن، در معرض دید قرار دادن HotelRoom به صورت یک DbSet است:
using BlazorServer.Entities; using Microsoft.EntityFrameworkCore; namespace BlazorServer.DataAccess { public class ApplicationDbContext : DbContext { public DbSet<HotelRoom> HotelRooms { get; set; } public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } } }
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\BlazorServer.DataAccess\BlazorServer.DataAccess.csproj" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> </Project>
- سپس چون میخواهیم از تامین کنندهی بانک اطلاعاتی SQL Server نیز استفاده کنیم، وابستگیهای آنرا نیز افزودهایم.
با این تنظیمات، به فایل BlazorServer\BlazorServer.App\Startup.cs مراجعه کرده و کار افزودن AddDbContext و UseSqlServer را انجام میدهیم تا DbContext برنامه از طریق تزریق وابستگیها قابل دسترسی شود و همچنین رشتهی اتصالی مشخص شده نیز به تامین کنندهی SQL Server ارسال شود:
namespace BlazorServer.App { public class Startup { // ... public void ConfigureServices(IServiceCollection services) { var connectionString = Configuration.GetConnectionString("DefaultConnection"); services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); // ...
{ "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=HotelManagement;Trusted_Connection=True;MultipleActiveResultSets=true" } }
اجرای مهاجرتها و تشکیل ساختار بانک اطلاعاتی
پس از این تنظیمات، اکنون میتوانیم به پوشهی BlazorServer\BlazorServer.DataAccess مراجعه کرده و از طریق خط فرمان، دستورات زیر را صادر کنیم:
dotnet tool update --global dotnet-ef --version 5.0.3 dotnet build dotnet ef migrations --startup-project ../BlazorServer.App/ add Init --context ApplicationDbContext dotnet ef --startup-project ../BlazorServer.App/ database update --context ApplicationDbContext
- همیشه بهتر است پیش از اجرای عملیات Migration، یکبار dotnet build را اجرا کرد؛ تا اگر خطایی وجود دارد، بتوان جزئیات دقیق آنرا مشاهده کرد. چون عموما این جزئیات در حین اجرای دستورات بعدی، با پیام مختصر «عملیات شکست خورد»، نمایش داده نمیشوند.
- دستور سوم، کار تشکیل پوشهی BlazorServer\BlazorServer.DataAccess\Migrations و تولید خودکار دستورات تشکیل بانک اطلاعاتی را بر اساس ساختار DbContext برنامه انجام میدهد.
- دستور چهارم، بر اساس اطلاعات موجود در پوشهی BlazorServer\BlazorServer.DataAccess\Migrations، بانک اطلاعاتی واقعی را تولید میکند.
در این دستورات ذکر پروژهی آغازین برنامه جهت یافتن وابستگیهای پروژه ضروری است.
تکمیل پروژهی DTOهای برنامه
همواره توصیه شدهاست که موجودیتهای برنامه را مستقیما در معرض دید UI قرار ندهید. حداقل مشکلی را که در اینجا ممکن است مشاهده کنید، حملات از نوع mass assignment هستند. برای مثال قرار است از کاربر، کلمهی عبور جدید آنرا دریافت کنید، ولی چون اطلاعات دریافتی، به اصل موجودیت متناظر با بانک اطلاعاتی نگاشت میشود، کاربر میتواند فیلد IsAdmin را هم خودش مقدار دهی کند! و چون سیستم Binding بسیار پیشرفته عمل میکند، این ورودی را معتبر یافته و در اینجا علاوه بر به روز رسانی کلمهی عبور، خواص دیگری را هم که نباید به روز رسانی شوند، به روز رسانی میکند و یا در بسیاری از موارد نیاز است data annotations خاصی را برای فیلدها تعریف کرد که ربطی به موجودیت اصلی ندارند و یا نیاز است فیلدهایی را در UI قرار داد که باز هم تناظر یک به یکی با موجودیت اصلی ندارند (گاهی کمتر و گاهی بیشتر هستند و باید بر روی آنها محاسباتی صورت گیرد تا قابلیت ذخیره سازی در بانک اطلاعاتی را پیدا کنند). به همین جهت کار مدل سازی UI و یا بازگشت اطلاعات نهایی از سرویسها را توسط DTOها که یک سری کلاس سادهی C# 9.0 از نوع record هستند، انجام میدهیم:
using System; using System.ComponentModel.DataAnnotations; namespace BlazorServer.Models { public record HotelRoomDTO { public int Id { get; init; } [Required(ErrorMessage = "Please enter the room's name")] public string Name { get; init; } [Required(ErrorMessage = "Please enter the occupancy")] public int Occupancy { get; init; } [Range(1, 3000, ErrorMessage = "Regular rate must be between 1 and 3000")] public decimal RegularRate { get; init; } public string Details { get; init; } public string SqFt { get; init; } } }
در اینجا فیلدهای UI برنامه را که در قسمت بعد تکمیل خواهیم کرد، مشاهده میکنید؛ به همراه یک سری data annotation برای تعریف اجباری و یا بازهی مورد قبول، به همراه پیامهای خطای مرتبط.
نگاشت DTOهای برنامه به موجودیتها و بر عکس
یا میتوان خواص DTO تعریف شده را یکی یکی به موجودیتی متناظر با آن انتساب داد و یا میتوان از AutoMapper برای اینکار استفاده کرد. به همین جهت به BlazorServer.Models.Mappings.csproj مراجعه کرده و تغییرات زیر را اعمال میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\BlazorServer.Entities\BlazorServer.Entities.csproj" /> <ProjectReference Include="..\BlazorServer.Models\BlazorServer.Models.csproj" /> </ItemGroup> <ItemGroup> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" /> </ItemGroup> </Project>
- همچنین بستهی مخصوص AutoMapper را که به همراه امکانات تزریق وابستگیهای آن نیز هست، در اینجا افزودهایم.
پس از افزودن این ارجاعات، نگاشت دو طرفهی بین مدل و موجودیت تعریف شده را به صورت زیر تعریف میکنیم:
using AutoMapper; using BlazorServer.Entities; namespace BlazorServer.Models.Mappings { public class MappingProfile : Profile { public MappingProfile() { CreateMap<HotelRoomDTO, HotelRoom>().ReverseMap(); // two-way mapping } } }
namespace BlazorServer.App { public class Startup { // ... public void ConfigureServices(IServiceCollection services) { services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); // ...
تعریف سرویس مدیریت اتاقهای هتل
پس از راه اندازی برنامه و تعریف موجودیتها، DbContext و غیره، اکنون میتوانیم از آنها جهت ارائهی منطق مدیریتی برنامه استفاده کنیم:
using System.Collections.Generic; using System.Threading.Tasks; using BlazorServer.Models; namespace BlazorServer.Services { public interface IHotelRoomService { Task<HotelRoomDTO> CreateHotelRoomAsync(HotelRoomDTO hotelRoomDTO); Task<int> DeleteHotelRoomAsync(int roomId); IAsyncEnumerable<HotelRoomDTO> GetAllHotelRoomsAsync(); Task<HotelRoomDTO> GetHotelRoomAsync(int roomId); Task<HotelRoomDTO> IsRoomUniqueAsync(string name); Task<HotelRoomDTO> UpdateHotelRoomAsync(int roomId, HotelRoomDTO hotelRoomDTO); } }
که پیاده سازی ابتدایی آن به صورت زیر است:
using System; using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using AutoMapper.QueryableExtensions; using BlazorServer.DataAccess; using BlazorServer.Entities; using BlazorServer.Models; using Microsoft.EntityFrameworkCore; namespace BlazorServer.Services { public class HotelRoomService : IHotelRoomService { private readonly ApplicationDbContext _dbContext; private readonly IMapper _mapper; private readonly IConfigurationProvider _mapperConfiguration; public HotelRoomService(ApplicationDbContext dbContext, IMapper mapper) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _mapperConfiguration = mapper.ConfigurationProvider; } public async Task<HotelRoomDTO> CreateHotelRoomAsync(HotelRoomDTO hotelRoomDTO) { var hotelRoom = _mapper.Map<HotelRoom>(hotelRoomDTO); hotelRoom.CreatedDate = DateTime.Now; hotelRoom.CreatedBy = ""; var addedHotelRoom = await _dbContext.HotelRooms.AddAsync(hotelRoom); await _dbContext.SaveChangesAsync(); return _mapper.Map<HotelRoomDTO>(addedHotelRoom.Entity); } public async Task<int> DeleteHotelRoomAsync(int roomId) { var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId); if (roomDetails == null) { return 0; } _dbContext.HotelRooms.Remove(roomDetails); return await _dbContext.SaveChangesAsync(); } public IAsyncEnumerable<HotelRoomDTO> GetAllHotelRoomsAsync() { return _dbContext.HotelRooms .ProjectTo<HotelRoomDTO>(_mapperConfiguration) .AsAsyncEnumerable(); } public Task<HotelRoomDTO> GetHotelRoomAsync(int roomId) { return _dbContext.HotelRooms .ProjectTo<HotelRoomDTO>(_mapperConfiguration) .FirstOrDefaultAsync(x => x.Id == roomId); } public Task<HotelRoomDTO> IsRoomUniqueAsync(string name) { return _dbContext.HotelRooms .ProjectTo<HotelRoomDTO>(_mapperConfiguration) .FirstOrDefaultAsync(x => x.Name == name); } public async Task<HotelRoomDTO> UpdateHotelRoomAsync(int roomId, HotelRoomDTO hotelRoomDTO) { if (roomId != hotelRoomDTO.Id) { return null; } var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId); var room = _mapper.Map(hotelRoomDTO, roomDetails); room.UpdatedBy = ""; room.UpdatedDate = DateTime.Now; var updatedRoom = _dbContext.HotelRooms.Update(room); await _dbContext.SaveChangesAsync(); return _mapper.Map<HotelRoomDTO>(updatedRoom.Entity); } } }
- در این کدها استفاده از متد ProjectTo را هم مشاهده میکنید. استفاده از این متد، بسیار بهینهتر از کار با متد Map درون حافظهای است. از این جهت که بر روی SQL نهایی ارسالی به سمت سرور تاثیرگذار است و تعداد فیلدهای بازگشت داده شده را بر اساس DTO تعیین شده، کاهش میدهد. درغیراینصورت باید تمام ستونهای جدول را بازگشت داد و سپس با استفاده از متد Map درون حافظهای، کار نگاشت نهایی را انجام داد که آنچنان بهینه نیست.
در آخر نیاز است این سرویس را نیز به سیستم تزریق وابستگیهای برنامه معرفی کنیم. به همین جهت در فایل BlazorServer\BlazorServer.App\Startup.cs، تغییر زیر را اعمال خواهیم کرد:
namespace BlazorServer.App { public class Startup { // ... public void ConfigureServices(IServiceCollection services) { services.AddScoped<IHotelRoomService, HotelRoomService>(); // ...
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-13.zip
ASP.NET MVC #17
عنوان خطا: The anti-forgery token could not be decrypted
جزئیات خطا
The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.Web.Mvc.HttpAntiForgeryException: The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.
موارد مشابه در سایر انجمنها : اینجا و اینجا و ...
نتیجه حاصله: درج MachinKey در WebConfig برای مثال:
<system.web> <machineKey validationKey="mykey" decryptionKey="myotherkey" validation="SHA1" decryption="AES" /> </system.web>