مطالب
نکات استفاده از افزونه‌ی Web Essentials جهت کار با تصاویر
در این مطلب نکات کار با تصاویر را توسط افزونه‌ی Web Esstentials بررسی می‌کنیم. این افزونه قابلیت‌های زیر را در کار با تصاویر در اختیار شما قرار می‌دهد:
بهینه‌سازی تصاویر
یکی از موارد مهمی که باید مورد توجه قرار بگیرد، استفاده از تصاویر کم حجم در وب‌سایت می‌باشد. روش‌های مختلفی جهت بهینه‌سازی تصاویر مورد استفاده در سایت وجود دارند، به طور مثال جهت بهینه‌سازی تصاویر PNG می‌توانید از ابزار PNGGauntlet استفاده کنید. همچنین اینجا نیز یک ابزار آنلاین موجود می‌باشد. افزونه‌ی Web Essentails این قابلیت را به آسانی در اختیار شما قرار می‌دهد؛ اینکار را می‌توانید توسط این افزونه به روش‌های زیر انجام دهید:
  • کلیک راست بر روی تصویر
برای اینکار بر روی فایلی که می‌خواهید optimize کنید، کلیک راست کرده و از منوی ظاهر شده گزینه Web Essentials و سپس Optimize Image را انتخاب کنید:

در قسمت status bar نیز می‌توانید نتیجه را مشاهده کنید:

  • انتخاب چندین تصویر
روال قبلی را می‌توانید برای چندین فایل انتخاب شده و یا یک پوشه تکرار کنید:

  • بهینه‌سازی تصاویر موجود در فایل‌های CSS
همچنین امکان بهینه‌سازی تصاویر داخل فایل‌های CSS نیز توسط این افزونه امکان پذیر است:

  • بهینه سازی تصاویر Base64 Encode
توسط این افزونه می‌توانیم تصاویر  Data Uri  را نیز بهینه سازی کنیم:

همانطور که در تصویر فوق مشاهده می‌کنید می‌توانیم تصاویری که به صورت Data Uri درون کد پیوست شده اند را با کلیک بر روی Save to file به صورت یک فایل ذخیره کنیم.

ایجاد تصاویر Sprite
یکی دیگر ار قابلیت‌های افزونه Web Essentials امکان تهیه تصاویر به صورت Sprite می باشد. برای اینکار کافی است به این صورت عمل کنید:

بعد از کلیک بر روی Create image sprite باید یک نام برای آن تعیین کنید و سپس بر روی کلید Save کلیک کنید. با اینکار یک فایل از نوع XML با پسوند sprite برای شما ساخته خواهد شد:
<?xml version="1.0" encoding="utf-8"?>
<sprite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://vswebessentials.com/schemas/v1/sprite.xsd">
  <settings>
    <!--Determines if the sprite image should be automatically optimized after creation/update.-->
    <optimize>true</optimize>
    <!--Determines the orientation of images to form this sprite. The value must be vertical or horizontal.-->
    <orientation>vertical</orientation>
    <!--File extension of sprite image.-->
    <outputType>png</outputType>
    <!--Determin whether to generate/re-generate this sprite on building the solution.-->
    <runOnBuild>false</runOnBuild>
    <!--Use full path to generate unique class or mixin name in CSS, LESS and SASS files. Consider disabling this if you want class names to be filename only.-->
    <fullPathForIdentifierName>true</fullPathForIdentifierName>
    <!--Use absolute path in the generated CSS-like files. By default, the URLs are relative to sprite image file (and the location of CSS, LESS and SCSS).-->
    <useAbsoluteUrl>false</useAbsoluteUrl>
    <!--Specifies a custom subfolder to save CSS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForCss />
    <!--Specifies a custom subfolder to save LESS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForLess />
    <!--Specifies a custom subfolder to save SCSS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForScss />
  </settings>
  <!--The order of the <file> elements determines the order of the images in the sprite.-->
  <files>
    <file>/Content/Images/01.png</file>
    <file>/Content/Images/02.png</file>
    <file>/Content/Images/03.png</file>
    <file>/Content/Images/04.png</file>
  </files>
</sprite>
یکی از زیر مجموعه‌های این فایل، تصویر نهایی می‌باشد، همچنین فایل‌های css, less, map و scss آن نیز تولید می‌شود:

به عنوان مثال فایل CSS تصویر فوق به صورت زیر می‌باشد:
/*
This is an example of how to use the image sprite in your own CSS files
*/
.Content-Images-01 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 0;
}
.Content-Images-02 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -32px;
}
.Content-Images-03 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -64px;
}
.Content-Images-04 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -96px;
}
هر کدام از کلاس‌های فوق به یک تصویر در فایل مربوطه توسط image position اشاره می‌کند. شما می‌توانید با انتساب هر کدام از کلاس‌های فوق به یک المنت از آن تصویر استفاده نمائید:
<div class="Content-Images-01"></div>
<div class="Content-Images-02"></div>
<div class="Content-Images-03"></div>
<div class="Content-Images-04"></div>

استفاده از تصاویر Data URIs
یکی دیگر از روش‌های کاهش درخواست‌های HTTP در یک سایت استفاده از Data URIs  می باشد، توسط این روش می‌توانید فایل هایتان را درون HTML و یا CSS قرار دهید یا به اصطلاح embed کنید. به طور مثال جهت استفاده از یک تصویر می‌توانید به راحتی با آدرس دهی تصویر درون تگ img، تصویر را درون صفحه نمایش دهید:
<img src="https://www.dntips.ir/images/logo.png" />

همین کار را می‌توانیم توسط Data URIs انجام دهیم:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC" />
در کد فوق تصویر موردنظر را درون HTML به صورت embed شده قرار داده ایم، در این حالت دیگری نیازی به رفت و برگشت به سرور جهت نمایش تصویر نیست.
سینتکس Data URIs
به طور مثال تگ زیر را در نظر داشته باشید:
<img src="data:image/png;base64,iVBOR..." />
مقدار ویژگی src شامل موارد زیر است:
data: نام schema
image/png: نوع محتوا(content type)
base64: نوع encoding استفاده شده برای encode کردن اطلاعات
iVBOR...: اطلاعات encode شده.
توسط افزونه Web Essentials به راحتی می‌توانید تصویر موردنظرتان را به صورت Data URI تهیه کنید:

مطالب
ایجاد سیستم وضعیت آب و هوا مانند گوگل (بخش اول)
در این آموزش قصد دارم چگونگی ایجاد یک سیستم اعلام وضعیت آب و هوا را مشابه آنچه که در سایت گوگل می‌بینید برای شما توضیح دهم. باید توجه داشت من این آموزش را با  ASP.NET MVC نوشتم ولی شما می‌توانید با اندک تغییراتی در کدها، آنرا در ASP.NET وب فرمز نیز استفاده کنید. برای گرفتن آب و هوای هر شهر از Rss‌های اعلام وضعیت آب و هوای یاهو استفاده می‌کنم و توضیح خواهم داد که چگونه با Rss آن کار کنید.
Rss آب و هوای هر شهر در یاهو به صورت یک لینک یکتا می‌باشد؛ به شکل زیر :

http://weather.yahooapis.com/forecastrss?w=WOEID&u=c
حال می‌خواهم کوئری استرینگ‌های این لینک را برای شما توضیح دهم. هر شهری بر روی کره‌ی زمین یک WOEID یکتا و منحصر بفرد دارد که شما به پارامتر w عدد WOEID شهر موردنظر خود را می‌دهید. بعد از مقداردهی پارامتر w، وقتی این لینک را در آدرس بار مرورگر خود می‌زنید، RSS مربوط به آب و هوای آن شهر را به شما می‌دهد. مثلا WOEID تهران عدد 28350859 می‌باشد.
و این لینک http://weather.yahooapis.com/forecastrss?w=28350859&u=c اطلاعات آب و هوای تهران را در قالب یک RSS به شما نمایش خواهد داد.

خوب، حالا پارامتر دوم یعنی پارامتر u چکاری را انجام می‌دهد؟
* چنانچه مقدار پارامتر u برابر c باشد، یعنی شما دمای آب و هوای شهر مد نظر را بر اساس سانتیگراد می‌خواهید.
* اگر مقدار پارامتر u برابر f باشد، یعنی شما دمای آب و هوای آن شهر مورد نظر را بر اساس فارنهایت می‌خواهید.

برای گرفتن WOEID شهر‌ها هم به این سایت بروید http://woeid.rosselliot.co.nz و اسم هر شهری که می‌خواهید بزنید تا WOEID را به شما نمایش دهد.

در این مثال من از یک DropDown استفاده کردم که کاربر با انتخاب هر شهر از  DropDown، آب و هوای آن شهر را مشاهده می‌کند.
Action مربوط به صفحه‌ی Index به صورت زیر می‌باشد :
[HttpGet]
        public ActionResult Index()
        {
           ViewBag.ProvinceList = _RPosition.Positions;
            ShowWeatherProvince(8);
            return View();
        }
در اینجا من لیست شهر‌ها را از جدول می‌خوانم که البته این جدول را چون بخش مهمی نبود و فقط شامل ID و نام شهر‌ها بود در فایل ضمیمه قرار ندادم و نام شهر‌ها و ID آنها را بر عهده‌ی خودتان گذاشتم.
حال تابعی را که آب و هوای مربوط به هر شهر را نمایش می‌دهد، به شرح زیر است:
public ActionResult ShowWeatherProvince(int dpProvince)
        {
            XDocument rssXml=null;
            CountryName CountryName = new CountryName();
            if (dpProvince != 0)
            {
                switch (dpProvince)
                {
                    case 1:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345768&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Azarbayejan-e Sharqhi" };
                            break;
                        }
                    case 2:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345767&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Azarbayejan-e Qarbi" };
                            break;
                        }
                    case 3:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254335&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Ardebil" };
                            break;
                        }
                    case 4:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=28350859&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Alborz" };
                            break;
                        }
                    case 5:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345787&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Esfahan" };
                            break;
                        }
                    case 6:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345775&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Ilam" };
                            break;
                        }
                    case 7:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254463&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Bushehr" };
                            break;
                        }
                    case 8:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=28350859&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Tehran" };
                            break;
                        }
                    case 9:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345769&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Chahar Mahall va Bakhtiari" };
                            break;
                        }
                    case 10:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=56189824&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Razavi Khorasan" };
                            break;
                        }
                    case 11:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345789&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Shomali Khorasan" };
                            break;
                        }
                    case 12:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345789&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Jonubi Khorasan" };
                            break;
                        }
                    case 13:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345778&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Khuzestan" };
                            break;
                        }
                    case 14:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2255311&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Zanjan" };
                            break;
                        }
                    case 15:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345784&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Semnan" };
                            break;
                        }
                    case 16:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345770&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Sistan va Baluchestan" };
                            break;
                        }
                    case 17:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345772&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Fars" };
                            break;
                        }
                    case 18:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=20070200&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Qazvin" };
                            break;
                        }
                    case 19:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2255062&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Qom" };
                            break;
                        }
                    case 20:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345779&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Kordestan" };
                            break;
                        }
                    case 21:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254796&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Kerman" };
                            break;
                        }
                    case 22:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254797&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Kermanshah" };
                            break;
                        }
                    case 23:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345771&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Kohgiluyeh va Buyer Ahmad" };
                            break;
                        }
                    case 24:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=20070201&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Golestan" };
                            break;
                        }
                    case 25:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345773&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Gilan" };
                            break;
                        }
                    case 26:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345782&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Lorestan" };
                            break;
                        }
                    case 27:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345783&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Markazi" };
                            break;
                        }
                    case 28:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345780&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Mazandaran" };
                            break;
                        }
                    case 29:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254664&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Hamedan" };
                            break;
                        }
                    case 30:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345776&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Hormozgan" };
                            break;
                        }
                    case 31:
                        {
                            rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2253355&u=c");
                            CountryName = new CountryName() { Country = "Iran", City = "Yazd" };
                            break;
                        }
                }
                ViewBag.Location = CountryName;
                XNamespace yWeatherNS = "http://xml.weather.yahoo.com/ns/rss/1.0";
                List<YahooWeatherRssItem> WeatherList = new List<YahooWeatherRssItem>();
                for (int i = 0; i < 4; i++)
                {
                    YahooWeatherRssItem YahooWeatherRssItem = new YahooWeatherRssItem()
                    {
                        Code = Convert.ToInt32(rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("code").Value),
                        Day = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("day").Value,
                        Low = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("low").Value,
                        High = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("high").Value,
                        Text = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("text").Value,
                    };

                    WeatherList.Add(YahooWeatherRssItem);
                }
                ViewBag.FeedList = WeatherList;
            }

          
                return PartialView("_Weather");
           
        }
قسمت SwitchCase، مقدار و Value مربوط به هر آیتم DropDown را که شامل یک اسم شهر است، میگیرد و RSS مربوط به آن شهر را بر می‌گرداند.
حالا کد مربوط به خواندن فایل Rss را برایتان توضیح می‌دهم : حلقه‌ی for 0  تا 4 (که در کد بالا مشاهده می‌کنید)یعنی اطلاعات 4 روز آینده را برایم برگردان.
من تگ‌های Code ، Day ، Low ، High و text فایل RSS را در این حلقه For می‌خوانم که البته مقادیر این 4 روز را در لیستی اضافه می‌کنم که نوع این لیست هم از نوع YahooWeatherRssItem می‌باشد. من این کلاس را در فایل ضمیمه قرار دادم. اکنون هر کدام از این تگ‌ها را برایتان توضیح می‌دهم:

code : هر آب و هوا کدی دارد .مثلا آب و هوای نیمه ابری یک کد ، آب و هوای آفتابی کدی دیگر و ...
Low: حداقل دمای آن روز را به ما می‌دهد .
High: حداکثر دمای آن روز را به می‌دهد .
day: نام روز از هفته را بر می‌گرداند مثلا شنبه ، یکشنبه و ....
text: که توضیحاتی می‌دهد مثلا اگر هوا آفتابی باشد مقدار sunny را بر می‌گرداند و ...


خوب، تا اینجا ما Rss مربوط به هر شهر را خواندیم حالا در قسمت Design باید چکار کنیم .
کدهای html صفحه‌ی Index ما شامل کدهای زیر است :
@{

    ViewBag.Title = "Weather";
}

<link href="~/Content/User/Weather/Weather.css" rel="stylesheet" />
@section scripts{
    <script src="@Url.Content("~/Scripts/jquery-1.6.2.min.js")" type="text/javascript"></script>
        <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
       <script type="text/javascript">
           $("#dpProvince").change(function () {
               $(this).parents("form").submit();
           });
    </script>
}
<h2>Weather</h2>
<div id="Progress">
    <img src="~/Images/User/Other/ajax-loader.gif" />
</div>
<div id="BoxContent"> @Html.Partial("_Weather")</div>

  @using (Ajax.BeginForm(actionName: "ShowWeatherProvince", ajaxOptions: new AjaxOptions { UpdateTargetId = "BoxContent", LoadingElementId = "Progress", InsertionMode = InsertionMode.Replace }))
                {
<div style="padding-top:15px;">
        <div style="float:left; width:133px; ">Select Your Province</div>
        <div style="float:left">   @Html.DropDownList("dpProvince", new SelectList(ViewBag.ProvinceList, "Id", "Name"),"Select Your Province", new { @class = "webUserDropDown", @style = "width:172px" })</div>
    </div>
  }
و کدهای _Weather که Partial است به صورت زیر است:
@{
    List<Weather.YahooWeatherRssItem> Feeds = ViewBag.FeedList;
}
<div>
    @{
        HtmlString StartTable = new HtmlString("<table class='WeatherTable' cellspacing='0' cellpadding='0'><tbody><tr>");
        HtmlString EndTable = new HtmlString("</tr></tbody></table>");
        HtmlString StartTD = new HtmlString("<td>");
        HtmlString EndTD = new HtmlString("</td>");
    }
    <div style="width: 300px;">
        @{
            @StartTable
            foreach (var item in Feeds)
            {
            @StartTD
            <div>@item.Day</div>
            <div>
                @{
                string FileName = "";
                switch (item.Code)
                {
                    case 0: { FileName = "/Images/User/Weather/Tornado.png"; break; }
                    case 1: { FileName = "/Images/User/Weather/storm2.gif"; break; }
                    case 2: { FileName = "/Images/User/Weather/storm2.gif"; break; }
                    case 3: { FileName = "/Images/User/Weather/storm2.gif"; break; }
                    case 4: { FileName = "/Images/User/Weather/15.gif"; break; }
                    case 5: { FileName = "/Images/User/Weather/29.gif"; break; }
                    case 6: { FileName = "/Images/User/Weather/29.gif"; break; }
                    case 7: { FileName = "/Images/User/Weather/29.gif"; break; }
                    case 8: { FileName = "/Images/User/Weather/26.gif"; break; }
                    case 9: { FileName = "/Images/User/Weather/drizzle.png"; break; }
                    case 10: { FileName = "/Images/User/Weather/26.gif"; break; }
                    case 11: { FileName = "/Images/User/Weather/18.gif"; break; }
                    case 12: { FileName = "/Images/User/Weather/18.gif"; break; }
                    case 13: { FileName = "/Images/User/Weather/19.gif"; break; }
                    case 14: { FileName = "/Images/User/Weather/19.gif"; break; }
                    case 15: { FileName = "/Images/User/Weather/19.gif"; break; }
                    case 16: { FileName = "/Images/User/Weather/22.gif"; break; }
                    case 17: { FileName = "/Images/User/Weather/Hail.png"; break; }
                    case 18: { FileName = "/Images/User/Weather/25.gif"; break; }
                    case 19: { FileName = "/Images/User/Weather/dust.png"; break; }
                    case 20: { FileName = "/Images/User/Weather/fog_icon.png"; break; }
                    case 21: { FileName = "/Images/User/Weather/hazy_icon.png"; break; }
                    case 22: { FileName = "/Images/User/Weather/2017737395.png"; break; }
                    case 23: { FileName = "/Images/User/Weather/32.gif"; break; }
                    case 24: { FileName = "/Images/User/Weather/32.gif"; break; }
                    case 25: { FileName = "/Images/User/Weather/31.gif"; break; }
                    case 26: { FileName = "/Images/User/Weather/7.gif"; break; }
                    case 27: { FileName = "/Images/User/Weather/38.gif"; break; }
                    case 28: { FileName = "/Images/User/Weather/6.gif"; break; }
                    case 29: { FileName = "/Images/User/Weather/35.gif"; break; }
                    case 30: { FileName = "/Images/User/Weather/7.gif"; break; }
                    case 31: { FileName = "/Images/User/Weather/33.gif"; break; }
                    case 32: { FileName = "/Images/User/Weather/1.gif"; break; }
                    case 33: { FileName = "/Images/User/Weather/34.gif"; break; }
                    case 34: { FileName = "/Images/User/Weather/2.gif"; break; }
                    case 35: { FileName = "/Images/User/Weather/freezing_rain.png"; break; }
                    case 36: { FileName = "/Images/User/Weather/30.gif"; break; }
                    case 37: { FileName = "/Images/User/Weather/15.gif"; break; }
                    case 38: { FileName = "/Images/User/Weather/15.gif"; break; }
                    case 39: { FileName = "/Images/User/Weather/15.gif"; break; }
                    case 40: { FileName = "/Images/User/Weather/12.gif"; break; }
                    case 41: { FileName = "/Images/User/Weather/22.gif"; break; }
                    case 42: { FileName = "/Images/User/Weather/22.gif"; break; }
                    case 43: { FileName = "/Images/User/Weather/22.gif"; break; }
                    case 44: { FileName = "/Images/User/Weather/39.gif"; break; }
                    case 45: { FileName = "/Images/User/Weather/thundershowers.png"; break; }
                    case 46: { FileName = "/Images/User/Weather/19.gif"; break; }
                    case 47: { FileName = "/Images/User/Weather/thundershowers.png"; break; }
                    case 3200: { FileName = "/Images/User/Weather/1211810662.png"; break; }
                }
                }
                <img alt='@item.Text' title='@item.Text' src='@FileName'>
            </div>
            <div>
                <span>@item.High°</span>
                <span>@item.Low°</span>
            </div>
            @EndTD
            }
        }
        @EndTable
    </div>
</div>
من عکس‌های مربوط به وضعیت آب و هوا را در فایل ضمیمه قرار دادم.
چنانچه در مورد RSS وضعیت آب و هوای یاهو اطلاعات دقیق‌تری را می‌خواهید بدانید به این  لینک بروید.
در آموزش بعدی قصد دارم برایتان این بخش را توضیح دهم که بر اساس IP بازدید کننده سایت شما، اطلاعات آب و هوایی شهر بازدید کننده را برایش در سایت نمایش دهد.

Files-06bf65bac63d4dd694b15fc24d4cb074.zip

موفق باشید
مطالب
آپلود فایل ها با استفاده از PlUpload در Asp.Net Mvc
امروزه بازار برنامه‌های تماما ajax و بدون Postback  شدن صفحه بسیار داغ میباشد که از این موارد میتوان به برنامه‌های تحت وب گوگل اشاره کرد. (gmail  ، googlePlus  ، Google Reader)
در این میان یکی از دغدغه‌های توسعه دهندگان وب ، آپلود فایل‌ها به صورت آنی (مثل attach files گوگل) میباشد. برای حل این مسئله ، ابزارها و پلاگین‌های متعددی وجود دارد که در اینجا به 10 تا از پلاگین‌های Jquery  اشاره شده است.
به شخصه با پلاگین Uploadify کار کرده ام و از استفاده از آن راضی هستم ولی همین دیشب برای قسمتی از یک پروژه نیاز
به ابزاری جهت آپلود فایل‌ها با امکانات مورد نظرم داشتم که به PlUpload برخورد کردم. 

از امکاناتی که این ابزار در اختیار شما قرار میدهد :
- یک اینترفیس زیبا جهت آپلود و افزودن فایل ها
- پشتیبانی از زبان‌های مختلف و همین طور زبان فارسی
- امکان استفاده از قالب Jquery UI
- Drag&Drop  برای مرورگرهایی که از Html5  پشتیبانی میکنند

حال که با امکانات این ابزار بیشتر آشنا شدید بریم سراغ استفاده از این ابزار در asp.net mvc  :)
ابتدا پروژه را از اینجا دانلود کنید. سپس یک پروژه‌ی جدید  mvc 3  بسازید (از نوع Internet Application و با نام دلخواه). سپس پوشه‌ی plupload  را در قسمت سلوشن برنامه کپی کنید.
حال در فایل Views->Shared->_Layout.cshtml  ، تگ head  را جهت افزودن امکانات پلاگین این گونه تغییر دهید :

    <title>@ViewBag.Title</title>

    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <link href="../../plupload/js/jquery.plupload.queue/css/jquery.plupload.queue.css" rel="stylesheet" />
    <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
    <script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>

    <script src="../../plupload/js/plupload.full.js"></script>
    <script src="../../plupload/js/jquery.plupload.queue/jquery.plupload.queue.js"></script>
    <script src="../../plupload/js/i18n/fa.js"></script>

نکته : فایل fa.js  که جهت استفاده از زبان فارسی در اینترفیس آپلود فایل‌ها میباشد، که وجود آن در آدرس واضح میباشد.
سپس به فایل Views->Home->Index.cshtml بروید و آن را این گونه دوباره نویسی کنید :
 @{
    ViewBag.Title = "Uploading Files using PlUpload";
}
<h2>@ViewBag.Message</h2>

@using (Html.BeginForm("Post", "home", FormMethod.Post,
    new { enctype = "multipart/form-data" }))
{
    <div id="uploader">
        <p>You browser doesn't have Flash, Silverlight, Gears, BrowserPlus or HTML5 support.</p>
    </div>
}

<script>
    $(function () {

        $("#uploader").pluploadQueue({
            // General settings
            runtimes: 'html5,gears,flash,silverlight,browserplus,html4',
            url: '@Url.Action("Upload" , "Home")',
            max_file_size: '10mb',
            chunk_size: '1mb',
            unique_names: true,

            // Resize images on clientside if we can
            resize: { width: 320, height: 240, quality: 90 },

            // Specify what files to browse for
            filters: [
                { title: "Image files", extensions: "jpg,gif,png" },
                { title: "Zip files", extensions: "zip" }
            ],

            // Flash settings
            flash_swf_url: '/plupload/js/plupload.flash.swf',

            // Silverlight settings
            silverlight_xap_url: '/plupload/js/plupload.silverlight.xap'
        });
    });
</script>
توضیحات و نکات :
- جهت آپلود فایل‌ها تگ enctype = "multipart/form-data" را فراموش نکنید.
- در قسمت مقداردهی به ویژگی‌های Plupload  ، قسمت runtime  به صورت ترتیبی کار میکند لذا اگر اولی پشتیبانی نشود سراغ دومی میرود و اگر دومی نشود سومی و ... در صفحه‌ی اول سایت PlUpload ، موارد پشتیبانی شده توسط تکنولوژی‌ها آورده شده است لذا این ترتیب را ترتیب مناسبی میبینم و اگر اولین مورد html5 باشد امکان Drag&Drop وجود خواهد داشت.
خود سایت PlUpload  داکیومنت خیلی خوبی جهت توضیح موارد مختلف دارد لذا توضیح دوباره لازم نیست.
همان طور که در ویژگی url  مشاهده میکنید به کنترلر Home  و اکشن متود Upload اشاره شده است که طرز کار به این گونه است که هر بار که یک فایل آپلود میشود درخواستی به این آدرس و محتوای فایل در قسمت Request.Files ارسال میشود و همین طور نام فایل که unique ارسال میشود و chunk که تیکه‌های فایل است(پست میشود).
پس اکشنی با نام Upload  در کنترلر HomeController بسازید :
        [HttpPost]
        public ActionResult Upload(int? chunk, string name)
        {
            var fileUpload = Request.Files[0];
            var uploadPath = Server.MapPath("~/App_Data");
            chunk = chunk ?? 0;
            using (var fs = new FileStream(Path.Combine(uploadPath, name), chunk == 0 ? FileMode.Create : FileMode.Append))
            {
                if (fileUpload != null)
                {
                    var buffer = new byte[fileUpload.InputStream.Length];
                    fileUpload.InputStream.Read(buffer, 0, buffer.Length);
                    fs.Write(buffer, 0, buffer.Length);
                }
            }
            return Content("chunk uploaded", "text/plain");
        } 
توضیحات : ابتدا فایل مورد نظر از قسمت Request.Files واکشی میشود و سپس فایل را در پوشه App_Data ذخیره میکند. (یکی از چندین روش ذخیره سازی که مطالعه در این قسمت به خواننده واگذار میشود.)

حال برنامه را اجرا کنید و از این ابزار لذت ببرید:) 
نکته : قسمت فارسی ساز اونو تغییر دادم چون که ترجمه‌ی فارسی خودش یه سری نقایصی داشت که گویا از کار با google translate به وجود اومده بود!
مطالب
بررسی ویجت Kendo UI File Upload
Kendo UI به همراه یک ویجت وب مخصوص ارسال فایل‌ها به سرور نیز هست. این ویجت قابلیت ارسال چندین فایل با هم را به صورت Ajax ایی دارا است و همچنین کاربران می‌توانند فایل‌ها را با کشیدن و رها کردن بر روی آن، به لیست فایل‌های قابل ارسال اضافه کنند.
ارسال فایل Ajax ایی آن توسط HTML5 File API صورت می‌گیرد که در تمام مرورگرهای جدید پشتیبانی خوبی از آن وجود دارد. در مرورگرهای قدیمی‌تر، به صورت خودکار همان حالت متداول ارسال همزمان فایل‌ها را فعال می‌کند (یا همان post back معمولی).

فعال سازی مقدماتی kendoUpload

ابتدایی‌ترین حالت کار با kendoUpload، فعال سازی حالت post back معمولی است؛ به شرح زیر:
<form method="post" action="submit" enctype="multipart/form-data">
  <div>
    <input name="files" id="files" type="file" />    
    <input type="submit" value="Submit" class="k-button" />
  </div>
</form>
<script>
  $(document).ready(function() {
     $("#files").kendoUpload();
  });
</script>
در این حالت صرفا input با نوع file، با ظاهری سازگار با سایر کنترل‌های Kendo UI به نظر می‌رسد و عملیات ارسال فایل، همانند قبل به همراه یک post back است. این روش برای حالتی مفید است که بخواهید یک فایل را به همراه سایر عناصر فرم در طی یک مرحله به سمت سرور ارسال کنید.


فعال سازی حالت ارسال فایل Ajax ایی kendoUpload

برای فعال سازی ارسال Ajax ایی فایل‌ها در Kendo UI نیاز است خاصیت async آن‌را به نحو ذیل مقدار دهی کرد:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: { // async configuration
                    saveUrl: "@Url.Action("Save", "Home")", // the url to save a file is '/save'
                    removeUrl: "@Url.Action("Remove", "Home")", // the url to remove a file is '/remove'
                    autoUpload: false, // automatically upload files once selected
                    removeVerb: 'POST'
                },
                multiple: true,
                showFileList: true
            }); 
        });
    </script>
در اینجا دو آدرس ذخیره سازی فایل‌ها و همچنین حذف آن‌ها را مشاهده می‌کنید. امضای این دو اکشن متد در ASP.NET MVC به صورت ذیل هستند:
        [HttpPost]
        public ActionResult Save(IEnumerable<HttpPostedFileBase> files)
        {
            if (files != null)
            {
                // ...
                // Process the files and save them
                // ...
            }

            // Return an empty string to signify success
            return Content("");
        }

        [HttpPost]
        public ContentResult Remove(string[] fileNames)
        {
            if (fileNames != null)
            {
                foreach (var fullName in fileNames)
                {
                    // ...
                    // delete the files
                    // ...
                }
            }

            // Return an empty string to signify success
            return Content("");
        }
در هر دو حالت، لیستی از فایل‌ها توسط kendoUpload به سمت سرور ارسال می‌شوند. در حالت Save، محتوای این فایل‌ها جهت ذخیره سازی بر روی سرور در دسترس خواهد بود. در حالت Remove، صرفا نام این فایل‌ها برای حذف از سرور، توسط کاربر ارسال می‌شوند.
دو دکمه‌ی حذف با کارکردهای متفاوت در ویجت kendoUpload وجود دارند. در ابتدای کار، پیش از ارسال فایل‌ها به سرور:


کلیک بر روی دکمه‌ی حذف در این حالت، صرفا فایلی را از لیست سمت کاربر حذف می‌کند.

پس از ارسال فایل‌ها به سرور:


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


چند نکته‌ی تکمیلی
- تنظیم خاصیت autoUpload به true سبب می‌شود تا پس از انتخاب فایل‌ها توسط کاربر، بلافاصله و به صورت خودکار عملیات ارسال فایل‌ها به سرور آغاز شوند. اگر به false تنظیم شود، دکمه‌ی ارسال فایل‌ها در پایین لیست نمایش داده خواهد شد.
- شاید علاقمند باشید تا removeVerb را به DELETE تغییر دهید؛ بجای POST. به همین منظور می‌توان خاصیت removeVerb در اینجا مقدار دهی کرد.
- با تنظیم خاصیت multiple به true، کاربر قادر خواهد شد تا توسط صفحه‌ی دیالوگ انتخاب فایل‌ها، قابلیت انتخاب بیش از یک فایل را داشته باشد.
- showFileList نمایش لیست فایل‌ها را سبب می‌شود.


تعیین پسوند فایل‌‌های صفحه‌ی انتخاب فایل‌ها

هنگامیکه کاربر بر روی دکمه‌ی انتخاب فایل‌ها برای ارسال کلیک می‌کند، در صفحه‌ی دیالوگ باز شده می‌توان پسوندهای پیش فرض مجاز را نیز تعیین کرد.
برای این منظور تنها کافی است ویژگی accept را به input از نوع فایل اضافه کرد. چند مثال در این مورد:
<!-- Content Type with wildcard.  All Images -->
<input type="file" id="demoFile" title="Select file" accept="image/*" />
 
<!-- List of file extensions -->
<input type="file" id="demoFile" title="Select file" accept=".jpg,.png,.gif" />
 
<!-- Any combination of the above -->
<input type="file" id="demoFile" title="Select file" accept="audio/*,application/pdf,.png" />


نمایش متن کشیدن و رها کردن، بومی سازی برچسب‌ها و نمایش راست به چپ

همانطور که در تصاویر فوق ملاحظه می‌کنید، نمایش این ویجت راست به چپ و پیام‌های آن نیز ترجمه شده‌اند.
برای راست به چپ سازی آن مانند قبل تنها کافی است input مرتبط، در یک div با کلاس k-rtl محصور شود:
        <div class="k-rtl k-header">
            <input name="files" id="files" type="file"  />
        </div>
برای بومی سازی پیام‌های آن می‌توان مانند مثال ذیل، خاصیت localization را مقدار دهی کرد:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                 //...
                },
                //...
                localization: {
                    select: 'انتخاب فایل‌ها برای ارسال',
                    remove: 'حذف فایل',
                    retry: 'سعی مجدد',
                    headerStatusUploading: 'در حال ارسال فایل‌ها',
                    headerStatusUploaded: 'پایان ارسال',
                    cancel: "لغو",
                    uploadSelectedFiles: "ارسال فایل‌ها",
                    dropFilesHere: "فایل‌ها را برای ارسال، کشیده و در اینجا رها کنید",
                    statusUploading: "در حال ارسال",
                    statusUploaded: "ارسال شد",
                    statusWarning: "اخطار",
                    statusFailed: "خطا در ارسال"
                }
            });
        });
    </script>
به علاوه متن dropFilesHere به صورت پیش فرض نامرئی است. برای نمایش آن نیاز است CSS موجود را بازنویسی کرد تا em مرتبط مرئی شود:
<style type="text/css">
div.k-dropzone {
    border: 1px solid #c5c5c5; /* For Default; Different for each theme */
}

div.k-dropzone em {
    visibility: visible;
}
</style>


تغییر قالب نمایش لیست فایل‌ها

لیست فایل‌ها در ویجت kendoUpload دارای یک قالب پیش فرض است که امکان بازنویسی کامل آن وجود دارد. ابتدا نیاز است یک kendo-template را بر این منظور تدارک دید:
    <script id="fileListTemplate" type="text/x-kendo-template">
        <li class='k-file'>
            <span class='k-progress'></span>
            <span class='k-icon'></span>
            <span class='k-filename' title='#=name#'>#=name# (#=size# bytes)</span>
            <strong class='k-upload-status'></strong>
        </li>
    </script>
و سپس برای استفاده از آن خواهیم داشت:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                // ...
                },
                // ...
                template: kendo.template($('#fileListTemplate').html()),
                // ...
            });
        });
    </script>
در این قالب، مقدار size هر فایل نیز در کنار نام آن نمایش داده می‌شود.


رخدادهای ارسال فایل‌ها

افزونه‌ی kendoUpload در حالت ارسال Ajax ایی فایل‌ها، رخدادهایی مانند شروع به ارسال، موفقیت، پایان، درصد ارسال فایل‌ها و امثال آن‌را نیز به همراه دارد که لیست کامل آن‌ها را در ذیل مشاهده می‌کنید:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: { // async configuration
                //...
                },
                //...
                localization: {
                },
                cancel: function () {
                    console.log('Cancel Event.');
                },
                complete: function () {
                    console.log('Complete Event.');
                },
                error: function () {
                    console.log('Error uploading file.');
                },
                progress: function (e) {
                    console.log('Uploading file ' + e.percentComplete);
                },
                remove: function () {
                    console.log('File removed.');
                },
                select: function () {
                    console.log('File selected.');
                },
                success: function () {
                    console.log('Upload successful.');
                },
                upload: function (e) {
                    console.log('Upload started.');
                }
            }); 
        });
    </script>


ارسال متادیتای اضافی به همراه فایل‌های ارسالی

فرض کنید می‌خواهید به همراه فایل‌های ارسالی به سرور، پارامتر codeId را نیز ارسال کنید. برای این منظور باید خاصیت e.data رویداد upload را به نحو ذیل مقدار دهی کرد:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                //...
                },
                //...
                localization: {
                },
                upload: function (e) {
                    console.log('Upload started.');
                    // Sending metadata to the save action
                    e.data = {
                        codeId: "1234567",
                        param2: 12
                        //, ...
                    };
                }
            });
        });
    </script>
سپس در سمت سرور، امضای متد Save بر اساس پارامترهای تعریف شده در سمت کاربر، به نحو ذیل تغییر می‌کند:
   [HttpPost]
  public ActionResult Save(IEnumerable<HttpPostedFileBase> files, string codeId)


فعال سازی ارسال batch

اگر در متد Save سمت سرور یک break point قرار دهید، مشاهده خواهید کرد که به ازای هر فایل موجود در لیست در سمت کاربر، یکبار متد Save فراخوانی می‌شود و عملا متد Save، لیستی از فایل‌ها را در طی یک فراخوانی دریافت نمی‌کند. برای فعال سازی این قابلیت تنها کافی است خاصیت batch را به true تنظیم کنیم:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                    // ....
                    batch: true
                },
            });
        });
    </script>
به این ترتیب دیگر لیست فایل‌ها به صورت مجزا در سمت کاربر نمایش داده نمی‌شود و تمام آن‌ها با یک کاما از هم جدا خواهند شد. همچنین دیگر شاهد نمایش درصد پیشرفت تکی فایل‌ها نیز نخواهیم بود و اینبار درصد پیشرفت کل batch گزارش می‌شود.
در یک چنین حالتی باید دقت داشت که تنظیم maxRequestLength در web.config برنامه الزامی است؛ زیرا به صورت پیش فرض محدودیت 4 مگابایتی ارسال فایل‌ها توسط ASP.NET اعمال می‌شود:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <!-- The request length is in kilobytes, execution timeout is in seconds  -->
    <httpRuntime maxRequestLength="10240" executionTimeout="120" />
  </system.web>

  <system.webServer>
    <security>
      <requestFiltering>
        <!-- The content length is in bytes  -->
        <requestLimits maxAllowedContentLength="10485760"/>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>
مطالب
نحوه اضافه کردن Auto-Complete به جستجوی لوسین در ASP.NET MVC و Web forms
پیشنیازها:
چگونه با استفاده از لوسین مطالب را ایندکس کنیم؟
چگونه از افزونه jQuery Auto-Complete استفاده کنیم؟
نحوه استفاده صحیح از لوسین در ASP.NET


اگر به جستجوی سایت دقت کرده باشید، قابلیت ارائه پیشنهاداتی به کاربر توسط یک Auto-Complete به آن اضافه شده‌است. در مطلب جاری به بررسی این مورد به همراه دو مثال Web forms و MVC پرداخته خواهد شد.


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

الف) دریافت لوسین
از طریق NuGet آخرین نگارش را دریافت و به پروژه خود اضافه کنید. همچنین Lucene.NET Contrib را نیز به همین نحو دریافت نمائید.

ب) ایجاد ایندکس
کدهای این قسمت با مطلب برجسته سازی قسمت‌های جستجو شده، یکی است:
using System.Collections.Generic;
using System.IO;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using LuceneSearch.Core.Model;
using LuceneSearch.Core.Utils;

namespace LuceneSearch.Core
{
    public static class CreateIndex
    {
        static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_30;

        public static Document MapPostToDocument(Post post)
        {
            var postDocument = new Document();
            postDocument.Add(new Field("Id", post.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            var titleField = new Field("Title", post.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS);
            titleField.Boost = 3;
            postDocument.Add(titleField);
            postDocument.Add(new Field("Body", post.Body.RemoveHtmlTags(), Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
            return postDocument;
        }

        public static void CreateFullTextIndex(IEnumerable<Post> dataList, string path)
        {
            var directory = FSDirectory.Open(new DirectoryInfo(path));
            var analyzer = new StandardAnalyzer(_version);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                foreach (var post in dataList)
                {
                    writer.AddDocument(MapPostToDocument(post));
                }

                writer.Optimize();
                writer.Commit();
                writer.Close();
                directory.Close();
            }
        }
    }
}
تنها تفاوت آن اضافه شدن titleField.Boost = 3 می‌باشد. توسط Boost به لوسین خواهیم گفت که اهمیت عبارات ذکر شده در عناوین مطالب، بیشتر است از اهمیت متون آن‌ها.


ج) تهیه قسمت منبع داده Auto-Complete

namespace LuceneSearch.Core.Model
{
    public class SearchResult
    {
        public int Id { set; get; }
        public string Title { set; get; }
    }
}

using System.Collections.Generic;
using System.IO;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using LuceneSearch.Core.Model;
using LuceneSearch.Core.Utils;

namespace LuceneSearch.Core
{
    public static class AutoComplete
    {
        private static IndexSearcher _searcher;

        /// <summary>
        /// Get terms starting with the given prefix
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public static IList<SearchResult> GetTermsScored(string indexPath, string prefix, int maxItems = 10)
        {
            if (_searcher == null)
                _searcher = new IndexSearcher(FSDirectory.Open(new DirectoryInfo(indexPath)), true);

            var resultsList = new List<SearchResult>();
            if (string.IsNullOrWhiteSpace(prefix))
                return resultsList;

            prefix = prefix.ApplyCorrectYeKe();

            var results = _searcher.Search(new PrefixQuery(new Term("Title", prefix)), null, maxItems);
            if (results.TotalHits == 0)
            {
                results = _searcher.Search(new PrefixQuery(new Term("Body", prefix)), null, maxItems);
            }

            foreach (var doc in results.ScoreDocs)
            {
                resultsList.Add(new SearchResult
                {
                    Title = _searcher.Doc(doc.Doc).Get("Title"),
                    Id = int.Parse(_searcher.Doc(doc.Doc).Get("Id"))
                });
            }

            return resultsList;
        }
    }
}
توضیحات:
برای نمایش Auto-Complete نیاز به منبع داده داریم که نحوه ایجاد آن‌را در کدهای فوق ملاحظه می‌کنید. در اینجا توسط جستجوی سریع لوسین و امکانات PrefixQuery آن، به تعدادی مشخص (maxItems)، رکوردهای یافت شده را بازگشت خواهیم داد. خروجی حاصل لیستی است از SearchResultها شامل عنوان مطلب و Id آن. عنوان را به کاربر نمایش خواهیم داد؛ از Id برای هدایت او به مطلبی مشخص استفاده خواهیم کرد.


د) نمایش Auto-Complete در ASP.NET MVC

using System.Text;
using System.Web.Mvc;
using LuceneSearch.Core;
using System.Web;

namespace LuceneSearch.Controllers
{
    public class HomeController : Controller
    {
        static string _indexPath = HttpRuntime.AppDomainAppPath + @"App_Data\idx";

        public ActionResult Index(int? id)
        {
            if (id.HasValue)
            {
                //todo: do something
            }
            return View(); //Show the page
        }

        public virtual ActionResult ScoredTerms(string q)
        {
            if (string.IsNullOrWhiteSpace(q))
                return Content(string.Empty);

            var result = new StringBuilder();
            var items = AutoComplete.GetTermsScored(_indexPath, q);
            foreach (var item in items)
            {
                var postUrl = this.Url.Action(actionName: "Index", controllerName: "Home", routeValues: new { id = item.Id }, protocol: "http");
                result.AppendLine(item.Title + "|" + postUrl);
            }

            return Content(result.ToString());
        }
    }
}

@{
    ViewBag.Title = "جستجو";
    var scoredTermsUrl = Url.Action(actionName: "ScoredTerms", controllerName: "Home");
    var bulletImage = Url.Content("~/Content/Images/bullet_shape.png");
}
<h2>
    جستجو</h2>

<div align="center">
    @Html.TextBox("term", "", htmlAttributes: new { dir = "ltr" })
    <br />
    جهت آزمایش lu را وارد نمائید
</div>

@section scripts
{
    <script type="text/javascript">
        EnableSearchAutocomplete('@scoredTermsUrl', '@bulletImage');
    </script>
}

function EnableSearchAutocomplete(url, img) {
    var formatItem = function (row) {
        if (!row) return "";
        return "<img src='" + img + "' /> " + row[0];
    }

    $(document).ready(function () {
        $("#term").autocomplete(url, {
            dir: 'rtl', minChars: 2, delay: 5,
            mustMatch: false, max: 20, autoFill: false,
            matchContains: false, scroll: false, width: 300,
            formatItem: formatItem
        }).result(function (evt, row, formatted) {
            if (!row) return;
            window.location = row[1];
        });
    });
}
توضیحات:
- ابتدا ارجاعاتی را به jQuery، افزونه Auto-Complete و اسکریپت سفارشی تهیه شده، در فایل layout پروژه تعریف خواهیم کرد.
در اینجا سه قسمت را مشاهده می‌کنید: کدهای کنترلر، View متناظر و اسکریپتی که Auto-Complete را فعال خواهد ساخت.
- قسمت مهم کدهای کنترلر، دو سطر زیر هستند:
result.AppendLine(item.Title + "|" + postUrl);
return Content(result.ToString());
مطابق نیاز افزونه انتخاب شده در مثال جاری، فرمت خروجی مدنظر باید شامل سطرهایی حاوی متن قابل نمایش به همراه یک Id (یا در اینجا یک آدرس مشخص) باشد. البته ذکر این Id اختیاری بوده و در اینجا جهت تکمیل بحث ارائه شده است.
return Content هم سبب بازگشت این اطلاعات به افزونه خواهد شد.
- کدهای View متناظر بسیار ساده هستند. تنها نام TextBox تعریف شده مهم می‌باشد که در متد جاوا اسکریپتی EnableSearchAutocomplete استفاده شده است. به علاوه، نحوه مقدار دهی آدرس دسترسی به اکشن متد ScoredTerms نیز مهم می‌باشد.
- در متد EnableSearchAutocomplete نحوه فراخوانی افزونه autocomplete را ملاحظه می‌کنید.
جهت آن، به راست به چپ تنظیم شده است. با 2 کاراکتر ورودی فعال خواهد شد با وقفه‌ای کوتاه. نیازی نیست تا انتخاب کاربر از لیست ظاهر شده حتما با عبارت جستجو شده صد در صد یکی باشد. حداکثر 20 آیتم در لیست ظاهر خواهند شد. اسکرول بار لیست را حذف کرده‌ایم. عرض آن به 300 تنظیم شده است و نحوه فرمت دهی نمایشی آن‌را نیز ملاحظه می‌کنید. برای این منظور از متد formatItem استفاده شده است. آرایه row در اینجا در برگیرنده اعضای Title و Id ارسالی به افزونه است. اندیس صفر آن به عنوان دریافتی اشاره می‌کند.
همچنین نحوه نشان دادن عکس العمل به عنصر انتخابی را هم ملاحظه می‌کنید (در متد result مقدار دهی شده).  window.location را به عنصر دوم آرایه row هدایت خواهیم کرد. این عنصر دوم مطابق کدهای اکشن متد تهیه شده، به آدرس یک صفحه اشاره می‌کند.


ه) نمایش Auto-Complete در ASP.NET WebForms

قسمت عمده مطالب فوق با وب فرم‌ها نیز یکی است. خصوصا توضیحات مرتبط با متد EnableSearchAutocomplete ذکر شده.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LuceneSearch.WebForms.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>جستجو</title>
    <link href="Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.autocomplete.js" type="text/javascript"></script>
    <script src="Scripts/custom.js" type="text/javascript"></script>
</head>
<body dir="rtl">
    <h2>
        جستجو</h2>
    <form id="form1" runat="server">
    <div align="center">
        <asp:TextBox runat="server" dir="ltr" ID="term"></asp:TextBox>
        <br />
        جهت آزمایش lu را وارد نمائید
    </div>
    </form>
    <script type="text/javascript">
        EnableSearchAutocomplete('Search.ashx', 'Content/Images/bullet_shape.png');
    </script>
</body>
</html>

using System.Text;
using System.Web;
using LuceneSearch.Core;

namespace LuceneSearch.WebForms
{
    public class Search : IHttpHandler
    {
        static string _indexPath = HttpRuntime.AppDomainAppPath + @"App_Data\idx";

        public void ProcessRequest(HttpContext context)
        {
            string q = context.Request.QueryString["q"];
            if (string.IsNullOrWhiteSpace(q))
            {
                context.Response.Write(string.Empty);
                context.Response.End();
            }

            var result = new StringBuilder();
            var items = AutoComplete.GetTermsScored(_indexPath, q);
            foreach (var item in items)
            {
                var postUrl = "Default.aspx?id=" + item.Id;
                result.AppendLine(item.Title + "|" + postUrl);
            }

            context.Response.ContentType = "text/plain";
            context.Response.Write(result.ToString());
            context.Response.End();
        }

        public bool IsReusable
        { get { return false; } }
    }
}

در اینجا بجای Controller از یک Generic handler استفاده شده است (Search.ashx).
result.AppendLine(item.Title + "|" + postUrl);
context.Response.Write(result.ToString());
در آن، عنوان مطالب یافت شده به همراه یک آدرس مشخص، تهیه و در Response نوشته خواهند شد.


کدهای کامل مثال فوق را از اینجا می‌توانید دریافت کنید:
همچنین باید دقت داشت که پروژه MVC آن از نوع MVC4 است (VS2010) و فرض براین می‌باشد که IIS Express 7.5 را نیز پیشتر نصب کرده‌اید.
کلمه عبور فایل: dotnettips91
 
مطالب
آشنایی با Saltarelle کامپایلر قدرتمند #C به جاوااسکریپت

شاید ساده‌ترین تعریف برای  Saltarelle  این باشد که «کامپایلریست که کد‌های C# را به جاوا اسکریپت تبدیل می‌کند». محاسن زیادی را می‌توان برای اینگونه کامپایلر‌ها نام برد؛ مخصوصا در پروژه‌های سازمانی که نگهداری از کد‌های جاوا اسکریپت بسیار سخت و گاهی خارج از توان است و این شاید مهمترین عامل ظهور ابزارهای جدید از قبیل Typescript باشد.

در هر صورت اگر حوصله و وقت کافی برای تجهیز تیم نرم افزاری، به دانش یک زبان جدید مانند Typescript نباشد، استفاده از توان و دانش تیم تولید، از زبان C# ساده‌ترین راه حل است و اگر ابزاری مطمئن برای استفاده از حداکثر قدرت JavaScript همراه با امکانات نگهداری و توسعه کد‌ها وجود داشته باشد، بی شک Saltarelle یکی از بهترین‌های آنهاست.

قبلا کامپایلر هایی از این دست مانند  Script# وجود داشتند، اما فاقد همه امکانات C# بوده وعملا قدرت کامل C# در کد نویسی وجود نداشت. اما با توجه به ادعای توسعه دهندگان این کامپایلر سورس باز در استفاده‌ی حداکثری از کلیه ویژگی‌های C# 5 و با وجود Library ‌های متعدد می‌توان Saltarelle  را عملا یک کامپایلر موفق در این زمینه دانست.

برای استفاده از Saltarelle در یک برنامه وب ساده باید یک پروژه Console Application به Solution اضافه کرد و پکیج Saltarelle.Compiler را از nuget نصب نمایید. بعد از نصب این پکیج، کلیه Reference ‌ها از پروژه حدف می‌شوند و هر بار Build توسط کامپایلر Saltarelle  انجام می‌شود. البته با اولین Build، مقداری Error را خواهید دید که برای از بین بردنشان نیاز است پکیج Saltarelle.Runtime را نیز در این پروژه نصب نمایید:

PM> Install-Package Saltarelle.Compiler
PM> Install-Package Saltarelle.Runtime

در صورتیکه کماکان Build  نهایی با Error همرا بود، یکبار این پروژه را Unload  و سپس مجددا Load نمایید



UI یک پروژه وب MVC است و Client یک Console Application که پکیج‌های مورد نیاز Saltarelle  روی آن نصب شده است.

در صورتیکه پروژه را Build نماییم و نگاهی به پوشه‌ی Debug بیاندازیم، یک فایل JavaScript همنام پروژه وجود دارد:


برای اینکه بعد از هر بار Build ، فایل اسکریپت به پوشه‌ی مربوطه در پروژه UI منتقل شود کافیست کد زیر را در Post Build  پروژه Client بنویسیم: 

copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)SalratelleSample.UI\Scripts"

اکنون پس از هر بار Build ، فایل اسکریپت مورد نظر در پوشه‌ی Scripts پروژه UI  آپدیت می‌شود:


در ادامه کافیست فایل اسکریپت را به layout اضافه کنیم. 

<script src="~/Scripts/SaltarelleSample.Client.js"></script>

در پوشه‌ی Saltarelle.Runtime در پکیج‌های نصب شده، یک فایل اسکریپت به نام mscorlib.min.js نیز وجود دارد که حاوی اسکریپت‌های مورد نیاز Saltarelle در هنگام اجراست. آن را به پوشه اسکریپت‌های پروژه UI کپی نمایید و سپس به Layout  اضافه کنید. 

<script src="~/Scripts/mscorlib.min.js"></script>
<script src="~/Scripts/SaltarelleSample.Client.js"></script>

حال نوبت به اضافه نمودن library‌های مورد نیازمان است. برای دسترسی به آبجکت هایی از قبیل document, window, element و غیره در جاوااسکریپت می‌توان پکیج Saltarelle.Web را در پروژه‌ی Client نصب نمود و برای دسترسی به اشیاء و فرمانهای jQuery، پکیج Salratelle.jQuery را نصب نمایید. 

> Install-Package Saltarelle.Web
> Install-Package Saltarelle.jQuery

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

به طور ساده‌تر وقتی از jQuery library استفاده می‌کنید هیچ فایل اسکریپت اضافه‌ای تولید نمی‌شود، اما باید اسکریپت jQuery به صفحه شما اضافه شده باشد.

<script src="~/Scripts/jquery-1.10.2.min.js"></script>

مثال ما یک اپلیکیشن ساده برای خواندن فید‌های همین سایت است. ابتدا کد‌های سمت سرور را در پروژه UI  می نویسیم.

کلاس‌های مورد نیاز ما برای این فید ریدر: 

public class Feed
    {
        public string FeedId { get; set; }
        public string Title { get; set; }
        public string Address { get; set; }

    }
    public class Item
    {
        public string Title { get; set; }
        public string Link { get; set; }
        public string Description { get; set; }
    }

و یک کلاس برای مدیریت منطق برنامه 

 public class SiteManager
    {
        private static List<Feed> _feeds;
        public static List<Feed> Feeds
        {
            get
            {
                if (_feeds == null)
                    _feeds = CreateSites();
                return _feeds;
            }
        }
        private static List<Feed> CreateSites()
        {
            return new List<Feed>() { 
                new Feed(){
                    FeedId = "1",
                    Title = "آخرین تغییرات سایت",
                    Address = "https://www.dntips.ir/rss.xml"
                },
                 new Feed(){
                    FeedId = "2",
                    Title = "مطالب سایت",
                    Address = "https://www.dntips.ir/feeds/posts"
                },
                 new Feed(){
                    FeedId = "3",
                    Title = "نظرات سایت",
                    Address = "https://www.dntips.ir/feeds/comments"
                },
                 new Feed(){
                    FeedId = "4",
                    Title = "خلاصه اشتراک ها",
                    Address = "https://www.dntips.ir/feed/news"
                },
            };
        }

        public static IEnumerable<Item> GetNews(string id)
        {
            XDocument feedXML = XDocument.Load(Feeds.Find(s=> s.FeedId == id).Address);
            var feeds = from feed in feedXML.Descendants("item")
                        select new Item
                        {
                            Title = feed.Element("title").Value,
                            Link = feed.Element("link").Value,
                            Description = feed.Element("description").Value
                        };
            return feeds;
        }

    }

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

حال دو ApiController برای دریافت داده‌ها ایجاد می‌کنیم

public class FeedController : ApiController  
{
        // GET api/<controller>
        public IEnumerable<Feed> Get()
        {
            return SiteManager.Feeds;
        }
    }

public class ItemsController : ApiController
    {
        // GET api/<controller>/5
        public IEnumerable<Item> Get(string id)
        {
            return SiteManager.GetNews(id);
        }
    }

در View پیش‌فرض که Index از کنترلر Home  است،  یک Html ساده برای فرم  صفحه اضافه می‌کنیم 

<div>
    <div>
        <h2>Feeds</h2>
        <ul id="Feeds">
           
        </ul>
    </div>
    <div>
        <h2>Items</h2>
        <p id="FeedItems">
        </p>
    </div>
   
</div>

در المنت Feeds لیست فید‌ها را قرار می‌دهیم و در FeedItems آیتم‌های مربوط به هر فید. حال به سراغ کد‌های سمت کلاینت می‌رویم و به جای جاوا اسکریپت از Saltarelle استفاده می‌کنیم.

کلاس Program را از پروژه Client باز می‌کنیم و متد Main را به شکل زیر تغییر می‌دهیم:

static void Main()
        {
            jQuery.OnDocumentReady(() => {
                FillFeeds();
            });
        }

بعد از کامپایل شدن، کد #C شارپ بالا به صورت زیر در می‌آید: 

$SaltarelleSample_Client_$Program.$main = function() {
$(function() {
$SaltarelleSample_Client_$Program.$fillFeeds();
});
};
$SaltarelleSample_Client_$Program.$main();

و این همان متد معروف jQuery است که Saltarelle.jQuery برایمان ایجاد کرده است.

متد FillFeeds را به شکل زیر پیاده سازی می‌کنیم

private static void FillFeeds()
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/feed",
                Type = "GET",
                Success = (d,t,r) => {

                    // Fill 
                    var ul = jQuery.Select("#Feeds");
                    jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });
                }
            });
        }

آبجکت jQuery، متدی به نام Ajax دارد که یک شی از کلاس jQueryAjaxOptions را به عنوان پارامتر می‌پذیرد. این کلاس کلیه خصوصیات متد Ajax در jQuery را پیاده سازی می‌کند. نکته شیرین آن توانایی نوشتن lambda برای Delegate هاست.

خاصیت Success یک Delegate است که 3 پارامتر ورودی را می‌پذیرد.

public delegate void AjaxRequestCallback(object data, string textStatus, jQueryXmlHttpRequest request);

data همان مقداریست که api باز می‌گرداند که یک لیست از Feed هاست. برای زیبایی کار، من یک کلاس Feed در پروژه Client اضافه می‌کنم که خصوصیاتی مشترک با کلاس اصلی سمت سرور دارد و مقدار برگشی Ajax را به آن تبدیل می‌کنم.

کلاس Feed و Item

 [PreserveMemberCase()]
    public class Feed
    {
        //[ScriptName("FeedId")]
        public string FeedId;

        //[ScriptName("Title")]
        public string Title;

        //[ScriptName("Address")]
        public string Address;

    }

    [PreserveMemberCase()]
    public class Item
    {
        // [ScriptName("Title")]
        public string Title;

        // [ScriptName("Link")]
        public string Link;

        // [ScriptName("Description")]
        public string Description;
    }
Attrubute‌های زیادی در Saltarelle وجود دارند و از آنجایی که کامپایلر اسم فیلد‌ها را camelCase کامپایل می‌کند من برای جلوگیری از آن از PreserveMemberCase  بر روی هر کلاس استفاده کردم. می‌توانید اسم هر فیلد را سفارشی کامپایل نمایید. 
jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });

به ازای هر آیتمی که در شیء بازگشتی وجود دارد، با استفاد از متد each در jQuery یک li ایجاد می‌کنیم. همان طور که می‌بینید کلیه خواص، به شکل Fluent قابل اضافه شدن می‌باشد. سپس برای li یک رویداد کلیک که در صورت وقوع، متد FillData را با شناسه فید کلیک شده فراخوانی می‌کند و در آخر li را به المنت ul اضافه می‌کنیم.

برای هر کلیک هم مانند مثال بالا api را با شناسه‌ی فید مربوطه فراخوانی کرده و به ازای هر آیتم، یک سطر ایجاد می‌کنیم.

private static void FillData(string p)
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/items/" + p,
                Type = "GET",
                Success = (d, t, r) => {
                    var content = jQuery.Select("#FeedItems");
                    content.Html("");
                    foreach (var item in (List<Item>)d)
                    {
                        var row = jQuery.Select("<div>").AddClass("row").CSS("direction", "rtl");
                        var link = jQuery.Select("<a>").Attribute("href", item.Link).Text(item.Title);
                        row.Append(link);
                        content.Append(row);
                    }
                }
            });
        }
خروجی برنامه به شکل زیر است: 

در این مثال ما از Saltarelle.jQuery برای استفاده از jQuery.js استفاده نمودیم. library‌های متعددی برای Saltarelle  از قبیل  linq,angular,knockout,jQueryUI,nodeJs ایجاد شده و همچنین قابلیت‌های زیادی برای نوشتن imported library‌های سفارشی نیز وجود دارد. 

مطمئنا استفاده از چنین کامپایلرهایی راه حلی سریع برای رهایی از مشکلات متعدد کد نویسی با جاوا اسکریپت در نرم افزارهای بزرگ مقیاس است. اما مقایسه آنها با ابزارهایی از قبیل typescript احتیاج به زمان و تجربه کافی در این زمینه دارد.

مطالب
ارتباط بین کامپوننت‌ها در Vue.js - قسمت سوم استفاده از تزریق وابستگی‌ها
در قسمت‌های قبلی( ^ و ^) نحوه‌ی ارتباط بین کامپوننت‌ها در Vue.js بررسی و مزایا و معایب آنها بیان شد. روش دیگری هم برای ارسال اطلاعات از کامپوننتِ Parent به فرزندانش وجود دارد که با استفاده از Dependency Injection یا به اختصار DI مقدور می‌باشد و در ورژن +2.2 معرفی شد که نحوه‌ی ارتباط بین کامپوننتِ Parent و فرزندانش را آسان نمود. پیش‌تر برای ارتباط از Parent به Child، از Props استفاده میکردیم، ولی اگر قرار بود در چند سطح این ارتباط عمیق باشد، باز هم مدیریت کردن Props مشکل و سخت بود. اکنون با استفاده از provide و inject قادر خواهیم بود تا آبجکت، فانکشن و یا دیتایِ یک کامپوننتِ Parent را در فرزندانش فراخوانی و استفاده کنیم. اگر در حالت عادی نیاز بود تا در دو سطح، یا بیشتر (مانند تصویر زیر) دیتایِ کامپوننت پدر را به فرزند، نوه و ... انتقال دهیم، می‌بایست اطلاعات را بصورت Props به هر Level انتقال دهیم.



روش جاری شباهت زیادی به استفاده از Context در React دارد:

Context provides a way to pass data through the component tree without having to pass props down manually at every level


جهت به اشتراک گذاری دیتا یا تابعی در کامپوننت Parent با Children، به شکل زیر عمل میکنیم. در اینجا با استفاده از provide، دیتای foo به اشتراک گذاشته شده‌است:
// parent component providing 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}
و در کامپوننت‌های فرزند به شکل زیر میتوانیم مقدار foo را دریافت کنیم:
// child component injecting 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

میتوانیم مقدار پیش فرض دیتایِ ارسالی از کامپوننت Parent را در قسمت data و props در کامپوننت Child دریافت نماییم:
Using an injected value as the default for a prop
//دریافت میکنیم props در قسمت  child را در کامپوننت  foo مقدار
const Child = {
  inject: ['foo'],
  props: {
    bar: {
      default () {
        return this.foo
      }
    }
  }
}

Using an injected value as data entry
//دریافت میکنیم data در قسمت  child را در کامپوننت  foo مقدار
const Child = {
  inject: ['foo'],
  data () {
    return {
      bar: this.foo
    }
  }
}

نکته: در این روش در صورتیکه دیتایِ به اشتراک گذاشته شده در کامپوننتِ Parent تغییر کند، مقدار آن در کامپوننت Child تغییری نخواهد کرد و مانند روش‌های قبلی (^ و ^) نیست و نیاز به نوشتن کدی برای تعامل داشتن و به‌روز رسانی مقادیر، در کامپوننت Child می‌باشد.
کد زیر  را در نظر بگیرید؛ با زدن دکمه‌ی Increment counter  مقدار counter در کامپوننتِParent تغییر میکند، ولی در کامپوننت Child، مقدار counter_in_child  تغییری حاصل نمی‌کند.
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <title>Dependency injection</title>
</head>

<body>
  <div id="app">
    <button @click="counter++">Increment counter</button>
    <h2>Parent</h2>
    <p>{{counter}}</p>
    <div>
      <h3>Child</h3>
      <child></child>
    </div>
  </div>
  
<script>
const Child = {
  inject: ['counter_in_child'],
  template: `<div>Counter Child is:{{ counter_in_child }}</div>`
};

new Vue ({
  el: "#app",
  components: { Child },
  provide() {
    return {
      counter_in_child: this.counter
    };
  },
  data() {
    return {
      counter: 0
    };
  }
});
</script>

</body>
</html>
       
برای اینکه بتوان تغییرات ایجاد شده‌ی بر روی دیتا را در کامپوننتِChild، مشاهده کرد، نیاز داریم کد زیر را در قسمت provide به ازای آن دیتا اضافه کنیم: 
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <title>Dependency injection</title>
</head>

<body>
  <div id="app">
    <button @click="counter++">Increment counter</button>
    <h2>Parent</h2>
    <p>{{counter}}</p>
    <div>
      <h3>Child</h3>
      <child></child>
    </div>
  </div>
  
<script>
const Child = {
  inject: ['counter_in_child'],
  template: `<div>Counter Child is:{{ counter_in_child.counter }}</div>`
};

new Vue ({
  el: "#app",
  components: { Child },
  provide() {
    const counter_in_child={};
Object.defineProperty(counter_in_child,'counter',{
enumerable:true,
get:()=>this.counter
})
    return {
      counter_in_child
    };
  },
  data() {
    return {
      counter: 0
    };
  }
});
</script>

</body>
</html>

مثالی از نحوه به اشتراک گذاری متد بین Parent و Child.

نتیجه گیری:
A) استفاده از این روش مرسوم نیست و بیشتر برای ساخت پلاگین در Vuejs مورد استفاده قرار میگیرد:
provide  and  inject  are primarily provided for advanced plugin / component library use cases. It is NOT recommended to use them in generic application code
B) برای استفاده از این روش کتابخانه‌هایی جهت ساده‌تر کردن بکارگیری ایجاد شده‌است.
C) این روش برای ارتباط  Sibling Component مناسب نیست.
مطالب
نحوه ایجاد یک اسلایدشو به صورت داینامیک

در این آموزش قصد دارم نحوه ایجاد یک Slideshow به صورت داینامیک را با ASP.NET طراحی کنم(منظور از ایجاد Slideshow به صورت داینامیک این است که عکس‌ها را به صورت داینامیک از DB بخواند).

اولین گام این است که Plugin مورد نظر را دریافت کنید که من از پلاگین Orbit استفاده کرده ام

ابتدا یک DataBase با نام DynamicSlideShow ایجاد و یک جدول با ساختار زیر با نام Picturesدرون آن ایجاد می‌کنیم

گام بعدی ایجاد یک پروژه Asp.Net با زبان C# و اضافه کردن فایلهای پلاگین به پروژه و ایجاد یک Handler برای بازیابی داده‌ها از DB است. ساختار   Solutionما باید به صورت زیر باشد

برای اینکه بتوانیم با DB ارتباط برقرار کنیم از EF استفاده می‌کنیم به همین منظور ابتدا یک Model  به نام DynamicSlideShowModel ایجاد می‌کنیم

در گام بعد بر روی GenerateFromDatabase  انتخاب کرده و بر روی دکمه Next کلیک می‌کنیم و در مراحل بعد ابتدا DB مربوط به مثال (DynamicSlideShow) و جدول آن را انتخاب می‌کنیم . حال یک Model به درون پوشه App_Code  اضافه شده است  

در ادامه برای واکشی رکوردهای موجود در جدول  Pictures  کدهای زیر را درون Handler می‌نویسیم

  var ctx = new DynamicSlideShowEntities();
  var list = ctx.Pictures.ToList();
  string str = JsonConvert.SerializeObject(list);
  context.Response.Write(str);

  

در این کدها تنها نکته‌ای که احتیاج به توضیح دارد این است که ابتدا یک لیست از رکوردها را از جدول Pictures واکشی می‌کنیم و برای پاس دادن به کلاینت ما آن‌ها را به فرمت Json تبدیل کرده ایم که برای تبدبل از کنابخانه آماده Newtonsoft.Json.dll استفاده کرده ایم 

حال باید با استفاده از jQuery کدهای درون Handler را اجرا کنیم؛ برای همین منظور یک صفحه با نام default.aspx ایجاد کرده و کدهای زیر را درون آن می‌نویسیم

<head runat="server">
    <title>Dynamic SlideShow</title>
    <link href="CSS/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Script/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.PicturePath + '" alt="' + b.PictureText + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"

            });
            });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
</body>

در اینجا ابتدا با استفاده از متد ajax کتابخانه jQuery کدهای درون Handler را اجرا کرده و به ازای هر المان موجود در جدول یک تگ img به صفحه اضافه می‌کنیم.

مطالب
رویدادها در jQuery
Jquery یکی از کتابخانه‌های قدرتمند JavaScript است که به طور وسیعی مورد استفاده طراحان وب قرار میگیرد. این کتابخانه از سه دیدگاه بسیار سودمند است؛ ابتدا به دلیل مدیریت قدرتمند Dom که دارد و دوم اینکه ارتباط Ajax را بسیار راحت کرده است و هم اینکه بستر پلاگین نویسی را فراهم ساخته است، به طوری که میلیون‌ها کتابخانه ثالث از روی همین کتابخانه ایجاد شده است. زمانیکه شما به مطالعه این کتابخانه رو آورید و نمونه کدهای مختلفی از آن را بر روی شبکه اینترنت ببینید، ممکن است این کتابخانه در عین سادگی که دارد، ابهاماتی را برای شما ایجاد کند که یکی از آن‌ها نحوه استفاده از رویدادهاست. لیست زیر نمونه‌ای از نحوه رویداد نویسی در جی کوئری است:
$().click(fn)
$().bind('click',fn)
$().live('click',fn)
$().delegate(selector, 'click', fn)
$().on('click',fn);
$().on('click', selector ,fn);
دو مورد اول در لیست، یکی هستند و در واقع مورد اول یک میانبر برای مورد دوم محسوب میشود و در پشت صحنه در واقع همان خط دوم مورد استفاده قرار میگیرد. از مزایایی که در مورد bind نسبت به استفاده مستقیم از اسم رویداد میتوان گفت این است که میتواند در یک زمان به چند رویداد متصل شود. به عنوان مثال کد زیر نمونه‌ای از آن است:
    $(".test").bind( "click mouseover mouseout", function() { console.log('fired!') } );
در خط بالا المان‌هایی که با کلاس test مزین گشته‌اند، به سه رویداد و یک تابع بایند یا متصل گشته‌اند. برای غیرفعال سازی این رویدادها میتوان از متد unbind استفاده کرد:
$(".test").unbind( "click");
برای استفاده از این دو حالت باید در نظر گرفت که تنها المان‌هایی به این رویداد متصل یا بایند میگردند که در زمان بررسی DOM، یعنی بارگذاری اولیه سایت وجود داشته‌اند و در صورتیکه المان جدیدی به صفحه اضافه شود یا اینکه جایگزین المان دیگری شود، بایند نخواهند شد و تنها المان‌های قدیمی کار خواهند کرد و در صورتیکه المانی از روی صفحه حذف شود، این اتصال از بین خواهد رفت.

مورد سوم، متد معروف live است که هم بر روی المان‌های قدیمی و هم بر روی المان‌های جدید جواب میدهد. ولی نکته‌ای که در مورد live وجود دارد این است که در نسخه‌های اخیر به دلیلی که در جلوتر عنوان میشود حذف شده است و دیگر وجود ندارد. دلیل این حذف هم این میباشد که در واقع متد live برای اینکه افزوده شدن المان‌های جدید را زیر نظر بگیرد، به خود المان متصل نمیشود؛ بلکه به به کل سند HTML یا بدنه وب سایت متصل میشد و کل ناحیه را مورد بررسی قرار میداد تا اگر المان جدیدی اضافه شد، بتواند رویداد مرتبطی را بر روی آن اجرا کند. همچنین در این حالت رویدادی‌هایی از این نوع، در اجداد این المان نیز اجرا میگردند. یعنی رویداد، از المان آغاز شده، سپس به سمت اجدادش انتقال خواهد یافت (به این حالت Bubbling up گفته میشود). پس برای اینکه این ناحیه کوچک‌تر و جزئی‌تر شود، متد دیگری جایگزین آن شد؛ به نام delegate:
$("#area").delegate('.test','click',function(e){...});

----------------------------------------
<div id="area">
<span>Span1</span>
<span>Span2</span>
</div>
این حالت که جایگزین حالت قبلی شده است بدین صورت است که ناحیه‌ای با آی دی area وجود دارد که المان‌هایی با کلاس test در آن قرار میگیرند. پس، از این به بعد، بجای اینکه کل سند بررسی شود، فقط همین بخش مورد بررسی قرار میگیرد و رویداد کلیک، بر روی المانی با کلاس test اجرا میشود و bubbling up کمتری خواهیم داشت و میتوان آن را کنترل کرد. برای حذف رویدادهای live از المان‌های مربوطه، میتوان از die و برای delegate از undelegate همانند unbind استفاده کرد. همچنین حالت استفاده از زنجیره‌ای از متدها در متد live در نظر نگرفته شده، ولی بعد از دلیگیت میتوان از متدهای دیگر، به صورت زنجیره‌ای استفاده کرد.

 از نسخه 1.7 به بعد  توصیه شده‌است به جای تمامی متدهای بالا،  از on استفاده شود که به مراتب کارآایی بهتری دارد و اکثر این متدهای بالا، حذف یا منسوخ شده اعلام گشته‌اند و احتمال حذف آن‌ها در نسخه آتی وجود دارد. به عنوان مثال delegate از نسخه 3 به بعد منسوخ اعلام شده‌است. نحوه صدا زدن on بجای دلیگیت به شکل زیر خواهد بود:
$("#area").on('click','.test',function(e){...});


نکته مهم : بعضی‌ها برای راحتی کار بدین شکل کار میکنند ولی این شیوه فرقی با متد live  ندارد.
$("body").on('click','.test',function(e){...});

در حالت Bubbling up این گونه است که اگر کدی به شکل زیر باشد و رویداد کلیک روی text هدف ما باشد، رویداد کلیک به سمت اجداد آن حرکت میکند. یعنی ابتدا رویداد کلیک b اجرا میشود، سپس a و سپس رویداد کلیک div اجرا میشود و الی آخر:
<div> <a> <b>text</b> </a> </div>
برای حل این مسئله و جلوگیری از این اتفاق میتوان از متد stopPropagation استفاده کرد که در حالت استفاده از متد on جواب میدهد؛ ولی در متد live، استفاده از آن بی فایده است. کد زیر را میتوانید با حالت‌های مختلف بررسی کرده و نتیجه این متد را در کنسول مرورگر خود ببینید:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <script
  src="https://code.jquery.com/jquery-1.12.4.min.js"
  integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
  crossorigin="anonymous"></script>
        <script src="http://code.jquery.com/jquery-migrate-1.4.1.js"></script>
    <script>
    $(document).ready(function(){
      $(".myspan").on( "click",".test", function(e) {
        e.stopPropagation();
        console.log('test fired!') } );
      $(".myspan").on( "click", function() { console.log(' myspan fired!') } );
      $(".mydiv").on( "click", function() { console.log(' mydiv fired!') } );

    });
    </script>
  </head>
  <body>
<div class="mydiv">
  <span class="myspan">
    <button type="button" class="test">Ok</button>
  </span>

</div>
  </body>
</html>

نکته تکمیلی
اینکه در استفاده از دلیگیت و on میتوان رویدادها را به شکل زیر هم مورد استفاده قرار داد:
$().on({
    'click': function (e) {
       // function
    },
    'hover': function (e) {
       // function    
    }
});
مطالب
ASP.NET MVC #13

اعتبار سنجی اطلاعات ورودی در فرم‌های ASP.NET MVC

زمانیکه شروع به دریافت اطلاعات از کاربران کردیم، نیاز خواهد بود تا اعتبار اطلاعات ورودی را نیز ارزیابی کنیم. در ASP.NET MVC، به کمک یک سری متادیتا، نحوه‌ی اعتبار سنجی، تعریف شده و سپس فریم ورک بر اساس این ویژگی‌ها، به صورت خودکار اعتبار اطلاعات انتساب داده شده به خواص یک مدل را در سمت کلاینت و همچنین در سمت سرور بررسی می‌نماید.
این ویژگی‌ها در اسمبلی System.ComponentModel.DataAnnotations.dll قرار دارند که به صورت پیش فرض در هر پروژه جدید ASP.NET MVC لحاظ می‌شود.

یک مثال کاربردی

مدل زیر را به پوشه مدل‌های یک پروژه جدید خالی ASP.NET MVC اضافه کنید:

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication9.Models
{
public class Customer
{
public int Id { set; get; }

[Required(ErrorMessage = "Name is required.")]
[StringLength(50)]
public string Name { set; get; }

[Display(Name = "Email address")]
[Required(ErrorMessage = "Email address is required.")]
[RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
ErrorMessage = "Please enter a valid email address.")]
public string Email { set; get; }

[Range(0, 10)]
[Required(ErrorMessage = "Rating is required.")]
public double Rating { set; get; }

[Display(Name = "Start date")]
[Required(ErrorMessage = "Start date is required.")]
public DateTime StartDate { set; get; }
}
}

سپس کنترلر جدید زیر را نیز به برنامه اضافه نمائید:
using System.Web.Mvc;
using MvcApplication9.Models;

namespace MvcApplication9.Controllers
{
public class CustomerController : Controller
{
[HttpGet]
public ActionResult Create()
{
var customer = new Customer();
return View(customer);
}

[HttpPost]
public ActionResult Create(Customer customer)
{
if (this.ModelState.IsValid)
{
//todo: save data
return Redirect("/");
}
return View(customer);
}
}
}

بر روی متد Create کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه باز شده، گزینه Create a strongly typed view را انتخاب کرده و مدل را Customer انتخاب کنید. همچنین قالب Scaffolding را نیز بر روی Create قرار دهید.

توضیحات تکمیلی

همانطور که در مدل برنامه ملاحظه می‌نمائید، به کمک یک سری متادیتا یا اصطلاحا data annotations، تعاریف اعتبار سنجی، به همراه عبارات خطایی که باید به کاربر نمایش داده شوند، مشخص شده است. ویژگی Required مشخص می‌کند که کاربر مجبور است این فیلد را تکمیل کند. به کمک ویژگی StringLength، حداکثر تعداد حروف قابل قبول مشخص می‌شود. با استفاده از ویژگی RegularExpression، مقدار وارد شده با الگوی عبارت باقاعده مشخص گردیده، مقایسه شده و در صورت عدم تطابق، پیغام خطایی به کاربر نمایش داده خواهد شد. به کمک ویژگی Range، بازه اطلاعات قابل قبول، مشخص می‌گردد.
ویژگی دیگری نیز به نام System.Web.Mvc.Compare مهیا است که برای مقایسه بین مقادیر دو خاصیت کاربرد دارد. برای مثال در یک فرم ثبت نام، عموما از کاربر درخواست می‌شود که کلمه عبورش را دوبار وارد کند. ویژگی Compare در یک چنین مثالی کاربرد خواهد داشت.
در مورد جزئیات کنترلر تعریف شده در قسمت 11 مفصل توضیح داده شد. برای مثال خاصیت this.ModelState.IsValid مشخص می‌کند که آیا کارmodel binding موفق بوده یا خیر و همچنین اعتبار سنجی‌های تعریف شده نیز در اینجا تاثیر داده می‌شوند. بنابراین بررسی آن پیش از ذخیره سازی اطلاعات ضروری است.
در حالت HttpGet صفحه ورود اطلاعات به کاربر نمایش داده خواهد شد و در حالت HttpPost، اطلاعات وارد شده دریافت می‌گردد. اگر دست آخر، ModelState معتبر نبود، همان اطلاعات نادرست وارد شده به کاربر مجددا نمایش داده خواهد شد تا فرم پاک نشود و بتواند آن‌ها را اصلاح کند.
برنامه را اجرا کنید. با مراجعه به مسیر http://localhost/customer/create، صفحه ورود اطلاعات کاربر نمایش داده خواهد شد. در اینجا برای مثال در قسمت ورود اطلاعات آدرس ایمیل، مقدار abc را وارد کنید. بلافاصله خطای اعتبار سنجی عدم اعتبار مقدار ورودی نمایش داده می‌شود. یعنی فریم ورک، اعتبار سنجی سمت کاربر را نیز به صورت خودکار مهیا کرده است.
اگر علاقمند باشید که صرفا جهت آزمایش، اعتبار سنجی سمت کاربر را غیرفعال کنید، به فایل web.config برنامه مراجعه کرده و تنظیم زیر را تغییر دهید:

<appSettings>
<add key="ClientValidationEnabled" value="true"/>

البته این تنظیم تاثیر سراسری دارد. اگر قصد داشته باشیم که این تنظیم را تنها به یک view خاص اعمال کنیم، می‌توان از متد زیر کمک گرفت:

@{ Html.EnableClientValidation(false); }

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

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

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Customer</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>

همانطور که ملاحظه می‌کنید به صورت پیش فرض از jQuery validator در سمت کلاینت استفاده شده است. فایل jquery.validate.unobtrusive متعلق به تیم ASP.NET MVC است و کار آن وفق دادن سیستم موجود، با jQuery validator می‌باشد (validation adapter). در نگارش‌های قبلی، از کتابخانه‌های اعتبار سنجی مایکروسافت استفاده شده بود، اما از نگارش سه به بعد، jQuery به عنوان کتابخانه برگزیده مطرح است.
Unobtrusive همچنین در اینجا به معنای مجزا سازی کدهای جاوا اسکریپتی، از سورس HTML صفحه و استفاده از ویژگی‌های data-* مرتبط با HTML5 برای معرفی اطلاعات مورد نیاز اعتبار سنجی است:
<input data-val="true" data-val-required="The Birthday field is required." id="Birthday" name="Birthday" type="text" value="" />

اگر خواستید این مساله را بررسی کنید، فایل web.config قرار گرفته در ریشه اصلی برنامه را باز کنید. در آنجا مقدار UnobtrusiveJavaScriptEnabled را false کرده و بار دیگر برنامه را اجرا کنید. در این حالت کلیه کدهای اعتبار سنجی، به داخل سورس View رندر شده، تزریق می‌شوند و مجزا از آن نخواهند بود.
نحوه‌ی تعریف این اسکریپت‌ها نیز جالب توجه است. متد Url.Content، یک متد سمت سرور می‌باشد که در زمان اجرای برنامه، مسیر نسبی وارد شده را بر اساس ساختار سایت اصلاح می‌کند. حرف ~ بکارگرفته شده، در ASP.NET به معنای ریشه سایت است. بنابراین مسیر نسبی تعریف شده از ریشه سایت شروع و تفسیر می‌شود.
اگر از این متد استفاده نکنیم، مجبور خواهیم شد که مسیرهای نسبی را به شکل زیر تعریف کنیم:

<script src="../../Scripts/customvaildation.js" type="text/javascript"></script>

در این حالت بسته به محل قرارگیری صفحات و همچنین برنامه در سایت، ممکن است آدرس فوق صحیح باشد یا خیر. اما استفاده از متد Url.Content، کار مسیریابی نهایی را خودکار می‌کند.
البته اگر به فایل Views/Shared/_Layout.cshtml، مراجعه کنید، تعریف و الحاق کتابخانه اصلی jQuery در آنجا انجام شده است. بنابراین می‌توان این دو تعریف دیگر مرتبط با اعتبار سنجی را به آن فایل هم منتقل کرد تا همه‌جا در دسترس باشند.
توسط متد Html.ValidationSummary، خطاهای اعتبار سنجی مدل که به صورت دستی اضافه شده باشند نمایش داده می‌شود. این مورد در قسمت 11 توضیح داده شد (چون پارامتر آن true وارد شده، فقط خطاهای سطح مدل را نمایش می‌دهد).
متد Html.ValidationMessageFor، با توجه به متادیتای یک خاصیت و همچنین استثناهای صادر شده حین model binding خطایی را به کاربر نمایش خواهد داد.



اعتبار سنجی سفارشی

ویژگی‌های اعتبار سنجی از پیش تعریف شده، پر کاربردترین‌ها هستند؛ اما کافی نیستند. برای مثال در مدل فوق، StartDate نباید کمتر از سال 2000 وارد شود و همچنین در آینده هم نباید باشد. این موارد اعتبار سنجی سفارشی را چگونه باید با فریم ورک، یکپارچه کرد؟
حداقل دو روش برای حل این مساله وجود دارد:
الف) نوشتن یک ویژگی اعتبار سنجی سفارشی
ب) پیاده سازی اینترفیس IValidatableObject


تعریف یک ویژگی اعتبار سنجی سفارشی

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication9.CustomValidators
{
public class MyDateValidator : ValidationAttribute
{
public int MinYear { set; get; }

public override bool IsValid(object value)
{
if (value == null) return false;

var date = (DateTime)value;
if (date > DateTime.Now || date < new DateTime(MinYear, 1, 1))
return false;

return true;
}
}
}

برای نوشتن یک ویژگی اعتبار سنجی سفارشی، با ارث بری از کلاس ValidationAttribute شروع می‌کنیم. سپس باید متد IsValid آن‌را تحریف کنیم. اگر این متد false برگرداند به معنای شکست اعتبار سنجی می‌باشد.
در ادامه برای بکارگیری آن خواهیم داشت:
[Display(Name = "Start date")]
[Required(ErrorMessage = "Start date is required.")]
[MyDateValidator(MinYear = 2000,
ErrorMessage = "Please enter a valid date.")]
public DateTime StartDate { set; get; }

اکنون مجددا برنامه را اجرا نمائید. اگر تاریخ غیرمعتبری وارد شود، اعتبار سنجی سمت سرور رخ داده و سپس نتیجه به کاربر نمایش داده می‌شود.


اعتبار سنجی سفارشی به کمک پیاده سازی اینترفیس IValidatableObject

یک سؤال: اگر اعتبار سنجی ما پیچیده‌تر باشد چطور؟ مثلا نیاز باشد مقادیر دریافتی چندین خاصیت با هم مقایسه شده و سپس بر این اساس تصمیم گیری شود. برای حل این مشکل می‌توان از اینترفیس IValidatableObject کمک گرفت. در این حالت مدل تعریف شده باید اینترفیس یاد شده را پیاده سازی نماید. برای مثال:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using MvcApplication9.CustomValidators;

namespace MvcApplication9.Models
{
public class Customer : IValidatableObject
{
//... same as before

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var fields = new[] { "StartDate" };
if (StartDate > DateTime.Now || StartDate < new DateTime(2000, 1, 1))
yield return new ValidationResult("Please enter a valid date.", fields);

if (Rating > 4 && StartDate < new DateTime(2003, 1, 1))
yield return new ValidationResult("Accepted date should be greater than 2003", fields);
}
}
}

در اینجا در متد Validate، فرصت خواهیم داشت تا به مقادیر کلیه خواص تعریف شده در مدل دسترسی پیدا کرده و بر این اساس اعتبار سنجی بهتری را انجام دهیم. اگر اطلاعات وارد شده مطابق منطق مورد نظر نباشند، کافی است توسط yield return new ValidationResult، یک پیغام را به همراه فیلدهایی که باید این پیغام را نمایش دهند، بازگردانیم.
به این نوع مدل‌ها، self validating models هم گفته می‌شود.


یک نکته:

از MVC3 به بعد، حین کار با ValidationAttribute، امکان تحریف متد IsValid به همراه پارامتری از نوع ValidationContext نیز وجود دارد. به این ترتیب می‌توان به اطلاعات سایر خواص نیز دست یافت. البته در این حالت نیاز به استفاده از Reflection خواهد بود و پیاده سازی IValidatableObject، طبیعی‌تر به نظر می‌رسد:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var info = validationContext.ObjectType.GetProperty("Rating");
//...
return ValidationResult.Success;
}




فعال سازی سمت کلاینت اعتبار سنجی‌های سفارشی

اعتبار سنجی‌های سفارشی تولید شده تا به اینجا، تنها سمت سرور است که فعال می‌شوند. به عبارتی باید یکبار اطلاعات به سرور ارسال شده و در بازگشت، نتیجه عملیات به کاربر نمایش داده خواهد شد. اما ویژگی‌های توکاری مانند Required و Range و امثال آن، علاوه بر سمت سرور، سمت کاربر هم فعال هستند و اگر جاوا اسکریپت در مرورگر کاربر غیرفعال نشده باشد، نیازی به ارسال اطلاعات یک فرم به سرور جهت اعتبار سنجی اولیه، نخواهد بود.
در اینجا باید سه مرحله برای پیاده سازی اعتبار سنجی سمت کلاینت طی شود:
الف) ویژگی سفارشی اعتبار سنجی تعریف شده باید اینترفیس IClientValidatable را پیاده سازی کند.
ب) سپس باید متد jQuery validation متناظر را پیاده سازی کرد.
ج) و همچنین مانند تیم ASP.NET MVC، باید unobtrusive adapter خود را نیز پیاده سازی کنیم. به این ترتیب متادیتای ASP.NET MVC به فرمتی که افزونه jQuery validator آن‌را درک می‌کند، وفق داده خواهد شد.

در ادامه، تکمیل کلاس سفارشی MyDateValidator را ادامه خواهیم داد:
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using System.Collections.Generic;

namespace MvcApplication9.CustomValidators
{
public class MyDateValidator : ValidationAttribute, IClientValidatable
{
// ... same as before

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata,
ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "mydatevalidator",
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName())
};
yield return rule;
}
}
}

در اینجا نحوه پیاده سازی اینترفیس IClientValidatable را ملاحظه می‌نمائید. ValidationType، نام متدی خواهد بود که در سمت کلاینت، کار بررسی اعتبار داده‌ها را به عهده خواهد گرفت.
سپس برای مثال یک فایل جدید به نام customvaildation.js به پوشه اسکریپت‌های برنامه با محتوای زیر اضافه خواهیم کرد:

/// <reference path="jquery-1.5.1-vsdoc.js" />
/// <reference path="jquery.validate-vsdoc.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

jQuery.validator.addMethod("mydatevalidator",
function (value, element, param) {
return Date.parse(value) < new Date();
});

jQuery.validator.unobtrusive.adapters.addBool("mydatevalidator");

توسط referenceهایی که مشاهده می‌کنید، intellisense جی‌کوئری در VS.NET فعال می‌شود.
سپس به کمک متد jQuery.validator.addMethod، همان مقدار ValidationType پیشین را معرفی و در ادامه بر اساس مقدار value دریافتی، تصمیم گیری خواهیم کرد. اگر خروجی false باشد، به معنای شکست اعتبار سنجی است.
همچنین توسط متد jQuery.validator.unobtrusive.adapters.addBool، این متد جدید را به مجموعه وفق دهنده‌ها اضافه می‌کنیم.
و در آخر این فایل جدید باید به View مورد نظر یا فایل master page سیستم اضافه شود:

<script src="@Url.Content("~/Scripts/customvaildation.js")" type="text/javascript"></script>




تغییر رنگ و ظاهر پیغام‌های اعتبار سنجی

اگر از رنگ پیش فرض قرمز پیغام‌های اعتبار سنجی خرسند نیستید، باید اندکی CSS سایت را ویرایش کرد که شامل اعمال تغییرات به موارد ذیل خواهد شد:

1. .field-validation-error
2. .field-validation-valid
3. .input-validation-error
4. .input-validation-valid
5. .validation-summary-errors
6. .validation-summary-valid




نحوه جدا سازی تعاریف متادیتا از کلاس‌های مدل برنامه

فرض کنید مدل‌های برنامه شما به کمک یک code generator تولید می‌شوند. در این حالت هرگونه ویژگی اضافی تعریف شده در این کلاس‌ها پس از تولید مجدد کدها از دست خواهند رفت. به همین منظور امکان تعریف مجزای متادیتاها نیز پیش بینی شده است:

[MetadataType(typeof(CustomerMetadata))]
public partial class Customer
{
class CustomerMetadata
{

}
}

public partial class Customer : IValidatableObject
{


حالت کلی روش انجام آن هم به شکلی است که ملاحظه می‌کنید. کلاس اصلی، به صورت partial معرفی خواهد شد. سپس کلاس partial دیگری نیز به همین نام که در برگیرنده یک کلاس داخلی دیگر برای تعاریف متادیتا است، به پروژه اضافه می‌گردد. به کمک ویژگی MetadataType، کلاسی که قرار است ویژگی‌های خواص از آن خوانده شود، معرفی می‌گردد. موارد عنوان شده، شکل کلی این پیاده سازی است. برای نمونه اگر با WCF RIA Services کار کرده باشید، از این روش زیاد استفاده می‌شود. کلاس خصوصی تو در توی تعریف شده صرفا وظیفه ارائه متادیتاهای تعریف شده را به فریم ورک خواهد داشت و هیچ کاربرد دیگری ندارد.
در ادامه کلیه خواص کلاس Customer به همراه متادیتای آن‌ها باید به کلاس CustomerMetadata منتقل شوند. اکنون می‌توان تمام متادیتای کلاس اصلی Customer را حذف کرد.



اعتبار سنجی از راه دور (remote validation)

فرض کنید شخصی مشغول به پر کردن فرم ثبت نام، در سایت شما است. پس از اینکه نام کاربری دلخواه خود را وارد کرد و مثلا به فیلد ورود کلمه عبور رسید، در همین حال و بدون ارسال کل صفحه به سرور، به او پیغام دهیم که نام کاربری وارد شده، هم اکنون توسط شخص دیگری در حال استفاده است. این مکانیزم از ASP.NET MVC3 به بعد تحت عنوان Remote validation در دسترس است و یک درخواست Ajaxایی خودکار را به سرور ارسال خواهد کرد و نتیجه نهایی را به کاربر نمایش می‌دهد؛ کارهایی که به سادگی توسط کدهای جاوا اسکریپتی قابل مدیریت نیستند و نیاز به تعامل با سرور، در این بین وجود دارد. پیاده سازی آن هم به نحو زیر است:
برای مثال خاصیت Name را در مدل برنامه به نحو زیر تغییر دهید:

[Required(ErrorMessage = "Name is required.")]
[StringLength(50)]
[System.Web.Mvc.Remote(action: "CheckUserNameAndEmail",
controller: "Customer",
AdditionalFields = "Email",
HttpMethod = "POST",
ErrorMessage = "Username is not available.")]
public string Name { set; get; }

سپس متد زیر را نیز به کنترلر Customer اضافه کنید:

[HttpPost]
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public ActionResult CheckUserNameAndEmail(string name, string email)
{
if (name.ToLowerInvariant() == "vahid") return Json(false);
if (email.ToLowerInvariant() == "name@site.com") return Json(false);
//...
return Json(true);
}


توضیحات:
توسط ویژگی System.Web.Mvc.Remote، نام کنترلر و متدی که در آن قرار است به صورت خودکار توسط jQuery Ajax فراخوانی شود، مشخص خواهند شد. همچنین اگر نیاز بود فیلدهای دیگری نیز به این متد کنترلر ارسال شوند، می‌توان آن‌ها را توسط خاصیت AdditionalFields، مشخص کرد.
سپس در کدهای کنترلر مشخص شده، متدی با پارامترهای خاصیت مورد نظر و فیلدهای اضافی دیگر، تعریف می‌شود. در اینجا فرصت خواهیم داشت تا برای مثال پس از بررسی بانک اطلاعاتی، خروجی Json ایی را بازگردانیم. return Json false به معنای شکست اعتبار سنجی است.
توسط ویژگی OutputCache، از کش شدن نتیجه درخواست‌های Ajaxایی جلوگیری کرده‌ایم. همچنین نوع درخواست هم جهت امنیت بیشتر، به HttpPost محدود شده است.
تمام کاری که باید انجام شود همین مقدار است و مابقی مسایل مرتبط با اعمال و پیاده سازی آن خودکار است.


استفاده از مکانیزم اعتبار سنجی مبتنی برمتادیتا در خارج از ASP.Net MVC

مباحثی را که در این قسمت ملاحظه نمودید، منحصر به ASP.NET MVC نیستند. برای نمونه توسط متد الحاقی زیر نیز می‌توان یک مدل را مثلا در یک برنامه کنسول هم اعتبار سنجی کرد. بدیهی است در این حالت نیاز خواهد بود تا ارجاعی را به اسمبلی System.ComponentModel.DataAnnotations، به برنامه اضافه کنیم و تمام عملیات هم دستی است و فریم ورک ویژه‌ای هم وجود ندارد تا یک سری از کارها را به صورت خودکار انجام دهد.

using System.ComponentModel.DataAnnotations;

namespace MvcApplication9.Helper
{
public static class ValidationHelper
{
public static bool TryValidateObject(this object instance)
{
return Validator.TryValidateObject(instance, new ValidationContext(instance, null, null), null);
}
}
}