مطالب
صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid
پس از آشنایی مقدماتی با Kendo UI DataSource، اکنون می‌خواهیم از آن جهت صفحه بندی، مرتب سازی و جستجوی پویای سمت سرور استفاده کنیم. در مثال قبلی، هر چند صفحه بندی فعال بود، اما پس از دریافت تمام اطلاعات، این اعمال در سمت کاربر انجام و مدیریت می‌شد.



مدل برنامه

در اینجا قصد داریم لیستی را با ساختار کلاس Product در اختیار Kendo UI گرید قرار دهیم:
namespace KendoUI03.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}


پیشنیاز تامین داده مخصوص Kendo UI Grid

برای ارائه اطلاعات مخصوص Kendo UI Grid، ابتدا باید درنظر داشت که این گرید، درخواست‌های صفحه بندی خود را با فرمت ذیل ارسال می‌کند. همانطور که مشاهده می‌کنید، صرفا یک کوئری استرینگ با فرمت JSON را دریافت خواهیم کرد:
 /api/products?{"take":10,"skip":0,"page":1,"pageSize":10,"sort":[{"field":"Id","dir":"desc"}]}
سپس این گرید نیاز به سه فیلد، در خروجی JSON نهایی خواهد داشت:
{
"Data":
[
{"Id":1500,"Name":"نام 1500","Price":2499.0,"IsAvailable":false},
{"Id":1499,"Name":"نام 1499","Price":2498.0,"IsAvailable":true}
],
"Total":1500,
"Aggregates":null
}
فیلد Data که رکوردهای گرید را تامین می‌کنند. فیلد Total که بیانگر تعداد کل رکوردها است و Aggregates که برای گروه بندی بکار می‌رود.

می‌توان برای تمام این‌ها، کلاس و Parser تهیه کرد و یا ... پروژه‌ی سورس بازی به نام  Kendo.DynamicLinq نیز چنین کاری را میسر می‌سازد که در ادامه از آن استفاده خواهیم کرد. برای نصب آن تنها کافی است دستور ذیل را صادر کنید:
 PM> Install-Package Kendo.DynamicLinq
Kendo.DynamicLinq به صورت خودکار System.Linq.Dynamic را نیز نصب می‌کند که از آن جهت صفحه بندی پویا استفاده خواهد شد.


تامین کننده‌ی داده سمت سرور

همانند مطلب کار با Kendo UI DataSource ، یک ASP.NET Web API Controller جدید را به پروژه اضافه کنید و همچنین مسیریابی‌های مخصوص آن‌را به فایل global.asax.cs نیز اضافه نمائید.
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Kendo.DynamicLinq;
using KendoUI03.Models;
using Newtonsoft.Json;

namespace KendoUI03.Controllers
{
    public class ProductsController : ApiController
    {
        public DataSourceResult Get(HttpRequestMessage requestMessage)
        {
            var request = JsonConvert.DeserializeObject<DataSourceRequest>(
                requestMessage.RequestUri.ParseQueryString().GetKey(0)
            );

            var list = ProductDataSource.LatestProducts;
            return list.AsQueryable()
                       .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter);
        }
    }
}
تمام کدهای این کنترلر همین چند سطر فوق هستند. با توجه به ساختار کوئری استرینگی که در ابتدای بحث عنوان شد، نیاز است آن‌را توسط کتابخانه‌ی JSON.NET تبدیل به یک نمونه از DataSourceRequest نمائیم. این کلاس در Kendo.DynamicLinq تعریف شده‌است و حاوی اطلاعاتی مانند take و skip کوئری LINQ نهایی است.
ProductDataSource.LatestProducts صرفا یک لیست جنریک تهیه شده از کلاس Product است. در نهایت با استفاده از متد الحاقی جدید ToDataSourceResult، به صورت خودکار مباحث صفحه بندی سمت سرور به همراه مرتب سازی اطلاعات، صورت گرفته و اطلاعات نهایی با فرمت DataSourceResult بازگشت داده می‌شود. DataSourceResult نیز در Kendo.DynamicLinq تعریف شده و سه فیلد یاد شده‌ی Data، Total و Aggregates را تولید می‌کند.

تا اینجا کارهای سمت سرور این مثال به پایان می‌رسد.


تهیه View نمایش اطلاعات ارسالی از سمت سرور

اعمال مباحث بومی سازی
<head>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Language" content="fa" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <title>Kendo UI: Implemeting the Grid</title>

    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <!--شیوه نامه‌ی مخصوص راست به چپ سازی-->
    <link href="styles/kendo.rtl.min.css" rel="stylesheet" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.all.min.js" type="text/javascript"></script>

    <!--محل سفارشی سازی پیام‌ها و مسایل بومی-->
    <script src="js/cultures/kendo.culture.fa-IR.js" type="text/javascript"></script>
    <script src="js/cultures/kendo.culture.fa.js" type="text/javascript"></script>
    <script src="js/messages/kendo.messages.en-US.js" type="text/javascript"></script>

    <style type="text/css">
        body {
            font-family: tahoma;
            font-size: 9pt;
        }
    </style>

    <script type="text/javascript">
        // جهت استفاده از فایل: kendo.culture.fa-IR.js
        kendo.culture("fa-IR");
    </script>
</head>
- در اینجا چند فایل js و css جدید اضافه شده‌اند. فایل kendo.rtl.min.css جهت تامین مباحث RTL توکار Kendo UI کاربرد دارد.
- سپس سه فایل kendo.culture.fa-IR.js، kendo.culture.fa.js و kendo.messages.en-US.js نیز اضافه شده‌اند. فایل‌های fa و fa-Ir آن هر چند به ظاهر برای ایران طراحی شده‌اند، اما نام ماه‌های موجود در آن عربی است که نیاز به ویرایش دارد. به همین جهت به سورس این فایل‌ها، جهت ویرایش نهایی نیاز خواهد بود که در پوشه‌ی src\js\cultures مجموعه‌ی اصلی Kendo UI موجود هستند (ر.ک. فایل پیوست).
- فایل kendo.messages.en-US.js حاوی تمام پیام‌های مرتبط با Kendo UI است. برای مثال«رکوردهای 10 تا 15 از 1000 ردیف» را در اینجا می‌توانید به فارسی ترجمه کنید.
- متد kendo.culture کار مشخص سازی فرهنگ بومی برنامه را به عهده دارد. برای مثال در اینجا به fa-IR تنظیم شده‌است. این مورد سبب خواهد شد تا از فایل kendo.culture.fa-IR.js استفاده گردد. اگر مقدار آن‌را به fa تنظیم کنید، از فایل kendo.culture.fa.js کمک گرفته خواهد شد.

راست به چپ سازی گرید
تنها کاری که برای راست به چپ سازی Kendo UI Grid باید صورت گیرد، محصور سازی div آن در یک div با کلاس مساوی k-rtl است:
    <div class="k-rtl">
        <div id="report-grid"></div>
    </div>
k-rtl و تنظیمات آن در فایل kendo.rtl.min.css قرار دارند که در ابتدای head صفحه تعریف شده‌است.

تامین داده و نمایش گرید

در ادامه کدهای کامل DataSource و Kendo UI Grid را ملاحظه می‌کنید:
    <script type="text/javascript">
        $(function () {
            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    parameterMap: function (options) {
                        return kendo.stringify(options);
                    }
                },
                schema: {
                    data: "Data",
                    total: "Total",
                    model: {
                        fields: {
                            "Id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است
                            "Name": { type: "string" },
                            "IsAvailable": { type: "boolean" },
                            "Price": { type: "number" }
                        }
                    }
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                pageSize: 10,
                sort: { field: "Id", dir: "desc" },
                serverPaging: true,
                serverFiltering: true,
                serverSorting: true
            });

            $("#report-grid").kendoGrid({
                dataSource: productsDataSource,
                autoBind: true,
                scrollable: false,
                pageable: true,
                sortable: true,
                filterable: true,
                reorderable: true,
                columnMenu: true,
                columns: [
                    { field: "Id", title: "شماره", width: "130px" },
                    { field: "Name", title: "نام محصول" },
                    {
                        field: "IsAvailable", title: "موجود است",
                        template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>'
                    },
                    { field: "Price", title: "قیمت", format: "{0:c}" }
                ]
            });
        });
    </script>
- با تعاریف مقدماتی Kendo UI DataSource پیشتر آشنا شده‌ایم و قسمت read آن جهت دریافت اطلاعات از سمت سرور کاربرد دارد.
- در اینجا ذکر contentType الزامی است. زیرا ASP.NET Web API بر این اساس است که تصمیم می‌گیرد، خروجی را به صورت JSON ارائه دهد یا XML.
- با استفاده از parameterMap، سبب خواهیم شد تا پارامترهای ارسالی به سرور، با فرمت صحیحی تبدیل به JSON شده و بدون مشکل به سرور ارسال گردند.
- در قسمت schema باید نام فیلدهای موجود در DataSourceResult دقیقا مشخص شوند تا گرید بداند که data را باید از چه فیلدی استخراج کند و تعداد کل ردیف‌ها در کدام فیلد قرار گرفته‌است.
- نحوه‌ی تعریف model را نیز در اینجا ملاحظه می‌کنید. ذکر نوع فیلدها در اینجا بسیار مهم است و اگر قید نشوند، در حین جستجوی پویا به مشکل برخواهیم خورد. زیرا پیش فرض نوع تمام فیلدها string است و در این حالت نمی‌توان عدد 1 رشته‌ای را با یک فیلد از نوع int در سمت سرور مقایسه کرد.
- در اینجا serverPaging، serverFiltering و serverSorting نیز به true تنظیم شده‌اند. اگر این مقدار دهی‌ها صورت نگیرد، این اعمال در سمت کلاینت انجام خواهند شد.

پس از تعریف DataSource، تنها کافی است آن‌را به خاصیت dataSource یک kendoGrid نسبت دهیم.
- autoBind: true سبب می‌شود تا اطلاعات DataSource بدون نیاز به فراخوانی متد read آن به صورت خودکار دریافت شوند.
- با تنظیم scrollable: false، اعلام می‌کنیم که قرار است تمام رکوردها در معرض دید قرارگیرند و اسکرول پیدا نکنند.
- pageable: true صفحه بندی را فعال می‌کند. این مورد نیاز به تنظیم pageSize: 10 در قسمت DataSource نیز دارد.
- با sortable: true مرتب سازی ستون‌ها با کلیک بر روی سرستون‌ها فعال می‌گردد.
- filterable: true به معنای فعال شدن جستجوی خودکار بر روی فیلدها است. کتابخانه‌ی Kendo.DynamicLinq حاصل آن‌را در سمت سرور مدیریت می‌کند.
- reorderable: true سبب می‌شود تا کاربر بتواند محل قرارگیری ستون‌ها را تغییر دهد.
- ذکر columnMenu: true اختیاری است. اگر ذکر شود، امکان مخفی سازی انتخابی ستون‌ها نیز مسیر خواهد شد.
- در آخر ستون‌های گرید مشخص شده‌اند. با تعیین "{format: "{0:c سبب نمایش فیلدهای قیمت با سه رقم جدا کننده خواهیم شد. مقدار ریال آن از فایل فرهنگ جاری تنظیم شده دریافت می‌گردد. با استفاده از template تعریف شده نیز سبب نمایش فیلد bool به صورت یک checkbox خواهیم شد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI03.zip
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک
همانطور که در قسمت قبل، با معرفی مقدماتی Middlewareها عنوان شد، تمام قابلیت‌های یک برنامه‌ی ASP.NET Core، به صورت پیش فرض غیرفعال هستند؛ مگر آنکه Middlewareهای مخصوص آن‌ها را به صورت دستی و با آگاهی کامل، به کلاس آغازین برنامه اضافه کنید. در این قسمت قصد داریم تعداد دیگری از این Middlewareهای توکار را مورد بررسی قرار دهیم.


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

در مورد پوشه‌ی جدید wwwroot در «قسمت 2 - بررسی ساختار جدید Solution» مطالبی عنوان شدند. جهت یادآوری:
اگر فایل Program.cs را بررسی کنید، یک چنین تعاریفی را مشاهده خواهید کرد:
public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();
 
        host.Run();
    }
}
در کدهای فوق، سطر UseContentRoot، پوشه‌ی خاصی را به نام content root معرفی می‌کند که در اینجا به همان پوشه‌ی اصلی برنامه اشاره می‌کند و پوشه‌ی wwwroot از مسیر content root/wwwroot خوانده می‌شود که جهت ارائه‌ی تمام فایل‌های عمومی برنامه مورد استفاده قرار می‌گیرد (مانند تصاویر، فایل‌های JS ،CSS و امثال آن). هدف این است که کدهای سمت سرور برنامه (قرار گرفته در content root) از کدهای عمومی آن (قرار گرفته در پوشه‌ی ویژه‌ی content root/wwwroot) جدا شده و به این ترتیب احتمال نشتی اطلاعات سمت سرور به حداقل برسد.

یک مثال: زمانیکه فایل استاتیک images/banner3.svg در پوشه‌ی wwwroot قرار می‌گیرد، با آدرس http://localhost:9189/images/banner3.svg توسط عموم قابل دسترسی خواهد بود.

یک نکته‌ی امنیتی مهم
در برنامه‌های ASP.NET Core، هنوز فایل web.config را نیز مشاهده می‌کنید. این فایل تنها کاربردی که در اینجا دارد، تنظیم ماژول AspNetCoreModule برای IIS است تا IIS static file handler آن، راسا اقدام به توزیع فایل‌های یک برنامه‌ی ASP.NET Core نکند. بنابراین توزیع این فایل را بر روی سرورهای IIS فراموش نکنید. همچنین بهتر است در ویندوزهای سرور، به قسمت Modules feature مراجعه کرده و StaticFileModule را از لیست ویژگی‌های موجود حذف کرد.


نصب Middleware مخصوص پردازش فایل‌های استاتیک

در قسمت قبل با نحوه‌ی نصب و فعال سازی middleware مخصوص WelcomePage آشنا شدیم. روال کار در اینجا نیز دقیقا به همان صورت است:
الف) نصب بسته‌ی نیوگت Microsoft.AspNetCore.StaticFiles
برای اینکار می‌توان بر روی گره‌ی references کلیک راست کرده و سپس از منوی ظاهر شده،‌گزینه‌ی manage nuget packages را انتخاب کرد. سپس ابتدا برگه‌ی browse را انتخاب کنید و در اینجا نام Microsoft.AspNetCore.StaticFiles را جستجو کرده و سپس نصب کنید.


انجام این کارها معادل افزودن یک سطر ذیل به فایل project.json است و سپس ذخیره‌ی آن که کار بازیابی بسته‌ها را به صورت خودکار آغاز می‌کند:
 "dependencies": {
   // same as before
    "Microsoft.AspNetCore.StaticFiles": "1.0.0"
},
ب) معرفی Middleware پردازش فایل‌های استاتیک
برای اینکار به فایل Startup.cs مراجعه کرده و سطر UseStaticFiles را به متد Configure اضافه کنید (به UseWelcomePage هم دیگر نیازی نداریم):
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }
 
    public void Configure(IApplicationBuilder app)
    {
        app.UseStaticFiles();
        //app.UseWelcomePage();
 
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello DNT!");
        });
    }
}

یک مثال: بر روی پوشه‌ی wwwroot کلیک راست کرده و گزینه‌ی add->new item را انتخاب کنید. سپس یک HTML page جدید را به نام index.html به این پوشه اضافه کنید.
با این محتوا:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Hello World</title>
</head>
<body>
    Hello World!
</body>
</html>
در این حالت برنامه را اجرا کنید. خروجی ذیل را مشاهده خواهید کرد:


که این خروجی دقیقا خروجی app.Run برنامه است و نه محتوای فایل index.html ایی که اضافه کردیم.
در ادامه اگر مسیر کامل این فایل را (http://localhost:7742/index.html) درخواست دهیم، آنگاه می‌توان خروجی این فایل استاتیک را مشاهده کرد:


این رفتار اندکی متفاوت است نسبت به نگارش‌های قبلی ASP.NET که فایل index.html را به عنوان فایل پیش فرض، درنظر می‌گرفت و محتوای آن‌را نمایش می‌داد. منظور از فایل پیش فرض، فایلی است که با درخواست ریشه‌ی یک مسیر، به کاربر ارائه داده می‌شود و index.html یکی از آن‌ها است.
برای رفع این مشکل، نیاز است Middleware مخصوص آن‌را به نام Default Files نیز به برنامه معرفی کرد:
public void Configure(IApplicationBuilder app)
{
   app.UseDefaultFiles();
   app.UseStaticFiles();
در این حالت است که با درخواست ریشه‌ی سایت، فایل پیش فرض آن نمایش داده خواهد شد:


فعال سازی Default Files، سبب جستجوی یکی از 4 فایل ذیل به صورت پیش فرض می‌شود (اگر تنها ریشه‌ی پوشه‌ای درخواست شود):
default.htm
default.html
index.htm
index.html

اگر خواستید فایل سفارشی خاص دیگری را معرفی کنید، نیاز است پارامتر DefaultFilesOptions آن‌را مقدار دهی نمائید:
 // Serve my app-specific default file, if present.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);


ترتیب معرفی Middlewares مهم است

در قسمت قبل، در حین معرفی تفاوت‌های Middlewareها با HTTP Modules، عنوان شد که اینبار برنامه نویس می‌تواند بر روی ترتیب اجرای Middlewareها کنترل کاملی داشته باشد و این ترتیب معادل است با ترتیب معرفی آن‌ها در متد Configure، به نحوی که مشاهده می‌کنید. برای آزمایش این مطلب، متد معرفی middleware فایل‌های پیش فرض را پس از متد معرفی فایل‌های استاتیک قرار دهید:
public void Configure(IApplicationBuilder app)
{
  app.UseStaticFiles();
  app.UseDefaultFiles();
در این حالت اگر برنامه را اجرا کنید، به این خروجی خواهید رسید:


بله. اینبار تعریف فایل‌های پیش فرض، هیچ تاثیری نداشته و درخواست ریشه‌ی سایت، بدون ذکر صریح نام فایلی، مجددا به app.Run ختم شده‌است.


توزیع فایل‌های استاتیک خارج از wwwroot

همانطور که در ابتدای بحث عنوان شد، با فعال سازی UseStaticFiles به صورت پیش فرض مسیر content root/wwwroot در معرض دید دنیای خارج قرار می‌گیرد و توسط وب سرور قابل توزیع خواهد شد:
○ wwwroot
   § css
   § images
   § ...
○ MyStaticFiles
   § test.png
اما اگر قصد داشته باشیم تا تصویر test.png موجود در پوشه‌ی MyStaticFiles خارج از wwwroot را نیز عمومی کنیم چه باید کرد؟
برای این منظور می‌توان از پارامتر StaticFileOptions متد UseStaticFiles به نحو ذیل جهت معرفی پوشه‌ی MyStaticFiles استفاده کرد:
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
    RequestPath = new PathString("/StaticFiles")
});
در این حالت، مسیر دسترسی عمومی به این فایل، به صورت  http://<app>/StaticFiles/test.png خواهد بود (بر مبنای RequestPath تنظیم شده).


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


فرض کنید پوشه‌ی تصاویر را به پوشه‌ی عمومی wwwroot اضافه کرده‌ایم. برای فعال سازی مرور محتوای این پوشه می‌توان از Middleware دیگری به نام DirectoryBrowser استفاده کرد:
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
    RequestPath = new PathString("/MyImages")
});
بعد از انجام اینکار به خطای ذیل خواهید رسید:
 Unable to resolve service for type 'System.Text.Encodings.Web.HtmlEncoder' while attempting to activate 'Microsoft.AspNetCore.StaticFiles.DirectoryBrowserMiddleware'.
برای رفع آن، سرویس آن نیز باید به متد ConfigureServices اضافه شود:
public void ConfigureServices(IServiceCollection services)
{
   services.AddDirectoryBrowser();
}
در این حالت پس از اجرای برنامه، اگر مسیر http://localhost:7742/myimages را درخواست دهید (MyImages از RequestPath تنظیم شده، گرفته می‌شود)، به تصویر ذیل خواهید رسید:


مشکل! در این حالت که DirectoryBrowser را فعال کرده‌ایم، اگر بر روی لینک فایل تصویر نمایش داده شده کلیک کنیم، باز پیام Hello DNT یا اجرای app.Run را شاهد خواهیم بود.
به این دلیل که UseStaticFiles پیش فرض، مسیر درخواستی MyImages را که بر روی file system وجود ندارد، نمی‌شناسد. برای رفع این مشکل تنها کافی است مسیریابی این Request Path خاص را نیز فعال کنیم:
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
    RequestPath = new PathString("/MyImages")
});


بررسی خلاصه‌ی تنظیماتی که به فایل آغازین برنامه اضافه شدند

تا اینجا اگر توضیحات را قدم به قدم دنبال و اجرا کرده باشید، یک چنین تنظیماتی را خواهید داشت:
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
 
namespace Core1RtmEmptyTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDirectoryBrowser();
        }
 
        public void Configure(IApplicationBuilder app)
        {
            app.UseDefaultFiles();
 
            app.UseStaticFiles(); // For the wwwroot folder
 
            // For the files outside of the wwwroot
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
                RequestPath = new PathString("/StaticFiles")
            });
 
            // For DirectoryBrowser
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
                RequestPath = new PathString("/MyImages")
            });
 
            app.UseDirectoryBrowser(new DirectoryBrowserOptions
            {
                FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
                RequestPath = new PathString("/MyImages")
            });
 
            //app.UseWelcomePage();
 
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Hello DNT!");
            });
        }
    }
}
services.AddDirectoryBrowser برای فعال سازی مرور پوشه‌ها اضافه شده‌است.
UseDefaultFiles کار فعال سازی شناسایی فایل‌های پیش فرضی مانند index.html را در صورت ذکر نام ریشه‌ی یک پوشه، انجام می‌دهد.
اولین UseStaticFiles تعریف شده، تمام مسیرهای فیزیکی ذیل wwwroot را عمومی می‌کند.
دومین UseStaticFiles تعریف شده، پوشه‌ی MyStaticFiles واقع در خارج از wwwroot را عمومی می‌کند.
سومین UseStaticFiles تعریف شده، پوشه‌ی فیزیکی wwwroot\images را به مسیر درخواست‌های MyImages نگاشت می‌کند (http://localhost:7742/myimages) تا توسط DirectoryBrowser تعریف شده، قابل استفاده شود.
در آخر هم DirectoryBrowser تعریف شده‌است.


یک نکته‌ی امنیتی مهم
یک چنین قابلیتی (مرور فایل‌های درون یک پوشه) به صورت پیش فرض بر روی تمام IIS‌ها به دلایل امنیتی غیرفعال است. به همین جهت بهتر است Middleware فوق را هیچگاه استفاده نکنید و به این قسمت صرفا از دیدگاه اطلاعات عمومی نگاه کنید.


ساده سازی تعاریف توزیع فایل‌های استاتیک

Middleware دیگری به نام FileServer کار تعریف توزیع فایل‌های استاتیک را ساده می‌کند. اگر آن‌را تعریف کنید:
 app.UseFileServer();
اینکار به معنای تعریف یکباره‌ی UseStaticFiles و UseDefaultFiles، با ترتیب صحیح آن‌ها است.
اگر خواستید DirectoryBrowsing آن‌را نیز فعال کنید، پارامتر ورودی آن‌را به true مقدار دهی کنید (که به صورت پیش فرض غیرفعال است):
 app.UseFileServer(enableDirectoryBrowsing: true);
همچنین در اینجا می‌توانید مسیر پوشه‌ی MyStaticFiles خارج از wwwroot را نیز با مقدار دهی پارامتر FileServerOptions آن، مشخص کنید:
app.UseFileServer(new FileServerOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
        RequestPath = new PathString("/StaticFiles"),
        EnableDirectoryBrowsing = false
    });


توزیع فایل‌های ناشناخته

اگر به سورس ASP.NET Core 1.0 دقت کنید، کلاسی را به نام FileExtensionContentTypeProvider خواهید یافت. این‌ها پسوندها و mime typeهای متناظری هستند که توسط ASP.NET Core شناخته شده و توزیع می‌شوند. برای مثال اگر فایلی را به نام test.xyz به پوشه‌ی wwwroot اضافه کنید، درخواست آن توسط کاربر، به Hello DNT ختم می‌شود؛ چون در این کلاس پایه، پسوند xyz تعریف نشده‌است.
برای رفع این مشکل و تکمیل این لیست می‌توان به نحو ذیل عمل کرد:
 // Set up custom content types -associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".xyz"] = "text/html";
 
app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
}) ; // For the wwwroot folder
در اینجا ابتدا همان کلاس پایه FileExtensionContentTypeProvider را نمونه سازی می‌کنیم و سپس به دیکشنری آن، پسوند و mime type ویژه‌ی خود را اضافه می‌کنیم. سپس این provider را می‌توان به خاصیت ContentTypeProvider پارامتر StaticFileOptions آن نسبت داد. اکنون این فایل با پسوند xyz، قابل شناسایی می‌شود:


و یا اگر خواستید کمی تمیزتر کار کنید، بهتر است از کلاس پایه FileExtensionContentTypeProvider ارث بری کرده و سپس در سازنده‌ی این کلاس، خاصیت Mappings را ویرایش نمود:
public class XyzContentTypeProvider : FileExtensionContentTypeProvider
{
    public XyzContentTypeProvider()
    {
        this.Mappings.Add(".xyz", "text/html");
    }
}
و برای استفاده‌ی از آن خواهیم داشت:
app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = new XyzContentTypeProvider()
}) ; // For the wwwroot folder

روش دیگر مدیریت این مساله، تنظیم مقدار خاصیت ServeUnknownFileTypes به true است:
app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true,
    DefaultContentType = "image/png"
});
در اینجا هر پسوند شناخته نشده‌ای با mime type تصویر png، توزیع خواهد شد. البته از لحاظ امنیتی توصیه شده‌است که چنین کاری را انجام ندهید و از این تنظیم عمومی نیز صرفنظر کنید.
نظرات مطالب
چه زمان‌هایی یک برنامه‌ی ASP.NET ری استارت می‌شود؟
یک نکته‌ی تکمیلی: چگونه یک برنامه‌ی هاست شده‌ی در IIS را همواره زنده (در حال اجرا) نگه داریم؟

application pool برنامه‌های هاست شده‌ی در IIS، پس از مدتی بیکاری، خاموش می‌شوند. راه اندازی مجدد آن‌ها هم وابسته به رسیدن یک درخواست جدید به آن‌ها است. یعنی اگر برای مثال بخواهید یک کار پس زمینه‌ای را توسط یک برنامه‌ی ASP.NET مدیریت کنید، احتمال از کار افتادن آن به علت خاموش شدن و عدم راه اندازی خودکار application pool، بسیار زیاد است. برای رفع این مشکل و زنده نگه‌داشتن برنامه اگر از یک سیستم توسعه‌ی معمولی استفاده می‌کنید، ابتدا مطمئن شوید که Application Initialization Module که جزئی از Windows Features است، نصب شده‌است:

و اگر از ویندوز سرور استفاده می‌کنید، باید Application Initialization را در قسمت Application Development فعال کنید:

- Open the Add Roles and Features Wizard.
- In the Select role services panel, open the Application Development node.
- Select the checkbox for Application Initialization.

سپس به تنظیمات application pool برنامه مراجعه کرده و دو تغییر زیر را اعمال کنید:
Start Mode -> AlwaysRunning
Idle Time-Out (minutes) -> 0


همچنین حالت Preload برنامه را نیز باید فعال کرد و برنامه هم باید در حالت In-Process hosting model اجرا شود:
Connections panel -> Sites node -> Right-click the app ->
Manage Website > Advanced Settings -> Set Preload Enabled -> True

نظرات مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت چهارم - پرهیز از الگوی Service Locator در برنامه‌های وب
ارتقاء به ASP.NET Core 3.0: محدود شدن امکان تزریق وابستگی‌ها در سازنده‌ی کلاس آغازین برنامه

یکی از تغییرات مهم ASP.NET Core 3.0 نسبت به نگارش‌های قبلی، جنریک شدن Host آن است (چون حالت‌های هاستینگ بیشتری را نسبت به حالت صرف MVC پشتیبانی می‌کند). به این ترتیب HostBuilder نگارش 2x:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
                 WebHost.CreateDefaultBuilder(args)
                 .UseStartup<Startup>();
اکنون در نگارش 3x به این صورت در آمده‌است:
public static IHostBuilder CreateHostBuilder(string[] args) =>
               Host.CreateDefaultBuilder(args)
                     .ConfigureWebHostDefaults(webBuilder =>
                     {
                        webBuilder.UseStartup<Startup>();
                     });
این مورد، یک تغییر مهم را هم در وضعیت تزریق وابستگی‌های سفارشی در کلاس آغازین برنامه ایجاد کرده‌است: در نگارش 3x، فقط و فقط سرویس‌های IHostEnvironment ،IWebHostEnvironment و IConfiguration را می‌توانید به سازنده‌ی کلاس آغازین آن تزریق کنید.
علت اینجا است که در ASP.NET Core 3x، یک باگ بسیار مهم سیستم تزریق وابستگی‌های ASP.NET Core برطرف شده‌است: اکنون فقط یک dependency injection container به ازای کل برنامه‌ی ASP.NET Core 3x ساخته می‌شود. در نگارش‌های قبلی، یک container برای برنامه و یک container مجزا برای host تولید می‌شدند. در این حالت اگر یک سرویس Singleton را در فایل program.cs معرفی می‌کردید:
WebHost.CreateDefaultBuilder()
             .UseStartup<Startup>()
             .ConfigureServices(services => 
                     services.AddSingleton<MySingleton>())
             .Build()
             .Run();
برخلاف تصور، این سرویس Singleton رفتار نمی‌کرد؛ چون همانطور که عنوان شد، دو container، برنامه را مدیریت می‌کردند (یعنی دوبار توسط دو ظرف متفاوت نگهدارنده‌ی اشیاء، وهله سازی می‌شد) که اکنون در نگارش 3x به یک مورد کاهش یافته‌است.
در اینجا هرچند متد ConfigureServices وجود دارد، اما اگر از آن استفاده کنید، سرویس معرفی شده‌ی توسط آن، در سازنده‌ی کلاس Startup شناسایی نمی‌شود.
مطالب
سازماندهی برنامه‌های Angular توسط ماژول‌ها
یک برنامه‌ی Angular، از گروهی از کامپوننت‌ها تشکیل می‌شود؛ برای مثال یک کامپوننت App وجود دارد که آن نیز از تعدادی کامپوننت مختلف تشکیل می‌شود. ماژول‌ها کار سازماندهی و بسته بندی این کامپوننت‌ها را انجام می‌دهند و با بزرگتر شدن برنامه می‌توان قسمت‌های مختلف را در ماژول‌های متفاوتی قرار داد. مزایای این روش به شرح زیر هستند:
- بهبود کپسوله سازی قسمت‌های مختلف برنامه با بسته بندی آن‌ها در ماژول‌های متفاوت
- فراهم آوردن امکان lazy loading و بهبود کارآیی برنامه


انواع ماژول‌های توصیه شده‌ی در برنامه‌های Angular

منهای App Module پیش‌فرض یک برنامه‌های Angular، ایجاد سه نوع ماژول دیگر نیز در جهت سازماندهی اینگونه برنامه‌ها توصیه می‌شوند:
- Core Module
هدف از آن فراهم آوردن سرویس‌های Singleton اشتراکی بین کامپوننت‌ها و ماژول‌های مختلف برنامه است. علت این‌جا است که سیستم تزریق وابستگی‌های Angular، به ازای هر ماژولی که Lazy loaded باشد، سرویس تزریقی در آن‌ها را مجددا وهله سازی می‌کند. به همین جهت نیاز است تک ماژول اختصاصی را برای مدیریت سرویس‌هایی که نیازی است تنها یکبار در طول عمر برنامه وهله سازی شوند، تدارک ببینیم و Core Module مکان مناسبی برای این‌کار است.
همچنین Code Module باید شامل کامپوننت‌هایی در سطح برنامه باشد. دراینجا منظور از «در سطح برنامه»، کامپوننت‌هایی که قرار است در بین تمام ماژول‌ها به اشتراک گذاشته شوند، نیست. منظور تنها کامپوننت‌هایی هستند که در App Component اصلی برنامه قرار است استفاده شوند؛ مانند منوی راهبری بالای سایت.

- Shared Module
هدف از آن مدیریت و بسته بندی کامپوننت‌ها، دایرکتیوها و Pipes اشتراکی بین تمام اجزای برنامه است. برای مثال کامپوننت «لطفا منتظر بمانید ...» اگر قرار است در تمام قسمت‌های برنامه استفاده شود، نیاز است در Shared Module تعریف شود. از این جهت که در یک برنامه‌ی Angular نمی‌توان یک کامپوننت را بین دو ماژول مختلف به اشتراک گذاشت. به همین جهت نیاز است یک مکان مرکزی برای تعریف این کامپوننت‌های اشتراکی ایجاد شود و سپس این تک ماژول را در قسمت‌های مختلف برنامه، بدون مشکل مورد استفاده قرار داد.

- Feature Module
این ماژول‌ها به ازای هر ویژگی برنامه ایجاد شده و کامپوننت‌ها، سرویس‌ها، دایرکتیوها و Pipes اختصاصی آن ویژگی را بسته بندی می‌کنند.


ایجاد Core Module

فرض کنید می‌خواهید اطلاعات کاربر جاری لاگین شده را در طول عمر برنامه نگهداری کنید و از آن در تمام قسمت‌های برنامه استفاده نمائید. یک چنین سرویسی نیاز است دارای طول عمر Singleton باشد و تنها یکبار وهله سازی شود تا اطلاعات کاربر جاری از دست نرود. به همین جهت بهترین مکان تعریف این سرویس، در Core Module است.
برای این منظور در ساختار برنامه‌ی خود، پوشه‌ی جدید src\app\core را ایجاد می‌کنیم. سپس فایل core.module.ts را به صورت ذیل در آن تعریف خواهیم کرد:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

import { UserRepositoryService } from './user-repository.service';
import { NavBarComponent } from './nav-bar.component';
import { AccountMenuComponent } from './account-menu.component';

@NgModule({
  imports: [ CommonModule, RouterModule ],
  exports: [ NavBarComponent, AccountMenuComponent ],
  declarations: [ NavBarComponent, AccountMenuComponent ],
  providers: [ UserRepositoryService ]
})
export class CoreModule { };
 - CoreModule در ابتدا تنها CommonModule و RouterModule را در صورت نیاز import می‌کند.
 - سپس سرویس‌های اشتراکی و Singleton برنامه در قسمت providers آن قرار می‌گیرند.
 - در اینجا همچنین دو کامپوننت منو که توسط app.component.ts مورد استفاده قرار می‌گیرند نیز import شده‌اند.
 - فایل‌های account-menu.component.ts، nav-bar.component.ts و user-repository.service.ts نیز به درون پوشه‌ی src\app\core منتقل خواهند شد (به همراه تمام فایل‌های html و css متناظر با آن‌ها).
 - اگر دقت کنید، قسمت exports این ماژول نیز مقدار دهی شده‌است. چون این کامپوننت‌ها قرار است خارج از این ماژول و در AppModule استفاده شوند، نیاز است آن‌ها را به صورت خروجی نیز معرفی کنیم.

اکنون جهت استفاده‌ی از این قابلیت‌ها، تنها کافی است تعریف CoreModule را به AppModule در فایل app.module.ts اضافه کنیم:
import { CoreModule } from "./core/core.module";

@NgModule({
  imports:      [
//...
    CoreModule,
//...
    RouterModule.forRoot(appRoutes)
  ],
//...
})
export class AppModule { }


ایجاد Shared Modules

در Shared Module اجزایی را قرار خواهیم داد که قرار است در بیش از یک ماژول مورد استفاده قرار گیرند. به همین جهت در ساختار برنامه‌ی خود، پوشه‌ی جدید src\app\shared را ایجاد می‌کنیم. سپس در آن، ماژول جدید shared.module.ts را ایجاد خواهیم کرد:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { LoadingSpinnerComponent } from './loading-spinner.component';

@NgModule({
  imports: [ CommonModule ],
  declarations: [ LoadingSpinnerComponent ],
  exports: [ LoadingSpinnerComponent, CommonModule ],
  providers: [ ]
})
export class SharedModule { };
ساختار این ماژول نیز شبیه به Core Module است. ابتدای CommonModule به آن import شده‌است. سپس کامپوننت‌هایی که قرار است در بین سایر ماژول‌های سایت به اشتراک گذاشته شوند (برای مثال یک کامپوننت Loading Spinner فرضی)، در هر دو قسمت declarations و exports این ماژول اشتراکی قرار می‌گیرند. همچنین فایل loading-spinner.component.ts و تمام اجزای وابسته‌ی به آن نیز به پوشه‌ی src\app\shared منتقل می‌شوند.
از این جهت که اجزای خروجی این ماژول قرار است در Feature Moduleها استفاده شوند، CommonModule مورد استفاده‌ی در آن‌ها نیز در قسمت exports ذکر شده‌است.

اکنون جهت استفاده‌ی از این قابلیت‌ها، تنها کافی است تعریف SharedModule را به AppModule در فایل app.module.ts اضافه کنیم:
import { CoreModule } from "./core/core.module";
import { SharedModule } from "./shared/shared.module";

@NgModule({
  imports:      [
//...
    CoreModule,
    SharedModule,
//...
    RouterModule.forRoot(appRoutes)
  ],
//...
})
export class AppModule { }


ایجاد Feature Modules

این مورد نکته‌ی ویژه‌ای را به همراه ندارد و همانند ایجاد سایر ماژول‌های برنامه‌است. برای مثال ویژگی مدیریت کاربران، به همراه تمام اجزای آن درون ماژول کاربران قرار می‌گیرد و به همین ترتیب برای سایر ویژگی‌های دیگر برنامه. ایجاد و مدیریت اینگونه ماژول‌ها توسط Angular CLI بسیار ساده‌است:
> ng g m users -m app.module --routing
> ng g c users/users-list
دستور اول ایجاد ماژول جدید users، پوشه‌ی مرتبط با آن و همچنین به روز رسانی فایل app.module را به صورت خودکار انجام می‌دهد.
دستور دوم نیز کامپوننتی را به این ماژول اضافه می‌کند؛ به همراه به روز رسانی تعاریف این ماژول.

فقط در اینجا SharedModule ایی را که پیشتر اضافه کردیم، به قسمت imports آن اضافه می‌کنیم:
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { SharedModule } from '../shared/shared.module';
import { UsersListComponent } from './users-list.component';

@NgModule({
  imports: [ RouterModule, SharedModule ],
  declarations: [ UsersListComponent ],
  exports: [  ],
  providers: [ ]
})
export class UsersModule { };
مطالب
مزیت‌های استفاده از رویه‌های ذخیره شده؛ واقعیت یا توهم؟!

متن زیر یک سری نکات و یا شاید توهماتی را مطرح می‌کند که در مورد رویه‌های ذخیره شده در اس کیوال سرور رایج هستند.

1- رویه‌های ذخیره شده در مقابل SQL Injection مقاوم هستند. کوئری‌های Ad hoc همیشه این آسیب پذیری را به همراه دارند.
نادرست است! رویه‌های ذخیره شده‌ای که رشته‌ها را به صورت پارامتر دریافت کرده و آن‌ها را به صورت یک عبارت sql اجرا می‌کنند، آسیب پذیر هستند. اگر هنگام استفاده از کوئری‌های Ad hoc از پارامترها استفاده شود، در برابر حملات SQL Injection مصون خواهید بود.

2- execution plan رویه‌های ذخیره شده کش می‌شوند اما این Plan برای کوئری‌های Ad hoc هر بار محاسبه و تولید می‌گردد.
نادرست است! اس کیوال سرور تا این اندازه بی هوش نیست! اگر execution plan ایی موجود باشد حتما استفاده خواهد شد و برای موتور اس کیوال سرور اصلا اهمیتی ندارد که کوئری در حال اجرا از یک رویه ذخیره شده صادر شده است یا از یک کوئری Ad hoc . رویه‌های ذخیره شده پیش کامپایل شده نیستند و مانند تمامی کوئری‌های دیگر در زمان اجرا کامپایل می‌شوند.

3- زمانیکه از رویه ذخیره شده استفاده می‌کنید همه چیز را در یک مکان به صورت متمرکز و مجتمع خواهید داشت (مدیریت بهتر)
نادرست است! در یک مکان متمرکز در اختیار شما نیستند. برنامه جای خود را دارد و رویه‌های ذخیره شده در دیتابیس در جای دیگری قرار دارند و برای مثال اگر قرار باشد یک پارامتر را به رویه ذخیره شده خود اضافه کنید، کدهای شما نیز باید تغییر کنند.

4- می‌توان از یک رویه ذخیره شده استفاده مجدد کرد (در نقاط مختلف یک کد) و اعمال تغییرات تنها در یک مکان (دیتابیس) باید صورت گیرد.
هر چند این مورد درست است، اما باید دقت داشت که اگر چندین برنامه از این رویه ذخیره شده استفاده می‌کنند نباید تغییرات شما باعث از کار افتادن سایر برنامه‌ها شوند.

5- می‌توان رویه ذخیره شده را بدون نیاز به توزیع مجدد برنامه تغییر داد.
این مورد تا حدودی صحیح است. اگر تنها بحث بهینه سازی و امثال آن مطرح باشد صحیح است اما اگر واقعا نیاز به تغییر یک کوئری در رویه ذخیره شده وجود داشته باشد به احتمال زیاد برنامه نیز باید دستخوش تغییراتی گردد تا این دو با هم هماهنگ شوند.

نظر شما چیست؟


نظرات نظرسنجی‌ها
شما برای کار با دیتا در اندروید، کدامیک از روش های زیر را استفاده میکنید یا ترجیح می دهید؟
بنظر میرسد من و سایر کاربران سایت جاری تجربه خوبی در استفاده از ORM قدرتمند  EF داریم بنابراین امکانات و ویژگی‌های هر ORM دیگری را نیز با آن مقایسه میکنیم و شاید عیار سایر ORM‌ها را نیز با EF  بسنجیم.
روش معمول Sqlite:
مزایای اصلی این روش انعطاف پذیری ، بالا بردن توانایی مانور برنامه نویس، سرعت اجرای بهتر و حجم کمتر در فایل خروجی نهایی است . عیب اصلی آن مجیک استرینگ‌های زیاد و کثیف شدن کد، بالاتر بودن نرخ تعداد خطا در برنامه ،دیباگ سخت تر،قابلیت نگهداری کمتر کد ، تعداد خط کد بالای برنامه ، سرعت به نسبت پایین در develop است.
ORM:
میتوان گفت مزایا و معایب استفاده از ORM دقیقا نقطه مقابل روش معمول Sqlite است و در اصل ایده اصلی خلق اولین ORM ‌های دنیا نیز چنین بود ! عیب دیگر این ORM‌های اندرویدی این است که در مقایسه با EF دست و پا بریده ، خسته کننده و بعضا باعث کلافگی برنامه نویس میشوند.
NoSql یا ترکیبی:
با توجه به مدرن بودن و mobile-first  بودن برخی از این دیتابیس‌ها ، از ابتدا با بسیاری از نیازمندی‌های مدرن از جمله sync شدن با دیتابیس سرور، push notification , پردازش و مدیریت داده‌های json و هویت سنجی با OAuth همسو هستند .همچنین میتوان با کمترین تعداد خط کد و نفرات از ویژگی‌های پیشرفته آنها استفاده کرد. از نمونه‌های خوب دیتابیس‌های NoSql میشود به Realm و couchbase اشاره کرد. از نظر من عیب اصلی این دیتابیس‌ها عدم پختگی و ثبات ایده آل است هرچند برای اکثر پروژه‌ها تا همینجا هم گزینه ای عالی محسوب میشوند. 
نظرات اشتراک‌ها
بررسی DataTable
- مجوز GPL داره برای کارهای سورس باز. مانند KendoUI است.
- یک نمونه دیگر هم هست که در سایت بررسی شده : اینجا