مطالب
تبدیل زیرنویس‌های خاص پلورال‌سایت به فرمت SRT - قسمت دوم
قسمت اول این مطلب را در اینجا می‌توانید مطالعه کنید. از سه سال قبل تا به این تاریخ، فرمت زیرنویس‌های این سایت به صورت JSON تغییر پیدا کرده‌است و یک چنین ساختار جدیدی را دارد:
{
    "userIsAuthorizedForCourseTranscripts": false,
    "modules": [
        {
            "title": "Course Overview",
            "clips": [
                {
                    "title": "Course Overview",
                    "playerParameters": "author=scott-allen&name=aspdotnet-core-1-0-fundamentals-m0&mode=live&clip=0&course=aspdotnet-core-1-0-fundamentals",
                    "transcripts": [ ]
                }
            ]
        },
        {
            "title": "Building Your First ASP.NET Core Application",
            "clips": [
                {
                    "title": "Introduction",
                    "playerParameters": "author=scott-allen&name=aspdotnet-core-1-0-fundamentals-m1&mode=live&clip=0&course=aspdotnet-core-1-0-fundamentals",
                    "transcripts": [
                        {
                            "displayTime": 0.0,
                            "text": "Hi! This is Scott, and this course will help you build your first application with ASP.NET Core."
                        },
                        {
                            "displayTime": 7.0,
                            "text": "In this course, we'll be using Visual Studio and the new ASP.NET Framework to build a web application that"
                        }
                    ]
                }
            ]
        }
    ]
}
که اگر بخواهیم کلاس‌های معادل آن‌را تشکیل دهیم، به ساختار زیر خواهیم رسید:
public class PluralsightCourse
{
    public bool UserIsAuthorizedForCourseTranscripts { get; set; }
    public PluralsightCourseItems[] Modules { get; set; }
}
public class PluralsightCourseItems
{
    public string Title { get; set; }
    public PluralsightCourseClip[] Clips { get; set; }
}
public class PluralsightCourseClip
{
    public string Title { get; set; }
    public string PlayerParameters { get; set; }
    public PluralsightCourseClipTranscript[] Transcripts { get; set; }
}
public class PluralsightCourseClipTranscript
{
    public float DisplayTime { get; set; }
    public string Text { get; set; }
}
و بعد از تشکیل این ساختار که توسط منوی edit و گزینه‌ی paste special ویژوال استودیو تشکیل شده‌است:


بارگذاری آن توسط کتابخانه‌ی JSON.NET به صورت ذیل خواهد بود:
public static PluralsightCourse ProcessJsonFile(string jsonData)
{
    return JsonConvert.DeserializeObject<PluralsightCourse>(jsonData);
}
و پس از آن اگر حلقه‌ای را بر روی ماژول‌ها و سپس آیتم‌های هر ماژول تشکیل دهیم، می‌توان فرمت آن‌را به فرمت استاندارد srt که قابلیت پخش در اکثر برنامه‌های video player را دارد (مانند kmplayer)، تبدیل کرد.


کدهای کامل این برنامه را از اینجا می‌توانید دریافت کنید:
PluralsightJsonTranscripts.V1.0.7z
مطالب
نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery
هر از چندگاهی سؤال «این مثال jQuery رو نمی‌تونم اجرا یا باز سازی کنم» در این سایت یا سایت‌های مشابه تکرار می‌شوند. بنابراین بهتر است نحوه عیب یابی برنامه‌های ASP.NET مبتنی بر jQuery را یکبار با هم مرور کنیم. در اینجا، مثال تهیه یک Image Slider را که پیشتر در سایت مطرح شده است، به نحوی دیگر بررسی خواهیم کرد:
1) فراموش می‌کنیم تا اسکریپت اصلی jQuery را به درستی پیوست و مسیردهی کنیم.
2) مسیر Generic handler دیگری را ذکر می‌کنیم.
3) مسیرهای تصاویری را که Image slider باید نمایش دهد، کاملا بی‌ربط ذکر می‌کنیم.
4) خروجی JSON نامربوطی را بازگشت می‌دهیم.
5) یکبار هم یک استثنای عمدی دستی را در بین کدها قرار خواهیم داد.

و ... بعد سعی می‌کنیم با استفاده از Firebug عیوب فوق را یافته و اصلاح کنیم؛ تا به یک برنامه قابل اجرا برسیم.


معرفی برنامه‌ای که کار نمی‌کند!

یک برنامه ASP.NET Empty web application را آغاز کنید. سپس سه پوشه Scripts، Content و Images را به آن اضافه نمائید. در این پوشه‌ها، اسکریپت‌های نمایش دهنده تصاویر، Css آن و تصاویری که قرار است نمایش داده شوند، قرار می‌گیرند:


سپس یک فایل default.aspx و یک فایل OrbitHandler.ashx را نیز به پروژه با محتویات ذیل اضافه کنید: (در این دو فایل، 5 مورد مشکل ساز یاد شده لحاظ شده‌اند)
محتویات فایل OrbitHandler.ashx.cs مطابق کدهای ذیل است:
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Script.Serialization;

namespace OrbitWebformsTest
{
    public class Picture
    {
        public string Title { set; get; }
        public string Path { set; get; }
    }

    public class OrbitHandler : IHttpHandler
    {
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = /*"Images/" + name*/ name,
                    Title = name
                });
            }

            return results;
        }

        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = /*new JavaScriptSerializer().Serialize(items)*/ string.Empty;
            throw new InvalidDataException("همینطوری");
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }

        public bool IsReusable
        { 
            get { return false; } 
        }
    }
}
در اینجا جهت سهولت دموی برنامه (و همچنین امکان باز تولید آن توسط خوانندگان)، از بانک اطلاعاتی استفاده نشده و عمدا از یک لیست جنریک تشکیل شده در حافظه کمک گرفته شده است. تصاویر برنامه در پوشه Images واقع در ریشه سایت، قرار دارند. بنابراین توسط متد PicturesDataSource، فایل‌های این پوشه را یافته و مطابق ساختار کلاس Picture بازگشت می‌دهیم. نهایتا این اطلاعات به ظاهر قرار است با فرمت JSON بازگشت داده شوند تا بتوان نتیجه را توسط افزونه Orbit استفاده کرد.

همچنین کدهای صفحه ASPX ایی که قرار است (به ظاهر البته) از این Generic handler استفاده کند به نحو ذیل است:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="OrbitWebformsTest._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">
    <title></title>
    <link href="Content/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
    <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.Path + '" alt="' + b.Title + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"
            });
        });
    </script>
</body>
</html>
خوب! اگر پروژه را اجرا کنیم، کار نمی‌کند. یک مستطیل مشکی رنگ در کنار صفحه ظاهر شده و همین! حالا چکار باید کرد؟


مراحل عیب یابی برنامه‌ای که کار نمی‌کند!

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


اولین موردی که مشهود است، نمایش عدد 3، کنار آیکن فایرباگ می‌باشد. این عدد به معنای وجود خطاهای اسکریپتی در کدهای ما است.
برای مشاهده این خطاها، بر روی برگه Console آن کلیک کنید: 


بله. مشخص است که مسیر دهی فایل jquery-1.5.1.min.js صحیح نبوده و همین مساله سبب بروز خطاهای اسکریپتی گردیده است. برای اصلاح آن سطر زیر را در برنامه تغییر دهید:
 <script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
پیشتر پوشه Script ذکر شده بود که باید تبدیل به Scripts شود.

مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید. اینبار در برگه Console و یا در برگه شبکه فایرباگ، خطای یافت نشدن Generic handler نمایان می‌شوند:


برای رفع آن به فایل default.aspx مراجعه و بجای معرفی Handler.ashx، نام OrbitHandler.ashx را وارد کنید.
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید.


اگر به برگه کنسول دقت کنیم، بروز استثناء در کدها تشخیص داده شده و همچنین در برگه Response پاسخ دریافتی از سرور، جزئیات صفحه خطای بازگشتی از آن نیز قابل بررسی و مشاهده است.
اینبار به فایل OrbitHandler.ashx.cs مراجعه کرده و سطر throw new InvalidDataException را حذف می‌کنیم. در ادامه برنامه را کامپایل و مجددا اجرا خواهیم کرد.



با اجرای مجدد سایت، تبادل اطلاعات صحیحی با فایل OrbitHandler.ashx برقرار شده است، اما خروجی خاصی قابل مشاهده نیست. بنابراین بازهم سایت کار نمی‌کند.
برای رفع این مشکل، متد ProcessRequest را به نحو ذیل تغییر خواهیم داد:
        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = new JavaScriptSerializer().Serialize(items);            
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }
برنامه را کامپایل کرده و اجرا می‌کنیم. برنامه اجرا می‌شود، اما باز هم کار نمی‌کند. مشکل از کجاست؟


بله. تمام تنظیمات به نظر درست هستند، اما در برگه شبکه فایرباگ تعدادی خطای 404 و یا «یافت نشد»، مشاهده می‌شوند. مشکل اینجا است که مسیرهای بازگشت داده شده توسط متد Directory.GetFiles، مسیرهای مطلقی هستند؛ مانند c:\path\images\01.jpg و جهت نمایش در یک وب سایت مناسب نمی‌باشند. برای تبدیل آن‌ها به مسیرهای نسبی، اینبار کدهای متد تهیه منبع داده را به نحو ذیل ویرایش می‌کنیم:
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = "Images/" + name,
                    Title = name
                });
            }

            return results;
        }
در این کدها فقط قسمت Path ویرایش شده است تا به مسیر پوشه Images واقع در ریشه سایت اشاره کند.
اینبار اگر برنامه را اجرا کنیم، بدون مشکل کار خواهد کرد.

بنابراین در اینجا مشاهده کردیم که اگر «برنامه‌ای مبتنی بر jQuery کار نمی‌کند»، چگونه باید قدم به قدم با استفاده از فایرباگ و امکانات آن، به خطاهایی که گزارش می‌دهد و یا مسیرهایی را که یافت نشد بیان می‌کند، دقت کرد تا بتوان برنامه را عیب یابی نمود.


سؤال مهم: اجرای کدهای jQuery Ajax فوق، چه تغییری را در صفحه سبب می‌شوند؟

اگر به برگه اسکریپت‌ها در کنسول فایرباگ مراجعه کنیم، امکان قرار دادن breakpoint بر روی سطرهای کدهای جاوا اسکریپتی نمایش داده شده نیز وجود دارد:


در اینجا همانند VS.NET می‌توان برنامه را در مرورگر اجرا کرده و تگ‌های تصویر پویای تولید شده را پیش از اضافه شدن به صفحه، مرحله به مرحله بررسی کرد. به این ترتیب بهتر می‌توان دریافت که آیا src بازگشت داده شده از سرور فرمت صحیحی دارد یا خیر و آیا به محل مناسبی اشاره می‌کند یا نه. همچنین در برگه HTML آن، عناصر پویای اضافه شده به صفحه نیز بهتر مشخص هستند:

مطالب
نرمال سازی اطلاعات کاربران در حین ثبت نام
شرایط دنیای واقعی، بسیار متفاوت است از طراحی‌های ساده‌ی اولیه‌ی ثبت نام. در طراحی‌های ساده، ایمیل، نام کاربری و بسیاری از اطلاعات دیگر باید منحصربفرد باشند. ایندکس منحصربفرد تعریف می‌کنید. قیود و اعتبار سنجی سمت سرور و سمت کاربر را اضافه می‌کنید. چقدر عالی! اما ... دنیای واقعی شکل دیگری را دارد!
یک روز با ایمیل username@gmail.com ثبت نام می‌کنند. فردا با ایمیل user.name@gmail.com ثبت نام خواهند کرد. پس فردا با ایمیل us.er.name@gmail.com و به همین ترتیب! امروز با نام «کاربر یک» ثبت نام می‌کنند. فردا با نام «کاربر  یک»! امروز با نام «مجید» ثبت نام می‌کنند، فردا با نام «مـجـــیـــد»! همچنین علاقه‌ی شدیدی هم به استفاده از ایمیل‌های fake دارند (راه حل).
بنابراین نیاز است اطلاعات کاربران را پیش از ثبت نام نرمال سازی کرد. برای مثال نقطه‌های ایمیل‌های جیمیل را حذف کرد؛ یا اگر اجازه داده‌اید که در بین کلمات نام کاربری، فاصله‌ای را وارد کنند، فقط یک فاصله مجاز باشد و یا اگر نامی را ثبت می‌کنید، به فکر حالت‌های کش آمده‌ی آن مانند «مـجـــــــــیــــــــــد» هم باید بود و آن‌را تبدیل به حالت اصلی‌اش کرد.


نرمال سازی ایمیل‌های gmail

تا جایی که اطلاع دارم، حداقل فیس بوک و جی‌میل، بکارگیری نقاط را در ایمیل‌ها مجاز می‌دانند. برای مثال ترکیب‌های زیر از دید gmail تنها یک ایمیل محسوب می‌شوند:
johndoe@gmail.com
john....doe@gmail.com
johndoe+spamsite@gmail.com

راه حل پیشنهادی:
public static string FixGmailDots(string email)
{
    if (string.IsNullOrWhiteSpace(email))
        return string.Empty;
 
    email = email.ToLowerInvariant().Trim();
    var emailParts = email.Split('@');
    var name = emailParts[0].Replace(".", string.Empty).Replace("+", string.Empty);
    var emailDomain = emailParts[1];
 
    string[] domainsAllowedDots =
    {
        "gmail.com",
        "facebook.com"
    };
 
    var isFromDomainsAllowedDots = domainsAllowedDots.Any(domain => emailDomain.Equals(domain));
    return !isFromDomainsAllowedDots ? email : string.Format("{0}@{1}", name, emailDomain);
}
در اینجا بررسی می‌شود که آیا دومین ایمیل دریافتی از سمت جیمیل یا فیس بوک است؟ اگر بله، آنگاه نقطه‌ها و +‌های آن‌ها حذف می‌شوند.
این بررسی باید در حین ثبت نام و همچنین ویرایش اطلاعات کاربری جهت نرمال سازی اطلاعات اعمال شود.
اگر سایت‌های دیگری هم هستند که بکارگیری نقاط را مجاز می‌دانند، آرایه‌ی domainsAllowedDots را تکمیل کنید.


نرمال سازی ورود حروف ویژه

نرمال سازی ابتدایی ثبت نام کاربران در سایت جاری به صورت ذیل است:
 friendlyName = friendlyName.ApplyCorrectYeKe().RemoveDiacritics().CleanUnderLines().RemovePunctuation();
var trimmedFriendlyName = friendlyName.Trim().Replace(" ", "");
با ApplyCorrectYeKe آشنایی دارید؛ همان یک دست کردن ی و ک فارسی و عربی است.
RemoveDiacritics همان حذف اعراب از کلمات است است.
متد پاکسازی underlineهای ویژه یا همان نام‌های کش آمده، به صورت زیر است:
public static string CleanUnderLines(string text)
{
    if (string.IsNullOrWhiteSpace(text))
        return string.Empty;
 
    const char chr1600 = (char)1600; //ـ=1600
    const char chr8204 = (char)8204; //‌=8204
 
    return text.Replace(chr1600.ToString(CultureInfo.InvariantCulture), "")
               .Replace(chr8204.ToString(CultureInfo.InvariantCulture), "");
}
و متد RemovePunctuation :
 public static string RemovePunctuation(string text)
{
 return string.IsNullOrWhiteSpace(text) ? string.Empty : new string(text.Where(c => !char.IsPunctuation(c)).ToArray());
}

این موارد، «حداقل‌»هایی هستند که باید جهت نرمال سازی اطلاعات، در حین ثبت نام اعمال شوند.
مطالب
Owin چیست ؟ قسمت اول
مطمئنا اکثر شما برنامه نویسان با معماری IIS و ASP.NET کمابیش آشنایی دارید
Request از سمت کلاینت به IIS ارسال می‌شود، و عموما بسته به نوع درخواست کلاینت یا به یک Static File مپ می‌شود( مثلا به یک عکس )، و یا به یک ISAPI
ISAPI کدی است که عموما با ++C نوشته می‌شود، و برای درخواست آمده از سمت کلاینت کاری را انجام می‌دهد
یکی از این ISAPI‌ها برای ASP.NET است، که درخواست کلاینت را به یک کد مبتنی بر NET. مپ می‌کند ( به همین علت به آن ASP.NET می‌گویند )
نکته ای که در خطوط فوق به وضوح دیده می‌شود، وابستگی شدید ASP.NET به IIS است
بدیهتا کدی که بر روی بستر ASP.NET نوشته می‌شود نیز وابستگی فوق العاده ای به IIS دارد، که یکی از بدترین نوع این وابستگی‌ها در ASP.NET Web Forms دیده می‌شود.
خب، این مسئله چه مشکلاتی را ایجاد می‌کند ؟
مشکل اول که شاید کمتر به چشم بیاید، بحث کندی اجرای بار اول برنامه‌‌های ASP.NET است.
اما مشکل دوم عدم توانایی در نوشتن کد برنامه، بدون وابستگی به وب سرور (در اینجا IIS ) است، که این مشکل دوم روز به روز در حال جدی‌تر شدن است.
این مشکل دوم را برنامه نویسان جاوا سالهاست که با آن درگیرند، نکته این است که بین دو وب سرور در نحوه پردازش یک درخواست کلاینت تفاوت هایی وجود دارد، که بالطبع این تفاوت‌ها در نحوه‌ی اجرای کد بالاخره خودش را جاهایی نشان می‌دهد، این که بگوییم رفتار وب سرورها نباید متفاوت باشد کمی مسخره است، زیرا تفاوت آنها  با یکدیگر باعث شده که سرعت یکسان و امکانات یکسانی نداشته باشند و هر کدام برای یک سناریوی خاص مناسب‌تر باشند
این مسئله برای ما نیز روز به روز دارای اهمیت بیشتری می‌شود، دیگر این که Web Server ما فقط IIS صرف باشد، سناریوی متداول در پروژه‌های Enterprise نیست
در چه جاهایی می‌توان یک برنامه را هاست کرد ؟
IIS به همراه ASP.NET
IIS بدون ASP.NET ( می‌خواهیم برنامه بر روی IIS هاست شود، ولی کاری با ASP.NET نداریم )
CLR AppDomains
و وب سرورهای لینوکسی در صورت اجرای برنامه بر روی Mono
و ...
هم اکنون به میزان زیادی مشکل شفاف شده است، مطابق با معماری فعلی داریم
 Request >> IIS >>  aspnet_isapi.dll >>  System.Web.dll >> Your codes
مشکل دیگری که وجود دارد این است که اگر تیمی بخواهد فریم ورکی برای برنامه نویسان نهایی فراهم کند، باید آنرا بر روی اکثر گزینه‌های هاست موجود سازگار  کنید، برای   مثال مشاهده می‌کنید که ASP.NET Signal R را هم می‌توان بر روی IIS و ASP.NET هاست کرد و هم بر روی یک App Domain کاملا معمولی  و علاوه بر این که تیم   SignalR باید این هزینه مضاعف را پرداخت کند، خروجی برای ما نیز چندان خوشایند نیست، برای مثال اجرای همزمان ASP.NET SignalR و ASP.NET  Web API اگر چه که بر  روی هاستی به غیر از ASP.NET نیز امکان پذیر است، اما متاسفانه به عنوان دو بازیگر جدا از هم کار می‌کنند و عملا تعاملی با یکدیگر ندارند، مگر این  که بر روی ASP.NET  هاست شوند، و یا بسیاری از امکانات Routing موجود در WCF بر روی بستری غیر از ASP.NET کار نمی‌کند.
بدیهی است که این بازار پر آشوب به نفع هیچ کس نیست.
و اما راه حل چیست ؟
تعدادی از برنامه نویسان حرفه ای NET. دور یکدیگر جمع شدند و طی بررسی هایشان به این نتیجه رسیدند که هاست‌های مختلف نقاط اشتراک بسیار زیادی دارند و  تفاوت‌ها نباید باعث این میزان مشکل شود.
پس استانداری را طراحی کردند با نام OWIN  یا Open Web Interface for .NET
این استاندارد به صورت کاملا ریز به طراحی هر چیزی را که شما به آن فکر کنید پرداخته است، Request, Cookie, Response, Web Sokcet و ...
اما همانطور که از نامش مشخص است این یک استاندارد است و پیاده سازی ندارد، و هر هاستی باید یک بار این استاندارد را بر روی خود پیاده سازی کند
خبر خوش این است که تا این لحظه اکثر هاست‌های مهم این استاندارد را پیاده سازی کرده اند و یا در دست پیاده سازی دارند
پروژه Helios برای IIS
پروژه Katana  برای IIS به در کنار و سازگار با ASP.NET برای پروژه هایی که تا این لحظه از امکانات سطح پایین ASP.NET استفاده زیادی کرده اند و فرصت تغییر ساختاری ندارند
پروژه هایی برای App Domains و ...
مرحله‌ی بعدی این است که فریم ورک‌ها خوشان را با Owin سازگار کنند
معروف‌ترین فریم ورک هایی که تا این لحظه اقدام به انجام این کار کرده اند، عبارتند از:
ASP.NET Web API
ASP.NET MVC
ASP.NET Identity
ASP.NET Signal R ( در حال حاضر Signal R فقط بر روی Owin قابلیت استفاده دارد )
بدیهی است که زمانی که پروژه ASP.NET Web API بر روی استاندارد OWIN نوشته می‌شود، دیگر نیازی به تحمل هزینه مضاعف برای سازگاری خود با انواع هاست ها ندارد و این مسئله توسط Katana، Helios  و ... انجام شده است، که بالطبع بزرگترین نفع آن برای ما جلوگیری از چند باره کاری توسط تیم Web API و ... است که بالطبع در  زمان کمتر امکانات بیشتری را به ما ارائه می‌دهند.
البته واضح است فریم ورک هایی که با کلاینت و درخواست‌ها کاری ندارند، با این مقولات کاری ندارند، پس Entity Framework و ... از این داستان مستثنا هستند.
و علاوه بر این فریم ورک هایی با طراحی اشتباه و قدیمی مانند ASP.NET Web Forms به صورت کلی قابلیت سازگار شدن با این استاندارد را ندارند، زیرا کاملا به ASP.NET  وابسته هستند
و در نهایت در مرحله‌ی بعدی لازم است شما نیز از فریم ورک هایی استفاده کنید که مبتنی بر OWIN هستند، یعنی برای مثال پروژه بعدی تان را مبتنی بر ASP.NET MVC و ASP.NET Web API و ASP.NET Identity پیاده سازی کنید، در این صورت شما می‌توانید برنامه ای بنویسید که به Web Server هیچ گونه وابستگی ندارد.
به این صورت کد زدن چند مزیت بزرگ دیگر هم دارد که از کم اهمیت‌ترین آنها شروع می‌کنیم:
1- سرعت بسیار بالاتر برنامه در هاست‌های غیر ASP.NET ای، مانند زمانی که شما از IIS به صورت مستقیم و بدون وابستگی به System.Web.dll استفاده می‌کنید.
توجه کنید که حتی در این حالت هم می‌توانید از ASP.NET Web API و Signal R و Identity استفاده کنید و تا 25% سرعت بیشتری داشته باشید ( بسته به سناریو ) 
2- قابلیت توسعه آسانتر و با قابلیت نگهداری بالاتر پروژه‌های Enterprise، برای مثال در یکی از پروژه‌ها من مجبور بودم از ASP.NET Web API به صورتی استفاده کنم  که هم توسط کلاینت JavaScript ای استفاده شود، و هم توسط کدهای Controller‌های MVC ( بدون استفاده مستقیم از کد سرویس با رفرنس زدن به سرویس‌ها البته )  که خوشبختانه OWIN به خوبی از پس این کار بر آمد، و عملا یک سرویس Web API را هم بر روی IIS هاست کردم و هم داخل یک AppDomain
3- در چند سال آینده که اکثریت مطلق سایت‌ها از این روش استفاده کنند ( شما چه بدانید و چه ندانید اگر در برنامه خودتان از Signal R نسخه 2 دارید استفاده می‌کنید،حتما از OWIN استفاده کرده اید )، مایکروسافت می‌تواند دست به تغییرات اساسی‌تری بزند، برای مثال معماری جدیدی از IIS ارائه دهد که مشکلات ساختاری فراوان  فعلی  IIS  را   که  از حوصله توضیح این مقاله خارج است را نداشته باشد، و فقط یک پیاده سازی OWIN جدید بر روی آن ارائه دهد و برنامه‌های ما بدون تغییر بر روی آن نیز کار  کنند، و یا  این که بتواند تعدادی از فریم ورک‌های با طراحی قدیمی را راحت‌تر از دور خارج کند، مانند Web Forms
نکته پایانی، اگر هم اکنون پروژه ای دارید که در داخل آن از ASP.NET استفاده شده، و برای مثال تعدای فرم ASP.NET Web Forms نیز دارد، نگران نباشید، کماکان می‌توانید از Owin برای سایر قسمت‌ها مانند Web API استفاده کنید، البته در این حالت تاثیری در بهبود سرعت اجرای برنامه مشاهده نخواهید کرد، اما برای مهاجرت و اعمال تغییرات این آسانترین روش ممکن است
در قسمت بعدی، مثالی را شروع می‌کنیم مبتنی بر ASP.NET Web API، ASP.NET Identity و Helios
اشتراک‌ها
R4MVC معادل T4MVC برای ASP.NET Core

R4MVC is a Roslyn code generator for ASP.NET MVC Core apps that creates strongly typed helpers that eliminate the use of literal strings in many places.

It is a re-implementation of T4MVC for ASP.NET Core projects. 

R4MVC معادل T4MVC برای ASP.NET Core
اشتراک‌ها
افزایش کارایی برنامه های ASP.NET

we will look at some of the approaches that you can take to make optimized and better performing ASP.NET web sites. Some of these principles can also be applied to websites created in a technology other than ASP.NET.

افزایش کارایی برنامه های  ASP.NET
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت نهم - مسیریابی
نکات ارتقاء به نگارش RC5
در نگارش RC5، فرمت فایل app.routes.ts که در نگارش RC4 معرفی شد، اندکی تغییر کرده‌است.
ابتدای فایل به این شکل:
// - Routes instead of RouteConfig
// - RouterModule instead of provideRoutes
import { Routes, RouterModule } from '@angular/router';
و انتهای آن نیز به این شکل تغییر پیدا می‌کند:
// - Updated Export
export const routing = RouterModule.forRoot(routes);
پس از آن، این تعاریف، با توجه به ساده شدن فایل main.ts، به قسمت imports فایل app.module.ts منتقل و معرفی می‌شوند.
به علاوه در قسمت‌های مختلف برنامه مواردی مانند ROUTER_DIRECTIVES, ROUTER_PROVIDERS, HTTP_PROVIDERS را یافته و حذف کنید. این‌ها نیز به فایل app.module.ts و قسمت imports آن منتقل شده‌اند.
مطالب
PowerShell 7.x - قسمت هشتم - ماژول‌ها
توسط ماژول‌ها میتوانیم یک مجموعه از دستورات را گروه‌بندی کنیم و تحت عنوان یک پکیج ارائه دهیم که برای دیگران نیز قابل استفاده باشند. برای ایجاد یک ماژول کافی است اسکریپت‌های خود را درون یک فایل با پسوند psm1 قرار دهیم؛ به این فایل اصطلاحاً root module گفته میشود. در واقع میتوان گفت ماژول‌ها یک روش مناسب برای به اشتراک‌گذاری اسکریپت‌ها میباشند. تا اینجا با کمک پروفایل‌ها توانستیم امکان استفاده مجدد از توابع و اسکریپت‌ها را داشته باشیم؛ ماژول‌ها نیز یک روش دیگر برای بارگذاری اسکریپت‌ها درون شل هستند. زمانیکه شل را باز میکنیم PowerShell به صورت خودکار یکسری مسیر را برای بارگذاری ماژول‌ها اسکن میکند. توسط متغیر env:PSModulePath$ میتوانیم لیست این مسیرها را ببینیم:  
PS /> $env:PSModulePath -Split ":"

/Users/sirwanafifi/.local/share/powershell/Modules
/usr/local/share/powershell/Modules
/usr/local/microsoft/powershell/7/Modules
همانطور که عنوان شد برای ایجاد یک ماژول کافی است اسکریپت‌های خود را داخل یک فایل با پسوند psm1 ذخیره کنیم. به عنوان مثال میتوانیم تابع Get-PingReply را درون یک فایل با نام PingModule.psm1 ذخیره و سپس توسط دستور Import-Module ماژول را ایمپورت کنیم:  
PS /> Import-Module ./PingModule.psm1
سپس توسط دستور Get-Module PingModule میتوانیم جزئیات ماژول ایمپورت شده را مشاهده نمائیم: 
PS /> Get-Module PingModule

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     0.0                   PingModule                          Get-PingReply
به صورت پیش‌فرض تمام توابع درون اسکریپت export خواهند شد. اگر ExportedCommands خالی باشد به این معنا است که ماژول به درستی ایمپورت نشده‌است. به عنوان مثال اگر سعی کنید فایل قبل را با پسوند ps1 به عنوان ماژول ایمپورت کنید. خطایی هنگام ایمپورت کردن مشاهده نخواهید کرد و قسمت ExportedCommands خالی خواهد بود. در این‌حالت نیز امکان استفاده از تابع درون اسکریپت را خواهیم داشت؛ اما هیچ تضمینی نیست که به صورت یک ماژول به درستی عمل کند. بنابراین بهتر است ماژول‌هایی که ایجاد میکنیم حتماً پسوند psm1 داشته باشند.
PS /> Import-Module ./PingModule.ps1
PS /> Get-Module PingModule

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     0.0                   PingModule
ممکن است بخواهیم یکسری توابع را به صورت private تعریف کنیم و فقط تعداد محدودی از توابع به صورت public باشند. در این حالت میتوانیم درون فایل psm1 با کمک دستور Export-ModuleMember اینکار را انجام دهیم: 
Function Get-PingReply {
    // as before
}

Function Get-PrivateFunction {
    Write-Debug 'This is a private function'
}

Export-ModuleMember -Function @(
    'Get-PingReply'
)
اضافه کردن متادیتا به ماژول‌ها
برای هر ماژول میتوانیم با کمک module manifest یکسری متادیتا به ماژول اضافه کنیم. این متادیتا درون یک فایل با پسوند psd1 که محتویات آن در واقع یک Hashtable است ذخیره میشود. برای ایجاد فایل manifest میتوانیم از دستور New-ModuleManifest استفاده کنیم. به عنوان مثال برای ایجاد فایل manifest برای ماژول PingModule.psm1 اینگونه عمل خواهیم کرد:  
$moduleSettings = @{
    Path                 = './PingModule.psd1'
    Description          = 'A module to ping a remote system'
    RootModule           = 'PingModule.psm1'
    ModuleVersion        = '1.0.0'
    FunctionsToExport    = @(
        'Get-PingReply'
    )
    PowerShellVersion    = '5.1'
    CompatiblePSEditions = @(
        'Core'
        'Desktop'
    )
}
New-ModuleManifest @moduleSettings
تنها پراپرتی موردنیاز برای ایجاد module manifest پراپرتی Path میباشد. این پراپرتی به فایلی که متادیتا قرار است درون آن ایجاد شود اشاره دارد. همانطور که مشاهده میکنید یکسری پراپرتی دیگر نیز اضافه کرده‌ایم و توسط splatted hash table (با کمک @) به دستور New-ModuleManifest ارسال کرده‌ایم. به این معنا که کلیدها و مقادیر درون hash table به جای اینکه یکجا به عنوان یک آبجکت به دستور موردنظر ارسال شوند، به صورت جدا پاس داده شده‌اند. اما اگر از $ استفاده میکردیم: 
$moduleSettings = @{
    Author = 'John Doe'
    Description = 'This is a sample module'
}

New-ModuleManifest $moduleSettings
با خطای زیر مواجه میشدیم: 
New-ModuleManifest : A parameter cannot be found that matches parameter name 'Author'.
در نهایت فایل manifest در مسیر تعیین شده ایجاد خواهد شد: 
#
# Module manifest for module 'PingModule'
#
# Generated by: sirwanafifi
#
# Generated on: 01/01/2023
#

@{

    # Script module or binary module file associated with this manifest.
    RootModule           = './PingModule.psm1'

    # Version number of this module.
    ModuleVersion        = '1.0.0'

    # Supported PSEditions
    CompatiblePSEditions = 'Core', 'Desktop'

    # ID used to uniquely identify this module
    GUID                 = '3f8561fc-c004-4c8e-b2fc-4a4191504131'

    # Author of this module
    Author               = 'sirwanafifi'

    # Company or vendor of this module
    CompanyName          = 'Unknown'

    # Copyright statement for this module
    Copyright            = '(c) sirwanafifi. All rights reserved.'

    # Description of the functionality provided by this module
    Description          = 'A module to ping a remote system'

    # Minimum version of the PowerShell engine required by this module
    PowerShellVersion    = '5.1'

    # Name of the PowerShell host required by this module
    # PowerShellHostName = ''

    # Minimum version of the PowerShell host required by this module
    # PowerShellHostVersion = ''

    # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
    # DotNetFrameworkVersion = ''

    # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
    # ClrVersion = ''

    # Processor architecture (None, X86, Amd64) required by this module
    # ProcessorArchitecture = ''

    # Modules that must be imported into the global environment prior to importing this module
    # RequiredModules = @()

    # Assemblies that must be loaded prior to importing this module
    # RequiredAssemblies = @()

    # Script files (.ps1) that are run in the caller's environment prior to importing this module.
    # ScriptsToProcess = @()

    # Type files (.ps1xml) to be loaded when importing this module
    # TypesToProcess = @()

    # Format files (.ps1xml) to be loaded when importing this module
    # FormatsToProcess = @()

    # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
    # NestedModules = @()

    # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
    FunctionsToExport    = 'Get-PingReply'

    # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
    CmdletsToExport      = '*'

    # Variables to export from this module
    VariablesToExport    = '*'

    # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
    AliasesToExport      = '*'

    # DSC resources to export from this module
    # DscResourcesToExport = @()

    # List of all modules packaged with this module
    # ModuleList = @()

    # List of all files packaged with this module
    # FileList = @()

    # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
    PrivateData          = @{

        PSData = @{

            # Tags applied to this module. These help with module discovery in online galleries.
            # Tags = @()

            # A URL to the license for this module.
            # LicenseUri = ''

            # A URL to the main website for this project.
            # ProjectUri = ''

            # A URL to an icon representing this module.
            # IconUri = ''

            # ReleaseNotes of this module
            # ReleaseNotes = ''

            # Prerelease string of this module
            # Prerelease = ''

            # Flag to indicate whether the module requires explicit user acceptance for install/update/save
            # RequireLicenseAcceptance = $false

            # External dependent modules of this module
            # ExternalModuleDependencies = @()

        } # End of PSData hashtable

    } # End of PrivateData hashtable

    # HelpInfo URI of this module
    # HelpInfoURI = ''

    # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
    # DefaultCommandPrefix = ''

}
هر ماژول باید یک آی‌دی منحصر به فرد داشته باشد که به صورت Guid توسط یک پراپرتی با همین نام تعیین میشود. برای هر پراپرتی درون این فایل توضیحی به صورت کامنت نوشته شده است؛ اما برای دیدن جزئیات کامل میتوانید به اینجا مراجعه نمائید. در اینجا RootModule به PingModule.psm1 تنظیم شده است. تنظیم این پراپرتی نحوه نمایش module type را در خروجی Get-Module مشخص میکند. این مقدار اگر به فایل ماژول (psm1) اشاره کند، نوع ماژول script در نظر گرفته میشود، اگر به یک DLL اشاره کند به binary و در نهایت اگر به یک فایلی با پسوند دیگری اشاره کند manifest در نظر گرفته میشود. همچنین درون فایل فوق یکسری پراپرتی مانند CmdletsToExport, VariablesToExport, AliasesToExport به صورت خودکار تنظیم شده‌اند. در نهایت برای تست صحت فایل میتوانیم از دستور Test-ModuleManifest استفاده کنیم: 
PS /> Test-ModuleManifest ./PingModule.psd1   

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     1.0.0                 PingModule                          Get-PingReply
پابلیش ماژول
در نهایت توسط Publish-Module میتوانیم ماژول موردنظرمان را درون یک مخزن PowerShell پابلیش کنیم. رایج‌ترین مخزن برای انتشار پکیج‌های PowerShell یک فید نیوگت (مانند PowerShell Gallery) است. همچنین میتوانیم یک مخزن لوکال نیز برای پابلیش پکیج‌ها نیز ایجاد کنیم و خروجی نهایی را به صورت یک فایل با پسوند nupkg با دیگران به اشتراک بگذاریم. برای اینکار ابتدا نیاز است یک مخزن لوکال را به PowerShell معرفی کنیم: 
PS /> Register-PSRepository -Name 'PSLocal' `                       
>>     -SourceLocation "$(Resolve-Path $RepoPath)" `
>>     -PublishLocation "$(Resolve-Path $RepoPath)" `
>>     -InstallationPolicy 'Trusted'
در کد فوق میتوانید مقدار متغیر RepoPath را به محل مدنظر روی سیستم‌تان تنظیم کنید. ساختار ماژولی که میخواهیم پابلیش کنیم نیز اینچنین خواهد بود: 
ProjectRoot 
| -- PingModule
     | -- PingModule.psd1 
     | -- PingModule.psm1
در ادامه برای پابلیش ماژول فوق درون ProjectRoot دستور Publish-Module را اینگونه اجرا خواهیم کرد: 
PS /> Publish-Module -Path ./PingModule/ -Repository PSLocal
خروجی دستور فوق یک فایل با پسوند nupkg در مسیر مخزنی است که معرفی کردیم. همچنین با کمک دستور Find-Module میتوانیم ماژول موردنظر را لیست کنیم: 
PS /> Find-Module -Name PingModule -Repository PSLocal

Version              Name                                Repository           Description
-------              ----                                ----------           -----------
0.0.1                PingModule                          PSLocal              Get-PingReply is a.
برای نصب ماژول روی یک سیستم دیگر نیز ابتدا باید مطمئن شوید که سیستم مقصد، مخزن لوکال را تعریف کرده باشد و در نهایت توسط دستور Install-Module میتوانیم ماژول موردنظر را نصب کنیم: 
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
همچنین امکان پابلیش کردن ورژن‌های متفاوت را نیز خواهیم داشت: 
ProjectRoot 
| -- PingModule
     | -- 1.0.0
          | -- PingModule.psd1 
          | -- PingModule.psm1
     | -- 1.0.1
          | -- PingModule.psd1 
          | -- PingModule.psm1
برای پابلیش کردن هر کدام از ماژول‌های فوق باید به ازای هر کدام یکبار دستور Publish-Module را اجرا کنیم: 
PS /> Publish-Module -Path ./PingModule/1.0.0 -Repository PSLocal
PS /> Publish-Module -Path ./PingModule/1.0.1 -Repository PSLocal
اکنون حین نصب ماژول میتوانیم ورژن را نیز تعیین کنیم؛ در غیراینصورت آخرین ورژن یعنی 1.0.1 نصب خواهد شد: 
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
PS /> Get-InstalledModule -Name PingModule                                                                            
Version              Name                                Repository           Description
-------              ----                                ----------           -----------
1.0.1                PingModule                          PSLocal              Get-PingReply is a.
ساختار مناسب برای ایجاد ماژول‌ها
برای توسعه ماژول‌های PowerShell توصیه میشود که از ساختار Multi-file layout استفاده شود به این معنا که بخش‌های مختلف پروژه به قسمت‌های کوچک‌تر و قابل‌نگهداری تقسیم شوند: 
ProjectRoot 
| -- PingModule
     | -- 1.0.0
          | -- Public/
          | -- Private/
          | -- PingModule.psd1
          | -- PingModule.psm1
در اینحالت باید اسکریپت‌ها و فایل‌های موردنیاز را توسط dot sourcing درون فایل ماژول بارگذاری کنیم: 
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}

$ScriptList = Get-ChildItem -Path $PSScriptRoot/Private/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}
یک مثال عملی
در ادامه میخواهیم یک ماژول تهیه کنیم که قابلیت امضاء روی PDF را با کمک کتابخانه iTextSharp.LGPLv2.Core انجام دهیم و به شکل زیر برای کاربر قابل استفاده باشد: 
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
بنابراین ساختار پروژه را اینگونه ایجاد خواهیم کرد: 
ProjectRoot 
| -- SignPdf
     | -- 1.0.0
          | -- Public/
               | -- dependencies/
                    | -- BouncyCastle.Crypto.dll
                    | -- System.Drawing.Common.dll
                    | -- Microsoft.Win32.SystemEvents.dll
                    | -- iTextSharp.LGPLv2.Core.dll
               | -- Set-PDFSingature.ps1
          | -- SignPdf.psd1
          | -- SignPdf.psm1
برای استفاده از پکیج ذکر شده نیاز خواهد بود که Dllهای موردنیاز را نیز به عنوان وابستگی به پروژه اضافه کنیم (محتویات پوشه dependencies) سپس درون فایل ماژول (SignPdf.psm1) همانطور که عنوان شد میبایست اسکریپت‌ها درون پوشه Public را بارگذاری کنیم و همچنین درون تابعی که قرار است Export شود را نیز تعیین کرده‌ایم (Set-PDFSingature) 
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}

Export-ModuleMember -Function Set-PDFSingature
در ادامه درون فایل Set-PDFSignature پیاده‌سازی را انجام خواهیم داد: 
using namespace iTextSharp.text
using namespace iTextSharp.text.pdf
using namespace System.IO

Function Set-PDFSingature {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                if (Test-Path ([Path]::Join($(Get-Location), $_))) {
                    return $true
                }
                else {
                    throw "Signature image not found"
                }
                if ($_.EndsWith('.pdf')) {
                    return $true
                }
                else {
                    throw "File extension must be .pdf"
                }
            })]
        [string]$PdfToSign,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                if (Test-Path ([Path]::Join($(Get-Location), $_))) {
                    return $true
                }
                else {
                    throw "Signature image not found"
                }
                if ($_.EndsWith('.png') -or $_.EndsWith('.jpg')) {
                    return $true
                }
                else {
                    throw "File extension must be .png or .jpg"
                }
            })]
        [string]$SignatureImage,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [int]$XPos = 130,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [int]$YPos = 50
    )
    Try {
        Add-Type -Path "$PSScriptRoot/dependencies/*.dll"
        $pdf = [PdfReader]::new("$(Get-Location)/$PdfToSign")
        $fs = [FileStream]::new("$(Get-Location)/$PdfToSign-signed.pdf", 
            [FileMode]::Create)
        $stamper = [PdfStamper]::new($pdf, $fs)
        $stamper.AcroFields.AddSubstitutionFont([BaseFont]::CreateFont())
        $content = $stamper.GetOverContent(1)
        $width = $pdf.GetPageSize(1).Width
        $image = [Image]::GetInstance("$(Get-Location)/$SignatureImage")
        $image.SetAbsolutePosition($width - $XPos, $YPos)
        $image.ScaleAbsolute(100, 30)
        $content.AddImage($image)
        $stamper.Close()
        $pdf.Close()
        $fs.Dispose()
    }
    Catch {
        Write-Host "Error: $($_.Exception.Message)"
    }
}
روال قرار دادن یک امضاء بر روی یک فایل PDF قبلاً در سایت توضیح داده شده است. کدهای فوق در واقع معادل PowerShell همان کدهای موجود در سایت هستند و نکته خاصی ندارند. در نهایت میتوانیم ماژول تهیه شده را روی مخزن موردنظر پابلیش کنیم: 
PS /> Publish-Module -Path ./SignPdf/1.0.0 -Repository PSLocal
برای نصب ماژول میتوانیم از دستور Install-Module استفاده کنیم: 
PS /> Install-Module -Name SignPdf -Repository PSLocal -Scope CurrentUser
در نهایت برای استفاده از ماژول ایجاد شده میتوانیم اینگونه عمل کنیم: 
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
خروجی نیز فایل امضاء شده خواهد بود: 

کدهای ماژول را میتوانید از اینجا دانلود کنید.