مسیرراه‌ها
ASP.NET MVC
              نظرات مطالب
              ذخیره‌ی سوابق کامل تغییرات یک رکورد در یک فیلد توسط Entity framework Core
              سلام خواهش می‌کنم، ببینید بستگی به سناریو و پروژه‌ی ما این مورد می‌تونه متغیر باشه طوری که برای پروژه‌های سبک با بار کم سناریوی بالا می‌تونه خیلی مفید و دم دستی باشه ولی برای پروژه‌های سنگین‌تر می‌تونیم از روش جدول جداگانه استفاده کنیم که هم نیاز به  عملیات Serialize/Deserialize نخواهیم داشت هم این جدول می‌تونه اصلا توی یک دیتابیس دیگ و روی یه سرور دیگ باشه. برای برنامه‌های خیلی سنگین هم میشه از سناریوهای پیشرفته‌تر مثل ذخیره در دیتابیس‌های غیر SQL Server مثل Elasticsearch و با روش‌های ایجاد صف و غیره استفاده کرد.
              اشتراک‌ها
              کپی کردن فایل mdf و ldf در حالی که سیستم در حال اجرا می‌باشد

              برای کپی کردن فایل mdf و فایل ldf ، یک بانک اطلاعاتی ، بدون این که سرویس SQL Server را متوقف کنیم و یا این که آن بانک اطلاعاتی را Detach کنیم ، می‌توانیم از نرم افزار HoboCopy.exe استفاده نمایید . برای این کار ابتدا این فایل را در یک مسیری کپی نمایید . به طور مثال آن را در مسیر C:\Windows\System32 کپی کنید . سپس وارد PowerShell شوید . البته باید به صورت Run as administrator این کار را انجام دهید .
              سپس در آن مسیر نام فایل اجرای HoboCopy و سپس آدرس فایل مبداء (آدرس فایل‌های بانک اطلاعاتی) و در نهایت آدرس فولدری که بناست اطلاعات mdf و ldf در آنجا کپی شوند . i:\MyDB آدرس مبداء می‌باشد و آدرس i:\My ، آدرس مقصد می‌باشد . 

              کپی کردن فایل mdf و ldf در حالی که سیستم در حال اجرا می‌باشد
              نظرات مطالب
              سفارشی کردن ASP.NET Identity در MVC 5
              سفارشی سازی فیلتر Authorize از ارث بری از AuthorizeAttribute و سپس override کردن متد
              public override void OnAuthorization(AuthorizationContext filterContext)
              آن شروع می‌شود. در اینجا به اطلاعاتی مانند
              filterContext.ActionDescriptor.ControllerDescriptor.ControllerName
              filterContext.ActionDescriptor.ActionName
              و خیلی موارد دیگر (آدرس صفحه filterContext.HttpContext.Request.Url تا کاربر filterContext.HttpContext.User و غیره) دسترسی خواهید داشت.
              سپس باید طراحی جدیدی را بر اساس ControllerName و ActionName پیاده سازی کنید (یک جدول جدید طراحی کنید) تا این اکشن متدها یا کنترلرها امکان انتساب چندین Role متغیر را داشته باشند.
              حالا زمانیکه این فیلتر Authorize سفارشی سازی شده بجای فیلتر Autorize اصلی استفاده می‌شود، نام اکشن متد و کنترلر جاری را از filterContext  دریافت می‌کنید. سپس این دو مورد به همراه اطلاعات User جاری، پارامترهایی خواهند شد جهت کوئری گرفتن از بانک اطلاعاتی و جدولی که از آن صحبت شد.
              در اینجا هر زمانیکه نیاز بود دسترسی کاربری را قطع کنید فقط کافی است نتیجه‌ی این فیلتر سفارشی را به نحو ذیل بازگردانید:
               filterContext.Result = new HttpStatusCodeResult(403);
              بنابراین در قسمت ادمین، یک صفحه‌ی جدید برای ثبت نام کنترلرها و اکشن متدها به همراه نقش‌های پویای آن‌ها خواهید داشت. سپس در این فیلتر Authorize سفارشی، دقیقا مشخص است که اکنون در کدام کنترلر و اکشن متد قرار داریم. بر این اساس (و سایر پارامترهایی که می‌توان از filterContext استخراج کرد) یک کوئری گرفته می‌شود و نقش‌های پویای فیلتر Authorize دریافت می‌شوند. نقش‌های کاربر جاری هم که مشخص هستند. این‌ها را با هم مقایسه می‌کنید و خروجی 403 را درصورت عدم تطابق، تنظیم خواهید کرد.
              ضمنا در صفحه‌ی طراحی انتساب نقش‌های متغیر به اکشن متدها یا کنترلرها، امکان یافتن پویای لیست آن‌ها نیز وجود دارد.
              مطالب
              Blazor 5x - قسمت 16 - کار با فرم‌ها - بخش 4 - تهیه سرویس‌های آپلود تصاویر
              در ادامه می‌خواهیم برای هر اتاق ثبت شده، تعدادی تصویر مرتبط را نیز به سرور آپلود کرده و مشخصات آن‌ها را در بانک اطلاعاتی ثبت کنیم. به همین جهت در این قسمت سرویس ثبت اطلاعات تصاویر در بانک اطلاعاتی و سرویس آپلود فایل‌ها را تهیه می‌کنیم.


              تعریف موجودیت و DbSet تصاویر یک اتاق هتل

              برای اینکه بتوان اطلاعات تصاویر آپلودی را در بانک اطلاعاتی ثبت کرد، نیاز است یک رابطه‌ی یک به چند را بین یک اتاق و تصاویر مرتبط با آن برقرار کرد. به همین جهت ابتدا به پروژه‌ی BlazorServer.Entities.csproj مراجعه کرده و موجودیت ثبت اطلاعات تصاویر را تعریف می‌کنیم:
              using System.ComponentModel.DataAnnotations.Schema;
              
              namespace BlazorServer.Entities
              {
                  public class HotelRoomImage
                  {
                      public int Id { get; set; }
              
                      public string RoomImageUrl { get; set; }
              
                      [ForeignKey("RoomId")]
                      public virtual HotelRoom HotelRoom { get; set; }
                      public int RoomId { get; set; }
                  }
              }
              که در اینجا باید سر دیگر این رابطه‌ی one-to-many، در جدول HotelRoom نیز تعریف شود:
              namespace BlazorServer.Entities
              {
                  public class HotelRoom
                  {
                      // ...
                      public virtual ICollection<HotelRoomImage> HotelRoomImages { get; set; }
                  }
              }
              در آخر باید این موجودیت جدید را به Context برنامه معرفی کرد. برای اینکار به پروژه‌ی BlazorServer.DataAccess مراجعه کرده و DbSet متناظری را تعریف می‌کنیم:
              namespace BlazorServer.DataAccess
              {
                  public class ApplicationDbContext : DbContext
                  {
                      public DbSet<HotelRoomImage> HotelRoomImages { get; set; }
              
                      // ...
                  }
              }
              پس از این تغییرات، نیاز است یکبار دیگر عملیات Migrations را اجرا کرد، تا ساختار متناظر بانک اطلاعاتی این تغییرات ایجاد شود. بنابراین توسط خط فرمان به پوشه‌ی پروژه‌ی BlazorServer.DataAccess وارد شده و دستورات زیر را اجرا می‌کنیم. در اینجا نگارش 5.0.3 باید معادل نگارشی از EF-Core باشد که از آن در حال استفاده‌اید:
              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
              در مورد این دستورات در قسمت 13 بیشتر بحث شده‌است.


              تعریف مدل UI متناظر با هر تصویر

              همانطور که در قسمت 13 نیز عنوان شد، در حین کار با رابط کاربری برنامه، با موجودیت‌های بانک اطلاعاتی، به صورت مستقیم کار نخواهیم کرد و بر اساس نیازهای برنامه، یکسری کلاس DTO را تعریف می‌کنیم. بنابراین به پروژه‌ی BlazorServer.Models مراجعه کرده و DTO متناظر با HotelRoomImage را به صورت زیر اضافه می‌کنیم:
              namespace BlazorServer.Models
              {
                  public class HotelRoomImageDTO
                  {
                      public int Id { get; set; }
              
                      public int RoomId { get; set; }
              
                      public string RoomImageUrl { get; set; }
                  }
              }
              و همچنین جهت سهولت تبدیل اطلاعات بین موجودیت تعریف شده و DTO ی آن، نگاشت AutoMapper دو طرفه‌ای را در پروژه‌ی BlazorServer.Models.Mappings برقرار می‌کنیم:
              using AutoMapper;
              using BlazorServer.Entities;
              
              namespace BlazorServer.Models.Mappings
              {
                  public class MappingProfile : Profile
                  {
                      public MappingProfile()
                      {
                          // ...
                          CreateMap<HotelRoomImageDTO, HotelRoomImage>().ReverseMap(); // two-way mapping
                      }
                  }
              }

              تعریف سرویس کار با HotelRoomImage

              در اینجا نیز همانند سرویسی که برای انجام عملیات تجاری مرتبط با یک اتاق هتل، در قسمت 13 پیاده سازی کردیم، سرویس دیگری را در پروژه‌ی BlazorServer.Services برای کار با تصاویر اتاق‌ها تهیه می‌کنیم:
              namespace BlazorServer.Services
              {
                  public interface IHotelRoomImageService
                  {
                      Task<int> CreateHotelRoomImageAsync(HotelRoomImageDTO imageDTO);
              
                      Task<int> DeleteHotelRoomImageByImageIdAsync(int imageId);
              
                      Task<int> DeleteHotelRoomImageByRoomIdAsync(int roomId);
              
                      Task<List<HotelRoomImageDTO>> GetHotelRoomImagesAsync(int roomId);
                  }
              }
              برای نمونه بر اساس اطلاعات مدل UI برنامه، نیاز است بتوانیم اطلاعات یک تصویر را ثبت و یا حذف کنیم و یا لیست تصاویر یک اتاق را از بانک اطلاعاتی دریافت کنیم؛ با این پیاده سازی:
              namespace BlazorServer.Services
              {
                  public class HotelRoomImageService : IHotelRoomImageService
                  {
                      private readonly ApplicationDbContext _dbContext;
                      private readonly IMapper _mapper;
                      private readonly IConfigurationProvider _mapperConfiguration;
              
                      public HotelRoomImageService(ApplicationDbContext dbContext, IMapper mapper)
                      {
                          _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
                          _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
                          _mapperConfiguration = mapper.ConfigurationProvider;
                      }
              
                      public async Task<int> CreateHotelRoomImageAsync(HotelRoomImageDTO imageDTO)
                      {
                          var image = _mapper.Map<HotelRoomImage>(imageDTO);
                          await _dbContext.HotelRoomImages.AddAsync(image);
                          return await _dbContext.SaveChangesAsync();
                      }
              
                      public async Task<int> DeleteHotelRoomImageByImageIdAsync(int imageId)
                      {
                          var image = await _dbContext.HotelRoomImages.FindAsync(imageId);
                          _dbContext.HotelRoomImages.Remove(image);
                          return await _dbContext.SaveChangesAsync();
                      }
              
                      public async Task<int> DeleteHotelRoomImageByRoomIdAsync(int roomId)
                      {
                          var imageList = await _dbContext.HotelRoomImages.Where(x => x.RoomId == roomId).ToListAsync();
                          _dbContext.HotelRoomImages.RemoveRange(imageList);
                          return await _dbContext.SaveChangesAsync();
                      }
              
                      public Task<List<HotelRoomImageDTO>> GetHotelRoomImagesAsync(int roomId)
                      {
                          return _dbContext.HotelRoomImages
                                          .Where(x => x.RoomId == roomId)
                                          .ProjectTo<HotelRoomImageDTO>(_mapperConfiguration)
                                          .ToListAsync();
                      }
                  }
              }
              پس از این تعاریف، به فایل BlazorServer\BlazorServer.App\Startup.cs مراجعه کرده و این سرویس را به سیستم تزریق وابستگی‌های برنامه معرفی می‌کنیم:
              namespace BlazorServer.App
              {
                  public class Startup
                  {
                      public void ConfigureServices(IServiceCollection services)
                      {
                          services.AddScoped<IHotelRoomImageService, HotelRoomImageService>();
                          // ...


              تهیه سرویسی برای آپلود فایل‌های یک برنامه‌ی Blazor Server به سرور

              جهت ساده سازی کار آپلود، در برنامه‌های Blazor Server، سرویس جدید FileUploadService را به پروژه‌ی BlazorServer.Services اضافه می‌کنیم:
              using Microsoft.AspNetCore.Components.Forms;
              using System.Threading.Tasks;
              
              namespace BlazorServer.Services
              {
                  public interface IFileUploadService
                  {
                      void DeleteFile(string fileName, string webRootPath, string uploadFolder);
                      Task<string> UploadFileAsync(IBrowserFile inputFile, string webRootPath, string uploadFolder);
                  }
              }
              کار آن حذف یک فایل، بر اساس مسیر آن است و همچنین دریافت یک IBrowserFile از کاربر و ذخیره سازی اطلاعات آن در سرور؛ با این پیاده سازی:
              using Microsoft.AspNetCore.Components.Forms;
              using System;
              using System.IO;
              using System.Threading.Tasks;
              
              namespace BlazorServer.Services
              {
                  public class FileUploadService : IFileUploadService
                  {
                      private const int MaxBufferSize = 0x10000;
              
                      public void DeleteFile(string fileName, string webRootPath, string uploadFolder)
                      {
                          var path = Path.Combine(webRootPath, uploadFolder, fileName);
                          if (File.Exists(path))
                          {
                              File.Delete(path);
                          }
                      }
              
                      public async Task<string> UploadFileAsync(IBrowserFile inputFile, string webRootPath, string uploadFolder)
                      {
                          createUploadDir(webRootPath, uploadFolder);
                          var (fileName, imageFilePath) = getOutputFileInfo(inputFile, webRootPath, uploadFolder);
              
                          using (var outputFileStream = new FileStream(
                                      imageFilePath, FileMode.Create, FileAccess.Write,
                                      FileShare.None, MaxBufferSize, useAsync: true))
                          {
                              using var inputStream = inputFile.OpenReadStream();
                              await inputStream.CopyToAsync(outputFileStream);
                          }
              
                          return $"{uploadFolder}/{fileName}";
                      }
              
                      private static (string FileName, string FilePath) getOutputFileInfo(
                                  IBrowserFile inputFile, string webRootPath, string uploadFolder)
                      {
                          var fileName = Path.GetFileName(inputFile.Name);
                          var imageFilePath = Path.Combine(webRootPath, uploadFolder, fileName);
                          if (File.Exists(imageFilePath))
                          {
                              var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
                              var fileExtension = Path.GetExtension(fileName);
                              fileName = $"{fileNameWithoutExtension}-{Guid.NewGuid()}{fileExtension}";
                              imageFilePath = Path.Combine(webRootPath, uploadFolder, fileName);
                          }
                          return (fileName, imageFilePath);
                      }
              
                      private static void createUploadDir(string webRootPath, string uploadFolder)
                      {
                          var folderDirectory = Path.Combine(webRootPath, uploadFolder);
                          if (!Directory.Exists(folderDirectory))
                          {
                              Directory.CreateDirectory(folderDirectory);
                          }
                      }
                  }
              }
              اگر در ASP.NET Core، اطلاعات فایل ارسالی به سرور، توسط IFormFile به اکشن متدهای کنترلرها ارسال می‌شود، در برنامه‌های Blazor Server اینکار توسط IBrowserFile صورت می‌گیرد. کلیات کار با آن، بسیار شبیه به IFormFile است و اگر به مطلب «بررسی روش آپلود فایل‌ها در ASP.NET Core» مراجعه کنید، تفاوت آنچنانی را مشاهده نخواهید کرد. تنها تفاوت پیاده سازی که در اینجا وجود دارد، نیاز به استفاده‌ی از متد ()inputFile.OpenReadStream جهت دسترسی به محتوای فایل آپلودی، برای ذخیره‌ی آن در سمت سرور است؛ وگرنه مابقی کدهای آپلود آن، با ASP.NET Core یکی است.
              همچنین برای دسترسی به IBrowserFile در یک سرویس، نیاز است وابستگی زیر را نیز به پروژه‌ی سرویس‌ها اضافه کرد:
              <Project Sdk="Microsoft.NET.Sdk">
                <ItemGroup>
                  <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.3" />
                </ItemGroup>
              </Project>
              پس از آن، به فایل BlazorServer\BlazorServer.App\Startup.cs مراجعه کرده و این سرویس را به سیستم تزریق وابستگی‌های برنامه معرفی می‌کنیم:
              namespace BlazorServer.App
              {
                  public class Startup
                  {
                      public void ConfigureServices(IServiceCollection services)
                      {
                          services.AddScoped<IFileUploadService, FileUploadService>();
                          // ...
              در قسمت بعد، از این سرویس‌ها جهت مدیریت آپلود تصاویر استفاده خواهیم کرد.


              کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-16.zip