protected override void OnModelCreating(DbModelBuilder builder) { base.OnModelCreating(builder);
- بررسی نحوه تعریف نگاشت جداول خود ارجاع دهنده (Self Referencing Entity)
- مباحث تکمیلی مدلهای خود ارجاع دهنده در EF Code first
- آشنایی با SQL Server Common Table Expressions - CTE
- بدست آوردن برگهای یک درخت توسط Recursive CTE
در پیشنیازهای بحث، روش تعریف روابط خود ارجاع دهنده و یا Self Referencing را تا EF 6.x میتوانید مطالعه کنید. در این قسمت قصد داریم معادل این روشها را در EF Core بررسی کنیم.
روش تعریف روابط خود ارجاع دهنده توسط Fluent API در EF Core
اگر همان مدل کامنتهای چندسطحی یک بلاگ را درنظر بگیریم:
public class BlogComment { public int Id { get; set; } public string Body { get; set; } public DateTime Date1 { get; set; } public virtual BlogComment Reply { get; set; } public int? ReplyId { get; set; } public virtual ICollection<BlogComment> Children { get; set; } }
public class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BlogComment>(entity => { entity.HasIndex(e => e.ReplyId); entity.HasOne(d => d.Reply) .WithMany(p => p.Children) .HasForeignKey(d => d.ReplyId); }); } public virtual DbSet<BlogComment> BlogComments { get; set; } }
نکتههای مهم مدلسازی این نوع روابط، موارد ذیل هستند:
الف) وجود خاصیتی از جنس کلاس اصلی در همان کلاس
public virtual BlogComment Reply { get; set; }
public int? ReplyId { get; set; }
علت نال پذیر بودن این خاصیت، نیاز به ثبت ریشهای است که خودش والد است و فرزند رکوردی پیشین، نیست.
ج) همچنین برای سهولت دریافت فرزندان یک ریشه، مجموعهای از جنس همان کلاس نیز تشکیل میشود.
public virtual ICollection<BlogComment> Children { get; set; }
روش تعریف روابط خود ارجاع دهنده توسط Data Annotations در EF Core
در ادامه معادل تنظیمات فوق را توسط ویژگیها ملاحظه میکنید که در اینجا توسط ویژگیهای InverseProperty و ForeignKey، کار تشکیل روابط صورت گرفتهاست:
public class BlogComment { public int Id { get; set; } public string Body { get; set; } public DateTime Date1 { get; set; } [ForeignKey("ReplyId")] [InverseProperty("Children")] public virtual BlogComment Reply { get; set; } public int? ReplyId { get; set; } [InverseProperty("Reply")] public virtual ICollection<BlogComment> Children { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BlogComment>(entity => { entity.HasIndex(e => e.ReplyId); }); }
ثبت اطلاعات و کوئری گرفتن از روابط خود ارجاع دهنده در EF Core
در اینجا نحوهی ثبت دو سری نظر و زیر نظر را مشاهده میکنید:
var comment1 = new BlogComment { Body = "نظر من این است که" }; var comment12 = new BlogComment { Body = "پاسخی به نظر اول", Reply = comment1 }; var comment121 = new BlogComment { Body = "پاسخی به پاسخ به نظر اول", Reply = comment12 }; context.BlogComments.Add(comment121); var comment2 = new BlogComment { Body = "نظر من این بود که" }; var comment22 = new BlogComment { Body = "پاسخی به نظر قبلی", Reply = comment2 }; var comment221 = new BlogComment { Body = "پاسخی به پاسخ به نظر من اول", Reply = comment22 }; context.BlogComments.Add(comment221); context.SaveChanges();
نظرات اصلی، با ReplyId مساوی نال قابل تشخیص هستند. سایر نظرات، توسط همین ReplyId به یکی از Idهای موجود، متصل شدهاند.
در تصویر فوق و با توجه به اطلاعات ثبت شده، فرض کنید میخواهیم ریشهی با id مساوی 1 و تمام زیر ریشههای آنرا بیابیم. انجام یک چنین کاری نه در EF Core و نه در EF 6.x، پشتیبانی نمیشود. بدیهی است در اینجا هنوز روشهای Include و ThenInclue هم جواب میدهند؛ اما چون Lazy loading فعال نیست، عملا نمیتوان تمام زیر ریشهها را یافت و همچنین به چندین و چند رفت و برگشت به ازای هر زیر ریشه خواهیم رسید که اصلا بهینه نیست.
برای اینکار نیاز است مستقیما کوئری نویسی کرد که در مطلب «شروع به کار با EF Core 1.0 - قسمت 10 - استفاده از امکانات بومی بانکهای اطلاعاتی» زیر ساخت آنرا بررسی کردیم:
var id = 1; var childIds = new List<int>(); var connection = context.Database.GetDbConnection(); connection.Open(); var command = connection.CreateCommand(); command.CommandText = @"WITH Hierachy(ChildId, ParentId) AS ( SELECT Id, ReplyId FROM BlogComments UNION ALL SELECT h.ChildId, bc.ReplyId FROM BlogComments bc INNER JOIN Hierachy h ON bc.Id = h.ParentId ) SELECT h.ChildId FROM Hierachy h WHERE h.ParentId = @Id"; command.Parameters.Add(new SqlParameter("id", id)); var reader = command.ExecuteReader(); while (reader.Read()) { var childId = (int)reader[0]; childIds.Add(childId); } reader.Dispose(); var list = context.BlogComments .Where(blogComment => blogComment.Id == id || childIds.Contains(blogComment.Id)) .ToList();
exec sp_executesql N'WITH Hierachy(ChildId, ParentId) AS ( SELECT Id, ReplyId FROM BlogComments UNION ALL SELECT h.ChildId, bc.ReplyId FROM BlogComments bc INNER JOIN Hierachy h ON bc.Id = h.ParentId ) SELECT h.ChildId FROM Hierachy h WHERE h.ParentId = @Id',N'@id int',@id=1
و پس از آن:
exec sp_executesql N'SELECT [blogComment].[Id], [blogComment].[Body], [blogComment].[Date1], [blogComment].[ReplyId] FROM [BlogComments] AS [blogComment] WHERE [blogComment].[Id] IN (@__id_0, 3, 5)',N'@__id_0 int',@__id_0=1
یافتن Idهای زیر ریشهها توسط روش CTE (پیشنیازهای ابتدای بحث) و سپس کوئری گرفتن بر روی این Idها، بهینهترین روشیاست که در EF میتوان جهت کار با مدلهای خود ارجاع دهنده بکار گرفت.
یک نکتهی تکمیلی: روش طراحی binding دو طرفه در Blazor SSR
در نکتهی قبل عنوان شد که مقدمات طراحی binding دو طرفه، داشتن حداقل سه خاصیت زیر در یک کامپوننت سفارشی است:
[Parameter] public T? Value { set; get; } [Parameter] public EventCallback<T?> ValueChanged { get; set; } [Parameter] public Expression<Func<T?>> ValueExpression { get; set; } = default!;
اگر این خواص را به کامپوننتهای توکار خود Blazor متصل کنیم (مانند InputBox آن و مابقی آنها)، نیازی به کدنویسی بیشتری ندارند و کار میکنند. اما اگر قرار است از یک input سادهی Html ای استفاده کنیم، نیاز است ValueChanged آنرا اینبار در متد OnInitialized فراخوانی کنیم؛ چون در زمان post-back به سرور است که مقدار آن در اختیار مصرف کنندهی کامپوننت قرار میگیرد. این مورد، مهمترین تفاوت نحوهی طراحی binding دوطرفه در Blazor SSR با مابقی حالات و نگارشهای Blazor است.
بررسی وقوع post-back به سرور به دو روش زیر میسر است:
الف) بررسی کنیم که آیا HttpPost ای رخدادهاست؟ سپس در همین لحظه، متد ValueChanged.InvokeAsync را فراخوانی کنیم:
[CascadingParameter] internal HttpContext HttpContext { set; get; } = null!; protected override void OnInitialized() { base.OnInitialized(); if (HttpContext.IsPostRequest()) { SetValueCallback(Value); } } private void SetValueCallback(string value) { if (!ValueChanged.HasDelegate) { return; } _ = ValueChanged.InvokeAsync(value); }
در این مثال نحوهی فعالسازی ارسال اطلاعات از یک کامپوننت سفارشی را به مصرف کنندهی آن ملاحظه میکنید. اینکار در قسمت OnInitialized و فقط در صورت ارسال اطلاعات به سمت سرور، فعال خواهد شد.
ب) میتوان در قسمت OnInitialized، بررسی کرد که آیا درخواست جاری به همراه اطلاعات یک فرم ارسال شدهی به سمت سرور است یا خیر؟ روش کار به صورت زیر است:
protected override void OnInitialized() { base.OnInitialized(); if (HttpContext.Request.HasFormContentType && HttpContext.Request.Form.TryGetValue(ValueField.HtmlFieldName, out var data)) { SetValueCallback(data.ToString()); } }
در اینجا از ValueField.HtmlFieldName که در نکتهی قبلی معرفی BlazorHtmlField به آن اشاره شد، جهت یافتن نام واقعی فیلد ورودی، استفاده شدهاست.
پشتیبانی از Full Text Search در SQL Server
Full Text Search یا به اختصار FTS یکی از قابلیتهای SQL Server جهت جستجوی پیشرفته در متون میباشد. این قابلیت تا کنون در EF 6.1.1 ایجاد نشده است.
در ادامه پیاده سازی از FTS در EF را مشاهده مینمایید.
جهت ایجاد قابلیت FTS از متد Contains در Linq استفاده شده است.
ابتدا متدهای الحاقی جهت اعمال دستورات FREETEXT و CONTAINS اضافه میشود.سپس دستورات تولیدی EF را قبل از اجرا بر روی بانک اطلاعاتی توسط امکان Command Interception به دستورات FTS تغییر میدهیم.
همانطور که میدانید دستور Contains در Linq توسط EF به دستور LIKE تبدیل میشود. به جهت اینکه ممکن است بخواهیم از دستور LIKE نیز استفاده کنیم یک پیشوند به مقادیری که میخواهیم به دستورات FTS تبدیل شوند اضافه مینماییم.
جهت استفاده از Fts در EF کلاسهای زیر را ایجاد نمایید.
کلاس FullTextPrefixes :
/// <summary> /// /// </summary> public static class FullTextPrefixes { /// <summary> /// /// </summary> public const string ContainsPrefix = "-CONTAINS-"; /// <summary> /// /// </summary> public const string FreetextPrefix = "-FREETEXT-"; /// <summary> /// /// </summary> /// <param name="searchTerm"></param> /// <returns></returns> public static string Contains(string searchTerm) { return string.Format("({0}{1})", ContainsPrefix, searchTerm); } /// <summary> /// /// </summary> /// <param name="searchTerm"></param> /// <returns></returns> public static string Freetext(string searchTerm) { return string.Format("({0}{1})", FreetextPrefix, searchTerm); } }
کلاس جاری جهت علامت گذاری دستورات FTS میباشد و توسط متدهای الحاقی و کلاس FtsInterceptor استفاده میگردد.
کلاس FullTextSearchExtensions :
public static class FullTextSearchExtensions { /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> public static IQueryable<TEntity> FreeTextSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class { return FreeTextSearchImp(source, expression, FullTextPrefixes.Freetext(searchTerm)); } /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> public static IQueryable<TEntity> ContainsSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class { return FreeTextSearchImp(source, expression, FullTextPrefixes.Contains(searchTerm)); } /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> private static IQueryable<TEntity> FreeTextSearchImp<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) { if (String.IsNullOrEmpty(searchTerm)) { return source; } // The below represents the following lamda: // source.Where(x => x.[property].Contains(searchTerm)) //Create expression to represent x.[property].Contains(searchTerm) //var searchTermExpression = Expression.Constant(searchTerm); var searchTermExpression = Expression.Property(Expression.Constant(new { Value = searchTerm }), "Value"); var checkContainsExpression = Expression.Call(expression.Body, typeof(string).GetMethod("Contains"), searchTermExpression); //Join not null and contains expressions var methodCallExpression = Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Lambda<Func<TEntity, bool>>(checkContainsExpression, expression.Parameters)); return source.Provider.CreateQuery<TEntity>(methodCallExpression); }
در این کلاس متدهای الحاقی جهت اعمال قابلیت Fts ایجاد شده است.
متد FreeTextSearch جهت استفاده از دستور FREETEXT استفاده میشود.در این متد پیشوند -FREETEXT- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد ContainsSearch جهت استفاده از دستور CONTAINS استفاده میشود.در این متد پیشوند -CONTAINS- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد FreeTextSearchImp جهت ایجاد دستور Contains در Linq میباشد.
کلاس FtsInterceptor :
public class FtsInterceptor : IDbCommandInterceptor { /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { RewriteFullTextQuery(command); } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { RewriteFullTextQuery(command); } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="cmd"></param> public static void RewriteFullTextQuery(DbCommand cmd) { var text = cmd.CommandText; for (var i = 0; i < cmd.Parameters.Count; i++) { var parameter = cmd.Parameters[i]; if ( !parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength)) continue; if (parameter.Value == DBNull.Value) continue; var value = (string)parameter.Value; if (value.IndexOf(FullTextPrefixes.ContainsPrefix, StringComparison.Ordinal) >= 0) { parameter.Size = 4096; parameter.DbType = DbType.AnsiStringFixedLength; value = value.Replace(FullTextPrefixes.ContainsPrefix, ""); // remove prefix we added n linq query value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE parameter.Value = value; cmd.CommandText = Regex.Replace(text, string.Format( @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName), string.Format(@"CONTAINS([$1].[$2], @{0})", parameter.ParameterName)); if (text == cmd.CommandText) throw new Exception("FTS was not replaced on: " + text); text = cmd.CommandText; } else if (value.IndexOf(FullTextPrefixes.FreetextPrefix, StringComparison.Ordinal) >= 0) { parameter.Size = 4096; parameter.DbType = DbType.AnsiStringFixedLength; value = value.Replace(FullTextPrefixes.FreetextPrefix, ""); // remove prefix we added n linq query value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE parameter.Value = value; cmd.CommandText = Regex.Replace(text, string.Format( @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName), string.Format(@"FREETEXT([$1].[$2], @{0})", parameter.ParameterName)); if (text == cmd.CommandText) throw new Exception("FTS was not replaced on: " + text); text = cmd.CommandText; } } } }
در این کلاس دستوراتی را که توسط متدهای الحاقی جهت امکان Fts علامت گذاری شده بودند را یافته و دستور LIKE را با دستورات CONTAINSو FREETEXT جایگزین میکنیم.
در ادامه برنامه ای جهت استفاده از این امکان به همراه دستورات تولیدی آنرا مشاهده مینمایید.
public class Note { /// <summary> /// /// </summary> public int Id { get; set; } /// <summary> /// /// </summary> public string NoteText { get; set; } } public class FtsSampleContext : DbContext { /// <summary> /// /// </summary> public DbSet<Note> Notes { get; set; } /// <summary> /// /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new NoteMap()); } } /// <summary> /// /// </summary> class Program { /// <summary> /// /// </summary> /// <param name="args"></param> static void Main(string[] args) { DbInterception.Add(new FtsInterceptor()); const string searchTerm = "john"; using (var db = new FtsSampleContext()) { var result1 = db.Notes.FreeTextSearch(a => a.NoteText, searchTerm).ToList(); //SQL Server Profiler result ===>>> //exec sp_executesql N'SELECT // [Extent1].[Id] AS [Id], // [Extent1].[NoteText] AS [NoteText] // FROM [dbo].[Notes] AS [Extent1] // WHERE FREETEXT([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 //char(4096)',@p__linq__0='(john)' var result2 = db.Notes.ContainsSearch(a => a.NoteText, searchTerm).ToList(); //SQL Server Profiler result ===>>> //exec sp_executesql N'SELECT // [Extent1].[Id] AS [Id], // [Extent1].[NoteText] AS [NoteText] // FROM [dbo].[Notes] AS [Extent1] // WHERE CONTAINS([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 //char(4096)',@p__linq__0='(john)' } Console.ReadKey(); } }
ابتدا کلاس FtsInterceptor را به EF معرفی مینماییم. سپس از دو متد الحاقی مذکور استفاده مینماییم. خروجی هر دو متد توسط SQL Server Profiler در زیر هر متد مشاهده مینمایید.
تمامی کدها و مثال مربوطه در آدرس https://effts.codeplex.com قرار گرفته است.
منبع:
Full Text Search in Entity Framework 6
var client = new SoapDisposableEmailAddressDetectorClient(); var context = new soapContext { //todo: get your API key here: http://www.nameapi.org/en/register/ apiKey = "test" }; var result = client.isDisposable(context, "DaDiDoo@mailinator.com"); if (result.disposable.ToString() == "YES") { Console.WriteLine("YES! It's Disposable!"); }
Additional information: The remote server returned an unexpected response: (400) Bad Request.
<html><head><title>Bad Request</title></head><body><h1>Bad Request</h1><p>No api-key provided!</p></body></html>
اگر همین وب سرویس را توسط امکانات سایت http://wsdlbrowser.com بررسی کنید، بدون مشکل کار میکند. اما تفاوت در کجاست؟
خروجی ارسالی به سرور، توسط سایت http://wsdlbrowser.com به این شکل است:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://disposableemailaddressdetector.email.services.v4_0.soap.server.nameapi.org/"> <SOAP-ENV:Body> <ns1:isDisposable> <context> <apiKey>test</apiKey> </context> <emailAddress>sdsdg@site.com</emailAddress> </ns1:isDisposable> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <isDisposable xmlns="http://disposableemailaddressdetector.email.services.v4_0.soap.server.nameapi.org/"> <context xmlns=""><apiKey>test</apiKey></context> <emailAddress xmlns="">DaDiDoo@mailinator.com</emailAddress> </isDisposable> </s:Body> </s:Envelope>
برای این منظور به سه کلاس ذیل خواهیم رسید:
public class EndpointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(new ClientMessageInspector()); } } public class ClientMessageInspector : IClientMessageInspector { public void AfterReceiveReply(ref Message reply, object correlationState) { } public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel) { request = new MyCustomMessage(request); return request; } } /// <summary> /// To customize WCF envelope and namespace prefix /// </summary> public class MyCustomMessage : Message { private readonly Message _message; public MyCustomMessage(Message message) { _message = message; } public override MessageHeaders Headers { get { return _message.Headers; } } public override MessageProperties Properties { get { return _message.Properties; } } public override MessageVersion Version { get { return _message.Version; } } protected override void OnWriteStartBody(XmlDictionaryWriter writer) { writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/"); } protected override void OnWriteBodyContents(XmlDictionaryWriter writer) { _message.WriteBodyContents(writer); } protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer) { writer.WriteStartElement("SOAP-ENV", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/"); writer.WriteAttributeString("xmlns", "ns1", null, value: "http://disposableemailaddressdetector.email.services.v4_0.soap.server.nameapi.org/"); } }
var client = new SoapDisposableEmailAddressDetectorClient(); client.Endpoint.Behaviors.Add(new EndpointBehavior());
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
بنده در پروژه ای دقیقا طبق موارد توضیح داده شده در این مقاله (و سایر پروژههای موجود در سایت جاری) عمل کردم ام . مشکلی که دارم کوکی مربوطه موجود میباشد (با استفاده از فایبر باگ چک کردم) ولی کاربر در کمتر از ده دقیقه کاربر خارج میشود در صورتی که کوکی هنوز معتبر و زمان منقضی شدن آن به اتمام نرسیده است . تنظیمات به صورت خلاصه به صورت زیر میباشد :
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), ExpireTimeSpan = TimeSpan.FromDays(30), Provider = new CookieAuthenticationProvider { OnValidateIdentity = StoreObjectFactory.Container.GetInstance<IApplicationUserManagerService>().OnValidateIdentity() }, SlidingExpiration = true, CookieName = "iasaccount" });
public Func<CookieValidateIdentityContext, Task> OnValidateIdentity() { return SecurityStampValidator.OnValidateIdentity<ApplicationUserManagerService, User, Guid>( validateInterval: TimeSpan.FromDays(3), regenerateIdentityCallback: (manager, user) => GenerateUserIdentityAsync(manager, user), getUserIdCallback: id => id.GetGuidUserId()); }
و :
var result = await _signInManagerService.PasswordSignInAsync(loginViewModel.Email, loginViewModel.Password, loginViewModel.RememberMe, shouldLockout: false);
آیا تنظیمات دیگه ای لازم هست که انجام شود ؟
//_EntityFormLayout.cshtml @inherits EntityFormRazorPage<dynamic> @{ Layout = null; } <div class="modal-header"> <h4 class="modal-title" asp-if="IsNew">Create New @EntityDisplayName</h4> <h4 class="modal-title" asp-if="!IsNew">Edit @EntityDisplayName</h4> <button type="button" class="close" data-dismiss="modal">×</button> </div> <form asp-action="@(IsNew ? CreateActionName : EditActionName)" asp-modal-form="@FormId"> <div class="modal-body"> <input type="hidden" name="continue-editing" value="true" asp-permission="@EditPermission"/> <input asp-for="@Version" type="hidden"/> <input asp-for="@Id" type="hidden"/> @RenderBody() </div> <div class="modal-footer"> <a class="btn btn-light btn-circle" asp-modal-delete-link asp-model-id="@Id" asp-modal-toggle="false" asp-action="@DeleteActionName" asp-if="!IsNew" asp-permission="@DeletePermission" title="Delete Role"> <i class="fa fa-trash text-danger"></i> </a> <a class="btn btn-light btn-circle" title="Refresh Role" asp-if="!IsNew" asp-modal-link asp-modal-toggle="false" asp-action="@EditActionName" asp-route-id="@Id"> <i class="fa fa-repeat"></i> </a> <a class="btn btn-light btn-circle mr-auto" title="New Role" asp-modal-link asp-modal-toggle="false" asp-permission="@CreatePermission" asp-action="@CreateActionName"> <i class="fa fa-plus"></i> </a> <button type="button" class="btn btn-light" data-dismiss="modal"> <i class="fa fa-ban"></i> Cancel </button> <button type="submit" class="btn btn-outline-primary"> <i class="fa fa-save"></i> Save Changes </button> </div> </form>
public abstract class EntityFormRazorPage<T> : RazorPage<T> { protected string EntityName { get => ViewData[nameof(EntityName)].ToString(); set => ViewData[nameof(EntityName)] = value; } protected string EntityDisplayName { get => ViewData[nameof(EntityDisplayName)].ToString(); set => ViewData[nameof(EntityDisplayName)] = value; } protected string DeletePermission { get => ViewData[nameof(DeletePermission)].ToString(); set => ViewData[nameof(DeletePermission)] = value; } protected string CreatePermission { get => ViewData[nameof(CreatePermission)].ToString(); set => ViewData[nameof(CreatePermission)] = value; } protected string EditPermission { get => ViewData[nameof(EditPermission)].ToString(); set => ViewData[nameof(EditPermission)] = value; } protected string CreateActionName { get => ViewData.TryGetValue(nameof(CreateActionName), out var value) ? value.ToString() : "Create"; set => ViewData[nameof(CreateActionName)] = value; } protected string EditActionName { get => ViewData.TryGetValue(nameof(EditActionName), out var value) ? value.ToString() : "Edit"; set => ViewData[nameof(EditActionName)] = value; } protected string DeleteActionName { get => ViewData.TryGetValue(nameof(DeleteActionName), out var value) ? value.ToString() : "Delete"; set => ViewData[nameof(DeleteActionName)] = value; } protected string FormId => $"{EntityName}Form"; protected bool IsNew => (Model as dynamic).IsNew(); protected string Id => (Model as dynamic).Id.ToString(CultureInfo.InvariantCulture); protected byte[] Version => (Model as dynamic).Version; }
//_BlogPartial.cshtml @inherits EntityFormRazorPage<BlogModel> @{ Layout = "_EntityFormLayout"; EntityName = "Blog"; DeletePermission = PermissionNames.Blogs_Delete; CreatePermission = PermissionNames.Blogs_Create; EditPermission = PermissionNames.Blogs_Edit; EntityDisplayName = "Blog"; }
//_BlogPartial.cshtml @inherits EntityFormRazorPage<BlogModel> @{ Layout = "_EntityFormLayout"; ... } <div class="form-group row"> <div class="col col-md-8"> <label asp-for="Title" class="col-form-label text-md-left"></label> <input asp-for="Title" autocomplete="off" class="form-control"/> <span asp-validation-for="Title" class="text-danger"></span> </div> </div> <div class="form-group row"> <div class="col"> <label asp-for="Url" class="col-form-label text-md-left"></label> <input asp-for="Url" class="form-control" type="url"/> <span asp-validation-for="Url" class="text-danger"></span> </div> </div>
//_BlogPartial.cshtml @inherits EntityFormRazorPage<BlogModel> @{ Layout = "_EntityFormLayout"; EntityName = "Blog"; DeletePermission = PermissionNames.Blogs_Delete; CreatePermission = PermissionNames.Blogs_Create; EditPermission = PermissionNames.Blogs_Edit; EntityDisplayName = "Blog"; } @Html.EditorForModel()