مطالب
مراحل تنظیم Let's Encrypt در IIS
روزگاری دریافت مجوزهای SSL، گران و سخت بود. برای رفع این مشکلات مؤسسه‌هایی مانند Let's Encrypt پدیدار شده‌اند که مجوزهای SSL رایگانی را برای سایت‌های اینترنتی صادر می‌کنند. دسترسی به سرویس آن‌ها از طریق API ارائه شده‌ی آن، بسیار ساده بوده، کار با آن رایگان است و نیاز به مجوز خاصی ندارد. فقط باید دقت داشت که گواهینامه‌های Let's Encrypt دو ماهه هستند و وب‌سرور سایت شما باید اجازه‌ی دسترسی به محل ویژه‌ای را که جهت تعیین اعتبار دومین درخواستی ایجاد می‌شود، صادر کند. البته درخواست گواهی مجدد و تمدید آن در هر زمانی، حتی اگر پیش از انقضای آن باشد، مسیر است و از این لحاظ محدودیتی ندارد. در ادامه نحوه‌ی کار با این سرویس را در ویندوزهای سرور بررسی خواهیم کرد.


دریافت برنامه‌ی win-acme

برنامه‌ی win-acme کار دریافت، نصب و تنظیم به روز رسانی خودکار مجوزهای Let’s Encrypt را در ویندوز بسیار ساده کرده‌است و تقریبا به برنامه‌ی استاندارد انجام اینکار تبدیل شده‌است. این برنامه، انجام مراحل زیر را خودکار کرده‌است:
- اسکن IIS برای یافتن bindings و نام سایت‌ها
-  اتصال به Let’s Encrypt certificate authority و دریافت مجوزهای لازم
- درج خودکار مجوزهای دریافتی در Windows certificate store
- ایجاد HTTPS binding خودکار در IIS
- استفاده از Windows Task Scheduler‌، جهت ایجاد وظیفه‌ی به روز رسانی خودکار مجوزهای درخواست شده

به همین جهت پیش از هر کاری نیاز است این برنامه را دریافت کنید:
https://github.com/PKISharp/win-acme/releases

این برنامه از دات نت نگارش 4.6.2 استفاده می‌کند. بنابراین نیاز است این نگارش و یا ترجیحا آخرین نگارش دات نت فریم ورک را بر روی سرور نصب کنید.


آماده سازی برنامه‌ی ASP.NET جهت دریافت مجوزهای Let’s Encrypt

سرور Let’s Encrypt، در حین صدور مجوز برای سایت شما نیاز دارد بررسی کند که آیا شما واقعا صاحب همان دومین هستید یا خیر. به همین جهت مسیر
/.well-known/acme-challenge/id
را بر روی سرور شما بررسی خواهد کرد (بنابراین سرور شما باید به اینترنت متصل بوده و همچنین مجوز دسترسی به این مسیر را عمومی کند). برنامه‌ی win-acme این id را از سرور Let’s Encrypt به صورت خودکار دریافت کرده و فایلی را در مسیر یاد شده ایجاد می‌کند. سپس سرور Let’s Encrypt یکبار این مسیر را خواهد خواند. مشکل اینجا است که دسترسی به این فایل بدون پسوند در برنامه‌های MVC به صورت پیش‌فرض مسیر نیست و نیازی به تنظیمات خاصی دارد:
روش انجام اینکار در ASP.NET Core به صورت زیر است:
[HttpGet("/.well-known/acme-challenge/{id}")]
public IActionResult LetsEncrypt(string id, [FromServices] IHostingEnvironment env)
{
   id = Path.GetFileName(id); // security cleaning
   var file = Path.Combine(env.ContentRootPath, ".well-known", "acme-challenge", id);
   return PhysicalFile(file, "text/plain");
}
این اکشن متد را در هر کنترلری قرار دهید، تفاوتی نمی‌کند و کار خواهد کرد؛ چون attribute routing آن مستقل از محل قرارگیری آن است.
در MVC 5x پارامتر env را حذف و بجای آن از Server.MapPath و در آخر از return File استفاده کنید.
[Route(".well-known/acme-challenge/{id}")]
public ActionResult LetsEncrypt(string id)
{
   id = Path.GetFileName(id); // security cleaning
   var file = Path.Combine(Server.MapPath("~/.well-known/acme-challenge"), id);
   return File(file, "text/plain", id);
}
اگر این مرحله را تنظیم نکنید، در وسط کار دریافت مجوز توسط برنامه‌ی win-acme، به علت اینکه مشخص نیست آیا شما صاحب دامنه هستید یا خیر، خطایی را دریافت کرده و برنامه متوقف می‌شود.


آماده سازی IIS برای دریافت مجوزهای Let’s Encrypt

ابتدا به قسمت Edit bindings وب سایتی که قرار است مجوز را دریافت کند مراجعه کرده:


و سپس bindings مناسبی را ایجاد کنید (از نوع HTTP و نه HTTPS):


برای مثال اگر سایت شما قرار است توسط آدرس‌های www.dotnettips.info و dotnettips.info در دسترس باشد، نیاز است دو binding را در اینجا ثبت کنید. برای تمام موارد ثبت شده هم این تنظیمات را مدنظر داشته باشید:
Type:http
Port:80
IP address:All Unassigned
برنامه‌ی win-acme بر اساس این HTTP Bindings است که معادل‌های متناظر HTTPS آن‌ها را به صورت خودکار ثبت و تنظیم می‌کند.


اجرای برنامه‌ی win-acme بر روی ویندوز سرور 2008

IISهای 8 به بعد (و یا ویندوز سرور 2012 به بعد) دارای ویژگی هستند به نام Server Name Indication و یا SNI که اجاز می‌دهند بتوان چندین مجوز SSL را بر روی یک IP تنظیم کرد.


در اینجا به ازای هر Bindings تعریف شده‌ی در قسمت قبل، یک مجوز Let’s Encrypt دریافت خواهد شد. اما چون ویندوز سرور 2008 به همراه IIS 7.5 است، فاقد ویژگی SNI است. به همین جهت در حالت عادی برای مثال فقط برای www.dotnettips.info مجوزی را دریافت می‌کنید و اگر کاربر به آدرس dotnettips.info مراجعه کند، دیگر نمی‌تواند به سایت وارد شود و پیام غیرمعتبر بودن مجوز SSL را مشاهده خواهد کرد.
برنامه‌ی win-acme برای رفع این مشکل، از ویژگی خاصی به نام «SAN certificate» پشتیبانی می‌کند.
به این ترتیب با ویندوز سرور 2008 هم می‌توان دامنه‌ی اصلی و زیر دامنه‌های تعریف شده را نیز پوشش داد و سایت به این ترتیب بدون مشکل کار خواهد کرد. مراحل تنظیم SAN توسط برنامه‌ی win-acme به این صورت است:

ابتدا که برنامه‌ی win-acme را با دسترسی admin اجرا می‌کنید، چنین منویی نمایش داده می‌شود:
 N: Create new certificate
 M: Create new certificate with advanced options
 L: List scheduled renewals
 R: Renew scheduled
 S: Renew specific
 A: Renew *all*
 V: Revoke certificate
 C: Cancel scheduled renewal
 X: Cancel *all* scheduled renewals
 Q: Quit
گزینه‌ی N یا ایجاد مجوز جدید را انتخاب کنید.
سپس منوی بعدی را نمایش می‌دهد:
 [INFO] Running in Simple mode

 1: Single binding of an IIS site
 2: SAN certificate for all bindings of an IIS site
 3: SAN certificate for all bindings of multiple IIS sites
 4: Manually input host names
 C: Cancel
در این حالت برای ویندوز سرور 2008، فقط و فقط گزینه‌ی 2 را انتخاب کنید.
سپس لیست سایت‌های نصب شده‌ی در IIS را نمایش می‌دهد:
 1: Default Web Site
 C: Cancel

 Choose site: 1
در اینجا برای مثال شماره‌ی 1 یا هر شماره‌ی دیگر متناظر با وب سایت مدنظر را انتخاب کنید.
در ادامه منوی زیر را نمایش می‌دهد:
 * www.dotnettips.info
 * dotnettips.info

 Press enter to include all listed hosts, or type a comma-separated lists of exclusions:
لیستی را که در اینجا مشاهده می‌کنید، همان Bindings است که پیشتر ایجاد کردیم. عنوان می‌کند که برای کدامیک از این‌ها نیاز است مجوز دریافت و نصب شود. کلید enter را فشار دهید تا برای تمام آن‌ها اینکار صورت گیرد.
و ... همین! پس از آن کار دریافت، نصب و به روز رسانی Bindings در IIS به صورت خودکار انجام خواهد شد. اکنون اگر به قسمت Binding سایت مراجعه کنید، این تنظیمات خودکار جدید را مشاهده خواهید کرد:


اگر به لاگ نصب مجوزها دقت کنید این دو سطر نیز در انتهای آن ذکر می‌شوند:
 [INFO] Adding renewal for Default Web Site
 [INFO] Next renewal scheduled at 2018/7/21 4:19:20 AM
علت اینجا است که مجوزهای Let’s Encrypt طول عمر کمی دارند و در صورت به روز نشدن مداوم، کاربران دیگر قادر به مرور سایت نخواهند بود. به همین جهت این برنامه یک Scheduled Task ویندوز را نیز جهت به روز رسانی خودکار این مجوزها ایجاد و تنظیم می‌کند.


اجرای برنامه‌ی win-acme بر روی ویندوزهای سرور 2012 به بعد

چون IIS ویندوزهای سرور 2012 به بعد، از نصب و فعالسازی بیش از یک مجوز SSL به ازای یک IP به صورت توکار تحت عنوان ویژگی SNI پشتیبانی می‌کنند، مراحل انجام آن ساده‌تر هستند.
ابتدا که برنامه‌ی win-acme را با دسترسی admin اجرا می‌کنید، چنین منویی نمایش داده می‌شود:
 N: Create new certificate
 M: Create new certificate with advanced options
 L: List scheduled renewals
 R: Renew scheduled
 S: Renew specific
 A: Renew *all*
 V: Revoke certificate
 C: Cancel scheduled renewal
 X: Cancel *all* scheduled renewals
 Q: Quit
گزینه‌ی N یا ایجاد مجوز جدید را انتخاب کنید.
سپس منوی بعدی را نمایش می‌دهد:
 [INFO] Running in Simple mode

 1: Single binding of an IIS site
 2: SAN certificate for all bindings of an IIS site
 3: SAN certificate for all bindings of multiple IIS sites
 4: Manually input host names
 C: Cancel
در این حالت گزینه‌ی 4 را انتخاب کنید (با فرض اینکه از IIS 8.0 به بعد استفاده می‌کنید).
سپس از شما درخواست می‌کند که لیست دامنه و زیر دامنه‌هایی را که قرار است برای آن‌ها مجوز SSL صادر شوند، به صورت لیست جدا شده‌ی توسط کاما، وارد کنید:
 Enter comma-separated list of host names, starting with the primary one: dotnettips.info, www.dotnettips.info
در ادامه لیست وب سایت‌های ثبت شده‌ی در IIS را نمایش می‌دهد:
1: Default Web Site
2: mysiteName
Choose site to create new bindings: 1
در اینجا شماره‌ی سایتی را که می‌خواهید برای آن مجوز صادر شود، انتخاب کنید.
و ... همین! پس از آن مجوزهای SSL درخواستی، دریافت، نصب و تنظیم خواهند شد. همچنین یک Scheduled Task هم برای به روز رسانی خودکار آن تنظیم می‌شود.
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 1

در این نوشتار که به صورت آموزش تصویری ارائه می‌‏شود؛ یک سرویس WCF در Visual Studio 2013 ایجاد می‌کنم، سپس روش استفاده از آن‏را در یک برنامه ویندوزی آموزش خواهم داد. در اینجا در نظرگرفته شده است که شما افزونه‎ی Resharper را روی ویژوال استودیوی خود نصب دارید. پس در صورتیکه هنوز به سراغ آن نرفته اید درنگ نکنید و واپسین نگارش آن را دانلود کنید.

در این پروژه‌ی ساده در نظر می‎گیریم که دو جدول یکی برای اخبار، شامل عنوان، متن خبر و تاریخ ثبت و دسته بندی و دیگری برای نگهداری دسته‎ها در پایگاه داده داریم و می‏خواهیم سرویس‏های مناسب با این دو جدول را بسازیم. با کد زیر، پایگاه داده‌‏ی dbTest و جدول‌های tblNews و tblCategory در SQL Server 2012 ساخته می‌شود:

USE [master]
GO
/****** Object:  Database [dbMyNews]    Script Date: 2014/01/14 09:46:04 ب.ظ ******/
CREATE DATABASE [dbMyNews]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'dbMyNews', FILENAME = N'D:\dbMyNews.mdf' , SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'dbMyNews_log', FILENAME = N'D:\dbMyNews_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO
USE [dbMyNews]
GO
/****** Object:  Table [dbo].[tblCategory]    Script Date: 2014/01/14 09:46:04 ب.ظ ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblCategory](
[tblCategoryId] [int] IDENTITY(1,1) NOT NULL,
[CatName] [nvarchar](50) NOT NULL,
[IsDeleted] [bit] NOT NULL,
 CONSTRAINT [PK_tblCategory] PRIMARY KEY CLUSTERED 
(
[tblCategoryId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
/****** Object:  Table [dbo].[tblNews]    Script Date: 2014/01/14 09:46:04 ب.ظ ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblNews](
[tblNewsId] [int] IDENTITY(1,1) NOT NULL,
[tblCategoryId] [int] NOT NULL,
[Title] [nvarchar](50) NOT NULL,
[Description] [nvarchar](max) NOT NULL,
[RegDate] [datetime] NOT NULL,
[IsDeleted] [bit] NULL,
 CONSTRAINT [PK_tblNews] PRIMARY KEY CLUSTERED 
(
[tblNewsId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO
ALTER TABLE [dbo].[tblNews]  WITH CHECK ADD  CONSTRAINT [FK_tblNews_tblCategory] FOREIGN KEY([tblCategoryId])
REFERENCES [dbo].[tblCategory] ([tblCategoryId])
GO
ALTER TABLE [dbo].[tblNews] CHECK CONSTRAINT [FK_tblNews_tblCategory]
GO
USE [master]
GO
ALTER DATABASE [dbMyNews] SET  READ_WRITE 
GO

اکنون Visual Studio 2013 را بازکنید سپس روی گزینه New Project کلیک کنید و برابر با نگاره‌ی زیر عمل کنید:

پروژه MyNewsWCFLibrary در راه حل MyNews ساخته می‌شود. این پروژه به صورت پیش‌گزیده دارای یک کلاس به نام Service و یک interface به نام IService است. هر دو را حذف کنید و سپس روی نام پروژه راست‌کلیک کرده، از منوی بازشده گزینه‌ی Add -> New Item را انتخاب کنید. سپس برابر با نگاره‌ی زیر عمل کنید:

در لایه‎‌ی Service Interface کلیه‎ی روال‌های مورد نیاز برای ارتباط با پایگاه داده را می‎سازیم. پیش از آن باید یک Model برای ارتباط با پایگاه داده ساخته باشیم. برای این کار از پنجره Add New Item و از زیرمجموعه Data، گزینه ADO.NET Entity Data Model را انتخاب کنید و به‌سان زیر پیش روید:

در گام پسین روی دکمه New Connection کلیک کنید و رشته‌ی اتصال به پایگاه داده‌ی dbMyNews را بسازید. سپس همانند تنظیمات نگاره‌ی زیر ادامه دهید:

در گام پسین گزینه‌‎ی Entity Framework 6.0 را برگزینید و روی دکمه‎ی Next کلیک کنید.

در پنجره نشان‎ داده شده، جدول‎های مورد نیاز را همانند نگاره‌ی زیر انتخاب کرده و روی دکمه Finish کلیک کنید:

در پایان مدل ما همانند نگاره‌ی زیر خواهد بود.

در بخش پسین درباره‏ی شیوه‏‌ی دست‎کاری کلاس‎های Entity خواهم نوشت.

مطالب
آموزش TypeScript #1
با گسترش روز افزون زبان برنامه نویسی Javascript و استفاده هر چه بیشتر آن در تولید برنامه‌های تحت وب این زبان به یکی از قدرت‌های بزرگ در تولید برنامه‌های مبتنی بر وب تبدیل شده است. ترکیب این زبان با Css و Html5 تقریبا هر گونه نیاز برای تهیه و توسعه برنامه‌های وب را حل کرده است. جاوا اسکریپت در ابتدا برای اسکریپت نویسی سمت کلاینت برای صفحات وب ایجاد شد و برای سال‌ها به‌عنوان ابزاری برای مدیریت کردن رویدادهای صفحات وب محدود شده بود  و در نتیجه بسیاری از امکانات لازم برای برنامه‌نویسی برنامه‌های مقیاس بزرگ را به‌همراه نداشت. امروزه به قدری Javascript  توسعه داده شده است که حتی در تولید برنامه‌های Native مانند Windows Store  و برنامه‌های تحت Cloud نیز استفاده می‌شود. پیشرفت‌های صورت گرفته و اشاره شده در این حوزه موجب شد تا شاهد پیداش برنامه‌های مبتنی بر جاوا اسکریپت با سایزهای بی سابقه‌ای باشیم و این بیانگر این بود که تولید برنامه‌های مبتنی بر جاوا اسکریپت در مقیاس‌های بزرگ امر دشواری است و اینک TypeScript توسط غول نرم افزاری جهان پا به عرصه گذاشته که این فرآیند را آسان‌تر نماید. به کمک TypeScript می‌توان برنامه تحت JavaScript در مقیاس بزرگ تولید کرد به طوری با هر مرورگر و سیستم عاملی سازگار باشد. TypeScript از شی گرایی نیز پشتیبانی می‌کند و خروجی آن در نهایت به JavaScript کامپایل می‌شود. خیلی‌ها عقیده دارند که هدف اصلی مایکروسافت از تولید و توسعه این زبان رقابت با CoffeeScript است.  CoffeeScript یک زبان متن باز است که در سال 2009 توسط Jeremy Ashkenas ایجاد شده است و سورس آن در GitHub موجود می‌باشد. در آینده، بیشتر به مباحث مربوط به CoffeeScript و آموزش آن خواهم پرداخت.

در تصویر ذیل یک مقایسه کوتاه بین CoffeeScript و TypeScript را مشاهده می‌کنید.

با TypeScript چه چیزهایی به دست خواهیم آورد؟

یک نکته مهم این است که این زبان به خوبی در Visual Studio پشتیبانی می‌شود و قابلیت Intellisense نوشتن برنامه به این زبان را دلپذیرتر خواهد کرد و از طرفی دیگر به نظر من یکی از مهم‌ترین مزیت هایی که TypeScript در اختیار ما قرار می‌دهد این است که می‌توانیم به صورت Syntax آشنای شی گرایی کد نویسی کنیم و خیلی راحت‌تر کد‌های خود را سازمان دهی کرده و از نوشتن کد‌های تکراری اجتناب کنیم.

یکی دیگر از مزیت‌های مهم این زبان این است که این زبان از Static Typing به خوبی پشتیبانی می‌کند. این بدین معنی است که شما ابتدا باید متغیر‌ها را تعریف کرده و نوع آن‌ها را مشخص نمایید و هم چنین در هنگام پاس دادن مقادیر به پارامتر‌های توابع باید حتما به نوع داده ای آن‌ها دقت داشته باشید چون کامپایلر بین انواع داده ای در TypeScript تمایز قایل است و در صورت رعایت نکردن این مورد شما با خطا مواجه خواهید شد. این تمایز قایل شدن باعث می‌شود که برنامه هایی خواناتر داشته باشیم از طرفی باعث می‌شود که خطا یابی و نوشتن تست برای برنامه راحت‌تر و تمیزتر باشد. بر خلاف JavaScript، در TypeScript(به دلیل پشتیبانی از شی گرایی) می‌توانیم علاوه بر داشتن کلاس، اینترفیس نیز داشته باشیم و در حال حاضر مزایای استفاده از اینترفیس بر کسی پوشیده نیست.

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

البته این نکته را نیز فراموش نخواهیم کرد که این زبان تازه متولد شده است(سال 2012 توسط Anders Hejlsberg) و همچنان در حال توسعه است و این در حال حاضر مهم‌ترین عیب این زبان می‌تواند باشد چون هنوز به پختگی سایر زبان‌های اسکریپتی در نیامده است.

در ذیل یک مثال کوچک به زبان TypeScript و JavaScript را برای مقایسه در خوانایی و راحتی کد نویسی قرار دادم:

TypeScript:

class Greeter {
    greeting: string;

    constructor (message: string) {
        this.greeting = message;
    }

    greet() {
        return "Hello, " + this.greeting;
    }
}
بعد از کامپایل کد بالا به کدی معادل زیر در JavaScript تبدیل خواهد شد:
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();
توضیح چند واژه در TypeScript

Program : یک برنامه TypeScript مجموعه ای از یک یا چند Source File است. این Source File‌ها شامل کد‌های پیاده سازی برنامه هستند ولی در خیلی موارد برای خوانایی بیشتر برنامه می‌توان فقط تعاریف را در این فایل‌های سورس قرار داد.
Module: ماژول در TypeScript شبیه به مفاهیم فضای نام یا namespace در دات نت است و می‌تواند شامل چندین کلاس یا اینترفیس باشد.
Class : مشابه به مفاهیم کلاس در دات نت است و دقیقا همان مفهوم را دارد. یک کلاس می‌تواند شامل چندین تابع و متغیر با سطوح دسترسی متفاوت باشد. در TypeScript مجاز به استفاده از کلمات کلیدی public و private نیز می‌باشید. یک کلاس در Typescript می‌تواند یک کلاس دیگر را توسعه دهد(ارث بری در دات نت) و چندین اینترفیس را پیاده سازی نماید.
Interface: یک اینترفیس فقط شامل تعاریف است و پیاده سازی در آن انجام نخواهد گرفت. یک اینترفیس می‌تواند چندین اینترفیس دیگر را توسعه دهد.
Function: معادل متد در دات نت است. می‌تواند پارامتر ورودی داشته باشد و در صورت نیاز یک مقدار را برگشت دهد.
Scope: دقیقا تمام مفاهیم مربوط به محدوده فضای نام و کلاس و متد در دات نت در این جا نیز صادق است. 


آماده سازی Visual Studio برای شروع به کار
در ابتدا باید Template مربوطه به TypeScript را نصب کنید تا از طریف VS.Net بتوانیم به راحتی به این زبان کد نویسی کنیم. می‌توانید فایل نصب را از اینجا دانلود کنید. بعد از نصب از قسمت Template‌های موجود گزینه Html Application With TypeScript را انتخاب کنید

یا از قسمت Add  در پروژه‌های وب خود نظیر MVC گزینه TypeScript File را انتخاب نمایید.


در پست بعدی کد نویسی با این زبان را آغاز خواهیم کرد. 

مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 3
پیش از ادامه‌ی نوشتار بهتر است توضیحاتی درباره‎ی قالب‎های T4 داده شود. این قالب‏‌های مصنوعی حاوی کدهایی که است که هدف آن صرفه‎جویی در نوشتن کد توسط برنامه‏ نویس است. مثلاً در MVC شما یکبار قالبی برای صفحه Index خود تهیه می‏‌کنید که برای نمونه بجای ساخت جدول ساده، از گرید Kendo استفاده کند و همچنین دارای دکمه ویرایش و جزئیات باشد. از این پس هر بار که نیاز به ساخت یک نمای نوع لیست برای یک ActionResult داشته باشید فرم‏ ساز MVC از قالب شما استفاده خواهد کرد. روشن است که خود Visual Studio نیز از T4 در ساخت بسیاری از فرم‏ها و کلاس‏‌ها بهره می‏برد.
خبر خوب این‏که برای ساخت کلاس‏‌های هر موجودیت در Entity Framework نیز از قالب‏های T4 استفاده می‏شود و این‏که این قالب‏‌ها در دسترس توسعه‌دهندگان برای ویرایش یا افزودن است.
افزونه‌ی  Tangible را دریافت کنید و سپس نصب کنید. این افزونه ظاهر نامفهوم قالب‏های T4 را ساده و روشن می‏کند. 
ما نیاز داریم که خود Visual Studio زحمت این سه کار را بکشد: 
1- بالای هر کلاس موجودیت عبارت using System.Runtime.Serialization; را بنویسید.
2- صفت [DataContract] را پیش از تعریف کلاس بیفزاید.
3- صفت [DataMember] را پیش از تعریف هر ویژگی بیفزاید.
همانند شکل زیر روی فایل MyNewsModel.tt دوکلیک کنید تا محتوای آن در سمت چپ نشان داده شود. این محتوا باید ظاهری همانند شکل پیدا کرده باشد:

کد زیر را در محتوای فایل جست‌وجو کنید:
    public string Property(EdmProperty edmProperty)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} {2} {{ {3}get; {4}set; }}",
            Accessibility.ForProperty(edmProperty),
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty),
            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
    }
متن آن‌را به این صورت تغییر دهید:
    public string Property(EdmProperty edmProperty)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
"[DataMember]" + Environment.NewLine +
            "{0} {1} {2} {{ {3}get; {4}set; }}",
            Accessibility.ForProperty(edmProperty),
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty),
            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
    }
بار دیگر به دنبال این کد بگردید:
 public string EntityClassOpening(EntityType entity)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1}partial class {2}{3}",
            Accessibility.ForType(entity),
            _code.SpaceAfter(_code.AbstractOption(entity)),
            _code.Escape(entity),
            _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
    }
این کد را نیز به این صورت تغییر دهید:
    public string EntityClassOpening(EntityType entity)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
"[DataContract]" + Environment.NewLine + 
            "{0} {1}partial class {2}{3}",
            Accessibility.ForType(entity),
            _code.SpaceAfter(_code.AbstractOption(entity)),
            _code.Escape(entity),
            _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
    }
برای واپسین تغییر به دنبال کد زیر بگردید:
    public string UsingDirectives(bool inHeader, bool includeCollections = true)
    {
        return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
            ? string.Format(
                CultureInfo.InvariantCulture,
                "{0}using System;{1}" +
                "{2}",
                inHeader ? Environment.NewLine : "",
                includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
                inHeader ? "" : Environment.NewLine)
            : "";
    }
سپس کد زیر را جاگزین آن کنید:
    public string UsingDirectives(bool inHeader, bool includeCollections = true)
    {
        return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
            ? string.Format(
                CultureInfo.InvariantCulture,
"using System.Runtime.Serialization;" + Environment.NewLine +
                "{0}using System;{1}" +
                "{2}",
                inHeader ? Environment.NewLine : "",
                includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
                inHeader ? "" : Environment.NewLine)
            : "";
    }
فایل MyNewsModel.tt را ذخیره کنید و از آن خارج شوید. بار دیگر هر کدام از کلاس‌های tblNews و tblCategory را باز کنید. خواهید دید که به صورت خودکار تغییرات مد نظر ما به آن افزوده شده است. از این پس بدون هیچ دلواپسی بابت حذف صفت‎ها، می‎توانید هرچند بار که خواستید مدل خود را به‎هنگام کنید.
در بخش پسین دوباره به WCF بازخواهیم گشت و به تعریف روال‏های مورد نیاز خواهیم پرداخت. 
مطالب
کوئری نویسی در EF Core - قسمت پنجم - اعمال تجمعی - بخش اول
امکان انجام محاسبات تجمعی، یکی از مواردی است که قدرت بانک‌های اطلاعاتی رابطه‌ای را نمایش می‌دهد. توسط این نوع کوئری‌ها از محدوده‌ی CRUD (ثبت/ویرایش/به روز رسانی) خارج شده و وارد دنیای تصمیم‌گیری‌ها می‌شویم. تعداد مثال‌های اعمال تجمعی این سری قابل توجه‌است. به همین جهت در دو قسمت ارائه می‌شوند.


مثال 1: چه تعداد امکانات، توسط این مجموعه ارائه می‌شود؟
var count = context.Facilities.Count();
برای شمارش ساده‌ی تعداد ردیف‌های یک کوئری، از متد Count استفاده می‌شود که به صورت زیر ترجمه خواهد شد:



مثال 2: چه تعداد امکانات گران قیمتی توسط این مجموعه ارائه می‌شود؟
می‌خواهیم تعداد امکاناتی را بیابیم که guestcost آن‌ها بزرگتر یا مساوی 10 است.
var count = context.Facilities.Count(x => x.GuestCost >= 10);
این گزارش نسبت به مثال قبلی، یک Where را بیشتر دارد که می‌توان این شرط را در همان متد Count نیز ذکر کرد؛ با این خروجی نهایی:



مثال 3: هر کاربر چه تعدادی کاربر دیگری را توصیه کرده‌است؟
خروجی این گزارش بر اساس recommendedby و count باشد و مرتب شده‌ی بر اساس ID افراد.
var members = context.Members
                        .Where(member => member.RecommendedBy != null)
                        .GroupBy(member => member.RecommendedBy)
                        .Select(group => new
                        {
                            RecommendedBy = group.Key,
                            Count = group.Count()
                        })
                        .OrderBy(result => result.RecommendedBy)
                        .ToList();
در دو مثال قبل، تنها یک حاصل عددی را از گزارشات دریافت کردیم. اما در اینجا نیاز است به ازای هر شخص، تعداد توصیه شده‌های او محاسبه شوند. به همین جهت نیاز است اطلاعات را به ازای هر شخص مجزا، گروه بندی کرد و سپس متد Count را بر روی این گروه ویژه اعمال نمود و همینطور برای مابقی گروه‌ها. در اینجا GroupBy بر روی خاصیت RecommendedBy انجام شده‌است. این خاصیت در Select بعدی، همان group.Key استفاده شده‌است.



مثال 4: تعداد slots رزرو شد‌ه‌ی به ازای هر کدام از امکانات موجود را نمایش دهید.
جهت یادآوری از قسمت اول: «هر رزرو کردن مکان و امکاناتی در این مجموعه، «نیم ساعته» است. بنابراین Slots در اینجا به معنای تعداد نیم ساعت‌های رزرو کردن یک مکان خاص است؛ که به آن «half hour slots» نیز گفته می‌شود و زمان شروع این رزرو نیز ثبت می‌شود.»
خروجی این گزارش بر اساس facid و Total Slots باشد و مرتب شده‌ی بر اساس ID هر امکان موجود.
var facilities = context.Bookings
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.FacId)
                                    .ToList();
در این گزارش بجای استفاده از متد Count، از متد Sum استفاده شده‌است. چون می‌خواهیم جمع slots را به ازای هر امکان موجود محاسبه کنیم، ابتدا گروه‌های مجزای این امکانات را تشکیل می‌دهیم و سپس Sum را به هر گروهی، به صورت مجزایی اعمال می‌کنیم. در اینجا group.Key دقیقا همان booking.FacId است و در متد Sum، امکان دسترسی به خواص booking نیز وجود دارد.



مثال 5: تعداد slots رزرو شد‌ه‌ی در ماه September 2012 را به ازای هر کدام از امکانات موجود، نمایش دهید.
خروجی این گزارش بر اساس facid و Total Slots باشد و مرتب شده‌ی بر اساس ID تعداد slots.
var date1 = new DateTime(2012, 09, 01);
var date2 = new DateTime(2012, 10, 01);

var facilities = context.Bookings
                                    .Where(booking => booking.StartTime >= date1
                                                        && booking.StartTime < date2)
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.TotalSlots)
                                    .ToList();
این گزارش تنها یک قسمت Where را نسبت به گزارش قبلی بیشتر دارد و این Where باید دقیقا پیش‌از گروه بندی اطلاعات اعمال شود. به عبارتی ابتدا باید ردیف‌های اصلی گزارش مشخص باشند تا بتوان آن‌ها را گروه بندی کرد.



مثال 6: محاسبه کنید در سال 2012 و به ازای هر ماه مجزای آن، چه تعداد slots رزرو شده‌اند.
خروجی این گزارش بر اساس facid, month, Total Slots باشد و مرتب شده‌ی بر اساس ID و شماره‌ی ماه.
var date1 = new DateTime(2012, 01, 01);
var date2 = new DateTime(2013, 01, 01);

var facilities = context.Bookings
                                    .Where(booking => booking.StartTime >= date1
                                                        && booking.StartTime < date2)
                                    .GroupBy(booking => new { booking.FacId, booking.StartTime.Month })
                                    .Select(group => new
                                    {
                                        group.Key.FacId,
                                        group.Key.Month,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.FacId)
                                        .ThenBy(result => result.Month)
                                    .ToList();
دو نکته‌ی جدید در این گزارش نسبت به مثال‌های قبلی وجود دارند:
الف) می‌توان گروه بندی را بر روی بیش از یک ستون اعمال کرد. در این حالت در Select بعدی، group.Key به کل شیء گروه بندی شده‌، اشاره می‌کند.
ب) روش انتخاب ماه میلادی از یک خاصیت DateTime و گروه بندی بر اساس آن

که به صورت زیر ترجمه می‌شود:



مثال 7: چه تعداد کاربر مجموعه، حداقل یکبار امکاناتی را رزرو کرده‌اند؟

var count = context.Bookings.Select(booking => booking.MemId).Distinct().Count();
هدف از این گزارش، رسیدن به COUNT DISTINCT است.
- (*)COUNT یعنی بازگشت تعداد ردیف‌های نهایی گزارش.
- COUNT(address) یعنی بازگشت تعداد آدرس‌های غیرنال، در کل ردیف‌های نهایی گزارش.
- COUNT(DISTINCT address) یعنی بازگشت تعداد آدرس‌های غیرمشابه در کل ردیف‌های نهایی گزارش.

  COUNT DISTINCT را EF-Core به صورت ترکیبی از یک sub-query ترجمه می‌کند:



مثال 8: امکاناتی را لیست کنید که بیش از 1000 slots رزرو شده دارند.
خروجی این گزارش بر اساس facid و Total Slots باشد و مرتب شده‌ی بر اساس ID هر امکان موجود.
var facilities = context.Bookings
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .Where(result => result.TotalSlots > 1000)
                                    .OrderBy(result => result.FacId)
                                    .ToList();
در مثال‌های قبل، از Where جهت تشکیل تعداد ردیف‌های اصلی گزارش و سپس گروه بندی آن‌ها استفاده کردیم. در اینجا می‌خواهیم Where را بر روی نتیجه‌ی حاصل از گروه بندی اعمال کنیم. در کوئری LINQ فوق، خواص قابل دسترسی پس از Select نهایی، همان‌هایی هستند که توسط آن ارائه می‌شوند. این نوع Whereها در SQL حاصل به Having ترجمه خواهند شد:



مثال 9: میزان فروش کل هر امکان موجود را محاسبه کنید.
خروجی این گزارش بر اساس name, revenue باشد و مرتب شده‌ی بر اساس میزان فروش. بخاطر داشته باشید که میزان فروش کاربران ثبت نام شده با کاربران مهمان یکی نیست.
 var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .OrderBy(result => result.TotalRevenue)
                                .ToList();
همانند تمام گروه بندی‌ها، ابتدا باید ردیف‌های اصلی گزارش را تشکیل داد و سپس بر روی آن‌ها گروه بندی نهایی را اعمال نمود. به همین جهت در ابتدا خاصیت محاسباتی Revenue را بر اساس کاربران مهمان با ID مساوی صفر و کاربران اصلی مجموعه، تشکیل داده و گروه بندی را به نام هر مجموعه اعمال می‌کنیم. سپس جمع Revenue محاسبه شده را به ازای هر گروه محاسبه کرده و نتیجه را بازگشت می‌دهیم.



مثال 10: کدامیک از امکانات موجود، میزان فروشی کمتر از 1000 داشته‌اند؟
خروجی این گزارش بر اساس name, revenue باشد و مرتب شده‌ی بر اساس میزان فروش. بخاطر داشته باشید که میزان فروش کاربران ثبت نام شده با کاربران مهمان یکی نیست.
var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .Where(result => result.TotalRevenue < 1000)
                                .OrderBy(result => result.TotalRevenue)
                                .ToList();
این مورد نیز همانند گزارش 9 است که یک Where به نتیجه‌ی حاصل از آن اعمال شده که در خروجی نهایی به Having ترجمه می‌شود:



مثال 11: کدامیک از امکانات موجود، بیشترین slots رزرو شده را دارد؟

var item = context.Bookings
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderByDescending(result => result.TotalSlots)
                                    .FirstOrDefault();
ساده‌ترین روش حل این مساله، گروه بندی اطلاعات بر اساس هر امکان موجود و سپس محاسبه‌ی TotalSlots هرکدام، به صورت مجزایی است. در ادامه ردیف‌های حاصل را بر اساس TotalSlots محاسبه شده، به صورت نزولی مرتب می‌کنیم. اولین ردیفی که در بالای گزارش قرار می‌گیرد همان FacId ای است که بیشترین TotalSlots را دارد.



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
نظرات مطالب
نحوه اجباری کردن استفاده از WWW در ASP.NET MVC
- بله حق با شماست. اگه به نمونه کارهای بنده هم توجه بفرمایید، همه فایل‌های استاتیک رو روی یه زیردامنه ای به اسم cdn.site.com یا static.site.com هاست کردم. سعی میکنم منظورمو توی یه مطلب جداگانه ای واضح شرح بدم. فکر میکنم برای اینکه کامنتو کوتاه کنم، خیلی بد نوشتم و نتونستم منظورمو برسونم.
- باز هم حق با شماست. بنده هم عرض نکردم نمیشه هاست کرد. خود من هم قبلا اینکارو انجام دادم (روی سرور برتینا؛ اردیبهشت 90). ببینید، منظورم اینه که مثلا امروز اکثرمون Win 7 یا 8 رو سیستممون داریم یا بیشترمون مثلا VS 2012 نصب کردیم. کلا دارم عرض میکنم که همیشه گرایشمون به آپدیت بودن هست. با توجه به هزینه هاستینگ که واقعا همینطور داره میاد پایین و شرکتهایی که شدیدا با هم در رقابت هستن و گرایش‌های درونی خود ما برنامه نویس ها، فکر میکنم کمتر کسی پیدا بشه که امروز تمایل داشته باشه کلا هیچ سایتی رو (حالا MVC بودن یا نبودنش یه طرف) روی IIS 6 هاست بکنه. چند وقت پیش یادمه یه مطلبی نوشته بودید درباره چک لیست برنامه‌های MVC و اون تو یه جمله ای که خیلی خوشم اومد و تو ذهنم حک شد این مضمون بود که IE 6 و 7 رو به رحمت ایزدی بپیوندانیم D: من منظورم دقیقا در همین راستاست. بعنوان نمونه من دقیقا 2 روز پیش یه هاستی رو ثبت کردم که روی ویندوز سرور 2012 با SQLServer 2012 و دات نت 4.5 و IIS 8 با 1 گیگ فضا و پهنای باند نامحدود و 300 مگ رم، هزینه یک ساله ـش با یه دامنه com شد فقط 60 دلار. دسترسی ریموت به IIS و SQLServer هم میده و از Web Deploy هم پشتیبانی میکنه. خوب همین پکیج خیلی ارزونتر هم گیر میومد. ولی این شرکت معتبرتر بود، گرونتر میداد که گرونش اینقدر شد. نمیدونم تونستم منظورمو برسونم یا نه.
- با اجازتون یه مطلب کوتاهی در راستای پست شما و استفاده از UrlRewrite توی این آدرس نوشتم و مثال مورد نظرمو اونجا ذکر کردم. در مورد فایل‌های استاتیک و سایر موارد هم حتما بیشتر توضیح میدم.
با احترام فراوان. پاینده باشید.
مطالب
مشکل نصب VS2012 بر روی ویندوز 7 تازه

در حین نصب ویژوال استودیو 2012 بر روی ویندوز 7 تازه که بصورت virtual هاست شده بوده خطایی مبنی بر عدم وجود ریشه گواهی Certificate ‌ها در سیستم اتفاق افتاد که مانع از نصب بعضی از پکیج‌های نصاب گردید.

1. http://stackoverflow.com/questions/16673292/microsoft-web-deploy-3-0-a-certificate-chain-could-not-be-built-to-a-trusted-r
2. http://blogs.msdn.com/b/heaths/archive/2012/08/17/a-certificate-chain-could-not-be-built-to-a-trusted-root-authority.aspx
3. http://social.msdn.microsoft.com/Forums/vstudio/en-US/aeb3a43d-e5d7-41ab-b875-6a0d3b438abf/web-deploy-3-setup-problem
4. http://msdnrss.thecoderblogs.com/2012/08/a-certificate-chain-could-not-be-built-to-a-trusted-root-authority/
5. http://forums.asp.net/p/1897741/5361492.aspx?Microsoft+Web+Deploy+3+0+A+certificate+chain+could+not+be+built+to+a+trusted+root+authority+

مشکل نیز با توجه به توضیح مشخص بود. راه حل‌ها نیز ختم به نصب یک آپدیت KB2746268   برای ثبت گواهی‌ها می‌شد که برای نصب این آپدیت میبایست ویندوز باصطلاح Genuine باشد و بعد از نصب نیز باید نصاب ویژوال استودیو را دوباره راه انداخت و گزینه‌های نصب نشده را دوباره انتخاب کرد. ولی با توجه به تجربه شخصی از محصولات مایکروسافت در حین نصب ویژوال استودیو ارتباط با اینترنت را برای ویندوز مجازی میسر کردم و خوشبختانه نتیجه مورد نظر حاصل شد و پس از طی مراحل Add\Remove مربوط به قابلیتهای نصب نشده ویژوال استودیو تمامی پکیج‌ها بصورت صحیح و بدون مشکل نصب شدند.

مطالب
معماری لایه بندی نرم افزار #4

UI

در نهایت نوبت به طراحی و کدنویسی UI می‌رسد تا بتوانیم محصولات را به کاربر نمایش دهیم. اما قبل از شروع باید موضوعی را یادآوری کنم. اگر به یاد داشته باشید، در کلاس ProductService موجود در لایه‌ی Domain، از طریق یکی از روشهای الگوی Dependency Injection به نام Constructor Injection، فیلدی از نوع IProductRepository را مقداردهی نمودیم. حال زمانی که بخواهیم نمونه ای را از ProductService ایجاد نماییم، باید به عنوان پارامتر ورودی سازنده، شی ایجاد شده از جنس کلاس ProductRepository موجود در لایه Repository را به آن ارسال نماییم. اما از آنجایی که می‌خواهیم تفکیک پذیری لایه‌ها از بین نرود و UI بسته به نیاز خود، نمونه مورد نیاز را ایجاد نموده و به این کلاس ارسال کند، از ابزارهایی برای این منظور استفاده می‌کنیم. یکی از این ابزارها StructureMap می‌باشد که یک Inversion of Control Container یا به اختصار IoC Container نامیده می‌شود. با Inversion of Control در مباحث بعدی بیشتر آشنا خواهید شد. StructureMap ابزاری است که در زمان اجرا، پارامترهای ورودی سازنده‌ی کلاسهایی را که از الگوی Dependency Injection استفاده نموده اند و قطعا پارامتر ورودی آنها از جنس یک Interface می‌باشد را، با ایجاد شی مناسب مقداردهی می‌نماید.

به منظور استفاده از StructureMap در Visual Studio 2012 باید بر روی پروژه UI خود کلیک راست نموده و گزینه‌ی Manage NuGet Packages را انتخاب نمایید. در پنجره ظاهر شده و از سمت چپ گزینه‌ی Online و سپس در کادر جستجوی سمت راست و بالای پنجره واژه‌ی structuremap را جستجو کنید. توجه داشته باشید که باید به اینترنت متصل باشید تا بتوانید Package مورد نظر را نصب نمایید. پس از پایان عمل جستجو، در کادر میانی structuremap ظاهر می‌شود که می‌توانید با انتخاب آن و فشردن کلید Install آن را بر روی پروژه نصب نمایید.

جهت آشنایی بیشتر با NuGet و نصب آن در سایر نسخه‌های Visual Studio می‌توانید به لینک‌های زیر رجوع کنید:

1. آشنایی با  NuGetقسمت اول

2. آشنایی با  NuGetقسمت دوم

3. Installing NuGet

کلاسی با نام BootStrapper را با کد زیر به پروژه UI خود اضافه کنید:

using StructureMap;
using StructureMap.Configuration.DSL;
using SoCPatterns.Layered.Repository;
using SoCPatterns.Layered.Model;

namespace SoCPatterns.Layered.WebUI
{
    public class BootStrapper
    {
        public static void ConfigureStructureMap()
        {
            ObjectFactory.Initialize(x => x.AddRegistry<ProductRegistry>());
        }
    }
    public class ProductRegistry : Registry
    {
        public ProductRegistry()
        {
            For<IProductRepository>().Use<ProductRepository>();
        }
    }
}

ممکن است یک WinUI ایجاد کرده باشید که در این صورت به جای فضای نام SoCPatterns.Layered.WebUI از SoCPatterns.Layered.WinUI استفاده نمایید.

هدف کلاس BootStrapper این است که تمامی وابستگی‌ها را توسط StructureMap در سیستم Register نماید. زمانی که کدهای کلاینت می‌خواهند به یک کلاس از طریق StructureMap دسترسی داشته باشند، Structuremap وابستگی‌های آن کلاس را تشخیص داده و بصورت خودکار پیاده سازی واقعی (Concrete Implementation) آن کلاس را، براساس همان چیزی که ما برایش تعیین کردیم، به کلاس تزریق می‌نماید. متد ConfigureStructureMap باید در همان لحظه ای که Application آغاز به کار می‌کند فراخوانی و اجرا شود. با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:

در WebUI:

فایل Global.asax را به پروژه اضافه کنید و کد آن را بصورت زیر تغییر دهید:

namespace SoCPatterns.Layered.WebUI
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            BootStrapper.ConfigureStructureMap();
        }
    }
}

در WinUI:

در فایل Program.cs کد زیر را اضافه کنید:

namespace SoCPatterns.Layered.WinUI
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            BootStrapper.ConfigureStructureMap();
            Application.Run(new Form1());
        }
    }
}

سپس برای طراحی رابط کاربری، با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:

در WebUI:

صفحه Default.aspx را باز نموده و کد زیر را به آن اضافه کنید:

<asp:DropDownList AutoPostBack="true" ID="ddlCustomerType" runat="server">
    <asp:ListItem Value="0">Standard</asp:ListItem>
    <asp:ListItem Value="1">Trade</asp:ListItem>
</asp:DropDownList>
<asp:Label ID="lblErrorMessage" runat="server" ></asp:Label>
<asp:Repeater ID="rptProducts" runat="server" >
    <HeaderTemplate>
        <table>
            <tr>
                <td>Name</td>
                <td>RRP</td>
                <td>Selling Price</td>
                <td>Discount</td>
                <td>Savings</td>
            </tr>
            <tr>
                <td colspan="5"><hr /></td>
            </tr>
    </HeaderTemplate>
    <ItemTemplate>
            <tr>
                <td><%# Eval("Name") %></td>
                <td><%# Eval("RRP")%></td>
                <td><%# Eval("SellingPrice") %></td>
                <td><%# Eval("Discount") %></td>
                <td><%# Eval("Savings") %></td>
            </tr>
    </ItemTemplate>
    <FooterTemplate>
        </table>
    </FooterTemplate>
</asp:Repeater>

در WinUI:

فایل Form1.Designer.cs را باز نموده و کد آن را بصورت زیر تغییر دهید:

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
    this.cmbCustomerType = new System.Windows.Forms.ComboBox();
    this.dgvProducts = new System.Windows.Forms.DataGridView();
    this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colRrp = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colSellingPrice = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colDiscount = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colSavings = new System.Windows.Forms.DataGridViewTextBoxColumn();
    ((System.ComponentModel.ISupportInitialize)(this.dgvProducts)).BeginInit();
    this.SuspendLayout();
    //
    // cmbCustomerType
    //
    this.cmbCustomerType.DropDownStyle =                                                                                                                                                                             System.Windows.Forms.ComboBoxStyle.DropDownList;
    this.cmbCustomerType.FormattingEnabled = true;
    this.cmbCustomerType.Items.AddRange(new object[] {
        "Standard",
        "Trade"});
    this.cmbCustomerType.Location = new System.Drawing.Point(12, 90);
    this.cmbCustomerType.Name = "cmbCustomerType";
    this.cmbCustomerType.Size = new System.Drawing.Size(121, 21);
    this.cmbCustomerType.TabIndex = 3;
    //
    // dgvProducts
    //
    this.dgvProducts.ColumnHeadersHeightSizeMode =     System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
    this.dgvProducts.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
    this.colName,
    this.colRrp,
    this.colSellingPrice,
    this.colDiscount,
    this.colSavings});
    this.dgvProducts.Location = new System.Drawing.Point(12, 117);
    this.dgvProducts.Name = "dgvProducts";
    this.dgvProducts.Size = new System.Drawing.Size(561, 206);
    this.dgvProducts.TabIndex = 2;
    //
    // colName
    //
    this.colName.DataPropertyName = "Name";
    this.colName.HeaderText = "Product Name";
    this.colName.Name = "colName";
    this.colName.ReadOnly = true;
    //
    // colRrp
    //
    this.colRrp.DataPropertyName = "Rrp";
    this.colRrp.HeaderText = "RRP";
    this.colRrp.Name = "colRrp";
    this.colRrp.ReadOnly = true;
    //
    // colSellingPrice
    //
    this.colSellingPrice.DataPropertyName = "SellingPrice";
    this.colSellingPrice.HeaderText = "Selling Price";
    this.colSellingPrice.Name = "colSellingPrice";
    this.colSellingPrice.ReadOnly = true;
    //
    // colDiscount
    //
    this.colDiscount.DataPropertyName = "Discount";
    this.colDiscount.HeaderText = "Discount";
    this.colDiscount.Name = "colDiscount";
    //
    // colSavings
    //
    this.colSavings.DataPropertyName = "Savings";
    this.colSavings.HeaderText = "Savings";
    this.colSavings.Name = "colSavings";
    this.colSavings.ReadOnly = true;
    //
    // Form1
    //
    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(589, 338);
    this.Controls.Add(this.cmbCustomerType);
    this.Controls.Add(this.dgvProducts);
    this.Name = "Form1";
    this.Text = "Form1";
    ((System.ComponentModel.ISupportInitialize)(this.dgvProducts)).EndInit();
    this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ComboBox cmbCustomerType;
private System.Windows.Forms.DataGridView dgvProducts;
private System.Windows.Forms.DataGridViewTextBoxColumn colName;
private System.Windows.Forms.DataGridViewTextBoxColumn colRrp;
private System.Windows.Forms.DataGridViewTextBoxColumn colSellingPrice;
private System.Windows.Forms.DataGridViewTextBoxColumn colDiscount;
private System.Windows.Forms.DataGridViewTextBoxColumn colSavings;

سپس در Code Behind، با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:

در WebUI:

وارد کد نویسی صفحه Default.aspx شده و کد آن را بصورت زیر تغییر دهید:

using System;
using System.Collections.Generic;
using SoCPatterns.Layered.Model;
using SoCPatterns.Layered.Presentation;
using SoCPatterns.Layered.Service;
using StructureMap;

namespace SoCPatterns.Layered.WebUI
{
    public partial class Default : System.Web.UI.Page, IProductListView
    {
        private ProductListPresenter _productListPresenter;
        protected void Page_Init(object sender, EventArgs e)
        {
            _productListPresenter = new ProductListPresenter(this,ObjectFactory.GetInstance<Service.ProductService>());
            this.ddlCustomerType.SelectedIndexChanged +=
                delegate { _productListPresenter.Display(); };
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            if(!Page.IsPostBack)
                _productListPresenter.Display();
        }
        public void Display(IList<ProductViewModel> products)
        {
            rptProducts.DataSource = products;
            rptProducts.DataBind();
        }
        public CustomerType CustomerType
        {
            get { return (CustomerType) int.Parse(ddlCustomerType.SelectedValue); }
        }
        public string ErrorMessage
        {
            set
            {
                lblErrorMessage.Text =
                    String.Format("<p><strong>Error:</strong><br/>{0}</p>", value);
            }
        }
    }
}

در WinUI:

وارد کدنویسی Form1 شوید و کد آن را بصورت زیر تغییر دهید:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using SoCPatterns.Layered.Model;
using SoCPatterns.Layered.Presentation;
using SoCPatterns.Layered.Service;
using StructureMap;

namespace SoCPatterns.Layered.WinUI
{
    public partial class Form1 : Form, IProductListView
    {
        private ProductListPresenter _productListPresenter;
        public Form1()
        {
            InitializeComponent();
            _productListPresenter =
                new ProductListPresenter(this, ObjectFactory.GetInstance<Service.ProductService>());
            this.cmbCustomerType.SelectedIndexChanged +=
                delegate { _productListPresenter.Display(); };
            dgvProducts.AutoGenerateColumns = false;
            cmbCustomerType.SelectedIndex = 0;
        }
        public void Display(IList<ProductViewModel> products)
        {
            dgvProducts.DataSource = products;
        }
        public CustomerType CustomerType
        {
            get { return (CustomerType)cmbCustomerType.SelectedIndex; }
        }
        public string ErrorMessage
        {
            set
            {
                MessageBox.Show(
                    String.Format("Error:{0}{1}", Environment.NewLine, value));
            }
        }
    }
}

با توجه به کد فوق، نمونه ای را از کلاس ProductListPresenter، در لحظه‌ی نمونه سازی اولیه‌ی کلاس UI، ایجاد نمودیم. با استفاده از متد ObjectFactory.GetInstance مربوط به StructureMap، نمونه ای از کلاس ProductService ایجاد شده است و به سازنده‌ی کلاس ProductListPresenter ارسال گردیده است. در مورد Structuremap در مباحث بعدی با جزئیات بیشتری صحبت خواهم کرد. پیاده سازی معماری لایه بندی در اینجا به پایان رسید.

اما اصلا نگران نباشید، شما فقط پرواز کوتاه و مختصری را بر فراز کدهای معماری لایه بندی داشته اید که این فقط یک دید کلی را به شما در مورد این معماری داده است. این معماری هنوز جای زیادی برای کار دارد، اما در حال حاضر شما یک Applicaion با پیوند ضعیف (Loosely Coupled) بین لایه‌ها دارید که دارای قابلیت تست پذیری قوی، نگهداری و پشتیبانی آسان و تفکیک پذیری قدرتمند بین اجزای آن می‌باشد. شکل زیر تعامل بین لایه‌ها و وظایف هر یک از آنها را نمایش می‌دهد.

نظرات مطالب
برنامه نویسی اندروید با Xamarin.Android - قسمت اول
- لینکی که برای دانلود دادند کمی قدیمی هست و تا 2013 VS رو بیشتر پشتیبانی نمی‌کنه. نسخه‌ی کامل سازگار با 2015 رو از سایت board4allcz.eu تاپیک شما‌ره‌ی 615407 دریافت کنید (بخش DotNet ->  Xamarin Visual Studio Enterprise 4.0.0.1717). 
- اگر به دنبال نسخه‌ی کامل با تمام وابستگی‌های مرتبط هستید (به حجم 1.17 GB) به سایت rutracker.org و تاپیک شما‌ره‌ی 4549935 آن مراجعه کنید. فایل تورنت آن‌را برای دریافت گذاشته: t4549935.torrent.7z