http://bahramshahrfa.blogspot.com/2007/12/persian-date-for-blogger.html
تاریخ شمسی برای blogger !
http://bahramshahrfa.blogspot.com/2007/12/persian-date-for-blogger.html
یکی از نکات امنیتی که استاندارد Owasp بررسی مینماید هدر X-Content-Type-Options است که جهت جلوگیری از حملات از طریق فایلهای نامرتبط میباشد. در این رخنه ممکن است فایلی که مرورگر دریافت میکند با آنچه که وب سایت ما آن را میشناسد متفاوت باشد. به عنوان مثال یک فایل اسکریپت که به عنوان یک فایل استایل معرفی میگردد ولی قابلیت اجرای کدهای آن در مرورگر امکان پذیر است؛ به این نوع حملات MIME Sniffing میگویند. در یکی از سایتهایی که در حال حاضر در حال توسعه آن هستیم بخشی از گزارشها با استفاده از ابزار FastReport ایجاد شده بود و با توجه به اینکه در این ابزار از فایلهای axd استفاده میگردید و مرورگر نوع دیتای برگشتی و MimeType معرفی شده را همخوان نمیدانست، از نمایش و بارگذاری آن ممانعت به عمل آورده و در نتیجه فایل گزارش دیده نمیشد. در این مقاله قصد داریم به معرفی این نوع حمله، روش جلوگیری از آن و همچنین رفع محدودیت پیش آمده را بررسی کرده تا در موارد مشابه از آن استفاده نماییم.
Mime Sniffing
زمانیکه مرورگر در هدر(پاسخ) Response، نوع محتوای ارسال شده، Content-Type را دریافت نکند، یا مرورگر متوجه مغایرتی در آن شود، این نوع رفتار را Mime Sniffing شناسایی میکند. نحوه این شناساییها در هر مرورگری میتواند متفاوت باشد؛ ولی عموما بر اساس type ارسالی و پسوند مورد نظر میباشد. در بعضی از موارد نیز خواندن بایتهای ابتدایی یک فایل نیز میتواند نشان دهد که محتوای ارسالی واقعا چیست. به عنوان مثال برای فایلهایی با پسوند Gif، الگوی بایتهای ابتدایی شامل 47 49 46 38 39 میباشد؛ ولی از آنجا که در همه فایلها، بایتهای ابتدایی الگوی یکسانی ندارند، پس نمیتوان به این روش نیز بسنده کرد.
روش اینکه به مروگر بگوییم جلوی این نوع حملات را بگیرد و در صورت شناسایی Sniffing از اجرای آن سر باز بزند، استفاده از هدر X-Content-Type-Options میباشد که نحوه افزودن آن در فایل web.config به شکل زیر است:
<httpProtocol> <customHeaders> ... <remove name="X-Content-Type-Options"/> <add name="X-Content-Type-Options" value="nosniff" /> .. </customHeaders> </httpProtocol>
در پروژه ما به دلیل اینکه بخشی از گزارشها با استفاده از FastReport طراحی شده بود این مورد برای ما ایجاد مشکل میکرد و در گزارش نمایش داده نمیشد و در کنسول پیامهایی به شکل زیر دریافت میکردیم:
Refused to execute script from 'https://localhost:44377/WebResource.axd?d=xxx' because its MIME type ('text/js') is not executable, and strict MIME type checking is enabled.
با نگاهی به Response دریافتی نیز میتوان بازگشت هدر امنیتی X-Content-Type-Options را نیز مشاهده نمود:
پس باید این هدر را برای بعضی از آدرسها که میتوانند دچار مشکلات اجرایی گردند حذف کرده و برای مابقی بخشها همچنان این هدر فعال باشد؛ پس با افزودن کد زیر به web.config، هدر مورد نظر را برای این نوع فایل حذف میکنیم:
<location path="WebResource.axd" > <system.webServer> <httpProtocol> <customHeaders> <remove name="X-Content-Type-Options" /> </customHeaders> </httpProtocol> </system.webServer> </location>
نتیجه آن را میتوانید در صفحه ذیل برای همان درخواست و پاسخ قبلی نیز مشاهده نمایید:
Roles
در مخزن پیش فرض ASP.NET Identity EntityFramework کلاسهای بیشتری برای موجودیتها مشاهده میکنید.
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user) { ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id); return Task.FromResult<IList<Claim>>(identity.Claims.ToList()); }
public class IdentityUser : IUser { public IdentityUser(){...} public IdentityUser(string userName) (){...} public string Id { get; set; } public string UserName { get; set; } public string PasswordHash { get; set; } public string SecurityStamp { get; set; } }
public class UserStore : IUserStore<IdentityUser>, IUserClaimStore<IdentityUser>, IUserLoginStore<IdentityUser>, IUserRoleStore<IdentityUser>, IUserPasswordStore<IdentityUser> { public UserStore(){...} public Task CreateAsync(IdentityUser user){...} public Task<IdentityUser> FindByIdAsync(string userId){...} ... }
public class IdentityRole : IRole { public IdentityRole(){...} public IdentityRole(string roleName) (){...} public string Id { get; set; } public string Name { get; set; } }
public class RoleStore : IRoleStore<IdentityRole> { public RoleStore(){...} public Task CreateAsync(IdentityRole role){...} public Task<IdentityRole> FindByIdAsync(string roleId){...} .... }
در ویزارد Choose Add-on به سمت پایین اسکرول کنید و گزینه ClearDB MySQL Database را انتخاب کنید. سپس به مرحله بعد بروید.
4. راهکار Free بصورت پیش فرض انتخاب شده، همین گزینه را انتخاب کنید و نام دیتابیس را به IdentityMySQLDatabase تغییر دهید. نزدیکترین ناحیه (region) به خود را انتخاب کنید و به مرحله بعد بروید.
5. روی علامت checkmark کلیک کنید تا دیتابیس شما ایجاد شود. پس از آنکه دیتابیس شما ساخته شد میتوانید از قسمت ADD-ONS آن را مدیریت کنید.
6. همانطور که در تصویر بالا میبینید، میتوانید اطلاعات اتصال دیتابیس (connection info) را از پایین صفحه دریافت کنید.
7. اطلاعات اتصال را با کلیک کردن روی دکمه مجاور کپی کنید تا بعدا در اپلیکیشن MVC خود از آن استفاده کنیم.
CREATE TABLE `IdentityMySQLDatabase`.`users` ( `Id` VARCHAR(45) NOT NULL, `UserName` VARCHAR(45) NULL, `PasswordHash` VARCHAR(100) NULL, `SecurityStamp` VARCHAR(45) NULL, PRIMARY KEY (`id`)); CREATE TABLE `IdentityMySQLDatabase`.`roles` ( `Id` VARCHAR(45) NOT NULL, `Name` VARCHAR(45) NULL, PRIMARY KEY (`Id`)); CREATE TABLE `IdentityMySQLDatabase`.`userclaims` ( `Id` INT NOT NULL AUTO_INCREMENT, `UserId` VARCHAR(45) NULL, `ClaimType` VARCHAR(100) NULL, `ClaimValue` VARCHAR(100) NULL, PRIMARY KEY (`Id`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userlogins` ( `UserId` VARCHAR(45) NOT NULL, `ProviderKey` VARCHAR(100) NULL, `LoginProvider` VARCHAR(100) NULL, FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userroles` ( `UserId` VARCHAR(45) NOT NULL, `RoleId` VARCHAR(45) NOT NULL, PRIMARY KEY (`UserId`, `RoleId`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade on update cascade, FOREIGN KEY (`RoleId`) REFERENCES `IdentityMySQLDatabase`.`roles` (`Id`) on delete cascade on update cascade);
6. در پنجره New ASP.NET Project قالب MVC را انتخاب کنید و تنظیمات پیش فرض را بپذیرید.
7. در پنجره Solution Explorer روی پروژه IdentityMySQLDemo کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.EntityFramework" را وارد کنید. در لیست نتایج این پکیج را انتخاب کرده و آن را حذف (Uninstall) کنید. پیغامی مبنی بر حذف وابستگیها باید دریافت کنید که مربوط به پکیج EntityFramework است، گزینه Yes را انتخاب کنید. از آنجا که کاری با پیاده سازی فرض نخواهیم داشت، این پکیجها را حذف میکنیم.
8. روی پروژه IdentityMySQLDemo کلیک راست کرده و Add, Reference, Solution, Projects را انتخاب کنید. در دیالوگ باز شده پروژه AspNet.Identity.MySQL را انتخاب کرده و OK کنید.
9. در پروژه IdentityMySQLDemo پوشه Models را پیدا کرده و کلاس IdentityModels.cs را حذف کنید.
10. در پروژه IdentityMySQLDemo تمام ارجاعات ";using Microsoft.AspNet.Identity.EntityFramework" را با ";using AspNet.Identity.MySQL" جایگزین کنید.
11. در پروژه IdentityMySQLDemo تمام ارجاعات به کلاس "ApplicationUser" را با "IdentityUser" جایگزین کنید.
12. کنترلر Account را باز کنید و متد سازنده آنرا مطابق لیست زیر تغییر دهید.
public AccountController() : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase()))) { }
13. فایل web.config را باز کنید و رشته اتصال DefaultConnection را مطابق لیست زیر تغییر دهید.
<add name="DefaultConnection" connectionString="Database=IdentityMySQLDatabase;Data Source=<DataSource>;User Id=<UserID>;Password=<Password>" providerName="MySql.Data.MySqlClient" />
مقادیر <DataSource>, <UserId> و <Password> را با اطلاعات دیتابیس خود جایگزین کنید.
5. در این مرحله کاربر جدید باید ایجاد شده و وارد سایت شود.
6. به ابزار MySQL Workbench بروید و محتوای جداول IdentityMySQLDatabase را بررسی کنید. جدول users را باز کنید و اطلاعات کاربر جدید را بررسی نمایید.
برای ساده نگاه داشتن این مقاله از بررسی تمام کدهای لازم خودداری شده، اما اگر مراحل را دنبال کنید و سورس کد نمونه را دریافت و بررسی کنید خواهید دید که پیاده سازی تامین کنندگان سفارشی برای ASP.NET Identity کار نسبتا ساده ای است.
One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmEntityType: : EntityType 'Blog' has no key defined.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace EF_Sample01.Models
{
public class Blog
{
[Key]
public int MyTableKey { set; get; }
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace EF_Sample01.Models
{
public class Blog
{
[Key]
public int MyTableKey { set; get; }
[MaxLength(100)]
public string Title { set; get; }
[Required]
public string AuthorName { set; get; }
public IList<Post> Posts { set; get; }
}
}
CREATE TABLE [dbo].[Blogs](
[MyTableKey] [int] IDENTITY(1,1) NOT NULL,
[Title] [nvarchar](100) NULL,
[AuthorName] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY CLUSTERED
(
[MyTableKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
[Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
public string AuthorName { set; get; }
using System.Data.Entity;
using EF_Sample01.Models;
namespace EF_Sample01
{
public class Context : DbContext
{
public DbSet<Blog> Blogs { set; get; }
public DbSet<Post> Posts { set; get; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasKey(x => x.MyTableKey);
modelBuilder.Entity<Blog>().Property(x => x.Title).HasMaxLength(100);
modelBuilder.Entity<Blog>().Property(x => x.AuthorName).IsRequired();
base.OnModelCreating(modelBuilder);
}
}
}
using System.Data.Entity;
using EF_Sample01.Models;
using System.Data.Entity.ModelConfiguration;
namespace EF_Sample01
{
public class BlogConfig : EntityTypeConfiguration<Blog>
{
public BlogConfig()
{
this.Property(x => x.Id).HasColumnName("MyTableKey");
this.Property(x => x.RowVersion).HasColumnType("Timestamp");
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new BlogConfig());
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace EF_Sample01.Models
{
[Table("tblBlogs")]
public class Blog
{
[Column("MyTableKey")]
public int Id { set; get; }
[MaxLength(100)]
public string Title { set; get; }
[Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
public string AuthorName { set; get; }
public IList<Post> Posts { set; get; }
[Timestamp]
public byte[] RowVersion { set; get; }
}
}
modelBuilder.Entity<Blog>().ToTable("tblBlogs");
modelBuilder.Entity<Blog>().Property(x => x.Id).HasColumnName("MyTableKey");
modelBuilder.Entity<Blog>().Property(x => x.RowVersion).HasColumnType("Timestamp");
System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
// or
System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
public class MyInitializer : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
if (context.Database.Exists() ||
context.Database.CompatibleWithModel(throwIfNoMetadata: false))
context.Database.Delete();
context.Database.Create();
}
}
System.Data.Entity.Database.SetInitializer(new MyInitializer());
Database.SetInitializer<Context>(null);
<appSettings>
<add key="DatabaseInitializerForType MyNamespace.MyDbContextClass, MyAssembly"
value="MyNamespace.MyInitializerClass, MyAssembly" />
</appSettings>
<appSettings>
<add key="DatabaseInitializerForType MyNamespace.MyDbContextClass, MyAssembly"
value="Disabled" />
</appSettings>
<appSettings>
<add key="DatabaseInitializerForType EF_Sample01.Context, EF_Sample01"
value="EF_Sample01.MyInitializer, EF_Sample01" />
</appSettings>
public class MyCustomInitializer : DropCreateDatabaseIfModelChanges<Context>
{
protected override void Seed(Context context)
{
context.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
context.Database.ExecuteSqlCommand("CREATE INDEX IX_title ON tblBlogs (title)");
base.Seed(context);
}
}
Server=(local);Database=yourDatabase;User ID=yourDBUser;Password=yourDBPassword;Trusted_Connection=False;Persist Security Info=True
[Table("tblBlogs", Schema="someUser")]
public class Blog
modelBuilder.Entity<Blog>().ToTable("tblBlogs", schemaName:"someUser");
using (var sha256 = new SHA256Managed()) { // Crypto code here... }
public static string GetHash(string text) { using (var sha256 = SHA256.Create()) { var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(); } }
"dependencies": { "System.Security.Cryptography.Algorithms": "4.2.0" },
using (var md5 = IncrementalHash.CreateHash(HashAlgorithmName.MD5)) { md5.AppendData(byteArray1, 0, byteArray1.Length); md5.AppendData(byteArray2, 0, byteArray2.Length); var hash = md5.GetHashAndReset(); }
private static readonly RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider();
var rand = new RNGCryptoServiceProvider();
public interface IRandomNumberProvider { int Next(); int Next(int max); int Next(int min, int max); } public class RandomNumberProvider : IRandomNumberProvider { private readonly RandomNumberGenerator _rand = RandomNumberGenerator.Create(); public int Next() { var randb = new byte[4]; _rand.GetBytes(randb); var value = BitConverter.ToInt32(randb, 0); if (value < 0) value = -value; return value; } public int Next(int max) { var randb = new byte[4]; _rand.GetBytes(randb); var value = BitConverter.ToInt32(randb, 0); value = value % (max + 1); // % calculates remainder if (value < 0) value = -value; return value; } public int Next(int min, int max) { var value = Next(max - min) + min; return value; } }
public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IRandomNumberProvider, RandomNumberProvider>();
public interface IProtectionProvider { string Decrypt(string inputText); string Encrypt(string inputText); } namespace Providers { public class ProtectionProvider : IProtectionProvider { private readonly IDataProtector _dataProtector; public ProtectionProvider(IDataProtectionProvider dataProtectionProvider) { _dataProtector = dataProtectionProvider.CreateProtector(typeof(ProtectionProvider).FullName); } public string Decrypt(string inputText) { var inputBytes = Convert.FromBase64String(inputText); var bytes = _dataProtector.Unprotect(inputBytes); return Encoding.UTF8.GetString(bytes); } public string Encrypt(string inputText) { var inputBytes = Encoding.UTF8.GetBytes(inputText); var bytes = _dataProtector.Protect(inputBytes); return Convert.ToBase64String(bytes); } } }
public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IProtectionProvider, ProtectionProvider>();
public string Decrypt(string inputText, string key, string salt) { var inputBytes = Convert.FromBase64String(inputText); var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt)); using (var ms = new MemoryStream()) { var alg = Aes.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(inputBytes, 0, inputBytes.Length); } return Encoding.UTF8.GetString(ms.ToArray()); } } public string Encrypt(string inputText, string key, string salt) { var inputBytes = Encoding.UTF8.GetBytes(inputText); var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt)); using (var ms = new MemoryStream()) { var alg = Aes.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(inputBytes, 0, inputBytes.Length); } return Convert.ToBase64String(ms.ToArray()); } }
namespace System { public interface IServiceProvider { object GetService(Type serviceType); } }
using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace CoreIocServices { public interface IProductService { void Delete(int id); } public class ProductService : IProductService { private readonly ITestService _testService; private readonly ILogger<ProductService> _logger; public ProductService(IServiceProvider serviceProvider) { _testService = serviceProvider.GetRequiredService<ITestService>(); _logger = serviceProvider.GetService<ILogger<ProductService>>() ?? NullLogger<ProductService>.Instance; } public void Delete(int id) { _testService.Run(); _logger.LogInformation($"Deleted a product with id = {id}"); } } }
namespace Microsoft.Extensions.DependencyInjection { public static class ServiceProviderServiceExtensions { public static T GetRequiredService<T>(this IServiceProvider provider); public static T GetService<T>(this IServiceProvider provider); } }
using Persia; namespace Iris.Utilities.DateAndTime { public class DateAndTime { public static DateTime GetDateTime() { return DateTime.Now; } public static string ConvertToPersian(DateTime dateTime, string mod = "") { SolarDate solar = Calendar.ConvertToPersian(dateTime); return string.IsNullOrEmpty(mod) ? solar.ToString() : solar.ToString(mod); } } }
using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; namespace GSD.Globalization { /// <summary> /// <Publisher>http://www.Sayan.ir</Publisher> /// <Author>Maziar Rezaie</Author> /// </summary> public class PersianCulture : CultureInfo { private readonly Calendar cal; private readonly Calendar[] optionals; /// <summary> /// کد رو بخوان تا بفهمی /// </summary> /// <param name="cultureName">fa-IR</param> /// <param name="useUserOverride">true</param> /// <remarks>لطفا در هنگام استفاده به سایت سایان اشاره کنید.</remarks> public PersianCulture() : this("fa-IR", true) { } public PersianCulture(string cultureName, bool useUserOverride) : base(cultureName, useUserOverride) { //Temporary Value for cal. cal = base.OptionalCalendars[0]; //populating new list of optional calendars. var optionalCalendars = new List<Calendar>(); optionalCalendars.AddRange(base.OptionalCalendars); optionalCalendars.Insert(0, new PersianCalendar()); Type formatType = typeof(DateTimeFormatInfo); Type calendarType = typeof(Calendar); PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars", BindingFlags.Instance | BindingFlags.NonPublic); //populating new list of optional calendar ids var newOptionalCalendarIDs = new Int32[optionalCalendars.Count]; for (int i = 0; i < newOptionalCalendarIDs.Length; i++) newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null); optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs); optionals = optionalCalendars.ToArray(); cal = optionals[0]; DateTimeFormat.Calendar = optionals[0]; DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" }; DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" }; DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" }; DateTimeFormat.AMDesignator = "ق.ظ"; DateTimeFormat.PMDesignator = "ب.ظ"; /* DateTimeFormat.ShortDatePattern = "yyyy/MM/dd"; DateTimeFormat.LongDatePattern = "yyyy/MM/dd"; DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd'); DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D'); DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y'); DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y'); */ } public override Calendar Calendar { get { return cal; } } public override Calendar[] OptionalCalendars { get { return optionals; } } } }
using GSD.Globalization; using System.Threading; protected void Application_BeginRequest(object sender, EventArgs e) { var persianCulture = new PersianCulture(); Thread.CurrentThread.CurrentCulture = persianCulture; Thread.CurrentThread.CurrentUICulture = persianCulture; }
Memento یک الگوی طراحی مفید و ساده است که برای ذخیره و بازیابی state یک object استفاده میشود. در بعضی از مقالات از آن به عنوان snapshot نیز یاد شده است! اگر با git کار کرده باشید، این مفهوم را میتوان در git بسیار یافت؛ هر commit به عنوان یک snapshot میباشد که میتوان به صورت مکرر آن را undo کرد و یا مثال خیلی سادهتر میتوان به ctrl+z در سیستم عامل اشاره کرد.
به مثال زیر توجه کنید:
Int temp; Int a=1; temp=a; a=2; . . a=temp;
شما قطعا در برنامه نویسی با کد بالا زیاد برخورد داشتهاید و آنرا به صورت مکرر انجام دادهاید. کد بالا را در قالب یک object بیان میکنیم. به مثال زیر توجه کنید:
int main() { MyClass One = new MyClass(); MyClass Temp = new MyClass(); // Set an initial value. One.Value = 10; One.Name = "Ten"; // Save the state of the value. Temp.Value = One.Value; Temp.Name = One.Name; // Change the value. One.Value = 99; One.Name = "Ninety Nine"; // Undo and restore the state. One.Value = Temp.Value; One.Name = Temp.Name; }
در کد بالا با استفاده از یک temp، شیء مورد نظر را ذخیره کرده و در آخر مجدد دادهها را درون شیء، restore میکنیم.
از مشکلات کد بالا میتوان گفت :
۱- برای هر object باید یک شیء temp ایجاد کنیم.
۲- ممکن است بخواهیم که حالات یک object را بر روی هارد ذخیره کنیم. با روش فوق کدها خیلی پیچیدهتر خواهند شد.
۳- نوشتن کد به این سبک برای پروژههای بزرگ، پیچیده و مدیریت آن سختتر میشود.
پیاده سازی memento
ما این مثال را در قالب یک پروژه NET Core onsole. ایجاد میکنیم. برای این کار یک پوشهی جدید را ایجاد و درون ترمینال دستور زیر را وارد کنید:
dotnet new console
روشهای زیادی برای پیاده سازی memento وجود دارند. برای پیاده سازی memento ابتدا یک abstract class را به شکل زیر ایجاد میکنیم:
abstract class MementoBase { protected Guid mementoKey = Guid.NewGuid(); abstract public void SaveMemento(Memento memento); abstract public void RestoreMemento(Memento memento); }
اگر به کلاس بالا دقت کنید، این کلاس قرار است parent کلاسهای دیگری باشد که داری دو متد SaveMemento و RestoreMemento برای ذخیره و بازیابی و همچنین یک Guid برای نگهداری stateهای مختلف میباشد.
ورودی متدها از نوع memento میباشد. پس کلاس memento را به شکل زیر ایجاد میکنیم:
class Memento { private Dictionary<Guid, object> stateList = new Dictionary<Guid, object>(); public object GetState(Guid key) { return stateList[key]; } public void SetState(Guid key, object newState) { stateList[key] = newState; } public Memento() { } }
در کد بالا با یک Dictionary میتوان هر object را با کلیدش ذخیره کنیم. توجه کنید که value دیکشنری از نوع object میباشد و چون object پدر تمام objectهای دیگر است پس میتوانیم هر نوع دادهای را در آن ذخیره کنیم. تا اینجا، Memento پیاده سازی شده است. میتوان این کار را با جنریکها نیز پیاده سازی کرد.
در ادامه میخواهیم یک کلاس بسازیم و حالتهای مختلف را در آن بررسی کنیم. کلاس زیر را ایجاد کنید:
class ConcreteOriginator : MementoBase { private int value = 0; public ConcreteOriginator(int newValue) { SetData(newValue); } public void SetData(int newValue) { value = newValue; } public void Speak() { Console.WriteLine("My value is " + value.ToString()); } public override void SaveMemento(Memento memento) { memento.SetState(mementoKey, value); } public override void RestoreMemento(Memento memento) { int restoredValue = (int)memento.GetState(mementoKey); SetData(restoredValue); } }
کلاس ConcreteOriginator از کلاس MementoBase ارث بری کرده و دو متد RestoreMemento و SaveMemento را پیاده سازی میکند و همچنین دارای یک مشخصه value میباشد. برای خروجی گرفتن، متد main را به صورت زیر پیاده سازی میکنیم:
static void Main(string[] args) { Memento memento = new Memento(); // Create an originator, which will hold our state data. ConcreteOriginator myOriginator = new ConcreteOriginator("Hello World!", StateType.ONE); ConcreteOriginator anotherOriginator = new ConcreteOriginator("Hola!", StateType.ONE); ConcreteOriginator2 thirdOriginator = new ConcreteOriginator2(7); // Set some state data. myOriginator.Speak(); anotherOriginator.Speak(); thirdOriginator.Speak(); // Save the states into our memento. myOriginator.SaveMemento(memento); anotherOriginator.SaveMemento(memento); thirdOriginator.SaveMemento(memento); // Now change our originators' states. myOriginator.SetData("Goodbye!", StateType.TWO); anotherOriginator.SetData("Adios!", StateType.TWO); thirdOriginator.SetData(99); myOriginator.Speak(); anotherOriginator.Speak(); thirdOriginator.Speak(); // Restore our originator's state. myOriginator.RestoreMemento(memento); anotherOriginator.RestoreMemento(memento); thirdOriginator.RestoreMemento(memento); myOriginator.Speak(); anotherOriginator.Speak(); thirdOriginator.Speak(); Console.ReadKey(); }
Hello World! I'm in state ONE Hola! I'm in state ONE My value is 7 Goodbye! I'm in state TWO Adios! I'm in state TWO My value is 99 Hello World! I'm in state ONE Hola! I'm in state ONE My value is 7
[Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", }; // /WeatherForecast/GetForecast2?from=1&to=3 [HttpGet("[action]")] public IEnumerable<WeatherForecast> GetForecast2(Duration days) { return Enumerable.Range(days.From, days.To) .Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)], }) .ToArray(); } }
public class Duration { public int From { get; set; } public int To { get; set; } } public class WeatherForecast { public DateOnly Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; set; } }
// /WeatherForecast/GetForecast3/1-3 [HttpGet("[action]/{days}")] public IEnumerable<WeatherForecast> GetForecast3(Days days) { return Enumerable.Range(days.From, days.To) .Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)], }) .ToArray(); }
using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace NET7Mvc.Models; public class Days : IParsable<Days> { public Days(int from, int to) { From = from; To = to; } public int From { get; } public int To { get; } public static bool TryParse([NotNullWhen(true)] string? value, IFormatProvider? provider, [MaybeNullWhen(false)] out Days result) { var items = value?.Split('-', StringSplitOptions.RemoveEmptyEntries); if (items is { Length: 2 }) { if (int.TryParse(items[0], NumberStyles.None, provider, out var from) && int.TryParse(items[1], NumberStyles.None, provider, out var to)) { result = new Days(from, to); return true; } } result = default; return false; } public static Days Parse(string value, IFormatProvider? provider) { if (!TryParse(value, provider, out var result)) { throw new ArgumentException("Could not parse the given value.", nameof(value)); } return result; } }