const storeContext = React.createContext(null); export function StoreProvider({ children }) { const userStore = useLocalStore({ id: 1, name: 'User', }); const orderStore = useLocalStore({ id: 0, productId: 1, }); return <storeContext.Provider value={{ userStore, orderStore }}>{children}</storeContext.Provider> } export function useStores() { const stores = React.useContext(storeContext); if (!stores) { throw new Error('useStores must be used within a StoreProvider.'); } return stores; } const { userStore, orderStore } = useStores();
class ConcreteCreator : Creator { public override IProduct FactoryMethod(string type) { switch (type) { case "A": return new ConcreteProductA(); case "B": return new ConcreteProductB(); default: throw new ArgumentException("Invalid type", "type"); } } }
public interface IVehicleFactory { IDiesel GetDiesel(); IMotorCar GetMotorCar(); }
public class IranKhodro : IVehicleFactory { public IDiesel GetDiesel() { return new Arena(); } public IMotorCar GetMotorCar() { return new Peugeot206(); } } public class Saipa : IVehicleFactory { public IDiesel GetDiesel() { return new Foton(); } public IMotorCar GetMotorCar() { return new Peride(); } }
public interface IDiesel { string GetName();} public interface IMotorCar { string GetName();}
public class Foton : IDiesel { public string GetName() { return "This is Foton"; } } public class Arena : IDiesel { public string GetName() { return "This is Arena"; } } public class Peugeot206 : IMotorCar { public string GetName() { return "This is Peugeot206"; } } public class Peride : IMotorCar { public string GetName() { return "This is Peride"; } }
IVehicleFactory factory = new IranKhodro(); Console.WriteLine("***" + factory.GetType().Name + "***"); IDiesel diesel = factory.GetDiesel(); Console.WriteLine(diesel.GetName()); IMotorCar motorCar = factory.GetMotorCar(); Console.WriteLine(motorCar.GetName()); factory = new Saipa(); Console.WriteLine("***" + factory.GetType().Name + "***"); diesel = factory.GetDiesel(); Console.WriteLine(diesel.GetName()); motorCar = factory.GetMotorCar(); Console.WriteLine(motorCar.GetName());
- پیاده سازی و نامگذاری Product در Factory مربوطه متمرکز میشود و بدین ترتیب Client به نام و نحوه پیاده سازی Typeهای مختلف Product وابستگی نخواهد داشت.
- به راحتی میتوان Concrete Factory مورد استفاده در برنامه را تغییر داد، بدون اینکه تاثیری در عملکرد سایر بخشها داشته باشد.
- در مواردی که بیش از یک محصول برای هر خانواده وجود داشته باشد، استفاده از Abstract Factory تضمین میکند که Productهای هر خانواده همه در کنار هم قرار دارند و با هم فعال و غیر فعال میشوند. (یا همه، یا هیچکدام)
- Factoryها معمولا Singleton هستند. زیرا هر Application بطور معمول فقط به یک instance از هر Concrete Factory نیاز دارد.
- انتخاب Concrete Factory مناسب معمولا توسط پارامترهایی انجام میشود.
و کلام آخر در مورد این الگو:
- Abstract Factory یک interface یا کلاس abstract است که signature متدهای ساخت Objectها در آن تعریف شده است و Concrete Factoryها آنها را implement مینمایند.
- در Abstract Factory Pattern همه Productهای هم خانواده در Concrete Factory مربوط به آن خانواده پیاده سازی و مجتمع میگردند.
- در کدهای برنامه تنها با Abstract Factory و Abstract Productها سر و کار داریم و به هیچ وجه درگیر این مساله که کدام یک از Concrete Classها در برنامه مورد استفاده قرار میگیرند، نمیشویم.
برنامههای Vue.jsای از چندین کامپوننت برای بخش بندی هر قسمت تشکیل میشوند و این بخش بندی برای مدیریت بهتر تغییرات، خطایابی، نگهداری و استفاده مجدد (reusable) میباشد. فرض کنید تعدادی کامپوننت در برنامه داریم و اطلاعات این کامپوننتها بهم وابسته میباشند؛ بطور مثال یک کامپوننت انتخاب دسته بندی را داریم و به محض تغییر این مقدار، میخواهیم لیستی از محصولات پیشنهادی یا پرفروشِ آن دسته بندی، در کامپوننت پایین صفحه نمایش داده شود و یا خرید یک محصول را در نظر بگیرید که بلافاصله محتوای نمایش سبد خرید، بروزرسانی شود. در این مقاله ارتباط از نوع Parent و Child بین کامپوننتها بررسی میشود.
نکته: برای ارسال اطلاعات از کامپوننتِ Parent به Child، از Props استفاده میشود و برای ارتباط از Child به Parent، از emit$ استفاده میشود.
یک برنامه Vue.js با نام vue-communication-part-1 ایجاد نمایید. سپس دو کامپوننت را با نامهای Parent و Child، در پوشه components ایجاد کنید:
در کامپوننت Parent، یک تابع با نام increase وجود دارد که مقدار متغیر parentCounter را افزایش میدهد. چون قصد داریم مقدار متغیر parentCounter در کامپوننت Child نیز بروزرسانی شود، آن را به کامپوننت Child پاس میدهیم:
<Child :childCounter="parentCounter"/>
محتوای کامپوننت Parent:
<template> <div> <div> <h2>Parent Component</h2> <!-- را نمایش میدهید parentCounter مقدار --> <h1>{{ parentCounter }}</h1> <button @click="increase">Increase Parent</button> </div> <div> <!-- پاس میدهید Child در کامپوننت childCounter را به پراپرتی parentCounter مقدار --> <!--از طریق decreaseParent سبب اتصال و فراخوانی تابع @callDecreaseParent به decreaseParent با انتساب --> <!-- میشود Child در کامپوننت callDecreaseMethodInParent تابع --> <Child :childCounter="parentCounter" @callDecreaseParent="decreaseParent"/> </div> </div> </template> <script> //برای استفاده در کامپوننت جاری Child ایمپورت کردن کامپوننت import Child from "./Child.vue"; export default { // در این بخش متغیرهای مورد نیاز کامپوننت را تعریف میکنیم data() { return { parentCounter: 0 }; }, components: { // میتوان آرایه ای از کامپوننتها را در یک کامپوننت استفاده نمود // در این مثال فقط از یک کامپوننت استفاده شده Child }, methods: { //را یک واحد افزایش میدهد parentCounter این متد مقدار increase() { this.parentCounter++; }, decreaseParent() { this.parentCounter--; } } }; </script> <style> .parent-block, .child { text-align: center; margin: 20px; padding: 20px; border: 2px gray solid; } </style>
در کامپوننت Child قصد دریافت مقدار پراپرتیِ childCounter را داریم که از طریق کامپوننت Parent، مقدارش تنظیم و بروزرسانی میشود. به این منظور در قسمت props یک متغیر بنام childCounter را ایجاد میکنیم.
Data is the private memory of each component where you can store any variables you need. Props are how you pass this data from a parent component down to a child component
محتوای کامپوننت Child
<template> <div> <h3>Child Component</h3> <!-- را نمایش میدهید childCounter مقدار --> <h3>{{ childCounter }}</h3> <button @click="increase">Increase Me</button> <button @click="callDecreaseMethodInParent">Call Decrease Method In Parent</button> </div> </template> <script> export default { // استفاده میشود Child به Parent برای ارتباط بین کامپوننت props از props: { childCounter: Number }, data() { return {}; }, methods: { //را یک واحد افزایش میدهد childCounter این متد مقدار increase() { this.childCounter++; }, // فراخوانی میکند Parent را در کامپوننت decreaseParent تابع callDecreaseMethodInParent() { this.$emit("callDecreaseParent"); } } }; </script>
محتوای کامپوننت اصلی برنامه App.vue:
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <parent></parent> </div> </template> <script> import Parent from "./components/Parent.vue"; export default { name: "app", components: { parent: Parent } }; </script> <style> #app { width: 50%; margin: 0 auto; text-align: center; } </style>
اکنون برنامه را با دستور زیر اجرا کنید:
npm run serve
بعد از اجرای دستور فوق، روی گزینه زیر ctrl+click میکنیم تا نتیجه کار در مرورگر قابل رویت باشد:
نمایش صفحه زیر نشان دهندهی درستی انجام کار تا اینجا است:
اکنون روی دکمهی Increase Parent کلیک میکنیم. همزمان مقدار شمارشگر، در هر دو کامپوننت Parent و Child افزایش مییابد و این بدین معناست که با استفاده از Props میتوانیم دادههای دلخواهی را در کامپوننت Child بروز رسانی کنیم. هر زمانی روی دکمهی Increase Me در کامپوننت Child کلیک کنیم، فقط به مقدار شمارشگر درون خودش اضافه میشود و تاثیری را بر شمارشگر Parent ندارد. در واقع یک کپی از مقدار شمارشگر Parent را درون خود دارد.
در ادامه قصد داریم بروزرسانی داده را از Child به Parent انجام دهیم. برای انجام اینکار از emit$ استفاده میکنیم. در دیکشنری Cambridge Dictionary معنی emit به ارسال یک سیگنال ترجمه شدهاست. در واقع بااستفاده از emit میتوانیم یک تابع را در کامپوننت Parent فراخوانی کنیم و در آن تابع، کد دلخواهی را برای دستکاری دادهها مینویسیم.
در تابع callDecreaseMethodInParent در کامپوننت Child، کد زیر را قرار میدهیم:
this.$emit("callDecreaseParent");
هر زمانکه این تابع اجرا شود، یک سیگنال از طریق کد زیر برای کامپوننت Parent ارسال میشود:
<Child @callDecreaseParent="decreaseParent"/>
در کد فوق مشخص شده که با ارسال سیگنال callDecreaseParent، تابع decreaseParent در کامپوننت Parent فراخوانی شود.
نکته: برای اجرای برنامه و دریافت پکیجهای مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:
npm install
چند نکته
this.$emit //dispatches an event to its parent component
کد فوق سبب اجرای یک تابع در کامپوننتِ Parent خودش میشود.
this.$parent // gives you a reference to the parent component
ارجاعی به کامپوننت Parent خودش را فراهم میکند:
this.$root // gives you a reference to the root component
زمانیکه چندین کامپوننت تو در تو را داریم یا به اصطلاح nested component، سبب ارجاعی به بالاترین کامپوننت Parent میگردد.
this.$parent.$emit // will make the parent dispatch the event to its parent
سبب اجرای تابعِ Parent کامپوننتِ Parent جاری میشود. به بیان ساده اگر این کد در کامپوننت فرزند فراخوانی شود، سبب اجرای تابعی در کامپوننت پدربزرگِ خود میشود.
this.$root.$emit // will make the root dispatch the event to itself
سبب اجرای تابعی در کامپوننت root میشود (بالاترین کامپوننتِ پدرِ کامپوننت جاری).
تابع emit$ دارای آگومانهای دیگری برای پاس دادن اطلاعات از کامپوننت Child به Parent میباشد؛ مثل زمانیکه قصد دارید اطلاعاتی در مورد محصول خریداری شده را به سبد خرید پاس دهید. در مثال دیگری که در ادامه قرار میگیرد نحوه کارکرد ارتباط کامپوننت Parent و Child را در یک برنامه بهتر تجربه میکنیم.
پیاده سازی یک سبد خرید ساده با روش مقالهی جاری
نکته: برای اجرای برنامه و دریافت پکیجهای مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:
npm install
همچنین نیاز هست تا پکیچ node-sass را با دستور زیر برای این مثال نصب کنید.
npm install node-sass
یک لامپ و سوئیچ برق را درنظر بگیرید. زمانیکه لامپ مشاهده میکند سوئیچ برق در حالت روشن قرار گرفتهاست، روشن خواهد شد و برعکس. در اینجا به سوئیچ، subject و به لامپ، observer گفته میشود. هر زمان که حالت سوئیچ تغییر میکند، از طریق یک callback، وضعیت خود را به observer اعلام خواهد کرد. علت استفاده از callbackها، ارائه راهحلهای عمومی است تا بتواند با انواع و اقسام اشیاء کار کند. به این ترتیب هر بار که شیء observer از نوع متفاوتی تعریف میشود (مثلا بجای لامپ یک خودرو قرار گیرد)، نیازی نخواهد بود تا subject را تغییر داد.
عموما به شیءایی که قرار است وضعیت را مشاهده یا رصد کند، Observer گفته میشود و به شیءایی که قرار است وضعیت آن رصد شود Observable یا Subject گفته میشود.
بد نیست بدانید این الگو یکی از کلیدیترین بخشهای معماری لایه بندی MVC نیز میباشد.
همچنین این نکته حائز اهمیت است که این الگو ممکن است باعث نشتی حافظه هم شود و به این مشکل Lapsed Listener Problem میگویند. یعنی یک listener وجود دارد که تاریخ آن منقضی شده، ولی هنوز در حافظه جا خوش کردهاست. این مشکل برای زبانهای شیءگرایی که با سیستمی مشابه GC پیاده سازی میشوند، رخ میدهد. برای جلوگیری از این حالت، برنامه نویس باید این مشکل را با رجیستر کردنها و عدم رجیستر یک شنوده، در مواقع لزوم حل کند. در غیر این صورت این شنونده بی جهت، یک ارتباط را زنده نگه داشته و حافظهی منبع را به هدر میدهد.
مثال: ما یک کلید داریم که سه کلاس RedLED،GreenLED و BlueLED قرار است آن را مشاهده و وضعیت کلید را رصد کنند.
برای پیاده سازی این الگو، ابتدا یک کلاس انتزاعی را با نام Observer که دارای متدی به نام Update است، ایجاد میکنیم. متغیر از نوع کلاس Observable را بعدا ایجاد میکنیم:
public abstract class Observer { protected Observable Observable; public abstract void Update(); }
public class Observable { private readonly List<Observer> _observers = new List<Observer>(); public void Attach(Observer observer) { _observers.Add(observer); } public void Dettach(Observer observer) { _observers.Remove(observer); } public void NotifyAllObservers() { foreach (var observer in _observers) { observer.Update(); } } }
حال کلاس Switch را با ارث بری از کلاس Observable مینویسیم:
public class Switch:Observable { private bool _state; public bool ChangeState { set { _state = value; NotifyAllObservers(); } get { return _state; } } }
برای هر سه چراغ، رنگی هم داریم:
public class RedLED:Observer { private bool _on = false; public override void Update() { _on = !_on; Console.WriteLine($"Red LED is {((_on) ? "On" : "Off")}"); } }
public class GreenLED:Observer { private bool _on = false; public override void Update() { _on = !_on; Console.WriteLine($"Green LED is {((_on) ? "On" : "Off")}"); } }
public class BlueLED:Observer { private bool _on = false; public override void Update() { _on = !_on; Console.WriteLine($"Blue LED is {((_on) ? "On" : "Off")}"); } }
var greenLed=new GreenLED(); var redLed=new RedLED(); var blueLed=new BlueLED(); var switchKey=new Switch(); switchKey.Attach(greenLed); switchKey.Attach(redLed); switchKey.Attach(blueLed); switchKey.ChangeState = true; switchKey.ChangeState = false;
Green LED is On Red LED is On Blue LED is On Green LED is Off Red LED is Off Blue LED is Off
Soft Delete در Entity Framework 6
A first chance exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll
سؤال: first chance exception چیست؟
وقتی استثنایی در یک برنامه رخ میدهد، به آن یک first chance exception میگویند. این اولین شانسی است که سیستم به شما میدهد تا استثنای رخ داده را مدیریت کنید. اگر کدهای برنامه یا ابزاری (یک try/catch یا دیباگر) این اولین شانس را ندید بگیرند، یک second chance exception رخ میدهد. اینجا است که برنامه به احتمال زیاد خاتمه خواهد یافت.
مشاهدهی پیامهای A first chance exception در پنجرهی output ویژوال استودیو به این معنا است که استثنایی رخ داده، اما توسط یک استثناءگردان مدیریت شدهاست. بنابراین در اکثر موارد، موضوع خاصی نیست و میتوان از آن صرفنظر کرد.
سؤال: چگونه میتوان منشاء اصلی پیام رخدادن یک first chance exception را یافت؟
ویژوال استودیو در پنجرهی output، مدام پیام رخدادن first chance exception را نمایش میدهد؛ اما واقعا کدام قطعه از کدهای برنامه سبب بروز آن شدهاند؟ به صورت پیش فرض صفحهی نمایش استثناءها در VS.NET زمانی نمایان میشود که استثنای رخ داده، مدیریت نشده باشد. برای فعال سازی نمایش استثناهای مدیریت شده باید تنظیمات ذیل را اعمال کرد:
- به منوی Debug | Exceptions مراجعه کنید.
- گره Common Language Runtime Exceptions را باز کنید.
- سپس گروه System آنرا نیز باز کنید.
- در اینجا بر اساس نوع استثنایی که در پنجرهی output نمایش داده میشود، آن استثناء را یافته و Thrown آنرا انتخاب کنید.
اینبار اگر برنامه را اجرا کنید، دقیقا محلی که سبب بروز استثنای ArgumentOutOfRangeException شده در VS.NET گزارش داده خواهد شد.
موجودیتهای زیر را در نظر بگیرید:
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"); } }
turbo.js is a small library that makes it easier to perform complex calculations that can be done in parallel. The actual calculation performed (the kernel executed) uses the GPU for execution. This enables you to work on an array of values all at once.
turbo.js is compatible with all browsers (even IE when not using ES6 template strings) and most desktop and mobile GPUs.
برپایی پروژههای مورد نیاز
ابتدا یک پوشهی جدید را مانند DownloadFilesSample، ایجاد کرده و در داخل آن دستور زیر را اجرا میکنیم:
> dotnet new react
سپس در این پوشه، پوشهی ClientApp پیشفرض آنرا حذف میکنیم؛ چون کمی قدیمی است. همچنین فایلهای کنترلر و سرویس آب و هوای پیشفرض آنرا به همراه پوشهی صفحات Razor آن، حذف میکنیم.
به علاوه بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
spa.UseReactDevelopmentServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
اکنون در ریشهی پروژهی ASP.NET Core ایجاد شده، دستور زیر را صادر میکنیم تا پروژهی کلاینت React را با فرمت جدید آن ایجاد کند:
> create-react-app clientapp
> cd clientapp > npm install --save bootstrap axios
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود که برای افزودن فایل bootstrap.css آن به پروژهی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
- برای دریافت فایلها از سمت سرور، از کتابخانهی معروف axios استفاده خواهیم کرد.
کدهای سمت سرور دریافت فایلهای پویا
در اینجا کدهای سمت سرور برنامه، یک فایل PDF ساده را بازگشت میدهند. این محتوای باینری میتواند حاصل اجرای یک گزارش اکسل، PDF و یا کلا هر نوع فایلی باشد:
using Microsoft.AspNetCore.Mvc; namespace DownloadFilesSample.Controllers { [Route("api/[controller]")] public class ReportsController : Controller { [HttpGet("[action]")] public IActionResult GetPdfReport() { return File(virtualPath: "~/app_data/sample.pdf", contentType: "application/pdf", fileDownloadName: "sample.pdf"); } } }
روش دریافت محتوای باینری در برنامههای React
برای دریافت یک محتوای باینری از سرور توسط axios مانند تصاویر، فایلهای PDF و اکسل و غیره، مهمترین نکته، تنظیم ویژگی responseType آن به blob است:
const getResults = async () => { const { headers, data } = await axios.get(apiUrl, { responseType: "blob" }); }
ساخت URL برای دسترسی به اطلاعات باینری
تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی میکنند. این متد، شیء URL را از شیء window جاری دریافت میکند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید میکند. حاصل آن، یک URL ویژهاست مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
در ادامه کدهای تبدیل blob دریافت شدهی از سرور را به این URL ویژه، مشاهده میکنید:
import axios from "axios"; import React, { useEffect, useState } from "react"; export default function DisplayPdf() { const apiUrl = "https://localhost:5001/api/Reports/GetPdfReport"; const [blobInfo, setBlobInfo] = useState({ blobUrl: "", fileName: "" }); useEffect(() => { getResults(); }, []); const getResults = async () => { try { const { headers, data } = await axios.get(apiUrl, { responseType: "blob" }); console.log("headers", headers); const pdfBlobUrl = window.URL.createObjectURL(data); console.log("pdfBlobUrl", pdfBlobUrl); const fileName = headers["content-disposition"] .split(";") .find(n => n.includes("filename=")) .replace("filename=", "") .trim(); console.log("filename", fileName); setBlobInfo({ blobUrl: pdfBlobUrl, fileName: fileName }); } catch (error) { console.log(error); } };
- توسط useEffect Hook و بدون ذکر وابستگی خاصی در آن، سبب شبیه سازی رویداد componentDidUpdate شدهایم. به این معنا که متد getResults فراخوانی شدهی در آن، پس از رندر کامپوننت در DOM فراخوانی میشود و بهترین محلی است که از آن میتوان برای ارسال درخواستهای Ajaxای به سمت سرور و دریافت اطلاعات از backend، استفاده کرد و سپس setState را با اطلاعات جدید فراخوانی نمود. معادل setState در اینجا نیز، همان شیء حالتی است که توسط useState Hook و متد setBlobInfo آن تعریف کردهایم.
- پس از دریافت headers و data از سرور، با استفاده از متد createObjectURL، آنرا تبدیل به یک blob URL کردهایم.
- همچنین در سمت سرور، پارامتر fileDownloadName را نیز تنظیم کردهایم. این نام در سمت کلاینت، توسط هدری با کلید content-disposition ظاهر میشود:
ontent-disposition: "attachment; filename=sample.pdf; filename*=UTF-8''sample.pdf"
- اکنون که نام فایل و URL دسترسی به دادهی فایل باینری دریافتی از سرور را استخراج و ایجاد کردهایم. با فراخوانی متد setBlobInfo، سبب تنظیم متغیر حالت blobInfo خواهیم شد. این مورد، رندر مجدد UI را سبب شده و توسط آن میتوان برای مثال فایل PDF دریافتی را نمایش داد.
نمایش فایل PDF دریافتی از سرور، به همراه دکمههای دریافت، چاپ و بازکردن آن در برگهای جدید
در ادامه کدهای کامل قسمت رندر این کامپوننت را مشاهده میکنید:
import axios from "axios"; import React, { useEffect, useState } from "react"; export default function DisplayPdf() { // ... const { blobUrl } = blobInfo; return ( <> <h1>Display PDF Files</h1> <button className="btn btn-info" onClick={handlePrintPdf}> Print PDF </button> <button className="btn btn-primary ml-2" onClick={handleShowPdfInNewTab}> Show PDF in a new tab </button> <button className="btn btn-success ml-2" onClick={handleDownloadPdf}> Download PDF </button> <section className="card mb-5 mt-3"> <div className="card-header"> <h4>using iframe</h4> </div> <div className="card-body"> <iframe title="PDF Report" width="100%" height="600" src={blobUrl} type="application/pdf" ></iframe> </div> </section> <section className="card mb-5"> <div className="card-header"> <h4>using object</h4> </div> <div className="card-body"> <object data={blobUrl} aria-label="PDF Report" type="application/pdf" width="100%" height="100%" ></object> </div> </section> <section className="card mb-5"> <div className="card-header"> <h4>using embed</h4> </div> <div className="card-body"> <embed aria-label="PDF Report" src={blobUrl} type="application/pdf" width="100%" height="100%" ></embed> </div> </section> </> ); }
در اینجا با انتساب مستقیم blob URL ایجاد شده، به خواص src و یا data اشیائی مانند iframe ،object و یا embed، میتوان سبب نمایش فایل pdf دریافتی از سرور شد. این نمایش نیز توسط قابلیتهای توکار مرورگر صورت میگیرد و نیاز به نصب افزونهی خاصی را ندارد.
در ادامه کدهای مرتبط با سه دکمهی چاپ، دریافت و بازکردن فایل دریافتی از سرور را مشاهده میکنید.
مدیریت دکمهی چاپ PDF
پس از اینکه به blobUrl دسترسی یافتیم، اکنون میتوان یک iframe مخفی را ایجاد کرد، سپس src آنرا به این آدرس ویژه تنظیم نمود و در آخر متد print آنرا فراخوانی کرد که سبب نمایش خودکار دیالوگ چاپ مرورگر میشود:
const handlePrintPdf = () => { const { blobUrl } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } const iframe = document.createElement("iframe"); iframe.style.display = "none"; iframe.src = blobUrl; document.body.appendChild(iframe); if (iframe.contentWindow) { iframe.contentWindow.print(); } };
مدیریت دکمهی نمایش فایل PDF در یک برگهی جدید
اگر علاقمند بودید تا این فایل PDF را به صورت تمام صفحه و در برگهای جدید نمایش دهید، میتوان از متد window.open استفاده کرد:
const handleShowPdfInNewTab = () => { const { blobUrl } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } window.open(blobUrl); };
مدیریت دکمهی دریافت فایل PDF
بجای نمایش فایل PDF میتوان دکمهای را بر روی صفحه قرار داد که با کلیک بر روی آن، این فایل توسط مرورگر به صورت متداولی جهت دریافت به کاربر ارائه شود:
const handleDownloadPdf = () => { const { blobUrl, fileName } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } const anchor = document.createElement("a"); anchor.style.display = "none"; anchor.href = blobUrl; anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); };
اگر خواستید عملیات axios.get و دریافت فایل، با هم یکی شوند، میتوان متد handleDownloadPdf را پس از پایان کار await axios.get، فراخوانی کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: DownloadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگیهای سمت کلاینت نیز میشود، ابتدا به پوشهی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار میکند. سپس به پوشهی اصلی برنامهی ASP.NET Core بازگشته و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائهی برنامهی React بر روی پورت 5001 میشود.
در پروژههای ویندوزی یکی از بیشترین ابزار کاربردی گریدویو تلریک Telerik
GridView میباشد و اینکه تمامی امکانات گرید مانند گروه بندی ، فیلترینگ و
... همه فارسی باشند خیلی برای پروژه خوب است.
منم در یکی از پروژهها نیاز به فارسی کردن این ابزار پرکاربرد ویندوزی داشتم و توانستم این مورد
را حل کنم . نحوه فارسی کردن این ابزار به شرح ذیل میباشد:
1- یک پروژه جدید ویندوزی در visual studio ایجاد میکنیم
2- اضافه کردن یک radGridView به فرم و خاصیت Dock آن را به حالت Fill و خاصیت RightToLeft را Yes قرار میدهیم :
3- حال برای اینکه یک سری اطلاعاتی داخل این گرید نمایش بدهیم یک کلاس در همان فرم درست میکنیم مشابه کد ذیل :
public List<MyCustomData> GetData() { List<MyCustomData> myList = new List<MyCustomData>(); for (int i = 1; i < 11; i++) { myList.Add(new MyCustomData() { ID = i, Name = "Name Family " + i.ToString(), Age = 29 }); } return myList; } public class MyCustomData { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } public bool Sex { get; set; } }
4 - حال برای اینکه این اطلاعات را در گرید نمایش دهیم کد زیر را در بخش Load_Form1 مینویسیم :
private void Form1_Load(object sender, EventArgs e) { radGridView1.DataSource = GetData(); }
6 - برای اینکه این موارد فارسی شوند نیاز به یک کلاس یا Provider داریم که این عمل ترجمه را انجام دهد که حتی در سایت خود تلریک در بخش مربوطه نیز ارائه شده است. بنده این کلاس را کپی کرده و تمامی ترجمههای آنها را نیز نوشتم ( اگر در ترجمه ایرادی بود به بزرگی خودتان بخشیده و تصحیح نمائید . ) که کد آن را در زیر میتوانید در اختیار داشته باشید:
class PersianRadGridLocalizationProvider : RadGridLocalizationProvider { public override string GetLocalizedString(string id) { switch (id) { case RadGridStringId.FilterFunctionBetween: return "بین"; //Between case RadGridStringId.FilterOperatorBetween: return "بین"; case RadGridStringId.FilterFunctionContains: return "حاوی"; case RadGridStringId.FilterOperatorContains: return "حاوی"; case RadGridStringId.FilterFunctionDoesNotContain: return "شامل نشود"; //Does not contain case RadGridStringId.FilterOperatorDoesNotContain: return "شامل نشود"; case RadGridStringId.FilterFunctionEndsWith: return "پایان پذیرد با"; //Ends with case RadGridStringId.FilterOperatorEndsWith: return "پایان پذیرد با"; case RadGridStringId.FilterFunctionEqualTo: return "برابر با"; //Equals case RadGridStringId.FilterOperatorEqualTo: return "برابر با"; case RadGridStringId.FilterFunctionGreaterThan: return "بزرگتر از"; //Greater than case RadGridStringId.FilterOperatorGreaterThan: return "بزرگتر از"; case RadGridStringId.FilterFunctionGreaterThanOrEqualTo: return "بزرگتر یا مساوی با"; //Greater than or equal to case RadGridStringId.FilterOperatorGreaterThanOrEqualTo: return "بزرگتر یا مساوی با"; case RadGridStringId.FilterFunctionIsEmpty: return "خالی باشد"; //Is empty case RadGridStringId.FilterOperatorIsEmpty: return "خالی باشد"; case RadGridStringId.FilterFunctionIsNull: return "تهی باشد"; //Is null case RadGridStringId.FilterOperatorIsNull: return "تهی باشد"; case RadGridStringId.FilterFunctionLessThan: return "کمتر از"; //Less than case RadGridStringId.FilterOperatorLessThan: return "کمتر از"; case RadGridStringId.FilterFunctionLessThanOrEqualTo: return "کمتر یا مساوی با"; //Less than or equal to case RadGridStringId.FilterOperatorLessThanOrEqualTo: return "کمتر یا مساوی با"; case RadGridStringId.FilterFunctionNoFilter: return "بدون شرط"; //No filter case RadGridStringId.FilterOperatorNoFilter: return "بدون شرط"; case RadGridStringId.FilterFunctionNotBetween: return "نباشد بین"; //Not between case RadGridStringId.FilterOperatorNotBetween: return "نباشد بین"; //Operator case RadGridStringId.FilterFunctionNotEqualTo: return "برابر نباشد با"; //Not equal to case RadGridStringId.FilterOperatorNotEqualTo: return "برابر نباشد با"; case RadGridStringId.FilterFunctionNotIsEmpty: return "خالی نباشد"; //Is not empty case RadGridStringId.FilterFunctionNotIsNull: return "خالی نباشد"; //Is not null case RadGridStringId.FilterFunctionStartsWith: return "شروع شود با"; //Starts with case RadGridStringId.FilterFunctionCustom: return "شرط دلخواه"; //Custom case RadGridStringId.CustomFilterMenuItem: return "شرط دلخواه منو"; //Custom case RadGridStringId.CustomFilterDialogCaption: return "انتخاب شرط دلخواه"; //RadGridView Custom Filter Dialog case RadGridStringId.CustomFilterDialogLabel: return ":نشان دادن سطرهایی که"; //Show rows where: case RadGridStringId.CustomFilterDialogRbAnd: return "و"; //And case RadGridStringId.CustomFilterDialogRbOr: return "یا"; //Or case RadGridStringId.CustomFilterDialogBtnOk: return "تایید"; //OK case RadGridStringId.CustomFilterDialogBtnCancel: return "انصراف"; //Cancel case RadGridStringId.AddNewRowString: return "برای افزودن سطر جدید اینجا کلیک کنید"; case RadGridStringId.ClearValueMenuItem: return "پاک کردن مقدار سلول"; case RadGridStringId.DeleteRowMenuItem: return "حذف سطر"; //Delete Row case RadGridStringId.SortAscendingMenuItem: return "مرتب سازی صعودی"; //Sort Ascending case RadGridStringId.SortDescendingMenuItem: return "مرتب سازی نزولی"; //Sort Descending case RadGridStringId.ClearSortingMenuItem: return "حذف مرتب سازی"; //Clear Sorting case RadGridStringId.ConditionalFormattingMenuItem: return "قالب بندی مشروط"; //Conditional Formatting case RadGridStringId.GroupByThisColumnMenuItem: return "گروهبندی بر حسب این ستون"; //Group by this column case RadGridStringId.UngroupThisColumn: return "حذف این ستون از گروهبندی "; //Ungroup this column case RadGridStringId.ColumnChooserMenuItem: return "انتخابگر ستون"; //Column Chooser case RadGridStringId.HideMenuItem: return "مخفی کردن ستون"; //Hide case RadGridStringId.UnpinMenuItem: return "حالت پیش فرض"; //Unpin case RadGridStringId.PinMenuItem: return "حالت ستون"; //Pin case RadGridStringId.PinAtLeftMenuItem: return "چسپیدن به سمت چپ"; case RadGridStringId.PinAtRightMenuItem: return "چسپیدن به سمت راست"; case RadGridStringId.PinAtTopMenuItem: return "چسپیدن به بالا"; case RadGridStringId.PinAtBottomMenuItem: return "چسپیدن به پایین"; case RadGridStringId.BestFitMenuItem: return "اندازه بهینه ستون"; //Best Fit case RadGridStringId.PasteMenuItem: return "چسپاندن"; //Paste case RadGridStringId.EditMenuItem: return "ویرایش"; //Edit case RadGridStringId.CopyMenuItem: return "کپی"; //Copy case RadGridStringId.ConditionalFormattingCaption: return "قالب بندی مشروط"; //Custom Formatting Condition Editor case RadGridStringId.ConditionalFormattingLblColumn: return "قالب بندی سلولهایی با شرط:"; //Column: case RadGridStringId.ConditionalFormattingLblName: return "نام شرط:"; //Name: case RadGridStringId.ConditionalFormattingLblType: return "مقدار سلول:"; //Type: case RadGridStringId.ConditionalFormattingLblValue1: return "مقدار اول:"; //Value 1: case RadGridStringId.ConditionalFormattingLblValue2: return "مقدار دوم:"; //Value 2: case RadGridStringId.ConditionalFormattingGrpConditions: return "شرایط"; //Conditions case RadGridStringId.ConditionalFormattingGrpProperties: return "مشخصات"; //Properties case RadGridStringId.ConditionalFormattingChkApplyToRow: return "اعمال این شرط به کل سطر"; //Apply to row case RadGridStringId.ConditionalFormattingBtnAdd: return "افزودن شرایط"; //Add case RadGridStringId.ConditionalFormattingBtnRemove: return "حذف شرایط انتخابی"; //Remove case RadGridStringId.ConditionalFormattingBtnOK: return "تایید"; //OK case RadGridStringId.ConditionalFormattingBtnCancel: return "انصراف"; //Cancel case RadGridStringId.ConditionalFormattingBtnApply: return "اعمال قالب بندی"; //Apply case RadGridStringId.ColumnChooserFormCaption: return "انتخاب ستون ها"; //Column Chooser case RadGridStringId.ColumnChooserFormMessage: return "برای حذف یکی از ستونها، آن ستون را به اینجا بکشید";//"Drag a column header from the grid here to remove it from the current view."; case RadGridStringId.CompositeFilterFormErrorCaption: return "خطا"; case RadGridStringId.ConditionalFormattingChooseOne: return "[یکی را انتخاب کنید]"; case RadGridStringId.ConditionalFormattingContains: return "[حاوی [مقدار اول"; case RadGridStringId.ConditionalFormattingDoesNotContain: return "حاوی [مقدار اول] نباشد"; case RadGridStringId.ConditionalFormattingEndsWith: return "با [مقدار اول] پایان یابد"; case RadGridStringId.ConditionalFormattingEqualsTo: return "[برابر با [مقدار اول"; case RadGridStringId.ConditionalFormattingIsBetween: return "بین [مقدار اول] و [مقدار دوم] باشد"; case RadGridStringId.ConditionalFormattingIsGreaterThan: return "[بزرگتر از [مقدار اول"; case RadGridStringId.ConditionalFormattingIsGreaterThanOrEqual: return "[بزرگتر یا مساوی با [مقدار اول"; case RadGridStringId.ConditionalFormattingIsLessThan: return "کوچکتر از [مقدار اول]"; case RadGridStringId.ConditionalFormattingIsLessThanOrEqual: return "کوچکتر یا مساوی با [مقدار اول]"; case RadGridStringId.ConditionalFormattingIsNotBetween: return "بین [مقدار اول] و [مقدار دوم] نباشد"; case RadGridStringId.ConditionalFormattingIsNotEqualTo: return "برابر با [مقدار اول] نباشد"; case RadGridStringId.ConditionalFormattingRuleAppliesOn: return "اعمال شرایط روی:"; case RadGridStringId.ConditionalFormattingStartsWith: return "با [مقدار اول] شروع میشود"; case RadGridStringId.CustomFilterDialogCheckBoxNot: return "با این شرایط نباشد"; case RadGridStringId.CustomFilterDialogFalse: return "False"; case RadGridStringId.CustomFilterDialogTrue: return "True"; case RadGridStringId.FilterCompositeNotOperator: return "نباشد"; case RadGridStringId.FilterLogicalOperatorAnd: return "و"; case RadGridStringId.FilterLogicalOperatorOr: return "یا"; case RadGridStringId.FilterMenuAvailableFilters: return "فیلتر شده"; case RadGridStringId.FilterMenuButtonCancel: return "انصراف"; case RadGridStringId.FilterMenuButtonOK: return "تایید"; case RadGridStringId.FilterMenuClearFilters: return "پاک کردن فیلتر"; case RadGridStringId.FilterMenuSearchBoxText: return "جستجو..."; case RadGridStringId.FilterMenuSelectionAll: return "همه"; //case RadGridStringId.FilterMenuSelectionAllSearched: return "نتیجه همه جستجو"; case RadGridStringId.FilterMenuSelectionNotNull: return "خالی نباشد"; case RadGridStringId.FilterMenuSelectionNull: return "خالی باشد"; case RadGridStringId.FilterOperatorCustom: return "دلخواه"; case RadGridStringId.FilterOperatorIsLike: return "مانند"; case RadGridStringId.FilterOperatorNotIsContainedIn: return "نباشد در"; case RadGridStringId.FilterOperatorNotIsEmpty: return "خالی نباشد"; case RadGridStringId.FilterOperatorNotIsLike: return "نباشد شبیه"; case RadGridStringId.FilterOperatorNotIsNull: return "خالی نباشد"; case RadGridStringId.FilterOperatorStartsWith: return "شروع شود با"; case RadGridStringId.GroupingPanelDefaultMessage: return "برای گروهبندی ستونها، ستونی را به اینجا بکشید"; case RadGridStringId.GroupingPanelHeader: return ":گروهبندی بر حسب"; case RadGridStringId.NoDataText: return "داده ای برای نمایش وجود ندارد"; case RadGridStringId.UnpinRowMenuItem: return "حالت پیش فرض"; default: return base.GetLocalizedString(id); } } }
7 - حال اگر برنامه را اجرا کنید باز موارد انگلیسی گرید تلریک فارسی نمیشوند و باید در کلاس Program.cs پروژه این یک خط کد را هم اضافه نمائید.
//using Telerik.WinControls.UI.Localization; RadGridLocalizationProvider.CurrentProvider = new PersianRadGridLocalizationProvider();
8 - حال اگر برنامه را اجرا نمایید تمامی موارد را فارسی مشاهده خواهید نمود ( شکل ذیل )
لطفا ما را از نظرات سازنده خود بی نصیب نفرمائید. با تشکر