Options یا Maybe در یک زبان تابعی مثل #F، نشان دهندهی این است که شیء (Object) ممکن است وجود نداشته باشد(Null Reference) که یکی از مهمترین ویژگیهای یک زبان شیءگرا مثل #C و یا Java محسوب میشود. ما برنامه نویسها (اغلب) از هرچیزی که باعث کرش برنامه میشود، بیزاریم و برای اینکه برنامه کرش نکند، مجبور میشویم تمام کدهای خود را از Null Reference محافظت کنیم. تمام این مشکلات توسط Tony Hoare مخترع ALOGL است که تنها دلیل وجود Null References را سادگی پیاده سازی آن میداند و او این مورد را یک «خطای میلیون دلاری» نامیدهاست.
به این مثال توجه بفرمایید:
public class User { public int Id { get; set; } public string Name { get; set; } } public class UserService : IUserService { private IList<User> _userData; public UserService() { _userData = new List<User> { new User {Id = 1,Name = "ali"}, new User {Id = 2,Name = "Karim"} }; } public User GetById(int id) { return _userData.FirstOrDefault(x => x.Id == id); } } public class UserController : Controller { private readonly IUserService _userService; public UserController(IUserService userService) { _userService = userService; } public ActionResult Details(int id) { var user=_userService.GetById(3); // این متد ممکن است مقداری برگرداند و یا مقدار نال برگرداند if( user == null) return HttpNotFound(); return View(user); } }
این کدی است که ما برنامه نویسان به صورت متداولی با آن سروکار داریم. اما چه چیزی درباره این کد اشکال دارد؟
مشکل از آن جایی هست که ما نمیدانیم متد GetById مقداری را برمیگرداند و یا Null را بر میگرداند. این متد هرگاه که امکان برگرداندن Null وجود داشته باشد، خطای NullReferenceException را در زمان اجرا بر میگرداند و همان طور که میدانید، به ازای هر شرطی که به برنامه اضافه میکنیم، پیچیدگی برنامه هم افزایش مییابد و کد خوانایی خود را از دست میدهد. تصور کنید دنیایی بدون NullReferenceException چه دنیایی زیبایی میبود؛ ولی متاسفانه این مورد از ویژگیهای زبان #C است. خوشبختانه راهحلهای برای حل NRE ارائه شدهاند که در ادامه به آنها میپردازیم.
ما میخواهیم متد GetById همیشه چیزی غیر از نال را برگرداند و یکی از راههایی که ما را به این هدف میرساند این است که این متد یک توالی را برگرداند.
public class UserService : IUserService { private IList<User> _userData; public UserService() { _userData = new List<User> { new User {Id = 1,Name = "ali"}, new User {Id = 2,Name = "Karim"} }; } public IEnumerable<User> GetById(int id) { var user = _userData.FirstOrDefault(x => x.Id == id); if (user == null) return new User[0]; return new[] { user }; } }
اگر به امضای متد GetById توجه کنید، به جای اینکه User را برگرداند، این متد یک توالی از User را بر میگرداند و اگر در اینجا کاربری یافت شد، این توالی دارای یک المان خواهد بود و در غیر این صورت اگر User یافت نشد، این متد یک توالی را بر میگرداند که دارای هیچ المانی نیست. در ادامه اگر کلاینت بخواهد از متد GetById استفاده کند، به صورت زیر خواهد بود:
public ActionResult Details(int id) { var user = _userService .GetById(3) .DefaultIfEmpty(new User()) .Single(); return View(user); }
متد GetById دارای دو وجه است و وجه مثبت آن این است که اگر مجموعه دارای مقداری باشد، هیچ مشکلی نیست؛ ولی اگر مجموعه دارای المانی نباشد، باید یک شیء را به صورت پیش فرض به آن اختصاص دهیم که این کار را با استفاده از متد DefualtIfEmpty انجام دادهایم.
در اول مقاله هم اشاره کردیم که Maybe یا Options، مجموعهای است که دارای یک المان و یا هیچ المانی است. اگر به امضای متد GetById توجه کنید، متوجه خواهید شد که این متد میتواند مجموعهای را برگرداند و نمیتواند گارانتی کند که حتما مجموعهای را بر میگرداند که دارای یک المان و یا هیچ باشد. برای حل این مشکل میتوانیم از کلاس Option استفاده کنیم:
public class Option<T> : IEnumerable<T> { private readonly T[] _data; private Option(T[] data) { _data = data; } public static Option<T> Create(T element) => new Option<T>(new[] { element }); public static Option<T> CreateEmpty() => new Option<T>(new T[0]); public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>) _data).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); }
تنها دلیل استفاده از متدهای Create و CreateEmpty این است که به خوانایی برنامه کمک کنیم؛ نه بیشتر. در ادامه اگر بخواهیم از کلاس option استفاده کنیم، به صورت زیر خواهد بود:
public class UserService : IUserService { ... ... public Option<User> GetById(int id) { var user = _userData.FirstOrDefault(x => x.Id == id); return user == null ? Option<User>.CreateEmpty() : Option<User>.Create(user); } } public class UserController : Controller { ... ... public ActionResult Details(int id) { var user = _userService .GetById(3) .DefaultIfEmpty(new User()) .Single(); return View(user); } }
چکیده:
مدیریت کردن References کار بسیار پیچیدهای است. قبل از آن که تلاش کنیم مقداری را برگردانیم و یا عملیاتی را بر روی آن انجام دهیم، اول باید مطمئن شویم که این شیء به جایی اشاره میکند. نمونههای متفاوتی از Option و یا Maybe را میتوانید در اینترنت پیدا کنید که هدف نهایی آنها، حذف NullReferenceException است و آشنایی با این ایده، شما را به دنیای برنامه نویسی تابعی در#C هدایت میکند.
public class ProductViewModel { public int ProductId { get; set; } public string Name { get; set; } public string Description { get; set; } public string ImageUrl { get; set; } public decimal UnitPrice { get; set; } }
public record ProductDTO(int Id, string Name, string Description);
public record ProductDTO { public int Id { get; init; } public string Name { get; init; } } var dto = new ProductDTO { Id = 1, Name = "some name" };
public class Product : DataObject<Product> { public Product(int id) { Id = id; InitializeFromDatabase(); } private void InitializeFromDatabase() { DataHelpers.LoadFromDatabase(this); } public int Id { get; private set; } // other properties and methods }
public class Product { public Product(int id) { Id = id; } private Product() { // required for EF } public int Id { get; private set; } // other properties and methods }
- برای اجرای وظایف خود به فریم ورک ثالثی وابسته نیست.
- به کلاس پایهای ( Base class) نیاز ندارد.
- وابستگی به متد استاتیکی ندارد.
- می تواند در هر جایی از پروژه، نمونه سازی شود.
- اصل Persistence Ignorant را بیشتر رعایت کرده، نه بطور کامل؛ چون یک سازنده دارد که به کتابخانهی ثالثی نیازمند است (سازندهی بدون پارامتر که مورد نیاز EF میباشد).
همانطور که در قسمت اول گفتم AutoMapper کارش رو بر اساس قراردادها انجام میده یا همون Convention Base. یکی از قردادهای AutoMapper، نگاشت براساس نام اعضای اون شی هست؛ مثلا در مثال قبلی FirstName در مبداء، به خاصیتی با همین نام نگاشت شد و ...
Projection
روش استفاده بعدی که به اون Projection (معنی فارسی خوب برا Projection چیه ؟) میگن برای مواقعی هست که اعضای یک شیء در مبداء، همتایی در مقصد ندارد (از نظر نام) و در واقع میخواهیم یک شیء رو به یک شیء دیگه تغییر شکل بدیم.
مدل زیر رو در نظر بگیرید:
public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
که قرار است به مدل PersonDTO نگاشت بشه.
public class PersonDTO { public int Id { get; set; } public string Name { get; set; } public string Family { get; set; } public string FullName { get; set; } public string Email { get; set; } }
Mapper.CreateMap<Person, PersonDTO>().ForMember(des => des.Name, op => op.MapFrom(src => src.FirstName)). ForMember(des => des.FullName, op => op.MapFrom(src => src.FirstName + " " + src.LastName)).ForMember( des => des.Email, op => op.Ignore());
AutoMapper.IMappingExpression<TSource,TDestination>.ForMember(System.Linq.Expressions.Expression<System.Func<TDestination,object>>, System.Action<AutoMapper.IMemberConfigurationExpression<TSource>>)
نکته:برای تست کردن اینکه آیا نگاشت ما Exception داره یا نه میتوان از متد زیر استفاده کرد.
Mapper.AssertConfigurationIsValid();
نکته: همیشه موقع نگاشت، اعضای شیء مقصد برای AutoMapper مهمن؛ مثلا در مثال بالا خاصیت LastName در مبداء به هیچ عضوی در مقصد نگاشت نشده (به صورت مستقیم) و این تولید Exception نمیکنه ولی برعکس اون باعث تولید Exception میشه؛ مثلا اگه خاصیت Email رو که در مبداء همتایی نداره رو کانفیگ نکنیم، باعث تولید Exception میشه.
نحوه استفاده
var person = new Person { Id = 1, FirstName = "Mohammad", LastName = "Saheb", }; var personDTO = Mapper.Map<Person, PersonDTO>(person);
Collection
بعد از نوشتن کانفیگ نگاشتها، بدون نیاز به تنظیمات خاصی میتونیم مجموعه ای از شیهای مبداء رو به مقصد نگاشت کنیم. مجموعههای پشتیبانی شده به شرح زیرن.
IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, IList, IList<T>, List<T>.
مثال
var persons = new[] { new Person { Id = 1, FirstName = "Mohammad", LastName = "Saheb" }, new Person { Id = 2, FirstName = "Babak", LastName = "Saheb" } }; var personDTOs = Mapper.Map<Person[], List<PersonDTO>>(persons);
Nested Mappings
برای نگاشت کلاسهای تو در تو از این روش استفاده میکنیم و ...
فرض کنید در کلاس Person خاصیتی از نوع Address داریم و در کلاس PersonDTO خاصیتی از نوع AddressDTO.
public class Address { public string Ad1 { get; set; } public string Ad2 { get; set; } } public class AddressDTO { public string Ad1 { get; set; } public string Ad2 { get; set; } }
Mapper.CreateMap<Address, AddressDTO>();
ادامه دارد...
درخواست راهنمایی بیشتر
این مطلب در ادامهی مطالب آزمونهای واحد یا unit testing است.
نوشتن آزمون واحد برای کلاسهایی که با یک سری از الگوریتمها ، مسایل ریاضی و امثال آن سر و کار دارند، ساده است. عموما این نوع کلاسها وابستگی خارجی آنچنانی ندارند؛ اما در عمل کلاسهای ما ممکن است وابستگیهای خارجی بسیاری پیدا کنند؛ برای مثال کار با دیتابیس، اتصال به یک وب سرویس، دریافت فایل از اینترنت، خواندن اطلاعات از انواع فایلها و غیره.
مطابق اصول آزمایشات واحد، یک آزمون واحد خوب باید ایزوله باشد. نباید به مرزهای سیستمهای دیگر وارد شده و عملکرد سیستمهای خارج از کلاس را بررسی کند.
این مثال ساده را در نظر بگیرید:
فرض کنید برنامه شما قرار است از یک وب سرویس لیستی از آدرسهای IP یک کشور خاص را دریافت کند و در یک دیتابیس محلی آنها را ذخیره نماید. به صورت متداول این کلاس باید اتصالی را به وب سرویس گشوده و اطلاعات را دریافت کند و همچنین آنها را خارج از مرز کلاس در یک دیتابیس ثبت کند. نوشتن آزمون واحد برای این کلاس مطابق اصول مربوطه غیر ممکن است. اگر کلاس آزمون واحد آنرا تهیه نمائید، این آزمون، integration test نام خواهد گرفت زیرا از مرزهای سیستم باید عبور نماید.
همچنین یک آزمون واحد باید تا حد ممکن سریع باشد تا برنامه نویس از انجام آن بر روی یک پروژه بزرگ منصرف نگردد و ایجاد این اتصالات در خارج از سیستم، بیشتر سبب کندی کار خواهند شد.
برای این ایزوله سازی روشهای مختلفی وجود دارند که در ادامه به آنها خواهیم پرداخت:
روش اول: استفاده از اینترفیسها
با کمک یک اینترفیس میتوان مشخص کرد که یک قطعه از کد "چه کاری" را قرار است انجام دهد؛ و نه اینکه "چگونه" باید آنرا به انجام رساند.
یک مثال ساده از خود دات نت فریم ورک، اینترفیس IComparable است:
public static string GetComparisonText(IComparable a, IComparable b)
{
if (a.CompareTo(b) == 1)
return "a is bigger";
if (a.CompareTo(b) == -1)
return "b is bigger";
return "same";
}
اکنون با توجه به این توضیحات، برای ایزوله کردن ارتباط با دیتابیس و وب سرویس در مثال فوق، میتوان اینترفیسهای زیر را تدارک دید:
public interface IEmailSource
{
IEnumerable<string> GetEmailAddresses();
}
public interface IEmailDataStore
{
void SaveEmailAddresses(IEnumerable<string> emailAddresses);
}
الف) به این صورت تنها مشخص میشود که چه کاری را قصد داریم انجام دهیم و کاری به پیاده سازی آن نداریم.
ب) ساخت کلاس بدون وجود یا دسترسی به یک دیتابیس میسر میشود. این مورد خصوصا در یک کار تیمی که قسمتهای مختلف کار به صورت همزمان در حالت پیشرفت و تهیه است حائز اهمیت میشود.
ج) با توجه به اینکه در اینجا به پیاده سازی توجهی نداریم، میتوان از این اینترفیسها جهت تقلید دنیای واقعی استفاده کنیم. (که در اینجا mocking نام گرفته است)
جهت تقلید رفتار و عملکرد این دو اینترفیس، به کلاسهای تقلید زیر خواهیم رسید:
public class MockEmailSource : IEmailSource
{
public IEnumerable<string> EmailAddressesToReturn { get; set; }
public IEnumerable<string> GetEmailAddresses()
{
return EmailAddressesToReturn;
}
}
public class MockEmailDataStore : IEmailDataStore
{
public IEnumerable<string> SavedEmailAddresses { get; set; }
public void SaveEmailAddresses(IEnumerable<string> emailAddresses)
{
SavedEmailAddresses = emailAddresses;
}
}
ادامه دارد ....
1- با استفاده از کلاس ApplicationUser، شیء ClaimsPrincipal را تولید میکند.
2- به این ClaimsPrincipal اطلاعاتی مانند ApplicationUser.Id و SecurityStamp اضافه میشوند.
3- در ادامه، ClaimsPrincipal به OWIN و کلاس CookieAuthenticationHandler آن ارسال میشود.
4- کار کلاس CookieAuthenticationHandler، تولید و تنظیم اطلاعاتی مانند تاریخ صدور کوکی، تاریخ انقضای آن، نوع کوکی، مانند ماندگار بودن یا امن بودن (HTTPS) و امثال آن است. حاصل این مراحله، تولید یک AuthenticationTicket است.
5- در آخر، AuthenticationTicket و ClaimsPrincipal به کلاس SecureDataFormat، برای ابتدا، serialize شدن اشیاء، رمزنگاری و در نهایت تبدیل آنها به فرمت base64، ارسال میشوند.
جزئیات تکمیلی مرحلهی آخر آن نیز به این ترتیب است:
AuthenticationTicket با استفاده از کلاس TicketSerializer سریالایز میشود. پس از آن یک memory stream تشکیل شده و اطلاعات ClaimsIdentity و AuthenticationTicket سریالایز شده به آن ارسال میشوند. این memory stream با استفاده از الگوریتم GZip فشرده شده و برای پردازش بیشتر بازگشت داده میشود. مرحلهی بعد، رمزنگاری اطلاعات فشرده سازی شدهاست. برای این منظور از کلاس DpapiDataProtector دات نت استفاده میکنند. پس از رمزنگاری، استریم نهایی با فرمت base64 برای درج در HTTP Response آماده خواهد شد.
سؤال: چرا کوکیهای یک کاربر معین لاگین شدهی توسط ASP.NET Identity، در مرورگرهای مختلف متفاوت است؟
هرچند اطلاعاتی مانند ApplicationUser.Id و SecurityStamp برای یک کاربر، در مرورگرهای مختلف یکسان هستند، اما در مرحلهی چهارم، ذکر شد که AuthenticationTicket دارای اطلاعات بیشتری مانند زمان تولید کوکی نیز هست. بنابراین اطلاعات نهایی رمزنگاری شدهی در این حالت که در زمانهای مختلفی تولید شدهاند، یکسان نخواهند بود.
سؤال: در ساب دومینهای مختلف دومین مشخصی، چندین برنامهی مختلف نصب شدهاند. چگونه میتوان از یک سیستم لاگین ASP.NET Identity برای تمام آنها استفاده کرد؟
برای این منظور نیاز هست خاصیت CookieDomain را به صورت صریح مقدار دهی کرد. برای اینکار فایل Startup.Auth.cs را گشوده و CookieAuthenticationOptions را تنظیم کنید:
var cookieAuthenticationOptions = new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), CookieDomain = ".mydomain.com" };
برای اینکار به کنسول IIS مراجعه کرده و گزینهی machine key آنرا بیابید. در این قسمت بر روی generate keys کلیک کرده و اطلاعات تولیدی را باید به تمام web.configهای موجود کپی کنید:
<machineKey validationKey="DAD9E2B0F9..." decryptionKey="ADD1C39C02..." validation="SHA1" decryption="AES" />
سؤال: برنامههای مختلفی بر روی یک دومین نصب هستند، اما قصد نداریم از سیستم اعتبارسنجی یکپارچهای برای تمام آنها استفاده کنیم. اما اگر در یکی لاگین کنیم، بلافاصله لاگین در برنامهی دوم منقضی میشود، چرا؟
شبیه به همین مساله با Forms Authentication هم وجود دارد. برای رفع آن باید نام کوکیهای هر برنامه را منحصربفرد کنید و از نام پیش فرض کوکیها استفاده نکنید تا بر روی یکدیگر بازنویسی نشوند. برای اینکار خاصیت CookieName شیء CookieAuthenticationOptions را جداگانه مقدار دهی کنید:
CookieName = "my-very-own-cookie-name"
سؤال: لاگین انجام شدهی در برنامهای که از ASP.NET Identity استفاده میکند، زود منقضی میشود؛ چرا؟
برای تنظیم صریح زمان انقضای کوکی ASP.NET Identity نیاز است خاصیت ExpireTimeSpan آنرا مقدار دهی کنید:
app.UseCookieAuthentication(new CookieAuthenticationOptions { ExpireTimeSpan = TimeSpan.FromHours(24.0), });
سؤال: کاربر سیستم ASP.NET Identity از سیستم خارج شدهاست (log off کرده) ولی هنوز میتوان از کوکی پیشین او برای اعتبارسنجی مجدد استفاده کرد. چطور میتوان این نقیصهی امنیتی را برطرف کرد؟
مشکل از اینجا است:
public ActionResult LogOff() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); }
[HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> LogOff() { var user = await UserManager.FindByNameAsync(User.Identity.Name); AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); await UserManager.UpdateSecurityStampAsync(user.Id); return RedirectToAction("Login", "Account"); }
همچنین بهتر است مقدار validateInterval مربوط به SecurityStampValidator.OnValidateIdentity که به صورت پیش فرض 30 دقیقه است را به مقدار کمتری مانند 5 دقیقه تغییر دهید (تنظیمات OnValidateIdentity مربوط به CookieAuthenticationOptions فایل آغارین برنامه). کار این تنظیم، بررسی اعتبار کوکی، در بازههای زمانی مشخص شدهاست.
<div class="alert"> نمایش اعلانات </div>
تعدادی کلاس دیگر نیز جهت استفاده از رنگهای مختلف نیز توسط بوت استرپ ارائه شده است:
همچنین اگر مایل بودید میتوانید با افزودن یک دکمه با کلاس close و ویژگی data-dismiss مساوی alert، امکان بستن پیام را در اختیار کاربر قرار دهید:
<div class="alert alert-warning alert-dismissable"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> نمایش اعلان </div>
در ادامه قصد داریم این پیام را بعد از ثبت اطلاعات، به کاربر نمایش دهیم. یعنی در داخل کد، امکان صدا زدن این نوع پیامها را داشته باشیم.
ابتدا کلاسهای زیر را تعریف میکنیم:
کلاس Alert
public class Alert { public const string TempDataKey = "TempDataAlerts"; public string AlertStyle { get; set; } public string Message { get; set; } public bool Dismissable { get; set; } }
در کلاس فوق خصوصیت یک alert را تعریف کردهایم (از خاصیت TempDataKey جهت پاس دادن alertها به view استفاده میکنیم).
public class AlertStyles { public const string Success = "success"; public const string Information = "info"; public const string Warning = "warning"; public const string Danger = "danger"; }
public class BaseController : Controller { public void Success(string message, bool dismissable = false) { AddAlert(AlertStyles.Success, message, dismissable); } public void Information(string message, bool dismissable = false) { AddAlert(AlertStyles.Information, message, dismissable); } public void Warning(string message, bool dismissable = false) { AddAlert(AlertStyles.Warning, message, dismissable); } public void Danger(string message, bool dismissable = false) { AddAlert(AlertStyles.Danger, message, dismissable); } private void AddAlert(string alertStyle, string message, bool dismissable) { var alerts = TempData.ContainsKey(Alert.TempDataKey) ? (List<Alert>)TempData[Alert.TempDataKey] : new List<Alert>(); alerts.Add(new Alert { AlertStyle = alertStyle, Message = message, Dismissable = dismissable }); TempData[Alert.TempDataKey] = alerts; } }
public ActionResult Index() { var userInfo = new { Name = "Sirwan", LastName = "Afifi", }; ViewData["User"] = userInfo; ViewBag.User = userInfo; TempData["User"] = userInfo; return RedirectToAction("About"); }
@{ ViewBag.Title = "About"; } <h1>Tempdata</h1><p>@TempData["User"]</p> <h1>ViewData</h1><p>@ViewData["User"]</p> <h1>ViewBag</h1><p>@ViewBag.User</p>
public class NewsController : BaseController { readonly INewsService _newsService; readonly IUnitOfWork _uow; public NewsController(INewsService newsService, IUnitOfWork uow) { _newsService = newsService; _uow = uow; } [HttpPost] [ValidateAntiForgeryToken] [ValidateInput(false)] public ActionResult Create(News news) { if (ModelState.IsValid) { _newsService.AddNews(news); _uow.SaveChanges(); Success(string.Format("خبر با عنوان <b>{0}</b> با موفقیت ذخیره گردید!", news.Title), true); return RedirectToAction("Index"); } Danger("خطا در هنگام ثبت اطلاعات "); return View(news); } [HttpPost] public ActionResult Delete(int id) { _newsService.DeleteNewsById(id); _uow.SaveChanges(); Danger("اطلاعات مورد نظر با موفقیت حذف گردید!", true); return RedirectToAction("Index"); } }
@{ var alerts = TempData.ContainsKey(Alert.TempDataKey) ? (List<Alert>)TempData[Alert.TempDataKey] : new List<Alert>(); if (alerts.Any()) { <hr /> } foreach (var alert in alerts) { var dismissableClass = alert.Dismissable ? "alert-dismissable" : null; <div class="alert alert-@alert.AlertStyle @dismissableClass"> @if (alert.Dismissable) { <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> } @Html.Raw(alert.Message) </div> } }
<div> @{ Html.RenderPartial("_Alerts"); } @RenderBody() </div>
بررسی قسمتهای مختلف قالب پروژه WPF Framework تهیه شده
new SendMsg().ShowMsg( new AlertConfirmBoxModel { ErrorTitle = "خطای اعتبار سنجی", Errors = errors, }, validationException);
public class DomainResult { public bool Succeed { get; set; } public IEnumerable<Exception> Errors { get; set; } public DomainErrorType ErrorType { get; set; } } public enum DomainErrorType { Validation, Concurrency, Update }
public DomainResult ApplyAllChanges(string userName, bool updateAuditFields = true) ...
dotnet add package Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
public class MyKeysContext : DbContext, IDataProtectionKeyContext { // A recommended constructor overload when using EF Core // with dependency injection. public MyKeysContext(DbContextOptions<MyKeysContext> options) : base(options) { } // This maps to the table that stores keys. public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } }
public int Id { get; set; } public string FriendlyName { get; set; } public string XmlData { get; set; }
public void ConfigureServices(IServiceCollection services) { // using Microsoft.AspNetCore.DataProtection; services.AddDataProtection() .PersistKeysToDbContext<MyKeysContext>(); }