مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API
در ASP.NET Core، برخلاف نگارش‌های قبلی ASP.NET که ASP.NET Web API مجزای از ASP.NET MVC و همچنین وب فرم‌ها ارائه شده بود، اکنون جزئی از ASP.NET MVC است و با آن یکپارچه می‌باشد. بنابراین پیشنیازهای راه اندازی Web API با ASP.NET Core شامل سه مورد ذیل هستند که پیشتر آن‌ها را بررسی کردیم:
الف) فعال سازی ارائه‌ی فایل‌های استاتیک
ب) فعال سازی ASP.NET MVC
ج) آشنایی با تغییرات مسیریابی

و مابقی آن صرفا یک سری نکات تکمیلی هستند که در ادامه آن‌ها را بررسی خواهیم کرد.


تعریف مسیریابی کلی کنترلر

در اینجا همانند مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 9 - بررسی تغییرات مسیریابی»، می‌توان در صورت نیاز، مسیریابی کلی کنترلر را توسط ویژگی Route بازنویسی کرد و برای مثال درخواست‌های آن‌را محدود به درخواست‌هایی کرد که با api/ شروع شوند:
[Route("api/[controller]")] // http://localhost:7742/api/test
public class TestController : Controller
{
    private readonly ILogger<TestController> _logger;
 
    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }
[controller] هم در اینجا یک توکن پیش فرض است که با نام کنترلر جاری یا همان Test، به صورت خودکار جایگزین می‌شود.
در مورد سرویس ثبت وقایع نیز در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 17 - بررسی فریم ورک Logging» بحث کردیم و از آن می‌توان برای ثبت استثناءهای رخ داده استفاده کرد.


یک کنترلر ، اما با قابلیت‌های متعدد

همانطور که ملاحظه می‌کنید، اینبار کلاس پایه‌ی این کنترلر Test، همان Controller متداول ASP.NET MVC ذکر شده‌است و نه Api Controller سابق. تمام قابلیت‌های موجود در این‌دو توسط همان Controller ارائه می‌شوند.


هنوز پیش فرض‌های سابق Web API برقرار هستند

در مثال ذیل که به نظر یک کنترلر ASP.NET MVC است،
- هنوز متد Get مربوط به Web API که به صورت پیش فرض به درخواست‌های Get ختم شده‌ی به نام کنترلر پاسخ می‌دهد، برقرار است (متد IEnumerable<string> Get). برای مثال اگر شخصی در مرورگر، آدرس http://localhost:7742/api/test را درخواست دهد، متد Get اجرا می‌شود.
- در اینجا می‌توان نوع خروجی متد را دقیقا از همان نوع اشیاء مدنظر، تعیین کرد؛ برای نمونه تعریف  <IEnumerable<string در مثال زیر.
- مهم نیست که از return Json استفاده کنید و یا خروجی را مستقیما با فرمت <IEnumerable<string ارائه دهید.
- اگر نیاز به کنترل بیشتری بر روی HTTP Response Status بازگشتی داشتید، می‌توانید از متدهایی مانند return Ok و یا return BadRequest در صورت بروز مشکلی استفاده نمائید. برای مثال در متد IActionResult GetEpisodes2، استثنای فرضی حاصل، ابتدا توسط سرویس ثبت وقایع ذخیره شده و در آخر یک BadRequest بازگشت داده می‌شود.
- تمام مسیریابی‌ها را توسط ویژگی Route و یا نوع‌های درخواستی مانند HttpGet، می‌توان بازنویسی کرد؛ مانند مسیر /api/path1
- امکان محدود ساختن نوع پارامترهای دریافتی همانند متد Get(int page) ذیل، توسط ویژگی‌های مسیریابی وجود دارد.
[Route("api/[controller]")] // http://localhost:7742/api/test
public class TestController : Controller
{
    private readonly ILogger<TestController> _logger;
 
    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }
 
    [HttpGet]
    public IEnumerable<string> Get() // http://localhost:7742/api/test
    {
        return new [] { "value1", "value2" };
    }
 
    [HttpGet("{page:int}")]
    public IActionResult Get(int page) // http://localhost:7742/api/test/1
    {
        return Json(new[] { "value3", "value4" });
    }
 
    [HttpGet("/api/path1")]
    public IActionResult GetEpisodes1() // http://localhost:7742/api/path1
    {
        return Json(new[] { "value5", "value6" });
    }
 
    [HttpGet("/api/path2")]
    public IActionResult GetEpisodes2() // http://localhost:7742/api/path2
    {
        try
        {
            // get data from the DB ...
            return Ok(new[] { "value7", "value8" });
        }
        catch (Exception ex)
        {
            _logger.LogError("Failed to get data from the API", ex);
            return BadRequest();
        }
    } 
}
بنابراین در اینجا اگر می‌خواهید یک کنترلر ASP.NET Web API 2.x را به ASP.NET Core 1.0 ارتقاء دهید، تمام متدهای Get و Put و امثال آن هنوز معتبر هستند و مانند سابق عمل می‌کنند:
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET: api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
// GET api/values/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }
// POST api/values
        [HttpPost]
        public void Post([FromBody]string value)
        {
        }
// PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }
// DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}
در مورد ویژگی FromBody در ادامه بیشتر بحث خواهد شد.

یک نکته: اگر می‌خواهید خروجی Web API شما همواره JSON باشد، می‌توانید ویژگی جدید Produces را به شکل ذیل به کلاس کنترلر اعمال کنید:
 [Produces("application/json")]
[Route("api/[controller]")] // http://localhost:7742/api/test
public class TestController : Controller


تغییرات Model binding پیش فرض، برای پشتیبانی از ASP.NET MVC و ASP.NET Web API

فرض کنید مدل زیر را به برنامه اضافه کرده‌اید:
namespace Core1RtmEmptyTest.Models
{
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }
}
و همچنین قصد دارید اطلاعات آن‌را از کاربر توسط یک عملیات POST دریافت کرده و به شکل JSON نمایش دهید:
using Core1RtmEmptyTest.Models;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class PersonController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
 
        [HttpPost]
        public IActionResult Index(Person person)
        {
            return Json(person);
        }
    }
}
برای اینکار، از jQuery به نحو ذیل استفاده می‌کنیم (از این جهت که بیشتر ارسال‌های به سرور جهت کار با Web API نیز Ajax ایی هستند):
@section scripts
{
    <script type="text/javascript">
        $(function () {
            $.ajax({
                type: 'POST',
                url: '/Person/Index',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify({
                    FirstName: 'F1',
                    LastName: 'L1',
                    Age: 23
                }),
                success: function (result) {
                    console.log('Data received: ');
                    console.log(result);
                }
            });
        });
    </script>
}


همانطور که مشاهده می‌کنید، اگر در ابتدای این متد یک break-point قرار دهیم، اطلاعاتی را از سمت کاربر دریافت نکرده‌است و مقادیر دریافتی نال هستند.
این مورد یکی از مهم‌ترین تغییرات Model binding این نگارش از ASP.NET MVC با نگارش‌های قبلی آن است. در اینجا اشیاء پیچیده از request body دریافت و bind نمی‌شوند و باید به نحو ذیل، محل دریافت و تفسیر آن‌ها را دقیقا مشخص کرد:
 public IActionResult Index([FromBody]Person person)
زمانیکه ویژگی FromBody را مشخص می‌کنیم، آنگاه اطلاعات دریافتی از request body دریافتی، به شیء Person نگاشت خواهند شد.


نکته‌ی مهم: حتی اگر FromBody را ذکر کنید ولی از JSON.stringify در سمت کاربر استفاده نکنید، باز هم نال دریافت خواهید کرد. بنابراین در این نگارش ذکر JSON.stringify نیز الزامی است.


حالت‌های دیگر تغییرات Model Binding در ASP.NET Core

تا اینجا مشخص شد که اگر یک درخواست Ajax ایی را به سمت سرور یک برنامه‌ی ASP.NET Core ارسال کنیم، به صورت پیش فرض به اشیاء پیچیده‌ی سمت سرور bind نمی‌شود و باید حتما ویژگی FromBody را نیز مشخص کرد تا اطلاعات را از request body واکشی کند (محل دریافت اطلاعات پیش فرض آن نامشخص است).
یک سؤال: اگر به سمت یک چنین اکشن متدی، اطلاعات فرمی را به حالت معمول ارسال کنیم، چه اتفاقی رخ خواهد داد؟
ارسال اطلاعات فرم‌ها به سرور، همواره شامل دو تغییر ذیل است:
  var dataType = 'application/x-www-form-urlencoded; charset=utf-8';
 var data = $('form').serialize();
اطلاعات فرم سریالایز می‌شوند و data type مخصوصی هم برای آن‌ها تنظیم خواهد شد. در این حالت، ارسال یک چنین اطلاعاتی به سمت اکشن متد فوق، با خطای 415 unsupported media type متوقف می‌شود. برای رفع این مشکل باید از ویژگی دیگری به نام FromForm استفاده کرد:
 [HttpPost]
public IActionResult Index([FromForm]Person person)
حالت‌های دیگر ممکن را در تصویر ذیل ملاحظه می‌کنید:


علت این مساله نیز بالا رفتن میزان امنیت سیستم است. در نگارش‌های قبلی، تمام مکان‌ها و حالت‌های میسر جستجو می‌شوند و اگر یکی از آن‌ها قابلیت تطابق با خواص شیء مدنظر را داشته باشد، کار binding به پایان می‌رسد. اما در اینجا با مشخص شدن محل دقیق منبع اطلاعات، دیگر سایر حالات جستجو نشده و سطح حمله کاهش پیدا می‌کند.
در اینجا باید مشخص کرد که دقیقا اطلاعاتی که قرار است به یک شیء پیچیده Bind شوند، آیا از یک Form تامین می‌شوند، یا از Body و یا از هدر، کوئری استرینگ، مسیریابی و یا حتی از یک سرویس.
تمام این حالت‌ها مشخص هستند (برای مثال دریافت اطلاعات از هدر درخواست HTTP و انتساب آن‌ها به خواص متناظری در شیء مشخص شده)، منهای FromService آن که به نحو ذیل عمل می‌کند:
در این حالت می‌توان در سازنده‌ی کلاس مدل خود، سرویسی را تزریق کرد و توسط آن خاصیتی را مقدار دهی نمود:
public class ProductModel
{
    public ProductModel(IProductService prodService)
    {
        Value = prodService.Get(productId);
    }
    public IProduct Value { get; private set; }
}
این تزریق وابستگی‌ها برای اینکه تکمیل شود، نیاز به ویژگی FromServices خواهد داشت:
 public async Task<IActionResult> GetProduct([FromServices]ProductModel product)
{
}
وجود ویژگی FromServices به این معنا است که سرویس‌های مدل یاد شده را از تنظیمات ابتدایی IoC Container خود خوانده و سپس در اختیار مدل جاری قرار بده. به این ترتیب حتی تزریق وابستگی‌ها در مدل‌های برنامه هم میسر می‌شود.


تغییر تنظیمات اولیه‌ی خروجی‌های ASP.NET Web API

در اینجا حالت ارائه‌ی خروجی XML به صورت پیش فرض فعال نیست. اگر علاقمند به افزودن آن نیز باشید، نحوه‌ی کار را در متد ConfigureServices کلاس آغازین برنامه در کدهای ذیل مشاهده می‌کنید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.FormatterMappings.SetMediaTypeMappingForFormat("xml", new MediaTypeHeaderValue("application/xml")); 
 
    }).AddJsonOptions(options =>
    {
        options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Include;
        options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
    });
همچنین اگر خواستید تنظیمات ابتدایی JSON.NET را تغییر داده و برای مثال خروجی JSON تولیدی را camel case کنید، این‌کار را توسط متد AddJsonOptions به نحو فوق می‌توان انجام داد.
بازخوردهای پروژه‌ها
خطا هنگام ایجاد هدر سفارشی با html
من میخواستم بااستفاده از html هدرم رو طراحی کنم ولی با خطاهای زیر روبرو میشم : 
[0] = {"Object reference not set to an instance of an object."}
[1] = {"The document has no pages."}

نمیدونم چون که نگارش پایین این کتابخانه رو استفاده میکنم این مشکل بوجود میاد یا نه؟
کدهای من :
PdfReport pdfrpt = new PdfReport();
                pdfrpt.DocumentPreferences(doc =>
                {
                    doc.RunDirection(PdfRunDirection.RightToLeft);
                    doc.Orientation(PageOrientation.Portrait);
                    doc.PageSize(PdfPageSize.A4);
                    doc.DocumentMetadata(new DocumentMetadata { Author = "Hovze", Application = "PdfRpt", Keywords = "Report", Subject = "Test Rpt", Title = "Report" });
                    doc.Compression(new CompressionSettings
                    {
                        EnableCompression = true,
                        EnableFullCompression = true
                    });
                    doc.PrintingPreferences(new PrintingPreferences
                    {
                        ShowPrintDialogAutomatically = true
                    });
                })
                .DefaultFonts(fonts =>
                {
                    fonts.Path(fontPath + "\\BNAZANIN.ttf", fontPath + "\\BNAZNNBD.ttf");
                    fonts.Size(13);
                })
                .PagesFooter(footer =>
                {
                    footer.DefaultFooter(PersianDate.ToPersianDateTime(DateTime.Now, "/", false, false));
                })
                .PagesHeader(header =>
                {
                    
                    header.HtmlHeader(rptheader =>
                    {
                        rptheader.PageHeaderProperties(new HeaderBasicProperties
                        {
                            RunDirection = PdfRunDirection.RightToLeft,
                            ShowBorder = true
                        });
                        rptheader.AddPageHeader(pageHeader =>
                        {
                            var message = "باسمه تعالی " + "</br>" + "حوزه علمیه امیر المومنین (ع) - معاونت آموزش - کارنامه تحصیلی طلبه";
                            var image = string.Format("<img src='{0}' />", imgPath);
                            return string.Format(@"<table style='width: 100%;font-size:9pt;font-family:BNAZANIN;'>
            <tr>
            <td align='center'>{0}</td>
            </tr>
            <tr>
            <td align='center'><b>{1}</b></td>
            </tr>
            <tr>
            <td align='center'>{2}</td>
            </tr>
                </table>", image, message, " تاریخ  " + PersianDate.ToPersianDateTime(DateTime.Now.Date, "/", false));
                        });

                    });
                })
                .MainTableTemplate(template =>
                {
                    template.BasicTemplate(BasicTemplate.SilverTemplate);
                })
                .MainTablePreferences(table =>
                {
                    table.ColumnsWidthsType(TableColumnWidthType.Relative);
                    table.NumberOfDataRowsPerPage(0);
                    table.GroupsPreferences(new GroupsPreferences
                    {
                        GroupType = GroupType.HideGroupingColumns,
                        RepeatHeaderRowPerGroup = true,
                        ShowOneGroupPerPage = true,
                        SpacingBeforeAllGroupsSummary = 10f,
                    });
                })
                .MainTableDataSource(dataSource =>
                {
                    dataSource.DataTable(dt);
                })
                .MainTableSummarySettings(summarySettings =>
                {
                    summarySettings.PreviousPageSummarySettings("نقل از صفحه قبل");
                })
                .MainTableColumns(columns =>
                {
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("سال");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Center);
                        column.IsVisible(true);
                        column.Order(0);
                        column.Width(2);
                        column.HeaderCell("سال تحصیلی");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("نیمسال");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(1);
                        column.Width(1);
                        column.HeaderCell("نیمسال");
                       
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("پایه");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(2);
                        column.Width(1);
                        column.HeaderCell("پایه");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("درس");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(3);
                        column.Width(1);
                        column.HeaderCell("درس");
                    });
                    for (int i = 6; i < countScore + 6; i++)
                    {
                            columns.AddColumn(column =>
                        {
                            column.PropertyName(dt.Columns[i].ToString());
                            column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                            column.IsVisible(true);
                            column.Order(i - 2);
                            column.Width(1);
                            column.HeaderCell(dtTitle.Rows[0][i - 5].ToString());
                        });
                     
                    }
                    
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("FinalScore1");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 4);
                        column.Width(1);
                        column.HeaderCell("نمره نهایی");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("t_term1");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore+5);
                        column.Width(1);
                        column.HeaderCell("نمره تجدیدی");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("t_term11");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 6);
                        column.Width(1);
                        column.HeaderCell("نمره استادیاری");
                    });
                   
                   
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("tabestan1");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 10);
                        column.Width(1);
                        column.HeaderCell("نمره تابستان");
                    });
                    
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("viewTermic");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 11);
                        column.Width(1);
                        column.HeaderCell("ترم نمایشی");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("Ghabol");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 12);
                        column.Width(1);
                        column.HeaderCell("قبول");
                    });
                });
               

                return  pdfrpt.MainTableEvents(events =>
                   {
                       events.DataSourceIsEmpty(message: "داده ای برای نمایش وجو د ندارد");
                       events.MainTableAdded(args =>
                       {
                           var taxTable = new PdfPTable(1);  // Create a clone of the MainTable's structure
                           taxTable.RunDirection = 3;
                           //taxTable.SetWidths(new float[] { 3, 3, 3 });
                           taxTable.WidthPercentage = 100f;
                           taxTable.SpacingBefore = 10f;

                           taxTable.AddSimpleRow(
                               (data, cellProperties) =>
                               {
                                   data.Value = "مهر و امضای مدیر";
                                   cellProperties.ShowBorder = false;
                                   cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                                   cellProperties.PdfFont = args.PdfFont;
                               });
                           args.PdfDoc.Add(taxTable);
                       });
                   })
                .Export(export =>
                {
                    // export.ToExcel();
                })
                .Generate(data => data.AsPdfFile(fo.Name));
               
            }

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 20 - بررسی تغییرات فیلترها
نکته تکمیلی
در صورتیکه قبل از فراخوانی delegate زیر
await next();
بخواید کاری انجام بدید که اکشن متود فراخوانی نشه نباید next رو فراخوانی کنید، چون با خطا مواجه میشوید؛ برای مثال اکشن فیلتری رو نوشتید که اگه آی پی کاربر جزء آی پی‌های بن شده سایت بود صفحه غیر مجاز رو به اون نمایش بده
public class CheckUserIp : IAsyncActionFilter
{
    private readonly string[] _bannedIps;

    public CheckUserIp(params string[] bannedIps)
    {
        _bannedIps = bannedIps;
    }
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var userIp = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty;
        if (_bannedIps.Contains(userIp))
        {
            context.Result = new ViewResult()
            {
                ViewName = "Forbidden"
            };
        }
        else
        {
            await next();
        }
        // await next(); => throw exception
    }
}

در مثال بالا در صورتیکه آی پی کاربر بن شده باشد دیگر next فراخوانی نمیشود و از بروز خطا جلوگیری میکند.
نحوه فراخوانی اکشن فیلتر بالا
[TypeFilter(typeof(CheckUserIp), Arguments = new object[]{ new[] { "::1", "134.56.110.44" } })]
public IActionResult Index()
{
    return View();
}
نظرات مطالب
حذف فضاهای خالی در خروجی صفحات ASP.NET MVC
با تشکر از مطلب ارسالی شما 
برای اینکه فضای خالی به درستی حذف شود و همچنین تگ Pre هم در این الگوریتم لحاظ نشود. می‌توان از اکشن فیلتر زیر استفاده کرد 
public class RemoveWhitespacesAttribute : ActionFilterAttribute
    {

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {

            var response = filterContext.HttpContext.Response;
      
            if (filterContext.HttpContext.Request.RawUrl != "/sitemap.xml")
            {

                if (response.ContentType == "text/html" && response.Filter != null)
                {
                    response.Filter = new HelperClass(response.Filter);
                }
            }
        }

        private class HelperClass : Stream
        {

            private System.IO.Stream Base;

            public HelperClass(System.IO.Stream ResponseStream)
            {

                if (ResponseStream == null)
                    throw new ArgumentNullException("ResponseStream");
                this.Base = ResponseStream;
            }

            StringBuilder s = new StringBuilder();

            public override void Write(byte[] buffer, int offset, int count)
            {

                string HTML = Encoding.UTF8.GetString(buffer, offset, count);

                Regex reg = new Regex(@"(?<=\s)\s+(?![^<>]*</pre>)");
                HTML = reg.Replace(HTML, string.Empty);

                buffer = System.Text.Encoding.UTF8.GetBytes(HTML);
                this.Base.Write(buffer, 0, buffer.Length);
            }

            #region Other Members

            public override int Read(byte[] buffer, int offset, int count)
            {

                throw new NotSupportedException();
            }

            public override bool CanRead { get { return false; } }

            public override bool CanSeek { get { return false; } }

            public override bool CanWrite { get { return true; } }

            public override long Length { get { throw new NotSupportedException(); } }

            public override long Position
            {

                get { throw new NotSupportedException(); }
                set { throw new NotSupportedException(); }
            }

            public override void Flush()
            {

                Base.Flush();
            }

            public override long Seek(long offset, SeekOrigin origin)
            {

                throw new NotSupportedException();
            }

            public override void SetLength(long value)
            {

                throw new NotSupportedException();
            }

            #endregion
        }

    }
برای اجرا هم در Global.asax آن را فراخوانی کرد.  
 protected void Application_Start()
        {
            try
            {
                GlobalFilters.Filters.Add(new App_Start.RemoveWhitespacesAttribute());
            }
            catch
            {
                HttpRuntime.UnloadAppDomain(); // سبب ری استارت برنامه و آغاز مجدد آن با درخواست بعدی می‌شود
                throw;
            }

        }
در نهایت خروجی به شکل زیر رندر می‌شود

برای Gzip هم  اکثر در این حالت که هردو مورد با هم قرار داده شده است در برخی از موارد فایل‌های جاواسکریپ را با مشکل روبرو می‌کند .به نظر من از Gzip توکار IIS استفاده شود بهتر است. البته باید ماژول آن در ISS فعال شده باشد.

برای اینکار هم داخل Web.config کد‌های زیر را داخل configuration قرار بدید.


<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
      <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" />
      <dynamicTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </dynamicTypes>
      <staticTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </staticTypes>
    </httpCompression>
    <urlCompression doStaticCompression="true" doDynamicCompression="true" />
  </system.webServer>
  <location path="Default Web Site">
    <system.webServer>
      <serverRuntime enabled="true"
         frequentHitThreshold="1"
         frequentHitTimePeriod="10:00:00" />
    </system.webServer>
  </location>


مطالب
پشتیبانی توکار از انجام کارهای پس‌زمینه در ASP.NET Core 2x
از زمان ASP.NET Core 2.1، قابلیت جدیدی به نام Generic Host، به آن اضافه شده‌است که از آن می‌توان برای انجام کارهای متداول پس زمینه، مانند ارسال ایمیل‌های خبرنامه‌ی یک برنامه، تهیه فایل‌های پشتیبان و غیره استفاده کرد.


Generic Host چیست؟

Generic Host یکی از ویژگی‌های جدید ASP.NET Core 2.1 است. هدف آن جداسازی HTTP pipeline برنامه، از Web Host API آن است. یکی از مزایای این‌کار، امکان استفاده‌ی از آن نه فقط در پروژه‌های وب، بلکه در پروژه‌های کنسول نیز می‌باشد. به این ترتیب می‌توان کارهای غیر HTTP را از برنامه‌ی وب مجزا کرد تا به کارآیی بیشتری رسید و برای این منظور اینترفیس IHostedService را که در فضای نام Microsoft.Extensions.Hosting قرار دارد، برای ثبت کارهای پس‌زمینه‌ی خارج از اعمال web host جاری، ارائه داده‌اند:
namespace Microsoft.Extensions.Hosting
{
    public interface IHostedService
    {
        Task StartAsync(CancellationToken cancellationToken);
        Task StopAsync(CancellationToken cancellationToken);
    }
}
بنابراین برای ایجاد یک HostedService، نیاز است سرویس کارهای پس‌زمینه‌ی ما، اینترفیس IHostedService را پیاده سازی کند. متد StartAsync آن جائی‌است که تنها یکبار پس از آغاز برنامه اجرا می‌شود و هدف آن اجرای کار پس‌زمینه‌ی مدنظر است. متد StopAsync نیز دقیقا پیش از خاتمه‌ی برنامه فراخوانی خواهد شد تا اگر نیاز به پاکسازی منابعی وجود داشته باشد، بتوان از این فرصت استفاده کرد. به این ترتیب اگر نیاز به اجرای متناوب کار پس‌زمینه‌ای وجود دارد، پیاده سازی آن به خود ما واگذار شده‌است.


یک مثال: معرفی کار پس‌زمینه‌ای که هر دو ثانیه یکبار انجام می‌شود

در SampleHostedService زیر، عبارت Hosted service executing به همراه زمان جاری، هر دو ثانیه یکبار لاگ می‌شود و اگر برنامه را توسط دستور dotnet run اجرا کنید، می‌توانید خروجی آن‌را در کنسول، مشاهده کنید:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace MvcTest
{
    public class SampleHostedService : IHostedService
    {
        private readonly ILogger<SampleHostedService> _logger;

        public SampleHostedService(ILogger<SampleHostedService> logger)
        {
            _logger = logger;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting Hosted service");

            while (!cancellationToken.IsCancellationRequested)
            {
                _logger.LogInformation("Hosted service executing - {0}", DateTime.Now);
                await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Stopping Hosted service");
            return Task.CompletedTask;
        }
    }
}
در ادامه برای معرفی این کار پس‌زمینه به سیستم به صورت یک سرویس با طول عمر Singleton خواهیم داشت:
namespace MvcTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IHostedService, SampleHostedService>();
روش دیگر انجام اینکار استفاده از متد الحاقی AddHostedService است:
services.AddHostedService<SampleHostedService>();
مزیت اینکار این است که متد Configure واقع در کلاس Startup یک چنین امضایی را دارد:
 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
و IHostingEnvironment هم در فضای نام Microsoft.AspNetCore.Hosting واقع شده‌است و هم در فضای نام Microsoft.Extensions.Hosting که IHostedService در آن قرار دارد. به همین جهت چون متد AddHostedService، تعریف IHostedService را مخفی می‌کند، خطای زمان کامپایلی را جهت مشخص سازی صریح فضای نام  IHostingEnvironment دریافت نخواهید کرد:
Startup.cs(82,56): error CS0104: 'IHostingEnvironment' is an ambiguous reference between
'Microsoft.AspNetCore.Hosting.IHostingEnvironment' and 'Microsoft.Extensions.Hosting.IHostingEnvironment'


مشکلات پیاده سازی کار پس‌زمینه‌ی SampleHostedService فوق

هر چند اگر مثال فوق را اجرا کنید، خروجی مناسبی را دریافت خواهید کرد، اما دارای این اشکال مهم نیز هست:
D:\MvcTest>dotnet run
info: MvcTest.SampleHostedService[0]
      Starting Hosted service
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 14:45:10
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 14:45:12
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 14:45:14
Ctrl+C
Application is shutting down...
Hosting environment: Development
Content root path: D:\MvcTest
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
پس از اجرای دستور dotnet run، سرویس پس زمینه شروع به کار کرده‌است. پس از مدتی کلیدهای Ctrl+C را فشرده‌ایم تا این حلقه‌ی بی‌نهایت و برنامه خاتمه یابد. اینجا است که مشاهده می‌کنید تازه قسمت هاست برنامه‌ی وب ما شروع به کار کرده‌است؛ یعنی دقیقا زمانیکه پروسه‌ی برنامه در حال خاتمه یافتن است. چرا اینگونه رفتار کرده‌است؟
از دیدگاه ASP.NET Core، یک کار پس زمینه زمانی خاتمه یافته محسوب می‌شود که متد StartAsync، مقدار Task.CompletedTask را بازگرداند؛ در غیراینصورت، در حال اجرا درنظر گرفته می‌شود و چون در پیاده سازی فوق این نکته رعایت نشده‌است، این Task همواره در حال اجرا و خاتمه نیافته محسوب می‌شود و نوبت به مابقی کارها نخواهد رسید. همچنین در قسمت StopAsync نیز بهتر است یک فیلد CancellationTokenSource تعریف شده‌ی در سطح کلاس را مورد استفاده قرار داد و متد Cancel آن‌را فراخوانی کرد تا اطلاع رسانی صحیحی را به متد StartAsync در مورد خاتمه‌ی برنامه، انجام دهد.
برای این منظور و جهت ساده سازی و پیاده سازی تمام این نکات، از اینترفیس خام IHostedService، یک کلاس abstract به نام BackgroundService نیز در فضای نام Microsoft.Extensions.Hosting پیش بینی شده‌است:
namespace Microsoft.Extensions.Hosting
{
    public abstract class BackgroundService : IHostedService, IDisposable
    {
        protected BackgroundService();
        public virtual void Dispose();
        public virtual Task StartAsync(CancellationToken cancellationToken);
        public virtual Task StopAsync(CancellationToken cancellationToken);
        protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
    }
}
برای استفاده‌ی از آن تنها کافی است متد ExecuteAsync آن‌را پیاده سازی کنیم. به این ترتیب اینبار پیاده سازی SampleHostedService به صورت زیر تغییر می‌کند:
namespace MvcTest
{
    public class PrinterHostedService : BackgroundService
    {
        private readonly ILogger<SampleHostedService> _logger;

        public PrinterHostedService(ILogger<SampleHostedService> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Starting Hosted service");

            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Hosted service executing - {0}", DateTime.Now);
                await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken);
            }
        }
    }
}
اینبار اگر این کار پس‌زمینه را به سیستم معرفی:
namespace MvcTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHostedService<PrinterHostedService>();
و سپس برنامه را اجرا کنیم:
D:\MvcTest>dotnet run
Hosting environment: Development
infoContent root path: D:\MvcTest
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
: MvcTest.SampleHostedService[0]
      Starting Hosted service
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 15:00:23
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 15:00:25
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 15:00:27
Application is shutting down...
^C
مشاهده می‌کنیم که ابتدا هاست وب برنامه شروع به کار کرده‌است و سپس سرویس انجام کارهای پس‌زمینه در حال اجرا است و به این ترتیب اجرای این سرویس پس‌زمینه، تداخلی را در کار برنامه‌ی وب ایجاد نکرده‌است. بنابراین از این پس بجای استفاده‌ی از IHostedService خام، از نمونه‌ی بهبود یافته‌ی BackgroundService آن استفاده کنید.


یک نکته: تزریق وابستگی DbContext برنامه در یک سرویس کار پس‌زمینه

IHostedServiceها با طول عمر singleton به سیستم تزریق وابستگی‌ها معرفی می‌شوند. در این حالت اگر سرویس‌هایی با طول عمر transient و یا scoped را به آن‌ها تزریق کنید، دیگر طول عمر مدنظر شما را نداشته و آن‌ها هم به صورت singleton عمل خواهند کرد. هر چند خود سیستم تزریق وابستگی‌های NET Core. با صدور استثنائی، از این مساله جلوگیری می‌کند (در این مورد در مطالب «مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت چهارم - پرهیز از الگوی Service Locator در برنامه‌های وب» و همچنین «قسمت سوم - رهاسازی منابع سرویس‌های IDisposable» بیشتر بحث شده‌است). یک چنین مواردی را به صورت زیر با تزریق IServiceScopeFactory و ساخت صریح یک Scope می‌توان مدیریت کرد:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public abstract class ScopedBackgroundService : BackgroundService
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public ScopedBackgroundService(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            await ExecuteInScope(scope.ServiceProvider, stoppingToken);
        }
    }

    public abstract Task ExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken);
}
از این پس برای تعریف کارهای پس‌زمینه‌ای که نیاز به تزریق سرویس‌هایی با طول عمر Scoped یا Transient دارند، می‌توان کلاس سرویس وظیفه را از ScopedBackgroundService مشتق کرد و سپس متد ExecuteInScope آن‌را پیاده سازی نمود. serviceProvider ای که در اینجا در اختیار مصرف کننده قرار می‌گیرد، داخل Scope قرار دارد و توسط آن می‌توان سرویس‌های مدنظر را توسط متدهایی مانند serviceProvider.GetRequiredService، دریافت کرد.


طراحی سرویس کارهای پس‌زمینه‌ی زمان‌بندی شده

ASP.NET Core، متد ExecuteAsync را یکبار بیشتر اجرا نمی‌کند. بنابراین پیاده سازی تایمری که بخواهد برای مثال ارسال ایمیل‌های خبرنامه‌ی سایت را هر روز ساعت 11 شب انجام دهد، به خود ما واگذار شده‌است. برای پیاده سازی بهتر این تایمر می‌توان از کتابخانه‌ی NCrontab که توسط نویسنده‌ی کتابخانه‌ی معروف ELMAH تهیه شده‌است، استفاده کرد که با برنامه‌های NET Core. نیز سازگاری دارد:
 dotnet add package ncrontab
عبارات Cron، روش بسیار متداولی برای تعریف و انجام کارهای زمانبندی شده در سیستم‌های لینوکسی هستند. برای مثال عبارت * * * 0 1 سبب اجرای یک وظیفه، هر روز یک دقیقه پس از نیمه‌شب، می‌شود و فرمت کلی 5 قسمتی آن، به صورت زیر است:
┌───────────── minute (0 - 59) 
│ ┌───────────── hour (0 - 23) 
│ │ ┌───────────── day of month (1 - 31) 
│ │ │ ┌───────────── month (1 - 12) 
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday; 
│ │ │ │ │                                       7 is also Sunday on some systems) 
│ │ │ │ │ 
│ │ │ │ │ 
* * * * *
و یا عبارت 6 قسمتی آن چنین مفهومی را دارد:
* * * * * *
- - - - - -
| | | | | |
| | | | | +--- day of week (0 - 6) (Sunday=0)
| | | | +----- month (1 - 12)
| | | +------- day of month (1 - 31)
| | +--------- hour (0 - 23)
| +----------- min (0 - 59)
+------------- sec (0 - 59)
اگر ScopedBackgroundService فوق را با CrontabSchedule یاد شده ترکیب کنیم، می‌توانیم به یک کلاس abstract دیگر برسیم که طراحی کلاس پایه‌ی اجرای کارهای زمانبندی شده را ارائه می‌دهد:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NCrontab;
using static NCrontab.CrontabSchedule;

public abstract class ScheduledScopedBackgroundService : ScopedBackgroundService
{
    private CrontabSchedule _schedule;
    private DateTime _nextRun;

    protected abstract string Schedule { get; }

    public ScheduledScopedBackgroundService(IServiceScopeFactory serviceScopeFactory)
     : base(serviceScopeFactory)
    {
        _schedule = CrontabSchedule.Parse(Schedule, new ParseOptions { IncludingSeconds = true });
        _nextRun = _schedule.GetNextOccurrence(DateTime.Now);
    }

    public override async Task ExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken)
    {
        do
        {
            var now = DateTime.Now;
            if (now > _nextRun)
            {
                await ScheduledExecuteInScope(serviceProvider, stoppingToken);
                _nextRun = _schedule.GetNextOccurrence(DateTime.Now);
            }
            await Task.Delay(1000, stoppingToken); //1 second delay
        }
        while (!stoppingToken.IsCancellationRequested);
    }

    public abstract Task ScheduledExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken);
}
این کلاس پایه، توسط متد CrontabSchedule.Parse، مقدار رشته‌ای Schedule را با فرمت Cron (فرمت 6 قسمتی که دارای ثانیه هم هست) دریافت و پردازش می‌کند. سپس متد GetNextOccurrence، زمان بعدی اجرای این وظیفه را مشخص می‌کند.
روش استفاده‌ی از آن برای تعریف یک وظیفه‌ی جدید نیز به صورت زیر است:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class MyScheduledTask : ScheduledScopedBackgroundService
{
    private readonly ILogger<MyScheduledTask> _logger;

    public MyScheduledTask(
        IServiceScopeFactory serviceScopeFactory,
        ILogger<MyScheduledTask> logger) : base(serviceScopeFactory)
    {
        _logger = logger;
    }

    protected override string Schedule => "*/10 * * * * *"; //Runs every 10 seconds

    public override Task ScheduledExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken)
    {
        _logger.LogInformation("MyScheduledTask executing - {0}", DateTime.Now);
        return Task.CompletedTask;
    }
}
در اینجا ابتدا کار با پیاده سازی کلاس پایه ScheduledScopedBackgroundService شروع می‌شود. سپس باید مقدار Schedule را با فرمت 6 قسمتی مشخص کرد. برای مثال در سرویس فوق، این تنظیم سبب اجرای هر 10 ثانیه یکبار این وظیفه می‌گردد. در آخر، خود وظیفه داخل متد ScheduledExecuteInScope تعریف خواهد شد که serviceProvider دریافتی آن، داخل یک Scope قرار دارد.
روش معرفی آن به سیستم نیز مانند قبل است:
namespace MvcTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHostedService<MyScheduledTask>();
در این حالت اگر برنامه را اجرا کنید، یک چنین خروجی را که بیانگر اجرای هر 10 ثانیه یکبار وظیفه‌ی تعریف شده‌است، مشاهده می‌کنید:
D:\MvcTest>dotnet run
Hosting environment: Development
Content root path: D:\MvcTest
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
info: MyScheduledTask[0]
      MyScheduledTask executing - 02/19/2019 19:18:50
info: MyScheduledTask[0]
      MyScheduledTask executing - 02/19/2019 19:19:00
info: MyScheduledTask[0]
      MyScheduledTask executing - 02/19/2019 19:19:10
Application is shutting down...
^C
مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #1
نگاشت اشیاء امری مفید و لذت بخش است. ولی بخاطر تنظیمات خاص آن و افزایش کدها، همیشه کمی دردسر ساز بوده است. استفاده از کلاس Profile راه کار مناسبی است؛ اما در این حالت کلاس مقصد (ViewModel) از تنظیمات نگاشت‌ها بی اطلاع می‌ماند و فقط حاوی داده خواهد بود. برای ادغام کلاس و تنظیمات نگاشت در اینجا راهکاری ارائه گردید که در ادامه و با الگو گیری از همین ایده، اقدام به ارائه‌ی روشی جدید می‌کنم که با استفاده از Attribute‌ها تنظیمات نگاشت اشیاء را در AutoMapper انجام می‌دهد.
در نهایت می‌خواهیم نگاشت‌ها را اینچنین تنظیم کنیم:
 [MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
 public class AdminStudentViewModel
 {
     // [IgnoreMap]
     public int Id { set; get; }

     [MapForMember("Name")]
     public string FirstName { set; get; }

     [MapForMember("Family")]
     public string LastName { set; get; }

     public string Email { set; get; }

     [MapForMember("RegisterDateTime")]
     public string RegisterDateTimePersian { set; get; }

     [UseValueResolver(typeof (BookCountValueResolver))]
     public int BookCounts { set; get; }

     [UseValueResolver(typeof (BookPriceValueResolver))]
     public decimal BookPrice { set; get; }
 };
  این سبک تنظیم کردن نگاشت‌های اشیاء به نظر بهتر از روش‌های دیگر است؛ چون کلاس‌های ویوومدل را معنادار کرده و همچنین برای برنامه نویسان EF و ASP.NET MVC استفاده‌ی از ویژگی‌ها، یک شیوه‌ی کاری معمول به حساب می‌آید. 
به تعریف و توضیح صفت‌های (ویژگی‌ها یا Attributes) مورد نیاز می‌پردازم:

صفت MapFromAttribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MapFromAttribute : Attribute
{
    public Type SourceType { get; private set; }
    public bool IgnoreAllNonExistingProperty { get; private set; }
    public bool AlsoCopyMetadata { get; private set; }    //Go to: https://www.dntips.ir/courses/topic/16/cb36bc2e-4263-431e-86a5-236322cb5576

    public MapFromAttribute(Type sourceType, bool ignoreAllNonExistingProperty = false,
        bool alsoCopyMetadata = false)
    {
        SourceType = sourceType;
        IgnoreAllNonExistingProperty = ignoreAllNonExistingProperty;
        AlsoCopyMetadata = alsoCopyMetadata;
    }
};
این صفت روی کلاس‌ها می‌نشیند و توسط آرگومان sourceType آن، نوع مبدأ را برای automapper مشخص می‌کند. در واقع همه چیز از اینجا شروع می‌شود. همچنین آرگومان ignoreAllNonExistingProperty مشخص می‌کند کلیه‌ی صفاتی که در مقصد هستند ولی معادل اسمی در مبدأ ندارند، بصورت خودکار رد (Ignore) شده و از آن‌ها صرف نظر شود تا از شکست متد AutoMapper.Mapper.AssertConfigurationIsValid جلوگیری کند (پرداخته شده در اینجا). آرگومان alsoCopyMetadata پیاده سازی نمی‌شود؛ ولی می‌تواند پرچمی باشد تا اجازه دهد  Data Annotations از مدل‌های ef به ViewModel انتقال یابند.


صفت IgnoreMapAttribute
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreMapAttribute : Attribute {};
از این صفت برای رد کردن خصیصه‌‌ای در نگاشت‌ها استفاده می‌کنیم. لازم به ذکر است که صفتی مشابه در Automapper.IgnoreAttribute وجود دارد که می‌تواند به جای این صفت مورد استفاده قرار گیرد. «نگارنده جهت همخوانی با سایر صفات، اقدام به استفاده‌ی از این صفت می‌کند»


صفت MapForMemberAttribute
[AttributeUsage(AttributeTargets.Property)]
public class MapForMemberAttribute : Attribute
{
    public string MemberToMap { get; private set; }
    public MapForMemberAttribute(string memberToMap)
    {
        MemberToMap = memberToMap;
    }
};
اگر نام خصیصه‌ها در مبدأ و مقصد یکی نباشند، از این صفت برای همگام سازی این دو استفاده می‌کنیم.


صفت UseValueResolverAttribute
[AttributeUsage(AttributeTargets.Property)]
public class UseValueResolverAttribute : Attribute
{
    public IValueResolver ValueResolver { get; private set; }
    public UseValueResolverAttribute(Type valueResolver)
    {
        ValueResolver = valueResolver.GetConstructors()[0].Invoke(new object[] {}) as IValueResolver;
    }
};
استفاده از ValueResolver‌ها در اینجا ذکر شده است. از این صفت برای تنظیم این مقدار برای یک خصیصه استفاده می‌شود. برای مثال فیلد FullName را در مقصد درنظر بگیرد که از دو فیلد Name و Family در مبدأ تشکیل می‌شود.

تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفت‌ها را به نگاشت متناسبی در automapper تبدیل کنیم.
دریافت کدها
ادامه دارد...
مطالب
پیدا کردن آیتم‌های تکراری در یک لیست به کمک LINQ

گاهی از اوقات نیاز می‌شود تا در یک لیست، آیتم‌های تکراری موجود را مشخص کرد. به صورت پیش فرض متد Distinct برای حذف مقادیر تکراری در یک لیست با استفاده از LINQ موجود است که البته آن‌هم اما و اگرهایی دارد که در ادامه به آن پرداخته خواهد شد، اما باز هم این مورد پاسخ سؤال اصلی نیست (نمی‌خواهیم موارد تکراری را حذف کنیم).

برای حذف آیتم‌های تکراری از یک لیست جنریک می‌توان متد زیر را نوشت:
public static List<T> RemoveDuplicates<T>(List<T> items)
{
return (from s in items select s).Distinct().ToList();
}
برای مثال:
public static void TestRemoveDuplicates()
{
List<string> sampleList =
new List<string>() { "A1", "A2", "A3", "A1", "A2", "A3" };
sampleList = RemoveDuplicates(sampleList);
foreach (var item in sampleList)
Console.WriteLine(item);
}
این متد بر روی لیست‌هایی با نوع‌های اولیه مانند string‌ و int و امثال آن درست کار می‌کند. اما اکنون مثال زیر را در نظر بگیرید:
public class Employee
{
public int ID { get; set; }
public string FName { get; set; }
public int Age { get; set; }
}

public static void TestRemoveDuplicates()
{
List<Employee> lstEmp = new List<Employee>()
{
new Employee(){ ID=1, Age=20, FName="F1"},
new Employee(){ ID=2, Age=21, FName="F2"},
new Employee(){ ID=1, Age=20, FName="F1"},
};

lstEmp = RemoveDuplicates<Employee>(lstEmp);

foreach (var item in lstEmp)
Console.WriteLine(item.FName);
}
اگر متد TestRemoveDuplicates را اجرا نمائید، رکورد تکراری این لیست جنریک حذف نخواهد شد؛ زیرا متد distinct بکارگرفته شده نمی‌داند اشیایی از نوع کلاس سفارشی Employee را چگونه باید با هم مقایسه نماید تا بتواند موارد تکراری آن‌ها را حذف کند.
برای رفع این مشکل باید از آرگومان دوم متد distinct جهت معرفی وهله‌ای از کلاسی که اینترفیس IEqualityComparer را پیاده سازی می‌کند، کمک گرفت.
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);
که نمونه‌ای از پیاده سازی آن به شرح زیر می‌تواند باشد:

public class EmployeeComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
//آیا دقیقا یک وهله هستند؟
if (Object.ReferenceEquals(x, y)) return true;

//آیا یکی از وهله‌ها نال است؟
if (Object.ReferenceEquals(x, null) ||
Object.ReferenceEquals(y, null))
return false;

return x.Age == y.Age && x.FName == y.FName && x.ID == y.ID;
}

public int GetHashCode(Employee obj)
{
if (Object.ReferenceEquals(obj, null)) return 0;
int hashTextual = obj.FName == null ? 0 : obj.FName.GetHashCode();
int hashDigital = obj.Age.GetHashCode();
return hashTextual ^ hashDigital;
}
}
اکنون اگر یک overload برای متد RemoveDuplicates با درنظر گرفتن IEqualityComparerتهیه کنیم، به شکل زیر خواهد بود:
public static List<T> RemoveDuplicates<T>(List<T> items, IEqualityComparer<T> comparer)
{
return (from s in items select s).Distinct(comparer).ToList();
}
به این صورت متد آزمایشی ما به شکل زیر (که وهله‌ای از کلاس EmployeeComparer‌ به آن ارسال شده) تغییر خواهد کرد:
public static void TestRemoveDuplicates()
{
List<Employee> lstEmp = new List<Employee>()
{
new Employee(){ ID=1, Age=20, FName="F1"},
new Employee(){ ID=2, Age=21, FName="F2"},
new Employee(){ ID=1, Age=20, FName="F1"},
};

lstEmp = RemoveDuplicates(lstEmp, new EmployeeComparer());

foreach (var item in lstEmp)
Console.WriteLine(item.FName);
}
پس از این تغییر، حاصل این متد تنها دو رکورد غیرتکراری می‌باشد.

سؤال: برای یافتن آیتم‌های تکراری یک لیست چه باید کرد؟
احتمالا مقاله "روش‌هایی برای حذف رکوردهای تکراری" را به خاطر دارید. اینجا هم می‌توان کوئری LINQ ایی را نوشت که رکوردها را بر اساس سن، گروه بندی کرده و سپس گروه‌هایی را که بیش از یک رکورد دارند، انتخاب نماید.
public static void FindDuplicates()
{
List<Employee> lstEmp = new List<Employee>()
{
new Employee(){ ID=1, Age=20, FName="F1"},
new Employee(){ ID=2, Age=21, FName="F2"},
new Employee(){ ID=1, Age=20, FName="F1"},
};

var query = from c in lstEmp
group c by c.Age into g
where g.Count() > 1
select new { Age = g.Key, Count = g.Count() };

foreach (var item in query)
{
Console.WriteLine("Age {0} has {1} records", item.Age, item.Count);
}
}


Vote on iDevCenter
مطالب
بررسی چند کتابخانه آپلود با پشتیبانی از DragDrop
برای یکی از پروژه‌ها نیاز به یک آپلودر داشتم که قابلیت  Drag&Drop را نیز داشته باشد و در ضمن پیاده سازی آسانی هم داشته باشد. در این بین به تعدادی از کتابخانه‌های جی کوئری می‌پردازیم.
FileDrop
اولین کتابخانه‌ای که با آن آشنا شدم و از آن استفاده کردم، کتابخانه‌ی FileDrop است که بسیار ساده و در عین حال قابلیت‌های خوبی را می‌دهد و از فناوری Filereader  (+) در Html5 برای اینکار استفاده می‌کند. مرورگرهای کروم، فایرفاکس 3.6 به بعد، IE10 به بعد و Opera 12 به بعد از آن پشتیبانی می‌کنند.
فایل‌های مورد نیاز را از اینجا دانلود کنید . فایل اسکریت آن را ابتدا صدا بزنید:
 <script src="~/scripts/jquery.filedrop.js" type="text/javascript"></script>
سپس المان‌های زیر را به کد HTML خود اضافه کنید:
 <div id="dropZone">فایل برنامه را به داخل این کادر بکشانید</div>
        <br>
        فایل یا فایل‌های آپلود شده:
        <ul id="uploadResult"></ul>
تگ اول، محلی است که فایل‌ها به سمت آن درگ و روی آن دراپ می‌شوند که از این به بعد به آن محل آپلود می‌گوییم. المان بعدی جهت گزارش فایل‌هایی است که آپلود شده‌اند. با آپلود شدن هر تعداد فایل، اسم آن به لیست اضافه می‌گردد.
کدهای css زیر را هم به صفحه اضافه کنید تا محل آپلود زیباتر شود:
        .files {
            min-height: 42px;
            background: #CCC none repeat scroll 0% 0%;
            border-top: 1px solid #FFF;
            margin: 11px 0px;
            padding: 11px 13px;
            border-radius: 6px;
        }

        #dropZone.mouse-over {
            background-color: #1d4257;
        }

کد جی کوئری زیر را به صفحه اضافه کنید:
      $('#dropZone').filedrop({
                url: uploadAddress,
                paramname: 'files',
                maxFiles: 1,
                dragOver: function() {
                    $('#dropZone').addClass('mouse-over');
                },
                dragLeave: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                drop: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                afterAll: function() {
                    $('#dropZone').html('آپلود با موفقیت انجام شد');
                },
                uploadFinished: function(i, file, response, time) {
                    $('#uploadResult').append('<li>' + file.name + '</li>');
                }
            });
ابتدا پلاگین جی کوئری را روی تگ مربوطه اعمال می‌کنیم و سپس پارامترها را با مشخصات زیر اعمال می‌کنیم:
Url  آدرسی که قرار است فایل‌ها به آن سمت ارسال شوند. 
 Paramname  در سمت سرور باید فایل‌ها را با استفاده از این نام پارامتر دریافت  کنید.
 maxFiles  تعداد فایلهایی که میتوان با درگ و دراپ کردن روی آن به دست آورد. در بالا به یک فایل محدود شده است.
 dragOver  این رویداد زمانی اجرا خواهد شد که اشاره گر با حالت درگ کرده فایل‌ها را به محل آپلود آورده است.
 dragLeave  موقعی که ماوس از محل آپلود خارج می‌شود
 drop  موقعی که شما فایل‌ها را روی محل  آپلود رها می‌کنید.
 afterAll  بعد از اینکه همه کارها تمام شد اجرا می‌شود.(آخرین رویداد)
 uploadFinished  کار آپلود به پایان رسیده است. در مثال بالا پس از پایان آپلود، نام فایل آپلود شده را به کاربر نشان داده‌ایم.

نحوه‌ی دریافت آن در سمت سرور, در یک اکشن متد به صورت زیر است:

[HttpPost]
        public virtual ActionResult UpdateApp(IEnumerable<HttpPostedFileBase>files)
        {
            foreach (HttpPostedFileBase file in files)
            {
                string filePath = Path.Combine(TempPath, file.FileName);
                file.SaveAs(filePath);
            }

            return Json(new {state = "success", message = "با موفقیت عملیات ارسال فایل انجام شد"}, JsonRequestBehavior.AllowGet);
        }

در اکشن متد بالا ما فایل‌ها را از طریق نام پارامتر files که مشخص کرده بودیم، به عنوان یک لیست شمارشی دریافت می‌کنیم.  کدها بالا برای ساده‌ترین راه اندازی ممکن کفایت می‌کنند.

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


fallback_id 
 اگر دوست دارید این آپلودر را نیر به یک آپلودر معمولی اتصال دهید از این شناسه استفاده کنید.
 withCredentials   با استفاده از کوکی‌ها یک درخواست cross-origin ایجاد می‌کند.
 data  اگر دوست دارید به همراه فایل‌ها اطلاعات دیگری هم به همراه آن ارسال و پست شوند از این طریق اقدام نمایید. می‌تواند در قالب یک متغیر باشد یا خروجی یک تابع.
data: {
        param1: 'value1',           
        param2: function(){
            return calculated_data; 
        }
 headers برای ارسال مقدار اضافه‌تر در هدر درخواست به کار میرود و صدا زدن آن همانند کد data می‌باشد. 
 error   در صورتیکه در فرایند آپلود خطایی رخ دهد، اجرا می‌گردد. نحوه‌ی کدنویسی آن و بررسی خطاهای آن به شرح زیر است:
error: function(err, file) {
        switch(err) {
            case 'BrowserNotSupported':
                alert('مرورگر از این فناوری پشتیبانی نمی‌کند')
                break;
            case 'TooManyFiles':
                // قصد آپلود همزمان فایل‌های بیشتری از حد مجاز تعیین شده دارید
                break;
            case 'FileTooLarge':
                //حداقل حجم یکی از فایل‌ها از حجم مجاز تعیین شده بیشتر است
                //برای دسترسی به نام آن فایل از کد زیر استفاده کنید
              //file.name
                break;
            case 'FileTypeNotAllowed':
                // نوع حداقل یکی از فایل‌ها با نوع‌ها مشخص شده ما یکی نیست
                break;
            case 'FileExtensionNotAllowed':
                // پسوند حداقل یکی از فایل‌ها مورد تایید نیست
                break;
            default:
                break;
        }
    }
 allowedfiletypes   نوع فایل‌های مجاز را تعیین می‌کند:
allowedfiletypes: 
['image/jpeg','image/png','image/gif']
 allowedfileextensions   پسوند فایل هایی که برای آپلود مجاز هستند را معرفی می‌کند.
allowedfileextensions:
 ['.apk','.jar']
 maxfilesize   حداکثر حجم مجاز برای هر فایل که به مگابایت بیان می‌شود.
 docOver   این رویداد زمانی اجرا می‌شود که فایل‌های درگ شده شما وارد محیط یا پنجره مرورگر می‌شود.
 uploadStarted 
 این رویداد زمانی اجرا میگردد که فرایند آپلود هر فایل به طور جداگانه در حال آغاز شدن است:
متغیر i در کد زیر شامل اندیس فایلی است که آپلودش آغاز شده است و این اندیس از صفر آغاز می‌شود.
متغیر file دسترسی شما را به اطلاعات یک فایل باز میکند مانند نام فایل.
متغیر len تعداد فایل هایی را که کاربر در محل آپلود رها کرده است، باز میگرداند.
function(i, file, len){

    },
uploadFinished 
با اتمام آپلود هر فایل، این رویداد فراخوانی می‌گردد. دو پارامتر اول آن، همانند سابق هستند. پارامتر response خروجی json ایی را که در سمت سرور برگرداندیم، به ما باز می‌گرداند. پارامتر بعدی، زمانی را که برای آپلود طول کشیده است، بر می‌گرداند.
 function(i, file, response, time) {
    }
progressUpdated 
این رویداد برای نمایش پیشرفت یک آپلود مناسب است که آخرین پارامتر آن یک عدد صحیح از پیشرفت فایل را بر می‌گرداند.
function(i, file, progress) {
    },
globalProgressUpdated 
این رویداد میزان پیشرفت کلیه فایل‌ها را به درصد باز می‌گرداند:
function(progress) {
        $('#progress div')
.width(progress+"%"); }
speedUpdated 
سرعت آپلود هر فایل را با کیلوبیت بر ثانیه مشخص می‌کند.
function(i, file, speed) {
    }
rename
در صورتی که قصد تغییر نام فایل ارسالی را دارید می‌توانید از این رویداد استفاده کنید. پارامتر name، نام اصلی فایل را بر می‌گرداند که می‌توانید آن را دستکاری کنید و نام جدیدی را به عنوان خروجی برگردانید. نمونه کاربردی از این رویداد
 rename: function(name) {
    }

beforeEach 
این رویداد قبل از آپلود هر فایل آغاز می‌گردد و برگرداندن مقدار false در آن باعث جلوگیری و کنسل شدن آپلود آن فایل می‌گردد.
function(file) {
    }

beforeSend 
پارامترهای اولی تکراری هستند ولی آخرین پارامتر یک تابع done را می‌توان به آن پاس کرد که قبل از اجرای کل عملیات آپلود صدا زده می‌شود.
 function(file, i, done) {
    }
رویدادی به اسم queuefiles هم هست تعداد فایل‌هایی را که میتوانند به طور موازی و همزمان آپلود گردند، مشخص می‌کند. ولی دراین حالت maxfiles مورد استفاده قرار نمی‌گیرد. جهت بررسی یک مثال عملی و همچنین کدهای سمت سرور در PHP میتوانید از این آموزش استفاده کنید.
با تستی که به صورت لوکال رو آن انجام دادم به نظر نمی‌رسد برای فایل‌های با حجم متوسط به بالا مناسب باشد و برای فایل‌های با حجم کم مناسب می‌باشد. یک فایل 8 مگابایتی در حالت لوکال 9 ثانیه آپلود آن زمان برد و برای فایل‌های بزرگتر، فایرفاکس دیالوگ Stop Script را نشان داد.

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

Bootstrap FileStyle
اگر از قالب بوت استراپ استفاده می‌کنید و دوست دارید روی المان input file  قدیمی، ولی به شکلی مدرن کار کنید این کتابخانه هم فراموش نشود.

DropZoneJS
این کتابخانه به نسبت DropFile امکانات بیشتری را دارد و در سایت اختصاصی آن مثال‌ها و مستندات خوبی قرار گرفته است. در ساده‌ترین حالت آن ابتدا فایل کتابخانه  را صدا زده و سپس تگ فرم را به آن نسبت دهید:
<script src="https://rawgit.com/enyo/dropzone/master/dist/dropzone.js"></script>

<form action="/upload-target" class="dropzone"></form>
ولی اگر بخواهید آن را به سمت سرور ارسال کنید و  از آنجا آن را کنترل کنید، کد فرم را به شکل زیر تغییر دهید:
ابتدا بسته‌ی نیوگت آن را صدا بزنید:
Install-Package dropzone

با نصب این کتابخانه یک سری فایل CSS هم به سیستم اضافه می‌شود که می‌توانید برای استایل دهی هر چه بیشتر از آن بهره ببرید. کد فرم را به شکل زیر تغییر دهید:
    <form action="~/Home/SaveUploadedFile" method="post" enctype="multipart/form-data" class="dropzone" id="dropzoneForm" style="width: 50px; background: none; border: none;">
        <div class="fallback">
            <input name="file" type="file" multiple />
            <input type="submit" value="Upload" />
        </div>
    </form>
تگی که با کلاس fallback مزین شده است موقعی به کار می‌آید که مرورگر از این کتابخانه پشتیبانی نکرده و مجبور به نمایش یک آپلود معمولی می‌شویم.
با استفاده از کدنویسی هم می‌توان یک المان را به یک آپلودر تبدیل کرد:
var myDropzone = new Dropzone("div#myId", { url: "/file/post"});

//============ OR ====================
$("div#myId").dropzone({ url: "/file/post" });
همانطور که می‌بینید الزامی برای اینکه از یک تگ فرم استفاده کنید ندارد.
برای کانفیگ آپلودرهایی که از طریف المانهای Html ایجاد می‌شوند، می‌توان از کد زیر استفاده کرد و یک تنظیم عمومی برای تمامی آپلودرهای html آن صفحه ایجاد کرد.
Dropzone.options.myId= {
  paramName: "file", //نام پارامتری که فایل از طریق آن انتقال می‌بابد
  maxFilesize: 2, // MB
  accept: function(file, done) {
    if (file.name == "justinbieber.jpg") {
      done("Naha, you don't.");
    }
    else { done(); }
  }
};
تابع بالا یک آرگومان از نوع file را برگردانده و اگر این تابع، تابع done را با پارامتری رشته‌ای صدا بزند، عملیات آپلود آن فایل کنسل شده و پیام خطایی را نمایش می‌دهد و در صورتیکه بدون پارامتر صدا زده شود، عمل آپلود بدون مشکل انجام می‌شود.
ازآنجا که این کتابخانه از تنظیمات وسیعی استفاده می‌کند و از حوصله‌ی این مقاله خارج است، بهتر هست که صفحه‌ی مستندات آن را که کامل هم هست، مطالعه بفرمایید. از سری قابلیت‌هایی که پشتیبانی می‌کند: موارد پوشش داده شده در FileDrop، ساخت layout، ایجاد صف، متد حذف و اضافه و از این قبیل، ایجاد تصویر تمبر مانند و ...

یک نکته تکمیلی در مورد آپلود: در ASP.net به طور پیش فرض نهایت حجم فایل آپلودی 4 مگابایتی تعیین شده است که میتوانید آن را از طریق web.config تغییر دهید:
<configuration>
    <system.web>
        <httpRuntime maxRequestLength="1048576" />
    </system.web>
</configuration>
برای IIS 7 به بعد هم از تکه کد زیر استفاده کنید:
<system.webServer>
   <security>
      <requestFiltering>
         <requestLimits maxAllowedContentLength="1073741824" />
      </requestFiltering>
   </security>
 </system.webServer>
در هر دو کد بالا نهایت حجم بر روی یک گیگابایت تعیین شده است که maxRequestLength به صورت کیلوبایت و maxAllowContentLength به صورت بایت تعیین شده است. توصیه می‌شود هر دو شکل آن را وارد کنید. به خصوص که IIS Express از کد ابتدایی استفاده می‌کند و بخواهید نتیجه‌ی آن را در تست‌ها ببینید.

مطالب
بررسی اینترفیس ICommand در WPF
مدتی هست که مشغول مطالعه و یادگیری WPF از طریق مطالب سایت هستم؛ به همین خاطر تصمیم گرفتم مطلبی را حول محور اینترفیس ICommnad  گردآوری کنم و در اختیار کاربران سایت قرار دهم.

سرفصل‌های این مطلب :
• Command چیست
• اینترفیس ICommand چیست 
• چرا اینترفیس ICommand
• ایجاد UI مورد نیاز 
• چگونگی استفاده از ICommand 
• استفاده از INotifyPropertyChanged

Command چیست ؟
در برنامه نویسی WPF به هر کلاسی که اینترفیس ICommand را پیاده سازی کند، اصطلاحا Commnad گوییم. تفاوت کوچکی بین یک Event و Command وجود دارد. رخداد‌ها برای کنترل‌های UI ساخته و تخصیص داده می‌شوند؛ اما Command‌ها انتزاعی‌تر هستند و تمرکز آنها بر روی نحوه‌ی انجام کارها می‌باشد.
برای تعاملات در برنامه‌ها از Commandها و Event‌ها استفاده می‌کنیم. ما در WPF دو روش برای تعامل با UI داریم:
1- استفاده از Event‌ها و نوشتن کد‌های مورد نیاز در بخش CodeBehind (رعایت نکردن الگوی MVVM).
WPF تعداد زیادی RoutedEvent پیش ساخته (Built In) دارد که از آنها می‌توان در این روش استفاده کرد. 
2- استفاده از Command و درگیر کردن کدهای اجرایی نوشته شده در ViewModel با استفاده از Command ها.
در زمان استفاده از الگوی MVVM مجاز به نوشتن کد در بخش CodeBehind نیستیم. بنابراین از سیستم رخداد‌های طراحی شده‌ی در WPF که RoutedEvent می‌باشد، نمی‌توان استفاده کرد. به این خاطر که رخداد‌ها در ViewModel قابل دسترسی نمی‌باشد.

اینترفیس ICommand:
این اینترفیس سه عضو دارد که آن‌ها را در جدول زیر مشاهده می‌کنید:

نام عضو

توضیحات

Bool CanExecute(object parameter)

این تابع پارامتری از نوع object را دریافت می‌کند و یک مقدار bool را به خروجی تابع می‌فرستد. اگر مقدار خروجی متد، true  باشدcommand  اجرا خواهد شد و در غیر اینصورت اتفاقی رخ نخواهد داد. اغلب ازDelegate  ها به عنوان پارامتر این تابع استفاده می‌شود؛Delegate های پیش ساخته‌ای همچون Action,Predicate,Func

Event EventHandler CanExecuteChanged

این رویداد برای آگاه سازی UI که به Command متصل است، استفاده می‌شود .بر اساس خروجی تابع CanExecute، این رویداد اتفاق می‌افتد.

Void Execute(Object parameter)

این متد کار اصلی را در Command‌ها انجام می‌دهد. این متد تنها زمانی اجرا می‌شود که متدCanExecute  مقدار true را بازگرداند. این تابع پارامتری را از نوع object دریافت می‌کند، اما عموما ما یکDelegate  را به آن ارسال می‌کنیم. Delegate ارجاعی را به متدی، در خود نگاه می‌دارد که انتظار داریم در صورت اجرایcommand ، اجرا شود.


چرا اینترفیس ICommand :
هسته‌ی اصلی Command‌ها در WPF، اینترفیس ICommand می‌باشد. این اینترفیس به‌صورت گسترده‌ای در الگوی MVVM و Prism  استفاده شده است و استفاده‌ی از آن محدود به MVVM نمی‌باشد. تعداد زیادی Commnad بصورت پیش ساخته وجود دارند که این اینترفیس را پیاده سازی کرده‌اند .کلاس پایه RoutedCommand اینترفیس ICommand را پیاده سازی کرده است و WPF فرمان‌های زیر را فراهم کرده است:
• MediaCommnads
• ApplicationCommnads
• NavigationCommands
• ComponentCommnads
• EditingCommnads
که همگی از کلاس RoutedCommand استفاده کرده‌اند.
در این مطلب به دنبال ایجاد برنامه‌ای هستیم که حاصل جمع مفدار دو Textbox را پس از فشردن کلید جمع در یک textblock نمایش دهد.

ساخت UI مورد نیاز :
گام اول : 
با اجرای ویژوال استودیو، برنامه‌ای را با نام ICommnadSample ایجاد کنید. ساختار پروژه به شکل زیر است:
همانطور که می‌بینید View و ViewModel و در نهایت Command‌ها در پوشه‌های مجزایی ساماندهی شده‌اند.

گام دوم:
ابتدا قالب گرافیکی را مشخص می‌کنیم. در پوشه‌ی Views یک UserControl را به نام CalculatorView.xaml ایجاد کرده و کد زیر را در آن نوشتیم:
<Grid DataContext="{Binding Source={StaticResource calculatorVM}}" Background="#FFCCCC">
        <Grid.RowDefinitions>
            <RowDefinition Height="80"/>
            <RowDefinition/>
            <RowDefinition Height="80"/>
            <RowDefinition Height="44"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" FontSize="25"
               VerticalAlignment="Top" HorizontalAlignment="Center" Foreground="Blue" Content="ICommand Sample"/>
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
               Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="First Input"/>
        <Label Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" 
               Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="Second Input"/>

        <TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20" 
                 HorizontalAlignment="Left" Height="30"  Width="150" TextAlignment="Center" Text="{Binding FirstValue, Mode=TwoWay}"/>
        <TextBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20"
                 HorizontalAlignment="Left"  Height="30" Width="150" TextAlignment="Center" Text="{Binding SecondValue, Mode=TwoWay}"/>

        <Rectangle Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Fill="LightBlue"/>
        <Button Grid.Row="2" Grid.Column="0" Content="+"  Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding AddCommand}"/>
        
        <Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" FontSize="25" Margin="10,0,0,0" HorizontalAlignment="Left" Height="50"  Content="Result : "/>
        <TextBlock Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" FontSize="20" Margin="10,0,0,0" Background="BlanchedAlmond"
                   TextAlignment="Center"  HorizontalAlignment="Left" Height="36" Width="150" Text="{Binding Output}"/>
    </Grid>
برای استفاده از این UserControl در پنجره‌ی اصلی برنامه (فایل MainWindows.Xaml) به شکل زیر عمل می‌کنیم:
ابتدا فضای نام View را به فایل MainWindows.xaml اضافه می‌کنیم :
   xmlns:myview="clr-namespace:ICommnadSample.Views"
ایجاد تگ برای استفاده از View تولید شده در  Grid اصلی برنامه :
<Grid>
        <myview:CalculatorView/>
</Grid>

گام سوم :
همانطور که مشاهده می‌کنید، کنترل‌هایی که در عملیات انقیاد داده‌ها (DataBinding) شرکت می‌کنند، از طریق خاصیت Binding و معرفی خصوصیت مورد نظر مشخص می‌شوند.
برای این منظور در پوشه‌ی ViewModels و به کلاس CalculatorViewModel سه خصوصیت به‌همراه فیلد خصوصی، به شکل زیر اضافه می‌کنیم:
private double firstValue; 
private double secondValue;
private double output;
public double FirstValue
        {
            get { return firstValue; }
            set
            {
                firstValue = value;
                OnPropertyChanged("FirstValue");
            }
        }
        public double SecondValue
        {
            get { return secondValue; }
            set
            {
                secondValue = value;
                OnPropertyChanged("SecondValue");
            }
        }
        public double Output
        {
            get { return output; }
            set
            {
                output = value;
                OnPropertyChanged("Output");
            }
        }

چگونگی استفاده‌ی از اینترفیس ICommand :
برای ایجاد ارتباط بین Command ‌ها و UI می‌بایست اینترفیس ICommand توسط کلاس مورد نظر ما پیاده سازی شود. در ابتدا کلاسی با عنوان PlusCommnad  ایجاد و از اینترفیس مورد نظر ارث بری می‌کنیم.
هدف ما شبیه سازی رویداد کلیک دکمه‌ی موجود در صفحه با استفاده از Command می‌باشد.

گام چهارم:
پس از پیاده سازی اینترفیس لازم است تا کمی تغییر در بدنه متد‌ها ایجاد کنیم:
public class PlusCommand : ICommand
{
    private CalculatorViewModel calculatorViewModel;
    public PlusCommand(CalculatorViewModel vm)
    {
        calculatorViewModel = vm;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        calculatorViewModel.Add();
    }
    public event EventHandler CanExecuteChanged;
}
همانطور که می‌بینید در ابتدا فیلدی از جنس کلاس CalculatorViewModel ایجاد و از طریق سازنده‌ی کلاس آن را مقدار دهی کردیم (قصد داریم نمونه‌ای از ViewModel مورد نظر را به این کلاس ارسال کنیم).
در ادامه بصورت دستی (Hard Code) مقدار بازگردانده شده را برای تابع CanExecute به مقدار true  تغییر دادیم و متد تابع Execute را به شکلی تغییر دادیم تا تابع Add را در CalculatorViewModel، اجرا کند.

گام پنجم:
از کلاس PlusCommand در CalculatorViewModel، یک شیء ساخته و در سازنده‌ی CalculatorViewModel آن را مقدار دهی می‌کنیم: 
        private PlusCommand plusCommand;
        public CalculatorViewModel()
        {
            plusCommand = new PlusCommand(this);
        }

گام ششم:
در فایل CalculatorView ارجاعی را به فضای نام کلاس CalculatorViewModel ایجاد می‌کنیم :
   xmlns:vm="clr-namespace:ICommnadSample.ViewModels"
پس از اضافه کردن فضای نام، از تگ UserControl.Resource برای رجیستر کردن CalculatorViewModel با کلید calculatorVM جهت مشخص کردن منبع داده استفاده کردیم.
 <UserControl.Resources>
        <vm:CalculatorViewModel x:Key="calculatorVM" />
    </UserControl.Resources>

گام هفتم:
اضافه کردن تابع Add در CalculatorViewModel برای عملیات جمع :
public void Add()
{
   Output = firstValue + secondValue;
}

گام هشتم:
تعریف یک Command  برای عملیات جمع به نام AddCommand. این همان خصوصیتی است که باید بجای رویداد کلیک دکمه از طریق خاصیت Command موجود در کنترل و ویژگی Binding به کنترل متصل شود.
  public ICommand AddCommand {
            get
            {
                return plusCommand;
            }
        }

نحوه‌ی استفاده:
   Command="{Binding AddCommand}"
 
گام نهم :
برای تکمیل عملیات انقیاد داده‌ها، کلاسی به نام ViewModelBase تعریف شده است. این کلاس از اینترفیس INotifyPropertyChange ارث بری کرده و اعضای این کلاس را پیاده سازی کرده است.
  public class ViewModelBase:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
پیاده سازی این اینترفیس سبب می‌شود تا کلاس‌های ViewModel ایی که احتیاج به این اینترفیس برای عملیات انقیاد داده‌ها دارند، تنها با ارث بری، از این ظرفیت استفاده کنند و نیازی به پیاده سازی این اینترفیس در هر کلاس نباشد.

گام دهم:
ارث بری کلاس CalculatorViewModel از کلاس ViewModelBase:
   public class CalculatorViewModel : ViewModelBase
در این مرحله هر کنترلی را که قصد داریم با تغییر منبع داده بروز شود و یا با تغییر وضعیتش منبع داده تغییر کند، اعلام می‌کنیم:
   OnPropertyChanged("FirstValue");
برای هر سه خصوصیت ViewModel کد زیر را در بلاک Set تکرار می‌کنیم (توجه شود که پارامتر ارسالی باید نام پراپرتی مورد نظر باشد)
کد کامل کلاس CalculatorViewModel به شکل زیر است:
public class CalculatorViewModel : ViewModelBase
    {
        private double firstValue;
        private double secondValue;
        private double output;
        private PlusCommand plusCommand;
        public CalculatorViewModel()
        {
            plusCommand = new PlusCommand(this);
        }

        public double FirstValue
        {
            get { return firstValue; }

            set
            {
                firstValue = value;
                OnPropertyChanged("FirstValue");
            }
        }
        public double SecondValue
        {
            get { return secondValue; }
            set
            {
                secondValue = value;
                OnPropertyChanged("SecondValue");
            }
        }
        public double Output
        {

            get { return output; }

            set
            {
                output = value;
                OnPropertyChanged("Output");
            }
        }


        public ICommand AddCommand
        {
            get
            {
                return plusCommand;
            }
        }

        internal void Add()
        {
            Output = firstValue + secondValue;
        }
    }


حال می‌توانید برنامه را اجرا و تست کنید:



برای درک بهتر عملیات انقیاد دادها می‌توانید به این مقاله مراجعه کنید.

مطالب
آموزش LINQ بخش ششم - عملگرهای پرس و جو قسمت اول
عملگرهای استاندارد پرس و جو

در یک طبقه بندی کلی، عملگرهای پرس و جو بر اساس ورودی و خروجی آنها به سه دسته تقسیم می‌شوند:
1- نتیجه‌ی توالی ورودی، بصورت یک توالی، به خروجی ارسال می‌شود.
2- نتیجه‌ی توالی ورودی، بصورت یک عنصر یکتا و واحد به خروجی ارسال می‌شود.
3- اثری از ورودی در توالی خروجی وجود ندارد (این عملگرها عناصر خودشان را تولید می‌کنند).

دسته‌ی آخر شاید کمی عجیب به نطر برسد. این عملگرها هیچ توالی ورودی را دریافت نمی‌کنند. مثلا می‌توان از طریق این عملگر‌ها، یک توالی از اعداد صحیح را تولید کرد.
تقسیم بندی عملگرهای پرس و جو بر اساس عملکرد به صورت زیر می‌باشد : 
  • محدود کننده (Restriction)
where
  • بازتابی (Projection)
Select,SelectMany 
  • جداکننده (Partitioning)
Take,Skip,TakeWhile,SkipWhile 
  • مرتب سازی (Ordering)
OrderBy,OrderByDescending,ThenBy,ThenByDescending,Reverse 
  • گروه بندی (Grouping)
GroupBy 
  • مجموعه (Set)
Concat,Union,Intersect,Except 
  • تبدیل (Conversion)
ToArray,ToList,ToDictionary,ToLookup,OfType,Cast 
  • عنصر(Element)
First,FirstOrDefault,Last,LastOrDefalt,Single,SingleOrDefault 
  • عنصر در (ElementAt)
ElementAtOrDefault,DefaultIfEmpty 
  • تولید (Generation)
Empty,Range,Report 
  • کمی (Quantifier)
Any,All,Contains,SequenceEqual 
  • مجموعه (Aggregate)
Count,LongCount,Sum,Min,Max,Average,Aggregate 
  • اتصال (Join)
Join,GroupJoin,Zip 

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

عملگرهای محدود کننده (Restriction Operators)
این عملگرها یک توالی ورودی را دریافت و یک توالی محدود شده یا به بیان دیگر فیلتر شده را تولید می‌کنند. عناصر توالی خروجی، عناصری هستند که با فیلتر اعمال شده مطابقت دارند.
Where
این عملگر، عناصری را به خروجی ارسال می‌کند که با گزاره‌ی (Predicate) تعریف شده مطابقت داشته باشند.
نکته : گزاره (Predicate) تابعی است که اگر شرط آن تامین شود، مقدار true و در غیر اینصورت مقدار false را باز می‌گرداند.
مثال : 
 Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Where(x => x.Calories >= 200);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
در کد فوق از عملگر where استفاده شده است. گزاره‌ی (x=>x.Calories>=200) به ازای هر غذایی که کالری آن مساوی یا بزرگتر از 200 باشد، مقدار true را باز می‌گرداند.
خروجی کد بالا:
 Sugar
Butter
عملگر where امضای دیگری دارد که اندیس عنصر ورودی توالی را نیز می‌پذیرد. در مثال قبل، اندیس Sugar برابر 0 و اندیس Butter برابر 4 است. پرس و جوی زیر خروجی مشابه مثال قبل را تولید می‌کند.
 IEnumerable<Ingredient> query = ingredients.Where((ingredient, index) => ingredient.Name == "Sugar" || index == 4);
گزاره نوشته شده در این پرس و جو  از نوع <Func<Ingredient,int,bool خواهد بود و پارامتر int، اندیس عنصر در توالی ورودی می‌باشد.

پیاده سازی توسط عبارت‌های پرس و جو
 در روش عبارت‌های پرس و جو، کلمه‌ی کلیدی where به‌همراه یک عبارت منطقی در پرس و جو ظاهر می‌شود:
 IEnumerable<Ingredient> gueryExpression =
from i in ingredients
where i.Calories >= 200
select i;


عملگرهای بازتاب (Projection Operators)

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

Select
عملگر پرس و جوی select هر عنصر توالی ورودی را به یک عنصر در توالی خروجی تبدیل می‌کند. تعداد عناصر ورودی و خروجی در این حالت یکسان می‌باشند.
پرس و جوی زیر عناصر توالی ورودی Ingredient را به عناصر رشته‌ای در توالی خروجی بازتاب می‌کند. عبارت Lambda تعریف شده، نحوه‌ی بازتاب عناصر را مشخص می‌کند (هر عنصر ingredient به یک عنصر رشته‌ای بازتاب می‌شود):
 IEnumerable<string> query = ingredients.Select(x => x.Name);
  می‌توان توالی خروجی با عناصر صحیح را نیز تولید کرد:  
 IEnumerable<int> query = ingredients.Select(x => x.Name.Length);

در عملیات بازتاب می‌توان یک شیء جدید را در توالی خروجی ایجاد کرد. در کد زیر عناصر Ingredient به یک عنصر جدید از نوع IngredientNameAndLenght بازتاب شده است.
class IngredientNameAndLength
{
    public string Name { get; set; }
    public int Length { get; set; }
    public override string ToString()
    {
      return Name + " " + Length;
    }
}

IEnumerable<IngredientNameAndLength> query = ingredients.Select(x =>
new IngredientNameAndLength
{
   Name = x.Name,
   Length = x.Name.Length
});
پرس و جوی بالا را می‌توان به شکل نوع‌های بی نام نیز بازنویسی کرد. باید دقت شود که نوع بازگشتی این پرس و جو باید از نوع var باشد.
var query = ingredients.Select(x =>
new
{
   Name = x.Name,
   Length = x.Name.Length
});
خروجی کد بالا به شکل زیر است :
{ Name = Sugar, Length = 5 }
{ Name = Egg, Length = 3 }
{ Name = Milk, Length = 4 }
{ Name = Flour, Length = 5 }
{ Name = Butter, Length = 6 }

پیاده سازی توسط عبارت‌های پرس و جو

کلمه‌ی کلیدی select در عبارت‌های پرس و جو، به شکل زیر استفاده می‌شود:
var query = from i in ingredients
select new
{
    Name=i.Name,
    Length=i.Name.Length
};

SelectMany 
برعکس دستور select که به ازای هر عنصر در توالی ورودی، یک عنصر را در توالی خروجی بازتاب می‌کرد، دستور SelectMany ممکن است تعداد عناصر کمتر و یا بیشتری را در توالی خروجی بازتاب کند (انتخاب مقادیر یک مجموعه از مجموعه‌ی دیگر).
عبارت Lambda نوشته شده در عملگر Select، یک مقدار را باز می‌گرداند. اما عبارت Lambda نوشته شده در عملگر SelectMany، یک توالی فرزند (Child Sequence) را ایجاد می‌کند. توالی فرزند ممکن است حاوی تعداد مختلفی از عناصر به ازای هر عنصر در توالی ورودی باشد.
در مثال زیر عبارت Lambda یک توالی فرزند از کاراکتر‌ها ایجاد می‌کند (یک کاراکتر به ازای هر حرف از هر عنصر توالی ورودی). به‌طور مثال عنصر ورودی Sugar، پس از پردازش توسط  عبارت Lambda، یک توالی فرزند با 5 عنصر 's','u','g','e','r' فراهم می‌کند. هر رشته‌ی در توالی Ingredient می‌تواند تعداد حروف متفاوتی داشته باشد. در نتیجه عبارت Lambda، توالی‌های فرزندی با طول‌های مختلف ایجاد می‌کند.
مثال:
string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"};
IEnumerable<char> query = ingredients.SelectMany(x => x.ToCharArray());
foreach (var item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا :
 S
u
g
a
r
E
g
g
M
i
l
k
F
l
o
u
r
B
u
t
t
e
r

پیاده سازی توسط عبارت‌های پرس و جو

در روش عبارت‌های پرس و جو یک عبارت (clause) اضافی from برای تولید یک توالی فرزند به کار برده می‌شود. خروجی کد زیر مشابه کد قبلی است:
 string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"};
IEnumerable<char> query2 = from i in ingredients
from c in i.ToCharArray()
select c;

foreach (var item in query2)
{
   Console.WriteLine(item);
}

عملگرهای جداکننده (Partitioning Operators)
عملگر‌های جداکننده، یک توالی ورودی را دریافت و آنها را از هم جدا می‌کنند.

Take
عملگر Takeیک توالی ورودی را دریافت کرده و تعداد مشخصی از توالی را باز می‌گرداند.
مثال: عملگر Take، سه عضو اول توالی Ingredient را باز می‌گرداند:
 Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Take(3);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
 Sugar
Egg
Milk
همچون سایر عملگر‌های پرس و جو، عملگر Take هم می‌تواند بصورت زنجیروار استفاده شود. در مثال زیر ابتدا عملگر Where برای محدود کردن عناصر با شرطی خاص و سپس عملگر Take برای جدا کردن عناصر حاصل از نتیجه‌ی مرحله قبل مورد استفاده قرار گرفته است:
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Where(x=>x.Calories>100).Take(2);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
Sugar
Milk

پیاده سازی توسط عبارت‌های پرس و جو

کلمه‌ی کلیدی (Key word) جایگزینی برای عملگر Take وجود ندارد، ولی می‌توان با ترکیب دو روش نوشتن پرس و جو، خروجی مورد نظر را تولید کرد:
 IEnumerable<Ingredient> query =
(from i in ingredients
  where i.Calories > 100
  select i).Take(2);
TakeWhile
عملگر TakeWhile بر عکس عملگر Take تعداد مشخصی را باز می‌گرداند . این عملگر تا زمانی که گزاره با عناصر مطابقت داشته باشد، اجرا می‌شود و در غیر اینصورت خاتمه پیدا می‌کند.
کد زیر تا زمانی که خصوصیت Calorie توالی ورودی بزرگتر و مساوی 100 باشد، عناصر را جدا می‌کند.
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.TakeWhile(x => x.Calories >= 100);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
 Sugar
Egg
Milk
همانطور که مشاهده می‌کنید، وقتی عملگر TakeWhile به عنصری می‌رسد که گزاره‌ی آن را نقض می‌کند، متوقف می‌شود (در اینجا Flour). در حالی که ممکن است عناصری بعد از Flour وجود داشته باشند که با گزاره‌ی TakeWhile تطابق داشته باشند.

پیاده سازی توسط عبارت‌های پرس و جو
برای این عملگر هم کلمه‌ی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو نتیجه‌ی دلخواه حاصل می‌شود.
 
Skip
این عملگر تعداد مشخصی از عناصر را از ابتدای توالی نادیده گرفته و باقی عناصر را باز می‌گرداند.
کد زیر سه عضو اول توالی را نادیده گرفته و مابقی را باز می‌گرداند:
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Skip(3);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
 Flour
Butter

پیاده سازی توسط عبارت‌های پرس و جو

برای این عملگر هم کلمه‌ی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو، نتیجه‌ی دلخواه حاصل می‌شود.
با ترکیب عملگر Take و Skip می‌توان اطلاعات را به‌صورت صفحه بندی به کاربر ارائه کرد. مثال زیر این حالت را نشان می‌دهد.
IEnumerable<Ingredient> firstPage = ingredients.Take(2);
IEnumerable<Ingredient> secondPage = ingredients.Skip(2).Take(2);
IEnumerable<Ingredient> thirdPage = ingredients.Skip(4).Take(2);

Console.WriteLine("First Page : ");
foreach (var ingredient in firstPage)
{
   Console.WriteLine(" - " + ingredient.Name);
}

Console.WriteLine("Second Page : ");
foreach (var ingredient in secondPage)
{
   Console.WriteLine(" - " + ingredient.Name);
}

Console.WriteLine("Third Page : ");
foreach (var ingredient in thirdPage)
{
   Console.WriteLine(" - " + ingredient.Name);
}
خروجی کد بالا :
 First Page :
 - Sugar
 - Egg
Second Page :
 - Milk
 - Flour
Third Page :
 - Butter
SkipWhile
عملگر SkipWhile، مثل عملگر TakeWhile، از یک گزاره برای ارزیابی عناصر توالی استفاده می‌کند. این عملگر تا زمانیکه عناصر توالی، گزاره را نقض نکنند، عناصر را نادیده می‌گیرد.

مثال:
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.SkipWhile(x => x.Name != "Milk");
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا:
 Milk
Flour
Butter