نظرات مطالب
کار با Docker بر روی ویندوز - قسمت اول - Container چیست؟
اخیراً Docker Desktop از WebAssembly پشتیبانی میکند. به این معنا که بدون نیاز به یک کانتینر خاص میتوانیم توسط یک Wasm runtime با نام WasmEdge اپلیکیشن‌های وب‌اسمبلی را اجرا کنیم. در واقع توسط این runtime رفتار یک کانتینر شبیه‌سازی شده است بدون اینکه ایمیج کانتینر حاوی OS یا runtime context باشد. با این اوصاف میتوانیم یک پروژه NET.ی را توسط Wasi.Sdk به Wasm تبدیل کنیم و آن را درون Docker به صورت مستقیم اجرا کنیم:

dotnet new console -o MyFirstWasiApp
cd MyFirstWasiApp
dotnet add package Wasi.Sdk --prerelease
dotnet build
اگر به مسیر bin/Debug/net7.0 مراجعه کنید خواهید دید که MyFirstWasiApp.wasm نیز تولید شده است؛ از دستور wasmtime برای اجرای آن نیز میتوانیم استفاده کنیم:
wasmtime bin/Debug/net7.0/MyFirstWasiApp.wasm
در ادامه میتوانیم یک Dockerfile ایجاد کرده و خروجی Wasm applicationمان را به اصطلاح containerised کنیم:
FROM scratch

COPY bin/Debug/net7.0/MyFirstWasiApp.wasm MyFirstWasiApp.wasm

ENTRYPOINT [ "MyFirstWasiApp.wasm" ]
برای بیلد کردن ایمیج فوق میتوانیم از دستور زیر استفاده کنیم:
docker buildx build . --file=Dockerfile --tag=dotnet-webassembly --platform wasi/wasm32
در اینجا با کمک فلگ platform به Docker گفته‌ایم که برای ساخت ایمیج موردنظر از معماری Wasm استفاده کند؛ به این معنا که برای کامپیوترهای مختلف نیاز نخواهد بود که ایمیج‌های جداگانه‌ایی تهیه کنیم بلکه wasm runtime اینکار را برای ما به صورت خودکار انجام خواهد داد. در نهایت بعد از ایجاد ایمیج میتوانیم از دستور docker run برای اجرای Wasm applicationمان استفاده کنیم:
docker run --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 dotnet-webassembly
نکته: برای اجرای Wasm application باید مطمئن شوید که فیچر containerd image store فعال باشد:


دقت داشته باشید که این فیچر هنوز در مرحله Beta قرار دارد؛ و ممکن است در حین تهیه ایمیج با edge caseهای روبرو شوید به عنوان مثال من سعی کردم یک وب‌سرور ASP.NET Core (توسط Wasi.Sdk این امکان وجود دارد) را containerised کنم که در نهایت با خطای زیر مواجه شدم:

[error] instantiation failed: incompatible import type, Code: 0x61
     Mismatched function type. Expected: FuncType {params{i32 , i32 , i32} returns{i32}} , Got: FuncType {params{i32 , i32} returns{i32}}
     When linking module: "wasi_snapshot_preview1" , function name: "sock_accept"
     At AST node: import description
     At AST node: import section
     At AST node: module


مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت نهم
در قسمت‌های قبل یاد گرفتید که چطور View و View Model را در متد RegisterTypes در App.xaml.cs به یکدیگر وصل کرده و به آن‌ها یک اسم دهید و با Navigation Service آن‌ها را در حالت‌های مختلف مثل Navigation Page و Master Detail و Popup و ... باز کنید و بین صفحات جابجا شوید.
در View‌ها دو مورد را به صورت ابتدایی توضیح دادیم، Binding و Command. در این قسمت وارد جزئیات می‌شویم.

Command قطعه‌ای کد در View Model است (عموما یک متد CSharp ای) که در View می‌تواند مورد استفاده قرار گیرد. برای این که کلیک بر روی یک دکمه، متد مربوطه در View Model را اجرا کند، آن را درون یک BitDelegateCommand قرار دادیم و به Command دکمه Bind کردیم.
می‌دانیم که Button، بجز کلیک، دارای Event‌های دیگری نیز هست. علاوه بر این، ممکن است بجای کلیک دکمه، بخواهیم زمانیکه روی یک عکس عمل Swipe انجام می‌شود، کاری در سمت View Model انجام شود.
برای حل این مشکل از Event To Command و Gesture Recognizer استفاده می‌کنیم. مورد اول همانطور که از نام‌اش پیداست، وظیفه آن اتصال بر قرار کردن بین یک Event و یک Command است و دومی نیز برای مسائلی چون Swipe - Tap - Pan - Pinch و ... بکار می‌رود.
در کد پایین که می‌تواند در HelloWorldView.xaml در پروژه XamApp نیز تست شود، یکبار Command دکمه را Bind کرده‌ایم و یکبار Clicked Event آن را، که البته نتیجه یکی است! به جای نام Clicked می‌توان نام هر یک از Event‌‌ها را نوشت. همچنین این کار بجز Button، بر روی باقی کنترل‌ها نیز کار می‌کند.
        <Button Command="{Binding IncreaseStepsCountCommand}" Text="Button with command binding" />
        <Button Text="Button with event to command">
            <Button.Behaviors>
                <prismBehaviors:EventToCommandBehavior Command="{Binding IncreaseStepsCountCommand}" EventName="Clicked" />
            </Button.Behaviors>
        </Button>
همانطور که می‌بینید، EventToCommandBehavior که توسط Prism ارائه شده‌است، به Behavior‌های Button اضافه شده‌است. هر Behavior، رفتاری خاص را به کنترل‌های شما اضافه می‌کند و امکان نوشتن Custom behavior نیز وجود دارد که از موضوع این بحث خارج است.

prismBehaviors در بالای فایل Xaml به شکل زیر تعریف شده است:
xmlns:prismBehaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
این عمل معادل using نوشتن در بالای فایل‌های CSharp است و می‌گوید که EventToCommandBehavior در کدام namespace و dll است. وقتی این کار را می‌کنید، می‌توانید در Xaml به تمامی کلاس‌های آن namespace دسترسی داشته باشید.

در همان مثال HelloWorldView.xaml، یک Label بود که تعداد زده شدن دکمه را نشان می‌داد. اگر بخواهیم وقتی روی آن Label به سمت چپ Swipe می‌کنیم نیز Command مربوطه اجرا شود، خواهیم داشت:
        <Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}">
            <Label.GestureRecognizers>
                <SwipeGestureRecognizer Command="{Binding IncreaseStepsCountCommand}" Direction="Left" />
            </Label.GestureRecognizers>
        </Label>
در صورتیکه قصد تست بر روی Windows-UWP را دارید و صفحه نمایش لمسی ندارید، مطابق با آموزش قسمت Windows همین سری آموزشی، بر روی Simulator خروجی بگیرید.

در مورد Command، مطالبی چون Command Parameter و Event Args Parameter Path و Event Args Converter باقی می‌مانند که در قسمت‌های بعدی به آن‌ها نیز خواهیم پرداخت. ولی به صورت کلی در نظر داشته باشید که جای منطق برنامه، در سمت View Model است و اگر در سناریویی نمی‌دانستید که چگونه در واکنش به تغییری در View، در سمت View Model کاری کنید، می‌توانید همینجا سوال خود را بپرسید.

Binding نیز همان طور که تا الان متوجه شده‌اید، باعث اتصال دو چیز به یک دیگر می‌شود. وقتی می‌نویسیم:
<Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}" />
داریم Text را که یک Property در Label در View است، به StepsCount که یک Property در View Model است، وصل می‌کنیم.
هر Binding یک Source دارد. به صورت پیش فرض، چون داریم از Prism استفاده می‌کنیم، تمامی Binding‌ها در View به صورت پیش فرض به View Model اشاره می‌کنند؛ مگر اینکه چیز دیگری بگوییم. برای مثال، اگر بخواهیم یک Label داشته باشیم که متن یک Entry را نمایش می‌دهد، هیچ‌یک از این دو در View Model نیستند. پس در این Binding خاص، باید سورس را نیز مشخص کنیم:
        <Entry x:Name="MyEntry" />
        <Label Text="{Binding Text, Source={x:Reference MyEntry}}" />
این باعث می‌شود که Text، که یک Property در Label است، به Text، که یک Property در کنترلی با نام MyEntry است، وصل شود.

x:DataType
Binding‌ها به صورت پیش فرض، در زمان اجرا و با کمک Reflection کار می‌کنند؛ این مهم دو مسئله را ایجاد می‌کند:
1- اگر در View، نام Property مد نظر در View Model را به اشتباه بنویسید، تا زمانیکه برنامه را اجرا نکنید و وارد آن فرم نشوید، متوجه مشکل دار بودن ماجرا نمی‌شوید و وقتی در View Model نام یک Property را عوض می‌کنید، در سمت View در هنگام Build خطایی نمی‌گیرید.
2- Reflection چیز خوبی است و برخی سناریوها بدون آن خیلی سخت پیاده سازی می‌شوند؛ ولی Reflection سربار کارآیی دارد و بهتر است در صورت امکان از آن پرهیز شود.
برای حل این مشکل در Xamarin Forms امکانی به نام x:DataType معرفی شده‌است که در View می‌گویید که Source چه کلاسی است، که قاعدتا View Model است. برای این کار داریم:
<ContentPage
...
    xmlns:vm="clr-namespace:XamApp.ViewModels"
    x:DataType="vm:HelloWorldViewModel">
با این روش، هر دو مشکل فوق حل می‌شوند و هم خطای زمان Build برای Binding‌های اشتباه خواهیم داشت و هم بدون Reflection از Binding‌ها استفاده می‌کنیم که بهبود سرعت برنامه را در بر دارد.

INotifyPropertyChanged
در مثال XamApp، وقتی روی دکمه کلیک می‌کنیم، مقدار StepsCount را که یک Property در View Model است، یکی یکی افزایش می‌دهیم. این Property در View به Label بایند شده‌است. وقتی مقداری در View Model که سورس Binding ما محسوب می‌شود رخ می‌دهد، View چگونه خبر دار می‌شود؟
هر کلاسی که بخواهد در Binding به عنوان Source استفاده شود (منجمله View Model)، می‌تواند یک interface را پیاده سازی کند؛ به نام INotifyPropertyChanged. همچنین تعریف یک Property نیز باید به این شکل باشد:
        private int _StepsCount;
        public int StepsCount
        {
            get => _StepsCount;
            set => SetProperty(ref _StepsCount, value);
        }
طبیعتا این کد که در سناریوهایی می‌تواند حتی پیچیده‌تر شود، جالب نیست و برای همین ما از  PropertyChanged.Fody استفاده کرده‌ایم که در زمان Build، خودش همه مسائل را مدیریت می‌کند و کد فوق به سادگی می‌تواند به صورت زیر نوشته شود:
public int StepsCount { get; set; }
به صفحه GitHub این کتابخانه مراجعه کنید و با سایر امکانات فوق العاده‌ی آن آشنا شوید!
در Binding، موارد دیگری چون Value Converters و Relative Source نیز وجود دارند که در ادامه با آنها آشنا می‌شویم. اگر در Binding نیز مشکلی دارید، می‌توانید همینجا نیز بپرسید.
نظرات مطالب
معرفی Kendo UI
کد زیر رو در ویو اضافه کردم و فایل جی کوئری هم در layout وجود داره
@section JavaScript
{
    
 <!--KendoUI: Web-->
<link href="@Url.Content("~/content/css/kendo/kendo.common.min.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/content/css/kendo/kendo.default.min.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/kendo/kendo.web.min.js")" type="text/javascript"></script>

      <!--KendoUI: DataViz-->
<link href="@Url.Content("~/content/css/kendo/kendo.dataviz.min.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/kendo/kendo.dataviz.min.js")" type="text/javascript"></script>

<!--KendoUI: Mobile-->
<link href="@Url.Content("~/content/css/kendo/kendo.mobile.all.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/kendo/kendo.mobile.min.js")" type="text/javascript"></script>

 <script type="text/javascript">
    $(function () {
        $("#pickDate").kendoDatePicker();
    });
</script>



}

جی کوئری یکبار صدا زده شده ولی متاسفانه خطای زیر رو میده:
TypeError: $(...).kendoDatePicker is not a function

چه مواردی رو باید برای رفع خطا بررسی کنم؟
نظرات مطالب
آغاز به کار با Twitter Bootstrap در ASP.NET MVC
دو مدخل برای من اضافه میشن
<link href="/Content/myCSS?v=LBa4VrARIJBApHOXLQoCWVPKu_c6QBb4D7npv-dq5TA1" rel="stylesheet"/>
<script src="/Scripts/js?v=rk-l3czPS8KW6usEOpK8aIglVqPOVEKWoSXV4N-5N2Q1"></script>
منتها برای CSS فقط فایل Style.css و برای جاوااسکریپت فقط jQuery رو اضافه میکنه، به نظرتون مشکل چی میتونه باشه؟
نظرات مطالب
تغییرات بوجود آمده در Bundling and Minification -MVC4
با سلام 
من برای استفاده از فایل‌های CSS از Styles.Render  استفاده میکنم . وقتی توی VS برنامه را اجرا میکنم کاملا درست جواب میدهد اما وقتی publish میکنم روی IIS  هیچکدام از فایل‌ها load نمیشود (توسط browser بارگذاری نمیشود) اما وقتی فایل‌ها را به صورت    <link href="~/Content/themes/blue/Site.css" rel="stylesheet" type="text/css" />
استفاده میکنم ، بعد از publish  درست عمل می‌کند ! مشکل چیست ؟
ممنون میشوم اگر راهنمایی بفرمائید
مطالب
مقدمه‌ای بر داکر، قسمت سوم
در قسمت قبلی با Volume آشنا شدیم و نحوه‌ی اجرا کردن یک Source Code را درون Container یاد گرفتیم. در این قسمت میخواهیم یک Image شخصی ساخته، آن‌را اجرا و درون Docker hub ارسال نماییم.


Dockerfile چیست؟
Dockerfile عملا چیزی بیشتر از یک دستور العمل از نوع متنی برای build و ساخت یک docker image از آن نمیباشد. ضمن اینکه مراحل build شدن، cache شده و build‌های بعدی با سرعت خیلی بیشتری اجرا خواهند شد. بعد از نوشتن چند dockerfile متوجه خواهیم شد که مراحلش بسیار ساده است.
ساخت اولین Dockerfile
قبل از ساخت dockerfile، مثل جلسه‌ی قبل یک پروژه‌ی ساده‌ی nodejsی را با فایل index.js میسازیم:
const express = require('express')
const app = express()
const PORT = 3000;
app.get('/', (req, res) => {
  res.send('Hello World')
})
app.listen(PORT, () => {
  console.log(`listening on port ${PORT}!`)
})
درون  package.json هم این قسمت را تغییر میدهیم:
"scripts": {
    "start": "node index"
  },
حال فایل Dockerfile را ساخته و دستورالعمل‌های زیر را درون آن مینویسیم:
FROM node
ENV NODE_ENV=production
COPY . /var/www
WORKDIR /var/www
RUN npm i
EXPOSE 3000
ENTRYPOINT npm start
توضیحات دستورات فوق
1) FROM node یک imageی است که برنامه‌ی شما از آن استفاده میکند.
2) از environment variable استفاده کرده و نوع آن را روی production میگذاریم.
3) COPY کردن تمام فایل‌های دایرکتوری جاری پروژه درون فایل سیستم container به آدرس فوق.
4) عوض کردن work directory روی آدرسی که پروژه کپی شده است.
5) اجرا کردن دستور npm i برای نصب شدن Dependencies‌های پروژه.
6) EXPOSE کردن یک port برای ایجاد دسترسی.
7) نقطه‌ی شروع برنامه و دستور npm start که درون package.json قبل تعریف نموده بودیم.
 
بعد از ساخت Dockerfile فوق نوبت به build گرفتن از آن میرسد که با استفاده از دستور زیر میباشد:
docker build -f Dockerfile -t alikhll/testnode1 .
نکته: اگر image node را روی سیستم خود نداشته باشید ابتدا بصورت خودکار آن را pull مینماید.
1) پرچم f- که برای شناساندن فایل Dockefile میباشد، بدلیل این است که نام این فایل قابل تغییر میباشد.
2) پرچم t- برای نام image ساخته شده میباشد. همچنین . نیز به دایرکتوری جاری اشاره میکند.
بعد از ساخته شدن image با استفاده از دستور docker images میتوانید آن را مشاهده نمایید.
برای اجرای image نیز از دستور زیر استفاده میکنیم:
docker run -d -p 8080:3000 alikhll/testnode1
حال با استفاده از port 8080 میتوانید اپلیکیشن را اجرا نمایید.
از آنجایی که اکثر خوانندگان این مجموعه برنامه نویسان دات نت هستند یک Dockerfile دات نتی نیز برای تسلط بیشتر مینویسیم.
ابتدا دستورات زیر را درون ترمینال خود وارد کرده و یک پروژه‌ی وب از نوع Net Core. را میسازیم:
dotnet new web
dotnet restore
dotnet run
حال روی localhost قابلیت اجرا خواهد داشت؛ اما میخواهیم این app را بر روی container اجرا کنیم. بنابراین Dockerfile را اینگونه مینویسیم:
FROM microsoft/dotnet
ENV ASPNETCORE_URLS http://*:3000
COPY . /var/www
WORKDIR /var/www
RUN dotnet restore
EXPOSE 3000
ENTRYPOINT dotnet run
همه چیز خیلی شبیه به داکرفایل قبلی است، با این تفاوت که از ایمیج microsoft/dotnet استفاده کرده‌ایم (از imageهای سبکتری برای محیط production استفاده میکنیم! ضمن اینکه image فوق از Debian استفاده میکند. image جدیدی روی توزیع Alpine ایجاد شده است که حجم خیلی پایینی داشته و برای مطالعه بیشتر به اینجا رجوع کنید).
نکته‌ی مهم ASPNETCORE_URLS میباشد چون میخواهیم بتوانیم از خارج از محیط container با استفاده از IP، به آن دسترسی داشته باشیم (محیط local نیست).
 دستورات زیر را برای build و اجرا وارد مینماییم:
docker build -f Dockerfile -t alikhll/testasp1 .
docker run -d -p 8080:3000 alikhll/testasp1
اکنون app شما باید روی پورت خارجی 8080 قابل اجرا باشد.
نکته: من container قبلی nodejsی را stop کرده بودم وگرنه این پورت قابل استفاده‌ی مجدد نبود!
پابلیش کردن روی Docker Hub
انتشار دادن روی Docker hub ّبسیار ساده است. میتوانید یک اکانت به صورت رایگان ساخته و image‌های خود را بر روی آن انتشار دهید.
نکته: پروژه‌های تستی خود را میتوانید آنجا انتشار داده که البته قابلیت private بودن را ندارند. در صورتیکه برای یک پروژه‌ی واقعی که image‌های عمومی نیستند و فقط درون سازمان باید به آن دسترسی داشته باشند، میتوانید اکانت enterprise تهیه کرده و image‌های خود را درون آن مدیریت نمایید. همچنین از سرویس‌های ابری Azure, Amazon نیز برای انتشار دادن imageهای خصوصی میتوان استفاده نمود.
دستور زیر برای انتشار دادن imageی که ساختیم روی docker hub میباشد. ابتدا login کرده user/password را وارد کرده سپس push مینماییم:
docker login
docker push alikhll/testnode1
نکته: به جای alikhll باید username شخصی خود را وارد نمایید.
اکنون به راحتی با استفاده از دستور زیر روی یک ماشین دیگر که داکر روی آن نصب شده است، میتوانید image را pull کرده و اجرا نمایید:
docker pull alikhll/testnode1
نظرات مطالب
EF Code First #4
با سلام؛
من بعد از این دستورات رو تو پاور شل زدم به همچین خطایی برخوردم، جریان چیه ؟
The term 'enable-migrations' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, veri
fy that the path is correct and try again.
At line:1 char:18
+ enable-migrations <<<< 
    + CategoryInfo          : ObjectNotFound: (enable-migrations:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException 
نظرات مطالب
فعال سازی عملیات CRUD در Kendo UI Grid
$("#grid").kendoGrid({
// ...
    columns:
    [
        {
            field: "Your Field",
            title: "Your Field Name",
            width: "20%",
            editor: function (container, options) {
                $('<textarea cols="20" rows="4" data-bind="value: ' + options.field + '"></textarea>').appendTo(container);
            }
        },
        // ...   
   ]
// ...
});
مطالب
فایرفاکس 4 و غیرفعال کردن قابلیت تنظیم دستی اندازه جعبه‌های متنی آن

احتمالا فایرفاکس 4 رو تازه نصب کردید:


یکی از موارد جالب توجه آن منهای مورد فوق، امکان تغییر سایز TextArea در آن به صورت "سر خود" می‌باشد (همانند مرورگر کروم) :


برای غیرفعال کردن این قابلیت باید css سایت یا عنصر مورد نظر را به صورت ذیل تغییر داد:
<style type="text/css">
textarea {
resize:none;
}
</style>

مطالب
پیاده سازی Open Search در ASP.NET MVC
اگر به امکانات مرورگرهای جدید دقت کرده باشید، امکان تعریف منبع جستجوی جدید، نیز برای آن‌ها وجود دارد. برای نمونه تصاویر ذیل مرتبط به مرورگرهای فایرفاکس و کروم هستند:




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


تهیه OpenSearchResult سفارشی

برنامه باید بتواند محتوای XML ایی ذیل را مطابق پروتکل Open Search به صورت پویا تهیه و در اختیار مرورگر قرار دهد:
<?xml version="1.0" encoding="UTF-8" ? />
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
    <ShortName>My Site's Asset Finder</ShortName>
    <Description>Find all your assets</Description>
    <Url type="text/html"
        method="get"
        template="http://MySite.com/Home/Search/?q=searchTerms"/>
    <InputEncoding>UTF-8</InputEncoding>
    <SearchForm>http://MySite.com/</SearchForm>
</OpenSearchDescription>
به همین جهت کلاس OpenSearchResult ذیل تهیه شده است تا انجام آن‌را با روشی سازگار با ASP.NET MVC سهولت بخشد:
using System;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Xml;

namespace WebToolkit
{
    public class OpenSearchResult : ActionResult
    {
        public string ShortName { set; get; }
        public string Description { set; get; }
        public string SearchForm { set; get; }
        public string FavIconUrl { set; get; }
        public string SearchUrlTemplate { set; get; }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            var response = context.HttpContext.Response;
            writeToResponse(response);
        }

        private void writeToResponse(HttpResponseBase response)
        {
            response.ContentEncoding = Encoding.UTF8;
            response.ContentType = "application/opensearchdescription+xml";
            using (var xmlWriter = XmlWriter.Create(response.Output, new XmlWriterSettings { Indent = true }))
            {
                xmlWriter.WriteStartElement("OpenSearchDescription", "http://a9.com/-/spec/opensearch/1.1/");

                xmlWriter.WriteElementString("ShortName", ShortName);
                xmlWriter.WriteElementString("Description", Description);
                xmlWriter.WriteElementString("InputEncoding", "UTF-8");
                xmlWriter.WriteElementString("SearchForm", SearchForm);

                xmlWriter.WriteStartElement("Url");
                xmlWriter.WriteAttributeString("type", "text/html");
                xmlWriter.WriteAttributeString("template", SearchUrlTemplate);                
                xmlWriter.WriteEndElement();

                xmlWriter.WriteStartElement("Image");
                xmlWriter.WriteAttributeString("width", "16");
                xmlWriter.WriteAttributeString("height", "16");
                xmlWriter.WriteString(FavIconUrl);
                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndElement();
                xmlWriter.Close();
            }
        }
    }
}
کار این Action Result، تهیه محتوایی XML ایی مطابق نمونه‌ای است که در ابتدای توضیحات ملاحظه نمودید. توضیحات خواص آن‌، در ادامه مطلب ارائه شده‌اند.


تهیه OpenSearchController

در ادامه برای استفاده از Action Result سفارشی تهیه شده، نیاز است یک کنترلر را نیز به برنامه اضافه کنیم:
using System.Web.Mvc;

namespace Readers
{
    public partial class OpenSearchController : Controller
    {
        public virtual ActionResult Index()
        {
            var fullBaseUrl = Url.Action(result: MVC.Home.Index(), protocol: "http");
            return new OpenSearchResult
            {
                ShortName = ".NET Tips",
                Description = ".NET Tips Contents Search",
                SearchForm = fullBaseUrl,
                FavIconUrl = fullBaseUrl + "favicon.ico",
                SearchUrlTemplate = Url.Action(result: MVC.Search.Index(), protocol: "http") + "?term={searchTerms}"
            };
        }
    }
}
برای استفاده از OpenSearchResult به چند نکته باید دقت داشت:
الف) آدرس‌های مطرح شده در آن باید مطلق باشند و نه نسبی. به همین جهت پارامتر protocol در اینجا ذکر شده است تا سبب تولید یک چنین آدرس‌هایی گردد.
ب) Url.Action ایی که در اینجا استفاده شده است مطابق تعاریف T4MVC است؛ ولی کلیات آن با نمونه پیش فرض ASP.NET MVC تفاوتی نمی‌کند. توسط T4MVC بجای ذکر نام اکشن متد و کنترلر مد نظر به صورت رشته‌ای، می‌توان به صورت Strongly typed به این موارد ارجاع داد.
ج) تنها نکته مهم این کلاس، خاصیت SearchUrlTemplate است. قسمت انتهایی آن یعنی ={searchTerms} همیشه ثابت است. اما ابتدای این آدرس باید به کنترلر جستجوی شما که قادر است پارامتری را به شکل کوئری استرینگ دریافت کند، اشاره نماید.
د) FavIconUrl به آدرس یک آیکن در سایت شما اشاره می‌کند. برای نمونه ذکر favicon.ico پیش فرض سایت می‌تواند مفید باشد.


معرفی OpenSearchController به Header سایت

<link href="@Url.Action(result: MVC.OpenSearch.Index(), protocol: "http")" rel="search"
        title=".NET Tips Search"  type="application/opensearchdescription+xml" />
مرحله نهایی افزودن پروتکل Open search به سایت، مراجعه به فایل layout پروژه و افزودن link خاص فوق به آن است. در این لینک، href آن باید به مسیر کنترلر OpenSearchایی که در قسمت قبل تعریف کردیم، اشاره کند. این مسیر نیز باید مطلق باشد. به همین جهت پارامتر protocol آن مقدار دهی شده است.