- Creating an IdentityServer 5 Project
- Adding JWT Bearer Authentication to an ASP.NET Core 5 API
- Adding Policy-Based Authorisation to an ASP.NET Core 5 API
- Authenticating a .NET 5 Console Application using IdentityServer 5
- Adding API Resources to IdentityServer 5
- Containerising a PostgreSQL 13 Database
- Adding Entity Framework Core 5 to IdentityServer 5
- Adding an OAuth 2.0 Security Scheme to an ASP.NET Core 5 API
- Adding ASP.NET Core 5 Identity to IdentityServer 5
- Seeding an ASP.NET Core 5 Identity Database with Users
- Authenticating an ASP.NET Core 5 Web Application using IdentityServer 5
- Authenticating an Angular 11 Single-Page Application using IdentityServer 5
- Authenticating a Flutter Android Application using IdentityServer 5
- Containerising an IdentityServer 5 Project
- Containerising an IdentityServer 5 Project with API Resources and Clients
public async Task<IActionResult> FileUpload(IFormFile file) { if (file == null || file.Length == 0) { return BadRequest(); } using (var memoryStream = new MemoryStream()) { await file.CopyToAsync(memoryStream); var fileBytes = memoryStream.ToArray(); // ... save it } }
using System.Windows.Forms; public static class GlobalData { public static Form ScheduleForm { get; set; } }
GlobalData.ScheduleForm = this;
namespace SchedulerDemo.Jobs { using System.Linq; using System.Windows.Forms; using Quartz; public class HelloJob : IJob { private delegate void ButtonTextWriter(string buttonId, string text); MainForm form = GlobalData.ScheduleForm as MainForm; private void SetButtonText(string buttonId, string text) { (form.Controls.Find(buttonId, true).FirstOrDefault() as Button).Text = text; } public void Execute(IJobExecutionContext context) { form.BeginInvoke(new ButtonTextWriter(SetButtonText), new object[] { "myButton", "My Text" }); } } }
یافتن سرویسهایی که به اشتباه بیش از یکبار ثبت شدهاند
کلاس زیر متدهایی را برای جمع آوری و گزارش سرویسهایی که تکراری ثبت شدهاند، ارائه میدهد:
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; namespace FindDuplicateServices.Utils { public static class DuplicateServicesFinder { private static List<(Type ServiceType, int RegistrationTimes)> _duplicateServices; public static IHostBuilder CountDuplicateServices(this IHostBuilder hostBuilder) { hostBuilder.ConfigureServices(services => { _duplicateServices = services.Where( serviceDescriptor => !serviceDescriptor.ServiceType.Assembly.FullName.Contains("Microsoft")) .GroupBy(serviceDescriptor => serviceDescriptor.ServiceType) .Where(g => g.Count() > 1) .Select(g => (g.Key, g.Count())) .ToList(); }); return hostBuilder; } public static IHost ReportDuplicateServices(this IHost host) { var logger = host.Services.GetRequiredService<ILogger<Program>>(); _duplicateServices.ForEach(item => logger.LogWarning($"Service Type: `{item.ServiceType}` -> Registration times: {item.RegistrationTimes}")); return host; } public static void RemoveService(this IServiceCollection services, Type serviceType) { var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == serviceType); if (serviceDescriptor != null) { services.Remove(serviceDescriptor); } } } }
سپس متد الحاقی ReportDuplicateServices که پس از متد Build در متد Main برنامه فراخوانی میشود، این سرویسها را توسط لاگر جاری، نمایش میدهد:
using FindDuplicateServices.Utils; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace FindDuplicateServices { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().ReportDuplicateServices().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .CountDuplicateServices(); } }
برای مثال اگر سرویس فرضی IWeatherForecastService دوبار ثبت شده باشد:
namespace FindDuplicateServices { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddScoped<IWeatherForecastService, WeatherForecastService>(); services.AddScoped<IWeatherForecastService, WeatherForecastService>(); }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: FindDuplicateServices.zip
افزودن اسمبلیهای مورد نیاز به مجموع Referencesها
کد استفاده شده جهت طراحی گزارش
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using FastReport; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string[] List = System.IO.File.ReadAllLines("List.TXT"); var Query = from list in List let items = list.Split(',') select new { Id = Convert.ToInt32(items[0]), FName = items[1], LName = items[2] }; using (Report report = new Report()) { report.RegisterData(Query.ToList(), "myQuery"); report.Design(); } } } }
نمایش گزارش ذخیره شده در List.frx با استفاده از کد زیر
report.Load("List.frx"); report.Show();
خروجی گزارش ساخته شده
سورس برنامه نمونه
ثبت رکورد جدید به جای بروزرسانی آن
public UpdatePostStatus UpdatePost(EditPostModel postModel) { Post selectedPost = _posts.Find(postModel.PostId); int count = selectedPost.Labels.Count; for (int i = 0; i < count; i++) { selectedPost.Labels.Remove(selectedPost.Labels.ElementAt(i)); count--; } selectedPost.Labels = postModel.Labels; selectedPost.DownloadLinks = postModel.DownloadLinks; selectedPost.Body = postModel.PostBody; selectedPost.Book.Author = postModel.Book.Author; selectedPost.Book.ISBN = postModel.Book.ISBN; selectedPost.Book.Language = postModel.Book.Language; selectedPost.Book.Name = postModel.Book.Name; selectedPost.Book.Page = postModel.Book.Page; selectedPost.Book.Year = postModel.Book.Year; selectedPost.Book.Description = postModel.Book.Description; selectedPost.Book.Publisher = postModel.Book.Publisher; selectedPost.Book.Image.Description = postModel.BookImage.Description; selectedPost.Book.Image.Path = postModel.BookImage.Path; selectedPost.Book.Image.Title = postModel.BookImage.Title; selectedPost.CommentStatus = postModel.PostCommentStatus; selectedPost.Description = postModel.PostDescription; selectedPost.Keyword = postModel.PostKeyword; selectedPost.ModifiedDate = postModel.ModifiedDate; selectedPost.Status = postModel.PostStatus; selectedPost.Title = postModel.PostTitle; return UpdatePostStatus.Successfull; }
استفاده از StructureMap به عنوان یک IoC Container
دریافت StructureMap
برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نیوگت ویژوال استودیو فراخوانی کنید:
PM> Install-Package structuremap
آشنایی با ساختار برنامه
ابتدا یک برنامه کنسول را آغاز کرده و سپس یک Class library جدید را به نام Services نیز به آن اضافه کنید. در ادامه کلاسها و اینترفیسهای زیر را به Class library ایجاد شده، اضافه کنید. سپس از طریق نیوگت به روشی که گفته شد، StructureMap را به پروژه اصلی (ونه پروژه Class library) اضافه نمائید و Target framework آنرا نیز در حالت Full قرار دهید بجای حالت Client profile.
namespace DI03.Services { public interface IUsersService { string GetUserEmail(int userId); } } namespace DI03.Services { public interface IEmailsService { void SendEmailToUser(int userId, string subject, string body); } } using System; namespace DI03.Services { public class UsersService : IUsersService { public UsersService() { //هدف صرفا نمایش وهله سازی خودکار این وابستگی است Console.WriteLine("UsersService ctor."); } public string GetUserEmail(int userId) { //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه return "name@site.com"; } } } using System; namespace DI03.Services { public class EmailsService: IEmailsService { private readonly IUsersService _usersService; public EmailsService(IUsersService usersService) { Console.WriteLine("EmailsService ctor."); _usersService = usersService; } public void SendEmailToUser(int userId, string subject, string body) { var email = _usersService.GetUserEmail(userId); Console.WriteLine("SendEmailTo({0})", email); } } }
سرویس کاربران بر اساس آی دی یک کاربر، برای مثال از بانک اطلاعاتی ایمیل او را بازگشت میدهد. سرویس ارسال ایمیل، نیاز به ایمیل کاربری برای ارسال ایمیلی به او دارد. بنابراین وابستگی مورد نیاز خود را از طریق تزریق وابستگیها در سازنده کلاس و وهله سازی شده در خارج از آن (معکوس سازی کنترل)، دریافت میکند.
در سازندههای هر دو کلاس سرویس نیز از Console.WriteLine استفاده شدهاست تا زمان وهله سازی خودکار آنها را بتوان بهتر مشاهده کرد.
نکته مهمی که در اینجا وجود دارد، بیخبری لایه سرویس از وجود IoC Container مورد استفاده است.
استفاده از لایه سرویس و تزریق وابستگیها به کمک StructureMap
using DI03.Services; using StructureMap; namespace DI03 { class Program { static void Main(string[] args) { // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود ObjectFactory.Initialize(x => { x.For<IEmailsService>().Use<EmailsService>(); x.For<IUsersService>().Use<UsersService>(); }); //نمونهای از نحوه استفاده از تزریق وابستگیهای خودکار var emailsService = ObjectFactory.GetInstance<IEmailsService>(); emailsService.SendEmailToUser(userId: 1, subject: "Test", body: "Hello!"); } } }
به این ترتیب IoC Container ما زمانیکه قرار است object graph مربوط به IEmailsService درخواستی را تشکیل دهد، خواهد دانست ابتدا به سازندهی کلاس EmailsService میرسد. در اینجا برای وهله سازی این کلاس به صورت خودکار، باید وابستگیهای آنرا نیز وهله سازی کند. بنابراین بر اساس تنظیمات آغازین برنامه میداند که باید از کلاس UsersService برای تزریق خودکار وابستگیها در سازنده کلاس ارسال ایمیل استفاده نماید.
در این حالت اگر برنامه را اجرا کنیم، به خروجی زیر خواهیم رسید:
UsersService ctor. EmailsService ctor. SendEmailTo(name@site.com)
ابتداییترین مزیت استفاده از تزریق وابستگیها امکان تعویض آنها است؛ خصوصا در حین Unit testing. اگر کلاسی برای مثال قرار است با شبکه کار کند، میتوان پیاده سازی آنرا با یک نمونه اصطلاحا Fake جایگزین کرد و در این نمونه تنها نتیجهی کار را بازگشت داد. کلاسهای لایه سرویس ما تنها با اینترفیسها کار میکنند. این تنظیمات قابل تغییر اولیه IoC container مورد استفاده هستند که مشخص میکنند چه کلاسهایی باید در سازندههای کلاسها تزریق شوند.
تعیین طول عمر اشیاء در StructureMap
برای اینکه بتوان طول عمر اشیاء را بهتر توضیح داد، کلاس سرویس کاربران را به نحو زیر تغییر دهید:
using System; namespace DI03.Services { public class UsersService : IUsersService { private int _i; public UsersService() { //هدف صرفا نمایش وهله سازی خودکار این وابستگی است Console.WriteLine("UsersService ctor."); } public string GetUserEmail(int userId) { _i++; Console.WriteLine("i:{0}", _i); //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه return "name@site.com"; } } }
//نمونهای از نحوه استفاده از تزریق وابستگیهای خودکار var emailsService1 = ObjectFactory.GetInstance<IEmailsService>(); emailsService1.SendEmailToUser(userId: 1, subject: "Test1", body: "Hello!"); var emailsService2 = ObjectFactory.GetInstance<IEmailsService>(); emailsService2.SendEmailToUser(userId: 1, subject: "Test2", body: "Hello!");
UsersService ctor. EmailsService ctor. i:1 SendEmailTo(name@site.com) UsersService ctor. EmailsService ctor. i:1 SendEmailTo(name@site.com)
اگر به هر دلیلی نیاز بود تا این رویه تغییر کند، میتوان بر روی طول عمر اشیاء تشکیل شده نیز تاثیر گذار بود. برای مثال تنظیمات آغازین برنامه را به نحو ذیل تغییر دهید:
// تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود ObjectFactory.Initialize(x => { x.For<IEmailsService>().Use<EmailsService>(); x.For<IUsersService>().Singleton().Use<UsersService>(); });
UsersService ctor. EmailsService ctor. i:1 SendEmailTo(name@site.com) EmailsService ctor. i:2 SendEmailTo(name@site.com)
حالتهای دیگر تعیین طول عمر مطابق متدهای زیر هستند:
Singleton() HttpContextScoped() HybridHttpOrThreadLocalScoped()
در حالت ThreadLocal، به ازای هر Thread، وهلهای متفاوت در اختیار مصرف کننده قرار میگیرد.
حالت Hybrid ترکیبی است از حالتهای HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ میکند.
شاید بپرسید که کاربرد مثلا HttpContextScoped در کجا است؟
در یک برنامه وب نیاز است تا یک وهله از DbContext (مثلا Entity framework) را در اختیار کلاسهای مختلف لایه سرویس قرار داد. به این ترتیب چون هربار new Context صورت نمیگیرد، هربار هم اتصال جداگانهای به بانک اطلاعاتی باز نخواهد شد. نتیجه آن رسیدن به یک برنامه سریع، با سربار کم و همچنین کار کردن در یک تراکنش واحد است. چون هربار فراخوانی new Context به معنای ایجاد یک تراکنش جدید است.
همچنین در این برنامه وب قصد نداریم از حالت طول عمر Singleton استفاده کنیم، چون در این حالت یک وهله از Context در اختیار تمام کاربران سایت قرار خواهد گرفت (و DbContext به صورت Thread safe طراحی نشده است). نیاز است به ازای هر کاربر و به ازای طول عمر هر درخواست، تنها یکبار این وهله سازی صورت گیرد. بنابراین در این حالت استفاده از HttpContextScoped توصیه میشود. به این ترتیب در طول عمر کوتاه Object graphهای تشکیل شده، فقط یک وهله از DbContext ایجاد و استفاده خواهد شد که بسیار مقرون به صرفه است.
مزیت دیگر مشخص سازی طول عمر به نحو HttpContextScoped، امکان Dispose خودکار آن به صورت زیر است:
protected void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); }
تنظیمات خودکار اولیه در StructureMap
اگر نام اینترفیسهای شما فقط یک I در ابتدا بیشتر از نام کلاسهای متناظر با آنها دارد، مثلا مانند ITest و کلاس Test هستند؛ فقط کافی است از قراردادهای پیش فرض StructureMap برای اسکن یک یا چند اسمبلی استفاده کنیم:
// تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود ObjectFactory.Initialize(x => { //x.For<IEmailsService>().Use<EmailsService>(); //x.For<IUsersService>().Singleton().Use<UsersService>(); x.Scan(scan => { scan.AssemblyContainingType<IEmailsService>(); scan.WithDefaultConventions(); }); });
دریافت مثال قسمت جاری
DI03.zip
به روز شدهی این مثالها را بر اساس آخرین تغییرات وابستگیهای آنها از مخزن کد ذیل میتوانید دریافت کنید:
Dependency-Injection-Samples
بررسی مقدمات کتابخانهی JSON.NET
استفاده از استریمها برای کار با فایلها در JSON.NET
public static T DeserializeFromFile<T>(string filePath, JsonSerializerSettings settings = null) { if (!File.Exists(filePath)) return default(T); using (var fileStream = File.OpenRead(filePath)) { using (var streamReader = new StreamReader(fileStream)) { using (var reader = new JsonTextReader(streamReader)) { var serializer = settings == null ? JsonSerializer.Create() : JsonSerializer.Create(settings); return serializer.Deserialize<T>(reader); } } } } public static void SerializeToFile(string filePath, object data, JsonSerializerSettings settings = null) { using (var fileStream = new FileStream(filePath, FileMode.Create)) { using (var streamReader = new StreamWriter(fileStream)) { using (var reader = new JsonTextWriter(streamReader)) { var serializer = settings == null ? JsonSerializer.Create() : JsonSerializer.Create(settings); serializer.Serialize(reader, data); } } } }
اگر ViewModel را همان فایل code behind عاری از ارجاعاتی به اشیاء بصری بدانیم، یک تفاوت مهم را علاوه بر مورد ذکر شده نسبت به Code behind متداول خواهد داشت: وهله سازی آن باید دستی انجام شود و خودکار نیست.
اگر به ابتدای کلاسهای code behind دقت کنید همیشه واژهی partial قابل رویت است، به این معنا که این کلاس در حقیقت جزئی از همان کلاس متناظر با XAML ایی است که مشاهده میکنید؛ یا به عبارتی با آن یکی است. فقط جهت زیبایی یا مدیریت بهتر، در دو کلاس قرار گرفتهاند اما واژه کلیدی partial اینها را نهایتا به صورت یکسان و یکپارچهای به کامپایلر معرفی خواهد کرد. بنابراین وهله سازی code behind هم خودکار خواهد بود و به محض نمایش رابط کاربری، فایل code behind آن هم وهله سازی میشود؛ چون اساسا و در پشت صحنه، از دیدگاه کامپایلر تفاوتی بین این دو وجود ندارد.
اکنون سؤال اینجا است که آیا میتوان با ViewModel ها هم همین وهله سازی خودکار را به محض نمایش یک View متناظر، پیاده سازی کرد؟
البته صحیح آن این است که عنوان شود ViewModel متناظر با یک View و نه برعکس. چون روابط در الگوی MVVM از View به ViewModel به Model است و نه حالت عکس؛ مدل نمیداند که ViewModel ایی وجود دارد. ViewModel هم از وجود View ها در برنامه بیخبر است و این «بیخبریها» اساس الگوهایی مانند MVC ، MVVM ، MVP و غیره هستند. به همین جهت شاعر در وصف ViewModel فرمودهاند که:
پاسخ:
بله. برای این منظور الگوی دیگری به نام ViewModel Locator طراحی شده است؛ روشهای زیادی برای پیاده سازی این الگو وجود دارند که سادهترین آنها مورد زیر است:
فرض کنید ViewModel ساده زیر را قصد داریم به کمک الگوی ViewModel Locator به View ایی تزریق کنیم:
namespace WpfViewModelLocator.ViewModels
{
public class MainWindowViewModel
{
public string SomeText { set; get; }
public MainWindowViewModel()
{
SomeText = "Data ...";
}
}
}
برای این منظور ابتدا کلاس ViewModelLocatorBase زیر را تدارک خواهیم دید:
using WpfViewModelLocator.ViewModels;
namespace WpfViewModelLocator.ViewModelLocator
{
public class ViewModelLocatorBase
{
public MainWindowViewModel MainWindowVm
{
get { return new MainWindowViewModel(); }
}
}
}
در اینجا یک وهله از کلاس MainWindowViewModel توسط خاصیتی به نام MainWindowVm در دسترس قرار خواهد گرفت. برای اینکه بتوان این کلاس را در تمام Viewهای برنامه قابل دسترسی کنیم، آنرا در App.Xaml تعریف خواهیم کرد:
<Application x:Class="WpfViewModelLocator.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vml="clr-namespace:WpfViewModelLocator.ViewModelLocator"
StartupUri="MainWindow.xaml">
<Application.Resources>
<vml:ViewModelLocatorBase x:Key="ViewModelLocatorBase" />
</Application.Resources>
</Application>
اکنون فقط کافی است در View خود DataContext را به نحو زیر مقدار دهی کنیم تا در زمان اجرا به صورت خودکار بتوان به خاصیت MainWindowVm یاد شده دسترسی یافت:
<Window x:Class="WpfViewModelLocator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid DataContext="{Binding Path=MainWindowVm, Source={StaticResource ViewModelLocatorBase}}">
<TextBlock Text="{Binding SomeText}" VerticalAlignment="Top" Margin="5" />
</Grid>
</Window>
در مورد ViewModel ها و Viewهای دیگر هم به همین ترتیب خواهد بود. یک وهله از آنها به کلاس ViewModelLocatorBase اضافه میشود. سپس Binding Path مرتبط به DataContext به نام خاصیتی که در کلاس ViewModelLocatorBase مشخص خواهیم کرد، Bind خواهد شد.
روش دوم:
اگر در اینجا بخواهیم Path را حذف کنیم و فقط دسترسی عمومی به ViewModelLocatorBase را ذکر کنیم، باید یک Converter نوشت (چون به این ترتیب میتوان به اطلاعات Binding در متد Convert دسترسی یافت). سپس یک قرار داد را هم تعریف خواهیم کرد؛ به این صورت که ما در Converter به نام View دسترسی پیدا میکنیم (از طریق ریفلکشن). سپس نام viewModel ایی را که باید به دنبال آن گشت مثلا ViewName به علاوه کلمه ViewModel در نظر خواهیم گرفت. در حقیقت یک نوع Convection over configuration است:
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace WpfViewModelLocator.ViewModelLocator
{
public class ViewModelLocatorBaseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//مقدار در اینجا همان مشخصات ویوو است
if (value == null) return null;
string viewTypeName = value.GetType().Name;
//قرار داد ما است
//ViewModel Name = ViewName + "ViewModel"
string viewModelName = string.Concat(viewTypeName, "ViewModel");
//یافتن اسمبلی که حاوی ویوو مدل ما است
var asms = AppDomain.CurrentDomain.GetAssemblies();
var viewModelAsmName = "WpfViewModelLocator"; //نام پروژه مرتبط
var viewModelAsm = asms.Where(x => x.FullName.Contains(viewModelAsmName)).First();
//یافتن این کلاس ویوو مدل مرتبط
var viewModelType = viewModelAsm.GetTypes().Where(x => x.FullName.Contains(viewModelName)).FirstOrDefault();
if (viewModelType == null)
throw new InvalidOperationException(string.Format("Could not find view model '{0}'", viewModelName));
//وهله سازی خودکار آن
return Activator.CreateInstance(viewModelType);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
کار این تبدیلگر بسیار ساده و واضح است. Value دریافتی، وهلهای از view است. پس به این ترتیب میتوان نام آنرا یافت. سپس قرارداد ویژه خودمان را اعمال میکنیم به این ترتیب که ViewModel Name = ViewName + "ViewModel" و سپس به دنبال اسمبلی که حاوی این نام است خواهیم گشت. آنرا یافته، کلاس مرتبط را در آن پیدا میکنیم و در آخر، به صورت خودکار آنرا وهله سازی خواهیم کرد.
اینبار تعریف عمومی این Conveter در فایل App.Xaml به صورت زیر خواهد بود:
<Application x:Class="WpfViewModelLocator.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vml="clr-namespace:WpfViewModelLocator.ViewModelLocator"
StartupUri="MainWindow.xaml">
<Application.Resources>
<vml:ViewModelLocatorBaseConverter x:Key="ViewModelLocatorBaseConverter" />
</Application.Resources>
</Application>
و استفادهی آن در تمام View های برنامه به شکل زیر میباشد (بدون نیاز به ذکر هیچ نام خاصی و بدون نیاز به کلاس ViewModelLocatorBase یاد شده در ابتدای مطلب):
<Window x:Class="WpfViewModelLocator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource ViewModelLocatorBaseConverter}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Text="{Binding SomeText}" VerticalAlignment="Top" Margin="5" />
</Grid>
</Window>
فرض کنید دارید در پروژه، از Entity Framework استفاده میکنید و یک مدل با نام Person دارید که تعریفش به صورت زیر است
public class Person { public int PersonId { get; set; } public string Name { get; set; } }
حالا میخوایم تعداد ٥٠٠٠ رکورد از Person رو یکجا وارد دیتابیس کنیم. برای استفاده از SqlBulkCopy، روش به این شکل هست که ابتدا یکDataTable ایجاد میکنیم. سپس ستونهای متناظر با جدول Person رو با استفاده از DataColumn ایجاد میکنیم و DataColumnهای ایجاد شده رو به DataTable اضافه میکنیم و سپس اطلاعات رو وارد DataTable میکنیم و اون رو با استفاده از SqlBulkCopy وارد دیتابیس میکنیم که این روش یکم وقتگیر و خسته کننده است.
راه آسانتر استفاده از یک کتابخانه با نام EntityDataReader هست که توسط مایکروسافت نوشته شده که دیگه نیازی به ساختنDataTable نیست و این کتابخانه کارهای لازم رو خودش انجام میده. در پروژەتون یک کلاس با نامEntityDataReader ایجاد کنید و سورس مربوط این کلاس رو از اینجا copy و در داخل کلاس paste کنید.
حالا یک لیست از Pesron با نام personList ایجاد مینماییم و با استفاده از یک حلقه تعداد ٥٠٠٠ تا نمونه از Person ایجاد و به لیست اضافه میکنیم.
var personList = new List<Person>(); for (var i = 0; i < 5000; i++) { var person = new Person { Name = "John Doe", }; }
در ادامه برای استفاده از SqlBulkCopy نیاز به ConnectionString و نام جدول متناظر با کلاس Person در دیتابیس داریم.
اگر از پروژ وب استفاده میکنید میتونید با این خط کد ConnectionString رو که در فایل web.config ذخیره شده است بروگردونید که در اینجا DataConnection نام ConnectionString ذخیره شده در web.config هست.
var connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString;
اگر از EF Code First استفاده میکنید و در تنظیمات Context خاصیت PluralizingTableNameConvention رو حذف کردیدەاید نام جدول dbo.Person هست و در غیر اینصورت db.People هست.
و در ادامه داریم:
var connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString; var bulkCopy = new SqlBulkCopy(connectionString) { DestinationTableName = "dbo.Person" }; bulkCopy.WriteToServer(personList.AsDataReader() );
سرعت این روش بسیار بالاست و برای درجهای با تعداد بالا بهینه است.
برای ویرایش و حذف چندین رکورد بصورت همزمان متیونید از کتابخانه Entity Framework Extended Library استفاده کنید که امکانات دیگری هم داره و از طریق nuget هم قابل نصب است.