نظرات مطالب
آموزش (jQuery) جی کوئری 7#
سلام
مثلا در مورد طراحی یک سایت که اطلاعاتی بصورت تکراری پشت سر هم تکرار میشن (مثلا کامنت‌های که برای یک موضوع ارسال میشن) چطور باید باید اطلاعات مثلا مربوط به یک فیلد رو  دستکاری انجام بدیم
برای مثال
@foreach(var item in Model)
{
   <td class="text-right itemfarsi">@item.Farsi</td>
}
چطور میشه مثلا همین تیبل دیتا رو برای هر کامنت باتوجه به متن اون تغییر داد
من با استفاده از کد‌های زیر دستور خودم رو انجام میدم اما در مورد تمام مطالب فقط اطلاعات مربوط به قسمت اول رو برمیگردونه.
    $(document).ready(function () {
        var content = $(".itemfarsi").text();        
        if (content.length >= 50) {
            var mycont = content.substring(0, 50);           
            $(".itemfarsi").html(mycont);
        } else {            
            $(".itemfarsi").html(content);
        }            
    });
مطالب
آغاز به کار با Twitter Bootstrap در ASP.NET MVC
Twitter Bootstrap یک فریم ورک CSS بسیار محبوب سورس باز تولید برنامه‌های وب به کمک HTML، CSS و جاوا اسکریپت است. این فریم ورک حاوی بسیاری از المان‌های مورد نیاز جهت تولید وب سایت‌هایی زیبا، مانند دکمه‌ها، عناصر فرم‌ها، منوها، ویجت‌ها و غیره است. تمام این‌ها نیز همانطور که عنوان شد برمبنای HTML، CSS و جاوا اسکریپت تهیه شده‌اند؛ بنابراین در هر نوع فناوری سمت سروری مانند ASP.NET، PHP، روبی و امثال آن قابل استفاده است.


دریافت Twitter Bootstrap

محل اصلی دریافت Twitter Bootstrap، آدرس ذیل است:
البته ما از آن در اینجا به شکل خام فوق استفاده نخواهیم کرد؛ زیرا نیاز است قابلیت‌های استفاده در محیط‌های راست به چپ فارسی نیز به آن اضافه شود. برای این منظور می‌توانید از یکی از دو بسته نیوگت ذیل استفاده نمائید:


و یا حتی از منابع سایت http://rbootstrap.ir نیز می‌توان استفاده کرد.
برای نمونه دستور زیر را در کنسول پاورشل ویژوال استودیو وارد نمائید تا اسکریپت‌ها و فایل‌های CSS مورد نیاز به پروژه جاری اضافه شوند:
 PM> Install-Package Twitter.BootstrapRTL
پس از نصب، شاهد افزوده شدن چنین فایل‌هایی به یک پروژه ASP.NET MVC خواهیم بود:


در اینجا فایل‌های min، نگارش‌های فشرده شده فایل‌های js یا css هستند که با توجه به امکانات اضافه شده به ASP.NET MVC4، از آن‌ها استفاده نخواهیم کرد و برای افزودن و تعریف آن‌ها از امکانات Bundling and minification توکار فریم ورک ASP.NET MVC به نحوی که در ادامه توضیح داده خواهد شد، استفاده می‌کنیم.
فایل‌های png اضافه شده، آیکون‌های مخصوص Twitter Bootstrap هستند که اصطلاحا به آن‌ها sprite images نیز گفته می‌شود. در این نوع تصاویر، تعداد زیادی آیکون در کنار هم، برای بهینه سازی تعداد بار رفت و برگشت به سرور جهت دریافت تصاویر، طراحی شده و قرار گرفته‌اند.
فایل‌های js این مجموعه اختیاری بوده و برای استفاده از ویجت‌های Twitter Bootstrap مانند آکاردئون کاربرد دارند. این فایل‌ها برای اجرا، نیاز به jQuery خواهند داشت.


افزودن تعاریف اولیه Twitter Bootstrap به یک پروژه ASP.NET MVC

امکانات Bundling and minification در نوع پروژه‌های نسبتا جامع‌تر ASP.NET MVC به صورت پیش فرض لحاظ شده است. اما اگر یک پروژه خالی را شروع کرده‌اید، نیاز است بسته نیوگت آن‌را نیز نصب کنید:
 PM> Install-Package Microsoft.AspNet.Web.Optimization
پس از آن، کلاس کمکی BundleConfig ذیل را به پروژه جاری اضافه نمائید. به کمک آن قصد داریم امکانات Bundling and minification را فعال کرده و همچنین به آن بگوییم اسکریپت‌ها و فایل‌های CSS تعریف شده را به همان نحوی که معرفی می‌کنیم، پردازش کن (توسط کلاس سفارشی AsIsBundleOrderer).

using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Optimization;

namespace Mvc4TwitterBootStrapTest.Helper
{
    /// <summary>
    /// A custom bundle orderer (IBundleOrderer) that will ensure bundles are 
    /// included in the order you register them.
    /// </summary>
    public class AsIsBundleOrderer : IBundleOrderer
    {
        public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
        {
            return files;
        }
    }

    public static class BundleConfig
    {
        private static void addBundle(string virtualPath, bool isCss, params string[] files)
        {
            BundleTable.EnableOptimizations = true;

            var existing = BundleTable.Bundles.GetBundleFor(virtualPath);
            if (existing != null)
                return;

            Bundle newBundle;
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                newBundle = new Bundle(virtualPath);
            }
            else
            {
                newBundle = isCss ? new Bundle(virtualPath, new CssMinify()) : new Bundle(virtualPath, new JsMinify());
            }
            newBundle.Orderer = new AsIsBundleOrderer();

            foreach (var file in files)
                newBundle.Include(file);

            BundleTable.Bundles.Add(newBundle);
        }

        public static IHtmlString AddScripts(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Render(virtualPath);
        }

        public static IHtmlString AddStyles(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Render(virtualPath);
        }

        public static IHtmlString AddScriptUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Url(virtualPath);
        }

        public static IHtmlString AddStyleUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Url(virtualPath);
        }
    }
}
نکته دیگری که در این کلاس سفارشی درنظر گرفته شده، عدم فعال سازی مباحث Bundling and minification در حالت Debug است. اگر در وب کانفیگ، تنظیمات پروژه را بر روی Release قرار دهید فعال خواهد شد (مناسب برای توزیع) و در حالت توسعه برنامه (حالت دیباگ)، این اسکریپت‌ها و فایل‌های CSS، قابلیت دیباگ و بررسی را نیز خواهند داشت.

پس از افزودن کلاس‌های کمکی فوق، به فایل layout پروژه مراجعه کرده و تعاریف ذیل را به ابتدای فایل اضافه نمائید:
@using Mvc4TwitterBootStrapTest.Helper
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    @BundleConfig.AddStyles("~/Content/css",
                            "~/Content/bootstrap.css",
                            "~/Content/bootstrap-responsive.css",
                            "~/Content/Site.css"
                            )
    @BundleConfig.AddScripts("~/Scripts/js",
                            "~/Scripts/jquery-1.9.1.min.js",
                            "~/Scripts/jquery.validate.min.js",
                            "~/Scripts/jquery.unobtrusive-ajax.min.js",
                            "~/Scripts/jquery.validate.unobtrusive.min.js",
                            "~/Scripts/bootstrap.min.js"
                            )
    @RenderSection("JavaScript", required: false)
</head>
و اگر برنامه را اجرا کنید، بجای حداقل 8 مدخل اشاره کننده به فایل‌های اسکریپت و CSS مورد نیاز یک برنامه ASP.NET MVC، فقط دو مدخل ذیل را مشاهده خواهید نمود:
<!DOCTYPE html>
<html>
<head>
    <title>Index</title>
    <link href="/Content/css?v=vsUQD0OJg4AJ-RZH8jSRRCu_rjl2U1nZrmSsaUyxoAc1" rel="stylesheet"/>
    <script src="/Scripts/js?v=GezdoTDiWY3acc3mI2Ujm_7nKKzh6Lu1Wr8TGyyLpW41"></script>
</head>
به این ترتیب تعداد رفت و برگشت‌های مرورگر به سرور، برای دریافت فایل‌های مورد نیاز جهت رندر صحیح یک صفحه، به شدت کاهش خواهد یافت. همچنین این فایل‌ها در حالت release فشرده نیز خواهند شد؛ به همراه تنظیم خودکار هدر کش شدن آن‌ها برای مدتی مشخص، جهت کاهش بار سرور و کاهش پهنای باند مصرفی آن.


مفاهیم پایه‌ای Twitter Bootstrap

الف) Semantic class names
به عبارتی کلاس‌های Twitter Bootstrap دارای نام‌هایی معنا دار و مفهومی می‌باشند؛ مانند کلاس‌های CSS‌ایی، به نام‌های Succes، Error، Info و امثال آن. این نام‌ها مفهومی را می‌رسانند؛ اما در مورد نحوه پیاده سازی آن‌ها جزئیاتی را بیان نمی‌کنند.
برای نمونه می‌توان کلاسی را به نام redText ایجاد کرد. هر چند این نام، توضیحاتی را در مورد علت وجودی‌اش بیان می‌کند، اما بسیار ویژه بوده و در مورد جزئیات پیاده سازی آن نیز اطلاعاتی را ارائه می‌دهد. در این حالت redText معنایی ندارد. چرا یک Text باید قرمز باشد؟ برای مثال این متن قرمز است چون مثلا شخصی، به آن رنگ ویژه علاقه دارد، یا اینکه قرمز است بخاطر نمایش خطایی در صفحه؟ به همین جهت در Twitter Bootstrap از نام‌های مفهومی یاده شده، مانند Error استفاده می‌شود. نام‌هایی معنا دار اما بدون دقیق شدن در مورد ریز جزئیات پیاده سازی آن‌ها. در این حالت می‌توان قالب جدیدی را تدارک دید و با ارائه تعاریف جدیدی برای کلاس Error و نحوه نمایش دلخواهی را به آن اعمال نمود.
یا برای نمونه نام rightside را برای نمایش ستونی در صفحه، درنظر بگیرید. این نام بسیار ویژه است؛ اما Sematic name آن می‌تواند sidebar باشد تا بدون دقیق شدن در جزئیات پیاده سازی آن، در چپ یا راست صفحه قابل اعمال باشد.
Semantic class names کلیدهایی هستند جهت استفاده مجدد از قابلیت‌های یک فریم ورک CSS.

ب) Compositional classes
اکثر کلاس‌های Twitter Bootstrap دارای محدوده کاری کوچکی هستند و به سادگی قابل ترکیب با یکدیگر جهت رسیدن به نمایی خاص می‌باشند. برای مثال به سادگی می‌توان به یک table سه ویژگی color، hover و width برگرفته شده از Twitter Bootstrap را انتساب داد و نهایتا به نتیجه دلخواه رسید؛ بدون اینکه نگران باشیم افزودن کلاس جدیدی در اینجا بر روی سایر کلاس‌های انتساب داده شده، تاثیر منفی دارد.

ج) Conventions
برای استفاده از اکثر قابلیت‌های این فریم ورک CSS یک سری قراردادهای پیش فرضی وجود دارند. برای مثال اگر از کلاس توکار pagination به همراه یک سری ul و li استفاده کنید، به صورت خودکار یک pager شکیل ظاهر خواهد شد. یا برای مثال اگر به یک html table کلاس‌های table table-striped table-hover را انتساب دهیم، به صورت خودکار قراردادهای پیش فرض table مجموعه Twitter Bootstrap به آن اعمال شده، به همراه رنگی ساختن یک درمیان زمینه ردیف‌ها و همچنین فعال سازی تغییر رنگ ردیف‌ها با حرکت ماوس از روی آن‌ها.


طرحبندی صفحات یک سایت به کمک Twitter Bootstrap

بررسی Grid layouts

Layout به معنای طرحبندی و چیدمان محتوا در یک صفحه است. یکی از متداولترین روش‌های طرحبندی صفحات چه در حالت چاپی و چه در صفحات وب، چیدمان مبتنی بر جداول و گریدها است. از این جهت که نحوه سیلان و نمایش محتوا از چپ به راست و یا راست به چپ را به سادگی میسر می‌سازند؛ به همراه اعمال حاشیه‌های مناسب جهت قسمت‌های متفاوت محتوای ارائه شده. Grid در طرحبندی، نمایش بصری نخواهد داشت اما در ساختار صفحه وجود داشته و مباحثی مانند جهت، موقعیت و یکپارچگی و یکدستی طراحی را سبب می‌شود.
به علاوه مرورگرها و مفهوم Grid نیز به خوبی با یکدیگر سازگار هستند. در دنیای HTML و ،CSS طراحی‌ها بر اساس مفهوم ساختار مستطیلی اشیاء صورت می‌گیرد:


برای نمونه در اینجا تصویر CSS Box Model را مشاهده می‌کنید. به این ترتیب، هر المان دارای محدوده‌ای مستطیلی با طول و عرض مشخص، به همراه ویژگی‌هایی مانند Margin، Border و Padding است.
در سال‌های اولیه طراحی وب، عموما کارهای طراحی صفحات به کمک HTML Tables انجام می‌شد. اما با پخته‌تر شدن CSS، استفاده از Tables برای طراحی صفحات کمتر و کمتر گشت تا اینکه نهایتا فریم ورک‌های CSS ایی پدید آمدند تا طراحی‌های مبتنی بر CSS را با ارائه گرید‌ها، ساده‌تر کنند. مانند Blue print، 960 GS و ... Twitter Bootstrap که طراحی مبتنی بر گرید‌های CSS ایی را به مجموعه قابلیت‌های دیگر خود افزوده است.


بررسی Fixed Grids

در اینجا در صفحه layout برنامه، یک Div دربرگیرنده دو Div دیگر را مشاهده می‌کنید:
<body>
    <div>
        <div>
            <h1>
                Title Title
            </h1>
            Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text
        </div>
        <div>
            @RenderBody()
        </div>
    </div>
</body>
اگر در این حالت صفحه را در مرورگر بررسی کنیم، تمام قسمت‌های نمایش داده شده به همین نحو از بالا به پایین صفحه قرار خواهند گرفت. اما قصد داریم این محتوا را، دو ستونی نمایش دهیم. Div به همراه Title در یک طرف صفحه و Div دربرگیرنده محتوای صفحات، در قسمتی دیگر.
برای اینکار در Twitter Bootstrap از کلاسی به نام row استفاده می‌شود که بیانگر یک ردیف است. این کلاس را به خارجی‌ترین Div موجود اعمال خواهیم کرد. در یک صفحه، هر تعداد row ایی را که نیاز باشد، می‌توان تعریف کرد. داخل این ردیف‌ها، امکان تعریف ستون‌های مختلف و حتی تعریف ردیف‌های تو در تو نیز وجود دارد. هر ردیف Twitter Bootstrap از 12 ستون تشکیل می‌شود و برای تعریف آن‌ها از کلاس span استفاده می‌گردد. در این حالت جمع اعداد ذکر شده پس از span باید 12 را تشکیل دهند.
<body>
    <div class="row">
        <div class="span7">
            <h1>
                Title Title
            </h1>
            Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text
        </div>
        <div  class="span5">
            @RenderBody()
        </div>
    </div>
</body>
برای نمونه در اینجا کلاس row به خارجی‌ترین Div موجود اعمال شده است. دو Div داخلی هر کدام دارای کلاس‌های span ایی جهت تشکیل ستون‌های داخل این ردیف هستند. جمع اعداد بکارگرفته شده پس از آن‌ها، عدد 12 را تشکیل می‌دهد. به این ترتیب به یک طراحی دو ستونی خواهیم رسید.


در این تصویر، قسمت RenderBody کار رندر اکشن متد Index کنترلر Home برنامه را با Viewایی معادل کدهای ذیل، انجام داده است:
@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<div class="hero-unit">
    <h2>@ViewBag.Message</h2>
    <p>
        This is a template to demonstrate the way to beautify the default MVC template
        using Twitter Bootstrap website. It is pretty simple and easy.</p>
    <p>
        <a href="http://asp.net/mvc" class="btn btn-primary btn-large" style="color: White;">
            To learn more about ASP.NET MVC visit &raquo;</a></p>
</div>

درک نحوه عملکرد Grid در Twitter Bootstrap

در مثال ذیل 5 ردیف را مشاهده می‌کنید:
    <div class="row">
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
    </div>
    <div class="row">
        <div class="span3">3</div>
        <div class="span4">4</div>
        <div class="span5">5</div>
    </div>
    <div class="row">
        <div class="span5">5</div>
        <div class="span7">7</div>
    </div>
    <div class="row">
        <div class="span3">3</div>
        <div class="span7 offset2">7 offset 2</div>
    </div>
    <div class="row">
        <div class="span12">12</div>
    </div>
در هر ردیف، امکان استفاده از حداکثر 12 ستون وجود دارد که یا می‌توان مانند ردیف اول، 12 ستون را ایجاد کرد و یا مانند ردیف دوم، سه ستون با عرض‌های متفاوت (و یا هر ترکیب دیگری که به جمع 12 برسد).
در ستون چهارم، از کلاس offset نیز استفاده شده است. این مورد سبب می‌شود ستون جاری به تعدادی که مشخص شده است به سمت چپ (با توجه به استفاده از حالت RTL در اینجا) رانده شود و سپس ترسیم گردد.
یا اینکه می‌توان مانند ردیف آخر، یک ستون را به عرض 12 که در حقیقت 940 پیکسل است، ترسیم نمود.
برای اینکه بتوانیم این گرید تشکیل شده و همچنین ستون‌ها را بهتر مشاهده کنیم، به فایل style.css سایت، تنظیم زیر را اضافه کنید:
[class*="span"]
{
background-color: lightblue;
text-align: center;
margin-top: 15px;
}


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


یک نکته
اگر نمی‌خواهید که چنین رفتار واکنش‌گرایی بروز کند، نیاز است کلیه ردیف‌ها را در div ایی با کلاسی به نام container محصور کنید.
به این ترتیب ابتدا گرید نمایش داده شده، در میانه صفحه ظاهر خواهد شد (پیشتر از سمت راست شروع شده بود). همچنین دیگر با کوچک و بزرگ شدن اندازه مرورگر، ستون‌ها به شکل یک پشته بر روی هم قرار نخواهند گرفت. (اگر پس از این تنظیم، چنین قابلیتی را مشاهده نکردید و هنوز هم طراحی، واکنشگرا بود، تعریف bootstrap-responsive.css را نیاز است برای آزمایش، از هدر صفحه حذف کنید)


بررسی Fluid Grids

به گرید قسمت قبل از این جهت Fixed Grid گفته می‌شود که عرض هر span آن با یک عدد مشخص تعیین گشته است. اما در حالت Fluid Grid، عرض هر span برحسب درصد تعیین می‌شود. بکارگیری درصد در اینجا به معنای امکان تغییر عرض یک ستون بر اساس عرض جاری Container آن است. در اینجا span12 دارای عرض 100 درصد خواهد بود.
در مثال قبل، برای استفاده از Fluid grids، تنها کافی است هرجایی کلاسی مساوی row وجود دارد، به row-fluid تغییر کند. همچنین کلاس container را به container-fluid تغییر دهید.
برای آزمایش آن، اندازه و عرض نمایشی مرورگر خود را تغییر دهید. اینبار مشاهده خواهید کرد که برخلاف حالت Fixed Grid، عرض ستون‌ها به صورت خودکار کم و زیاد می‌شوند. این مورد بر روی محتوای قرار گرفته در این ستون‌ها نیز تاثیر گذار است. برای مثال اگر یک تصویر را در حالت Fluid grid در ستونی قرار دهید، با تغییر عرض مرورگر، اندازه این تصویر نیز تغییر خواهند کرد؛ اما در حالت Fixed Grid خیر.
حالت Fluid، شیوه متداول استفاده از bootstrap در اکثر سایت‌های مهمی است که تابحال از این فریم ورک CSS استفاده کرده‌اند.



مروری بر طراحی واکنشگرا یا Responsive

این روزها تعدادی از کاربران، با استفاده از ابزارهای موبایل و تبلت‌ها از وب سایت‌ها بازدید می‌کنند. هر کدام از این‌ها نیز دارای اندازه نمایشی متفاوتی می‌باشند. بنابراین نیاز خواهد بود تا حالت بهینه‌ای را جهت اینگونه وسایل نیز طراحی نمود. حالت بهینه در اینجا به معنای قابل خواندن بودن متون، امکان استفاده از لینک‌های ورود به صفحات مختلف و همچنین حذف اسکرول و مباحث زوم جهت مشاهده صفحات است.
یکی از ویژگی‌های CSS به نام media چنین قابلیتی را فراهم می‌سازد. برای نمونه قسمتی از فایل bootstrap-responsive.css دارای چنین تعاریفی است:
@media (min-width: 768px) and (max-width: 979px) {
    .hidden-desktop {
        display: inherit !important;
    }
توسط این تنظیمات، شیوه نامه تعیین شده، تنها به صفحاتی با اندازه‌هایی بین 768 تا 979 پیکسل اعمال می‌شود (تعدادی از تبلت‌ها برای نمونه).
Bootstrap برای مدیریت اندازه‌های مختلف وسایل موبایل، شیوه‌نامه‌های خاصی را تدارک دیده است که از اندازه px480 و یا کمتر، تا px1200 و یا بیشتر را پوشش می‌دهد.
به این ترتیب با اندازه px940 که پیشتر در مورد آن بحث شد، اندازه span12 به صورت خودکار به اندازه‌های متناسب با صفحات نمایشی کوچکتر تنظیم می‌گردد. همچنین برای اندازه‌های صفحات کوچکتر از 768px به صورت خودکار از Fluid columns استفاده می‌گردد.
تنها کاری که برای اعمال این قابلیت باید صورت گیرد، افزودن تعاریف فایل bootstrap-responsive.css به هدر صفحه است که در قسمت قبل انجام گردید. این فایل باید پس از فایل اصلی bootstrap.css اضافه شود.


کلاس‌های کمکی طراحی واکنشگرا

Bootstrap شامل تعدادی کلاس کمکی در فایل bootstrap-responsive.css خود می‌باشد شامل visible-phone، visible-tablet و visible-desktop به علاوه hidden-phone، hidden-tablet و hidden-desktop. به این ترتیب می‌توان محتوای خاصی را جهت وسایل ویژه‌ای نمایان یا مخفی ساخت.
برای مثال محتوای مشخص شده با کلاس hidden-desktop، در اندازه وسایل دسکتاپ نمایش داده نخواهد شد.
برای آزمایش آن، شش div را با کلاس‌های یاد شده و محتوایی دلخواه ایجاد کرده و سپس اندازه عرض مرورگر را تغییر دهید تا بهتر بتوان مخفی یا نمایان ساختن محتوا را بر اساس اندازه صفحه جاری درک کرد.
یکی از کاربردهای این قابلیت، قرار دادن تبلیغاتی با اندازه‌های تصاویری مشخص برای وسایل مختلف است؛ بجای اینکه منتظر شویم تا Fluid layout اندازه تصاویر را به صورت خودکار کوچک یا بزرگ کند، که الزاما بهترین کیفیت را حاصل نخواهد ساخت.
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span4">
                <div class="visible-phone">
                    visible-phone</div>
            </div>
            <div class="span4">
                <div class="visible-tablet">
                    visible-tablet</div>
            </div>
            <div class="span4">
                <div class="visible-desktop">
                    visible-desktop</div>
            </div>
        </div>
    </div>
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span4">
                <div class="hidden-phone">
                    hidden-phone</div>
            </div>
            <div class="span4">
                <div class="hidden-tablet">
                    hidden-tablet</div>
            </div>
            <div class="span4">
                <div class="hidden-desktop">
                    hidden-desktop</div>
            </div>
        </div>
    </div>

مطالب
استفاده از Google Analytics در ASP.Net

قبل از استفاده از بلاگر، در سایت wordpress وبلاگ داشتم، که به‌دلایلی کنسل شد. تفاوت محسوسی را که اینجا مشاهده می‌کنم، نبود قسمت آمار سایت است. در سایت wordpress آمار مبسوطی را از بازدید کنندگان سایت می‌توانید در کنترل پنل مدیریتی وبلاگ مشاهده کنید، اما در اینجا خیر.
به همین جهت اولین کاری را که انجام دادم استفاده از سرویس رایگان persianstat بود که انصافا هم با کیفیت است و قابل مقایسه با آماری که wordpress ارائه می‌دهد، می‌باشد.
جالب اینجا است که هر چند هاست اینجا، گوگل است اما استفاده‌ی خودکار از ابزار Google analytics در آن مهیا نیست. احتمالا علت آن آماده نبودن API آن است که قرار است به زودی ارائه شود، بنابراین ارزش وقت گذاشتن را دارد.



برای استفاده از Google analytics ، پس از ثبت نام و ورود به آن، سایت مورد نظر را معرفی کرده (در قسمت Add Website Profile) و نهایتا یک کد جاوا اسکریپتی به شما خواهد داد که می‌توانید آنرا به صفحات مورد نظر خود در سایت اضافه نمائید تا تحت کنترل آماری قرار گیرد. محدودیتی هم در مورد تعداد سایت وجود ندارد و با یک اکانت می‌توانید چندین سایت را معرفی کرده و تحت کنترل قرار دهید.
اگر از ASP.Net استفاده می‌کنید، تنها کافی است به master page سایت مراجعه کنید و پیش از بسته شدن تگ body ، اسکریپت مربوط به Google analytics را اضافه کنید تا تمام سایت را تحت کنترل قرار دهید.
یا اگر علاقمند بودید که اینکار را به صورت "شیک‌تری" انجام دهید، می‌توان از این http module استفاده کرد. به این صورت ابتدا تگ بسته شدن body به صورت خودکار پیدا شده و سپس اسکریپت به پیش از آن اضافه می‌شود.
این روش بار بزرگ تهیه آمار سایت را حذف خواهد کرد. عموما دیتابیس جمع آوری آمار سایت خیلی زود (برای مثال پس از گذشت 6 ماه) حجیم می‌شود و تاثیر مشهودی را بر روی کارآیی سایت خواهد گذاشت. بنابراین، این سؤال مطرح می‌شود که چرا گوگل اینکار را برای ما انجام ندهد؟! هزینه بانک اس کیوال سرور بر روی هاست‌های اینترنتی بالا بوده و حجمی را هم که در اختیار قرار می‌دهند محدود است. در صورت نیاز به حجم‌های بالاتر باید هزینه بیشتری را پرداخت کرد. بنابراین هم از لحاظ قیمت و هچنین کارآیی سایت، استفاده از این سرویس واقعا مقرون به صرفه است. بعلاوه از تنوع آماری که ارائه می‌دهد نیز نمی‌توان چشم پوشی کرد. برای مثال کاربران چه واژه‌های کلیدی را در موتورهای جستجو وارد کرده‌اند تا به سایت شما رسیده‌اند؟ چند درصد کاربر وفادار دارید؟! (کاربرهای وفادار، منظور افرادی هستند که به صورت منظم به سایت سر می‌زنند) و امثال این. انصافا تهیه چنین ماژولی برای یک سایت از لحاظ برنامه نویسی شاید با برنامه نویسی کل یک سایت برابری کند.
اگر هم نیاز به یک برنامه سورس باز داشتید که هر روز به اکانت Google analytics شما سر بزند و اطلاعات آنرا استخراج کرده و در یک بانک SQL server ذخیره کند، می‌توانید به پروژه سی شارپ زیر مراجعه نمائید:
Google Analytics Data Extractor

البته باید دقت داشت که پس از ارائه API کامل Google analytics ، دیگر نیازی به این نوع روش‌های ابتکاری وجود نداشته و استخراج داده از آن بسیار ساده‌تر خواهد شد.

مطالب
ایجاد رشته Alphanumeric تصادفی در سی شارپ
برای ایجاد یک رشته تصادفی Alphanumeric (شامل حرف و عدد) روشهای زیادی وجود دارد ولی در اینجا به تشریح 2 روش آن اکتفا می‌کنیم.

روش کلی: ابتدا بازه رشته تصادفی مورد نظر را تعیین می‌کنیم. سپس به اندازه طول رشته، اندیس تصادفی ایجاد می‌کنیم و بوسیله آنها کاراکتر تصادفی را از بازه بدست می‌آورم و در انتها کاراکترهای تصادفی را با هم ادغام کرده تا رشته نهایی حاصل شود.
  • روش اول:

ابتدا بازه (char) رشته را مشخص می‌کنیم.

var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
سپس بوسیله LINQ آن را به اندازه طول رشته دلخواه (در این مثال 8کاراکتر) تکرار می‌کنیم و برای انتخاب تصادفی یک کاراکتر در هر بازه (char) تکرار شده از کلاس Random  جهت بدست آوردن اندیس تصادفی بازه استفاده می‌کنیم.
var random = new Random();
var result = new string(
Enumerable.Repeat(chars, 8)
          .Select(s => s[random.Next(s.Length)])
          .ToArray());
توجه: از این روش برای هیچ کدام از موارد مهم و کلیدی مانند ساخت کلمه عبور و توکن استفاده نکنید.
  • روش دوم:

همانند روش اول ابتدا بازه رشته را تعیین می‌کنیم.

char[] chars = new char[62];
chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
بعد از تعریف بازه، یک سری اعداد تصادفی غیر صفر را بوسیله کلاس RNGCryptoServiceProvider و متد GetNonZeroBytes آن، در متغیری که قرار است بعدا در ایجاد رشته‌ی تصادفی نیاز است پر می‌کنیم.
byte[] data = new byte[maxSize];
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
crypto.GetNonZeroBytes(data);
در این مرحله به تعداد طول رشته تصادفی مورد نظر عدد تصادفی بین 0 تا 255 ذخیره شده در متغیر data داریم، برای ایجاد اندیس تصادفی از باقیمانده عدد تصادفی ایجاد شده در مرحله قبل  (byte) به طول بازه (chars.Length) استفاده می‌کنیم سپس کاراکترهای تصادفی را کنار یکدیگر قرار می‌دهیم.
StringBuilder result = new StringBuilder(maxSize);
foreach (byte b in data)
{
  result.Append(chars[b % (chars.Length)]);
}
و در نهایت متد ما جهت ایجاد رشته Alphanumeric در روش دوم به شکل زیر خواهد بود:
public static string GetRandomAlphaNumeric (int maxSize)
{
      char[] chars = new char[62];
      chars ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();            
      RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();            
      byte[] data = new byte[maxSize];
      crypto.GetNonZeroBytes(data);
      StringBuilder result = new StringBuilder(maxSize);
      foreach (byte b in data)
      {
           result.Append(chars[b % (chars.Length)]);
      }
      return result.ToString();
}
لازم به یادآوری است که رشته ایجاد شده در 2روش بیان شده منحصر بفرد نیست بلکه تصادفی است، درواقع تصادفی بودن با منحصر بودن متفاوت است، برای ایجاد رشته‌های منحصر بفرد روشهایی (البته این روشها 100 درصد نیستند ولی از قابلیت اطمینان بالایی برخوردار هستند) وجود دارد که پست‌های بعدی به آنها اشاره خواهم کرد.
اشتراک‌ها
معرفی ابزارهای مفید و رایگان fishcode
نرم افزارهای ارائه شده این تیم به صورت رایگان بوده و عموما بدون نیاز به نصب و با حجم بسیار کم میباشد که ابزارهای بسیار کارآمدی در اختیار برنامه نویسان قرار داده است.   
توضیحات تکمیلی در مورد نرم افزار‌ها در سایت این تیم قرار دارد ولی جهت آشنایی دوستان به صورت مختصر در مورد چند نرم افزار مفید این تیم توضحاتی خواهم داد. 

  • Convert.Net
یکی از مفیدترین و بهترین ابزارهای این تیم نرم افزار Convert.Net است که امکانات مفیدی در اختیار شما قرار میدهد از جمله :
  1. Regular Expression Tester : که جهت تست Regex‌های نوشته شده استفاده میشود.
  2. Encoding & Decoding : جهت تبدیل انواع رشته‌های Encoded ویا Decoded به یکدیگر استفاده میشود و از html - url - EScape-js - Base64 - string و ... پشتیبانی میکند.
  3. Encryption & Decryption : جهت Encrypt و Decrypt انواع رشته استفاده میشود که از انکریپتورهای معروفی همچون  AES - Rijndael - DES - SHA - TripleDES پشتیبانی میکند.
  4. Language Translation : یک دیکشنری Multi Language آنلاین در اختیار شما برای ترجمه متون قرار میدهد.
  5. C# & VB.Net Convertor : برای تبدیل کدهای C# به Vb و برعکس استفاده میشود و  طبق تست هایی که روش به شخصه انجام دادم در اکثر موارد بدون خطا تا حدود 90 درصد تبدیلات رو به صورت موفق انجام میده ولی مانند سایر Convertor‌ها با Lambda Expression کمی مشکل دارد.
  6.  Xml & Json Browser : برای مشاهده و تبدیل Xml به Json و برعکس بسیار مفید است .. 
  7. Linq Tester : برای تست کوئری‌های Linq استفاده میشود . (برای استفاده از این امکان باید Roslyne روی سیستم شما نصب باشد)
حجم برنامه 2 مگابایت : دانلود

  • Database.Net
نرم افزار کم حجم جهت اتصال و مدیریت انواع دیتابیس میباشد در عین سادگی و حجم کم ابزار مفیدی جهت اجرای Query‌ها ساخت جداول , مشاهده و ویرایش رکوردها و .... در اختیارتان قرار میدهد.
دیتابیس‌های پشتیابنی شده در این نرم افزار : 
SQL Server 2000/2005/2008/2012/2014/Express/LocalDB         
SQL Server Compact 3.1/3.5/4.0 (*.sdf;*.*)         
SQL Azure 10/11        
MS Access 97/2000/2002/2003 (*.mdb;*.mde;*.*), 2007/2010/2013 (*.accdb;*.accde;*.*)         
MS Excel 97/2000/2002/2003(*.xls;*.*), 2007/2010/2013 (*.xlsx;*.xlsm;*.xlsb;*.*)         
Firebird SuperServer/Embedded 1.5/2.0/2.1/2.5 (*.gdb;*.fdb;*.*)       
SQLite 3.x (*.db;*.db3;*.sqlite;*.*)       
MySQL 5.x, MariaDB 5.x/10.x         
PostgreSQL 8.x/9.x     
Oracle 10g/11g/12c       
IBM DB2 9.x/10.x         
IBM Informix 11.x/12.x         
Sybase ASE 15.x         
dBASE IV (*.dbf)   
Visual FoxPro (*.dbc)    
Data Sources (OleDB)(*.udl;*.*)     
ODBC DSN (Data Source Name)(*.dsn;*.*)       
OData (Open Data Protocol) v1/v2/v3/v4
حجم برنامه 9 مگابایت : دانلود

  • Resource.Net
این نرم افزار جهت ساخت , ویرایش و مدیریت انواع فایل Resource برنامه‌های دات نت بسیار مفید میباشد که از نسخه‌های مختلف دات نت پشتیبانی میکند.

سایر نرم افزار‌های این تیم هم مانند نرم افزارهای معرفی شده بین کاربران محبوبیت زیادی کسب کرده اند که میتوانید برای کسب اطلاعات بیشتر و دانلود این نرم افزار‌ها به وب سایت این تیم مراجعه فرمائید. 
معرفی ابزارهای مفید و رایگان fishcode
مطالب
دو نکته کوتاه در مورد RSS های فارسی

اولین نکته مربوط به تاریخ هر مدخل (entry) می‌شود. این تاریخ نباید شمسی باشد! این تاریخ باید حتما استاندارد باشد. عموما یکی از دو استاندارد زیر باید مورد استفاده قرار گیرد:


RFC #822
http://www.ietf.org/rfc/rfc0822.txt
Standard for ARPA Internet Text Messages (Date and Time Specification)

RFC #3339
http://www.ietf.org/rfc/rfc3339.txt
Date and Time on the Internet (Timestamps)


برای مثال در دات نت برای تولید این فرمت استاندارد می‌توان به صورت زیر عمل کرد:
DateTime.Now.ToUniversalTime().ToString("r")

متاسفانه بسیاری از برنامه نویس‌های هم وطن این نکته را رعایت نمی‌کنند و برنامه‌های فیدخوان را دچار مشکل می‌کنند. (برای نمونه برنامه معروف feedDemon تاریخ چند سال پیش را ثبت خواهد کرد و در به روز رسانی و دنبال کردن مطالب سایت مورد نظر دچار مشکل خواهد شد)

چند مثال از این دست: (سورس صفحه را در مرورگر مطالعه نمائید)
http://www.faradade.com/Xml/RSS.xml
و یا
http://www.ayande.ir/atom.xml
و یا
http://www.tci-sk.ir/Rss.aspx
(ایشان بهتر است علاوه بر این مورد، از XmlTextWriter استفاده کنند و خروجی را به صورت یک فایل xml و نه html در مرورگر Flush کنند)

و یا بدتر از این بعضی از سایت‌ها آموزش‌های غلطی را هم ارائه می‌دهند:
http://www.faradade.com/Article.aspx?code=a726ae6a-f8e1-4b29-88b4-8e7a04e6d06d
به قسمت pubDate دقت کنید.
مطابق معمول این آموزش الان در 200 سایت کپی و پیست شده! عنوان آموزش را در گوگل جستجو کنید!
این کد آموزش داده شده یک ایراد دیگر هم دارد. آیا الزامی دارد که حتما قسمت con.Close به همین ترتیب نوشته شده اجرا شود؟ اگر این بین خطایی رخ دهد تکلیف این کانکشن باز و سایر موارد چه خواهد شد؟ کلا استفاده از try و finally و یا استفاده از using را برای چه هدفی اختراع کرده‌اند؟

و یا بعضی از سایت‌ها این مورد را رعایت می‌کنند اما به صورت نصفه و نیمه. برای مثال: (تاریخ ارائه شده کامل نیست. بنابراین استاندارد تلقی نخواهد شد)
http://www.srco.ir/Articles/RSSArticles.xml

برای آزمایش میزان استاندارد بودن خروجی فید خود می‌توان از سرویس زیر استفاده کرد:
http://validator.w3.org/feed/

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

اصلاحیه برای RSS فارسی:
<language>fa-IR</language>

و برای Atom فارسی:
<feed xml:lang="fa">

و اگر می‌خواهید خروجی استانداردی داشته باشید، کتابخانه سورس باز زیر توصیه می‌شود:
http://www.codeplex.com/Argotic

با تشکر از همکاری شما!

مطالب
معرفی سایت ExtensionMethod.NET

سایت ExtensionMethod.NET بانک اطلاعاتی است از قابلیتی تحت عنوان extension methods معرفی شده در C# 3.0 و Visual Basic 2008 . هدف اصلی از extension methods ، بسط کلاس‌های دات نت فریم ورک توسط جامعه‌ی برنامه نویس‌ها می‌باشد.



مثلا در کلاس پایه string ، متدی به نام Right وجود ندارد. برای اضافه کردن آن می‌توان به صورت زیر عمل کرد:

public static string Right(this string s, int length)
{
length = Math.Max(length, 0);
if (s.Length > length)
{
return s.Substring(s.Length - length, length);
}
else
{
return s;
}
}

و پس از آن هم استفاده از این متد که کلاس پایه string را بسط داده است به شکل زیر خواهد بود (همانند یکی از متدهای کلاس string می‌توان از آن استفاده کرد):
string s = "abcde";
s = s.Right(3); //s becomes "cde"

برای پیگیری سایت هم می‌توان از فید آن استفاده نمود.

مطالب
ارتقاء به 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 و پورت آن‌را مجزا درنظر گرفت و یا از وب سرور دیگری استفاده کرد.
مطالب
Globalization در ASP.NET MVC - قسمت دوم

به‌روزرسانی فایلهای Resource در زمان اجرا

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

نکته: البته نحوه پیاده سازی این فایلها در اسمبلی نهایی (و در حالت کلی نحوه استفاده از هر فایلی در اسمبلی نهایی) در ویژوال استودیو توسط خاصیت Build Action تعیین میشود. برای کسب اطلاعات بیشتر راجع به این خاصیت به اینجا رجوع کنید.

یکی از روشهای نسبتا من‌درآوردی که برای ویرایش و به روزرسانی کلیدهای Resource وجود دارد بدین صورت است:
- ابتدا باید اصل فایلهای Resource به همراه پروژه پابلیش شود. بهترین مکان برای نگهداری این فایلها فولدر App_Data است. زیرا محتویات این فولدر توسط سیستم FCN (همان File Change Notification) در ASP.NET رصد نمیشود.
نکته: علت این حساسیت این است که FCN در ASP.NET تقریبا تمام محتویات فولدر سایت در سرور (فولدر App_Data یکی از معدود استثناهاست) را تحت نظر دارد و رفتار پیشفرض این است که با هر تغییری در این محتویات، AppDomain سایت Unload میشود که پس از اولین درخواست دوباره Load میشود. این اتفاق موجب از دست دادن تمام سشن‌ها و محتوای کش‌ها و ... میشود (اطلاعات بیشتر و کاملتر درباره نحوه رفتار FCN در اینجا).
- سپس با استفاده یک مقدار کدنویسی امکاناتی برای ویرایش محتوای این فایلها فراهم شود. ازآنجا که محتوای این فایلها به صورت XML ذخیره میشود بنابراین براحتی میتوان با امکانات موجود این ویژگی را پیاده سازی کرد. اما در فضای نام System.Windows.Forms کلاسهایی وجود دارد که مخصوص کار با این فایلها طراحی شده اند که کار نمایش و ویرایش محتوای فایلهای Resource را ساده‌تر میکند. به این کلاسها در قسمت قبلی اشاره کوتاهی شده بود.
- پس از ویرایش و به روزرسانی محتوای این فایلها باید کاری کنیم تا برنامه از این محتوای تغییر یافته به عنوان منبع جدید بهره بگیرد. اگر از این فایلهای Rsource به صورت embed استفاده شده باشد در هنگام build پروژه محتوای این فایلها به صورت Satellite Assembly در کنار کتابخانه‌های دیگر تولید میشود. اسمبلی مربوط به هر زبان هم در فولدری با عنوان زبان مربوطه ذخیره میشود. مسیر و نام فایل این اسمبلی‌ها مثلا به صورت زیر است:
bin\fa\Resources.resources.dll
بنابراین در این روش برای استفاده از محتوای به روز رسانی شده باید عملیات Build این کتابخانه دوباره انجام شود و کتابخانه‌های جدیدی تولید شود. راه حل اولی که به ذهن میرسد این است که از ابزارهای پایه و اصلی برای تولید این کتابخانه‌ها استفاده شود. این ابزارها (همانطور که در قسمت قبل نیز توضیح داده شد) عبارتند از Resource Generator و Assembly Linker. اما استفاده از این ابزارها و پیاده سازی روش مربوطه سختتر از آن است که به نظر می‌آید. خوشبختانه درون مجموعه عظیم دات نت ابزار مناسبتری برای این کار نیز وجود دارد که کار تولید کتابخانه‌های موردنظر را به سادگی انجام میدهد. این ابزار با عنوان Microsoft Build شناخته میشود که در اینجا توضیح داده شده است. 

خواندن محتویات یک فایل resx.
همانطور که در بالا توضیح داده شد برای راحتی کار میتوان از کلاس زیر که در فایل System.Windows.Forms.dll قرار دارد استفاده کرد:
System.Resources.ResXResourceReader
این کلاس چندین کانستراکتور دارد که مسیر فایل resx. یا استریم مربوطه به همراه چند گزینه دیگر را به عنوان ورودی میگیرد. این کلاس یک Enumator دارد که یک شی از نوع IDictionaryEnumerator برمیگرداند. هر عضو این enumerator از نوع object است. برای استفاده از این اعضا ابتدا باید آنرا به نوع DictionaryEntry تبدیل کرد. مثلا بصورت زیر:
private void TestResXResourceReader()
{
  using (var reader = new ResXResourceReader("Resource1.fa.resx"))
  {
    foreach (var item in reader)
    {
      var resource = (DictionaryEntry)item;
      Console.WriteLine("{0}: {1}", resource.Key, resource.Value);
    }
  }
}
همانطور که ملاحظه میکنید استفاده از این کلاس بسیار ساده است. ازآنجاکه DictionaryEntry یک struct است، به عنوان یک راه حل مناسبتر بهتر است ابتدا کلاسی به صورت زیر تعریف شود:
public class ResXResourceEntry
{
  public string Key { get; set; }
  public string Value { get; set; }
  public ResXResourceEntry() { }
  public ResXResourceEntry(object key, object value)
  {
    Key = key.ToString();
    Value = value.ToString();
  }
  public ResXResourceEntry(DictionaryEntry dictionaryEntry)
  {
    Key = dictionaryEntry.Key.ToString();
    Value = dictionaryEntry.Value != null ? dictionaryEntry.Value.ToString() : string.Empty;
  }
  public DictionaryEntry ToDictionaryEntry()
  {
    return new DictionaryEntry(Key, Value);
  }
}
سپس با استفاده از این کلاس خواهیم داشت:
private static List<ResXResourceEntry> Read(string filePath)
{
  using (var reader = new ResXResourceReader(filePath))
  {
    return reader.Cast<object>().Cast<DictionaryEntry>().Select(de => new ResXResourceEntry(de)).ToList();
  }
}
حال این متد برای استفاده‌های آتی آماده است.

نوشتن در فایل resx.
برای نوشتن در یک فایل resx. میتوان از کلاس ResXResourceWriter استفاده کرد. این کلاس نیز در کتابخانه System.Windows.Forms در فایل System.Windows.Forms.dll قرار دارد:
System.Resources.ResXResourceWriter
متاسفانه در این کلاس امکان افزودن یا ویرایش یک کلید به تنهایی وجود ندارد. بنابراین برای ویرایش یا اضافه کردن حتی یک کلید کل فایل باید دوباره تولید شود. برای استفاده از این کلاس نیز میتوان به شکل زیر عمل کرد:
private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath)
{
  using (var writer = new ResXResourceWriter(filePath))
  {
    foreach (var resource in resources)
    {
      writer.AddResource(resource.Key, resource.Value);
    }
  }
}
در متد فوق از همان کلاس ResXResourceEntry که در قسمت قبل معرفی شد، استفاده شده است. از متد زیر نیز میتوان برای حالت کلی حذف یا ویرایش استفاده کرد:
private static void AddOrUpdate(ResXResourceEntry resource, string filePath)
{
  var list = Read(filePath);
  var entry = list.SingleOrDefault(l => l.Key == resource.Key);
  if (entry == null)
  {
    list.Add(resource);
  }
  else
  {
    entry.Value = resource.Value;
  }
  Write(list, filePath);
}
در این متد از متدهای Read و Write که در بالا نشان داده شده‌اند استفاده شده است.

حذف یک کلید در فایل resx.
برای اینکار میتوان از متد زیر استفاده کرد:
private static void Remove(string key, string filePath)
{
  var list = Read(filePath);
  list.RemoveAll(l => l.Key == key); 
  Write(list, filePath);
}
در این متد، از متد Write که در قسمت معرفی شد، استفاده شده است.

راه حل نهایی
قبل از بکارگیری روشهای معرفی شده در این مطلب بهتر است ابتدا یکسری قرارداد بصورت زیر تعریف شوند:
- طبق راهنماییهای موجود در قسمت قبل یک پروژه جداگانه با عنوان Resources برای نگهداری فایلهای resx. ایجاد شود.
- همواره آخرین نسخه از محتویات موردنیاز از پروژه Resources باید درون فولدری با عنوان Resources در پوشه App_Data قرار داشته باشد.
- آخرین نسخه تولیدی از محتویات موردنیاز پروژه Resource در فولدری با عنوان Defaults در مسیر App_Data\Resources برای فراهم کردن امکان "بازگرداندن به تنظیمات اولیه" وجود داشته باشد.
برای فراهم کردن این موارد بهترین راه حل استفاده از تنظیمات Post-build event command line است. اطلاعات بیشتر درباره Build Eventها در اینجا.

برای اینکار من از دستور xcopy استفاده کردم که نسخه توسعه یافته دستور copy است. دستورات استفاده شده در این قسمت عبارتند از:
xcopy $(ProjectDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources /e /y /i /exclude:$(ProjectDir)excludes.txt
xcopy $(ProjectDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources\Defaults /e /y /i /exclude:$(ProjectDir)excludes.txt
xcopy $(ProjectDir)$(OutDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources\Defaults\bin /e /y /i 
در دستورات فوق آرگومان e/ برای کپی تمام فولدرها و زیرفولدرها، y/ برای تایید تمام کانفیرم ها، و i/ برای ایجاد خودکار فولدرهای موردنیاز استفاده میشود. آرگومان exclude/ نیز همانطور که از نامش پیداست برای خارج کردن فایلها و فولدرهای موردنظر از لیست کپی استفاده میشود. این آرگومان مسیر یک فایل متنی حاوی لیست این فایلها را دریافت میکند. در تصویر زیر یک نمونه از این فایل و مسیر و محتوای مناسب آن را مشاهده میکنید:

با استفاده از این فایل excludes.txt فولدرهای bin و obj و نیز فایلهای با پسوند user. و vspscc. (مربوط به TFS) و نیز خود فایل excludes.txt از لیست کپی دستور xcopy حذف میشوند و بنابراین کپی نمیشوند. درصورت نیاز میتوانید گزینه‌های دیگری نیز به این فایل اضافه کنید.
همانطور که در اینجا اشاره شده است، در تنظیمات Post-build event command line یکسری متغیرهای ازپیش تعریف شده (Macro) وجود دارند که از برخی از آنها در دستوارت فوق استفاده شده است:
(ProjectDir)$ : مسیر کامل و مطلق پروژه جاری به همراه یک کاراکتر \ در انتها
(SolutionDir)$ : مسیر کامل و مطلق سولوشن به همراه یک کاراکتر \ در انتها
(OutDir)$ : مسیر نسبی فولدر Output پروژه جاری به همراه یک کاراکتر \ در انتها

نکته: این دستورات باید در Post-Build Event پروژه Resources افزوده شوند.

با استفاده از این تنظیمات مطمئن میشویم که پس از هر Build آخرین نسخه از فایلهای موردنیاز در مسیرهای تعیین شده کپی میشوند. درنهایت با استفاده از کلاس ResXResourceManager که در زیر آورده شده است، کل عملیات را ساماندهی میکنیم:
public class ResXResourceManager
{
  private static readonly object Lock = new object();
  public string ResourcesPath { get; private set; }
  public ResXResourceManager(string resourcesPath)
  {
    ResourcesPath = resourcesPath;
  }
  public IEnumerable<ResXResourceEntry> GetAllResources(string resourceCategory)
  {
    var resourceFilePath = GetResourceFilePath(resourceCategory);
    return Read(resourceFilePath);
  }
  public void AddOrUpdateResource(ResXResourceEntry resource, string resourceCategory)
  {
    var resourceFilePath = GetResourceFilePath(resourceCategory);
    AddOrUpdate(resource, resourceFilePath);
  }
  public void DeleteResource(string key, string resourceCategory)
  {
    var resourceFilePath = GetResourceFilePath(resourceCategory);
    Remove(key, resourceFilePath);
  }
  private string GetResourceFilePath(string resourceCategory)
  {
    var extension = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en" ? ".resx" : ".fa.resx";
    var resourceFilePath = Path.Combine(ResourcesPath, resourceCategory.Replace(".", "\\") + extension);
    return resourceFilePath;
  }
  private static void AddOrUpdate(ResXResourceEntry resource, string filePath)
  {
    var list = Read(filePath);
    var entry = list.SingleOrDefault(l => l.Key == resource.Key);
    if (entry == null)
    {
      list.Add(resource);
    }
    else
    {
      entry.Value = resource.Value;
    }
    Write(list, filePath);
  }
  private static void Remove(string key, string filePath)
  {
    var list = Read(filePath);
    list.RemoveAll(l => l.Key == key); 
    Write(list, filePath);
  }
  private static List<ResXResourceEntry> Read(string filePath)
  {
    lock (Lock)
    {
      using (var reader = new ResXResourceReader(filePath))
      {
        var list = reader.Cast<object>().Cast<DictionaryEntry>().ToList();
        return list.Select(l => new ResXResourceEntry(l)).ToList();
      }
    }
  }
  private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath)
  {
    lock (Lock)
    {
      using (var writer = new ResXResourceWriter(filePath))
      {
        foreach (var resource in resources)
        {
          writer.AddResource(resource.Key, resource.Value);
        }
      }
    }
  }
}
در این کلاس تغییراتی در متدهای معرفی شده در قسمتهای بالا برای مدیریت دسترسی همزمان با استفاده از بلاک lock ایجاد شده است.
با استفاده از کلاس BuildManager عملیات تولید کتابخانه‌ها مدیریت میشود. (در مورد نحوه استفاده از MSBuild در اینجا توضیحات کافی آورده شده است):
public class BuildManager
{
  public string ProjectPath { get; private set; }
  public BuildManager(string projectPath)
  {
    ProjectPath = projectPath;
  }
  public void Build()
  {
    var regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0");
    if (regKey == null) return;
    var msBuildExeFilePath = Path.Combine(regKey.GetValue("MSBuildToolsPath").ToString(), "MSBuild.exe");
    var startInfo = new ProcessStartInfo
    {
      FileName = msBuildExeFilePath,
      Arguments = ProjectPath,
      WindowStyle = ProcessWindowStyle.Hidden
    };
    var process = Process.Start(startInfo);
    process.WaitForExit();
  }
}
درنهایت مثلا با استفاده از کلاس ResXResourceFileManager مدیریت فایلهای این کتابخانه‌ها صورت میپذیرد:
public class ResXResourceFileManager
{
  public static readonly string BinPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///", ""));
  public static readonly string ResourcesPath = Path.Combine(BinPath, @"..\App_Data\Resources");
  public static readonly string ResourceProjectPath = Path.Combine(ResourcesPath, "Resources.csproj");
  public static readonly string DefaultsPath = Path.Combine(ResourcesPath, "Defaults");
  public static void CopyDlls()
  {
    File.Copy(Path.Combine(ResourcesPath, @"bin\debug\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true);
    File.Copy(Path.Combine(ResourcesPath, @"bin\debug\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true);
    Directory.Delete(Path.Combine(ResourcesPath, "bin"), true);
    Directory.Delete(Path.Combine(ResourcesPath, "obj"), true);
  }
  public static void RestoreAll()
  {
    RestoreDlls();
    RestoreResourceFiles();
  }
  public static void RestoreDlls()
  {
    File.Copy(Path.Combine(DefaultsPath, @"bin\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true);
    File.Copy(Path.Combine(DefaultsPath, @"bin\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true);
  }
  public static void RestoreResourceFiles(string resourceCategory)
  {
    RestoreFile(resourceCategory.Replace(".", "\\"));
  }
  public static void RestoreResourceFiles()
  {
    RestoreFile(@"Global\Configs");
    RestoreFile(@"Global\Exceptions");
    RestoreFile(@"Global\Paths");
    RestoreFile(@"Global\Texts");

    RestoreFile(@"ViewModels\Employees");
    RestoreFile(@"ViewModels\LogOn");
    RestoreFile(@"ViewModels\Settings");

    RestoreFile(@"Views\Employees");
    RestoreFile(@"Views\LogOn");
    RestoreFile(@"Views\Settings");
  }

  private static void RestoreFile(string subPath)
  {
    File.Copy(Path.Combine(DefaultsPath, subPath + ".resx"), Path.Combine(ResourcesPath, subPath + ".resx"), true);
    File.Copy(Path.Combine(DefaultsPath, subPath + ".fa.resx"), Path.Combine(ResourcesPath, subPath + ".fa.resx"), true);
  }
}
در این کلاس از مفهومی با عنوان resourceCategory برای استفاده راحتتر در ویوها استفاده شده است که بیانگر فضای نام نسبی فایلهای Resource و کلاسهای متناظر با آنهاست که براساس استانداردها باید برطبق مسیر فیزیکی آنها در پروژه باشد مثل Global.Texts یا Views.LogOn. همچنین در متد RestoreResourceFiles نمونه هایی از مسیرهای این فایلها آورده شده است.
پس از اجرای متد Build از کلاس BuildManager، یعنی پس از build پروژه Resource در زمان اجرا، باید ابتدا فایلهای تولیدی به مسیرهای مربوطه در فولدر bin برنامه کپی شده سپس فولدرهای تولیدشده توسط msbuild، حذف شوند. این کار در متد CopyDlls از کلاسResXResourceFileManager انجام میشود. هرچند در این قسمت فرض شده است که فایل csprj. موجود برای حالت debug تنظیم شده است.
نکته: دقت کنید که در این قسمت بلافاصله پس از کپی فایلها در مقصد با توجه به توضیحات ابتدای این مطلب سایت Restart خواهد شد که یکی از ضعفهای عمده این روش به شمار میرود.
سایر متدهای موجود نیز برای برگرداندن تنظیمات اولیه بکار میروند. در این متدها از محتویات فولدر Defaults استفاده میشود.
نکته: درصورت ساخت دوباره اسمبلی و یا بازگرداندن اسمبلی‌های اولیه، از آنجاکه وب‌سایت Restart خواهد شد، بنابراین بهتر است تا صفحه جاری بلافاصله پس از اتمام عملیات،دوباره بارگذاری شود. مثلا اگر از ajax برای اعمال این دستورات استفاده شده باشد میتوان با استفاده از کدی مشابه زیر در پایان فرایند صفحه را دوباره بارگذاری کرد:
window.location.reload();

در قسمت بعدی راه حل بهتری با استفاده از فراهم کردن پرووایدر سفارشی برای مدیریت فایلهای Resource ارائه میشود.