مطالب
نمایش یک فایل PDF در WinForms ، WPF و سیلورلایت

شاید PDF را بشود تنها فرمت گزارشگیری دانست که همه‌جا و در تمام سیستم عامل‌ها پشتیبانی می‌شود. از ویندوز تا لینوکس از وب تا WPF تا سیلورلایت تا همه جا و از همه مهم‌تر اینکه خروجی آن دقیقا همان چیزی است که کاربر نهایی می‌خواهد: من می‌خوام اون چیزی رو که می‌بینم، دقیقا همان را، بدون کم و کاست و با همان صفحه بندی، بتوانم چاپ کنم.
برای تولید PDF می‌شود از کتابخانه‌ی iTextSharp استفاده کرد اما برای نمایش آن حداقل در ویندوز بهترین راه حل استفاده از COM Components‌ شرکت Adobe است که به همراه برنامه رایگان Adobe PDF reader ارائه می‌شود. در ادامه نحوه‌ی استفاده از این Active-X را بررسی خواهیم کرد.

نمایش PDF در WPF
در تمام حالت‌ها هدف این است که به نحوی به اکتیوایکس شرکت Adobe دسترسی پیدا کنیم؛ یا با اضافه کردن آن به پروژه یا استفاده از امکانات یکپارچه مرورگرها. در WPF از زمان ارائه سرویس پک یک دات نت سه و نیم (به بعد)، کنترل مرورگر وب هم به جمع کنترل‌های قابل استفاده در آن اضافه شده است. در اینجا به سادگی چند سطر زیر می‌شود یک فایل PDF را در WPF نمایش داد:
<Window x:Class="WpfAppTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pdf Report" Height="495" WindowState="Maximized"
WindowStartupLocation="CenterScreen" Width="703">
<Grid>
<WebBrowser x:Name="WebBrowser1"/>
</Grid>
</Window>
و بعد هم در کدهای برنامه تنها کافی است که مقدار Source کنترل WebBrowser را مقدار دهی کرد:
WebBrowser1.Source = new Uri(PdfFilePath);


نمایش PDF در WinForms
اکتیوایکس نمایش دهنده PDF شرکت Adobe اساسا در فایل ذیل قرار گرفته است:
C:\Program Files\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll
بنابراین برای استفاده از آن در یک برنامه‌ی WinForms باید مراحل ذیل طی شود:
الف) در VS.NET‌ از طریق منوی Tools گزینه‌ی Choose toolbox items ، برگه‌ی Com components را انتخاب کنید.
ب) سپس گزینه‌ی Adobe PDF reader که به همان مسیر dll فوق اشاره می‌کند را انتخاب نمائید و بر روی دکمه‌ی OK‌ کلیک کنید.
ج) اکنون این کنترل جدید را بر روی فرم برنامه قرار دهید. به صورت خودکار COMReference های متناظر به پروژه اضافه می‌شوند.

اکنون نحوه‌ی استفاده از این شیء COM به همراه آزاد سازی منابع مرتبط به شرح زیر خواهند بود:
using System.Windows.Forms;

namespace WindowsFormsAppTests
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
this.FormClosing += Form1_FormClosing;
}

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
axAcroPDF1.Dispose();
}

void Form1_Load(object sender, System.EventArgs e)
{
axAcroPDF1.LoadFile(PdfFilePath);
axAcroPDF1.setShowToolbar(true);
axAcroPDF1.Show();
}
}
}

نمایش PDF در Silverlight

در Silverlight هم از نسخه‌ی 4 به بعد کنترل WebBrowser همانند آنچه که در WPF موجود است، اضافه شده است؛ اما این کنترل فقط در حالت اجرای در خارج از مرورگر برنامه Silverlight در دسترس می‌باشد. بنابراین روش دیگری را باید انتخاب کرد. این روش بر اساس تعامل سیلورلایت با کدهای HTML صفحه کار می‌کند. یک IFrame مخفی را در صفحه بالای شیء مرتبط با سیلورلایت قرار خواهیم داد. سپس در سیلورلایت Src این IFrame را به مسیر فایل PDF تنظیم می‌کنیم و همین. به این ترتیب فایل PDF نمایش داده می‌شود.
این IFrame به صورت زیر در همان صفحه‌ی aspx ایی که object مرتبط با Silverlight نمایش داده می‌شود قرار می‌گیرد:
<iframe id="pdfFrame" style="visibility:hidden; position:absolute"><b>No Content</b></iframe>
<div id="silverlightControlHost">
سپس در کدهای سیلورلایت، ابتدا این IFrame یافت شده:
var iFrame = HtmlPage.Document.GetElementById("pdfFrame");
در ادامه بر اساس اطلاعات مکانی یک Grid ساده به نام pdfHost که در صفحه قرار گرفته، این iFrame بالاتر از سطح Grid (بر اساس z-index تنظیم شده) نمایش داده می‌شود:
var gt = pdfHost.TransformToVisual(Application.Current.RootVisual);
var offset = gt.Transform(new Point(0, 0));
var controlLeft = (int)offset.X;
var controlTop = (int)offset.Y;
iFrame.SetStyleAttribute("left", string.Format("{0}px", controlLeft));
iFrame.SetStyleAttribute("top", string.Format("{0}px", controlTop));
iFrame.SetStyleAttribute("visibility", "visible");
iFrame.SetStyleAttribute("height", string.Format("{0}px", pdfHost.ActualHeight));
iFrame.SetStyleAttribute("width", string.Format("{0}px", pdfHost.ActualWidth));
iFrame.SetStyleAttribute("z-index", "1000");
و در آخر نام فایلی را که می‌خواهیم مشاهده کنیم به یک صفحه‌ی aspx در همان سایت ارسال می‌کنیم:
iFrame.SetProperty("src", "ShowPdf.aspx?file=" + fileName);
کدهای این صفحه در حد یک Response.Redirect ساده برای نمایش دادن فایل pdf در مرورگر کافی هستند. در کل در اینجا سیلورلایت تنها نقش انتخاب فایل را به عهده دارد و کار اصلی را خود مرورگر انجام می‌دهد.

مطالب دوره‌ها
استفاده از XQuery - قسمت دوم
در ادامه‌ی مباحث XQuery، سایر قابلیت‌های توکار SQL Server را برای کار با اسناد XML بررسی خواهیم کرد.

کوئری گرفتن از اسناد XML دارای فضای نام، توسط XQuery

در مثال زیر، تمام المان‌های سند XML، در فضای نام http://www.people.com تعریف شده‌اند.
DECLARE @doc XML 
SET @doc ='
<p:people xmlns:p="http://www.people.com">
 <p:person name="Vahid" /> 
 <p:person name="Farid" />
</p:people>
'
SELECT @doc.query('/people/person')
اگر کوئری فوق را برای یافتن اشخاص اجرا کنیم، خروجی آن خالی خواهد بود (و یا یک empty sequence)؛ زیرا کوئری نوشته شده به دنبال اشخاصی است که در فضای نام خاصی تعریف نشده‌اند.
سعی دوم احتمالا روش ذیل خواهد بود
 SELECT @doc.query('/p:people/p:person')
که به خطای زیر منتهی می‌شود:
 XQuery [query()]: The name "p" does not denote a namespace.
برای حل این مشکل باید از مفهومی به نام prolog استفاده کرد. هر XQuery از دو قسمت prolog و body تشکیل می‌شود. قسمت prolog می‌تواند شامل تعاریف فضاهای نام، متغیرها، متدها و غیره باشد و قسمت body، همان کوئری تهیه شده‌است. البته SQL Server از قسمت prolog استاندارد XQuery، فقط تعریف فضاهای نام آن‌را مطابق مثال ذیل پشتیبانی می‌کند:
 SELECT @doc.query('
declare default element namespace "http://www.people.com";
/people/person
')
یک سند XML ممکن است با بیش از یک فضای نام تعریف شود. در این حالت خواهیم داشت:
 SELECT @doc.query('
declare namespace aa="http://www.people.com";
/aa:people/aa:person
')
در اینجا در قسمت prolog، برای فضای نام تعریف شده در سند XML، یک پیشوند را تعریف کرده و سپس، استفاده از آن مجاز خواهد بود.
روش دیگر تعریف فضای نام، استفاده از WITH XMLNAMESPACES، پیش از تعریف کوئری است:
 WITH XMLNAMESPACES(DEFAULT 'http://www.people.com')
SELECT @doc.query('/people/person')
البته باید دقت داشت، زمانیکه WITH XMLNAMESPACES تعریف می‌شود، عبارت T-SQL پیش از آن باید با یک سمی‌کالن خاتمه یابد؛ و گرنه یک خطای دستوری خواهید گرفت.
در اینجا نیز امکان کار با چندین فضای نام وجود دارد و برای این منظور تنها کافی است از تعریف Alias استفاده شود. فضاهای نام بعدی با یک کاما از هم مجزا خواهند شد.
 WITH XMLNAMESPACES('http://www.people.com' AS aa)
SELECT @doc.query('/aa:people/aa:person')


عبارات XPath و FLOWR

XQuery از دو نوع عبارت XPath و FLOWR می‌تواند استفاده کند. XQuery همیشه از XPath برای انتخاب داده‌ها و نودها استفاده می‌کند. در اینجا هر نوع XPath سازگار با استاندارد 2 آن، یک XQuery نیز خواهد بود. برای انجام اعمالی بجز انتخاب داده‌ها، باید از عبارات FLOWR استفاده کرد؛ برای مثال برای ایجاد حلقه، مرتب سازی و یا ایجاد نودهای جدید.
در مثال زیر که data آن در قسمت قبل تعریف شد، دو کوئری نوشته شده یکی هستند:
 SELECT @data.query('
 (: FLOWE :)
 for $p in /people/person
 where $p/age > 30
 return $p
 ')

SELECT @data.query('
(: XPath :)
/people/person[age>30]
')
اولین کوئری به روش FLOWR تهیه شده‌است و دومین کوئری از استاندارد XPath استفاده می‌کند. از دیدگاه SQL Server این دو یکی بوده و حتی Query Plan یکسانی نیز دارند.

 XPath بسیار شبیه به مسیر دهی‌های یونیکسی است. بسیار فشرده بوده و همچنین مناسب است برای کار با ساختارهای تو در تو و سلسله مراتبی. مثال زیر را درنظر بگیرید:
 /books/book[1]/title/chapter
در اینجا books، المان ریشه است. سپس به اولین کتاب این ریشه اشاره می‌شود. سپس به المان عنوان و مسیر نهایی، به فصل ختم می‌شود. البته همانطور که در قسمت‌های پیشین نیز ذکر شد، حالت content، پیش فرض بوده و یک فیلد XML می‌تواند دارای چندین ریشه باشد.

در XPath توسط قابلیتی به نام محور می‌توان به المان‌های قبلی یا بعدی دسترسی پیدا کرد. این محورهای پشتیبانی شده در SQL Server عبارتند از self (خود نود)، child (فرزند نود)، parent (والد نود)، decedent (فرزند فرزند فرزند ...)و attribute (دسترسی به ویژگی‌ها). محورهای استانداردی مانند preceding-sibling و following-sibling در SQL Server با عملگرهایی مانند >> و << پشتیبانی می‌شوند.



مثال‌هایی از نحوه‌ی استفاده از محورهای XPath

اینبار قصد داریم یک سند XML نسبتا پیچیده را بررسی کرده و اجزای مختلف آن‌را به کمک XPath بدست بیاوریم.
DECLARE @doc XML 
SET @doc='
<Team name="Project 1" xmlns:a="urn:annotations">
  <Employee id="544" years="6.5">
    <Name>User 1</Name>
<Title>Architect</Title>
<Expertise>Games</Expertise>
<Expertise>Puzzles</Expertise>
<Employee id="101" years="7.1" a:assigned-to="C1">
 <Name>User 2</Name>
 <Title>Dev lead</Title>
 <Expertise>Video Games</Expertise>
 <Employee id="50" years="2.3" a:assigned-to="C2">
 <Name>User 3</Name>
 <Title>Developer</Title>
 <Expertise>Hardware</Expertise>
 <Expertise>Entertainment</Expertise>
</Employee>
</Employee> 
  </Employee>
</Team>
'
در این سند، کارمند و کارمندانی را که باید به یک کارمند گزارش دهند، ملاحظه می‌کنید.
در XPath، محور پیش فرض، child است (اگر مانند کوئری زیر مورد خاصی ذکر نشود):
 SELECT @doc.query('/Team/Employee/Name')
و اگر بخواهیم این محور را به صورت صریح ذکر کنیم، به نحو ذیل خواهد بود:
 SELECT @doc.query('/Team/Employee/child::Name')
خروجی آن User1 است.
 <Name>User 1</Name>
برای ذکر محور decedent-or-self می‌توان از // نیز استفاده کرد:
 SELECT @doc.query('//Employee/Name')
با خروجی
 <Name>User 1</Name>
<Name>User 2</Name>
<Name>User 3</Name>
در این حالت به تمام نودهای سند، در سطوح مختلف آن مراجعه شده و به دنبال نام کارمند خواهیم گشت.

برای کار با ویژگی‌ها و attributes از [] به همراه علامت @ استفاده می‌شود:
 SELECT @doc.query('
declare namespace a = "urn:annotations";
//Employee[@a:assigned-to]/Name
')
در این کوئری، تمام کارمندانی که دارای ویژگی assigned-to واقع در فضای نام urn:annotations هستند، یافت خواهند شد. با خروجی:
 <Name>User 2</Name>
<Name>User 3</Name>
معادل طولانی‌تر آن ذکر کامل محور attribute است بجای @
 SELECT @doc.query('
declare namespace a = "urn:annotations";
//Employee[attribute::a:assigned-to]/Name
')
و برای یافتن کارمندانی که دارای ویژگی assigned-to نیستند، می‌توان از عملگر not استفاده کرد:
 SELECT @doc.query('
declare namespace a = "urn:annotations";
//Employee[not(@a:assigned-to)]/Name
')
با خروجی
 <Name>User 1</Name>
و اگر بخواهیم تعداد کارمندانی را که به user 1 مستقیما گزارش می‌دهند را بیابیم، می‌توان از count به نحو ذیل استفاده کرد:
 SELECT @doc.query('count(//Employee[Name="User 1"]/Employee)')

در XPath برای یافتن والد از .. استفاده می‌شود:
 SELECT @doc.query('//Employee[../Name="User 1"]')
برای مثال در کوئری فوق، کارمندانی که والد آن‌ها user 1 هستند، یافت می‌شوند.
استفاده از .. در SQL Server به دلایل کارآیی پایین توصیه نمی‌شود. بهتر است از همان روش قبلی کوئری تعداد کارمندانی که به user 1 مستقیما گزارش می‌دهند، استفاده شود.



عبارات FLOWR

FLOWR هسته‌ی XQuery را تشکیل داده و قابلیت توسعه XPath را دارد. FLOWR مخفف for، let، order by، where و retrun است. از for برای تشکیل حلقه، از let برای انتساب، از where و order by برای فیلتر و مرتب سازی اطلاعات و از return برای بازگشت نتایج کمک گرفته می‌شود. FLOWR بسیار شبیه به ساختار SQL عمل می‌کند.
معادل عبارت SQL
 Select p.name, p.job
from people as p
where p.age > 30
order by p.age
با عبارات FLOWR، به صورت زیر است:
 for $p in /people/person
where $p.age > 30
order by $p.age[1]
return ($p/name, $p/job)
همانطور که مشاهده می‌کنید علت انتخاب FLOWR در اینجا عمدی بوده‌است؛ زیرا افرادی که SQL می‌دانند به سادگی می‌توانند شروع به کار با عبارات FLOWR کنند.
تنها تفاوت مهم، در اینجا است که در عبارات SQL، خروجی کار توسط select، در ابتدای کوئری ذکر می‌شود، اما در عبارات FLOWR در انتهای آن‌ها.

از let برای انتساب مجموعه‌ای از نودها استفاده می‌شود:
 let $p := /people/person
return $p
تفاوت آن با for در این است که در هر بار اجرای حلقه‌ی for، تنها با یک نود کار خواهد شد، اما در let با مجموعه‌ای از نودها سر و کار داریم. همچنین let از نگارش 2008 اس کیوال سرور به بعد قابل استفاده‌است.

یک نکته
اگر به order by  دقت کنید، به اولین سن اشاره می‌کند. Order by در اینجا با تک مقدارها کار می‌کند و امکان کار با مجموعه‌ای از نودها را ندارد. به همین جهت باید طوری آن‌را تنظیم کرد که هربار فقط به یک مقدار اشاره کند.
هر زمانیکه به خطای requires a singleton برخوردید، یعنی دستورات مورد استفاده با یک سری از نودها کار نکرده و نیاز است دقیقا مشخص کنید، کدام مقدار مدنظر است.


مثال‌هایی از عبارات FLOWR

دو کوئری ذیل یک خروجی 1 2 3 را تولید می‌کنند
 DECLARE @x XML = '';
SELECT @x.query('
for $i in (1,2,3)
return $i
');

SELECT @x.query('
let $i := (1,2,3)
return $i
');
در کوئری اول، هر بار که حلقه اجرا می‌شود، به یکی از اعضای توالی دسترسی خواهیم داشت. در کوئری دوم، یکبار توالی تعریف شده و کار با آن در یک مرحله صورت می‌گیرد.
در ادامه اگر سعی کنیم به این کوئری‌ها یک order by را اضافه کنیم، کوئری اول با موفقیت اجرا شده،
 DECLARE @x XML = '';
SELECT @x.query('
for $i in (1,2,3)
order by $i descending
return $i
');

SELECT @x.query('
let $i := (1,2,3)
order by $i descending
return $i
');
اما کوئری دوم با خطای ذیل متوقف می‌شود:
 XQuery [query()]: 'order by' requires a singleton (or empty sequence), found operand of type 'xs:integer +'
در خطا عنوان شده‌است که مطابق تعریف، order by با یک مجموعه از نودها، مانند حاصل let کار نمی‌کند و همانند حلقه for نیاز به singleton یا atomic values دارد.


ساخت المان‌های جدید XML توسط عبارات FLOWR

ابتدا همان سند XML قسمت قبل را درنظر بگیرید:
DECLARE @doc XML  =' 
<people>
 <person>
  <name>
<givenName>name1</givenName>
<familyName>lname1</familyName>
  </name>
  <age>33</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name2</givenName>
<familyName>lname2</familyName>
  </name>
  <age>40</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name3</givenName>
<familyName>lname3</familyName>
  </name>
  <age>30</age>
  <height>medium</height>
 </person>
</people>
'
در ادامه قصد داریم، المان‌های اشخاص را صرفا بر اساس مقدار givenName آن‌ها بازگشت دهیم:
 SELECT @doc.query('
for $p in /people/person
return <person>
{$p/name[1]/givenName[1]/text()}
</person>
');
در اینجا نحوه‌ی تولید پویای تگ‌های XML را توسط FLOWR مشاهده می‌کنید. عبارات داخل {} به صورت خودکار محاسبه و جایگزین می‌شوند و خروجی آن به شرح زیر است:
 <person>name1</person>
<person>name2</person>
<person>name3</person>

سؤال: اگر به این خروجی بخواهیم یک root element اضافه کنیم، چه باید کرد؟ اگر المان root دلخواهی را در return قرار دهیم، به ازای هر آیتم یافت شده، یکبار تکرار می‌شود که مدنظر ما نیست.
 SELECT @doc.query('
<root>
{
for $p in /people/person
return <person>
{$p/name[1]/givenName[1]/text()}
</person>
}
</root>
');
بله. در این حالت نیز می‌توان از همان روشی که در return استفاده کردیم، برای کل حلقه و return آن استفاده کنیم. المان root به صورت استاتیک محاسبه می‌شود و هر آنچه که داخل {} باشد، به صورت پویا. با این خروجی:
 <root>
  <person>name1</person>
  <person>name2</person>
  <person>name3</person>
</root>


مفهوم quantification در FLOWR

همان سند Team name=Project 1 ابتدای بحث جاری را درنظر بگیرید.
 SELECT @doc.query('some $emp in //Employee satisfies $emp/@years >5')
-- true

SELECT @doc.query('every $emp in //Employee satisfies $emp/@years >5')
-- false
به عبارات some و every در اینجا quantification گفته می‌شود. در کوئری اول، می‌خواهیم بررسی کنیم، آیا در بین کارمندان، بعضی از آن‌ها دارای ویژگی (با @ شروع شده) years بیشتر از 5 هستند. در کوئری دوم، عبارت «بعضی» به «هر» تغییر یافته است. 
نظرات مطالب
EF Code First #9
 public class Person:BaseEntity
    {
        public int PersonId { get; set; }        
        [StringLength(100)]
        public string Title { get; set; }
        public PersonType PersonType { get; set; }
        public virtual ICollection<PrivacyPolicy> PrivacyPolicies { get; set; }


        public override string ToString()
        {
            return Title;
        }
    }
کلاس دوم
  [Table("Organs")]
    public class Organ:Person
    {
        [StringLength(100)]
        public string FullName { get; set; }
        [StringLength(1000)]
        public string Address { get; set; }

        public override string ToString()
        {
            return FullName;
        }
        public Organ()
        {
            PersonType = PersonType.Organ;
        }
    }
تو این مثال از Discriminator استفاده کرده ولی بعضی وقتها کوئری‌ها به این شکل ایجاد میکنه
CASE WHEN (( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL))) AND ( NOT (([UnionAll2].[C8] = 1) AND ([UnionAll2].[C8] IS NOT NULL))) AND ( NOT (([UnionAll2].[C9] = 1) AND ([UnionAll2].[C9] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll2].[C9] = 1) AND ([UnionAll2].[C9] IS NOT NULL)) THEN [UnionAll2].[C2] WHEN (([UnionAll2].[C8] = 1) AND ([UnionAll2].[C8] IS NOT NULL)) THEN CAST(NULL AS varchar(1)) END AS [C2], 
CASE WHEN (( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL))) AND ( NOT (([UnionAll2].[C8] = 1) AND ([UnionAll2].[C8] IS NOT NULL))) AND ( NOT (([UnionAll2].[C9] = 1) AND ([UnionAll2].[C9] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll2].[C9] = 1) AND ([UnionAll2].[C9] IS NOT NULL)) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll2].[C8] = 1) AND ([UnionAll2].[C8] IS NOT NULL)) THEN [UnionAll2].[C3] END AS [C3], 
CASE WHEN (( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL))) AND ( NOT (([UnionAll2].[C8] = 1) AND ([UnionAll2].[C8] IS NOT NULL))) AND ( NOT (([UnionAll2].[C9] = 1) AND ([UnionAll2].[C9] IS NOT NULL)))) THEN CAST(NULL AS int) WHEN (([UnionAll2].[C9] = 1) AND ([UnionAll2].[C9] IS NOT NULL)) THEN CAST(NULL AS int) WHEN (([UnionAll2].[C8] = 1) AND ([UnionAll2].[C8] IS NOT NULL)) THEN [UnionAll2].[C4] END AS [C4],
زمان اجرای کوئری پایین هست ، ولی حجم کد تولید شده بالاهست.
مطالب
الگوی استراتژی - Strategy Pattern
الگوی استراتژی (Strategy)  اجازه می­دهد که یک الگوریتم در یک کلاس بسته بندی شود و در زمان اجرا برای تغییر رفتار یک شیئ تعویض شود.
برای مثال فرض کنید که ما در حال طراحی یک برنامه مسیریابی برای یک شبکه هستیم. همانطوریکه می‌دانیم برای مسیر یابی الگوریتم‌های مختلفی وجود دارد که هر کدام دارای مزایا و معایبی هستند. و با توجه به وضعیت موجود شبکه یا عملی که قرار است انجام پذیرد باید الگوریتمی را که دارای بالاترین کارائی است انتخاب کنیم. همچنین این برنامه باید امکانی را به کاربر بدهد که کارائی الگوریتم‌های مختلف را در یک شبکه فرضی بررسی کنید. حالا طراحی پیشنهادی شما برای این مسئله چست؟

دوباره فرض کنید که در مثال بالا در بعضی از الگوریتم‌ها نیاز داریم که گره‌های شبکه را بر اساس فاصله‌ی آنها از گره مبداء مرتب کنیم. دوباره برای مرتب سازی الگوریتم‌های مختلف وجود دارد و هر کدام در شرایط خاص، کارائی بهتری نسبت به الگوریتم‌های دیگر دارد. مسئله دقیقا شبیه مسئله بالا است و این مسله می‌توانند دارای طراحی شبیه مسله بالا باشد. پس اگر ما بتوانیم یک طراحی خوب برای این مسئله ارائه دهیم می‌توانیم این طراحی را برای مسائل مشابه به کار ببریم.

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

الگوی استراتژی گزینه مناسبی برای مسائلی است که می‌توانند از چندین الگوریتم مختلف به مقصود خود برسند.

نمودار UML الگوی استراتژی به صورت زیر است : 


اجازه بدهید، شیوه کار این الگو را با مثال مربوط به مرتب سازی بررسی کنیم. فرض کنید که ما تصمیم گرفتیم که از سه الگویتم زیر برای مرتب سازی استفاده کنیم.

1 - الگوریتم مرتب سازی Shell Sort
2 - الگوریتم مرتب سازی Quick Sort
3 - الگوریتم مرتب سازی Merge Sort

ما برای مرتب سازی در این برنامه دارای سه استراتژی هستیم. که هر کدام را به عنوان یک کلاس جداگانه در نظر می‌گیریم (همان کلاس‌های ConcreteStrategy ). برای اینکه کلاس Client بتواند به سادگی یک از استراتژی‌ها را انتخاب کنید بهتر است که تمام کلاس‌های استراتزی دارای اینترفیس مشترک باشند. برای این کار می‌توانیم یک کلاس abstract تعریف کنیم و ویژگیهای مشترک کلاس‌های استراتژی را در آن قرار دهیم و کلاس‌های استراتژی آنها را به ارث ببرند(همان کلاس Strategy ) و پیاده سازی کنند.


در زیل کلاس Abstract که کل کلاس‌های استراتژی از آن ارث می‌برند را مشاهده می‌کنید :

abstract class SortStrategy
{
        public abstract void Sort(ArrayList list);
}
کلاس مربوط به QuickSort
class QuickSort : SortStrategy
{
        public override void Sort(ArrayList list)
        {
          // الگوریتم مربوطه   
        }
}

کلاس مربوط به ShellSort

class ShellSort : SortStrategy
{
        public override void Sort(ArrayList list)
        {
          // الگوریتم مربوطه
        }
}
کلاس مربوط به MergeSort
 class MergeSort : SortStrategy
{
        public override void Sort(ArrayList list)
        {
          // الگوریتم مربوطه
        }
}
و در آخر کلاس Context که یکی از استراتژی‌ها را برای مرتب کردن به کار می‌برد :
 class SortedList
{
        private ArrayList list = new ArrayList();
        private SortStrategy sortstrategy;

        public void SetSortStrategy(SortStrategy sortstrategy)
        {
            this.sortstrategy = sortstrategy;
        }
        public void Add(string name)
        {
            list.Add(name);
        }
        public void Sort()
        {
            sortstrategy.Sort(list);
        }
}
نظرات مطالب
نوشتن اعتبارسنج‌های سفارشی برای فرم‌های مبتنی بر قالب‌ها در Angular
شبیه به پیاده سازی EmailValidatorDirective مطلب جاری، از Regex ذیل استفاده کنید:
[RegularExpression(@"^[\u0600-\u06FF,\u0590-\u05FF,0-9\s]*$",
                          ErrorMessage = "لطفا تنها از اعداد و حروف فارسی استفاده نمائید")]
public string FriendlyName { get; set; }
نظرات مطالب
خودکارسازی فرآیند نگاشت اشیاء در AutoMapper
ممنونم. مشکل قبل حل شد. 
 اگر ویومدل من بدینصورت باشه
public class MyViewModel : IHaveCustomMappings
    {
       
        public int SchedulerId { get; set; }
        public bool IsApplied { get; set; }
        public int Qty { get; set; }
        
        public void CreateMappings(IMapperConfigurationExpression configuration)
        {
            configuration.CreateMap<DomainModels.Models.Scheduler, MyViewModel>()
                .ForMember(dest => dest.SchedulerId, opt => opt.MapFrom(src => src.Id));
                    }
    }
خطایی صادر میکندبا این عنوان: 
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
اگر دو فیلد IsApplied  ,    Qty   هم در متد  CreateMappings بصورت دستی map کنم خطایی صادر نمیشود. در صورتیکه قبلا با اتوماتیک مپ میشدند. آیا تغییری در AutoMapperRegistry  نیاز هست؟
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
اگر یک پروژه‌ی خالی ASP.NET Core Web Application را شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
 .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
تغییرات ساختاری ASP.NET Core 1.0، با نگارش‌های قبلی ASP.NET، بسیار قابل ملاحظه هستند:


در اینجا نقش Solution همانند نگارش‌های قبلی ویژوال استودیو است: ظرفی است برای ساماندهی موارد مورد نیاز جهت تشکیل یک برنامه‌ی وب و شامل مواردی است مانند پروژه‌ها، تنظیمات آن‌ها و غیره. بنابراین هنوز در اینجا فایل sln. تشکیل می‌شود.


نقش فایل global.json

زمانیکه یک پروژه‌ی جدید ASP.NET Core 1.0 را آغاز می‌کنیم، ساختار پوشه‌های آن به صورت زیر هستند:


در اینجا هنوز فایل sln. قابل مشاهده است. همچنین در اینجا فایل جدیدی به نام global.json نیز وجود دارد، با این محتوا:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview2-003121"
  }
}
شماره نگارش ذکر شده‌ی در اینجا را در قسمت قبل بررسی کردیم.
خاصیت projects در اینجا به صورت یک آرایه تعریف شده‌است و بیانگر محل واقع شدن پوشه‌های اصلی پروژه‌ی جاری هستند. پوشه‌ی src یا source را در تصویر فوق مشاهده می‌کنید و محلی است که سورس‌های برنامه در آن قرار می‌گیرند. یک پوشه‌ی test نیز در اینجا ذکر شده‌است و اگر در حین ایجاد پروژه، گزینه‌ی ایجاد unit tests را هم انتخاب کرده باشید، این پوشه‌ی مخصوص نیز ایجاد خواهد شد.
نکته‌ی مهم اینجا است، هرکدی که درون پوشه‌های ذکر شده‌ی در اینجا قرار نگیرد، قابلیت build را نخواهد داشت. به عبارتی این نسخه‌ی از ASP.NET پوشه‌ها را قسمتی از پروژه به حساب می‌آورد. در نگارش‌های قبلی ASP.NET، مداخل تعریف فایل‌های منتسب به هر پروژه، درون فایلی با پسوند csproj. قرار می‌گرفتند. معادل این فایل در اینجا اینبار پسوند xproj را دارد و اگر آن‌را با یک ادیتور متنی باز کنید، فاقد تعاریف مداخل فایل‌های پروژه است.
در این نگارش جدید اگر فایلی را به پوشه‌ی src اضافه کنید یا حذف کنید، بلافاصله در solution explorer ظاهر و یا حذف خواهد شد.
یک آزمایش: به صورت معمول از طریق windows explorer به پوشه‌ی src برنامه وارد شده و فایل پیش فرض Project_Readme.html را حذف کنید. سپس به solution explorer ویژوال استودیو دقت کنید. مشاهده خواهید کرد که این فایل، بلافاصله از آن حذف می‌شود. در ادامه به recycle bin ویندوز مراجعه کرده و این فایل حذف شده را restore کنید تا مجددا به پوشه‌ی src برنامه اضافه شود. اینبار نیز افزوده شدن خودکار و بلافاصله‌ی این فایل را می‌توان در solution explorer مشاهده کرد.
بنابراین ساختار مدیریت فایل‌های این نگارش از ASP.NET در ویژوال استودیو، بسیار شبیه به ساختار مدیریت فایل‌های VSCode شده‌است که آن نیز بر اساس پوشه‌ها کار می‌کند و یک پوشه و تمام محتوای آن‌را به صورت پیش فرض به عنوان یک پروژه می‌شناسد. به همین جهت دیگر فایل csproj ایی در اینجا وجود ندارد و file system همان project system است.

یک نکته: در اینجا مسیرهای مطلق را نیز می‌توان ذکر کرد:
  "projects": [ "src", "test", "c:\\sources\\Configuration\\src" ],
اما در مورد هر مسیری که ذکر می‌شود، NET Core. باید بتواند یک سطح پایین‌تر از پوشه‌ی ذکر شده، فایل مهم project.json را پیدا کند؛ در غیراینصورت از آن صرفنظر خواهد شد. برای مثال برای مسیر نسبی src، مسیر src\MyProjectName\project.json را جستجو می‌کند و برای مسیر مطلق ذکر شده، این مسیر را c:\\sources\\Configuration\\src\\SomeName\\project.json


کامپایل خودکار پروژه در ASP.NET Core 1.0

علاوه بر تشخیص خودکار کم و زیاد شدن فایل‌های سیستمی پروژه، بدون نیاز به Add new item کردن آن‌ها در ویژوال استودیو، اگر سورس‌های برنامه را نیز تغییر دهید، فایل سورس جدیدی را اضافه کنید و یا فایل سورس موجودی را حذف کنید، کل پروژه به صورت خودکار کامپایل می‌شود و نیازی نیست این‌کار را به صورت دستی انجام دهید.
یک آزمایش: برنامه را از طریق منوی debug و گزینه‌ی start without debugging اجرا کنید. اگر برنامه را در حالت معمول debug->start debugging اجرا کنید، حالت کامپایل خودکار را مشاهده نخواهید کرد. در اینجا (پس از start without debugging) یک چنین خروجی را مشاهده خواهید کرد:


این خروجی حاصل اجرای کدهای درون فایل Startup.cs برنامه است:
 app.Run(async (context) =>
{
   await context.Response.WriteAsync("Hello World!");
});
اکنون در همین حال که برنامه در حال اجرا است و هنوز IIS Express خاتمه نیافته است، از طریق windows explorer، به پوشه‌ی src برنامه وارد شده و فایل Startup.cs را با notepad باز کنید. هدف این است که این فایل را در خارج از ویژوال استودیو ویرایش کنیم. اینبار سطر await دار را در notepad به نحو ذیل ویرایش کنید:
 await context.Response.WriteAsync("Hello DNT!");
پس از آن اگر مرورگر را refresh کنید، بلافاصله خروجی جدید فوق را مشاهده خواهید کرد که بیانگر کامپایل خودکار پروژه در صورت تغییر فایل‌های آن است.
این مساله قابلیت استفاده‌ی از ASP.NET Core را در سایر ادیتورهای موجود، مانند VSCode سهولت می‌بخشد.


نقش فایل project.json

فایل جدید project.json مهم‌ترین فایل تنظیمات یک پروژه‌ی ASP.NET Core است و مهم‌ترین قسمت آن، قسمت وابستگی‌های آن است:
"dependencies": {
  "Microsoft.NETCore.App": {
    "version": "1.0.0",
    "type": "platform"
  },
  "Microsoft.AspNetCore.Diagnostics": "1.0.0",
  "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
  "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
  "Microsoft.Extensions.Logging.Console": "1.0.0"
},
همانطور که در قسمت قبل نیز عنوان شد، در این نگارش از دات نت، تمام وابستگی‌های پروژه از طریق نیوگت تامین می‌شوند و دیگر خبری از یک دات نت «بزرگ» که شامل تمام اجزای این مجموعه‌است نیست. این مساله توزیع برنامه را ساده‌تر کرده و همچنین امکان به روز رسانی سریع‌تر این اجزا را توسط تیم‌های مرتبط فراهم می‌کند؛ بدون اینکه نیازی باشد تا منتظر یک توزیع «بزرگ» دیگر ماند.
در نگارش‌های قبلی ASP.NET، فایلی XML ایی به نام packages.config حاوی تعاریف مداخل بسته‌های نیوگت برنامه بود. این فایل در اینجا جزئی از محتوای فایل project.json در قسمت dependencies آن است.
با قسمت وابستگی‌های این فایل، به دو طریق می‌توان کار کرد:
الف) ویرایش مستقیم این فایل که به همراه intellisense نیز هست. با افزودن مداخل جدید به این فایل و ذخیره کردن آن، بلافاصله کار restore و دریافت و نصب آن‌ها آغاز می‌شود:


ب) از طریق NuGet package manager
روش دیگر کار با وابستگی‌ها، کلیک راست بر روی گره references و انتخاب گزینه‌ی manage nuget packages است:


برای نمونه جهت نصب ASP.NET MVC 6 این مراحل باید طی شوند:


ابتدا برگه‌ی browse را انتخاب کنید و سپس تیک مربوط به include prerelease را نیز انتخاب نمائید.
البته بسته‌ی اصلی MVC در اینجا Microsoft.AspNetCore.Mvc نام دارد و نه MVC6.

اینبار بسته‌هایی که restore می‌شوند، در مسیر اشتراکی C:\Users\user_name\.nuget\packages ذخیره خواهند شد.


یک نکته‌ی مهم:
قرار هست در نگارش‌های پس از RTM، فایل‌های project.json و xproj را جهت سازگاری با MSBuild، اندکی تغییر دهند (که این تغییرات به صورت خودکار توسط VS.NET انجام می‌شود). اطلاعات بیشتر


انتخاب فریم ورک‌های مختلف در فایل project.json

در قسمت قبل عنوان شد که ASP.NET Core را می‌توان هم برفراز NET Core. چندسکویی اجرا کرد و هم NET 4.6. مختص به ویندوز. این انتخاب‌ها در قسمت frameworks فایل project.json انجام می‌شوند:
"frameworks": {
  "netcoreapp1.0": {
    "imports": [
      "dotnet5.6",
      "portable-net45+win8"
    ]
  }
},
در اینجا، netcoreapp1.0 به معنای برنامه‌‌ای است که برفراز NET Core. اجرا می‌شود. نام پیشین آن dnxcore50 بود (اگر مقالات قدیمی‌تر پیش از RTM را مطالعه کنید).
در اینجا اگر علاقمند بودید که از دات نت کامل مخصوص ویندوز نیز استفاده کنید، می‌توانید آن‌را در لیست فریم ورک‌ها اضافه نمائید (در این مثال، دات نت کامل 4.5.2 نیز ذکر شده‌است):
 "frameworks": {
    "netcoreapp1.0": {
    },
    "net452": {
    }
  }
لیست کامل این اسامی را در مستندات NET Starndard می‌توانید مطالعه کنید و خلاصه‌ی آن به این صورت است:
- “netcoreapp1.0” برای معرفی و استفاده‌ی از NET Core 1.0. بکار می‌رود.
- جهت معرفی فریم ورک‌های کامل و ویندوزی دات نت، اسامی “net45”, “net451”, “net452”, “net46”, “net461” مجاز هستند.
-  “portable-net45+win8” برای معرفی پروفایل‌های PCL یا portable class libraries بکار می‌رود.
- “dotnet5.6”, “dnxcore50” برای معرفی نگارش‌های پیش نمایش NET Core.، پیش از ارائه‌ی نگارش RTM استفاده می‌شوند.
- “netstandard1.2”, “netstandard1.5” کار معرفی برنامه‌های NET Standard Platform. را انجام می‌دهند.

بر این مبنا، dotnet5.6 ذکر شده‌ی در قسمت تنظیمات نگارش RTM، به این معنا است که قادر به استفاده‌ی از بسته‌های نیوگت و کتابخانه‌های تولید شده‌ی با نگارش‌های RC نیز خواهید بود (هرچند برنامه از netcoreapp1.0 استفاده می‌کند).

یک مثال: قسمت فریم ورک‌های فایل project.json را به نحو ذیل جهت معرفی دات نت 4.6.1 تغییر دهید:
"frameworks": {
  "netcoreapp1.0": {
      "imports": [
          "dotnet5.6",
          "portable-net45+win8"
      ]
  },
  "net461": {
      "imports": [
          "portable-net45+win8"
      ],
      "dependencies": {
      }
  }
},
لیست وابستگی‌های خاص این فریم ورک در خاصیت dependencies آن قابل ذکر است.
در این حالت پس از ذخیره‌ی فایل و شروع خودکار بازیابی وابستگی‌ها، با پیام خطای Package Microsoft.NETCore.App 1.0.0 is not compatible with net461 متوقف خواهید شد.
برای رفع این مشکل باید وابستگی Microsoft.NETCore.App را حذف کنید، چون با net461 سازگاری ندارد
 "dependencies": {
 //"Microsoft.NETCore.App": {
 //  "version": "1.0.0",
 //  "type": "platform"
 //},

فریم ورک دوم استفاده شده نیز در اینجا قابل مشاهده است.


فایل project.lock.json چیست؟


ذیل فایل project.json، فایل دیگری به نام project.lock.json نیز وجود دارد. اگر به محتوای آن دقت کنید، این فایل حاوی لیست دقیق بسته‌های نیوگت مورد استفاده‌ی توسط برنامه است و الزاما با آن‌چیزی که در فایل project.json قید شده، یکی نیست. از این جهت که در فایل project.json، قید می‌شود netcoreapp1.0؛ ولی این netcoreapp1.0 دقیقا شامل چه بسته‌هایی است؟ لیست کامل آن‌ها را در این فایل می‌توانید مشاهده کنید.
در ابتدای این فایل یک خاصیت locked نیز وجود دارد که مقدار پیش فرض آن false است. اگر به true تنظیم شود، در حین restore وابستگی‌های برنامه، تنها از نگارش‌های ذکر شده‌ی در این فایل استفاده می‌شود. از این جهت که در فایل project.json می‌توان شماره نگارش‌ها را با * نیز مشخص کرد؛ مثلا *.1.0.0


پوشه‌ی جدید wwwroot و گره dependencies

یکی از پوشه‌های جدیدی که در ساختار پروژه‌ی ASP.NET Core معرفی شده‌است، wwwroot نام دارد:


از دیدگاه هاستینگ برنامه، این پوشه، پوشه‌ای است که در معرض دید عموم قرار می‌گیرد (وب روت). برای مثال فایل‌های ایستای اسکریپت، CSS، تصاویر و غیره باید در این پوشه قرار گیرند تا توسط دنیای خارج قابل دسترسی و استفاده شوند. بنابراین سورس کدهای برنامه خارج از این پوشه قرار می‌گیرند.
گره dependencies که ذیل پوشه‌ی wwwroot قرار گرفته‌است، جهت مدیریت این وابستگی‌های سمت کلاینت برنامه است. در اینجا می‌توان از NPM و یا Bower برای دریافت و به روز رسانی وابستگی‌های اسکریپتی و شیوه‌نامه‌های برنامه کمک گرفت (علاوه بر نیوگت که بهتر است صرفا جهت دریافت وابستگی‌های دات نتی استفاده شود).
یک مثال: فایل جدیدی را به نام bower.json به پروژه‌ی جاری با این محتوا اضافه کنید:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.6",
    "jquery": "2.2.0",
    "jquery-validation": "1.14.0",
    "jquery-validation-unobtrusive": "3.2.6"
  }
}
این نام‌ها استاندارد هستند. برای مثال اگر قصد استفاده‌ی از npm مربوط به node.js را داشته باشید، نام این فایل packag.json است (با ساختار خاص خودش) و هر دوی این‌ها را نیز می‌توانید با هم اضافه کنید و از این لحاظ محدودیتی وجود ندارد.
پس از اضافه شدن فایل bower.json، بلافاصله کار restore بسته‌ها از اینترنت شروع می‌شود:


و یا با کلیک راست بر روی گره dependencies، گزینه‌ی restore packages نیز وجود دارد.
فایل‌های نهایی دریافت شده را در پوشه‌ی bower_components خارج از wwwroot می‌توانید مشاهده کنید.

در مورد نحوه‌ی توزیع و دسترسی به فایل‌های استاتیک یک برنامه‌ی ASP.NET Core 1.0، نکات خاصی وجود دارند که در قسمت‌های بعد، بررسی خواهند شد.

یک نکته: اگر خواستید نام پوشه‌ی wwwroot را تغییر دهید، فایل جدیدی را به نام hosting.json با این محتوا به پروژه اضافه کنید:
{
    "webroot":"AppWebRoot"
}
در اینجا AppWebRoot نام دلخواه پوشه‌ی جدیدی است که نیاز است به ریشه‌ی پروژه اضافه نمائید تا بجای wwwroot پیش فرض استفاده شود.


نقطه‌ی آغازین برنامه کجاست؟

اگر به فایل project.json دقت کنید، چنین تنظیمی در آن موجود است:
"buildOptions": {
  "emitEntryPoint": true,
  "preserveCompilationContext": true
},
true بودن مقدار تولید entry point به استفاده‌ی از فایلی به نام Program.cs بر می‌گردد؛ با این محتوا:
public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();
 
    host.Run();
}
به این ترتیب، یک برنامه‌ی ASP.NET Core، دقیقا همانند سایر برنامه‌های NET Core. رفتار می‌کند و در اساس یک برنامه‌ی کنسول است.
 var outputKind = compilerOptions.EmitEntryPoint.GetValueOrDefault() ?
OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary;
این چند سطر، قسمتی از سورس کد ASP.NET Core 1.0 هستند. به این معنا که اگر مقدار خاصیت emitEntryPoint مساوی true بود، با این برنامه به شکل یک برنامه‌ی کنسول رفتار کن و در غیر اینصورت یک Dynamically Linked Library خواهد بود.
در فایل Program.cs تنظیماتی را مشاهده می‌کنید، در مورد راه اندازی Kestler که وب سرور بسیار سریع و چندسکویی کار با برنامه‌های ASP.NET Core 1.0 است و قسمت مهم دیگر آن به استفاده‌ی از کلاس Startup بر می‌گردد (()<UseStartup<Startup). این کلاس را در فایل جدید Startup.cs می‌توانید ملاحظه کنید که کار تنظیمات آغازین برنامه را انجام می‌دهد. اگر پیشتر با OWIN، در نگارش‌های قبلی ASP.NET کار کرده باشید، قسمتی از این فایل برای شما آشنا است:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
در اینجا امکان دسترسی به تنظیمات برنامه، معرفی سرویس‌ها، middleware‌ها و غیره وجود دارند که هرکدام، در قسمت‌های آتی به تفصیل بررسی خواهند شد.


نقش فایل launchsetting.json


محتویات پیش فرض این فایل برای قالب empty پروژه‌های ASP.NET Core 1.0 به صورت ذیل است:
{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:7742/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Core1RtmEmptyTest": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
همانطور که مشاهده می‌کنید، در اینجا تنظیمات IIS Express قابل تغییر هستند. برای مثال port پیش فرض برنامه 7742 تنظیم شده‌است.
پروفایل‌هایی که در اینجا ذکر شده‌اند، در تنظیمات پروژه نیز قابل مشاهده هستند: (کلیک راست بر روی پروژه و مشاهده‌ی properties آن و یا دوبار کلیک بر روی گره properties)


به علاوه امکان انتخاب این پروفایل‌ها در زمان آغاز برنامه نیز وجود دارند:


نکته‌ی مهم تمام این موارد به قسمت environment variable قابل مشاهده‌ی در تصاویر فوق بر می‌گردد. این متغیر محیطی می‌تواند سه مقدار Development ، Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن، می‌توان پروفایل جدیدی را تشکیل داد. زمانیکه برنامه بر اساس پروفایل خاصی بارگذاری می‌شود، اینکه چه متغیر محیطی انتخاب شده‌است، در کلاس Startup قابل استخراج و بررسی بوده و بر این اساس می‌توان اقدامات خاصی را انجام داد. برای مثال تنظیمات خاصی را بارگذاری کرد و یا صفحات ویژه‌ای را فعال و غیرفعال کرد (این موارد را در قسمت‌های بعدی مرور می‌کنیم).
همچنین در اینجا به ازای هر پروفایل مختلف می‌توان Url آغازین یا launch url و پورت آن‌را مجزا درنظر گرفت و یا از وب سرور دیگری استفاده کرد.
مطالب
بررسی Bad code smell ها: میراث رد شده

میراث رد شده یا Refused bequest به دسته «بد استفاده کنندگان از شیء گرایی» تعلق دارد. این دسته از کدهای بد بو، معمولا استفاده ناقص یا نادرستی از مفاهیم و اصول شیء گرایی دارند. 
زمانیکه یک کلاس تنها بخشی از اعضای (خصوصیت، متد و ...) کلاس پدر خود را استفاده می‌کند، با این الگو سر و کار داریم. در چنین شرایطی دیگر اعضای کلاس پدر یا استفاده نمی‌شوند و یا حتی در صورت پیاده سازی شدن توسط کلاس، بلااستفاده می‌مانند. به طور مثال متدهایی از کلاس پدر پیاده سازی می‌شوند و با پرتاب یک استثناء در بدنه‌شان از کار می‌افتند. 
یکی از دلایل مهم ایجاد چنین کدهای بد بویی، ایجاد رابطه ارث بری تنها برای استفاده دوباره از کدهای یک کلاس است. در صورتیکه ممکن است کلاس پدر و فرزند هیچ ارتباط منطقی ای از نظر ارث بری با یکدیگر نداشته باشند.   
به طور مثال فرض کنید در حال توسعه یک محصول هستید که در آن فروش کالا اتفاق می‌افتد. در ابتدای کار، تنها کاربران این محصول، شرکت‌ها هستند. روالی نیز برای محاسبه تخفیف مربوط به شرکت‌ها ایجاد شده است. در این روال یک تخفیف پایه وجود دارد که به همه مشتریان تعلق می‌گیرید و تخفیف‌هایی نیز وجود دارند که به هر یک از شرکت‌ها تعلق می‌گیرند. میزان تخفیف نهایی برای یک شرکت از مجموع این دو مقدار بدست می‌آید. کلاس مربوط به محاسبه تخفیف به این صورت است: 

public class DiscountCalculator 
{ 
    protected decimal CalculateGeneralDiscount() 
    { 
        // calculate general discount 
        return 0; 
    } 
    protected decimal AddSpecificDiscount(decimal baseDiscount) 
    { 
        // add specific discounts to base discount 
        return 0; 
    } 
    protected virtual decimal GetFinalDiscount() 
    { 
        var baseDiscount = CalculateGeneralDiscount(); 
        var addedDiscount = AddSpecificDiscount(baseDiscount); 
        return addedDiscount; 
    } 
}
بعد از مدتی نیاز می‌شود که افراد حقیقی از نرم افزار استفاده کرده و خرید نمایند و این مکانیزم تخفیف برای آن‌ها نیز اعمال شود. توسعه دهنده بعد از بررسی به این نتیجه می‌رسد که تنها تفاوت این مکانیزم با مکانیزم قبلی، متد AddSpecificDiscount است که باید برای اشخاص حقیقی بازنویسی شود. این توسعه دهنده تصمیم می‌گیرد برای استفاده دوباره از کد موجود در کلاس DiscountCalculator، کلاس مربوط به اشخاص حقیقی را از آن ارث ببرد. که نتیجه به صورت زیر خواهد بود:
public class PersonDiscountCalculator : DiscountCalculator 
{ 
    private decimal AddSpecificDiscountForPerson(decimal baseDiscount) 
    { 
        // calculate base discount for person 
        return 0; 
    } 
    protected override decimal GetFinalDiscount() 
    { 
        var baseDiscount = CalculateGeneralDiscount(); 
        var added = AddSpecificDiscountForPerson(baseDiscount); 
        return added; 
    } 
}

همانطور که در کد بالا مشاهده می‌کنید، متد مربوط به افزودن تخفیف‌های خاص شخص از نو نوشته شده و متد مربوط به محاسبه تخفیف‌های پایه استفاده شده و متد GetFinalDiscount دوباره نویسی شده تا با مکانیزم جدید همخوانی داشته باشد.
در این مثال متد مربوط به محاسبه تخفیف برای شرکت، علارغم اینکه به دلیل رابطه ارث بری در کلاس فرزند قابل دسترس است، بلااستفاده مانده است. و حتی وجود چنین متدی در کلاس PersonDiscountCalculator نیز بی معنی است. در واقع کلاس PersonDiscountCalculator برخی از میراث کلاس پدر خود را رد کرده است.

روش‌های اصلاح این نوع کد بد بو 

1) زمانیکه رابطه ارث بری هیچ معنایی ندارد، می‌توان به جای استفاده از ارث بری، شیء مربوط به کلاس پدر را ایجاد و در بدنه کلاس فرزند از آن استفاده کرد. مثلا برای فراخوانی متدها یا دسترسی به خصوصیت‌ها (مثال ذکر شده در این دسته نیست).
2) زمانیکه رابطه منطقی وجود داشته باشد، ولی اعضای اضافی کلاس پدر، به فرزند به ارث برسند، راه حل معمولا در جدا کردن کلاس پدر و ایجاد یک کلاس جدید پدر برای مصرف مورد نظر است.  
در مثال مطرح شده رابطه منطقی بین کلاس‌ها وجود دارد؛ ولی نه به شکلی که پیاده سازی شده است. یکی از راه‌های مناسب برای رفع چنین مشکلی، ایجاد یک کلاس پدر مشترک (در اینجا DiscountCalculator) و انتقال منطق‌های مربوطه به آن و ارث بری کردن تک تک کلاسها از آن است.  
public abstract class DiscountCalculator 
{ 
    protected decimal CalculateGeneralDiscount() 
    { 
        // calculate general discount 
        return 0; 
    } 
    protected abstract decimal AddSpecificDiscount(decimal baseDiscount); 
    protected virtual decimal GetFinalDiscount() 
    { 
        var baseDiscount = CalculateGeneralDiscount(); 
        var addedDiscount = AddSpecificDiscount(baseDiscount); 
        return addedDiscount; 
    } 
}

کلاس DiscountCalculator به عنوان کلاس پایه برای محاسبات تخفیف تعریف شده و کلاس‌های مربوط به شرکت و شخص از آن ارث بری می‌کنند. به این صورت که محاسبات کلی تخفیف‌ها در کلاس پایه و محاسبات مربوط به افزودن تخفیف‌های خاص در کلاس‌های فرزند انجام می‌شود (با فرض این که محاسبات مربوط به افزودن تخفیف‌های خاص تفاوت دارند).   
public class CompanyDiscountCalculator: DiscountCalculator 
{ 
    protected override decimal AddSpecificDiscount(decimal baseDiscount) 
        { 
            // add some customer specific discounts to base discount 
            return 0; 
        } 
    } 
    public class PersonDiscountCalculator : DiscountCalculator 
    { 
        protected override decimal AddSpecificDiscount(decimal baseDiscount) 
        { 
            // calculate base discount for person 
            return 0; 
        } 
}

جمع بندی 

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

مطالب
استفاده از دیتابیس Sqlite در الکترون (قسمت دوم)
در مقاله قبلی با یکی از کتابخانه‌های مدیریت دیتابیس sqlite آشنا شدیم و و یاد گرفتیم که چگونه یک دیتابیس جدید را بسازیم و اطلاعات را از آن دریافت کنیم. در این مقاله قصد داریم، بیشتر در مورد دستورات این کتابخانه بدانیم و بفهمیم که چگونه باید آن‌ها را به کار بست.

دستورات بدون خروجی:
یک سری از دستورات هستند که خروجی ندارند و رکوردی را باز نمی‌گردانند و برای اجرای دستوراتی چون افزودن، به روزرسانی و حذف بسیار مناسبند. اجرای  این دستورات را ما به متدی به نام run می‌سپاریم. در دفعه قبل که از این دستور استفاده کردیم، پارامتری برای تعیین کردن نداشت؛ ولی در این مقاله، دستور با پارامتر آن را اجرا می‌کنیم:
ابتدا کدهای زیر را به فایل html، برای درج رکورد جدید اضافه می‌کنیم:
First Name:<br/>
<input type="text" id="txtfname" /><br/>
Last Name:<br/>
<input type="text" id=txtlname /><br/>
Number:<br/>
<input type="tel" id="txttel" /><br/>
<button id="btnsubmit">Save</button><br/>
برای خواندن مقادیر هم از این تابع کمک می‌گیریم:
function GetValues()
{
  let fname=$("#txtfname").val();
  let lname=$("#txtlname").val()
  let tel=$("#txttel").val();
  let row=
  {
    fname:fname,
    lname:lname,
    number:tel
  };
  return row;
}
سپس تکه کد زیر را  با کمک  جی کوئری اضافه می‌کنیم:
$("#btnsubmit").click((e)=>{
  e.preventDefault();
  let row=GetValues();

  //save in db
  //get last id
  let statement==db.prepare("select id from numbers order by id desc limit 1");
  let lastRecord=statement.getAsObject({}).id;
  row.id=lastRecord++;
  let count=db.prepare("select count(*) as count from numbers order by id desc").getAsObject({}).count;
  statement.free();
  let insertCommand="insert into numbers values(?,?,?,?)";
  db.run(insertCommand,[row.id,row.fname,row.lname,String(row.number)])
  let newcount=db.prepare("select count(*) as count from numbers order by id desc").getAsObject({}).count;
  SaveChanges();

//show in the table
if(count<newcount)
{
  AddToTable(row);
}
});
});
متد GetValues  مقادیر را از input‌ها دریافت و در متغیر row نگهداری می‌کند. سپس برای درج رکورد، از آنجاکه ما فیلد id را افزایشی تعیین نکرده‌ایم، باید آخرین رکورد موجود در جدول را واکشی کنیم که برای این منظور از متد prepare کمک می‌گیریم. این متد در عوض کوئری داده شده، یک شیء statement را بر می‌گرداند که حاوی نتایج کوئری داده شده است؛ ولی هنوز به مرحله اجرا نرسیده است. برای اجرای دستور کوئری، دو متد وجود دارند که یکی step است و دیگری getAsObject می‌باشد. متد getAsObject برای زمانی خوب است که شما در حد یک رکورد خروجی دارید و می‌خواهید همان یک رکورد را به صورت شیء برگردانید؛ یعنی با "نام شی.نام خصوصیت" به آن دسترسی پیدا کنید. همانطور که می‌بینید ما درخواست فیلد id را کرده‌ایم و تنها یک رکورد را درخواست کرده‌ایم که باعث می‌شود به راحتی به فیلد id دسترسی داشته باشیم. وجود علامت {} داخل این متد به این معنی است که پارامتری برای ارسال وجود ندارد. ولی اگر قرار بود پارامتری را داشته باشیم، به این شکل می‌نوشتیم:
var statement= db.prepare("SELECT * FROM NUMBERS WHERE fname=@fname AND lname=@lname");

var result = statement .getAsObject({'@fname' :'ali', '@lname' : 'yeganeh'});
اگر دوست داشتید دوباره از این دستور کوئری بگیرید ولی با مقادیر متفاوت، می‌توانید از متد bind کمک بگیرید:
statement.bind(['hossein','yeganeh']);
متد دیگری هم برای پاکسازی پارامترها به نام reset، وجود دارد که فضای اختصاصی و گرفته شده توسط پارامترها را باز می‌گرداند.

متد step همانند متدهای next در cursor یا read در datareader عمل میکند و با هر بار صدا زدن، یک رکورد، به سمت جلو حرکت می‌کند. دریافت هر رکورد جاری توسط متد get و نوع خروجی آرایه انجام می‌شود:
while(statement.step())
{
     var rec=statement.get();
}
بعد از اینکه کارمان با آن تمام شد، برای پاکسازی حافظه از متد free استفاده می‌کنیم. در دستورات بعد، شیء statement را مستقیما مورد استفاده قرار داده‌ایم و توسط آن تعداد رکوردها را دریافت کرده‌ایم. سپس با استفاده از متد run دستور درج را داده‌ایم. اینبار این متد را به شکل متفاوتی استفاده کردیم و به آن پارامتری هم دادیم. نحوه ارائه پارامتر به این متد، باید به صورت آرایه و به ترتیب علامت‌های ؟ باشد. نهایتا با دریافت تعداد رکوردهای جاری و مقایسه با تعداد رکوردهای سابق متوجه می‌شویم که آیا رکوردی اضافه شده است یا خیر؟ در صورتی که اضافه شده است، باید رکورد جدید در جدول، توسط جی کوئری نمایش داده شود و تغییرات دیتابیس هم روی دیسک سخت ذخیره شوند. چون دیتابیس مورد استفاده به صورت in-memory یعنی مقیم در حافظه مورد استفاده قرار میگیرد، باید کل دیتابیس، بر روی دیسک سخت رونویسی شود. متد SaveChanges شامل کد زیر است که حاوی کد ارسال پیام به Main Thread یا Main Process می‌باشد تا در دیسک سخت بنویسد:
  const {ipcRenderer} = require('electron');
function SaveChanges()
{
    ipcRenderer.send("SaveToDb");
}
و برای ترد اصلی:
const {ipcMain} = require('electron');
ipcMain.on("SaveToDb", (event, arg) => {
  SaveToDb();
});

function SaveToDb()
{
  var data=db.export();
  var buffer=new Buffer(data);
  fs.writeFileSync(dbPath,buffer);
}

پی نوشت :یکی از بهترین روش‌ها این است که از همین تعداد سطرها برای id رکورد استفاده کنیم. چرا که فیلد id به عنوان کلید اصلی است و چینش فیزیکی دارد و از لحاظ کارایی بهتر است ولی به علت آموزشی بودن مطلب و آشنایی با دیگر دستورات، این شیوه را انتخاب کردیم.

ویرایش رکورد
ابتدا template string سطر جدول را به شکل زیر تغییر می‌دهیم:
function AddToTable(row)
{
  let tableBody=$("#people");
  let rowTemplate=`<tr><td>${row.fname}</td><td>${row.lname}</td><td>${row.number}</td><td><button class=
  "btn btn-success btnupdate" data-id="${row.id}" >Edit</button></td></tr>`;
  tableBody.append(rowTemplate);
}
تغییری که رخ داده است این است که یک دکمه ویرایش، به صفحه اضافه شده‌است که خصوصیت data-id آن با id رکورد پر خواهد شد.
سپس در تگ اسکریپت، در رویداد ready جی کوئری، این دستورات را اضافه می‌کنیم:
$("#people").on('click','.btnupdate',function(e)
{
  e.preventDefault();

  row.id=$(this).data("id");
  let row=GetValues();
  db.run("UPDATE NUMBERS SET FNAME=?,LNAME=?,NUMBER=? WHERE ID=?",[row.fname,row.lname,row.number,row.id]);
  SaveChanges();
  
  tr=$(this).closest("tr");
  let column=0;
  tr.find("td").each(function(index)
  {
    oldRow=$(this);
    switch(column)
    {
      case 0: //fname
        oldRow.text(row.fname);
        break;
      case 1: //lname
        oldRow.text(row.lname);
        break;
      case 2: //number
        oldRow.text(row.number);
        break;
    }
    column++;
  });
});
ابتدای id ذخیره شده در المان و مقادیر جدید را دریافت می‌کنیم. با استفاده از متد run کوئری به روزرسانی را به همراه پارامترها ارسال میکنیم و نتیجه را بر روی دیسک سخت ذخیره می‌کنیم. از اینجا به بعد نقش جی کوئری پر رنگ‌تر می‌شود و به خوبی می‌توانیم اهمیت آن را درک کنیم. سطر دکمه جاری را پیدا می‌کنیم و مقادیر جدید را ستون به ستون تغییر می‌دهیم.

خواندن و بازگردانی رکوردها

در مقاله قبلی با دستور each آشنا شدیم که یک متد غیرهمزمان بود و نتیجه هر رکورد را با یک callback به ما بازگشت میداد. در اصل این متد شامل 4 پارامتر است: پارامتر اول آن، کوئری ارسالی است. پارامتر دوم آن، پارامتر کوئری‌ها ، پارامتر سوم، تابع callback که به ازای هر رکورد اجرا می‌شود و پارامتر چهارم، تابع done می باشد. یعنی زمانی که کلیه رکوردها بازگشت داده شدند. شکل کامل آن به این صورت است:
db.each("SELECT name,age FROM users WHERE age >= $majority",
  {$majority:18},
  function(row){console.log(row.name)},
                                function(){console.log("done");}
  );


در این مقاله با متد دیگری به نام exec نیز آشنا می‌شویم که بازگردانی مقادیر در آن به صورت همزمان صورت می‌گیرد و توانایی آن را دارد که چندین دستور select را بازگردانی کند. به عنوان مثال دستور زیر را در نظر بگیرید:
SELECT ID FROM NUMBERS;SELECT FNAME,LNAME FROM NUMBERS
شکل خروجی آن به این صورت خواهد بود:
 [
         {columns: ['id'], values:[[1],[2],[3]]},
         {columns: ['fname','lname'], values:[['ali','yeganeh'],['hossein','yeganeh'],['mohammad','yeganeh']]}
    ]
پس اگر بخواهیم کد بازیابی رکورهای جدول numbers را در این پروژه با این متد بازنویسی کنیم، از کد زیر استفاده می‌کنیم:
var records=db.exec("select * from numbers");
let values=records[0].values;
let length=values.length;

for(let i=0;i<length;i++)
{
  let object=values[i];
  let row={
    id:object[0],
    fname:object[1],
    lname:object[2],
    number:object[3]
  };
  AddToTable(row);
}
در کد بالا از آنجا که تنها یک data result یا query result داریم، فقط اندیس 0 آن را می‌خوانیم و سپس تعداد آرایه‌ها را که برابر تعداد رکوردهاست، دریافت می‌کنیم و در یک حلقه، رکورد به رکورد، در جدول اضافه می‌کنیم.
نظرات مطالب
مدیریت اسپم‌ها در SignalR
بسیار مفید و کاربردی. البته یک نکته در استفاده از این راه وجود دارد که در هنگام پیاده سازی بهتر هست لحاظ شود:
- بسته به سیستمی که در آن استفاده میشود نیاز است "تعداد درخواست‌ها در زمان مورد نظر"، تغییر کند (به این دلیل که بعضی از درخواست‌های مهم در سیستمی که غیر از چت، همزمان از عملیات دیگری نیز استفاده میکند مختل میشود) یا متغیری برای متمایز کردن درخواست‌ها در کلاس ActivityInfo ایجاد شود. از قطعه کد زیر میتوان به نام تابعی که از سمت کلاینت صدا زده شده است دسترسی داشت:
var methodName = context.MethodDescriptor.Name;
برای دوستانی که از سی شارپ استفاده میکنند کدهای درج شده در پست به شکل زیر است:
public class ActivityInfo
    {
        public ActivityInfo(string connectionId)
        {
            ConnectionId = connectionId;
            Time = DateTime.Now;
        }
        public string ConnectionId { get; set; }

        public DateTime Time { get; set; }
    }
   
    public class SpamDetectionPiplelineModule : HubPipelineModule
    {
        public static HashSet<ActivityInfo> SpamDetection = new HashSet<ActivityInfo>();
        private readonly object _spamDetectionLock = new object();
        public bool IsSpam(string connectionId)
        {
            lock (_spamDetectionLock)
            {
                //Remove all old info before 3 seconds ago
                SpamDetection.RemoveWhere(q => q.Time < DateTime.Now.AddSeconds(-3));

                SpamDetection.Add(new ActivityInfo(connectionId));

                //Check activities from 3 seconds ago
                if (SpamDetection.Count(q => q.ConnectionId == connectionId) > 3)
                {
                    return true;
                }
                return false;
            }
        }
        protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
        {
            if (IsSpam(context.Hub.Context.ConnectionId))
            {
                return false;
            }
            return base.OnBeforeIncoming(context);
        }
    }