using System; using System.ComponentModel.DataAnnotations; namespace Test { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class CompareAttribute : ValidationAttribute { public CompareAttribute(string originalProperty, string confirmProperty) { OriginalProperty = originalProperty; ConfirmProperty = confirmProperty; } public string ConfirmProperty { get; private set; } public string OriginalProperty { get; private set; } protected override ValidationResult IsValid(object value, ValidationContext ctx) { if (value == null) return new ValidationResult("لطفا فیلدها را تکمیل نمائید"); var confirmProperty = ctx.ObjectType.GetProperty(ConfirmProperty); if (confirmProperty == null) throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", ConfirmProperty)); var confirmValue = confirmProperty.GetValue(ctx.ObjectInstance, null) as string; if (string.IsNullOrWhiteSpace(confirmValue)) return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", ConfirmProperty)); var originalProperty = ctx.ObjectType.GetProperty(OriginalProperty); if (originalProperty == null) throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", OriginalProperty)); var originalValue = originalProperty.GetValue(ctx.ObjectInstance, null) as string; if (string.IsNullOrWhiteSpace(originalValue)) return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", OriginalProperty)); return originalValue == confirmValue ? ValidationResult.Success : new ValidationResult("مقادیر وارد شده یکسان نیستند"); } } }
"در کتاب ASP.NET Core: Cloud-ready, Enterprise Web Application Development ما از دو فریمورک مطرح استفاده میکنیم. از ASP.NET Core برای پوشش مفاهیم سمت سرور و از Angular 2 برای مباحث سمت کلاینت نه فقط به خاطر قابلیتهای فوق العادشان و طراحی بی نقصشان، بلکه هر دوی آنها بازنویسی کاملی از نسخههای پیشین بسیار محبوبشان بودند که نقش رهبری در زمینهی خودشان را بر عهده داشتند. "
هدف از این مطلب، ارائه راه حلی برای تولید خودکار کد یا شماره یکتا و ترتیبی در زمان ثبت رکورد جدید به صورت یکپارچه با EF Core، میباشد. به عنوان مثال فرض کنید در زمان ثبت سفارش، نیاز است بر اساس یکسری تنظیمات، یک شماره منحصر به فرد برای آن سفارش، تولید شده و در فیلدی تحت عنوان Number قرار گیرد؛ یا به صورت کلی برای موجودیتهایی که نیاز به یک نوع شماره گذاری منحصر به فرد دارند، مانند: سفارش، طرف حساب و ...
یک مثال واقعی
در زمان ثبت یک Task، کاربر میتواند به صورت دستی یک شماره منحصر به فرد را نیز وارد کند؛ در غیر این صورت سیستم به طور خودکار شمارهای را به رکورد در حال ثبت اختصاص خواهد داد. بررسی یکتایی این کد در صورت وارد کردن به صورت دستی، توسط اعتبارسنج مرتبط باید انجام گیرد؛ ولی در غیر این صورت، زیرساخت مورد نظر تضمین میکند که شماره یکتایی را ایجاد کند.
public interface INumberedEntity { string Number { get; set; } }
foreach (var entityType in builder.Model.GetEntityTypes() .Where(e => typeof(INumberedEntity).IsAssignableFrom(e.ClrType))) { builder.Entity(entityType.ClrType) .Property(nameof(INumberedEntity.Number)).IsRequired().HasMaxLength(50); if (typeof(IMultiTenantEntity).IsAssignableFrom(entityType.ClrType)) { builder.Entity(entityType.ClrType) .HasIndex(nameof(INumberedEntity.Number), nameof(IMultiTenantEntity.TenantId)) .HasName( $"UIX_{entityType.ClrType.Name}_{nameof(IMultiTenantEntity.TenantId)}_{nameof(INumberedEntity.Number)}") .IsUnique(); } else { builder.Entity(entityType.ClrType) .HasIndex(nameof(INumberedEntity.Number)) .HasName($"UIX_{entityType.ClrType.Name}_{nameof(INumberedEntity.Number)}") .IsUnique(); } }
public class NumberedEntity : Entity, IMultiTenantEntity { public string EntityName { get; set; } public long NextNumber { get; set; } public long TenantId { get; set; } }
public class NumberedEntityConfiguration : IEntityTypeConfiguration<NumberedEntity> { public void Configure(EntityTypeBuilder<NumberedEntity> builder) { builder.Property(a => a.EntityName).HasMaxLength(256).IsRequired().IsUnicode(false); builder.HasIndex(a => a.EntityName).HasName("UIX_NumberedEntity_EntityName").IsUnique(); builder.ToTable(nameof(NumberedEntity)); } }
شاید به نظر، استفاده از این موجودیت ضروریتی نداشته باشد و خیلی راحت میتوان آخرین شماره ثبت شدهی در جدول مورد نظر را واکشی، مقداری را به آن اضافه و به عنوان شماره منحصر به فرد رکورد جدید استفاده کرد؛ با این رویکرد حداقل دو مشکل زیر را خواهیم داشت:
- ایجاد Gap مابین شمارههای تولید شده، که مدنظر ما نمیباشد. (با توجه به اینکه امکان ثبت دستی را هم داریم، ممکن است کاربر شمارهای را وارد کرده باشد که با آخرین شماره ثبت شده تعداد زیادی فاصله دارد که به خودی خود مشکل ساز نیست؛ ولی در زمان ثبت رکورد بعدی اگر به صورت خودکار ثبت شماره داشته باشد، قطعا آخرین شماره (بزرگترین) را که به صورت دستی وارد شده بود، از جدول دریافت خواهد کرد)
پیاده سازی یک PreInsertHook برای مقداردهی پراپرتی Number
internal class NumberingPreInsertHook : PreInsertHook<INumberedEntity> { private readonly IUnitOfWork _uow; private readonly IOptions<NumberingConfiguration> _configuration; public NumberingPreInsertHook(IUnitOfWork uow, IOptions<NumberingConfiguration> configuration) { _uow = uow ?? throw new ArgumentNullException(nameof(uow)); _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); } protected override void Hook(INumberedEntity entity, HookEntityMetadata metadata) { if (!entity.Number.IsNullOrEmpty()) return; bool retry; string nextNumber; do { nextNumber = GenerateNumber(entity); var exists = CheckDuplicateNumber(entity, nextNumber); retry = exists; } while (retry); entity.Number = nextNumber; } private bool CheckDuplicateNumber(INumberedEntity entity, string nextNumber) { //... } private string GenerateNumber(INumberedEntity entity) { //... } }
ابتدا بررسی میشود اگر پراپرتی Number مقداردهی شدهاست، عملیات مقداردهی خودکار برروی آن انجام نگیرد. سپس با توجه به اینکه ممکن است به صورت دستی قبلا شمارهای مانند Task_1000 وارد شده باشد و NextNumber مرتبط هم مقدار 1000 را داشته باشد؛ در این صورت به هنگام ثبت رکورد بعدی، با توجه به Prefix تنظیم شده، دوباره به شماره Task_1000 خواهیم رسید که در این مورد خاص با استفاده از متد CheckDuplicateNumber این قضیه تشخیص داده شده و سعی مجددی برای تولید شماره جدید صورت میگیرد.
بررسی متد GenerateNumber
private string GenerateNumber(INumberedEntity entity) { var option = _configuration.Value.NumberedEntityOptions[entity.GetType()]; var entityName = $"{entity.GetType().FullName}"; var lockKey = $"Tenant_{_uow.TenantId}_" + entityName; _uow.ObtainApplicationLevelDatabaseLock(lockKey); var nextNumber = option.Start.ToString(); var numberedEntity = _uow.Set<NumberedEntity>().AsNoTracking().FirstOrDefault(a => a.EntityName == entityName); if (numberedEntity == null) { _uow.ExecuteSqlCommand( "INSERT INTO [dbo].[NumberedEntity]([EntityName], [NextNumber], [TenantId]) VALUES(@p0,@p1,@p2)", entityName, option.Start + option.IncrementBy, _uow.TenantId); } else { nextNumber = numberedEntity.NextNumber.ToString(); _uow.ExecuteSqlCommand("UPDATE [dbo].[NumberedEntity] SET [NextNumber] = @p0 WHERE [Id] = @p1 ", numberedEntity.NextNumber + option.IncrementBy, numberedEntity.Id); } if (!string.IsNullOrEmpty(option.Prefix)) nextNumber = option.Prefix + nextNumber; return nextNumber; }
ابتدا با استفاده از متد الحاقی ObtainApplicationLevelDatabaseLock یک قفل منطقی را برروی یک منبع مجازی (lockKey) در سطح نرم افزار از طریق sp_getapplock ایجاد میکنیم. به این ترتیب بدون نیاز به درگیر شدن با مباحث isolation level بین تراکنشهای همزمان یا سایر مباحث locking در سطح row یا table، به نتیجه مطلوب رسیده و تراکنش دوم که خواهان ثبت Task جدید میباشد، با توجه به اینکه INumberedEntity میباشد، لازم است پشت این global lock صبر کند و بعد از commit یا rollback شدن تراکنش جاری، به صورت خودکار قفل منبع مورد نظر باز خواهد شد.
پیاده سازی متد مذکور به شکل زیر میباشد:
public static void ObtainApplicationLevelDatabaseLock(this IUnitOfWork uow, string resource) { uow.ExecuteSqlCommand(@"EXEC sp_getapplock @Resource={0}, @LockOwner={1}, @LockMode={2} , @LockTimeout={3};", resource, "Transaction", "Exclusive", 15000); }
با توجه به اینکه ممکن است درون تراکنش جاری چندین نمونه از موجودیتهای INumberedEntity در حال ذخیره سازی باشند و از طرفی Hook ایجاد شده به ازای تک تک نمونهها قرار است اجرا شود، ممکن است تصور این باشد که اجرای مجدد sp مذکور مشکل ساز شود و در واقع به Lock خود برخواهد خورد؛ ولی از آنجایی که پارامتر LockOwner با "Transaction" مقداردهی میشود، لذا فراخوانی مجدد این sp درون تراکنش جاری مشکل ساز نخواهد بود.
گام بعدی، واکشی NextNumber مرتبط با موجودیت جاری میباشد؛ اگر در حال ثبت اولین رکورد هستیم، لذا numberedEntity مورد نظر مقدار null را خواهد داشت و لازم است شماره بعدی را برای موجودیت جاری ثبت کنیم. در غیر این صورت عملیات ویرایش با اضافه کردن IncrementBy به مقدار فعلی انجام میگیرد. در نهایت اگر Prefix ای تنظیم شده باشد نیز به ابتدای شماره تولیدی اضافه شده و بازگشت داده خواهد شد.
ساختار NumberingConfiguration
public class NumberingConfiguration { public bool Enabled { get; set; } public IDictionary<Type, NumberedEntityOption> NumberedEntityOptions { get; } = new Dictionary<Type, NumberedEntityOption>(); }
public class NumberedEntityOption { public string Prefix { get; set; } public int Start { get; set; } = 1; public int IncrementBy { get; set; } = 1; }
با استفاده از دوکلاس بالا، امکان تنظیم الگوی تولید برای موجودیتها را خواهیم داشت.
گام آخر: ثبت PreInsertHook توسعه داده شده و همچنین تنظیمات مرتبط با الگوی تولید شماره موجودیتها
public static void AddNumbering(this IServiceCollection services, IDictionary<Type, NumberedEntityOption> options) { services.Configure<NumberingConfiguration>(configuration => { configuration.Enabled = true; configuration.NumberedEntityOptions.AddRange(options); }); services.AddTransient<IPreActionHook, NumberingPreInsertHook>(); }
و استفاده از این متد الحاقی در Startup پروژه
services.AddNumbering(new Dictionary<Type, NumberedEntityOption> { [typeof(Task)] = new NumberedEntityOption { Prefix = "T_", Start = 1000, IncrementBy = 5 } });
و موجودیت Task
public class Task : TrackableEntity, IAggregateRoot, INumberedEntity { public const int MaxTitleLength = 256; public const int MaxDescriptionLength = 1024; public string Title { get; set; } public string NormalizedTitle { get; set; } public string Description { get; set; } public TaskState State { get; set; } = TaskState.Todo; public byte[] RowVersion { get; set; } public string Number { get; set; } }
با خروجیهای زیر
inject$ در AngularJs
bookCtrl.$inject = ['$scope','bookService'];
app.controller('bookCtrl', ['$scope', 'bookService', function (sc, bs) { sc.books = bs; }])
عدم سازگاری با EF
لایه سرویس همه این کارها رو انجام میده
شیی Order به متد فوق ارسال میشه سپس OrderProductVarients که به عنوان یک Custome property تعریف شده پشت صحنه یک linq query اجرا میکنه و اگر لازم باشه مکانیزم کش هم انجام میشه توی تصویر هم این شی یک لیست از شی OrderProductVarient نه چیز دیگه ای و یک رکورد داره.
من متوجه نمیشم دیتا سورس من یک Ilist با یک رکورد این چه تناقضی با Lazy loading توکار لایه بیزنس داره
جالب اینجاست با تعریف یک کلاس مدل مشکل حل میشه!
public class OrderProductVarientModel { public int OrderProductVarientId { get; set; } }
حالا ازین کد استفاده کردم
.MainTableDataSource(dataSource => { var listOfRows = new List<OrderProductVarientModel>(); for (int i = 0; i < orderProductVariants.Count; i++) { listOfRows.Add(new OrderProductVarientModel { OrderProductVarientId = orderProductVariants[i].OrderProductVariantId }); } dataSource.StronglyTypedList(listOfRows); //dataSource.StronglyTypedList(orderProductVariants); })
به نظر من یک جایی از متد StronglyTypedList داره همه propertyهای شی جنریک مپ میکنه!
در نهایت میخواهیم نگاشتها را اینچنین تنظیم کنیم:
[MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)] public class AdminStudentViewModel { // [IgnoreMap] public int Id { set; get; } [MapForMember("Name")] public string FirstName { set; get; } [MapForMember("Family")] public string LastName { set; get; } public string Email { set; get; } [MapForMember("RegisterDateTime")] public string RegisterDateTimePersian { set; get; } [UseValueResolver(typeof (BookCountValueResolver))] public int BookCounts { set; get; } [UseValueResolver(typeof (BookPriceValueResolver))] public decimal BookPrice { set; get; } };
به تعریف و توضیح صفتهای (ویژگیها یا Attributes) مورد نیاز میپردازم:
صفت MapFromAttribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class MapFromAttribute : Attribute { public Type SourceType { get; private set; } public bool IgnoreAllNonExistingProperty { get; private set; } public bool AlsoCopyMetadata { get; private set; } //Go to: https://www.dntips.ir/courses/topic/16/cb36bc2e-4263-431e-86a5-236322cb5576 public MapFromAttribute(Type sourceType, bool ignoreAllNonExistingProperty = false, bool alsoCopyMetadata = false) { SourceType = sourceType; IgnoreAllNonExistingProperty = ignoreAllNonExistingProperty; AlsoCopyMetadata = alsoCopyMetadata; } };
صفت IgnoreMapAttribute
[AttributeUsage(AttributeTargets.Property)] public class IgnoreMapAttribute : Attribute {};
صفت MapForMemberAttribute
[AttributeUsage(AttributeTargets.Property)] public class MapForMemberAttribute : Attribute { public string MemberToMap { get; private set; } public MapForMemberAttribute(string memberToMap) { MemberToMap = memberToMap; } };
صفت UseValueResolverAttribute
[AttributeUsage(AttributeTargets.Property)] public class UseValueResolverAttribute : Attribute { public IValueResolver ValueResolver { get; private set; } public UseValueResolverAttribute(Type valueResolver) { ValueResolver = valueResolver.GetConstructors()[0].Invoke(new object[] {}) as IValueResolver; } };
تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفتها را به نگاشت متناسبی در automapper تبدیل کنیم.
دریافت کدها
ادامه دارد...
اجرای دات نت در مروگر توسط Ooui
Earlier this year, Microsoft announced their support for Blazor, and now Frank A. Krueger has developed the Ooui library which allows C# or F# to be used to write applications that run in the browser. Ooui can target WASM, enabling Xamarin.Forms app to be deployed in web assembly and the result runs entirely in-browser without the need for an application server.
صورت مساله
میخواهیم اطلاعات نمایش داده شده در گرید را به نحوی فرمت کنیم که
الف) اگر id ردیف مساوی 5 بود، رنگ و پس زمینهی آن تغییر کند.
ب) نام محصول، به جزئیات آن لینک شود و این اطلاعات توسط یک jQuery UI Dialog نمایش داده شود.
ج) عدد قیمت با سه رقم جدا کننده همراه باشد.
تکمیل مدل برنامه
مدل قسمت اول صرفا یک محصول بود. مدل قسمت جاری، اطلاعات تولید/تامین کننده آنرا توسط کلاس Supplier نیز به همراه دارد:
namespace jqGrid02.Models { public class Product { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public Supplier Supplier { set; get; } } public class Supplier { public int Id { set; get; } public string CompanyName { set; get; } public string Address { set; get; } public string PostalCode { set; get; } public string City { set; get; } public string Country { set; get; } public string Phone { set; get; } public string HomePage { set; get; } } }
کدهای سمت سرور
کدهای سمت سرور مانند متد GetProducts به همراه صفحه بندی و مرتب سازی پویای آن دقیقا مانند قسمت قبل است.
در اینجا فقط یک اکشن متد جدید جهت بازگشت اطلاعات تولید کنندهای مشخص با فرمت JSON، اضافه شدهاست:
public ActionResult GetGetSupplierData(int id) { var list = ProductDataSource.LatestProducts; var product = list.FirstOrDefault(x => x.Id == id); if (product == null) return Json(null, JsonRequestBehavior.AllowGet); return Json(new { product.Supplier.CompanyName, product.Supplier.Address, product.Supplier.PostalCode, product.Supplier.City, product.Supplier.Country, product.Supplier.Phone, product.Supplier.HomePage }, JsonRequestBehavior.AllowGet); }
کدهای سمت کلاینت
صفحه دیالوگی که قرار است اطلاعات تولید کننده را نمایش دهد، یک چنین ساختاری دارد:
<div dir="rtl" id="supplierDialog"> <span id="CompanyName"></span><br /><br /> <span id="Address"></span><br /> <span id="PostalCode"></span>, <span id="City"></span><br /> <span id="Country"></span><br /><br /> <span id="Phone"></span><br /> <span id="HomePage"></span> </div>
<script type="text/javascript"> function showSupplierDialog(linkElement, supplierId) { //request json data $.getJSON('@Url.Action("GetGetSupplierData","Home")', { id: supplierId }, function (data) { //set values in dialog for (var property in data) { if (data.hasOwnProperty(property)) { $('#' + property).text(data[property]); } } //get link position var linkPosition = $(linkElement).offset(); $('#supplierDialog').dialog('option', 'position', [linkPosition.left, linkPosition.top]); //open dialog $('#supplierDialog').dialog('open'); }); } $(document).ready(function () { $('#supplierDialog').dialog({ autoOpen: false, bgiframe: true, resizable: false, title: 'تولید کننده' }); $('#list').jqGrid({ // .... مانند قبل colNames: ['شماره', 'نام محصول', 'قیمت'], //columns model colModel: [ { name: 'Id', index: 'Id', align: 'right', width: 20, formatter: function (cellvalue, options, rowObject) { var cellValueInt = parseInt(cellvalue); if (cellValueInt == 5) { return "<span style='background: brown; color: yellow'>" + cellvalue + "</span>"; } return cellvalue; } }, { name: 'Name', index: 'Name', align: 'right', width: 300, formatter: function (cellvalue, options, rowObject) { return "<a href='#' onclick='showSupplierDialog(this, " + rowObject[0] + ");'>" + cellvalue + "</a>"; } }, { name: 'Price', index: 'Price', align: 'center', width: 50, formatter: 'currency', formatoptions: { decimalSeparator: '.', thousandsSeparator: ',', decimalPlaces: 2, prefix: '$' } } ], // .... مانند قبل }); }); </script>
در حالت ستون Id، از یک formatter سفارشی استفاده شدهاست. در اینجا این فرمت کننده به صورت یک callback عمل کرده و پیش از رندر نهایی اطلاعات، مقدار سلول جاری را توسط cellvalue در اختیار ما قرار میدهد. در این بین هر نوع فرمتی را که نیاز است میتوان اعمال کرد و سپس یک رشته را بازگشت میدهیم. این رشته در سلول جاری درج خواهد شد.
- اگر مانند ستون Name، نیاز به مقادیر سایر سلولها نیز وجود داشت، میتوان از آرایهی rowObject استفاده کرد. برای مثال در این حالت، یک لینک که کلیک بر روی آن سبب فراخوانی تابع showSupplierDialog میشود، در سلولهای ستون Name درج خواهند شد. اولین rowObject که در اینجا مورد استفاده است، به ستون اول یا همان Id محصول اشاره میکند.
- در ستون Price از یک سری formatter از پیش تعریف شده استفاده شدهاست. نمونهای از آن را در قسمت اول در ستون نمایش وضعیت موجود بودن محصول با تنظیم formatter: checkbox مشاهده کردهاید. در اینجا از یک formatter توکار دیگر به نام currency برای کار با مقادیر پولی استفاده شدهاست به همراه تنظیمات خاص آن.
- متد showSupplierDialog طوری تنظیم شدهاست که پس از دریافت Id یک محصول، آنرا به سرور ارسال کرده و مشخصات تولید کنندهی آنرا با فرمت JSON دریافت میکند. سپس در حلقهای که مشاهده میکنید، خواص شیء جاوا اسکریپتی دریافتی استخراج و به spanهای supplierDialog انتساب داده میشوند. جهت سهولت کار، Id این spanها دقیقا مساوی Id خواص شیء دریافتی از سرور، درنظر گرفته شدهاند.
- در مورد راست به چپ نمایش داده شدن عنوان دیالوگ، تغییرات CSS ایی لازم است که در قسمت اول بیان شدند.
برای مطالعه بیشتر
لیست کامل فرمت کنندههای توکار
فرمت کنندههای سفارشی
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid02.zip
Asp.Net Identity #3
using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity.Owin; using Users.Infrastructure; namespace Users.Controllers { public class HomeController : Controller { private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); } } // GET: Home public ActionResult Index() { return View(UserManager.Users); } }
@using Users.Models @model IEnumerable<AppUser> @{ ViewBag.Title = "Index"; } <div class="panel panel-primary"> <div class="panel-heading"> User Accounts </div> <table class="table table-striped"> <tr><th>ID</th><th>Name</th><th>Email</th></tr> @if (!Model.Any()) { <tr><td colspan="3" class="text-center">No User Accounts</td></tr> } else { foreach (AppUser user in Model) { <tr> <td>@user.Id</td> <td>@user.UserName</td> <td>@user.Email</td> </tr> } } </table> </div> @Html.ActionLink("Create", "CreateUser", null, new { @class = "btn btn-primary" })
نحوهی ساخت یک کاربر جدید
namespace Users.Models { public class CreateModel { [Required] public string Name { get; set; } [Required] public string Email { get; set; } [Required] public string Password { get; set; } } }
public ActionResult CreateUser() { return View(); } [HttpPost] public async Task<ActionResult> CreateUser(CreateModel model) { if (!ModelState.IsValid) return View(model); var user = new AppUser { UserName = model.Name, Email = model.Email }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { return RedirectToAction("Index"); } foreach (var error in result.Errors) { ModelState.AddModelError("", error); } return View(model); }
@model Users.ViewModels.CreateModel @Html.ValidationSummary(false) @using (Html.BeginForm()) { <div class="form-group"> <label>Name</label> @Html.TextBoxFor(x => x.UserName, new { @class = "form-control" }) </div> <div class="form-group"> <label>Email</label> @Html.TextBoxFor(x => x.Email, new { @class = "form-control" }) </div> <div class="form-group"> <label>Password</label> @Html.PasswordFor(x => x.Password, new { @class = "form-control" }) </div> <button type="submit" class="btn btn-primary">Create</button> @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default" }) }
اعتبار سنجی رمز
var manager = new AppUserManager(new UserStore<AppUser>(db)) { PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true } };
فقط دوستان توجه داشته باشید که کد بالا را در متد Create از کلاس AppUserManager استفاده کنید.
اعتبار سنجی نام کاربری
برای اعبارسنجی نام کاربری از کلاس UserValidator به صورت زیر استفاده میکنیم:
manager.UserValidator = new UserValidator<AppUser>(manager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true };
کد بالا را نیز در متد Create از کلاس AppUserManager قرار میدهیم.