مطالب
توزیع پروژه‌های ASP.NET MVC بدون ارائه فایل‌های View آن
پروژه دیگری از آقای David Ebbo (عضو تیم ASP.NET که پیشتر با پروژه T4 MVC آن در این سایت آشنا شده‌اید)، جهت کامپایل کامل فایل‌های View و ارائه پروژه نهایی ASP.NET MVC بدون نیاز به ارائه پوشه Views آن به نام Razor Generator وجود دارد که در ادامه خلاصه‌ای از نحوه استفاده از آن‌را مرور خواهیم کرد.

الف) ابتدا افزونه Razor Generator را از اینجا دریافت و نصب کنید.
ب) سپس به پروژه MVC خود بسته NuGet زیر را اضافه نمائید:
 PM> Install-Package RazorGenerator.Mvc
در این حالت باید پروژه پیش فرض، همان وب سایت MVC شما انتخاب گردد:


با اضافه کردن این بسته NuGet تغییرات زیر به پروژه جاری اعمال خواهند شد:
  - ارجاعی به اسمبلی RazorGenerator.Mvc.dll به پروژه اضافه خواهد شد.
  - در پوشه App_Start، فایلی به نام RazorGeneratorMvcStart.cs اضافه گردیده و کار تنظیم موتور View مخصوص کار با Viewهای کامپایل شده را انجام می‌دهد.

ج) پس از نصب بسته NuGet یاد شده در همان خط فرمان پاورشل نوگت دستور زیر را صادر کنید:
 PM> Enable-RazorGenerator
و ... همین!

پس از انجام اینکار، دو کار صورت خواهد گرفت:
  - برای تمام Viewهای برنامه، فایل cs متناظری تولید می‌شود که ذیل فایل‌های View قابل مشاهده است.
  - گزینه Custom tool این Viewها نیز به RazorGenerator تنظیم می‌شود.


بدیهی است اگر از دستور Enable-RazorGenerator استفاده نکنید، نیاز خواهید داشت تا تنظیم گزینه Custom tool به RazorGenerator کلیه Viewها را دستی انجام داده و اگر فایل cs متناظر با View تولید نشد روی فایل view کلیک راست کرده و گزینه run custom tool را انتخاب کنید. اما دستور Enable-RazorGenerator کار را بسیار ساده می‌کند.

مزایا:
  - در عمل موتور ASP.NET همین کارها را در زمان اولین بار اجرای Viewها(ی کامپایل نشده) در پشت صحنه انجام می‌دهد. بنابراین با این روش زمان آغاز برنامه سریعتر می‌شود.
  - دیگر نیازی به توزیع فایل‌های cshtml نخواهید داشت.
  - خطایابی Viewها نیز ساده‌تر می‌شود. از این جهت که کامپایل آن‌ها به زمان اجرا موکول نخواهد شد.

یک سری قابلیت‌های دیگر نیز به همراه این پروژه است مانند انتقال Viewها به یک اسمبلی دیگر و یا استفاده از MSBuild برای انجام عملیات که می‌توانید آن‌ها را در Wiki پروژه Razor Generator مطالعه کنید. انتقال Viewها به یک اسمبلی دیگر هرچند در این روش کاملا ممکن شده و کار می‌کند اما صفحه dialog افزودن یک view جدید مهیا در کلیک راست بر روی اکشن متدهای یک کنترلر را غیرفعال می‌کند که در عمل آنچنان جالب نیست.


یک نکته مهم:
اگر در آینده بسته NuGet و افزونه یاد شده را به روز کردید نیاز است دستور زیر را اجرا کنید:
 PM> Redo-RazorGenerator
به این ترتیب بر اساس ساختار و کدهای جدید RazorGenerator، کلیه فایل‌های cs تولید شده مجددا به روز و تولید خواهند شد.


فایل‌های Helper قرار گرفته در پوشه App_Code

اگر HTML Helperهای خود را توسط فایل‌های Razor قرار گرفته در پوشه App_Code تولید می‌کنید، پس از اجرای دستور Enable-RazorGenerator، برای این موارد نیز فایل‌های cs متناظری تولید می‌شود. با این تفاوت که Build Action آن‌ها بر روی Compile قرار ندارند که این مورد را باید دستی تنظیم کنید. همچنین حین استفاده از این توابع کمکی نیاز است فضای نام مرتبط را نیز در ابتدای فایل View خود ذکر کنید مثلا:
 @using MvcViewGenTest2.app_code
البته با استفاده از Razor Generaor دیگر نیازی به استفاده از پوشه App_Code نخواهد بود؛ از این جهت که کار کامپایل خودکار، به زمان اجرا موکول نخواهد شد. بنابراین اینبار در هر جایی که علاقمند بودید می‌توانید این فایل‌های کمکی را تولید و کامپایل کنید. فقط ذکر فضای نام مرتبط را در ابتدای View خود فراموش نکنید.


حذف فایل RazorGeneratorMvcStart.cs
 اگر علاقمند به استفاده از فایل پیش فرض RazorGeneratorMvcStart.cs نیستید و می‌خواهید موتورهای View اضافی را حذف کنید، ابتدا فایل RazorGeneratorMvcStart.cs را حذف کرده و سپس در فایل global.asax.cs تغییر زیر را اعمال نمائید:
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.WebPages;
using RazorGenerator.Mvc;

namespace MvcViewGenTest2
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            // Adding PrecompiledMvcEngine
            var engine = new PrecompiledMvcEngine(typeof(MvcApplication).Assembly);
            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(engine);
            VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
        }
    }
}
مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت هفتم
در قسمت ششم، یک صفحه ساده برای لاگین نوشتیم که عملا کار خاصی نمی‌کرد. حال می‌خواهیم در این قسمت روی UI آن کمی کار کنیم. دقت کنید که هدف این قسمت، آموزش زیبا سازی صفحات نیست؛ بلکه هدف، آشنایی شما با تنظیمات مهم UI است.
صفحه Login ای که در قسمت قبل نوشته شد، خود یک Content Page است و دارای یک Stack Layout با چینش عمودی است. داخل آن دو Entry برای گرفتن نام و نام کاربری وجود دارد و یک Button.
مشکلی که صفحه لاگین دارد این است که اگر در یک تبلت 10 اینچی هم باز شود، عرض Entry‌ها و Button هم 10 اینچ می‌شوند که ما قطعا این را نمی‌خواهیم. اینکه چه می‌خواهیم خود یک سوال است! ولی یکی از راه‌های متداول و آسان که برای صفحه لاگین هم قابلیت استفاده دارد این است که از المان‌های صفحه درخواست می‌شود مثلا 5 سانتی متر باشند. حال اگر صفحه نمایش بیشتر از 5 سانتی متر فضا داشت، دلیلی و ارزشی برای بیشتر بزرگ شدن المان‌های روی صفحه نیست. البته اگر صفحه نمایش کوچک‌تر از 5 سانتی متر هم باشد، اینکه المان‌های روی صفحه اصرار به 5 سانتی متر ماندن کنند، نتیجه جالبی نخواهد داشت! برای دستیابی به بهترین نتیجه، شما می‌توانید از WidthRequest و HeightRequest استفاده کنید. وقتی WidthRequest را برابر با 5 سانتی متر قرار دهید، در واقع دارید درخواست می‌کنید که این مقدار شود. اگر فضا بیشتر شود، المان مربوطه بزرگ‌تر از 5 سانتی متر نخواهد شد. اگر فضا کل فضایی که دارد، کمتر از 5 سانتی متر باشد و مثلا 4 سانتی متر باشد، آن هم 4 سانتی متر خواهد شد.
برای شروع سؤال پیش می‌آید که این WidthRequest را به کدامیک از Tag‌ها بدهیم؟ Content Page یا Stack Layout یا هر کدام از Entry‌ها و Button لاگین؟ اساسا وظیفه Layout به عهده Page نیست. در اکثر مواقع، اینکار با خود Stack Layout انجام می‌شود ( یا در سایر مثال‌ها با Grid - Flex Layout و ... ) تنظیم WidthRequest برابر با 5 سانتی متر، آن هم برای تک تک Entry‌ها و Button مربوطه هم اساسا ایده جالبی نیست.
برای این که Width Request برابر با 5 سانتی متر شود، باید به آن مقدار 320 داده شود. هر 64 تا می‌شود یک سانتی متر و 320 = 64 * 5
اگر Width Request را در مثال Login برابر با 320 قرار دهید، می‌بینید که کوچک نمی‌شود و کماکان کل فضای صفحه را می‌گیرد! علت این است که هر المان روی صفحه، از جمله Stack Layout، علاوه بر WidthRequest و HeightRequest که عرض و طول Child را نسبت به Parent مشخص می‌کند، دارای VerticalOptions و HorizontalOptions نیز هست. VerticalOptions و HorizontalOptions در کنار WidthRequest و HeightRequest، رابطه بین یک Parent و Child را مشخص می‌کنند (در این مثال رابطه بین Content Page و Stack Layout به عنوان Parent و Child). به صورت پیش فرض هر Child ( در اینجا Stack Layout ) به صورت عمودی و افقی Parent اش ( در اینجا Content Page ) را Fill می‌کند. علت پیش فرض بودن این رفتار این است که کمترین سربار ممکن را از لحاظ Performance دارد و خیلی از جاها هم مفید است. در اینجا چون Stack Layout قصد دارد Parent خود را Fill کند، دیگر WidthRequest برایش معنی ندارد. در ادامه من VerticalOptions را برابر با Start و HorizontalOptions را برابر با Center می کنم. این باعث می‌شود که Stack Layout ما از لحاظ عمودی در بالا و از لحاظ افقی در وسط Content Page قرار بگیرد و بیش از پنج سانتی متر بزرگ نشود و در صورتیکه Content Page خود فضای کمی داشته باشد ( مثلا در یک گوشی موبایل با صفحه نمایش خیلی کوچک باشد )، Stack Layout حتی از پنج سانتی متر کوچک‌تر هم می‌شود.
دقت کنید که دو Entry و Button داخل Stack Layout نیز Fill هستند، پس داخل Parent خود یعنی Stack Layout را پر می‌کنند. اگر به Stack Layout یک Background دهید، درک بهتری از آن چه که دارد اتفاق می‌افتد، خواهید داشت.
<StackLayout
      BackgroundColor="LightYellow"
      HorizontalOptions="Center"
      Orientation="Vertical"
      VerticalOptions="Start"
      WidthRequest="320">

مشکل دیگری که وجود دارد این است که اگر بابت کمبود فضا، Stack Layout کوچکتر از 5 سانتی متر شود، از بغل، کامل، به کناره‌های Parent خود می‌چسبد که اصلا جالب نیست. برای رفع این مشکل می‌توانیم از Padding (حاشیه داخلی) و Margin (حاشیه خارجی) استفاده کنیم. در عکس زیر، Background مربوط به Content Page را قرمز و Stack Layout را آبی کرده‌ایم. با اختصاص دادن یک سانتی متر (64) به Padding و نیم سانت به Margin، می‌توانید در عکس زیر تاثیر آن را ببینید:

در سمت راست، بالا و چپ Stack Layout، به اندازه نیم سانت حاشیه قرمز می‌بینید که تاثیر Margin است. همچنین المان‌های داخل Stack Layout نیز هر کدام یک سانت از اطراف فاصله دارند که تاثیر Padding است. در پایین Stack Layout، تا جایی که Content Page بزرگ شود، فضای قرمز می‌بینید، نیم سانت از این فضای قرمز به خاطر Margin بوده، ولی باقی مربوط به این است که VerticalOptions را برای Stack Layout، بر روی Start تنظیم کرده‌ایم که باعث می‌شود Stack Layout بالای Content Page قرار بگیرد و زیر آن تماما قرمز شود. حتی اگر "عرض" صفحه نمایش بزرگتر از عکس قبلی می‌بود، فضای قرمز از راست و چپ بیشتر می‌شد که نیم سانت آن بابت Margin بوده و بیشتر از آن مربوط به HorizontalOptions است که برای Stack Layout روی Center تنظیم شده.

با توجه به اینکه Page - Layout - Control‌ها در Android-iOS-Windows به معادل‌های Native خود تبدیل می‌شوند و برای مثال ظاهر Button در این سه پلتفرم متفاوت است، ممکن است Padding ای که به یک کنترل داده ایم و در اندروید خوب به نظر می‌رسد، در iOS مقدار دیگری را لازم داشته باشد. مثلا در Android مقدار 2 و در iOS مقدار 3 لازم داشته باشد. یا ممکن است این مقدار به ازای این که در موبایل، تبلت یا دسکتاپ باشیم، لازم باشد که متفاوت باشد. در Xaml ما دو امکان OnPlatform و OnIdiom را داریم که هر Property اعم از Font Size - Padding - Text و ... را می‌توان به ازای هر شرایطی با مقداری متفاوت مقدار دهی نمود. برای مثال داریم:

<StackLayout
        Margin="{OnPlatform Android=3,
                            iOS='0,0,20,0',
                            UWP='2'}"
        Padding="{OnIdiom Default=2,
                          Tablet=3}" ...

البته Property‌های مهم دیگری نیز در UI تاثیر گذار هستند. برای مثال Opacity برای تنظیم شفافیت، FlowDirection برای تنظیم Right to left و Left to right بودن و خیلی Property‌های دیگه که به مرور و با بیشتر کار کردن با Xamarin Forms با آن‌ها آشنا می‌شوید.

در قسمت بعدی به نوشتن منطق می‌پردازیم؛ نحوه نمایش دادن خطا و رفتن از صفحه‌ای به صفحه‌ی دیگر. 

نظرات مطالب
ASP.NET MVC #5
- Page Inspector برای اینکار طراحی شده.
- فایل‌های cshtml در تمام ادیتور بصری موجود قابل گشودن و ویرایش هستند.
- اما ... اکثر ادیتورهای بصری قادر نیستند با بسیاری از فریم ورک‌های جدید CSS کار کنند؛ مانند بوت استرپ. طراحی و کار با آن‌ها عموما بدون ادیتورهای بصری و به کمک استفاده از مرورگرها انجام می‌شود. Razor هم به همین نحو است. صفحه را تغییر داده و Save کنید. بعد مرورگر را Refresh کنید (نیازی به کامپایل مجدد نیست).
- صفحات ASP.NET، یک سری صفحات پویا هستند. نیاز به برقراری اتصالات خاصی بین بانک اطلاعاتی، کوئری استرینگ‌ها، مقادیر Post شده به صفحه و غیره، برای نمایش اطلاعات خاصی است. طراح‌های بصری در یک چنین مواردی کارآمد نیستند و باید چرخه‌ی کامل طول عمر صفحه در مرورگر طی شود.
- خیلی از مسایل توسط طراح‌های بصری قابل پیاده سازی نیستند؛ برای مثال نوشتن یک if و else برای نمایش قسمتی از صفحه به کاربران اعتبارسنجی شده یا نمایش داده‌ها در یک حلقه.
مطالب
تبدیل پروژه ویراستار به دات نت

پروژه ویراستار را از Ruby به سی شارپ تبدیل کردم. سورس نهایی کامل، فایل‌های باینری، به همراه unit tests و راهنمای کتابخانه، از آدرس زیر قابل دریافت هستند:



خلاصه کارهایی را که انجام می‌دهد:



مطالب
پشتیبانی از IE در برنامه‌های Angular
اگر یک برنامه‌ی Angular را به صورت پیش‌فرض در IE اجرا کنیم، یک چنین تصویری مشاهده خواهد شد:


برای اجرای برنامه توسط نگارش‌های مختلف IE می‌توانید برنامه‌ی IE Tester را نصب کنید.


مشکل چیست؟

مشکل عدم اجرای برنامه‌های Angular در IE، به قدیمی بودن موتور JavaScript آن بر می‌گردد؛ خصوصا در مورد توابع کار با آرایه‌ها. برای مثال در مورد کار با for..of هیچ نوع پشتیبانی از آن در IE وجود ندارد (و نخواهد داشت؛ با توجه به پایان دوره‌ی پشتیبانی آن):



چگونه پشتیبانی از ویژگی‌های جدید JavaScript را به مرورگر IE اضافه کنیم؟

Angular امکان افزودن کمبودهای موتور JavaScript پیش‌فرض IE را توسط کتابخانه‌ی core-js میسر کرده‌است که اصطلاحا به آن polyfills گفته می‌شود. برای این منظور فایل src\polyfills.ts را گشوده و تغییرات زیر را به آن اعمال کنید:
الف) در این فایل، هرجایی  import // وجود دارد، آن‌را تبدیل به import کنید (تمام importهایی که کامنت شده‌اند را از حالت کامنت خارج کنید).
ب) دو بسته‌ی زیر را نیز باید نصب کنید:
npm install --save classlist.js
npm install --save web-animations-js
البته اگر به کامنت‌های این فایل دقت کنید، نیاز به نصب این بسته‌ها نیز در آن عنوان شده‌است.


نتیجه‌ی نهایی پس از افزودن polyfills مخصوص IE

اکنون اگر مجددا برنامه را کامپایل و اجرا کنید، برنامه‌ی Angular بدون مشکل در IE اجرا خواهد شد:

نظرات اشتراک‌ها
دریافت Twitter.BootstrapRTL 3.0.0 RC2 از Nuget
سلام
با تشکر از زحمات ارزنده شما
حین استفاده از این فریم ورک ، فکر کنم این قسمت باید به این صورت تغییر پیدا کنه
@media (min-width: 768px) {
    .navbar-nav {
        float: right;
        padding-top: 0;
        padding-bottom: 0;
        margin: 0;
    }

        .navbar-nav > li {
            float: right;
        }
قسمت آخر یعنی float:right مد نظر بنده می‌باشد.
مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
در این مطلب مثالی را در مورد نحوه‌ی تنظیمات یک پروژه‌ی خالی ASP.NET Core، جهت استفاده‌ی از یک پروژه‌ی Angular CLI قرار گرفته‌ی در پوشه‌ی آن‌را بررسی خواهیم کرد.

پیشنیازها

 - مطالعه‌ی سری کار با Angular CLI خصوصا قسمت نصب و قسمت ساخت برنامه‌های آن، پیش از مطالعه‌ی این مطلب ضروری است.
 - همچنین فرض بر این است که سری ASP.NET Core را نیز یکبار مرور کرده‌اید و با نحوه‌ی برپایی یک برنامه‌ی MVC آن و ارائه‌ی فایل‌های استاتیک توسط یک پروژه‌ی ASP.NET Core آشنایی دارید.


ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017

در ابتدا یک پروژه‌ی خالی ASP.NET Core را در VS 2017 ایجاد خواهیم کرد. برای این منظور:
 - ابتدا از طریق منوی File -> New -> Project (Ctrl+Shift+N) گزینه‌ی ایجاد یک ASP.NET Core Web application را انتخاب کنید.
 - در صفحه‌ی بعدی آن هم گزینه‌ی «empty template» را انتخاب نمائید.


تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI

برای اجرای یک برنامه‌ی مبتنی بر Angular CLI، نیاز است بر روی فایل csproj برنامه‌ی ASP.NET Core کلیک راست کرده و گزینه‌ی Edit آن‌را انتخاب کنید.
سپس محتوای این فایل را به نحو ذیل تکمیل نمائید:

الف) درخواست عدم کامپایل فایل‌های TypeScript
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
  </PropertyGroup>
چون نحوه‌ی کامپایل پروژه‌های Angular CLI صرفا مبتنی بر کامپایل مستقیم فایل‌های TypeScript آن نیست و در اینجا از یک گردش کاری توکار مبتنی بر webpack، به صورت خودکار استفاده می‌کند، کامپایل فایل‌های TypeScript توسط ویژوال استودیو، مفید نبوده و صرفا سبب دریافت گزارش‌های خطای بیشماری به همراه کند کردن پروسه‌ی Build آن خواهد شد. بنابراین با افزودن تنظیم TypeScriptCompileBlocked به true، از VS 2017 خواهیم خواست تا در این زمینه دخالت نکند.

ب) مشخص کردن پوشه‌هایی که باید الحاق و یا حذف شوند
  <ItemGroup>
    <Folder Include="Controllers\" />
    <Folder Include="wwwroot\" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="node_modules\**" />
    <Content Remove="node_modules\**" />
    <EmbeddedResource Remove="node_modules\**" />
    <None Remove="node_modules\**" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="src\**" />
    <Content Remove="src\**" />
    <EmbeddedResource Remove="src\**" />    
  </ItemGroup>
در اینجا پوشه‌های کنترلرها و wwwroot به پروژه الحاق شده‌اند. پوشه‌ی wwwroot جایی است که فایل‌هایی خروجی را Angular CLI ارائه خواهد کرد.
سپس دو پوشه‌ی node_modules و src واقع در ریشه‌ی پروژه را نیز به طور کامل از سیستم ساخت و توزیع VS 2017 حذف کرده‌ایم. پوشه‌ی node_modules وابستگی‌های Angular را به همراه دارد و src همان پوشه‌ی برنامه‌ی Angular ما خواهد بود.

ج) افزودن وابستگی‌های سمت سرور مورد نیاز
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
  </ItemGroup>
 
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
  </ItemGroup>
 
  <ItemGroup>
    <!-- extends watching group to include *.js files -->
    <Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" />
  </ItemGroup>
برای اجرای یک برنامه‌ی تک صفحه‌ای وب Angular، صرفا به وابستگی MVC و StaticFiles آن نیاز است.
در اینجا Watcher.Tools هم به همراه تنظیمات آن اضافه شده‌اند که در ادامه‌ی بحث به آن اشاره خواهد شد.


افزودن یک کنترلر Web API جدید

با توجه به اینکه دیگر در اینجا قرار نیست با فایل‌های cshtml و razor کار کنیم، کنترلرهای ما نیز از نوع Web API خواهند بود. البته در ASP.NET Core، کنترلرهای معمولی آن، توانایی ارائه‌ی Web API و همچنین فایل‌های Razor را دارند و از این لحاظ تفاوتی بین این دو نیست و یکپارچگی کاملی صورت گرفته‌است.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
 
namespace ASPNETCoreIntegrationWithAngularCLI.Controllers
{
  [Route("api/[controller]")]
  public class ValuesController : Controller
  {
    // GET: api/values
    [HttpGet]
    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
    public IEnumerable<string> Get()
    {
      return new string[] { "Hello", "DNT" };
    }
  }
}
در اینجا کدهای یک کنترلر نمونه را جهت بازگشت یک خروجی JSON ساده مشاهده می‌کنید که در ادامه، در برنامه‌ی Angular CLI تهیه شده از آن استفاده خواهیم کرد.


تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular

در ادامه به فایل Startup.cs برنامه‌ی خالی جاری، مراجعه کرده و آن‌را به نحو ذیل تغییر دهید:
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace ASPNETCoreIntegrationWithAngularCLI
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
 
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
 
            app.Use(async (context, next) => {
                await next();
                if (context.Response.StatusCode == 404 &&
                    !Path.HasExtension(context.Request.Path.Value) &&
                    !context.Request.Path.Value.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }
            });
 
            app.UseMvcWithDefaultRoute();
            app.UseDefaultFiles();
            app.UseStaticFiles();
        }
    }
}
در اینجا برای ارائه‌ی کنترلر Web API، نیاز به ثبت سرویس‌های MVC است. همچنین ارائه‌ی فایل‌های پیش فرض و فایل‌های استاتیک (همان پوشه‌ی wwwroot برنامه) نیز فعال شده‌اند.
در قسمت app.Use آن، تنظیمات URL Rewriting مورد نیاز جهت کار با مسیریابی برنامه‌های Angular را مشاهده می‌کنید. برای نمونه اگر کاربری در ابتدای کار آدرس /products را درخواست کند، این درخواست به سمت سرور ارسال می‌شود و چون چنین صفحه‌ای در سمت سرور وجود ندارد، خطای 404 بازگشت داده می‌شود و کار به پردازش برنامه‌ی Angular نخواهد رسید. اینجا است که تنظیم میان‌افزار فوق، کار مدیریت خروجی‌های 404 را بر عهده گرفته و کاربر را به فایل index.html برنامه‌ی تک صفحه‌ای وب، هدایت می‌کند. به علاوه در اینجا اگر درخواست وارد شده، دارای پسوند باشد (یک فایل باشد) و یا با api/ شروع شود (اشاره کننده‌ی به کنترلرهای Web API برنامه)، از این پردازش و هدایت به صفحه‌ی index.html معاف خواهد شد.


ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری

اکنون از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core‌، جائیکه فایل Startup.cs قرار دارد، وارد شده و دستور ذیل را اجرا کنید:
 >ng new ClientApp --routing --skip-install --skip-git --skip-commit
به این ترتیب پوشه‌ی جدید ClientApp، در داخل پوشه‌ی برنامه اضافه خواهد شد که در آن تنظیمات اولیه‌ی مسیریابی Angular نیز انجام شده‌است؛ از دریافت وابستگی‌های npm آن صرفنظر شده و همچنین کار تنظیمات git آن نیز صورت نگرفته‌است (تا از تنظیمات git پروژه‌ی اصلی استفاده شود).
پس از تولید ساختار برنامه‌ی Angular CLI، به پوشه‌ی آن وارد شده و تمام فایل‌های آن را Cut کنید. سپس به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core جاری، وارد شده و این فایل‌ها را در آنجا paste نمائید. به این ترتیب به حداکثر سازگاری ساختار پروژه‌های Angular CLI و VS 2017 خواهیم رسید. زیرا اکثر فایل‌های تنظیمات آن‌را می‌شناسد و قابلیت پردازش آن‌ها را دارد.
پس از این cut/paste، پوشه‌ی خالی ClientApp را نیز حذف کنید.


تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot

برای اینکه سیستم Build پروژه‌ی Angular CLI جاری، خروجی خود را در پوشه‌ی wwwroot قرار دهد، تنها کافی است فایل .angular-cli.json را گشوده و outDir آن‌را به wwwroot تنظیم کنیم:
"apps": [
    {
      "root": "src",
      "outDir": "wwwroot",
به این ترتیب پس از هر بار build آن، فایل‌های index.html و تمام فایل‌های js نهایی، در پوشه‌ی wwwroot که در فایل Startup.cs‌، کار عمومی کردن آن انجام شد، تولید می‌شوند.


فراخوانی کنترلر Web API برنامه در برنامه‌ی Angular CLI

در ادامه صرفا جهت آزمایش برنامه، فایل src\app\app.component.ts را گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http'; 
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private _httpService: Http) { }
 
  apiValues: string[] = [];
 
  ngOnInit() {
    this._httpService.get('/api/values').subscribe(values => {
      this.apiValues = values.json() as string[];
    });
  }
}
در اینجا خروجی JSON کنترلر Web API برنامه دریافت شده و به آرایه‌ی apiValues انتساب داده می‌شود.

سپس این آرایه را در فایل قالب این کامپوننت (src\app\app.component.html) استفاده خواهیم کرد:
<h1>Application says:</h1>
<ul *ngFor="let value of apiValues">
  <li>{{value}}</li>
</ul>
<router-outlet></router-outlet>
در اینجا یک حلقه ایجاد شده و عناصر آرایه‌ی apiValues به صورت یک لیست نمایش داده می‌شوند.


نصب وابستگی‌های برنامه‌ی Angular CLI

در ابتدای ایجاد پوشه‌ی ClientApp، از پرچم skip-install استفاده شد، تا صرفا ساختار پروژه، جهت cut/paste آن با سرعت هر چه تمام‌تر، ایجاد شود. اکنون برای نصب وابستگی‌های آن یا می‌توان در solution explorer به گره dependencies مراجعه کرده و npm را انتخاب کرد. در ادامه با کلیک راست بر روی آن، گزینه‌ی restore packages ظاهر می‌شود. و یا می‌توان به روش متداول این نوع پروژه‌ها، از طریق خط فرمان به پوشه‌ی ریشه‌ی پروژه وارد شد و دستور npm install را صادر کرد. بهتر است اینکار را از طریق خط فرمان انجام دهید تا مطمئن شوید که از آخرین نگارش‌های این ابزار که بر روی سیستم نصب شده‌است، استفاده می‌کنید.


روش اول اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

تا اینجا اگر برنامه را از طریق VS 2017 اجرا کنید، خروجی را مشاهده نخواهید کرد. چون هنوز فایل index.html آن تولید نشده‌است.
بنابراین روش اول اجرای این نوع برنامه‌ها، شامل مراحل ذیل است:
الف) ساخت پروژه‌ی Angular CLI در حالت watch
 > ng build --watch
برای اجرای آن از طریق خط فرمان، به پوشه‌ی ریشه‌ی پروژه وارد شده و دستور فوق را وارد کنید. به این ترتیب کار build پروژه انجام شده و همچنین فایل‌های نهایی آن در پوشه‌ی wwwroot قرار می‌گیرند. به علاوه چون از پرچم watch استفاده شده‌است، با هر تغییری در پوشه‌ی src برنامه، این فایل‌ها به صورت خودکار به روز رسانی می‌شوند. بنابراین این پنجره‌ی خط فرمان را باید باز نگه داشت تا watcher آن بتواند کارش را به صورت مداوم انجام دهد.

ب) اجرای برنامه از طریق ویژوال استودیو
اکنون که کار ایجاد محتوای پوشه‌ی wwwroot برنامه انجام شده‌است، می‌توان برنامه را از طریق VS 2017 به روش متداولی اجرا کرد:


یک نکته: می‌توان قسمت الف را تبدیل به یک Post Build Event هم کرد. برای این منظور باید فایل csproj را به نحو ذیل تکمیل کرد:
<Target Name="AngularBuild" AfterTargets="Build">
    <Exec Command="ng build" />
</Target>
به این ترتیب با هربار Build پروژه در VS 2017، کار تولید مجدد محتوای پوشه‌ی wwwroot نیز انجام خواهد شد.
تنها مشکل روش Post Build Event، کند بودن آن است. زمانیکه از روش ng build --watch به صورت مستقل استفاده می‌شود، برای بار اول اجرا، اندکی زمان خواهد برد؛ اما اعمال تغییرات بعدی به آن بسیار سریع هستند. چون صرفا نیاز دارد این تغییرات اندک و تدریجی را کامپایل کند و نه کامپایل کل پروژه را از ابتدا.


روش دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

روش دومی که در اینجا بررسی خواهد شد، مستقل است از قسمت «ب» روش اول که توضیح داده شد. برنامه‌های NET Core. نیز به همراه CLI خاص خودشان هستند و نیازی نیست تا حتما از VS 2017 برای اجرای آن‌ها استفاده کرد. به همین جهت وابستگی Microsoft.DotNet.Watcher.Tools را نیز در ابتدای کار به وابستگی‌های برنامه اضافه کردیم.

الف) در ادامه، VS 2017 را به طور کامل ببندید؛ چون نیازی به آن نیست. سپس دستور ذیل را در خط فرمان، در ریشه‌ی پروژه‌، صادر کنید:
> dotnet watch run
این دستور پروژه‌ی ASP.NET Core را کامپایل کرده و بر روی پورت 5000 ارائه می‌دهد:
>dotnet watch run
[90mwatch : [39mStarted
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
به علاوه پارامتر watch آن سبب خواهد شد تا هر تغییری که در کدهای پروژه‌ی ASP.NET Core صورت گیرند، بلافاصله کامپایل شده و قابل استفاده شوند.

ب) در اینجا چون برنامه بر روی پورت 5000 ارائه شده‌است، بهتر است دستور ng serve -o را صادر کرد تا بتوان به نحو ساده‌تری از سرور وب ASP.NET Core استفاده نمود. در این حالت برنامه‌ی Angular CLI بر روی پورت 4200 ارائه شده و بلافاصله در مرورگر نیز نمایش داده می‌شود.
مشکل! سرور وب ما بر روی پورت 5000 است و سرور آزمایشی Angular CLI بر روی پورت 4200. اکنون برنامه‌ی Angular ما، یک چنین درخواست‌هایی را به سمت سرور، جهت دریافت اطلاعات ارسال می‌کند: localhost:4200/api
برای رفع این مشکل می‌توان فایلی را به نام proxy.config.json با محتویات ذیل ایجاد کرد:
{
  "/api": {
    "target": "http://localhost:5000",
    "secure": false
  }
}
سپس دستور ng server صادر شده، اندکی متفاوت خواهد شد:
 >ng serve --proxy-config proxy.config.json -o
در اینجا به ng serve اعلام کرده‌ایم که تمامی درخواست‌های ارسالی به مسیر api/  (و یا همان localhost:4200/api جاری) را به سرور وب ASP.NET Core، بر روی پورت 5000 ارسال کن و نتیجه را در همینجا بازگشت بده. به این ترتیب مشکل عدم دسترسی به سرور وب، جهت تامین اطلاعات برطرف خواهد شد.
مزیت این روش، به روز رسانی خودکار مرورگر با انجام هر تغییری در کدهای قسمت Angular برنامه است.

نکته 1: بدیهی است می‌توان قسمت «ب» روش دوم را با قسمت «الف» روش اول نیز جایگزین کرد (ساخت پروژه‌ی Angular CLI در حالت watch). اینبار گشودن مرورگر بر روی پورت 5000 (و یا آدرس http://localhost:5000) را باید به صورت دستی انجام دهید. همچنین هربار تغییر در کدهای Angular، سبب refresh خودکار مرورگر نیز نمی‌شود که آن‌را نیز باید خودتان به صورت دستی انجام دهید (کلیک بر روی دکمه‌ی refresh پس از هر بار پایان کار ng build).
نکته 2: می‌توان قسمت «الف» روش دوم را حذف کرد (حذف dotnet run در حالت watch). یعنی می‌خواهیم هنوز هم ویژوال استودیو کار آغاز IIS Express را انجام دهد. به علاوه می‌خواهیم برنامه را توسط ng serve مشاهده کنیم (با همان پارامترهای قسمت «ب» روش دوم). در این حالت تنها موردی را که باید تغییر دهید، پورتی است که برای IIS Express تنظیم شده‌است. عدد این پورت را می‌توان در فایل Properties\launchSettings.json مشاهده کرد و سپس به تنظیمات فایل proxy.config.json اعمال نمود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: ASPNETCoreIntegrationWithAngularCLI.zip
به همراه این کدها تعدادی فایل bat نیز وجود دارند که جهت ساده سازی عملیات یاد شده‌ی در این مطلب، می‌توان از آن‌ها استفاده کرد:
 - فایل restore.bat کار بازیابی و نصب وابستگی‌های پروژه‌ی دات نتی و همچنین Angular CLI را انجام می‌دهد.
 - دو فایل ng-build-dev.bat و ng-build-prod.bat بیانگر قسمت «الف» روش اول هستند. فایل dev مخصوص حالت توسعه است و فایل prod مخصوص ارائه‌ی نهایی.
 - دو فایل dotnet_run.bat و ng-serve-proxy.bat خلاصه کننده‌ی قسمت‌های «الف» و «ب» روش دوم هستند.
مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
jqGrid یکی از افزونه‌های بسیار محبوب jQuery جهت نمایش جدول مانند اطلاعات، در سمت کلاینت است. توانمندی‌های آن صرفا به نمایش ستون‌ها و ردیف‌ها خلاصه نمی‌شود. قابلیت‌هایی مانند صفحه بندی، مرتب سازی، جستجو، ویرایش توکار، تولید خودکار صفحات افزودن رکوردها، اعتبارسنجی داده‌ها، گروه بندی، نمایش درختی و غیره را نیز به همراه دارد. همچنین به صورت توکار پشتیبانی از راست به چپ را نیز لحاظ کرده‌است.
 مجوز استفاده از فایل‌های جاوا اسکریپتی آن MIT است؛ به این معنا که در هر نوع پروژه‌ای قابل استفاده است. مجوز استفاده از کامپوننت‌های سمت سرور آن که برای نمونه جهت ASP.NET MVC یک سری HTML Helper را تدارک دیده‌اند، تجاری می‌باشد. در ادامه قصد داریم صرفا از فایل‌های JS عمومی آن استفاده کنیم.


دریافت jqGrid

برای دریافت jqGrid می‌توانید به مخزن کد آن، در آدرس https://github.com/tonytomov/jqGrid/releases و یا از طریق NuGet اقدام کنید:
 PM> Install-Package Trirand.jqGrid
استفاده از NuGet بیشتر توصیه می‌شود، زیرا به صورت خودکار وابستگی‌های jQuery و همچنین jQuery UI آن‌را نیز به همراه داشته و نصب خواهد کرد.
از jQuery UI برای تولید صفحات جستجوی بر روی رکوردها و همچنین تولید خودکار صفحات ویرایش و یا افزودن رکوردها استفاده می‌کند. به علاوه آیکن‌ها، قالب و رنگ خود را نیز از jQuery UI دریافت می‌کند. بنابراین اگر قصد تغییر قالب آن‌را داشتید تنها کافی است یک قالب استاندارد دیگر jQuery UI را مورد استفاده قرار دهید.


تنظیمات اولیه فایل Layout سایت

پس از دریافت بسته‌ی نیوگت jqGrid، نیاز است فایل‌های مورد نیاز اصلی آن‌را به شکل زیر به فایل layout پروژه اضافه کرد:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    
    <link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
    <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div>
        @RenderBody()
    </div>

    <script src="~/Scripts/jquery-1.7.2.min.js"></script>
    <script src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
    <script src="~/Scripts/i18n/grid.locale-fa.js"></script>
    <script src="~/Scripts/jquery.jqGrid.min.js"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>
فایل jquery.ui.all.css شامل تمامی فایل‌های CSS مرتبط با jQuery UI است و نیازی نیست تا سایر فایل‌های آن‌را لحاظ کرد.
این گرید به همراه فایل زبان فارسی grid.locale-fa.js نیز می‌باشد که در کدهای فوق پیوست شده‌است. البته اگر فرصت کردید نیاز است کمی ترجمه‌های آن بهبود پیدا کنند.


تنظیمات ثانویه site.css

.ui-widget {
}

/*how to move jQuery dialog close (X) button from right to left*/
.ui-jqgrid .ui-jqgrid-caption-rtl {
    text-align: center !important;
}

.ui-dialog .ui-dialog-titlebar-close {
    left: .3em !important;
}

.ui-dialog .ui-dialog-title {
    margin: .1em 0 .1em .8em !important;
    direction: rtl !important;
    float: right !important;
}
احتمالا تنظیمات قلم‌های jQuery UI و یا jqGrid مدنظر شما نیستند و نیاز به تعویض دارند. در اینجا نحوه‌ی بازنویسی آن‌ها را ملاحظه می‌کنید.
همچنین محل قرار گیری دکمه‌ی بسته شدن دیالوگ‌ها و راست به چپ کردن عناوین آن‌ها نیز در اینجا قید شده‌اند.


مدل برنامه

در ادامه قصد داریم لیستی از محصولات را با ساختار ذیل، توسط jqGrid نمایش دهیم:
namespace jqGrid01.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}


ساختار داده‌ای مورد نیاز توسط jqGrid

jqGrid مستقل است از فناوری سمت سرور. بنابراین هر چند در عنوان بحث ASP.NET MVC ذکر شده‌است، اما از ASP.NET MVC صرفا جهت بازگرداندن خروجی JSON استفاده خواهیم کرد و این مورد در هر فناوری سمت سرور دیگری نیز می‌تواند انجام شود.
using System.Collections.Generic;

namespace jqGrid01.Models
{
    public class JqGridData
    {
        public int Total { get; set; }

        public int Page { get; set; }

        public int Records { get; set; }

        public IList<JqGridRowData> Rows { get; set; }

        public object UserData { get; set; }
    }

    public class JqGridRowData
    {
        public int Id { set; get; }
        public IList<string> RowCells { set; get; }
    }
}
خروجی JSON مدنظر توسط jqGrid، یک چنین ساختاری را باید داشته باشد.
Total، نمایانگر تعداد صفحات اطلاعات است. عدد Page، شماره صفحه‌ی جاری است. عدد Records، تعداد کل رکوردهای گزارش را مشخص می‌کند. ساختار ردیف‌های آن نیز تشکیل شده‌است از یک Id به همراه سلول‌هایی که باید با فرمت string، بازگشت داده شوند.
UserData اختیاری است. برای مثال اگر خواستید جمع کل صفحه را در ذیل گرید نمایش دهید، می‌توانید یک anonymous object را در اینجا مقدار دهی کنید. خاصیت‌های آن دقیقا باید با نام خاصیت‌های ستون‌های متناظر، یکی باشند. برای مثال اگر می‌خواهید عددی را در ستون Id، در فوتر گرید نمایش دهید، باید نام خاصیت را Id ذکر کنید.


کدهای سمت کلاینت گرید

در اینجا کدهای کامل سمت کلاینت گرید را ملاحظه می‌کنید:
@{
    ViewBag.Title = "Index";
}

<div dir="rtl" align="center">
    <div id="rsperror"></div>
    <table id="list" cellpadding="0" cellspacing="0"></table>
    <div id="pager" style="text-align:center;"></div>
</div>

@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('#list').jqGrid({
                caption: "آزمایش اول",
                //url from wich data should be requested
                url: '@Url.Action("GetProducts","Home")',
                //type of data
                datatype: 'json',
                jsonReader: { 
                    root: "Rows",
                    page: "Page",
                    total: "Total",
                    records: "Records",
                    repeatitems: true,
                    userdata: "UserData",
                    id: "Id",
                    cell: "RowCells"
                },
                //url access method type
                mtype: 'GET',
                //columns names
                colNames: ['شماره', 'نام محصول', 'موجود است', 'قیمت'],
                //columns model
                colModel: [
                { name: 'Id', index: 'Id', align: 'right', width: 50, sorttype: "number" },
                { name: 'Name', index: 'Name', align: 'right', width: 300 },
                { name: 'IsAvailable', index: 'IsAvailable', align: 'center', width: 100, formatter: 'checkbox' },
                { name: 'Price', index: 'Price', align: 'center', width: 100, sorttype: "number" }
                ],
                //pager for grid
                pager: $('#pager'),
                //number of rows per page
                rowNum: 10,
                rowList: [10, 20, 50, 100],
                //initial sorting column
                sortname: 'Id',
                //initial sorting direction
                sortorder: 'asc',
                //we want to display total records count
                viewrecords: true,
                altRows: true,
                shrinkToFit: true,
                width: 'auto',
                height: 'auto',
                hidegrid: false,
                direction: "rtl",
                gridview: true,
                rownumbers: true,
                footerrow: true,
                userDataOnFooter: true,
                loadComplete: function() {
                    //change alternate rows color
                    $("tr.jqgrow:odd").css("background", "#E0E0E0");
                },
                loadError: function(xhr, st, err) {
                     jQuery("#rsperror").html("Type: " + st + "; Response: " + xhr.status + " " + xhr.statusText);
                }
                //, loadonce: true
            })
            .jqGrid('navGrid', "#pager",
            {
                edit: false, add: false, del: false, search: false,
                refresh: true
            })
            .jqGrid('navButtonAdd', '#pager',
            {
                caption: "تنظیم نمایش ستون‌ها", title: "Reorder Columns",
                onClickButton: function() {
                     jQuery("#list").jqGrid('columnChooser');
                }
            });
        });
    </script>
}
- برای نمایش این گرید، به یک جدول و یک div نیاز است. از جدول با id مساوی list جهت نمایش رکوردهای برنامه استفاده می‌شود. از div با id مساوی pager برای نمایش اطلاعات صفحه بندی و نوار ابزار پایین گرید کمک گرفته خواهد شد.
Div سومی با id مساوی rsperror نیز تعریف شده‌است که از آن جهت نمایش خطاهای بازگشت داده شده از سرور استفاده کرده‌ایم.
- در ادامه نحوه‌ی فراخوانی افزونه‌ی jqGrid را بر روی جدول list ملاحظه می‌کنید.
- خاصیت caption، عنوان نمایش داده شده در بالای گرید را مقدار دهی می‌کند:


- خاصیت url، به آدرسی اشاره می‌کند که قرار است ساختار JqGridData ایی را که پیشتر در مورد آن بحث کردیم، با فرمت JSON بازگشت دهد. در اینجا برای مثال به یک اکشن متد کنترلری در یک پروژه‌ی ASP.NET MVC اشاره می‌کند.
- datatype را برابر json قرار داده‌ایم. از نوع xml نیز پشتیبانی می‌کند.
- شیء jsonReader را از این جهت مقدار دهی کرده‌ایم تا بتوانیم شیء JqGridData را با اصول نامگذاری دات نت، هماهنگ کنیم. برای درک بهتر این موضوع، فایل jquery.jqGrid.src.js را باز کنید و در آن به دنبال تعریف jsonReader بگردید. به یک چنین مقادیر پیش فرضی خواهید رسید:
ts.p.jsonReader = $.extend(true,{
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: true,
cell: "cell",
id: "id",
userdata: "userdata",
subgrid: {root:"rows", repeatitems: true, cell:"cell"}
},ts.p.jsonReader);
برای مثال سلول‌ها را با نام cell دریافت می‌کند که در شیء JqGridData به RowCells تغییر نام یافته‌است. برای اینکه این تغییر نام‌ها توسط jqGrid پردازش شوند، تنها کافی است jsonReader را مطابق تعاریفی که ملاحظه می‌کنید، مقدار دهی کرد.
- در ادامه mtype به GET تنظیم شده‌است. در اینجا مشخص می‌کنیم که عملیات Ajax ایی دریافت اطلاعات از سرور توسط GET انجام شود یا برای مثال توسط POST.
- خاصیت colNames، معرف نام ستون‌های گرید است. برای اینکه این نام‌ها از راست به چپ نمایش داده شوند، باید خاصیت direction به rtl تنظیم شود.
- colModel آرایه‌ای است که تعاریف ستون‌ها را در بر دارد. مقدار name آن باید یک نام منحصربفرد باشد. از این نام در حین جستجو یا ویرایش اطلاعات استفاده می‌شود. مقدار index نامی است که جهت مرتب سازی اطلاعات، به سرور ارسال می‌شود. تنظیم sorttype در اینجا مشخص می‌کند که آیا به صورت پیش فرض، ستون جاری رشته‌ای مرتب شود یا اینکه برای مثال عددی پردازش گردد. مقادیر مجاز آن text (مقدار پیش فرض)، float، number، currency، numeric، int ، integer، date و datetime هستند.
- در ستون IsAvailable، مقدار formatter نیز تنظیم شده‌است. در اینجا توسط formatter، نوع bool دریافتی با یک checkbox نمایش داده خواهد شد.
- خاصیت pager به id متناظری در صفحه اشاره می‌کند.
- توسط rowNum مشخص می‌کنیم که در هر صفحه چه تعداد رکورد باید نمایش داده شوند.
- تعداد رکوردهای نمایش داده شده را می‌توان توسط rowList پویا کرد. در اینجا آرایه‌ای را ملاحظه می‌کنید که توسط اعداد آن، کاربر امکان انتخاب صفحاتی مثلا 100 ردیفه را نیز پیدا می‌کند. rowList به صورت یک dropdown در کنار عناصر راهبری صفحه در فوتر گرید ظاهر می‌شود.
- خاصیت sortname، نحوه‌ی مرتب سازی اولیه گرید را مشخص می‌کند.
- خاصیت sortorder، جهت مرتب سازی اولیه‌ی گردید را تنظیم می‌کند.
- viewrecords: تعداد رکوردها را در نوار ابزار پایین گرید نمایش می‌دهد.
- altRows: سبب می‌شود رنگ متن ردیف‌ها یک در میان متفاوت باشد.
- shrinkToFit: به معنای تنظیم خودکار اندازه‌ی سلول‌ها بر اساس اندازه‌ی داده‌ای است که دریافت می‌کنند.
- width: عرض گرید، که در اینجا به auto تنظیم شده‌است.
- height: طول گرید، که در اینجا به auto جهت محاسبه‌ی خودکار، تنظیم شده‌است.
- gridview: برای بالا بردن سرعت نمایشی به true تنظیم شده‌است. در این حالت کل ردیف یکباره درج می‌شود. اگر از subgird یا حالت نمایش درختی استفاده شود، باید این خاصیت را false کرد.
- rownumbers: ستون سمت راست شماره ردیف‌های خودکار را نمایش می‌دهد.
- footerrow: سبب نمایش ردیف فوتر می‌شود.
- userDataOnFooter: سبب خواهد شد تا خاصیت UserData مقدار دهی شده، در ردیف فوتر ظاهر شود.
- loadComplete : یک callback است که زمان پایان بارگذاری صفحه‌ی جاری را مشخص می‌کند. در اینجا با استفاده از jQuery سبب شده‌ایم تا رنگ پس زمینه‌ی ردیف‌ها یک در میان تغییر کند.
- loadError: اگر از سمت سرور خطایی صادر شود، در این callback قابل دریافت خواهد بود.
- در ادامه توسط فراخوانی متد jqGrid با پارامتر navGrid، در ناحیه pager سبب نمایش دکمه refresh شده‌ایم. این دکمه سبب بارگذاری مجدد اطلاعات گردید از سرور می‌شود.
- همچنین به کمک متد jqGrid با پارامتر navButtonAdd در ناحیه pager، سبب نمایش دکمه‌ای که صفحه‌ی انتخاب ستون‌ها را ظاهر می‌کند، خواهیم شد.



پیشنیاز کدهای سمت سرور jqGrid

اگر به تنظیمات گرید دقت کرده باشید، خاصیت index ستون‌ها، نامی است که به سرور، جهت اطلاع رسانی در مورد فیلتر اطلاعات و مرتب سازی مجدد آن‌ها ارسال می‌گردد. این نام، بر اساس کلیک کاربر بر روی ستون‌های موجود، هر بار می‌توان متفاوت باشد. بنابراین بجای if و else نوشتن‌های طولانی جهت مرتب سازی اطلاعات، می‌توان از کتابخانه‌ی معروفی به نام dynamic LINQ استفاده کرد.
 PM> Install-Package DynamicQuery
به این ترتیب می‌توان قسمت orderby را به صورت پویا و با رشته‌ای دریافتی، مقدار دهی کرد.


کدهای سمت سرور بازگشت اطلاعات به فرمت JSON

در کدهای سمت کلاینت، به اکشن متد GetProducts اشاره شده بود. تعاریف کامل آن‌را در ذیل مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
using jqGrid01.Models;
using jqGrid01.Extensions; // for dynamic OrderBy

namespace jqGrid01.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult GetProducts(string sidx, string sord, int page, int rows)
        {
            var list = ProductDataSource.LatestProducts;

            var pageIndex = page - 1;
            var pageSize = rows;
            var totalRecords = list.Count;
            var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize);

            var products = list.AsQueryable()
                               .OrderBy(sidx + " " + sord)
                               .Skip(pageIndex * pageSize)
                               .Take(pageSize)
                               .ToList();

            var jqGridData = new JqGridData
            {
                UserData = new // نمایش در فوتر
                {
                    Name = "جمع صفحه",
                    Price = products.Sum(x => x.Price)
                },
                Total = totalPages,
                Page = page,
                Records = totalRecords,
                Rows = (products.Select(product => new JqGridRowData
                                                {
                                                    Id = product.Id,
                                                    RowCells = new List<string>
                                                    {
                                                        product.Id.ToString(CultureInfo.InvariantCulture),
                                                        product.Name,
                                                        product.IsAvailable.ToString(),
                                                        product.Price.ToString(CultureInfo.InvariantCulture)
                                                    }
                                                })).ToList()
            };
            return Json(jqGridData, JsonRequestBehavior.AllowGet);
        }
    }
}
- سطر ProductDataSource.LatestProducts چیزی نیست بجز لیست جنریکی از محصولات.
- امضای متد GetProducts نیز مهم است. دقیقا همین پارامترها با همین نام‌ها از طرف jqGrid به سرور ارسال می‌شوند که توسط آن‌ها ستون مرتب سازی، جهت مرتب سازی، صفحه‌ی جاری و تعداد ردیفی که باید بازگشت داده شوند، قابل دریافت است.
- در این کدها دو قسمت مهم وجود دارند:
الف) متد OrderBy نوشته شده، به صورت پویا عمل می‌کند و از کتابخانه‌ی Dynamic LINQ مایکروسافت بهره می‌برد.
به علاوه توسط Take و Skip کار صفحه بندی و بازگشت تنها بازه‌ای از اطلاعات مورد نیاز، انجام می‌شود.
ب) لیست جنریک محصولات، در نهایت باید با فرمت JqGridData به صورت JSON بازگشت داده شود. نحوه‌ی این Projection را در اینجا می‌توانید ملاحظه کنید.
هر ردیف این لیست، باید تبدیل شود به ردیفی از جنس JqGridRowData، تا توسط jqGrid قابل پردازش گردد.
- توسط مقدار دهی UserData، برچسبی را در ذیل ستون Name و مقداری را در ذیل ستون Price نمایش خواهیم داد.


برای مطالعه‌ی بیشتر

بهترین راهنمای جزئیات این Grid، مستندات آنلاین آن هستند: http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs
همچنین این مستندات را با فرمت PDF نیز می‌توانید مطالعه کنید: http://www.trirand.com/blog/jqgrid/downloads/jqgriddocs.pdf


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid01.zip
 

مثال‌های سری jqGrid تغییرات زیادی داشتند. برای دریافت آن‌ها به این مخزن کد مراجعه کنید. 
مطالب
فشرده سازی فایل های CSS و JavaScript بصورت خودکار توسط MS Ajax Minifier
با توجه به افزایش کاربرد jQuery و دیگر کتابخانه‌های جاوا اسکریپت در برنامه‌های تحت وب، یکی از چالش‌های همیشگی برنامه نویسان، فشرده سازی فایل‌های دربرگیرنده کدهای جاوا اسکریپت و شیوه نامه‌ها  می باشد. برای این منظور راه‌های مختلفی مانند استفاده از ابزارهای آنلاین مانند این +  و این +  وجود دارند. اما یک روش خودکار هم وجود دارد که در زمان Build پروژه‌های دات نت می‌توان از آن بهره گرفت.

Microsoft Ajax Minifier
یک ابزار رایگان جهت فشرده سازی فایل‌های جاوا اسکریپت و شیوه نامه‌ها است. شما می‌توانید این ابزار را از صفحه خانگی آن در سایت asp.net دریافت کنید. جهت استفاده از این ابزار می‌توان از طریق خط فرمان عمل کرد. اما روش ساده‌تر که هدف اصلی این مطلب است به شرح زیر است:
1. در VisualStudio.NET از طریق منو به مسیر Tools, Options, Projects and Solutions بروید و گزینه Always show solution را تیک بزنید.

2. از Solution Explorer بر روی عنوان پروژه کلیک راست کرده و گزینه Unload Project را انتخاب نمایید.

3. مجدداً روی عنوان پروژه کلیک راست کرده و گزینه Edit را انتخاب کنید و دستورات زیر را قبل از بسته شدن تگ Project اضافه کنید:
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<Target Name="AfterBuild">
<ItemGroup>
  <JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" />
</ItemGroup>
<ItemGroup>
  <CSS Include="**\*.css" Exclude="**\*.min.css" />
</ItemGroup>
<AjaxMin 
    JsSourceFiles="@(JS)"  JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js"
    CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css"  />
</Target>
4. دوباره بر روی عنوان پروژه کلیک راست کرده و گزینه Reload Project را انتخاب کنید.
توجه کنید که با این کار ما یک MSBuild task با عنوان ajaxmini به پروژه اضافه کردیم. این وظیفه که در زمان Build پروژه اجرا خواهد شد فایل‌های جاوا اسکریپت را فشرده و با پسوند .min.js و همچنین فایل‌های CSS را پس از فشرده سازی با پسوند .min.css در همان مسیر فایل مادر بطور خودکار ذخیره می‌کند.

نکته:
اگر به دستورات تنظیمات فوق نگاه دقیقتری بیندازیم، متوجه عبارات Include و Exclude می شویم. توسط این دو صفت شما می‌توانید الگوهایی را جهت فشرده سازی و یا عدم فشرده سازی تعریف کنید. بدین معنا که توسط الگوی‌های ذکر شده در تنظیمات فوق از فشرده سازی فایل‌های با پسوند .min.css و .min.js خودداری می‌شود.
در این شرایط در حین توسعه برنامه، شما می‌توانید از فایل‌های با کد خوانا استفاده نمایید و زمان انتشار و Build پروژه بصورت خودکار آنها را با فایل‌های فشرده جایگزین کنید.
این ابزار تمامی فضاهای خالی، ';‌' و '{ }'‌های اضافی و توضیحات را از کدهای شما حذف می‌کند. متغیر‌ها و توابع شما را به اسامی کوجک‌تر تغییر نام می‌دهد. و ...  
همچنین شما از کتابخانه این پروژه می‌توانید در زمان اجرا و سورس برنامه خود استفاده کنید. جهت اطلاعات بیشتر می‌توانید به سایت مربوطه  مراجعه نمایید.