مطالب
شروع کار با Dart - قسمت 1

Dart کتابخانه ای است که توسط شرکت گوگل ارائه شده است و گفته می‌شود، قرار است جایگزین جاوا اسکریپت گردد و از آدرس https://www.dartlang.org قابل دسترسی می‌باشد. این کتابخانه، دارای انعطاف پذیری فوق العاده بالایی است و کد نویسی Java Script را راحت‌تر می‌کند. در حال حاضر هیچ مرورگری به غیر از Chromium از این تکنولوژی پشتیبانی نمی‌کند و جهت تسهیل در کدنویسی، باید از ویرایشگر Dart Editor استفاده کنید. این ویرایشگر کدهای نوشته شده را به دو صورت Native و JavaScript Compiled در اختیار مرورگر قرار می‌دهد. در ادامه با نحوه‌ی کار و راه اندازی Dart آشنا خواهید شد.

ابتدا Dart و ویرایشگر مربوط به آن را توسط لینک‌های زیر دانلود کنید:

دانلود نسخه 64 بیتی دارت + ویرایشگر

دانلود نسخه 32 بیتی دارت + ویرایشگر

بعد از اینکه فایلهای فوق را از حالت فشرده خارج کردید، پوشه ای با نام dart ایجاد می‌نماید. وارد پوشه dart شده و DartEditor را اجرا کنید.

توجه: جهت اجرای dart به JDK 6.0 یا بالاتر نیاز دارید

در مرحله بعد نمونه کدهای Dart را از لینک زیر دانلود نمایید و از حالت فشرده خارج کنید. پوشه ای با نام one-hour-codelab ایجاد می‌گردد.

دانلود نمونه کدهای دارت

از منوی File > Open Existing Folder… پوشه one-hour-codelab را باز کنید .

توضیحات

- پوشه packages و همچنین فایلهای pubspec.yaml و pubspec.lock شامل پیش نیازها و Package هایی هستند که جهت اجرای برنامه‌های تحت Dart مورد نیاز هستند. Dart Editor این نیازمندی‌ها را به صورت خودکار نصب و تنظیم می‌کند.

توجه: اگر پوشه Packages را مشاهده نکردید و یا در سمت چپ فایلها علامت X قرمز رنگ وجود داشت، بدین معنی است که package ‌ها به درستی نصب نشده اند. برای این منظور بر روی pubspec.yaml کلیک راست نموده و گزینه Get Pub را انتخاب کنید. توجه داشته باید که بدلیل تحریم ایران توسط گوگل باید از ابزارهای عبور از تحریم استفاده کنید.

- 6 پوشه را نیز در تصویر فوق مشاهده می‌کنید که نمونه کد piratebadge را بصورت مرحله به مرحله انجام داده و به پایان می‌رساند.

- Dart SDK شامل سورس کد مربوط به تمامی توابع، متغیرها و کلاس هایی است که توسط کیت توسعه نرم افزاری Dart ارائه شده است.

- Installed Packages شامل سورس کد مربوط به تمامی توابع، متغیرها و کلاس‌های کتابخانه‌های اضافه‌تری است که Application به آنها وابسته است.


گام اول: اجرای یک برنامه کوچک

در این مرحله سورس کدهای آماده را مشاهده می‌کنید و با ساختار کدهای Dart و HTML آشنا می‌شوید و برنامه کوچکی را اجرا می‌نمایید.

در Dart Editor پوشه 1-blankbadge را باز کنید و فایلهای piratebadge.html و piratebadge.dart را مشاهده نمایید.

کد موجود در فایل piratebadge.html

<html>
  <head>
    <meta charset="utf-8">
    <title>Pirate badge</title>
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="piratebadge.css">
  </head>
  <body>
    <h1>Pirate badge</h1>
    
    <div>
      TO DO: Put the UI widgets here.
    </div>
    <div>
      <div>
        Arrr! Me name is
      </div>
      <div>
        <span id="badgeName"> </span>
      </div>
    </div>

    <script type="application/dart" src="piratebadge.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

توضیحات

- در کد HTML ، اولین تگ <script> ، فایل piratebadge.dart را جهت پیاده سازی دستورات dart به صفحه ضمیمه می‌نماید

- Dart Virtual Machine (Dart VM) کدهای Dart را بصورت Native یا بومی ماشین اجرا می‌کند. Dart VM کدهای خود را در Dartium که یک ویرایش ویژه از مرورگر Chromium می‌باشد اجرا می‌کند که می‌تواند برنامه‌های تحت Dart را بصورت Native اجرا کند.

- فایل packages/browser/dart.js پشتیبانی مرورگر از کد Native دارت را بررسی می‌کند و در صورت پشتیبانی، Dart VM را راه اندازی می‌کند و در غیر این صورت JavaScript کامپایل شده را بارگزاری می‌نماید.


کد موجود در piratebadge.dart

void main() {
  // Your app starts here.
}

- این فایل شامل تابع main می‌باشد که تنها نقطه ورود به application است. تگ <script> موجود در piratebadge.html برنامه را با فراخوانی این تابع راه اندازی می‌کند.

- تابع main() یک تابع سطح بالا یا top-level می‌باشد.

- متغیرها و توابع top-level عناصری هستند که خارج از ساختار تعریف کلاس ایجاد می‌شوند.

جهت اجرای برنامه در Dart Editor بر روی piratebadge.html کلیک راست نمایید و گزینه Run in Dartium را اجرا کنید. این فایل توسط Dartium اجرا می‌شود و تابع main() را فراخوانی می‌کند و صفحه ای همانند شکل زیر را نمایش می‌دهد.


گام دوم: افزودن فیلد input

توجه داشته باشید که در این مرحله یا می‌توانید تغییرات مورد نظر خود را در طی آموزش بر روی پوشه‌ی 1-blankbadge اعمال کنید و یا به پوشه‌های تهیه شده در نمونه کد موجود در همین پروژه مراجعه نمایید.

در این مرحله یک تگ <input> به تگ <div class=”widgets”> اضافه کنید.

...
<div>
  <div>
    <input type="text" id="inputName" maxlength="15">
  </div>
</div>
...

سپس کتابخانه dart:html را به ابتدای فایل piratebadge.dart اضافه کنید.

import 'dart:html';

توضیحات

- دستور فوق کلاس‌ها و Resource ‌های موجود در کتابخانه dart:html را اضافه می‌کند.

- از حجیم شدن کدهای خود نگران نباشید، زیرا فرایند کامپایل کدهای اضافی را حذف خواهد کرد.

- کتابخانه dart:html شامل کلاسهایی جهت کار با عناصر DOM و توابعی جهت دسترسی به این عناصر می‌باشد.

- در مباحث بعدی یاد می‌گیرید که با استفاده از کلمه کلیدی show فقط کلاسهایی را import کنید که به آن نیاز دارید.

- اگر کتابخانه ای در هیچ بخش کد استفاده نشود، خود Dart Editor به صورت warning اخطار می‌دهد و می‌توانید آن را حذف کنید.


دستور زیر را در تابع main بنویسید تا رویداد مربوط به ورود اطلاعات  در فیلد input را مدیریت نمایید.

void main() {
  querySelector('#inputName').onInput.listen(updateBadge);
}

توضیحات

- تابع querySelector() در کتابخانه dart:html تعریف شده است و یک المنت DOM را جستجو می‌نماید. پارامتر ورودی آن یک selector می‌باشد که در اینجا فیلد input را توسط #inputName جستجو نمودیم که یک ID Selector می‌باشد.

- نوع خروجی این متد یک شی از نوع DOM می‌باشد.

- تابع onInput.Listen() رویدادی را برای پاسخگویی به ورود اطلاعات در فیلد input تعریف می‌کند. زمانی که کاربر اطلاعاتی را وارد نماید، تابع updateBadge فراخوانی می‌گردد.

- رویداد input زمانی رخ می‌دهد که کاربر کلیدی را از صفحه کلید فشار دهد.

- رشته‌ها همانند جاوا اسکریپت می‌توانند در " یا '  قرار بگیرند.


تابع زیر را به صورت top-level یعنی خارج از تابع main تعریف کنید.

...

void updateBadge(Event e) { 
  querySelector('#badgeName').text = e.target.value;
}

توضیحات

- این تابع محتوای المنت badgeName را به محتوای وارد شده در فیلد input تغییر می‌دهد.

- پارامتر ورودی این تابع شی e از نوع Event می‌باشد و به همین دلیل می‌توانیم این تابع را یک Event Handler بنامیم.

- e.target به شی ای اشاره می‌کند که موجب رخداد رویداد شده است و در اینجا همان فیلد input می‌باشد

- با نوشتن کد فوق یک warning را مشاهده می‌کنید که بیان می‌کند ممکن است خصوصیت value برای e.target وجود نداشته باشد. برای حل این مسئله کد را بصورت زیر تغییر دهید.

...

void updateBadge(Event e) { 
  querySelector('#badgeName').text = (e.target as InputElement).value;
}

توضیحات

- کلمه کلیدی as به منظور تبدیل نوع استفاده می‌شود که e.target را به یک InputElement تبدیل می‌کند.

همانند گام اول برنامه را اجرا کنید و نتیجه را مشاهده نمایید. با تایپ کردن در فیلد input به صورت همزمان در کادر قرمز رنگ نیز نتیجه تایپ را مشاهده می‌نمایید. 

مطالب
تهیه پردازنده‌های سفارشی برای افزونه XMLWorker کتابخانه iTextSharp
پیشتر مطلب «تهیه پردازنده‌های سفارشی برای HTMLWorker کتابخانه iTextSharp» را در این سایت مطالعه کرده‌اید. از آنجائیکه افزونه HTMLWorker منسوخ شده است و دیگر پشتیبانی نخواهد شد، باید کدهای فعلی را به افزونه XMLWorker منتقل کرد. مقدمه‌ای را در این زمینه در مطلب «تبدیل HTML فارسی به PDF با استفاده از افزونه‌ی XMLWorker کتابخانه‌ی iTextSharp» می‌توانید مطالعه نمائید.
در ادامه قصد داریم همان امکان پشتیبانی از تصاویر base64 مدفون شده در صفحات HTML را به کتابخانه XMLWorker نیز اضافه کنیم.


تهیه پردازنده‌های سفارشی تگ‌های HTML جهت افزونه XMLWorker

در اینجا کار با ارث بری از کلاس AbstractTagProcessor شروع می‌شود. سپس کافی است تا متد End این کلاس پایه را override کرده و تگ سفارشی خود را پردازش کنیم. نمونه‌هایی از این نوع پردازنده‌ها را در پوشه itextsharp.xmlworker\iTextSharp\tool\xml\html سورس‌های iTextSharp می‌توانید ملاحظه کنید و جهت ایده گرفتن بسیار مناسب می‌باشند.
یکی از این پردازنده‌های پیش فرض موجود در افزونه XMLWorker کار پردازش تگ‌های img را انجام می‌دهد و در کلاس iTextSharp.tool.xml.html.Image قرار گرفته است. این پردازنده به صورت پیش فرض تصاویر base64 را پردازش نمی‌کند. برای رفع این مشکل می‌توان به نحو ذیل عمل کرد:
    public class CustomImageTagProcessor : iTextSharp.tool.xml.html.Image
    {
        public override IList<IElement> End(IWorkerContext ctx, Tag tag, IList<IElement> currentContent)
        {
            IDictionary<string, string> attributes = tag.Attributes;
            string src;
            if (!attributes.TryGetValue(HTML.Attribute.SRC, out src))
                return new List<IElement>(1);

            if (string.IsNullOrEmpty(src))
                return new List<IElement>(1);

            if (src.StartsWith("data:image/", StringComparison.InvariantCultureIgnoreCase))
            {
                // data:[<MIME-type>][;charset=<encoding>][;base64],<data>
                var base64Data = src.Substring(src.IndexOf(",") + 1);
                var imagedata = Convert.FromBase64String(base64Data);
                var image = iTextSharp.text.Image.GetInstance(imagedata);

                var list = new List<IElement>();
                var htmlPipelineContext = GetHtmlPipelineContext(ctx);
                list.Add(GetCssAppliers().Apply(new Chunk((iTextSharp.text.Image)GetCssAppliers().Apply(image, tag, htmlPipelineContext), 0, 0, true), tag, htmlPipelineContext));
                return list;
            }
            else
            {
                return base.End(ctx, tag, currentContent);
            }
        }
    }
با ارث بری از کلاس پردازنده پیش فرض تگ‌های تصاویر یا iTextSharp.tool.xml.html.Image شروع و سپس متد End آن‌را  تحریف کرده‌ایم.
در اینجا اگر src یک تگ img با الگوی تصاویر base64 شروع شده باشد، تصویر آن استخراج و تبدیل به وهله‌ای از تصاویر استاندارد iTextSharp می‌شود. سپس این تصویر در اختیار htmlPipelineContext قرار داده خواهد شد و یا اگر این تصویر از نوع base64 نباشد، همان متد base.End کلاس پایه، جهت پردازش آن کفایت می‌کند.


استفاده از یک پردازنده تگ سفارشی HTML افزونه XMLWorker

تا اینجا یک پردازنده جدید تصاویر را ایجاد کرده‌ایم. برای اینکه XMLWorker را از وجود آن مطلع کنیم، نیاز است آن‌را به درون htmlPipeline این افزونه تزریق نمائیم که کدهای کامل آن‌را در ذیل مشاهده می‌کنید:
            using (var doc = new Document(PageSize.A4))
            {
                var writer = PdfWriter.GetInstance(doc, new FileStream("test.pdf", FileMode.Create));
                doc.Open();
                var html = @"<img src='data:image/png;base64,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' width='62' height='80' style='float: left; margin-right: 28px;' />";

                var tagProcessors = (DefaultTagProcessorFactory)Tags.GetHtmlTagProcessorFactory();
                tagProcessors.RemoveProcessor(HTML.Tag.IMG); // remove the default processor
                tagProcessors.AddProcessor(HTML.Tag.IMG, new CustomImageTagProcessor()); // use our new processor

                var cssResolver = new StyleAttrCSSResolver();
                cssResolver.AddCss(@"code { padding: 2px 4px; }", "utf-8", true);
                var charset = Encoding.UTF8;
                var hpc = new HtmlPipelineContext(new CssAppliersImpl(new XMLWorkerFontProvider()));
                hpc.SetAcceptUnknown(true).AutoBookmark(true).SetTagFactory(tagProcessors); // inject the tagProcessors
                var htmlPipeline = new HtmlPipeline(hpc, new PdfWriterPipeline(doc, writer));
                var pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
                var worker = new XMLWorker(pipeline, true);
                var xmlParser = new XMLParser(true, worker, charset);
                xmlParser.Parse(new StringReader(html));
            }
            Process.Start("test.pdf");
در اینجا ابتدا لیست پردازنده‌های پیش فرض افزونه XMLWorker را دریافت و سپس پردازنده تگ img آن‌را حذف و با نمونه جدید خود جایگزین کرده‌ایم. در ادامه این لیست تغییر یافته به درون HtmlPipelineContext تزریق شده‌است تا بجای DefaultTagProcessorFactory اصلی مورد استفاده قرار گیرد.
مطالب
HTML5 Offline Web Applications
وب به سمتی پیش رفته که کاربران زیادی از تلفن همراه ، تبلت‌ها و دیگر عامل ها(Agent) جهت مرور صفحات وب استفاده می‌کنند. در نتیجه تعداد کاربرانی که مدام در حال حرکت به مرور صفحات وب و استفاده از سرویس‌های برخط می‌پردازند رو به افزایش است. برنامه‌های خارج از شبکه‌ی HTML 5 یا به عبارتی HTML5 Offline Web Applications توسعه دهنگان را قادر می‌سازد تا نرم افزار‌های تحت وبی ارائه دهند که در حالت قطع بودن اینترنت و یا شبکه همچنان به سرویس دادن به کاربران ادامه دهد. دیگر اینگونه نیست که وب تنها در حالت برخط بودن معنی پیدا کند. یک نرم افزار مدیریت هزینه‌ی تحت وب را بررسی کنید که روی تلفن همراه شما اجرا شده ، در محلی که دسترسی به اینترنت نیست قصد استفاده از آن را دارید. چه قدر خوب می‌شود که این نرم افزار به گونه ای پیاده سازی شده باشد که بتواند در حالت برون خطی (Offline) به سرویس دهی ادامه دهد به طور مثال قادر به ذخیره‌ی داده‌های شما به صورت برون خط و همزمان سازی آنها پس از اتصال به اینترنت باشد.
برنامه‌های تحت وب برون خط با کمک قابلیتی به نام نهانگاه برنامه (Application Cache) کار می‌کنند. این قابلیت می‌تواند تمامی بخش‌های سایت را به شکل برون خط و خارج از شبکه، ذخیره کند. با به کار گیری این ویژگی می‌توان تمامی فایل‌های ایستا (JavaScript , HTML , CSS , Image) بر روی ابزار کاربر ذخیره نمود.

 نهانگاه برنامه چه تفاوتی با نهانگاه مرورگر (Browser cache) دارد ؟

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

برای استفاده از این ویژگی اولین باید فایلی به نام cache.manifest ایجاد کرد. این فایل باید با نوع محتوای (Mime type) مناسب برای کاربر ارسال شود. این فایل یک فایل متنی می‌باشد که لیست فایل‌های مورد نظر ما با قواعد خاصی در آن قرار می‌گیرد.
همیشه اولین خط این فایل عبارت CACHE MANIFEST قرار دارد ، پس از این خط عبارت CACHE: وارد می‌شود و فایل‌های مد نظر ما لیست می‌شود.
CACHE MANIFEST
 
#Cache Section
CACHE:
/Content/Images/icons-18-white.png
/Content/Images/icons-36-white.png
/Content/Images/ajax-loader.png
/Content/css
/Scripts/js
ذخیره‌ی برخی فایل‌ها روی سیستم کاربر ضرورتی ندارد ، برای مثال اسکریپتی که از یک وب سرویس برخط اطلاعات آب و هوا را دریافت می‌کند در حالت Offline کاربردی ندارد. برای مشخص کردن این فایل‌ها یک لیست سفید آماده می‌کنیم.
NETWORK
webService.Js
در بخش معرفی فایل‌های آفلاین بازید همه‌ی فایل‌های مد نظر ما معرفی شوند اما در لیست سفید با گذاشتن ستاره به مرورگر اعلام می‌کنیم که هر فایل و مسیری که در بخش قبلی (CACHE) نیامده را همواره از سرور درخواست کن. درج ستاره در بخش NETWORK ضروری است زیرا همه‌ی آدرس‌های سایت باید در یکی از بخش‌های cache.manifest قرار بگیرد ، اگر آدرسی در cache.manifest قرار نگیرد هیچگاه بارگذاری نخواهد شد.
در فایل cache.manifest می‌توان یادداشت هم اضافه کرد ، تمامی کاراکتر هایی که بعد از # قرار گیرند پردازش نمی‌شوند. یادداشت‌ها مفید هستند ، می‌تونید اطلاعات نسخه‌ی فعلی فایل cache.manifest را در آنها قرار دهید.
مثال :
CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
css
images/
stylesheet
.logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
login.php
/myapi
http://api.twitter.com
گفته شد فایل Cache.manifest باید با نوع محتوای مناسب ارسال شود ، برای تعیین نوع محتوا در وب سرور apache باید خط زیر به فایل .htaaccess اضافه شود :
AddType text/cache-manifest .appcache
در ASP.NET Web Forms می‌توان از یک Generic Handler برای ارسال فایل با نوع محتوای مناسب استفاده کرد : 
using System.Web;
 
namespace JavaScriptReference {
 
    public class Manifest : IHttpHandler {
 
        public void ProcessRequest(HttpContext context) {
            context.Response.ContentType = "text/cache-manifest";
            context.Response.WriteFile(context.Server.MapPath("Manifest.txt"));
        }
 
        public bool IsReusable {
            get {
                return false;
            }
        }
    }
}
یا می‌توان در یک فایل ASPX به صورتی دستی نوع محتوا را مشخص کرد : 
CACHE MANIFEST

# Version Jesus 3!

CACHE:

index.html
js/Custom.js
js/Utility.js
styles/index.css
styles/kendo.common.min.css
styles/BlueOpal/loading.gif
styles/BlueOpal/slider-h.gif
styles/BlueOpal/slider-v.gif

NETWORK:
*

<%@ Page Language="VB" ContentType="text/cache-manifest"  ResponseEncoding="utf-8" AutoEventWireup="true" CodeFile="manifest.aspx.vb" Inherits="Configuration_manifest" %>
در ASP.NET MVC علاوه بر اینکه می‌توان دستی نوع محتوای Response را مشخص کرد، می‌توان یک ActionResult منحصر به فرد ایجاد کرد. یک نمونه پیاده سازی شده را اینجا مشاهده کنید.
پس از انجام همه‌ی این پیش نیاز‌ها باید فایل cache manifest را به خصیصه‌ی manifest برچسب html ارجاع دهیم. 
<!DOCTYPE html>
<html manifest="/manifest.aspx">
اکنون قسمت‌های مد نظر سایت ما در حالت عدم دسترسی به شبکه نیز قابل استفاده می‌باشد. حتی این ویژگی در حالت برخط صفحات ما با سرعت بالاتری بارگذاری شوند.
خصیصه‌ی manifest تمامی فایل‌های HTML باید مقدار گیرد در غیر این صورت ممکن است صفحات درون نهانگاه قرار نگیرند.  
برای بررسی مرورگرهایی که این ویژگی را پشتیبانی می‌کنند این لینک  را مشاهده کنید.
مطالب
Blogger auto poster

حتما مطالب «خلاصه اشتراک‌های روز xyz» را دیده‌اید و شاید گفته باشید که ... عجب حالی دارد؛ هر شب ساعت 12، یک لیست مرتب را ارسال می‌کند! باید خدمتتان عرض کنم که بیشتر از 90 درصد کار تهیه و ارسال این لیست‌ها، خودکار است؛ منهای روزی چندبار کلیک کردن بر روی لینک Share در عناوین Google reader و همین! (البته اخیرا شده ارسال Public به GooglePlus)
برای اتوماسیون این‌کار، یک برنامه را تهیه کرده‌ام که این‌کارها را انجام می‌دهد:
هر چند دقیقه یکبار (قابل تنظیم است)، فید حاصل از مطالب به اشتراک گذاشته شده در Google reader را می‌خواند و ذخیره می‌کند (یا موارد عمومی گوگل پلاس را). بانک اطلاعاتی آن هم یک فایل XML ساده است. از این جهت که روزی حدودا 20 رکورد یا کمتر، نیازی به بانک اطلاعاتی آنچنانی ندارد. سپس آخر هر شب، تمام این‌ها را تبدیل به یک لیست Html ایی کرده و به صورت خودکار در این بلاگ ارسال می‌کند.
بنابراین تنها کاری که من به صورت دستی انجام می‌دهم کلیک کردن بر روی لینک Share در Google reader است (یا ارسال به GooglePlus). سپس این‌ها به صورت خودکار به فید مرتبط اضافه می‌شوند و مابقی آن هم که عنوان شد.



از کجا می‌شود آن‌را دریافت کرد؟
این پروژه به صورت سورس باز از آدرس زیر قابل دریافت است:


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


چگونه باید آن‌را تنظیم کرد؟
کلیه تنظیمات این برنامه و یا سرویس آن، در فایل .config همراه آن قرار دارند. این فایل xml ایی را با مثلا notepad باز کرده و تغییرات لازم را در آن اعمال کنید (یا از طریق رابط کاربری برنامه هم قابل انجام است):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="BlogUrl" value="https://www.blogger.com/feeds/blogId/posts/default" />
<add key="UserName" value="name@gmail.com" />
<add key="Password" value="myPass..." />
<add key="PostAt" value="00:05" />
<add key="FeedToParse" value="http://feeds2.feedburner.com/VahidsSharedItemsInGoogleReader" />
<add key="DBName" value="D:\Prog\db.xml"/>
<add key="Tag" value="News, daily news" />
<add key="Title" value="خلاصه اشتراک‌های روز " />
<add key="UsePersianDate" value="true" />
<add key="ErrorsLogFile" value="D:\Prog\errors.log"/>
<add key="ReadSitesDataIntervalMin" value="15"/>
<add key="GooglePlusUserId" value="105013528531611201860"/>

<!--proxy settings-->
<add key="IsProxyEnabled" value="false"/>
<add key="ProxyServerAddress" value="127.0.0.1"/>
<add key="ProxyServerPort" value="8080"/>
<add key="ProxyServerUserName" value="user1"/>
<add key="ProxyServerPassword" value="pass1"/>
</appSettings>
</configuration>

  • BlogUrl آدرس ویژه وبلاگ شما در بلاگر است. این مورد تنها نکته‌ی مهم تنظیمات جاری است:
برای یافتن آن به آدرس http://www.blogger.com/home مراجعه کنید. روی لینک «ویرایش پیام‌ها» که کلیک نمائید چیزی شبیه به لینک زیر خواهد بود:
http://www.blogger.com/posts.g?blogID=number
این عدد ذکر شده پس از blogId همان عددی است که باید در آدرس BlogUrl خود جایگزین کنید.
  • نام کاربری و کلمه عبور، همان آدرس ایمیل و کلمه عبور جی‌میل شما هستند (جهت ارسال خودکار مطلب به بلاگ شما نیاز است).
  • PostAt مشخص می‌کند که در چه ساعت و دقیقه‌ای باید ارسال روزانه صورت گیرد. لطفا به فرمت آن دقت کنید. به همین شکل باید باشد و هر زمانی که آن‌را تنظیم کنید، تنها اطلاعات یک روز قبل را ارسال خواهد کرد. مهم نیست 5 دقیقه بامداد باشد یا 5 صبح یا 6 عصر. بدیهی است مطالب امروز هم در PostAt روز بعد ارسال می‌شوند و همینطور الی آخر. (البته در خود برنامه امکان انتخاب موارد دلخواه و سپس ارسال دستی آن‌ها هم هست. حالت خودکار همانی است که توضیح داده شد.)
  • FeedToParse آدرس فید اشتراک‌های شما در گوگل ریدر است که قرار است اطلاعات آن دریافت و ذخیره و سپس خلاصه آن ارسال شود (این مورد اختیاری است؛ از این جهت که گوگل آن‌را اخیرا غیرفعال کرده).
  • DBName نام و مسیر بانک اطلاعاتی برنامه است. فقط کافی است این مسیر را اصلاح کنید. بانک اطلاعاتی هم به صورت خودکار در زمان اولین بار اجرای آن ساخته می‌شود و نیاز به تنظیم دیگری ندارد (همان پیش فرض آن کافی است).
  • Tag در اینجا نام برچسب‌هایی است که قرار است مطلب خودکار ارسالی تحت آن‌ها یا در گروه آن‌ها در وبلاگ شما ارسال شوند. بهتر است حداقل یک مورد را ذکر کنید. بیشتر از یک مورد را باید با کاما از هم جدا کنید.
  • Title عنوان مطلب خودکاری است که در سایت شما نمایش داده می‌شود. برنامه به صورت خودکار تاریخ روز قبل را هم به آن اضافه می‌کند. (حداقل فعلا اینطور است)
  • UsePersianDate در اینجا تاریخ شمسی و راست به چپ بودن خروجی را تعیین می‌کند. اگر نیازی به آن نداشتید،‌ آن‌را false کنید.
  • ErrorsLogFile محل فایل log خطاهای برنامه را مشخص می‌کند. اگر در حین کار برنامه خطایی رخ دهد در این فایل که مسیر آن‌را نیاز است اصلاح کنید، ثبت خواهد شد (پیش فرض آن کافی است).
  • ReadSitesDataIntervalMin مشخص می‌کند که هر چند دقیقه یکبار فید ذکر شده در قسمت FeedToParse باید بررسی شود (یا موارد گوگل پلاس شما بررسی شوند). عموما هر 10 دقیقه یکبار کافی است.
  • GooglePlusUserId همان شماره کاربری شما در گوگل پلاس است. برای یافتن آن باید به صفحه «پروفایل» در گوگل پلاس، مراجعه کرده و به آدرس آن دقت نمود: https://plus.google.com/u/0/userId/posts . این userId را در تنظیمات برنامه وارد کنید (فقط موارد Public شما توسط برنامه دریافت خواهند شد).
سایر موارد هم کاملا مشخص است و نیاز به توضیحات خاصی ندارند.


چند نکته
در این برنامه توضیحات شما در حین به اشتراک گذاری در گوگل پلاس نسبت به توضیحات انگلیسی اصلی آن ارجحیت دارد.
توضیحی منسوخ شده ...!
اگر به طراحی گوگل ریدر دقت کرده باشید، حداقل دو لینک به اشتراک گذاری دارد. به اشتراک گذاری ساده و به اشتراک گذاری به همراه کامنت. اگر مورد دوم را انتخاب کنید و توضیحی فارسی را ارائه دهید (اصطلاحا annotation اضافه کنید)، سرویس برنامه جاری توانایی تشخیص آن‌را داشته و از آن بجای عنوان لینک‌ مورد نظر استفاده خواهد کرد. استفاده مهم آن می‌تواند تبدیل عناوین انگلیسی به فارسی جهت ارائه در سایت باشد. (این مورد اختیاری است)
روش دوم: زمانیکه گزینه share with note را انتخاب کنید،‌ اگر بر روی عنوان مطلب ظاهر شده کلیک نمائید،‌ قابل ویرایش می‌شود (نکته‌ای که در نگاه اول مشخص نیست).


مطالب
تبدیل زیرنویس‌های یوتیوب به فایل srt

تعدادی از ویدیوهای یوتیوب، خصوصا مواردی که از Google videos به یوتیوب منتقل شده‌اند، دارای زیر نویس هستند. این زیر نویس هم فرمت جالبی دارد:
<?xml version="1.0" encoding="UTF-8"?>
<transcript>
<text start="23.49" dur="5.97">
&amp;gt;&amp;gt;Commentator: We have here today, Steve with
us whom I met not too long ago at something
</text>
...
</transcript>
و از هر لحاظ بهتر است از فرمت srt متداول (خصوصا از لحاظ قابلیت بهتر parse آن) ولی ... این فرمت xml ایی، متداول نیست و بهتر است جهت استفاده از آن در برنامه‌های پخش کننده فایل‌های ویدیویی، به srt تبدیل شود.
ابتدا شاید بپرسید که این‌ها را چگونه می‌توان دریافت کرد. روش کار به صورت زیر است:
http://video.google.com/timedtext?hl=en&v=Id&lang=en

در قسمت‌های hl و lang ، زبان مورد نظر قرار می‌گیرد و قسمت v همان id معروف ویدیوی یوتیوب مورد نظر است. برای مثال اگر لینک ویدیوی ما http://www.youtube.com/watch?v=-PA-buwI3q4 باشد، لینک زیرنویس انگلیسی هماهنگ با آن http://video.google.com/timedtext?hl=en&v=-PA-buwI3q4&lang=en خواهد بود.

البته زمان‌های این فایل xml یک نکته‌ی خاص گوگلی دارد!
اگر عدد 3.4 را مشاهده کردید یعنی سه ثانیه و 400 میلی ثانیه
اگر عدد 3.04 را مشاهده کردید یعنی سه ثانیه و 40 میلی ثانیه
به عبارتی با قسمت اعشاری آن باید به صورت رشته برخورد کنید. اگر طول آن یک بود، دو صفر و اگر طول آن 2 بود، 1 صفر در جلوی آن باید قرار گیرد تا قابل استفاده شود.

کلاس تهیه شده زیر کار تبدیل Xml گوگل را به Srt انجام می‌دهد:
using System;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;

namespace YtSubs
{
public class GoogleXmlToSrt
{
public string ConvertXmlToSrt(string url)
{
var transcript = loadTranscript(url);

var srtBuf = new StringBuilder();
var lineNumber = 0;

foreach (var item in transcript.Root.Elements("text"))
{
lineNumber++;
srtBuf.Append(lineNumber.ToString());

var timeLine = getTimeLine(item);
srtBuf.AppendFormat("{0}{1} --> {2}{3}", Environment.NewLine, timeLine.Item1, timeLine.Item2, Environment.NewLine);

var msg = decodeText(item);
srtBuf.AppendLine(msg + Environment.NewLine);
}
return srtBuf.ToString();
}

private static XDocument loadTranscript(string url)
{
var transcript = XDocument.Load(url);

if (transcript.Root == null)
throw new InvalidOperationException("couldn't load received data.");
return transcript;
}

private static string decodeText(XElement text)
{
var textValue = text.Value.Split('\n');
var msg = textValue.Aggregate(
string.Empty, (current, line) =>
current + (HttpUtility.HtmlDecode(line.Trim()) + Environment.NewLine)
);
return msg.Trim();
}

private static int fractionalPart(string data)
{
var idx = data.IndexOf(".");
if (idx == -1) return 0;
var fractionalPart = data.Substring(idx + 1);
if (fractionalPart.Length == 1) //3.4 --> 3.400
return int.Parse(fractionalPart + "00");

if (fractionalPart.Length == 2) //3.04 --> 3.040
return int.Parse(fractionalPart + "0");

return int.Parse(fractionalPart.Substring(0, 3));
}

private Tuple<string, string> getTimeLine(XElement text)
{
var startTs = getStartTs(text);
TimeSpan endTs = getEndTs(text, startTs);
return new Tuple<string, string>(timeSpanToString(startTs), timeSpanToString(endTs));
}

private static TimeSpan getStartTs(XElement text)
{
var startData = text.Attribute("start");
if (startData == null)
throw new InvalidOperationException("This is not a valid subtitle file.");

var start = startData.Value;
var startTs = new TimeSpan(0, 0, 0, (int)Math.Truncate(double.Parse(start)), fractionalPart(start));
return startTs;
}

private static TimeSpan getEndTs(XElement text, TimeSpan startTs)
{
TimeSpan endTs;
var durData = text.Attribute("dur");
if (durData == null)
{
endTs = startTs + new TimeSpan(0, 0, 0, 3, 0);
}
else
{
var dur = durData.Value;
var durTs = new TimeSpan(0, 0, 0, (int)Math.Truncate(double.Parse(dur)), fractionalPart(dur));
endTs = startTs + durTs;
}
return endTs;
}

private static string timeSpanToString(TimeSpan lineTs)
{
return string.Format("{0}:{1}:{2},{3}",
lineTs.Hours.ToString("D2"),
lineTs.Minutes.ToString("D2"),
lineTs.Seconds.ToString("D2"),
lineTs.Milliseconds.ToString("D3"));
}
}
}
و یک نمونه از استفاده آن می‌تواند به شکل زیر باشد:
using System.IO;

namespace YtSubs
{
class Program
{
static void Main(string[] args)
{
var url = "http://video.google.com/timedtext?hl=en&v=-PA-buwI3q4&lang=en";
//var url = "subtitle.xml";
var srtBuf = new GoogleXmlToSrt().ConvertXmlToSrt(url);
File.WriteAllText("-PA-buwI3q4.srt", srtBuf);
}
}
}


مطالب تکمیلی:
همه چیز درباره زیرنویس ویدئوهای گوگل

مطالب
معرفی Microsoft.Data.dll یا WebMatrix.Data.dll

مایکروسافت اخیرا علاوه بر تکمیل ORM های خود مانند LINQ to SQL و همچنین Entity framework ، لایه دیگری را نیز بر روی ADO.NET جهت کسانی که به هر دلیلی دوست ندارند با ORMs کار کنند و از نوشتن کوئری‌های مستقیم SQL لذت می‌برند،‌ ارائه داده است که Microsoft.Data library نام دارد و از قابلیت‌های جدید زبان سی شارپ مانند واژه‌ کلیدی dynamic استفاده می‌کند.

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

مراحل استفاده از Microsoft.Data library:
الف) این اسمبلی جدید به همراه پروژه WebMatrix ارائه شده است. بنابراین ابتدا باید آن‌را دریافت کنید: +
لازم به ذکر است که این کتابخانه اخیرا به WebMatrix.Data.dll تغییر نام یافته است. (اگر وب را جستجو کنید فقط به Microsoft.Data.dll اشاره شده است)

ب) پس از نصب، ارجاعی را از اسمبلی WebMatrix.Data.dll به پروژه خود اضافه نمائید. این اسمبلی در صفحه‌ی Add References ظاهر نمی‌شود و باید کامپیوتر خود را برای یافتن آن جستجو کنید که عموما در آدرس زیر قرار دارد:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies\WebMatrix.Data.dll

ج) اتصال به بانک اطلاعاتی
پیش فرض اصلی این کتابخانه بانک اطلاعاتی SQL Server CE است. بنابراین اگر قصد استفاده از پروایدرهای دیگری را دارید باید به صورت صریح آن‌را ذکر نمائید:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="systemData:defaultProvider" value="System.Data.SqlClient" />
</appSettings>
<connectionStrings>
<add name="Northwind"
connectionString="Data Source=(local);Integrated Security = true;Initial Catalog=Northwind"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>

این تعاریف در فایل web.config و یا app.config برنامه وب یا ویندوزی شما قرار خواهند گرفت.

د) نحوه‌ی تعریف کوئری‌ها و دریافت اطلاعات
using System;
using WebMatrix.Data;

namespace TestMicrosoftDataLibrary
{
class Program
{
static void Main(string[] args)
{
getProducts();

Console.Read();
Console.WriteLine("Press a key ...");
}

private static void getProducts()
{
using (var db = Database.Open("Northwind"))
{
foreach (var product in db.Query("select * from products where UnitsInStock < @0", 20))
{
Console.WriteLine(product.ProductName + " " + product.UnitsInStock);
}
}
}
}
}
پس از افزودن ارجاعی به اسمبلی WebMatrix.Data و مشخص سازی رشته‌ی اتصالی به بانک اطلاعاتی، استفاده از آن جهت دریافت اطلاعات کوئری‌ها همانند چند سطر ساده‌ی فوق خواهد بود که از امکانات dynamic زبان سی شارپ 4 استفاده می‌کند؛ به این معنا که product.ProductName و product.UnitsInStock در زمان اجرا مورد ارزیابی قرار خواهند گرفت.
همچنین نکته‌ی مهم دیگر آن نحوه‌ی تعریف پارامتر در آن است (همان 0@ ذکر شده) که نسبت به ADO.NET کلاسیک به شدت ساده شده‌است (و نوشتن کوئری‌های امن و SQL Injection safe را تسهیل می‌کند).
در اینجا Database.Open کار گشودن name ذکر شده در فایل کانفیگ برنامه را انجام خواهد داد. اگر بخواهید این تعاریف را در کدهای خود قرار دهید (که اصلا توصیه نمی‌شود)، می‌توان از متد Database.OpenConnectionString استفاده نمود.

یا مثالی دیگر: استفاده از LINQ حین تعریف کوئری‌ها:
private static void getCustomerFax()
{
using (var db = Database.Open("Northwind"))
{
var product = db.Query("SELECT * FROM [Customers] WHERE City=@0", "Paris").FirstOrDefault();
if (product != null)
Console.WriteLine(product.Fax);
else
Console.WriteLine("not found.");
}
}

ه) اجرای کوئری‌ها بر روی بانک اطلاعاتی
private static void ExecQuery()
{
using (var db = Database.Open("Northwind"))
{
int affectedRecords = db.Execute("UPDATE [Customers] SET fax = fax + '*' WHERE City = @0", "Paris");
Console.WriteLine("Affected records: {0}", affectedRecords);
}
}

با استفاده از متد Execute آن می‌توان کوئری‌های دلخواه خود را بر روی بانک اطلاعاتی اجرا کرد. خروجی آن تعداد رکورد تغییر کرده است.

و) نحوه‌ی اجرای یک رویه ذخیره شده و نمایش خروجی آن
private static void ExecSPShowResult()
{
using (var db = Database.Open("Northwind"))
{
var customer = db.Query("exec CustOrderHist @0", "ALFKI").FirstOrDefault();
if (customer != null)
{
Console.WriteLine(customer.ProductName);
}
}
}
در این مثال رویه ذخیره شده CustOrderHist در بانک اطلاعاتی Northwind اجرا گردیده و سپس اولین خروجی آن نمایش داده شده است.

ز) اجرای یک تابع و نمایش خروجی آن
private static void useFuncs()
{
using (var db = Database.Open("Northwind"))
{
var query = db.Query("SELECT dbo.FN_GET_CATEGORY_TREE(@0) as Rec1", 3);
foreach(var tree in query)
{
Console.WriteLine(tree.Rec1);
}
}
}
در اینجا تابع FN_GET_CATEGORY_TREE موجود در بانک اطلاعاتی Northwind انتخاب گردیده و سپس خروجی آن به کمک یک نام مستعار (برای مثال Rec1) نمایش داده شده است.

سؤال : آیا WebMatrix.Data.dll بهتر است یا استفاده از ORMs ؟

در اینجا چون از قابلیت‌های داینامیک زبان سی شارپ 4 استفاده می‌شود، کامپایلر درکی از اشیاء خروجی و خواص آن‌ها برای مثال tree.Rec1 (در مثال آخر) ندارد و تنها در زمان اجرا است که مشخص می‌شود آیا یک چنین ستونی در خروجی کوئری وجود داشته است یا خیر. اما حین استفاده از ORMs این طور نیست و Schema یک بانک اطلاعاتی پیشتر از طریق نگاشت‌های جداول به اشیاء دات نتی، به کامپایلر معرفی می‌شوند و همین امر سبب می‌شود تا اگر ساختار بانک اطلاعاتی تغییر کرد، پیش از اجرای برنامه و در حین کامپایل بتوان مشکلات را دقیقا مشاهده نمود و سپس برطرف کرد.
ولی در کل استفاده از این کتابخانه نسبت به ADO.NET کلاسیک بسیار ساده‌تر بوده، می‌توان اشیاء و خواص آن‌ها را مطابق نام جداول و فیلدهای بانک اطلاعاتی تعریف کرد و همچنین تعریف پارامترها و برنامه نویسی امن نیز در آن بسیار ساده‌تر شده است.

برای مطالعه بیشتر:
Introduction to Microsoft.Data.dll

اشتراک‌ها
عدم پشتیبانی W2G از ویژوال استودیو
قابلیت Windwos to Go که در ویندوز 8 نسخه Enterprise وجود دارد توانایی هایی جالبی را ارائه میکند که شما می‌توانید ویندوز 8 را به صورت همراه در یک فلش نصب کنید. اما متاسفانه اگر فلش شما از نوع فلش‌های تایید شده توسط مایکروسافت نباشه نمی‌توان ویژوال استودیو و MS-SQL Server را روی آن نصب کرد. پیشنهاد: بنده به این نکته دقت نکردم و حالا دنبال راه حلی برای نصب ویژوال استودیو برروی فلش خود هستم که احتمالا به پرتابل کردن آن دست بزنم، لذا پیشنهاد می‌کنم هرکسی بخواهد این کار را انجام دهد حتما فلش خود را از بین لیست فلش‌های تایید شده مایکروسافت انتخاب کند(این لیست را در لینک مرجع که در لینک این پست قرار دارد می‌توان یافت). موفق باشید.
عدم پشتیبانی W2G از ویژوال استودیو
مطالب
بررسی تغییرات Blazor 8x - قسمت چهاردهم - امکان استفاده از کامپوننت‌های Blazor در برنامه‌های ASP.NET Core 8x
ASP.NET Core 8x به همراه یک IResult جدید به‌نام RazorComponentResult است که توسط آن می‌توان در Endpoint‌های Minimal-API و همچنین اکشن متدهای MVC، از کامپوننت‌های Blazor، خروجی گرفت. این خروجی نه فقط static یا به عبارتی SSR، بلکه حتی می‌تواند تعاملی هم باشد. در این مطلب، جزئیات فعالسازی و استفاده از این IResult جدید را در یک برنامه‌ی Minimal-API بررسی می‌کنیم.


ایجاد یک برنامه‌ی Minimal-API جدید در دات نت 8

پروژه‌ای را که در اینجا پیگیری می‌کنیم، بر اساس قالب استاندارد تولید شده‌ی توسط دستور dotnet new webapi تکمیل می‌شود.


ایجاد یک صفحه‌ی Blazor 8x به همراه مسیریابی و دریافت پارامتر

در ادامه قصد داریم که یک کامپوننت جدید را به نام SsrTest.razor در پوشه‌ی جدید Components\Tests ایجاد کرده و برای آن مسیریابی از نوع page@ هم تعریف کنیم. یعنی نه‌فقط قصد داریم آن‌را توسط RazorComponentResult رندر کنیم، بلکه می‌خواهیم اگر آدرس آن‌را در مرورگر هم وارد کردیم، قابل دسترسی باشد.
به همین جهت یک پوشه‌ی جدید را به نام Components در ریشه‌ی پروژه‌ی Web API جاری ایجاد می‌کنیم، با این محتوا:
برای ایده گرفتن از محتوای مورد نیاز، به «معرفی قالب‌های جدید شروع پروژه‌های Blazor در دات نت 8» قسمت دوم این سری مراجعه کرده و برای مثال قالب ساده‌ترین حالت ممکن را توسط دستور زیر تولید می‌کنیم (در یک پروژه‌ی مجزا، خارج از پروژه‌ی جاری):
dotnet new blazor --interactivity None
پس از اینکار، محتویات پوشه‌ی Components آن‌را مستقیما داخل پوشه‌ی پروژه‌ی Minimal-API جاری کپی می‌کنیم. یعنی در نهایت در این پروژه‌ی جدید Web API، به فایل‌های زیر می‌رسیم:
- فایل Imports.razor_ ساده شده برای سهولت کار با فضاهای نام در کامپوننت‌های Blazor (فضاهای نامی را که در آن وجود ندارند و مرتبط با پروژه‌ی دوم هستند، حذف می‌کنیم).
- فایل App.razor، برای تشکیل نقطه‌ی آغازین برنامه‌ی Blazor.
- فایل Routes.razor برای معرفی مسیریابی صفحات Blazor تعریف شده.
- پوشه‌ی Layout برای معرفی فایل MainLayout.razor که در Routes.razor استفاده شده‌است.

و ... یک فایل آزمایشی جدید به نام Components\Tests\SsrTest.razor با محتوای زیر:
@page "/ssr-page/{Data:int}"

<PageTitle>An SSR component</PageTitle>

<h1>An SSR component rendered by a Minimal-API!</h1>

<div>
    Data: @Data
</div>

@code {

    [Parameter]
    public int Data { get; set; }

}
این فایل، می‌تواند پارامتر Data را از طریق فراخوانی مستقیم آدرس فرضی http://localhost:5227/ssr-page/2 دریافت کند و یا ... از طریق خروجی جدید RazorComponentResult که توسط یک Endpoint سفارشی ارائه می‌شود:




تغییرات مورد نیاز در فایل Program.cs برنامه‌ی Web-API برای فعالسازی رندر سمت سرور Blazor

در ادامه کل تغییرات مورد نیاز جهت اجرای این برنامه را مشاهده می‌کنید:
var builder = WebApplication.CreateBuilder(args);

// ...

builder.Services.AddRazorComponents();

// ...

// http://localhost:5227/ssr-component?data=2
// or it can be called directly http://localhost:5227/ssr-page/2
app.MapGet("/ssr-component",
           (int data = 1) =>
           {
               var parameters = new Dictionary<string, object?>
                                {
                                    { nameof(SsrTest.Data), data },
                                };
               return new RazorComponentResult<SsrTest>(parameters);
           });

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>();
app.Run();

// ...
توضیحات:
- همین اندازه تغییر در جهت فعالسازی رندر سمت سرور کامپوننت‌های Blazor در یک برنامه‌ی ASP.NET Core کفایت می‌کند. یعنی اضافه شدن:
AddRazorComponents ،UseAntiforgery و MapRazorComponents
- در اینجا نحوه‌ی ارسال پارامترها را به یک RazorComponentResult نیز مشاهده می‌کنید.
- در حالت فراخوانی از طریق مسیر endpoint (یعنی فراخوانی مسیر http://localhost:5227/ssr-component در مثال فوق)، خود کامپوننت فراخوانی شده، بدون layout تعریف شده‌ی در فایل App.razor، رندر می‌شود. علت اینجا است که layout برنامه به همراه کامپوننت Router و RouteView آن فعال می‌شود که این دو هم مختص به صفحات دارای مسیریابی Blazor هستند و برای رندر کامپوننت‌های خالص آن بکار گرفته نمی‌شوند. خروجی RazorComponentResult تنها یک static SSR خالص است؛ مگر اینکه فایل blazor.web.js را نیز بارگذاری کند.

یک نکته: اگر در حالت رندر توسط RazorComponentResult، علاقمند به استفاده‌ی از layout هستید، می‌توان از کامپوننت LayoutView داخل یک کامپوننت فرضی به صورت زیر استفاده کرد؛ اما این مورد هم شامل اطلاعات فایل App.razor نمی‌شود:
<LayoutView Layout="@typeof(MainLayout)">
    <PageTitle>Home</PageTitle>

    <h2>Welcome to your new app.</h2>
</LayoutView>


سؤال: آیا در این حالت کامپوننت‌های تعاملی هم کار می‌کنند؟

پاسخ: بله. فقط برای ایده گرفتن، یک نمونه پروژه‌ی تعاملی Blazor 8x را در ابتدا ایجاد کنید و قسمت‌های اضافی AddRazorComponents و MapRazorComponents آن‌را در اینجا کپی کنید؛ یعنی برای مثال جهت فعالسازی کامپوننت‌های تعاملی Blazor Server، به این دو تغییر زیر نیاز است:
// ...

builder.Services.AddRazorComponents()
       .AddInteractiveServerComponents();

// ...

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

// ...
همچنین باید دقت داشت که امکانات تعاملی، به دلیل وجود و دسترسی به یک سطر ذیل که در فایل Components\App.razor واقع شده، اجرایی می‌شوند:
<script src="_framework/blazor.web.js"></script>
و همانطور که عنوان شد، اگر از روش new RazorComponentResult استفاده می‌شود، باید این سطر را به صورت دستی اضافه‌کرد؛ چون به همراه رندر layout تعریف شده‌ی در فایل App.razor نیست. برای مثال فرض کنید کامپوننت معروف Counter را به صورت زیر داریم که حالت رندر آن به InteractiveServer تنظیم شده‌است:
@rendermode InteractiveServer

<h1>Counter</h1>

<p role="status">Current count: @_currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int _currentCount;

    private void IncrementCount()
    {
        _currentCount++;
    }

}
در این حالت پس از تعریف endpoint زیر، خروجی آن فقط یک صفحه‌ی استاتیک SSR خواهد بود و دکمه‌ی Click me آن کار نمی‌کند:
// http://localhost:5227/server-interactive-component
app.MapGet("/server-interactive-component", () => new RazorComponentResult<Counter>());
علت اینجا است که اگر به سورس HTML رندر شده مراجعه کنیم، خبری از درج اسکریپت blazor.web.js در انتهای آن نیست. به همین جهت برای مثال فایل جدید CounterInteractive.razor را به صورت زیر اضافه می‌‌کنیم که ساختار آن شبیه به فایل App.razor است:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive server component</title>
    <base href="/"/>
</head>
<body>
   <h1>Interactive server component</h1>

   <Counter/>

  <script src="_framework/blazor.web.js"></script>
</body>
</html>
و هدف اصلی از آن، تشکیل یک قالب و درج اسکریپت blazor.web.js در انتهای آن است.
سپس تعریف endpoint متناظر را به صورت زیر تغییر می‌دهیم:
// http://localhost:5227/server-interactive-component
app.MapGet("/server-interactive-component", () => new RazorComponentResult<CounterInteractive>());
اینبار به علت بارگذاری فایل blazor.web.js، امکانات تعاملی کامپوننت Counter فعال شده و قابل استفاده می‌شوند.


سؤال: آیا می‌توان این خروجی static SSR کامپوننت‌های بلیزر را در سرویس‌های یک برنامه ASP.NET Core هم دریافت کرد؟

منظور این است که آیا می‌توان از یک کامپوننت Blazor، به همراه تمام پیشرفت‌های Razor در آن که در Viewهای MVC قابل دسترسی نیستند، به‌شکل یک رشته‌ی خالص، خروجی گرفت و برای مثال از آن به‌عنوان قالب پویای محتوای ایمیل‌ها استفاده کرد؟
پاسخ: بله! زیر ساخت RazorComponentResult که از سرویس HtmlRenderer استفاده می‌کند، بدون نیاز به برپایی یک endpoint هم قابل دسترسی است:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace WebApi8x.Services;

public class BlazorStaticRendererService
{
    private readonly HtmlRenderer _htmlRenderer;

    public BlazorStaticRendererService(HtmlRenderer htmlRenderer) => _htmlRenderer = htmlRenderer;

    public Task<string> StaticRenderComponentAsync<T>() where T : IComponent
        => RenderComponentAsync<T>(ParameterView.Empty);

    public Task<string> StaticRenderComponentAsync<T>(Dictionary<string, object?> dictionary) where T : IComponent
        => RenderComponentAsync<T>(ParameterView.FromDictionary(dictionary));

    private Task<string> RenderComponentAsync<T>(ParameterView parameters) where T : IComponent =>
        _htmlRenderer.Dispatcher.InvokeAsync(async () =>
                                             {
                                                 var output = await _htmlRenderer.RenderComponentAsync<T>(parameters);
                                                 return output.ToHtmlString();
                                             });
}
برای کار با آن، ابتدا باید سرویس فوق را به صورت زیر ثبت و معرفی کرد:
builder.Services.AddScoped<HtmlRenderer>();
builder.Services.AddScoped<BlazorStaticRendererService>();
و سپس یک نمونه مثال فرضی نحوه‌ی تزریق و فراخوانی سرویس BlazorStaticRendererService به صورت زیر است که در آن روش ارسال پارامترها هم بررسی شده‌است:
app.MapGet("/static-renderer-service-test",
           async (BlazorStaticRendererService renderer, int data = 1) =>
           {
               var parameters = new Dictionary<string, object?>
                                {
                                    { nameof(SsrTest.Data), data },
                                };
               var html = await renderer.StaticRenderComponentAsync<SsrTest>(parameters);
               return Results.Content(html, "text/html");
           });

کدهای کامل این مطلب را می‌توانید از اینجا دریافت کنید: WebApi8x.zip
مطالب
استفاده از پیاده سازی Katana مربوط به استاندارد Owin در ASP.NET 4.x
قطعا ASP.NET MVC 5.x به عنوان یک فریم ورک بالغ و با امکانات فراوان شناخته میشود که در این مساله هیچ بحثی نیست. اما آیا در همه‌ی پروژه‌ها حتما باید از این فریم ورک استفاده شود؟ امروزه اکثر وب اپلیکیشن‌ها از فریم ورک‌های SPA استفاده میکنند و بنده به وفور در پروژه‌های مختلف شاهد این بوده‌ام که ASP.NET MVCی که در کنار آن استفاده میشود، عملا چیزی بیشتر از یک کنترلر Home و یک متد Index و حداکثر یک Layout، نیستند و معمولا در کنار آن از Web Api استفاده میکنند که حداقل در ASP.NET MVC 5.x چیزی کاملا مجزای از آن به حساب می‌آید. با این حال آیا واقعا از امکانات MVC 5.x استفاده شده است؟! یا فقط اینگونه پروژه‌ها محدودیت‌های MVC را به دوش میکشند؟
در ASP.NET 4.x به صورت معمول ارسال درخواست‌ها بدین صورت است که از سمت کلاینت به IIS و بعد از آن بر روی ISAPI نگاشت میشوند (یا Static File برای فایل‌های استاتیک). پس عملا وابستگی شدیدی به IIS ایجاد شده‌است و اینکه مشکلات این وابستگی چیست در این مقاله نمیگنجد. اگر قرار باشد همین امروز پروژه‌ای شروع شود قطعا ASP.NET 4.x گزینه‌ی معقولی به نظر میرسد؛ اما در پروژه‌های حجیم بیزینسی که باید ماه‌ها و شاید چندین سال بر روی نرم افزار آن کار شود، آیا آن موقع نیز ASP.NET مانند حال گزینه‌ی معقولی است یا بطور مثال ASP.NET Core با امکانات منحصر به فردش جایگزین خواهد شد؟ در نگاه اول وقتی دو پروژه‌ی ASP.NET 4.x و ASP.NET Core را در کنار هم میگذاریم، شاید اختلافات زیاد باشند و ارتقاء نرم افزار به ASP.NET Core سخت و یا حتی غیر ممکن به نظر برسد. اما آیا واقعا هیچ راهی وجود ندارد که هم اکنون نرم افزار خود را با ASP.NET 4.x که کاملا بالغ هم شده شروع کرده و بعد‌ها به ASP.NET Core به روز رسانی شود؟
این‌ها سوال‌هایی است که قطعا قبل از شروع یک پروژه‌ی بزرگ نرم افزاری باید از خود بپرسیم. شاید با نگاه عمیق‌تری بر روی این سوال‌ها بتوان پاسخی مناسب را برای آن‌ها داد. یکی از این راه‌حل‌ها استفاده از استاندارد Owin و پیاده سازی آن به نام Katana است.

برای شروع پروژه ابتدا یک پروژه‌ی ASP.NET 4.x از نوع empty را بسازید.
برای راحت شدن کار، ابتدا packages.config را باز کرده و کد‌های زیر را جایگزین آن نمایید:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.AspNet.OData" version="6.0.0" targetFramework="net462" />
  <package id="Microsoft.AspNet.SignalR.Core" version="2.2.1" targetFramework="net462" />
  <package id="Microsoft.AspNet.SignalR.Owin" version="1.2.2" targetFramework="net462" />
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net462" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net462" />
  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net462" />
  <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.2" targetFramework="net462" />
  <package id="Microsoft.Extensions.DependencyInjection" version="1.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.0.0" targetFramework="net462" />
  <package id="Microsoft.Net.Compilers" version="1.3.2" targetFramework="net462" developmentDependency="true" />
  <package id="Microsoft.OData.Core" version="7.0.0" targetFramework="net462" />
  <package id="Microsoft.OData.Edm" version="7.0.0" targetFramework="net462" />
  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Spatial" version="7.0.0" targetFramework="net462" />
  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net462" />
  <package id="Owin" version="1.0" targetFramework="net462" />
  <package id="System.Collections" version="4.0.11" targetFramework="net462" />
  <package id="System.Collections.Concurrent" version="4.0.12" targetFramework="net462" />
  <package id="System.ComponentModel" version="4.0.1" targetFramework="net462" />
  <package id="System.Diagnostics.Debug" version="4.0.11" targetFramework="net462" />
  <package id="System.Globalization" version="4.0.11" targetFramework="net462" />
  <package id="System.Linq" version="4.1.0" targetFramework="net462" />
  <package id="System.Linq.Expressions" version="4.1.0" targetFramework="net462" />
  <package id="System.Reflection" version="4.1.0" targetFramework="net462" />
  <package id="System.Resources.ResourceManager" version="4.0.1" targetFramework="net462" />
  <package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net462" />
  <package id="System.Threading" version="4.0.11" targetFramework="net462" />
  <package id="System.Threading.Tasks" version="4.0.11" targetFramework="net462" />
</packages>
ما پکیج‌های OData و SignlarR , WebApi, Owin را اضافه نموده‌ایم و دستور زیر را برای اضافه شدن ارجاعات اجرا می‌کنیم:
PM>Update-Package -reinstall -Project YourProjectName
اکنون عملا تمام پکیج‌های لازم را برای شروع به کار، در اختیار داریم (اگر از dotNetFrameWork نسخه‌ی پایین‌تری بطور مثال 4.6.1 استفاده میکنید، بعد از اجرای دستور فوق، targetFramework شما به 461 اصلاح خواهد شد).
حال میخواهیم کمی کار اختیاری را نیز بر روی وب کانفیگ انجام دهیم که به performance نرم افزار شما بهبود قابل ملاحظه‌ای را می‌افزاید. کد‌های زیر را در وب کانفیگ جایگزین نمایید:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="owin:AppStartup" value="OwinKatanaTest.OwinAppStartup, OwinKatanaTest" />
    <!-- Owin App Startup Class -->
    <add key="webpages:Enabled" value="false" />
    <!-- Disable asp.net web pages. Note that based on our current configuration, asp.net web forms, mvc and web pages won't work. This configuration is for owin stuffs only, for example asp.net web api & odata, signalr, etc. -->
  </appSettings>
  <system.web>
    <compilation debug="true" defaultLanguage="c#" enablePrefetchOptimization="true" optimizeCompilations="true" targetFramework="4.6.2">
      <assemblies>
        <remove assembly="*" />
        <!-- To improve app startup performance, our app will continue its work without this compilations, these are required for asp.net web forms, mvc and web pages. -->
        <add assembly="OwinKatanaTest" />
      </assemblies>
    </compilation>
    <httpRuntime targetFramework="4.6.2" />
    <httpModules>
      <!-- No need to these modules and handlers, owin handler itself will do everything for us -->
      <clear />
    </httpModules>
    <httpHandlers>
      <clear />
    </httpHandlers>
    <sessionState mode="Off" />
  </system.web>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
    </compilers>
  </system.codedom>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.AspNet.SignalR.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.2.1.0" newVersion="2.2.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime.Extensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="false">
      <!-- We're not going to remove all modules, some modules such as static & dynamic compression modules are really cool (-: -->
      <remove name="RewriteModule" />
      <remove name="OutputCache" />
      <remove name="Session" />
      <remove name="WindowsAuthentication" />
      <remove name="FormsAuthentication" />
      <remove name="DefaultAuthentication" />
      <remove name="RoleManager" />
      <remove name="FileAuthorization" />
      <remove name="UrlAuthorization" />
      <remove name="AnonymousIdentification" />
      <remove name="Profile" />
      <remove name="UrlMappingsModule" />
      <remove name="ServiceModel-4.0" />
      <remove name="UrlRoutingModule-4.0" />
      <remove name="ScriptModule-4.0" />
      <remove name="Isapi" />
      <remove name="IsapiFilter" />
      <remove name="DigestAuthentication" />
      <remove name="WindowsAuthentication" />
      <remove name="ServerSideInclude" />
      <remove name="DirectoryListing" />
      <remove name="DefaultDocument" />
      <remove name="CustomError" />
      <remove name="Cgi" />
    </modules>
    <defaultDocument>
      <!-- Default docs will be configured using owin static files middleware -->
      <files>
        <clear />
      </files>
    </defaultDocument>
    <handlers>
      <!-- Only use this handler for all requests -->
      <clear />
      <add name="Owin" verb="*" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, Microsoft.Owin.Host.SystemWeb" />
    </handlers>
    <httpProtocol>
      <customHeaders>
        <clear />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>
1) AppSettings برای کانفیگ Owin startup خواهد بود (در ادامه‌ی مقاله آن را مینویسیم).
2) در تگ compilation اسمبلی‌های اضافی را حذف مینماییم (برای بهبود performance از آنجایی که به asp.net web form یا mvc احتیاجی نداریم).
3) حذف http module و http handler در system.web (مربوط به iis 6).
4) در تگ system.codedom کامپایلر مربوط به vb را حذف مینماییم.
5) در تگ system.webserver ماژول‌ها و هندلر‌های اضافی را پاک مینماییم.
6) تگ defaultdocument، به دلیل اینکه از static file مربوط به owin استفاده میکنیم.
7) custom headers‌ها را نیز پاک میکنیم.

بعد از build کردن پروژه، در صورت خطا داشتن از References‌ها، System.Reflection و System.Runtime.Extensions را حذف کنید.
یک ریشه جدید را به نام Model ساخته و مدل‌های آزمایشی Product و Category را که هر دو فقط حاوی دو پراپرتی Id, Name میباشند، به آن اضافه کنید.

در root پروژه یک کلاس به نام OwinAppStartup را با محتوای زیر بسازید
using Microsoft.AspNet.SignalR;
using Microsoft.OData;
using Microsoft.OData.Edm;
using Owin;
using OwinKatanaTest.Model;
using OwinKatanaTest.ODataControllers;
using System.Collections.Generic;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using System.Web.OData.Routing.Conventions;

namespace OwinKatanaTest
{
    public class OwinAppStartup
    {
        public void Configuration(IAppBuilder owinApp)
        {
            owinApp.Map("/odata", innerOwinAppForOData =>
            {
                HttpConfiguration webApiODataConfig = new HttpConfiguration();
                webApiODataConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;

                webApiODataConfig.Formatters.Clear();

                IEnumerable<IODataRoutingConvention> conventions = ODataRoutingConventions.CreateDefault();

                ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(webApiODataConfig);

                modelBuilder.Namespace = modelBuilder.ContainerName = "Test";
                var categoriesSetConfig = modelBuilder.EntitySet<Category>("Categories");
                var getBestCategoryFunctionConfig = categoriesSetConfig.EntityType.Collection.Function(nameof(CategoriesController.GetBestCategory));
                getBestCategoryFunctionConfig.ReturnsFromEntitySet<Category>("Categories");

                IEdmModel edmModel = modelBuilder.GetEdmModel();

                webApiODataConfig.MapODataServiceRoute("default", "", builder =>
                {
                    builder.AddService(ServiceLifetime.Singleton, sp => conventions);
                    builder.AddService(ServiceLifetime.Singleton, sp => edmModel);
                });

                innerOwinAppForOData.UseWebApi(webApiODataConfig);

            });

            owinApp.Map("/api", innerOwinAppForWebApi =>
            {
                HttpConfiguration webApiConfig = new HttpConfiguration();
                webApiConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;

                webApiConfig.MapHttpAttributeRoutes();

                webApiConfig.Routes.MapHttpRoute(name: "default", routeTemplate: "{controller}/{action}", defaults: new { action = RouteParameter.Optional });

                innerOwinAppForWebApi.UseWebApi(webApiConfig);
            });

            owinApp.Map("/signalr", innerOwinAppForSignalR =>
            {
                innerOwinAppForSignalR.RunSignalR(new HubConfiguration
                {
                    EnableDetailedErrors = true
                });
            });

            owinApp.UseStaticFiles();

            owinApp.Run(async context =>
            {
                await context.Response.WriteAsync("owin katana");
            });
        }
    }
}
در وب کانفیگ، کار مربوط به استارتاپ را انجام دادیم و دیگر نیازی به قید کردن آن نیست. نگاشت اول، کانفیگ OData، دومی برای web api و همچنین سومی کانفیگ SignalR میباشد.
سپس یک پوشه‌ی جدید را به نام ODataControllers حاوی کلاسی با نام CategoriesController بدین گونه بسازید:
using OwinKatanaTest.Model;
using System.Web.Http;
using System.Web.OData;

namespace OwinKatanaTest.ODataControllers
{
    public class CategoriesController : ODataController
    {
        [HttpGet]
        public Category GetBestCategory()
        {
            return new Category { Id = 1, Name = "Test" };
        }
    }
}
و همچنین یک پوشه دیگر را به نام ApiControllers  به نام ProductsController با محتوای زیر:
using OwinKatanaTest.Model;
using System.Collections.Generic;
using System.Web.Http;

namespace OwinKatanaTest.ApiControllers
{
    public class ProductsController : ApiController
    {
        [HttpGet]
        [Route("products/{categoryId}")]
        public List<Product> GetProductsByCategoryId(int categoryId)
        {
            return new List<Product>
            {
                new Product { Id = 1 , Name = "Test" }
            };
        }
    }
}
حالا میتوانیم یه پروژه‌ی یونیت تست نوشته و کلیات مراحل فوق را تست نماییم. unit test را به پروژه اضافه کنید و reference پروژه‌ی اصلی خود را بدان اضافه کنید.
مانند پروژه‌ی قبلی، package.config را اضافه کرده و همه‌ی پکیج‌های قبلی به علاوه پکیج زیر را اضافه کنید:
<package id="Microsoft.Owin.Testing" version="3.0.1" targetFramework="net462" />
update-package فراموش نشود

در ادامه تست خود را اینگونه مینویسیم
using Microsoft.Owin.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OwinKatanaTest;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace Test
{
    [TestClass]
    public class Test
    {
        [TestMethod]
        public async Task TestWebApi()
        {
            using (TestServer server = TestServer.Create<OwinAppStartup>())
            {
                HttpResponseMessage apiResponse = await server.HttpClient.GetAsync("/api/products/1");
                apiResponse.EnsureSuccessStatusCode();
                Assert.AreEqual(HttpStatusCode.OK, apiResponse.StatusCode);
            }
        }

        [TestMethod]
        public async Task TestOData()
        {
            using (TestServer server = TestServer.Create<OwinAppStartup>())
            {
                HttpResponseMessage odataResponse = await server.HttpClient.GetAsync("odata/Categories/Test.GetBestCategory");
                odataResponse.EnsureSuccessStatusCode();
                Assert.AreEqual(HttpStatusCode.OK, odataResponse.StatusCode);
            }
        }
    }
}
الان باید solution شما چیزی شبیه به این باشد:

بعد از اجرای تست‌ها، باید تیک سبز کنارشان ایجاد شود.
بعد از خواندن این مقاله شاید متوجه شده باشید که چقدر pipeline این پروژه شبیه به پروژه‌های ASP.NET Coreی است؛ یا بهتر است تصحیح کنم به عکس. بلکه ASP.NET Core هست که خیلی شبیه به این میباشد!
عملا سرویس‌های شما کاملا مجزا شده‌اند و میتوانید به راحتی یک فریم ورک SPA را به پروژه‌ی خود اضافه کرده و برای Authentication هم Single Sign On Identity Server بسیار مناسب میباشد. بدون اینکه حتی برنامه نویسان بیزینسی پروژه‌ی شما متوجه بشوند، با تغییراتی در کانفیگ این پروژه، می‌توان آن را بروزرسانی نمود.
لینک دانلود پروژه OwinKatanaTest.zip 
اشتراک‌ها
نگاهی به تاریخچه‌ی ASP.NET - قسمت دوم
Part 1 took an overview of the initial design of ASP.NET and how Microsoft reacted to the various changes in webdev. In Part II, we will now look at how those changes influenced the development of ASP.NET MVC and ended up transforming ASP.NET into a much more flexible framework composed of multiple libraries that solved different problems.
نگاهی به تاریخچه‌ی ASP.NET - قسمت دوم