public class MyClass { public int Id { get; set; } public string Name { get; set; } } [HttpPost] public ActionResult TestValueProvider(string data1, MyClass data2) { var id = data2.Id; var name = data2.Name; return new JsonNetResult { Data = new { result = data1 }, JsonRequestBehavior = JsonRequestBehavior.AllowGet, Settings = { ReferenceLoopHandling = ReferenceLoopHandling.Ignore } }; }
- آیا مسیر فایل p12 را درست تنظیم کردهاید؟ در یک برنامهی وب باید به این نحو باشد:
Server.MapPath("~/folder/" + filename)
- تنظیم زیر را هم امتحان کنید:
بجای
var certificate = new X509Certificate2(Authentication.KeyFilePath, Authentication.KeyFilePassword, X509KeyStorageFlags.Exportable);
var certificate = new X509Certificate2(Authentication.KeyFilePath, Authentication.KeyFilePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
EF Code First #13
به طور مثال این دستور
DBEntities MyDB = new DBEntities(); var Query1 = from P in MyDB.Per where P.IDRANK == 2 select P;
string strquery = "where P.IDRANK == 2"; DBEntities MyDB = new DBEntities(); var Query1 = from P in MyDB.Per strquery select P;
در قسمت :
//فقط موارد جدید به تگها و ارتباطات موجود اضافه میشوند foreach (var tag in data.Tags) { if (!listOfActualTagNames.Contains(tag.ToLowerInvariant().Trim())) { _tag.Add(new Tag { Title = tag.Trim() }); } } _uow.SaveChanges(); // ثبت موارد جدید
فقط تگها در جدول خودشون ذخیره میشن و ارتباطی با پست مربوطه ایجاد نمیشه.
آیا نباید به کد زیر تغییر داد؟
if (!listOfActualTagNames.Contains(tag.ToLowerInvariant().Trim())) { var tags = new Tag { Title = tag.Trim() }; _tag.Add(tags); posts.Tags.Add(tags); }
موجودیتهای زیر را در نظر بگیرید:
public class Customer { public Customer() { Orders = new ObservableCollection<Order>(); } public Guid Id { get; set; } public string Name { get; set; } public string Family { get; set; } public string FullName { get { return Name + " " + Family; } } public virtual IList<Order> Orders { get; set; } }
public class Product { public Product() { } public Guid Id { get; set; } public string Name { get; set; } public int Price { get; set; } } public class OrderDetail { public Guid Id { get; set; } public Guid ProductId { get; set; } public int Count { get; set; } public Guid OrderId { get; set; } public int Price { get; set; } public virtual Order Order { get; set; } public virtual Product Product { get; set; } public string ProductName { get { return Product != null ? Product.Name : string.Empty; } } }
public class Order { public Order() { OrderDetail = new ObservableCollection<OrderDetail>(); } public Guid Id { get; set; } public DateTime Date { get; set; } public Guid CustomerId { get; set; } public virtual Customer Customer { get; set; } public virtual IList<OrderDetail> OrderDetail { get; set; } public string CustomerFullName { get { return Customer == null ? string.Empty : Customer.FullName; } } public int TotalPrice { get { if (OrderDetail == null) return 0; return OrderDetail.Where(orderdetail => orderdetail.Product != null) .Sum(orderdetail => orderdetail.Price*orderdetail.Count); } } }
و نگاشت موجودیت ها:
public class CustomerConfiguration : EntityTypeConfiguration<Customer> { public CustomerConfiguration() { HasKey(c => c.Id); Property(c => c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } public class ProductConfiguration : EntityTypeConfiguration<Product> { public ProductConfiguration() { HasKey(p => p.Id); Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } public class OrderDetailConfiguration : EntityTypeConfiguration<OrderDetail> { public OrderDetailConfiguration() { HasKey(od => od.Id); Property(od => od.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } public class OrderConfiguration: EntityTypeConfiguration<Order> { public OrderConfiguration() { HasKey(o => o.Id); Property(o => o.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } }
و برای معرفی موجودیتها به Entity Framwork کلاس StoreDbContext را به صورت زیر تعریف میکنیم:
public class StoreDbContext : DbContext { public StoreDbContext() : base("name=StoreDb") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new CustomerConfiguration()); modelBuilder.Configurations.Add(new OrderConfiguration()); modelBuilder.Configurations.Add(new OrderDetailConfiguration()); modelBuilder.Configurations.Add(new ProductConfiguration()); } public DbSet<Customer> Customers { get; set; } public DbSet<Product> Products { get; set; } public DbSet<Order> Orders { get; set; } public DbSet<OrderDetail> OrderDetails { get; set; } }
جهت مقدار دهی اولیه به database تستی یک DataBaseInitializer به صورت زیر تعریف میکنیم:
public class MyTestDb : DropCreateDatabaseAlways<StoreDbContext> { protected override void Seed(StoreDbContext context) { var customer1 = new Customer { Name = "Vahid", Family = "Nasiri" }; var customer2 = new Customer { Name = "Mohsen", Family = "Jamshidi" }; var customer3 = new Customer { Name = "Mohsen", Family = "Akbari" }; var product1 = new Product {Name = "CPU", Price = 350000}; var product2 = new Product {Name = "Monitor", Price = 500000}; var product3 = new Product {Name = "Keyboard", Price = 30000}; var product4 = new Product {Name = "Mouse", Price = 20000}; var product5 = new Product {Name = "Power", Price = 70000}; var product6 = new Product {Name = "Hard", Price = 250000}; var order1 = new Order { Customer = customer1, Date = new DateTime(2013, 1, 1), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product1, Count = 1, Price = product1.Price}, new OrderDetail {Product = product2, Count = 1, Price = product2.Price}, new OrderDetail {Product = product3, Count = 1, Price = product3.Price}, } }; var order2 = new Order { Customer = customer1, Date = new DateTime(2013, 1, 5), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product1, Count = 2, Price = product1.Price}, new OrderDetail {Product = product3, Count = 4, Price = product3.Price}, } }; var order3 = new Order { Customer = customer1, Date = new DateTime(2013, 1, 9), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product1, Count = 4, Price = product1.Price}, new OrderDetail {Product = product3, Count = 5, Price = product3.Price}, new OrderDetail {Product = product5, Count = 6, Price = product5.Price}, } }; var order4 = new Order { Customer = customer2, Date = new DateTime(2013, 1, 9), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product4, Count = 1, Price = product4.Price}, new OrderDetail {Product = product3, Count = 1, Price = product3.Price}, new OrderDetail {Product = product6, Count = 1, Price = product6.Price}, } }; var order5 = new Order { Customer = customer2, Date = new DateTime(2013, 1, 12), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product4, Count = 1, Price = product4.Price}, new OrderDetail {Product = product5, Count = 2, Price = product5.Price}, new OrderDetail {Product = product6, Count = 5, Price = product6.Price}, } }; context.Customers.Add(customer3); context.Orders.Add(order1); context.Orders.Add(order2); context.Orders.Add(order3); context.Orders.Add(order4); context.Orders.Add(order5); context.SaveChanges(); }
و در ابتدای برنامه کد زیر را جهت مقداردهی اولیه به Database مان قرار میدهیم:
Database.SetInitializer(new MyTestDb());
در انتها ConnectionString را در App.Config به صورت زیر تعریف میکنیم:
<connectionStrings> <add name="StoreDb" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=StoreDBTest;Integrated Security = true" providerName="System.Data.SqlClient"/> </connectionStrings>
بسیار خوب، حالا همه چیز محیاست برای اجرای اولین پرس و جو:
using (var context = new StoreDbContext()) { var query = context.Customers; foreach (var customer in query) { Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); } }
پرس و جوی تعریف شده لیست تمام Customerها را باز میگرداند. query فقط یک "عبارت" پرس و جو هست و زمانی اجرا میشود که از آن درخواست نتیجه شود. در مثال بالا این درخواست در اجرای حلقه foreach اتفاق میافتد و درست در این لحظه است که دستور SQL ساخته شده و به Database فرستاده میشود. EF در این حالت تمام دادهها را در یک لحظه باز نمیگرداند بلکه این ارتباط فعال است تا حلقه به پایان برسد و تمام دادهها از database واکشی شود. خروجی به صورت زیر خواهد بود:
Customer Name: Vahid, Customer Family: Nasiri Customer Name: Mohsen, Customer Family: Jamshidi Customer Name: Mohsen, Customer Family: Akbari
نکته: با هر بار درخواست نتیجه از query ، پرس و جوی مربوطه دوباره به database فرستاده میشود که ممکن است مطلوب ما نباشد و باعث افت سرعت شود. برای جلوگیری از تکرار این عمل کافیست با استفاده از متد ToList پرس و جو را در لحظه تعریف به اجرا در آوریم
var customers = context.Customers.ToList();
خط بالا دیگر یک عبارت پرس و جو نخواهد بود بلکه لیست تمام Customer هاست که به یکباره از database بازگشت داده شده است. در ادامه هرجا که از customers استفاده کنیم دیگر پرس و جویی به database فرستاده نخواهد شد.
پرس و جوی زیر مشتریهایی که نام آنها Mohsen هست را باز میگرداند:
private static void Query3() { using (var context = new StoreDbContext()) { var methodSyntaxquery = context.Customers .Where(c => c.Name == "Mohsen"); var sqlSyntaxquery = from c in context.Customers where c.Name == "Mohsen" select c; foreach (var customer in methodSyntaxquery) { Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); } } // Output: // Customer Name: Mohsen, Customer Family: Jamshidi // Customer Name: Mohsen, Customer Family: Akbari }
همانطور که مشاهده میکنید پرس و جو به دو روش Method Syntax و Sql Syntax نوشته شده است.
روش Method Syntax روشی است که از متدهای الحاقی (Extention Method) و عبارتهای لامبدا (Lambda Expersion) برای نوشتن پرس و جو استفاده میشود. اما #C روش Sql Syntax را که همانند دستورات SQL هست، نیز فراهم کرده است تا کسانیکه آشنایی با این روش دارند، از این روش استفاده کنند. در نهایت این روش به Method Syntax تبدیل خواهد شد بنابراین پیشنهاد میشود که از همین روش استفاده شود تا با دست و پنجه نرم کردن با این روش، از مزایای آن در بخشهای دیگر کدنویسی استفاده شود.
اگر به نوع Customers که در DbContext تعریف شده است، دقت کرده باشید، خواهید دید که DbSet میباشد. DbSet کلاس و اینترفیسهای متفاوتی را پیاده سازی کرده است که در ادامه با آنها آشنا خواهیم شد:
- IQueryable<TEntity>, IEnumerable<TEntity>, IQueryable, IEnumerable: که امکان استفاده از متدهای نام آشنای LINQ را برای ما فراهم میکند. البته فراموش نشود که EF از Provider ای با نام LINQ To Entity برای تفسیر پرس و جوی ما و ساخت دستور SQL متناظر آن استفاده میکند. بنابراین تمامی متدهایی که در LINQ To Object استفاده میشوند در اینجا قابل استفاده نیستند. بطور مثال اگر در پرس و جو از LastOrDefault روی Customer استفاده شود در زمان اجرا با خطای زیر مواجه خواهیم شد و در نتیجه در استفاده از این متدها به این مسئله باید دقت شود.
- <IDbSet<TEntity: که دارای متدهای Add, Attach, Create, Find, Remove, Local میباشد و برای بحث ما Find و Local جهت ساخت پرس و جو استفاده میشوند که در ادامه توضیح داده خواهند شد.
- <DbQuery<TEntity: که دارای متدهای AsNoTracking و Include میباشد و در ادامه توضیح داده خواهند شد.
- دادههای موجود در حافظه را بررسی میکند یعنی آنهایی که Load و یا Attach شده اند.
- داده هایی که به DbContext اضافه (Add) ولی هنوز در database درج نشده اند.
- داده هایی که در database هستند ولی هنوز Load نشده اند.
private static void Query4() { using (var context = new StoreDbContext()) { var customer = context.Customers.Find(new Guid("2ee2fd32-e0e9-4955-bace-1995839d4367")); if (customer == null) Console.WriteLine("Customer not found"); else Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); } }
private static void Query5() { using (var context = new StoreDbContext()) { try { var customer1 = context.Customers.Single(c => c.Name == "Unkown"); // Exception: Sequence contains no elements } catch (Exception ex) { Console.WriteLine(ex.Message); } try { var customer2 = context.Customers.Single(c => c.Name == "Mohsen"); // Exception: Sequence contains more than one element } catch (Exception ex) { Console.WriteLine(ex.Message); } var customer3 = context.Customers.SingleOrDefault(c => c.Name == "Unkown"); // customer3 == null var customer4 = context.Customers.Single(c => c.Name == "Vahid"); // customer4 != null } }
private static void Query6() { using (var context = new StoreDbContext()) { try { var customer1 = context.Customers.First(c => c.Name == "Unkown"); // Exception: Sequence contains no elements } catch (Exception ex) { Console.WriteLine(ex.Message); } var customer2 = context.Customers.FirstOrDefault(c => c.Name == "Unknown"); // customer2 == null var customer3 = context.Customers.First(c => c.Name == "Mohsen"); } }
OpenCVSharp #3
تهیهی یک نمونهی سیاه و سفید از تصویر اصلی در OpenCVSharp
برای تهیهی یک نمونهی مشابه تصویر اصلی، از متد CreateImage استفاده میشود:
using (var src = Cv.LoadImage(@"..\..\images\ocv02.jpg", LoadMode.Color)) using (var dst = Cv.CreateImage(new CvSize(src.Width, src.Height), BitDepth.U8, 1)) { Cv.CvtColor(src, dst, ColorConversion.BgrToGray); using (new CvWindow("src", image: src)) using (new CvWindow("dst", image: dst)) { Cv.WaitKey(); } }
معرفی متد CreateImage
پارامتر اول متد CreateImage، اندازهی تصویر تولیدی را مشخص میکند. پارامتر دوم آن تعداد بیت تصویر را تعیین خواهد کرد. این تعداد بیت عموما بر اساس نیاز متدهای مختلف پردازش تصویر، متغیر خواهند بود و برای تعیین آن نیاز است مستندات هر متد را مطالعه کرد. BitDepth.U8 به معنای 8bit unsigned است.
پارامتر سوم این متد، تعیین کنندهی تعداد کانال تصویر است. تصاویر رنگی دارای سه کانال سبز، قرمز و آبی، هستند. چون در اینجا قصد داریم تصویر را سیاه و سفید کنیم، تعداد کانال را به عدد یک تنظیم کردهایم.
متد CreateImage جهت سازگاری با اینترفیس C مربوط به OpenCV در اینجا وجود دارد. معادل
using (var dst = Cv.CreateImage(new CvSize(src.Width, src.Height), BitDepth.U8, 1))
var dst = new IplImage(new CvSize(src.Width, src.Height), BitDepth.U8, 1)
using (var dst = new IplImage(src.Size, BitDepth.U8, 1))
تبدیل تصویر به حالت سیاه و سفید
متد CvtColor جهت تغییر color space بکار میرود که در اینجا (BGR (Blue/Green/Red را به Gray تبدیل کردهاست:
Cv.CvtColor(src, dst, ColorConversion.BgrToGray);
src.CvtColor(dst, ColorConversion.BgrToGray);
بنابراین به صورت خلاصه میتوان کدهای ابتدای بحث را به صورت زیر نیز نوشت که با کلاسهای OpenCVSharp بیشتر سازگاری دارد:
using (var src = new IplImage(@"..\..\images\ocv02.jpg", LoadMode.Color)) //using (var dst = new IplImage(new CvSize(src.Width, src.Height), BitDepth.U8, 1)) using (var dst = new IplImage(src.Size, BitDepth.U8, 1)) { src.CvtColor(dst, ColorConversion.BgrToGray); using (new CvWindow("src", image: src)) using (new CvWindow("dst", image: dst)) { Cv.WaitKey(); } }
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
آموزش LINQ بخش چهارم
انواع JOIN :
• Inner JOIN
• Group JOIN
• Left JOIN
کلاسهای زیر را در نظر بگیرید:
/// <summary> /// دستور العمل /// </summary> class Recipe { public int Id { get; set; } public string Name { get; set; } } /// <summary> /// بازخورد /// </summary> class Review { public int RecipeId { get; set; } public string ReviewText { get; set; } }
Inner Join
این دستور عنصری از توالی اول را که متناظر با آن عنصری در توالی دوم وجود داشته باشد، به خروجی میبرد.
مثال:
Recipe[] recipes = { new Recipe {Id = 1, Name = "Mashed Potato"}, new Recipe {Id = 2, Name = "Crispy Duck"}, new Recipe {Id = 3, Name = "Sachertorte"} }; Review[] reviews = { new Review {RecipeId = 1, ReviewText = "Tasty!"}, new Review {RecipeId = 1, ReviewText = "Not nice :("}, new Review {RecipeId = 1, ReviewText = "Pretty good"}, new Review {RecipeId = 2, ReviewText = "Too hard"}, new Review {RecipeId = 2, ReviewText = "Loved it"} }; var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId select new //anonymous type { RecipeName = recipe.Name, RecipeReview = review.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it
Group Join
بکارگیری into به همراه join، دستور Group Join را میسازد.
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup select new //anonymous type { RecipeName = recipe.Name, Reviews = reviewGroup//collection of related reviews }; foreach (var item in query) { Console.WriteLine($"Review for {item.RecipeName}"); foreach (var review in item.Reviews) { Console.WriteLine($"-{review.ReviewText}"); } }
متغیر reviewGroup توالی حاصل از اجرای join را نمایش میدهد. برای ایجاد توالی خروجی، نتیجه به یک نوع بی نام، بازتاب شده است. هر عنصر در نوع بی نام یک گروه را نشان میدهد. نوع بی نام شامل دو خصوصیت RecipeName که مقدار آن از توالی اول میآید و Reviews که حاصل خروجی Join است میباشد.
خروجی مثال بالا به شکل زیر است:
Review for Mashed Potato -Tasty! -Not nice :( -Pretty good Review for Crispy Duck -Too hard -Loved it Review for Sachertorte
Left outer join
در این مثال از مجموعههای تعریف شدهی در بخش اول مطلب استفاده کردهایم:
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty() select new //anonymous type { RecipeName = recipe.Name, //RecipeReview = rg.ReviewText SystemNullException RecipeReview = (rg == null ? "n/a" : rg.ReviewText) }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
خروجی مثال فوق به شکل زیر است:
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-n/a
مثال:
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty(new Review { ReviewText = "N/A" }) select new //anonymous type { RecipeName = recipe.Name, RecipeReview = rg.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-N/A
EF Code First #10
حین کار با ORMهای پیشرفته، ویژگیهای جالب توجهی در اختیار برنامه نویسها قرار میگیرد که در زمان استفاده از کلاسهای متداول SQLHelper از آنها خبری نیست؛ مانند:
الف) Deferred execution
ب) Lazy loading
ج) Eager loading
نحوه بررسی SQL نهایی تولیدی توسط EF
برای توضیح موارد فوق، نیاز به مشاهده خروجی SQL نهایی حاصل از ORM است و همچنین شمارش تعداد بار رفت و برگشت به بانک اطلاعاتی. بهترین ابزاری را که برای این منظور میتوان پیشنهاد داد، برنامه EF Profiler است. برای دریافت آن میتوانید به این آدرس مراجعه کنید: (^) و (^)
پس از وارد کردن نام و آدرس ایمیل، یک مجوز یک ماهه آزمایشی، به آدرس ایمیل شما ارسال خواهد شد.
زمانیکه این فایل را در ابتدای اجرای برنامه به آن معرفی میکنید، محل ذخیره سازی نهایی آن جهت بازبینی بعدی، مسیر MyUserName\Local Settings\Application Data\EntityFramework Profiler خواهد بود.
استفاده از این برنامه هم بسیار ساده است:
الف) در برنامه خود، ارجاعی را به اسمبلی HibernatingRhinos.Profiler.Appender.dll که در پوشه برنامه EFProf موجود است، اضافه کنید.
ب) در نقطه آغاز برنامه، متد زیر را فراخوانی نمائید:
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
نقطه آغاز برنامه میتواند متد Application_Start برنامههای وب، در متد Program.Main برنامههای ویندوزی کنسول و WinForms و در سازنده کلاس App برنامههای WPF باشد.
ج) برنامه EFProf را اجرا کنید.
مزایای استفاده از این برنامه
1) وابسته به بانک اطلاعاتی مورد استفاده نیست. (برخلاف برای مثال برنامه معروف SQL Server Profiler که فقط به همراه SQL Server ارائه میشود)
2) خروجی SQL نمایش داده شده را فرمت کرده و به همراه Syntax highlighting نیز هست.
3) کار این برنامه صرفا به لاگ کردن SQL تولیدی خلاصه نمیشود. یک سری از Best practices را نیز به شما گوشزد میکند. بنابراین اگر نیاز دارید سیستم خود را بر اساس دیدگاه یک متخصص بررسی کنید (یک Code review ارزشمند)، این ابزار میتواند بسیار مفید باشد.
4) میتواند کوئریهای سنگین و سبک را به خوبی تشخیص داده و گزارشات آماری جالبی را به شما ارائه دهد.
5) میتواند دقیقا مشخص کند، کوئری را که مشاهده میکنید از طریق کدام متد در کدام کلاس صادر شده است و دقیقا از چه سطری.
6) امکان گروه بندی خودکار کوئریهای صادر شده را بر اساس DbContext مورد استفاده به همراه دارد.
و ...
استفاده از این برنامه حین کار با EF «الزامی» است! (البته نسخههای NH و سایر ORMهای دیگر آن نیز موجود است و این مباحث در مورد تمام ORMهای پیشرفته صادق است)
مدام باید بررسی کرد که صفحه جاری چه تعداد کوئری را به بانک اطلاعاتی ارسال کرده و به چه نحوی. همچنین آیا میتوان با اعمال اصلاحاتی، این وضع را بهبود بخشید. بنابراین عدم استفاده از این برنامه حین کار با ORMs، همانند راه رفتن در خواب است! ممکن است تصور کنید برنامه دارد به خوبی کار میکند اما ... در پشت صحنه فقط صفحه جاری برنامه، 100 کوئری را به بانک اطلاعاتی ارسال کرده، در حالیکه شما تنها نیاز به یک کوئری داشتهاید.
کلاسهای مدل مثال جاری
کلاسهای مدل مثال جاری از یک دپارتمان که دارای تعدادی کارمند میباشد، تشکیل شده است. ضمنا هر کارمند تنها در یک دپارتمان میتواند مشغول به کار باشد و رابطه many-to-many نیست :
using System.Collections.Generic;
namespace EF_Sample06.Models
{
public class Department
{
public int DepartmentId { get; set; }
public string Name { get; set; }
//Creates Employee navigation property for Lazy Loading (1:many)
public virtual ICollection<Employee> Employees { get; set; }
}
}
namespace EF_Sample06.Models
{
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
//Creates Department navigation property for Lazy Loading
public virtual Department Department { get; set; }
}
}
نگاشت دستی این کلاسها هم ضرورتی ندارد، زیرا قراردادهای توکار EF Code first را رعایت کرده و EF در اینجا به سادگی میتواند primary key و روابط one-to-many را بر اساس navigation properties تعریف شده، تشخیص دهد.
در اینجا کلاس Context برنامه به شرح زیر است:
using System.Data.Entity;
using EF_Sample06.Models;
namespace EF_Sample06.DataLayer
{
public class Sample06Context : DbContext
{
public DbSet<Department> Departments { set; get; }
public DbSet<Employee> Employees { set; get; }
}
}
و تنظیمات ابتدایی نحوه به روز رسانی و آغاز بانک اطلاعاتی نیز مطابق کدهای زیر میباشد:
using System.Collections.Generic;
using System.Data.Entity.Migrations;
using EF_Sample06.Models;
namespace EF_Sample06.DataLayer
{
public class Configuration : DbMigrationsConfiguration<Sample06Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(Sample06Context context)
{
var employee1 = new Employee { FirstName = "f name1", LastName = "l name1" };
var employee2 = new Employee { FirstName = "f name2", LastName = "l name2" };
var employee3 = new Employee { FirstName = "f name3", LastName = "l name3" };
var employee4 = new Employee { FirstName = "f name4", LastName = "l name4" };
var dept1 = new Department { Name = "dept 1", Employees = new List<Employee> { employee1, employee2 } };
var dept2 = new Department { Name = "dept 2", Employees = new List<Employee> { employee3 } };
var dept3 = new Department { Name = "dept 3", Employees = new List<Employee> { employee4 } };
context.Departments.Add(dept1);
context.Departments.Add(dept2);
context.Departments.Add(dept3);
base.Seed(context);
}
}
}
نکته: تهیه خروجی XML از نگاشتهای خودکار تهیه شده
اگر علاقمند باشید که پشت صحنه نگاشتهای خودکار EF Code first را در یک فایل XML جهت بررسی بیشتر ذخیره کنید، میتوان از متد کمکی زیر استفاده کرد:
void ExportMappings(DbContext context, string edmxFile)
{
var settings = new XmlWriterSettings { Indent = true };
using (XmlWriter writer = XmlWriter.Create(edmxFile, settings))
{
System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(context, writer);
}
}
بهتر است پسوند فایل XML تولیدی را edmx قید کنید تا بتوان آنرا با دوبار کلیک بر روی فایل، در ویژوال استودیو نیز مشاهده کرد:
using (var db = new Sample06Context())
{
ExportMappings(db, "mappings.edmx");
}
الف) بررسی Deferred execution یا بارگذاری به تاخیر افتاده
برای توضیح مفهوم Deferred loading/execution بهترین مثالی را که میتوان ارائه داد، صفحات جستجوی ترکیبی در برنامهها است. برای مثال یک صفحه جستجو را طراحی کردهاید که حاوی دو تکست باکس دریافت FirstName و LastName کاربر است. کنار هر کدام از این تکست باکسها نیز یک چکباکس قرار دارد. به عبارتی کاربر میتواند جستجویی ترکیبی را در اینجا انجام دهد. نحوه پیاده سازی صحیح این نوع مثالها در EF Code first به چه نحوی است؟
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample06.DataLayer;
using EF_Sample06.Models;
namespace EF_Sample06
{
class Program
{
static IList<Employee> FindEmployees(string fName, string lName, bool byName, bool byLName)
{
using (var db = new Sample06Context())
{
IQueryable<Employee> query = db.Employees.AsQueryable();
if (byLName)
{
query = query.Where(x => x.LastName == lName);
}
if (byName)
{
query = query.Where(x => x.FirstName == fName);
}
return query.ToList();
}
}
static void Main(string[] args)
{
// note: remove this line if you received : create database is not supported by this provider.
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample06Context, Configuration>());
var list = FindEmployees("f name1", "l name1", true, true);
foreach (var item in list)
{
Console.WriteLine(item.FirstName);
}
}
}
}
نحوه صحیح این نوع پیاده سازی ترکیبی را در متد FindEmployees مشاهده میکنید. نکته مهم آن، استفاده از نوع IQueryable و متد AsQueryable است و امکان ترکیب کوئریها با هم.
به نظر شما با فراخوانی متد FindEmployees به نحو زیر که هر دو شرط آن توسط کاربر انتخاب شده است، چه تعداد کوئری به بانک اطلاعاتی ارسال میشود؟
var list = FindEmployees("f name1", "l name1", true, true);
شاید پاسخ دهید که سه بار : یکبار در متد db.Employees.AsQueryable و دوبار هم در حین ورود به بدنه شرطهای یاد شده و اینجا است که کسانی که قبلا با رویههای ذخیره شده کار کرده باشند، شروع به فریاد و فغان میکنند که ما قبلا این مسایل رو با یک SP در یک رفت و برگشت مدیریت میکردیم!
پاسخ صحیح: «فقط یکبار»! آنهم تنها در زمان فراخوانی متد ToList و نه قبل از آن.
برای اثبات این مدعا نیاز است به خروجی SQL لاگ شده توسط EF Profiler مراجعه کرد:
SELECT [Extent1].[EmployeeId] AS [EmployeeId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM [dbo].[Employees] AS [Extent1]
WHERE ([Extent1].[LastName] = 'l name1' /* @p__linq__0 */)
AND ([Extent1].[FirstName] = 'f name1' /* @p__linq__1 */)
IQueryable قلب LINQ است و تنها بیانگر یک عبارت (expression) از رکوردهایی میباشد که مد نظر شما است و نه بیشتر. برای مثال زمانیکه یک IQueryable را همانند مثال فوق فیلتر میکنید، هنوز چیزی از بانک اطلاعاتی یا منبع دادهای دریافت نشده است. هنوز هیچ اتفاقی رخ نداده است و هنوز رفت و برگشتی به منبع دادهای صورت نگرفته است. به آن باید به شکل یک expression builder نگاه کرد و نه لیستی از اشیاء فیلتر شدهی ما. به این مفهوم، deferred execution (اجرای به تاخیر افتاده) نیز گفته میشود.
کوئری LINQ شما تنها زمانی بر روی بانک اطلاعاتی اجرا میشود که کاری بر روی آن صورت گیرد مانند فراخوانی متد ToList، فراخوانی متد First یا FirstOrDefault و امثال آن. تا پیش از این فقط به شکل یک عبارت در برنامه وجود دارد و نه بیشتر.
اطلاعات بیشتر: «تفاوت بین IQueryable و IEnumerable در حین کار با ORMs»
ب) بررسی Lazy Loading یا واکشی در صورت نیاز
در مطلب جاری اگر به کلاسهای مدل برنامه دقت کنید، تعدادی از خواص به صورت virtual تعریف شدهاند. چرا؟
تعریف یک خاصیت به صورت virtual، پایه و اساس lazy loading است و به کمک آن، تا به اطلاعات شیءایی نیاز نباشد، وهله سازی نخواهد شد. به این ترتیب میتوان به کارآیی بیشتری در حین کار با ORMs رسید. برای مثال در کلاسهای فوق، اگر تنها نیاز به دریافت نام یک دپارتمان هست، نباید حین وهله سازی از شیء دپارتمان، شیء لیست کارمندان مرتبط با آن نیز وهله سازی شده و از بانک اطلاعاتی دریافت شوند. به این وهله سازی با تاخیر، lazy loading گفته میشود.
Lazy loading پیاده سازی سادهای نداشته و مبتنی است بر بکارگیری AOP frameworks یا کتابخانههایی که امکان تشکیل اشیاء Proxy پویا را در پشت صحنه فراهم میکنند. علت virtual تعریف کردن خواص رابط نیز به همین مساله بر میگردد، تا این نوع کتابخانهها بتوانند در نحوه تعریف اینگونه خواص virtual در زمان اجرا، در پشت صحنه دخل و تصرف کنند. البته حین استفاده از EF یا انواع و اقسام ORMs دیگر با این نوع پیچیدگیها روبرو نخواهیم شد و تشکیل اشیاء Proxy در پشت صحنه انجام میشوند.
یک مثال: قصد داریم اولین دپارتمان ثبت شده در حین آغاز برنامه را یافته و سپس لیست کارمندان آنرا نمایش دهیم:
using (var db = new Sample06Context())
{
var dept1 = db.Departments.Find(1);
if (dept1 != null)
{
Console.WriteLine(dept1.Name);
foreach (var item in dept1.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}
رفتار یک ORM جهت تعیین اینکه آیا نیاز است برای دریافت اطلاعات بین جداول Join صورت گیرد یا خیر، واکشی حریصانه و غیرحریصانه را مشخص میسازد.
در حالت واکشی حریصانه به ORM خواهیم گفت که لطفا جهت دریافت اطلاعات فیلدهای جداول مختلف، از همان ابتدای کار در پشت صحنه، Join های لازم را تدارک ببین. در حالت واکشی غیرحریصانه به ORM خواهیم گفت به هیچ عنوان حق نداری Join ایی را تشکیل دهی. هر زمانی که نیاز به اطلاعات فیلدی از جدولی دیگر بود باید به صورت مستقیم به آن مراجعه کرده و آن مقدار را دریافت کنی.
به صورت خلاصه برنامه نویس در حین کار با ORM های پیشرفته نیازی نیست Join بنویسد. تنها باید ORM را طوری تنظیم کند که آیا اینکار را حتما خودش در پشت صحنه انجام دهد (واکشی حریصانه)، یا اینکه خیر، به هیچ عنوان SQL های تولیدی در پشت صحنه نباید حاوی Join باشند (lazy loading).
در مثال فوق به صورت خودکار دو کوئری به بانک اطلاعاتی ارسال میگردد:
SELECT [Limit1].[DepartmentId] AS [DepartmentId],
[Limit1].[Name] AS [Name]
FROM (SELECT TOP (2) [Extent1].[DepartmentId] AS [DepartmentId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Departments] AS [Extent1]
WHERE [Extent1].[DepartmentId] = 1 /* @p0 */) AS [Limit1]
SELECT [Extent1].[EmployeeId] AS [EmployeeId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM [dbo].[Employees] AS [Extent1]
WHERE ([Extent1].[Department_DepartmentId] IS NOT NULL)
AND ([Extent1].[Department_DepartmentId] = 1 /* @EntityKeyValue1 */)
یکبار زمانیکه قرار است اطلاعات دپارتمان یک (db.Departments.Find) دریافت شود. تا این لحظه خبری از جدول Employees نیست. چون lazy loading فعال است و فقط اطلاعاتی را که نیاز داشتهایم فراهم کرده است.
زمانیکه برنامه به حلقه میرسد، نیاز است اطلاعات dept1.Employees را دریافت کند. در اینجا است که کوئری دوم، به بانک اطلاعاتی صادر خواهد شد (بارگذاری در صورت نیاز).
ج) بررسی Eager Loading یا واکشی حریصانه
حالت lazy loading بسیار جذاب به نظر میرسد؛ برای مثال میتوان خواص حجیم یک جدول را به جدول مرتبط دیگری منتقل کرد. مثلا فیلدهای متنی طولانی یا اطلاعات باینری فایلهای ذخیره شده، تصاویر و امثال آن. به این ترتیب تا زمانیکه نیازی به اینگونه اطلاعات نباشد، lazy loading از بارگذاری آنها جلوگیری کرده و سبب افزایش کارآیی برنامه میشود.
اما ... همین lazy loading در صورت استفاده نا آگاهانه میتواند سرور بانک اطلاعاتی را در یک برنامه چندکاربره از پا درآورد! نیازی هم نیست تا شخصی به سایت شما حمله کند. مهاجم اصلی همان برنامه نویس کم اطلاع است!
اینبار مثال زیر را درنظر بگیرید که بجای دریافت اطلاعات یک شخص، مثلا قصد داریم، اطلاعات کلیه دپارتمانها را توسط یک Grid نمایش دهیم (فرقی نمیکند برنامه وب یا ویندوز باشد؛ اصول یکی است):
using (var db = new Sample06Context())
{
foreach (var dept in db.Departments)
{
Console.WriteLine(dept.Name);
foreach (var item in dept.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}
There is already an open DataReader associated with this Command which must be closed first
برای رفع این مشکل نیاز است گزینه MultipleActiveResultSets=True را به کانکشن استرینگ اضافه کرد:
<connectionStrings>
<clear/>
<add
name="Sample06Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true;MultipleActiveResultSets=True;"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
سؤال: به نظر شما در دو حلقه تو در توی فوق چندبار رفت و برگشت به بانک اطلاعاتی صورت میگیرد؟ با توجه به اینکه در متد Seed ذکر شده در ابتدای مطلب، تعداد رکوردها مشخص است.
پاسخ: 7 بار!
و اینجا است که عنوان شد استفاده از EF Profiler در حین توسعه برنامههای مبتنی بر ORM «الزامی» است! اگر از این نکته اطلاعی نداشتید، بهتر است یکبار تمام صفحات گزارشگیری برنامههای خود را که حاوی یک Grid هستند، توسط EF Profiler بررسی کنید. اگر در این برنامه پیغام خطای n+1 select را دریافت کردید، یعنی در حال استفاده ناصحیح از امکانات lazy loading میباشید.
آیا میتوان این وضعیت را بهبود بخشید؟ زمانیکه کار ما گزارشگیری از اطلاعات با تعداد رکوردهای بالا است، استفاده ناصحیح از ویژگی Lazy loading میتواند به شدت کارآیی بانک اطلاعاتی را پایین بیاورد. برای حل این مساله در زمانهای قدیم (!) بین جداول join مینوشتند؛ الان چطور؟
در EF متدی به نام Include جهت Eager loading اطلاعات موجودیتهای مرتبط به هم درنظر گرفته شده است که در پشت صحنه همینکار را انجام میدهد:
using (var db = new Sample06Context())
{
foreach (var dept in db.Departments.Include(x => x.Employees))
{
Console.WriteLine(dept.Name);
foreach (var item in dept.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}
همانطور که ملاحظه میکنید اینبار به کمک متد Include، نسبت به واکشی حریصانه Employees اقدام کردهایم. اکنون اگر برنامه را اجرا کنیم، فقط یک رفت و برگشت به بانک اطلاعاتی انجام خواهد شد و کار Join نویسی به صورت خودکار توسط EF مدیریت میگردد:
SELECT [Project1].[DepartmentId] AS [DepartmentId],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
[Project1].[EmployeeId] AS [EmployeeId],
[Project1].[FirstName] AS [FirstName],
[Project1].[LastName] AS [LastName],
[Project1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM (SELECT [Extent1].[DepartmentId] AS [DepartmentId],
[Extent1].[Name] AS [Name],
[Extent2].[EmployeeId] AS [EmployeeId],
[Extent2].[FirstName] AS [FirstName],
[Extent2].[LastName] AS [LastName],
[Extent2].[Department_DepartmentId] AS [Department_DepartmentId],
CASE
WHEN ([Extent2].[EmployeeId] IS NULL) THEN CAST(NULL AS int)
ELSE 1
END AS [C1]
FROM [dbo].[Departments] AS [Extent1]
LEFT OUTER JOIN [dbo].[Employees] AS [Extent2]
ON [Extent1].[DepartmentId] = [Extent2].[Department_DepartmentId]) AS [Project1]
ORDER BY [Project1].[DepartmentId] ASC,
[Project1].[C1] ASC
متد Include در نگارشهای اخیر EF پیشرفت کرده است و همانند مثال فوق، امکان کار با lambda expressions را جهت تعریف خواص مورد نظر به صورت strongly typed ارائه میدهد. در نگارشهای قبلی این متد، تنها امکان استفاده از رشتهها برای معرفی خواص وجود داشت.
همچنین توسط متد Include امکان eager loading چندین سطح با هم نیز وجود دارد؛ مثلا x.Employees.Kids و همانند آن.
چند نکته در مورد نحوه خاموش کردن Lazy loading
امکان خاموش کردن Lazy loading در تمام کلاسهای برنامه با تنظیم خاصیت Configuration.LazyLoadingEnabled کلاس Context برنامه به نحو زیر میسر است:
public class Sample06Context : DbContext
{
public Sample06Context()
{
this.Configuration.LazyLoadingEnabled = false;
}
یا اگر تنها در مورد یک کلاس نیاز است این خاموش سازی صورت گیرد، کلمه کلیدی virtual را حذف کنید. برای مثال با نوشتن public ICollection<Employee> Employees بجای public virtual ICollection<Employee> Employees در اولین بار وهله سازی کلاس دپارتمان، لیست کارمندان آن به نال تنظیم میشود. البته در این حالت null object pattern را نیز فراموش نکنید (وهله سازی پیش فرض Employees در سازنده کلاس):
public class Department
{
public int DepartmentId { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
public Department()
{
Employees = new HashSet<Employee>();
}
}
به این ترتیب به خطای null reference object بر نخواهیم خورد. همچنین وهله سازی، با مقدار دهی لیست دریافتی از بانک اطلاعاتی متفاوت است. در اینجا نیز باید از متد Include استفاده کرد.
بنابراین در صورت خاموش کردن lazy loading، حتما نیاز است از متد Include استفاده شود. اگرlazy loading فعال است، جهت تبدیل آن به eager loading از متد Include استفاده کنید (اما اجباری نیست).
OpenCVSharp #11
using (var src = new Mat(@"test.jpg", LoadMode.Color)) { image1.Source = KMeans(src, 2).ToWriteableBitmap(); image2.Source = Binary(src).ToWriteableBitmap(); }
private static Mat Binary(Mat src) { // Convert the image to Gray src = src.CvtColor(ColorConversion.RgbToGray); // Convert the Gray to Binary with auto threshold Cv2.Threshold(src, src, 0, 255, ThresholdType.Binary | ThresholdType.Otsu); // Convert the Gray to Binary with manual threshold //Cv2.Threshold(src, src, 127, 255, ThresholdType.Binary); return src; } private static Mat KMeans(Mat src, int k) { var columnVector = src.Reshape(cn: 3, rows: src.Rows * src.Cols); var samples = new Mat(); columnVector.ConvertTo(samples, MatType.CV_32FC3); var bestLabels = new Mat(); var centers = new Mat(); Cv2.Kmeans( data: samples, k: k, bestLabels: bestLabels, criteria: new TermCriteria(type: CriteriaType.Epsilon | CriteriaType.Iteration, maxCount: 10, epsilon: 1.0), attempts: 3, flags: KMeansFlag.PpCenters, centers: centers); var dst = new Mat(src.Rows, src.Cols, src.Type()); for (var size = 0; size < src.Cols * src.Rows; size++) { var clusterIndex = bestLabels.At<int>(0, size); var newPixel = new Vec3b { Item0 = (byte)(centers.At<float>(clusterIndex, 0)), // B Item1 = (byte)(centers.At<float>(clusterIndex, 1)), // G Item2 = (byte)(centers.At<float>(clusterIndex, 2)) // R }; dst.Set(size / src.Cols, size % src.Cols, newPixel); } return dst; }
نتایج :
[BreezeController] public class AccountController : ApiController { ... }
module Interfaces { export interface IAuthService { user: Models.IUserToken getUserInfo(accessToken); login(data); logOut(); register(data); changePassword(data); accessToken(accessToken, data); } }
سرویس AuthService پیاده سازی اینترفیس IAuthService را برعهده دارد. در سازنده آن، وابستگیهای آن مقداردهی شدهاست و همچنین تنظیمات manager را انجام دادهایم.
"grant_type=password & username=myusername & password=mypassword";
var ajaxAdapter = breeze.config.getAdapterInstance("ajax"); breeze.ajaxpost(ajaxAdapter);
.withParameters({ $method: 'POST', $encoding: 'JSON', $data: newData }
module AdApps { var securityUrls = { site: '/', login: '/token', logout: 'logout', register: 'register', userInfo: 'getUserInfo', changePassword: 'changePassword', } export class AuthService implements Interfaces.IAuthService { private manager: breeze.EntityManager; constructor( private _breeze: typeof breeze, private $http: ng.IHttpProvider, private toaster: ngtoaster.IToasterService, private $location: ng.ILocationService) { var dataService = new _breeze.DataService({ serviceName: "/breeze/Account", hasServerMetadata: false }); var metadataStore = new _breeze.MetadataStore({ namingConvention: _breeze.NamingConvention.camelCase }); this.manager = new _breeze.EntityManager({ dataService: dataService, metadataStore: metadataStore, saveOptions: new _breeze.SaveOptions({ allowConcurrentSaves: true, tag: [{}] }) }); } user: Models.IUserToken; accessToken(accessToken, data): string { if (accessToken === 'clear') { localStorage.removeItem('accessToken'); delete this.$http.defaults.headers.common.Authorization; } else { window.localStorage.setItem("accessToken", accessToken); this.$http.defaults.headers.common.Authorization = 'Bearer ' + accessToken; } return accessToken; } getUserInfo(): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.userInfo); return this.manager.executeQuery(query).then(data => { return data.results[0]; }); } login(data: any): ng.IPromise<any> { var newData = "grant_type=password&username=" + data.userName + "&password=" + data.password; var query = this._breeze.EntityQuery.from(securityUrls.login) .withParameters({ $method: 'POST', $encoding: 'JSON', $data: newData }); return this.manager.executeQuery(query).then(data => { var self = this; var result = data.results[0] as any; self.accessToken(result.access_token, data.results[0]); self.user = <Models.IUserToken>{}; self.user = <Models.IUserToken>result; return result; }); } logOut(): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.logout) .withParameters({ $method: 'POST', $encoding: 'JSON', }); return this.manager.executeQuery(query).then(data => { this.user = null; this.accessToken('clear', null); this.$location.path("/"); }); } register(data: Object): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.register) .withParameters({ $method: 'POST', $encoding: 'JSON', $data: data }); return this.manager.executeQuery(query).then(data => { }); } changePassword(data: Object): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.changePassword) .withParameters({ $method: 'POST', $encoding: 'JSON', $data: data }); return this.manager.executeQuery(query).then(data => { }); } } }
سرویس HttpInterceptor : رهگیری و پیگیری کردن نتیجه درخواستهای http را بر عهده دارد.
module AdApps { export class HttpInterceptor { private static _toaster: ngtoaster.IToasterService; private static _$q: ng.IQService; constructor( private $q: ng.IQService, private toaster: ngtoaster.IToasterService, private $location: ng.ILocationService) { HttpInterceptor._toaster = toaster; HttpInterceptor._$q = $q; } request(config): string { config.headers = config.headers || {}; var authData = window.localStorage.getItem("accessToken"); if (authData) { config.headers.Authorization = "Bearer " + authData; } return config; }; response(response): ng.IPromise<any> { if (response.data && response.data.message && response.status === 200) { HttpInterceptor._toaster.success(response.data.message) } return HttpInterceptor._$q.resolve(response); }; responseError(response): ng.IPromise<any> { var self = this; var data = response.data; var title = "خطا"; var messages = []; if (data) { if (data.error) { title = data.error; } if (data.message) { messages.push(data.message); } if (data.Message) { messages.push(data.Message); } if (data.ModelState) { angular.forEach(data.ModelState, function (errors, key) { if (key.substr(0, 1) != "$") { messages.push(errors); } }); } if (data.exceptionMessage) { messages.push(data.exceptionMessage); } if (data.ExceptionMessage) { messages.push(data.ExceptionMessage); } if (data.error_description) { messages.push(data.error_description); } if (messages.length > 0) { HttpInterceptor._toaster.error(title, messages.join("<br/>")); } if (response.status === "401") { self.$location.path("/ورود"); } } return HttpInterceptor._$q.reject(response); } } }
معرفی کردن مسیرهای ورود، ثبت نام و تغییر رمز عبور به انگولار
module AdApps { class SecurityCtrl { constructor(private $scope: Interfaces.IAuthScope, private authService: AuthService) { $scope.authService = authService; if (window.localStorage.getItem("accessToken") != null) { authService.getUserInfo().then(function (data) { $scope.authService.user = data; }); } $scope.logOut = function () { return authService.logOut().then(function () { }); } } } define(["angularAmd", "angular", "factory/AuthService", "factory/httpInterceptor"], (angularAmd, ng) => { angularAmd = angularAmd.__proto__; var app = ng.module("AngularTypeScript", ['ngRoute', 'breeze.angular', 'toaster']); var viewPath = "app/views/"; var controllerPath = "app/controller/"; app.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { $httpProvider.interceptors.push("HttpInterceptor"); $routeProvider .when("/", angularAmd.route({ templateUrl: viewPath + "home.html", controllerUrl: controllerPath + "home.js" })) .when("/login", angularAmd.route({ templateUrl: viewPath + "login.html", controllerUrl: controllerPath + "login.js" })) .when("/register", angularAmd.route({ templateUrl: viewPath + "register.html", controllerUrl: controllerPath + "register.js" })) .when("/changePassword", angularAmd.route({ templateUrl: viewPath + "change-password.html", controllerUrl: controllerPath + "changePassword.js" })) .otherwise({ redirectTo: '/' }); } ]); app.service('AuthService', ['breeze', '$http', 'toaster', '$location', AuthService]); app.service("HttpInterceptor", ["$q", "toaster", "$location", HttpInterceptor]); app.controller('SecurityCtrl', ['$scope', 'AuthService', SecurityCtrl]); return angularAmd.bootstrap(app); }); }
ایجاد کنترلر .login.ts و ارسال سرویسهای لازم به کلاس LoginCtrl
در صورت صحیح بودن نام کاربری و رمز عبور به صفحه اصلی هدایت خواهد شد.
module AdApps { define(['app'], function (app) { app.controller('LoginCtrl', ["$scope", "AuthService", "$location", LoginCtrl]); }); export class LoginCtrl { constructor($scope: Interfaces.ILoginScope, authService: AuthService, $location: ng.ILocationService) { $scope.submit = function () { authService.login(angular.copy($scope.form)) .then(function (data) { this.$location.path("/"); }) }; } } }
ایجاد login.html
<div ng-controller="LoginCtrl"> <div> <i></i> <span>ورود</span> <div> <div> </div> </div> </div> <div> <div> <div> <form name="Form" id="form1"> <fieldset> <div> <div> <input name="username" ng-model="form.userName" placeholder="نام کاربری" required> <span> <i></i> </span> </div> </div> <div> <div> <input name="password" type="password" ng-model="form.password" placeholder="{{'Password'}}" validator="required"> <span> <i></i> </span> </div> </div> </fieldset> <div> <button type="submit" ng-click="submit()">ورود</button> </div> </form> </div> </div> </div> </div>
requirejs.config({ paths: { "app": "app", "angularAmd": "/Scripts/angularAmd", "angular": "/Scripts/angular", "breezeAjaxpost": "/Scripts/breeze/breeze.ajaxpost", "breeze": "/Scripts/breeze/breeze.debug", "breezeAngular": "/Scripts/breeze/breeze.angular", "bootstrap": "/Scripts/bootstrap", "angularRoute": "/Scripts/angular-route", "jquery": "/Scripts/jquery-2.2.2", "entityManagerService": "factory/entityManagerService", "toaster": "/Scripts/toaster", }, waitSeconds: 0, shim: { "angular": { exports: "angular" }, "angularRoute": { deps: ["angular"] }, "bootstrap": { deps: ["jquery"] }, "breeze": { deps: ["jquery"] }, "breezeAngular": { deps: ["angular", "breeze"] }, "toaster": { deps: ["angular"] }, "app": { deps: ["bootstrap", "angularRoute", "toaster", "breezeAngular", "breezeAjaxpost"] } } }); require(["app"]);