مطالب
OneNote و مصرف بالای RAM و CPU

از برنامه OneNote زیاد استفاده می‌کنم. عموما برای یادداشت برداری از سایت‌ها؛ فقط کافی است یک صفحه از مرورگر خودتون را با Ctrl+A انتخاب و با Ctrl+C در حافظه کپی کنید. سپس با Ctrl+V در OneNote ، کل صفحه با همان فرمت اصلی و تمام تصاویر، جداول و غیره ذخیره خواهد شد. همچنین در OneNote 2010، دریافت تصاویر از سایت‌ها به صورت asynchronous است (برخلاف نگارش 2007 آن) و حین دریافت تصاویر برنامه متوقف نمی‌شود.

اما اگر سرویس Windows search که کار indexing را انجام می‌دهد خاموش باشد:
  • احتمالا OneNote شما بالای 400 مگ رم مصرف خواهد کرد.
  • مرتبا هنگ می‌کند.
  • عموما CPU Usage ایی بالای 50 درصد به صورت مداوم خواهید داشت.
  • جستجوی آن دیگر درست کار نمی‌کند و بسیار بسیار کند خواهد بود.
و اگر سرویس یاد شده روشن باشد،‌ همه چیز عادی است، از مصرف رم تا CPU و غیره.

مطالب
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 استفاده کنید (اما اجباری نیست).
نظرات مطالب
یکسان سازی ی و ک دریافتی حین استفاده از NHibernate
مایکروسافت مورد مشابهی در مورد الف و همزه برای عربی زبان‌ها دارد که مشابه اون رو می‌شه برای ی/ی ک/ک ما هم می‌شه درخواست داد.
در سیستم‌های مدیریت محتوای (غالبن متن‌باز ِ) با پشتیبانی فارسی مقل جوملا و سایر خلنواده‌هاش تبدیل خودکار حروف عربی به فارسی معمولن ساپورت می‌شه و از جمله مثلن یکی از پلاگین‌های پیش‌فرض وردپرس تبدیل خودکار ی/ک به ی/ک هستش.

+ یه موردی که بارها خواسته بودم ازتون سوال کنم و بحثش پیش نیومده بود اینه که شما خودتون از kbdfa عربی پورت شده از روی ویندوز98 هنوز استفاده می‌کنید که "ک" و "ی" عربیه.
چرا از برنامه‌های زیادی که برای این کار است استفاده نمی‌کنید یا خودتون dll مذکور رو جوری تعییر نمی‌دید که ک و ی هاتون فارسی باشه؟
یادمه چند سال پیش در جایی صحبت شد و توجیه‌تون این بود که مطالب با ی عربی ایندکس شده توی گوگل بیشتره و این استاندارد رایج وب فارسی هستش. برای اون موقع شاید این مطلب درستی بود ولی الان هر کلمه‌ای که خواستید رو سرچ کنید می‌بینید نتایج جستجوی فارسی‌ش برای "ی/ک" بیشتر از "ی/ک" هستش.
مطالب
راه اندازی StimulSoft Report در ASP.NET MVC
یکی از ارکان لاینفک سیستم‌های سازمانی در هر پلتفرمی، چه وب و چه دسکتاپ و ... گزارش گیری از اطلاعات موجود و جزو ساختار حیاتی آن است. از آنجا که حتی ممکن است این گزارشات در هر دوره نیاز به تغییرات داشته باشند و گزارش‌های پویایی باشند؛ این نیاز احساس می‌شود که از یک برنامه گزارش ساز مناسب بهره ببریم. یکی از گزارش سازهای محبوب به خصوص در ایران که حتی نماینده رسمی آن هم در ایران وجود دارد، گزارش ساز StimulSoft Report است.

دارا بودن امکانات بسیار قدرتمند و پشتیبانی از محیط فارسی و همچنین پشتیبانی آن‌ها جهت پاسخگویی به سوالات، چه از طریق ایمیل یا چت، از نقاط قوت این ابزار به شمار می‌روند. در جدول مقایسات می‌توانید تفاوت نسخه‌های موجود این گزارش ساز را مشاهده کنید. برای استفاده در MVC از نسخه وب آن استفاده میکنیم.

در این مقاله قصد داریم با نحوه راه ندازی این ابزار در وب (MVC) آشنا شویم که شامل مباحث زیر می‌شود:
  1. استفاده از EF به عنوان منبع داده و ارسال آن‌ها به سمت گزارش ساز
  2. نحوه طراحی فایل MRT و بایند کردن داده‌های اطلاعاتی و ایجاد جدول
  3. استفاده از امکانات فایل خروجی ، چاپ و پیش نمایش و...
  4. بررسی Direction جهت استفاده در محیط‌های فارسی زبان
  5. نحوه ارسال اطلاعات بین دو اکشن متفاوت


طراحی فایل MRT

فایل MRT در واقع یک قالب (Template) خالی از مقادیر متغیر است که در StimulSoft Studio به طراحی آن میپردازیم و در برنامه خود، این مقادیر متغیر را با اطلاعات دلخواه خود جایگزین می‌کنیم. تصویر زیر یک نمونه از یک گزارش خالی است که ابتدا آن را طراحی کرده و سپس در برنامه آن را مورد استفاده قرار می‌دهیم:

برای اینکه فایل MRT بتواند دیتاهای لازمی را که به آن پاس میدهیم، بخواند و در جای مشخص شده قرار بدهد، باید یک BussinessObject برای آن ایجاد کنیم. بعد از اینکه یک گزارش جدید ایجاد کردید، در سمت راست به قسمت Dictionary بروید و در قسمت BussinessObject گزینه NewBussinessObject را انتخاب کنید. یک نام و نام مستعار که عموما هم یکی است، برای آن انتخاب کنید. در زیر همان پنجره شما می‌توانید ستون‌های اطلاعاتی خود را تعریف کنید. در اینجا من میخواهم اطلاعات یک راننده را به همراه خودروی وی، نشان دهم. برای همین، من دو موجودیت راننده و خودروی راننده را دارم. پس اسم Business Object را DriverReport میگذارم و ستون‌های اطلاعاتی فقط راننده (بدون در نظر گرفتن خودروی وی) را وارد میکنم.

در همین کادر بالا شما میتوانید تصیم بگیرید که آیا میخواهید اطلاعات خودرو را به همراه دیگرستون‌های اطلاعاتی راننده، ایجاد کنید یا اینکه برای خودرو یک نوع مجزا انتخاب کنید. اگر تنها یک خودرو برای راننده باشد، شاید راحت‌تر باشید همانند اطلاعات راننده با آن رفتار کنید. ولی اگر مثلا بخواهید خودروی‌های گذشته راننده را هم جز لیست داشته باشید، بهتر است یک  Business Object جدید متعلق و زیر مجموعه Business Object راننده ایجاد کنید. در اینجا چون تنها یک خودرو است، من آن اطلاعات آن را به همراه راننده، ارسال میکنم. شکل زیر ساختار درختی از گزارش بالاست:

شکل زیر هم یک ساختار دیگر از یک گزارش است که شامل Business object‌های مختلف می‌شود: 

سپس همین فیلدها را به سمت صفحه خالی بکشانید. با دو بار کلیک روی فیلدهای قرار گرفته در صفحه، با نحوه بایند کردن مقادیر آشنا می‌شوید؛ هر فیلدی که قرار است دیتای آن بایند شود، باید به شکل زیر در بخش Expression پنجره باز شده، نوشته شود:

{driverReport.LastName}
در صورتیکه قرار است Business Object به شکل یک لیست ارسال شود؛ مثلا لیست رانندگان یا حتی لیست خودروهایی که یک راننده از گذشته تا کنون داشته است، می‌توانید به جای درگ کردن فیلد به درون صفحه، خود Business Object را درگ کنید تا از روی آن یک جدول درست شود و با ارسال یک لیست به سمت آن و به ازای هر آیتم از این لیست یک سطر داشته باشید.

در دیکشنری همچنین انواع دیگری از فیلدها نیز به چشم میخورد:
متغیرها: این نوع فیلد یک متغیر است که به طور جداگانه میتواند مقداردهی شود و از آن بیشتر برای ارسال داده‌های تکی چون تصاویر، تاریخ شمسی و ... میتوان استفاده کرد.
متغیرهای سیستمی: این نوع متغیرها توسط خود گزارش ساز به طور مستقیم پر می‌شوند که شامل شماره صفحه، تاریخ و زمان، تعداد صفحات، مقادیر دو ارزشی (آیا صفحه آخر گزارش است؟) و ... می‌شود.
توابع: گزارش ساز شامل یک سری توابع آماده برای اعمال تغییرات بر روی داده‌ها میباشد که در دسته‌های مختلفی چون کار با رشته‌ها، زمان، ریاضیات و... قرار گرفته‌اند.
بعد از تکمیل آن، فایل MRT را ذخیره و در یک دایرکتوری در ساختار پروژه قرار دهید.

راه اندازی گزارش ساز در ASP.Net MVC

اولین کاری که می‌کنیم، ورود سه dll اصلی به پروژه است:

Stimulate.Base

Stimulate.Report

Stimulate.Report.MVC

در مرحله بعد یک متد ساخته و یک ویوو را برای صفحه گزارش گیری ایجاد می‌کنیم:

public ActionResult Report(int id)
{
   return View();
}
در ویوی مربوطه کدهای زیر را اضافه می‌کنیم:
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions()
{
   
    Localization = "~/content/reports/fa.xml",
    Actions =
        {
        
            GetReportSnapshot = "LoadReportSnapshot",
            ViewerEvent = "ViewerEvent",
            ExportReport = "ExportReport",
            PrintReport = "PrintReport",
        }
 }

در نسخه‌های دو سال اخیر، استفاده از این Helper تفاوت‌هایی در نحوه استفاده از خصوصیت‌های آن کرده است. در این روش جدید، پراپرتی‌ها دسته بندی شده و برای دسترسی به هر کدام باید به بخش آن مراجعه کنید؛ مثلا پراپرتی‌های Action، در دسته Actions قرار گرفته‌اند یا خصوصیت‌های ظاهری در دسته Appearance، یا گزینه‌های مرتبط با خروجی گرفتن‌ها، در دسته Export قرار گرفته‌اند و الی آخر که در نسخه‌های پیشین، کد بالا را به شکل زیر،  با پیشوند نام دسته می‌نوشتیم:
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions()
{
   
    Localization = "~/content/reports/fa.xml",
            ActionGetReportSnapshot = "LoadReportSnapshot",
            ActionViewerEvent = "ViewerEvent",
            ActionExportReport = "ExportReport",
            ActionPrintReport = "PrintReport",
            
}
خصوصیت GetReportSnapshot نام یک اکشن متد است که کار ارسال دیتا را به گزارش ساز، انجام میدهد. باقی خصوصیت‌ها را در ادامه بررسی میکنیم. پس متد LoadReportSnapshot را ایجاد می‌کنم و کدهای زیر را در ادامه آن می‌نویسیم:

بعد از آن لازم است دیتا‌ها را از طریق EF خوانده و به یک مدل جدید که بر اساس اطلاعات گزارش شماست و قرار است گزارش شما این پراپرتی‌ها را بشناسد، به طور دستی یا با استفاده یک کتابخانه mapping مثل automapper انتقال دهید. یا حتی می‌توانید مانند کد زیر از ساختاری ناشناس استفاده کنید. در کد زیر، من به صورت تمرینی اطلاعات یک راننده و خودروی او را انتقال میدهم:
var driver = new
            {
                FirstName = "علی",
                LastName = "یگانه مقدم",
                NationalCode = "12500000000",
                FatherName = "حسین",
                Model = "نام خودرو",
                MotorNumber = 415244,
                ProductionYear = 1394,
                Capacity = 4
                
};
اگر اطلاعات خودرو را هم به صورت مجزا BussinessObject ساخته‌اید باید آن را به شکل زیر تعریف کنید ( با فرض اینکه نام BussinessObject در گزارش، با نام car تعریف شده باشد):
var driver = new
            {
                FirstName = "علی",
                LastName = "یگانه مقدم",
                NationalCode = "12500000000",
                FatherName = "حسین",
                car = new
                {
                    Model = "نام خودرو",
                    MotorNumber = 415244,
                    ProductionYear = 1394,
                    Capacity = 4
                }
};
بعد از اینکه دیتاهای لازم را بر اساس فرمت دلخواه خود آماده کردیم، باید آن‌ها را به سمت گزارش ساز ارسال کنیم:
var report = new StiReport();
            report.Load(Server.MapPath("~/Content/Reports/driver.mrt"));
            report.RegBusinessObject("driverReport", driver);
            report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date));
در مرحله اول یک وهله از شیء StiReport را می‌سازیم و فایل گزارشی را که در مرحله قبل ساختیم، به آن معرفی میکنیم. سپس داده‌های لازم را به آن انتقال می‌دهیم. پارامتر اول نام BussinessObject اصلی یعنی driverReport را وارد می‌کنیم و پارامتر دوم هم، همان اطلاعات گزارش است. خط بعدی هم یک متغیر است که من در گزارش تعریف کرده‌ام و در اینجا آن را با تاریخ شمسی امروز پر میکنم. توجه داشته باشید که انتقال اطلاعات حتما باید بعد از استفاده از متد Load باشد؛ در غیر اینصورت انتقالی انجام نخواهد شد. اینکه صرفا شما وهله‌ای از شیء StiReport بسازید و مقادیر را بدون ترتیب پر کنید، کفایت نمی‌کند. یعنی ترتیب زیر یک ترتیب کاملا اشتباه است:
var report = new StiReport();
            report.RegBusinessObject("driverReport", driver);
            report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date));
           report.Load(Server.MapPath("~/Content/Reports/driver.mrt"));
چیزی که بعدا در خروجی می‌بینید، یک صفحه گزارش بدون مقدار است.
پس کد کامل ما برای ایجاد یک گزارش به شکل زیر می‌شود:
 public ActionResult LoadReportSnapshot()
{
  var driver = new
            {
                FirstName = "علی",
                LastName = "یگانه مقدم",
                NationalCode = "12500000000",
                FatherName = "حسین",
                Model = "نام خودرو",
                MotorNumber = 415244,
                ProductionYear = 1394,
                Capacity = 4

            };

            var report = new StiReport();
            report.Load(Server.MapPath("~/Content/Reports/driver.mrt"));
            report.RegBusinessObject("driverReport", driver);
            report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date));
return StiMvcViewer.GetReportSnapshotResult(HttpContext, report);
}
همه خطوط، همان قبلی هاست که بررسی کردیم؛ بجز خط آخر که یک ActionResult اختصاصی است که در آن نحوه انتقال اطلاعات به گزارش ساز پیاده سازی شده است و تنها کاری که باید بکنیم این است که شیء گزارش ایجاد شده در بالا را به آن پاس دهیم.

اگر دوباره در ویو مربوطه، به سراغ helper برویم می‌بینیم که سه اکشن متد دیگر وجود دارند که در زیر، به ترتیب با نحوه کار آن‌ها و کد اکشن متد آن‌ها اشاره میکنیم:
Viewer Events : این اکشن متد که تنها یک خط ActionResult استاتیک را فراخوانی می‌کند، جهت مدیریت رویدادهای گزارش چون: زوم، صفحه بندی گزارش، خروجی‌ها و چاپ می‌باشد و وجود آن در گزارش از الزامات است.
 public virtual ActionResult ViewerEvent()
 {
            return StiMvcViewer.ViewerEventResult();
 }

PrintReport: برای مدیریت و ارسال گزارشات به دستگاه چاپ می‌باشد. این اطلاعات از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط PrintReportResult آن را دریافت می‌کند.
public virtual ActionResult PrintReport()
        {
            return StiMvcViewer.PrintReportResult(this.HttpContext);
        }

ExportReport: گزارش ساز استیمول به شما اجاز میدهد در فرمت‌های گوناگونی چون xlsx,docx,pptx,pdf,rtf و ... از گزارش خود خروجی بگیرید. اطلاعات گزارش از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط ExportReportResult  دریافت می‌شود. 
 public virtual ActionResult ExportReport()
        {
            return StiMvcViewer.ExportReportResult(this.HttpContext);
        }
حال اگر برنامه را اجرا کنید، باید گزارشی به شکل زیر نمایش داده شود و مقادیر در جای خود شکل گرفته باشند. ولی مشکلی  که ممکن است این گزارش داشته باشد این است که برای فارسی، حالت راست به چپ را ندارد.


 البته خوشبختانه این مشکل  در حالت پیش نمایش و چاپ و خروجی‌ها دیده نمی‌شود و فقط مختص نمایش روی فرم Html است. برای حل این مشکل ممکن است از گزینه یا پراپرتی RightToLeft، در بخش Appearance موجود در helper استفاده کنید که البته استفاده از آن مانند تصویر بالا، فقط محدود به container گزارش و نوار ابزار آن می‌شود. برای حل این مشکل کافی است کد css زیر را به صفحه گزارش اضافه کنید تا مشکل حل شود:
.stiMvcViewerReportPanel table{
    direction:ltr !important;
}
مجددا گزارش را ایجادکنید تا گزارش را به طور صحیحی مشاهده کنید:

حال حتما پیش خود میگویید که این روش برای اطلاعات ایستا و تمرینی مناسب است و من چگونه باید پارامترهای ارسالی به اکشن متد Report را به اکشن متد LoadReportSnapshot ارسال کنم. برای این منظور استفاده از SessionState‌ها زیاد توصیه شده‌است:
 public virtual ActionResult Report(int id)
        {
            TempData["id"]=id;
            return View();
        }

         public virtual ActionResult LoadReportSnapshot()
        {
            var driverId = (int)TempData ["id"];
              //.....
        }
  ولی این روش مشکلات زیادی را دارد. اول اینکه اگر کاربر چند گزارش جداگانه را پشت سر هم باز کند، به دلیل اینکه گزارش مدتی طول می‌کشد باز شود، همه گزارش‌ها آخرین گزارش درخواستی خواهند بود و دوم اینکه مقداری از حافظه سرور را هم بی جهت اشغال میکند. ولی برای کار با استیمول به هیچ یک از این کارها نیازی نیست، چون خود استیمول به طور خودکار پارامترهای ارسالی را انتقال می‌دهد. یعنی کد باید به شکل زیر نوشته شود:
public virtual ActionResult Report(int id)
        {
    
            return View();
        }

         public virtual ActionResult LoadReportSnapshot(int id)
        {
             //.....
        }
همین مقدار کد برای ارسال پارمترها کفایت میکند و مابقی کار را به stimul بسپارید.

نکته بسیار مهم: گزارش ساز استیمول متاسفانه شامل تنظیم پیش فرض نامناسبی است که عملیات کش را بر روی گزارش‌ها اعمال می‌کند. به عنوان مثال تصور کنید من صفحه گزارش شخصی به نام «وحید نصیری» را باز میکنم و در تب دیگر گزارش شخص دیگری با نام «علی یگانه مقدم» را باز میکنم. حال اگر کاربر به سراغ تب آقای نصیری برود و بخواهد چاپ یا خروجی درخواست کند، اشتباها با گزارش علی یگانه مقدم روبرو خواهد شد که این اتفاق به دلیل کش شدن رخ میدهد. برای غیر فعال کردن این قابلیت پیش فرض، کد زیر را در Helper اضافه کنید:
Server =
    {
        GlobalReportCache = false
    }
مطالب
ExtJs! رویا یا کابوس؟
چندی پیش یکی از دوستان درباره فریم ورک ExtJs سوالاتی را پرسیده بود که تصمیم گرفتم جواب‌های مورد نظر را به صورت عمومی در قالب یک پست منتشر کنم.
  •  ExtJs چیست؟
  •  چه زمانی کاربرد دارد؟
  •  تفاوت آن با سایر فریم ورک‌های جاوااسکریپ در چیست؟
شاید خیلی از شما با MODX آشنایی داشته باشید یا حتی با این CMS کار کرده باشید. اگر این طور است پس حتما با پنجره‌های زیبا و کامپوننت‌های قوی و اعتبارسنجی‌های سفارشی و تعاملاتی Ajax ای آن آشنایی دارید و شاید این سوال به ذهنتان خطور کرده باشد که در طراحی این CMS که بر پایه زبان PHP است دقیقا از چه چیز استفاده شده است؟
پاسخ یک کلمه است: ExtJs. بله درست است در طراحی این CMS تنها از یک فریم ورک جاوااسکریپتی به نام ExtJs استفاده شده است. فریم ورکی که به عقیده بعضی‌ها یک رویا برای توسعه دهندگان وب است و به عقیده سایرین شاید یک کابوس. در این پست قصد دارم به عنوان کسی که با این فریم ورک آشنایی دارم این موضوع را بررسی و مزایا و معایب این فریم ورک را عنوان کنم.
ExtJs یک فریم ورک جاوااسکریپ است بر مبنای Sencha و طراحی شده برای توسعه پروژه‌های وب در مقیاس بزرگ و به صورت cross-platform . مجوز استفاده از این فریم ورک به صورت GPLv3 است.(یعنی مجاز به استفاده رایگان از فایل‌های این فریم ورک هستید به شرطی که قصد استفاده تجاری از پروژه تهیه شده را نداشته باشید! در غیر این صورت باید زحمت خرید نسخه تجاری این فریم ورک را متحمل شوید).
نسخه ای که درباره آن بحث می‌کنیم نسخه چهار این فریم ورک (ExtJs 4) که بر مبنای ExtJs 3 تولید شده است. تفاوت عمده آن با نسخه قبلی در تکمیل ابزار و کامپوننت هاست و از طرفی نسخه چهار این فریم ورک بر مبنای مدل MVC توسعه داده شده است. یعنی همانند Angular و BackBoneJs می‌توانید مفاهیم کنترلر و مدل را به راحتی پیاده سازی کنید.

رویایی به نام ExtJs

اگر بخواهیم این فریم ورک را یک رویا برای توسعه دهندگان وب بنامیم می‌توان عناوین زیر را به عنوان مزایا برشمرد:
  • در درجه اول قابلیتی که این فریم ورک را متفاوت از سایر فریم ورک‌های جاوااسکریپتی می‌کند این است که این فریم ورک انبوهی از کامپوننت‌ها و ویجیت‌های آماده را به همراه خود دارد (با کارایی و انعطاف پذیری قابل قبول) و به نوعی شما را بی نیاز از هرگونه مجموعه کامپوننت‌های دیگر خواهد کرد. 
  • این فریم ورک به خوبی از مباحت OOP پشتیبانی می‌کند و به این صورت است که یک سری مفاهیم و مدل‌های پایه در این فریم ورک تعبیه شده و به راحتی شما می‌توانید مدل‌های مورد نظر خود را بر اساس این مفاهیم و مدل‌های پایه توسعه دهید.
  • تمام مفاهیم و ابزار لازم جهت درخواست‌های Ajax ای و اعتبار سنجی سفارشی و دستکاری عناصر DOM و... به خوبی در این فریم ورک وجود دارد.
  • به دلیل وجود کامپوننت‌های یک دست و آماده به راحتی می‌توانید امکان تغییر theme را در پروزه‌های خود بدون کوچکترین زحمت قرار دهید. 
  • کنترل GridPanel،TreeView ، کنترل‌های ورود اطلاعات، کنترل Tab با قابلیت درخواست‌های لود صفحات به صورت Ajax و Async با کمترین زحمت در کد نویسی و هم چنین چارت‌های بسیار گسترده و متنوع از دیگر مزایای این فریم ورک می‌تواند باشد.
  • ارائه مکانیزمی مناسب برای کار با عملیات داده ای Json. به عنوان نمونه:
Ext.data.JsonP.request({
  url: '@url',
  params: {
  apiKey: '1234'
  },
  callbackKey: 'myCallbackFn',
  success: function(){
 },
  failure: function(){
},
scope: this
});
  • این فریم ورک ابزارهای جالب و کارآمدی برای توسعه به صورت SPA را داراست.
  • کنترل‌های داده ای این فریم ورک در هنگام کار با حجم داده بسیار زیاد، فراتر از انتظار عمل می‌کنند(برای مثال کنترل GridPanel و DataView)
  • اگر قصد تولید و توسعه بک پروژه بزرگ درون سازمانی را دارید و سرعت تولید نیز برای شما مهم است ExtJs در این زمینه کمک شایانی به شما خواهد کرد.
  • و...

حال با همه این تفاسیر آیا این فریم ورک یک رویا برای هر توسعه دهنده وب خواهد بود؟ 

به طور قطع نه. با توجه به اصل واقع بینی! همواره به خاطر داشته باشید که اگر این فریم ورک یک ابزار بی نقص و همه منظوره بود الآن مطمئنا صدها کتاب و مستندات درباره آن نوشته شده بود و شاید شهرتی بس فراتر از این داشت. 

کابوسی به نام ExtJs

  • اگر قصد ایجاد یک وب سایت کوچک و جمع و جور را دارید به طوری که مباحث مربوط به SEO نیز برای شما اهمیت دارد تجربه نشان داده است که انتخاب ExtJs می‌تواند یکی از بزرگ‌ترین اشتباهات در طول عمر کاری شما شود.
  • ExtJs هیچ گونه کمکی برای تولید و توسعه اپلیکیشن‌های موبایل یا پروژه‌های وب گرافیکی نمی‌تواند به شما کند.
  • اگر سرعت یکی از فاکتور خیلی مهم برای شماست بهتر است به این فریم ورک علاقه نشان ندهید.(کتابخانه آن چیزی در حدود 500KB است! البته با فشرده سازی به 150KB خواهد رسید که باز هم قابل قبول نیست)
  • مجوز استفاده برای پروژه‌های تجاری به صورت رایگان نیست.(^)
  • به دلیل وجود ابزار‌های متنوع و زیاد؛ زمان یادگیری برای آشنایی و کار کردن با ابزارها، نسبتا طولانی خواهد شد.
  • کد نویسی برای استفاده از ابزار‌های آن در مقایسه با Jquery و Angular بیشتر خواهد بود(البته این به نوعی مزیت هم است، به دلیل اینکه خوانایی کد‌ها بسیار بالا می‌رود)
  • در طراحی کامپوننت‌ها آن از تگ div در حد غیر قابل قبول استفاده شده است به طوری که Debug صفحات حتی با Firebug هم در بعضی مواقع سخت می‌شود.
  • و...

Ext.Net چیست؟

Ext.Net یک پیاده سازی خاص از فریم ورک ExtJs است که برای توسعه پروژه‌های Asp.Net Web Forms و Asp.Net MVC طراحی شده است. تفاوت اصلی بین این دو محصول در نوع کدنویسی برای استفاده در پروژه‌های Asp.Net است. برای مثال در هنگام کار با Ext.Net و پروژه‌های MVC از آنجا که این محصول سازگاری کامل با موتور Razor دارد به راحتی می‌توانید به صورت سینتکس Razor صفحات خود را طراحی کنید. 

مثال:

ExtJs

Ext.create('Ext.panel.Panel', {
title: 'Fit Layout',
width: 500,
height: 200,
items: {
title: 'Inner Panel',
html: 'Panel content',
bodyPadding: 10,
border: true
},
renderTo: Ext.getBody()
});
اجرای کد بالا با استفاده از ExtJs به صورت زیر خواهد بود:

Ext.Net
@(X.Panel()
        .ID("ExpandablePanel")
        .Title("Panel")
        .Width(500)
        .Height(300)
        .Collapsible(true)
        .Loader(X.ComponentLoader()
            .Url(Url.Action("RenderChild"))
            .Mode(LoadMode.Frame)
            .DisableCaching(true)
            .Params(new { containerId = "ExpandablePanel" })
            .LoadMask(lm => lm.ShowMask = true)
        )
        .Listeners(l => {
            l.Expand.Handler = "this.reload();";
            l.Collapse.Handler = "this.clearContent();";
        })
    )  
خروجی مورد نظر برای Ext.Net:

جمع بندی:
با توجه مواردی که ذکر شد می‌توان به یک نکته مهم رسید و آن هم این است که هنگام انتخاب ExtJs یا Ext.Net (البته این شامل اکثر ابزارهای توسعه دیگر نیز خواهد شد) حتما شرایط موجود و حاکم برای توسعه محصول را مد نظر داشته باشید که این شرایط شامل محیط اجرای محصول، مدت زمان لازم برای توسعه، سطح دانش نیروی‌های توسعه دهنده و ... نیز می‌باشد.
مطالب
آناتومی یک گزارش خطای خوب
به مشکلی در برنامه‌ای برخورده‌اید؟ کتابخانه‌ای کار نمی‌کند؟ خطایی را دریافت کرده‌اید؟ برنامه کامپایل نمی‌شود؟ برنامه آنطور که مدنظر شما است رفتار نمی‌کند؟ برای طرح این مسایل، صرف عنوان کردن «برنامه کار نمی‌کنه» یا «خطا می‌ده» منزلت خودتان را تا حد یک کاربر عادی تازه کار تنزل داده‌اید. در ادامه ساختار یک گزارش خطای خوب را بررسی خواهیم کرد، تا شما را سریعتر به مقصودتان برساند و همچنین کار پیگیری برنامه نویس یا برنامه نویس‌های مسئول را نیز مقداری ساده‌تر کند.


کارهای لازم پیش از طرح سؤال
- سعی کنید انجمن‌های مرتبط را یکبار بررسی و جستجو کنید.
- عین خطای دریافتی را در گوگل جستجو کنید. اگر از برنامه‌ها یا کتابخانه‌های معروف و متداول استفاده می‌کنید، یکی از مزیت‌های مهم کار با آن‌ها، «تنها نبودن» است! یقین داشته باشید خطایی را که دریافت کرده‌اید پیشتر توسط ده‌ها نفر دیگر در سایت‌های مختلف مطرح شده‌اند و بالاخره با بررسی آن‌ها می‌توان به پاسخ رسید.
- شاید راهنمای برنامه در این مورد خاص مطلبی را عنوان کرده است.

و ... به صورت خلاصه باید بتوانید به این سؤال پاسخ دهید: «خودت چکار کردی؟». حداقل نشان دهید که فرد حاضر و آماده طلبی نیستید و پیشتر یک حداقل تقلایی را انجام داده‌اید.


کجا باید سؤال پرسید؟
- اگر به انجمنی برای طرح سؤال خود مراجعه کرده‌اید، حتما زیر شاخه صحیحی را انتخاب کنید تا سؤال شما بسته نشود یا کلا حذف نگردد. برای مثال سؤال ASP.NET را در بخش سی‌شارپ نپرسید یا برعکس یا اگر سایتی مقاله‌ای را منتشر کرده، ذیل آن در مورد نحوه بک آپ گرفتن از اکانت توئیتر خود سؤال نپرسید!
- اگر پاسخی را دریافت کردید، ادامه بحث را ذیل همان مطلب پیگیری کنید و مجددا مطلب جدیدی را ایجاد نکنید.
- اگر تا نیم ساعت بعد جوابی را دریافت نکردید، کل بخش‌های یک سایت را با ارسال پیام خود اسپم نکنید. یکبار ارسال یک سؤال کافی است. اکثر این سایت‌ها حالت یک «چت آفلاین» را دارند. به این معنا که ابتدا پیغام خود را می‌گذارید، اگر مدتی بعد (ممکن است چند ساعت بعد) شخصی آن‌را مشاهده کرد و قادر به پاسخ دهی بود، به شما کمک خواهد کرد. بنابراین اگر سریعا به جواب نرسیدید، نه کل سایت را اسپم کنید و نه ... شروع به رفتارهای ناشایست کنید. اینکار با فریاد کشیدن وسط یک جمع تفاوتی ندارد. اشخاص مرتبط همواره آنلاین نیستند؛ ضمنا ممکن است واقعا پاسخی برای یک سؤال نداشته باشند. منصف باشید.
- از ایمیل‌های خصوصی افراد یا قسمت پیام‌های خصوصی سایت‌ها برای ارسال سؤالات شخصی استفاده نکنید. ایمیل خصوصی، مخصوص کارهای شخصی است. قسمت پیام‌های خصوصی یک سایت عموما مخصوص رسیدگی به مشکلات کاربری است. این تصور را نداشته باشید که اشخاص مشاور شخصی رایگان پروژه‌های تجاری شما هستند.
- بهترین محل برای پرسیدن سؤالات مرتبط با یک پروژه خاص، mailing list یا انجمن گفتگو و یا issue tracker آن پروژه است. وقت خودتان را با ارسال خطاهای یک پروژه خاص، در یک انجمن عمومی و همه منظوره تلف نکنید. کمی جستجو کنید که سایت اصلی پروژه کجا است. بعد دقت کنید آیا جایی برای پرسش و پاسخ دارد یا خیر. اکثر پروژه‌های خوب، مکانی را جهت جمع آوری بازخوردهای پروژه خود، اختصاص می‌دهند.


چطور باید سؤال پرسید؟
سؤال فنی خوب پرسیدن هم یک هنر است؛ که تعدادی از مشخصه‌های مهم آن‌را در ذیل مرور خواهیم کرد:
- عنوان مناسبی را برای سؤال خود انتخاب کنید. «لطفا کمک کنید» یا «من مشکل دارم» یا «مشکل در پروژه»، عموما واکنش‌های تندی را به همراه دارند؛ و تا حد ارسال اسپم در یک سایت بی‌کیفیت تلقی می‌شوند. ضمن اینکه انتخاب عنوان‌های مناسب، جستجوهای بعدی را در سایت ساده می‌کنند و کمک بزرگی خواهند بود به افراد بعدی.
- محیطی را که خطا در آن رخ داده است، توضیح دهید. ذکر IIS تنها کافی نیست. کدام نگارش آن؟ در کدام ویندوز؟
برای مثال شماره نگارش کتابخانه یا نرم افزار مورد استفاده را ذکر کنید. شاید خطایی که گرفته‌اید در نگارش بعدی آن برطرف شده است.
ذکر شماره نگارش VS.NET یا شماره نگارش دات نت مورد استفاده، سیستم عامل و کلا توصیف محیط بروز خطا، عموما بسیار مفید هستند.
- حتما کل خطای دریافت شده را ارسال کنید. اگر در یک برنامه C خطایی حاصل شود، احتمالا شکلی مانند Error 0xABCD را دارد. اما استثناءهای دات نت به همراه stack trace و حتی شماره سطر خطای حاصل نیز هستند. همین مساله می‌تواند به خطایابی نهایی بسیار کمک کند.
- سؤال خود را طوری مطرح کنید که شخص مقابل بتواند آن‌را در کمترین زمان ممکن «باز تولید» کند. برای مثال ذکر خطای دریافتی بسیار خوب است. اگر داده‌ای که سبب بروز این خطا شده است را هم ارسال کنید، مفید‌تر خواهد بود؛ یا اگر دستور پاور شل خاصی در کنسول نیوگت خطا می‌دهد، صرفا عنوان نکنید که جواب نگرفته‌اید. چه دستوری را اجرا کرده‌اید؟ چه خطایی را دریافت کرده‌اید؟ ساختار پروژه شما چیست؟ آیا شخص مقابل می‌تواند بر اساس اطلاعاتی که ارائه دادید یک آزمایش شخصی را تدارک ببیند؟ آیا می‌تواند آن‌را با توضیحات شما مجددا تولید کند؟
زمان باز تولید خطا را هم مدنظر داشته باشید. برای مثال اگر بتوانید قطعه کدی را ارائه دهید که در کمترین زمان ممکن، صرفا با کپی و پیست آن در VS.NET قابل کامپایل باشد، بسیاری علاقمند به پاسخگویی به شما خواهند شد. در غیراینصورت آنچنان انتظار نداشته باشید که شخص پاسخ دهنده وقت زیادی را برای رسیدگی به جزئیات سؤال شما صرف کند؛ یا مدتی مشغول به تهیه یک مثال جدید بر مبنای توضیحات شما شود.
حجم کدهای ارسالی شما نیز در اینجا مهم هستند. کل پروژه خود را ارسال نکنید! سعی کنید یک مثال کوچک را که بتواند سریعا خطای مدنظر شما را بازتولید کند، ارسال کنید و نه بیشتر. همچنین کدهایی که برای اجرا نیاز به GUI نداشته باشند نیز در این حالت اولویت دارند.
و به صورت خلاصه، خودتان را بجای پاسخ دهنده قرار دهید. آیا با چند جمله‌ای که ارائه داده‌اید، می‌توان انتظار پاسخی را داشت یا خیر.
- ایمیل شخصی خود را در انتهای پیام ارسال نکنید. کسی اهمیتی نمی‌دهد! اگر سؤال شما پاسخی داشته باشد، همانجا دریافت خواهید کرد و نه در میل باکس شخصی.
- املاء و انشای متنی را که ارسال می‌کنید، یکبار بررسی کنید. اگر برای شما اهمیتی ندارد که چه کلمات و جمله بندی را باید بکار برد، برای شخص مقابل هم آنچنان اهمیتی نخواهد داشت که زیاد وقت صرف کند.
- از بکار بردن smileyهای بیش از حد یا قرار دادن تعداد علامت تعجب‌های بیش از حد خودداری کنید. این موارد عموما به مسخره کردن شخص مقابل تفسیر می‌شوند.
- در بدو امر فریاد نکشید که «باگ» پیدا کرده‌اید؛ خصوصا اگر به mailing list اختصاصی یک پروژه پیامی را ارسال می‌کنید. چون اگر مشکل شما واقعا باگ نباشد، بیشتر یک توهین تلقی خواهد شد و در دفعات بعدی پاسخ دادن به شما به صورت ضمنی مؤثر خواهند بود؛ یا جواب نمی‌گیرید و یا جدی گرفته نخواهید شد.  
- هدف از کاری را که مشغول به انجام آن بود‌ه‌اید را نیز ذکر کنید. ذکر خطای دریافتی بسیار مفید است اما اگر بتوانید یک دید کلی را نسبت به کاری که مشغول به آن بوده‌اید، ایجاد کنید، شاید پاسخ بهتری را دریافت کنید. برای مثال جهت رسیدن به هدف و مقصود شما بهتر است از روش دیگری استفاده کنید.
- پس از اینکه پیامی را دریافت کردید، یک حداقل واکنشی را ارسال کنید. مثلا خوب بود؛ کمک کرد و یا مفید نبود. همین واکنش‌ها در آینده به کمک نتایج جستجوهای انجام شده خواهند آمد و اشخاص بعدی حداقل خواهند دانست که پاسخ داده شده صحیح بوده است یا خیر.

و همیشه بخاطر داشته باشید: تمام خدماتی که سایت‌های عمومی به شما ارائه می‌دهند «یک لطف» است و حقی را برای شما ایجاد نمی‌کنند. این اشخاص از شما پول نمی‌گیرند تا به سؤالات شما پاسخ دهند یا تبدیل به مشاور خصوصی رایگان شما شوند. می‌توانید محیط را برای این اشخاص، با اندکی احترام، ملایمت و انصاف، دلپذیرتر کنید.
نظرات مطالب
امکان ساخت برنامه‌های دسکتاپ چندسکویی Blazor در دات نت 6
- این مطلب برای شروع کار مناسب نیست و یک مطلب تکمیلی است. برای آشنایی با معماری برنامه‌های Blazor از اینجا شروع کنید؛ از این جهت که Blazor Server خودش یک پروژه‌ی وب کامل هست و نیازی به پروژه‌ی وب مجزا ندارد. نحوه‌ی دسترسی به اطلاعات آن با Blazor WASM متفاوت است و خیلی موارد دیگر (که نیازی به تکرار آن‌ها در اینجا نیست). نمونه‌اش منوی fetch data در تصویر برنامه‌ی دسکتاپ هست که اطلاعات خودش را از وب سرور اجرا شده‌ی به همراه برنامه دریافت می‌کند (این اتصال هم از نوع Web socket مخصوص SignalR هست که در سری Blazor سایت در مورد آن بحث شده). همچنین این را هم مدنظر داشته باشید که زمانیکه بحث برنامه‌ی «دسکتاپ» هست، یعنی برنامه‌های «کوچک» و «تک کاربره» که توضیحات این مطلب برای پوشش آن‌ها کافی است.
- اگر هم Web API راه دور دارید، فقط کافی است سرویس HttpClient را ثبت کنید (در همان سازنده‌ی فرم برنامه‌ی وین‌فرمز مثال فوق):
serviceCollection.AddScoped<HttpClient>();
بعد هم در صفحات razor کتابخانه‌ی مثال می‌توان به نحو متداولی با این سرویس کار کرد (و نکات کار با این سرویس در سری Blazor سایت بررسی شده‌اند):
@inject HttpClient Http
مطالب
Angular CLI - قسمت اول - نصب و راه اندازی
از زمان Angular 2 به بعد، تنها یک نام برای نگارش‌های جدید آن درنظر گرفته شده‌است و آن هم «Angular» است. بنابراین در اینجا منظور از Angular همان +AngularJS 2.0 است.
ایجاد و توزیع برنامه‌های جدید AngularJS به همراه تمام وابستگی‌های آن‌ها و همچنین رعایت بهترین تجربه‌های کاری آن، اندکی مشکل است. به همین جهت تیم Angular برنامه‌ای را به نام Angular CLI تدارک دیده‌است که تمام این مراحل را به سادگی هرچه تمام‌تر مدیریت می‌کند. ممکن است قالب‌های زیادی را در مورد شروع به کار با AngularJS 2.0+ در وب پیدا کنید؛ اما هیچکدام از آن‌ها تمام قابلیت‌های Angular CLI را ارائه نمی‌دهند و همواره چندین قدم عقب‌تر از تیم Angular هستند. به همین جهت در طی یک سری قصد داریم قابلیت‌های گوناگون این ابزار را بررسی کنیم.


Angular CLI چیست؟

ایجاد برنامه‌های جدید Angular لذت بخش هستند؛ اما ایجاد برنامه‌هایی که از بهترین تجربه‌های کاری توصیه شده‌ی توسط تیم Angular پیروی می‌کنند، به همراه Unit tests هستند و همچنین برای توزیع بهینه سازی شده‌اند، بسیار چالش برانگیز می‌باشند. به همین جهت برنامه‌ی خط فرمانی به نام Angular CLI برای مدیریت این مسایل توسط تیم Angular ایجاد شده‌است، تا توسعه دهندگان بیشتر وقت خود را صرف بهینه سازی کدهای خود کنند تا اینکه درگیر تدارک مسایل جانبی این فریم ورک باشند.
اگر به پروژه‌های سورس باز ارائه شده‌ی جهت شروع کار با +AngularJS 2.0 دقت کنید، تعداد بی‌شماری پروژه‌ی seed، قالب‌های آماده، کدساز و غیره را خواهید یافت. اکثر آن‌ها تفاوت‌های قابل ملاحظه‌ای را با یکدیگر داشته و در اغلب موارد بهترین تجربه‌های کاری Angular را نیز رعایت نمی‌کنند. برای مثال خبری از style guide آن و یا مباحث بهینه سازی ساخت و توزیع لحاظ شده‌ی در نگارش‌های جدید Angular، در آن‌ها نیست.
در اینجا بود که تیم Angular تصمیم گرفت تا در جهت ساماندهی به این وضعیت آشفته، برنامه‌ی Angular CLI را ایجاد کند تا برنامه نویس‌ها به همراه ابزاری باشند که بر اساس بهترین تجربه‌های کاری Angular تهیه شده‌است؛ سبب ایجاد برنامه‌هایی خواهد شد که یکدست به نظر می‌رسند و همچنین همواره آخرین تغییرات توزیع و آزمایش برنامه‌ها را نیز به همراه دارد.


پیشنیازهای نصب Angular CLI

پیش از شروع به نصب Angular CLI باید مطمئن شوید که آخرین نگارش NodeJS را نصب کرده‌اید. برای این منظور خط فرمان را گشوده و دستور ذیل را صادر کنید:
C:\>node -v
v5.10.1
در اینجا نگارش نصب شده‌ی بر روی سیستم من 5.10 است که برای کار با Angular CLI مناسب نیست و این برنامه‌ی خط فرمان، حداقل نیاز به نصب نگارش 6.9 آن‌را دارد. به همین جهت نیاز است به آدرس https://nodejs.org/en/download مراجعه کرده و آخرین نگارش node.js را دریافت و نصب کرد.

اگر این مطلب را در چند ماه بعد پس از نگارش آن مطالعه می‌کنید، به پروژه‌ی Angular CLI مراجعه کرده و قسمت Prerequisites مستندات ابتدایی آن‌را برای مشاهده‌ی آخرین نگارش NodeJS مورد نیاز آن، بررسی کنید.


نصب Angular CLI

پس از نصب پیشنیاز آن، اکنون خط فرمان را گشوده و دستور ذیل را صادر کنید:
 C:\>npm install -g @angular/cli
به این ترتیب پس از چند دقیقه، Angular CLI به صورت global و عمومی نصب خواهد شد.

پس از نصب آن، جهت اطمینان از عملیات انجام شده، دستور ذیل را در خط فرمان صادر کنید:
 C:\>npm list -g @angular/cli --depth=0
کار سوئیچ list، ارائه گزارشی از بسته‌های عمومی نصب شده‌ی با نام angular/cli@ است. depth=0 به این معنا است که نیازی به تهیه لیستی از وابستگی‌های آن نیست. برای نمونه خروجی آن می‌تواند به صورت ذیل باشد:
 C:\>npm list -g @angular/cli --depth=0
C:\Users\Vahid\AppData\Roaming\npm
`-- @angular/cli@1.0.0

و همچنین برای مشاهده‌ی نگارش CLI نصب شده، دستور ذیل را اجرا نمائید:
C:\>ng -v
    _                      _                 ____ _     ___
   / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
               |___/
@angular/cli: 1.0.0
node: 6.10.2
os: win32 x64
در اینجا ng همان Angular CLI است.


ایجاد یک برنامه‌ی جدید توسط Angular CLI

پس از نصب Angular CLI، اکنون می‌توان از آن جهت ساخت یک برنامه‌ی جدید Angular استفاده کرد. برای این منظور یک پوشه‌ی جدید را ایجاد کرده و سپس از طریق خط فرمان به آن وارد شده (نگه داشتن دکمه‌ی shift و سپس کلیک راست و انتخاب گزینه‌ی Open command window here) و دستور ذیل را صادر کنید:
> ng new ngtest --skip-install
ng به معنای اجرای Angular CLI است. پارامتر new آن سبب ایجاد یک برنامه‌ی جدید خواهد شد و پارامتر skip-install آن، کار فراخوانی خودکار npm install را لغو می‌کند. به این ترتیب می‌توان در سریعترین زمان ممکن، یک برنامه‌ی Angular را ایجاد کرد.


در اینجا ساختار یک پروژه‌ی جدید Angular را مشاهده می‌کنید.
فایل
توضیحات
 .angular-cli.json   تنظیمات cli را به همراه دارد.
 editorconfig   مربوط به تنظیمات VSCode است.
 karma.conf.js   برای انجام unit tests است. 
 package.json    وابستگی‌های npm برنامه را به همراه دارد (که در زمان نگارش این مطلب تنظیمات Angular 4 را به همراه دارد). 
 protractor.conf.js   برای اجرای آزمون‌های end to end که در اینجا e2e نام گرفته‌است، می‌باشد. 
 tsconfig.json   تنظیمات کامپایلر TypeScript را به همراه دارد. 
 tslint.json   جهت اجرای Lint و ارائه‌ی بهترین تجربه‌های کاری با TypeScript است. 

داخل پوشه‌ی src، فایل‌های اصلی پروژه قرار دارند:

- فایل index.html کار ارائه و شروع برنامه را انجام می‌دهد.
- فایل main.ts نقطه‌ی آغاز برنامه است.

با توجه به استفاده‌ی از پارامتر skip-install، هنوز وابستگی‌های فایل package.json نصب نشده‌اند. برای این منظور به پوشه‌ی اصلی پروژه وارد شده (جایی که پوشه‌ی ngtest و فایل package.json قرار دارد) و دستور npm install را صادر کنید تا وابستگی‌های برنامه نیز دریافت شوند. البته اگر از پارامتر یاد شده استفاده نمی‌شد، اینکار به صورت خودکار توسط ng new  انجام می‌گرفت.
>npm install


به این ترتیب وابستگی‌های پروژه در پوشه‌ی node_modules تشکیل خواهند شد.


به روز رسانی Angular CLI

روش به روز رسانی AngularCLI شامل این مراحل است:
الف) به روز رسانی بسته‌ی عمومی نصب شده‌ی آن
npm uninstall -g @angular/cli
npm cache clean
npm install -g @angular/cli@latest
ابتدا باید نگارش موجود عزل شود. سپس cache قدیمی مربوط به npm نیز باید پاک شود و پس از آن نیاز است مجددا آخرین نگارش cli نصب گردد.

ب) به روز رسانی یک برنامه‌ی محلی
در ادامه به پوشه‌ی برنامه‌ی خود وارد شده و دستورات ذیل را اجرا کنید:
rm rmdir /S/Q node_modules dist
npm install --save-dev @angular/cli@latest
npm install
این دستورات ابتدا پوشه‌های node_modules و همچنین dist قبلی را پاک می‌کنند. دستور بعدی، کار به روز رسانی وابستگی‌های package.json را انجام می‌دهد و در آخر دستور npm install، تغییرات فایل package.json را دریافت و نصب می‌کند.

مورد «الف» را به ازای هر نگارش جدید CLI، تنها یکبار باید انجام داد. اما مورد «ب» به ازای هر پروژه‌ی موجود باید یکبار انجام شود (که سریعترین روش به روز رسانی وابستگی‌های یک برنامه، به آخرین نگارش Angular است).
نظرات مطالب
روش استفاده‌ی صحیح از HttpClient در برنامه‌های دات نت
ممنونم از مطلب خوبتون
بنده در پروژه‌ی خودم لایه‌ی سرویس رو با Web Api پیاده سازی کردم، لایه‌ی اپلیکیشن با MVC
احراز هویت در لایه‌ی سرویس با JWT انجام می‌شود و نیاز است تا token دریافتی در DefaultRequestHeaders.Authorization تنظیم شود و سپس درخواست HTTP ارسال شود.( این موضوع در MVC انجام می‌پذیرد.)
با توجه به گفته‌ی خودتون Thread Safe ،DefaultRequestHeaders نیست. پس نمیتونم به ازای تمامی درخواست‌ها از یک HttpClient استفاده کنم و مجبورم به ازای هرکاربر HttpClient جدیدی ایجاد کنم تا token در header رو برای هر کاربر جداگانه تنظیم کنم.
پس کلید دیکشنری رو از Uri baseAddress به string token تغییر دادم.
حالا مشکلی که برام پیش اومده اینه که تعداد HttpClient‌های منقضی شده در دیکشنری زیاد میشه و باید با روشی اونها رو از دیکشنری حذف کنم که سعی میکنم حلش کنم اما سوالم اینه که:
آیا تغییری که در کلاس شما دادم اصولی بود ؟