مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت دوم - اجرای قواعد اعتبارسنجی تعریف شده
در قسمت قبل، روش تعریف قواعد اعتبارسنجی را با استفاده از کتابخانه‌ی Fluent Validation بررسی کردیم. در این قسمت می‌خواهیم این قواعد را به صورت خودکار به یک برنامه‌ی ASP.NET Core معرفی کرده و سپس از آن‌ها استفاده کنیم.


روش اول: استفاده‌ی دستی از اعتبارسنج کتابخانه‌ی Fluent Validation

روش‌های زیادی برای استفاده‌ی از قواعد تعریف شده‌ی توسط کتابخانه‌ی Fluent Validation وجود دارند. اولین روش، فراخوانی دستی اعتبارسنج، در مکان‌های مورد نیاز است. برای اینکار در ابتدا نیاز است با اجرای دستور «dotnet add package FluentValidation.AspNetCore»، این کتابخانه را در پروژه‌ی وب خود نیز نصب کنیم تا بتوانیم از کلاس‌ها و متدهای آن استفاده نمائیم. پس از آن، روش دستی کار با کلاس RegisterModelValidator که در قسمت قبل آن‌را تعریف کردیم، به صورت زیر است:
using FluentValidationSample.Models;
using Microsoft.AspNetCore.Mvc;

namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult RegisterValidateManually(RegisterModel model)
        {
            var validator = new RegisterModelValidator();
            var validationResult = validator.Validate(model);
            if (!validationResult.IsValid)
            {
                return BadRequest(validationResult.Errors[0].ErrorMessage);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
ساده‌ترین روش کار با RegisterModelValidator تعریف شده، ایجاد یک وهله‌ی جدید از آن و سپس فراخوانی متد Validate آن شیء است. در این حالت می‌توان کنترل کاملی را بر روی قالب پیام نهایی بازگشت داده شده داشت. برای مثال در اینجا اولین خطای بازگشت داده شده، به اطلاع کاربر رسیده‌است. حتی می‌توان کل شیء Errors را نیز بازگشت داد.

یک نکته: متد الحاقی AddToModelState که در فضای نام FluentValidation.AspNetCore قرار دارد، امکان تبدیل نتیجه‌ی اعتبارسنجی حاصل را به ModelState استاندارد ASP.NET Core نیز میسر می‌کند:
public IActionResult RegisterValidateManually(RegisterModel model)
{
    var validator = new RegisterModelValidator();
    var validationResult = validator.Validate(model);
    if (!validationResult.IsValid)
    {
       validationResult.AddToModelState(ModelState, null);
       return BadRequest(ModelState);
    }

    // TODO: Save the model

    return Ok();
}


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

بجای وهله سازی دستی RegisterModelValidator و ایجاد وابستگی مستقیمی به آن، می‌توان از روش تزریق وابستگی‌های آن نیز استفاده کرد. در این حالت اعتبارسنج RegisterModelValidator با طول عمر Transient به سیستم تزریق وابستگی‌ها معرفی شده:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>();
            services.AddControllersWithViews();
        }
 و پس از آن با تزریق <IValidator<RegisterModel به سازنده‌ی کنترلر مدنظر، می‌توان به امکانات آن همانند روش اول، دسترسی یافت:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IValidator<RegisterModel> _registerModelValidator;

        public HomeController(IValidator<RegisterModel> registerModelValidator)
        {
            _registerModelValidator = registerModelValidator;
        }

        [HttpPost]
        public IActionResult RegisterValidatorInjection(RegisterModel model)
        {
            var validationResult = _registerModelValidator.Validate(model);
            if (!validationResult.IsValid)
            {
                return BadRequest(validationResult.Errors[0].ErrorMessage);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
به این ترتیب new RegisterModelValidator را با وهله‌ای از <IValidator<RegisterModel، تعویض کردیم. کار با این روش بسیار انعطاف پذیر بوده و همچنین قابلیت آزمون پذیری بالایی را نیز دارد.


روش سوم: خودکار سازی اجرای یک تک اعتبارسنج تعریف شده

اگر متد الحاقی AddFluentValidation را به صورت زیر به سیستم معرفی کنیم:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>();
            services.AddControllersWithViews().AddFluentValidation();
        }
سبب اجرای خودکار تمام IValidatorهای اضافه شده‌ی به سیستم، پیش از اجرای اکشن متد مرتبط با آن‌ها می‌شود. برای مثال اگر اکشن متدی دارای پارامتری از نوع RegisterModel بود، چون IValidator مخصوص به آن به سیستم تزریق وابستگی‌ها معرفی شده‌است، متد الحاقی AddFluentValidation، کار وهله سازی خودکار این IValidator و سپس فراخوانی متد Validate آن‌را به صورت خودکار انجام می‌دهد. به این ترتیب، قطعه کدهایی را که تاکنون نوشتیم، به صورت زیر خلاصه خواهند شد که در آن‌ها اثری از بکارگیری کتابخانه‌ی FluentValidation مشاهده نمی‌شود:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        [HttpPost]
        public IActionResult RegisterValidatorAutomatically(RegisterModel model)
        {
            if (!ModelState.IsValid)
            {
                // re-render the view when validation failed.
                return View(model);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
زمانیکه model به سمت اکشن متد فوق ارسال می‌شود، زیرساخت model-binding موجود در ASP.NET Core، اینبار کار اعتبارسنجی آن‌را توسط RegisterModelValidator به صورت خودکار انجام داده و نتیجه‌ی آن‌را به ModelState اضافه می‌کند که برای مثال در اینجا سبب رندر مجدد فرم شده که تمام مباحث tag-helper‌های استانداردی مانند asp-validation-summary و asp-validation-for پس از آن به صورت متداولی و همانند قبل، قابل استفاده خواهند بود.

نکته 1: تنظیمات فوق برایASP.NET Web Pages و PageModels نیز یکی است. فقط با این تفاوت که اعتبارسنج‌ها را فقط می‌توان به مدل‌هایی که به صورت خواص یک page model تعریف شده‌اند، اعمال کرد و نه به کل page model.

نکته 2: اگر کنترلر شما به ویژگی [ApiController] مزین شده باشد:
namespace FluentValidationSample.Web.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class HomeController : Controller
    {
        [HttpPost]
        public IActionResult RegisterValidatorAutomatically(RegisterModel model)
        {
            // TODO: Save the model

            return Ok();
        }
    }
}
در این حالت دیگر نیازی به ذکر if (!ModelState.IsValid) نیست و خطای حاصل از شکست اعتبارسنجی، به صورت خودکار توسط FluentValidation تشکیل شده و بازگشت داده می‌شود (پیش از رسیدن به بدنه‌ی اکشن متد فوق) و برای نمونه یک چنین شکل و خروجی خودکاری را پیدا می‌کند:
{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|84df05e2-41e0d4841bb61293.",
    "errors": {
        "FirstName": [
            "'First Name' must not be empty."
        ]
    }
}
اگر علاقمند به سفارشی سازی این خروجی خودکار هستید، باید به این صورت با تنظیم ApiBehaviorOptions و مقدار دهی نحوه‌ی تشکیل ModelState نهایی، عمل کرد:
        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            // override modelstate
            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory = context =>
                {
                    var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => p.ErrorMessage)).ToList();
                    return new BadRequestObjectResult(new
                    {
                        Code = "00009",
                        Message = "Validation errors",
                        Errors = errors
                    });
                };
            });
        }


روش چهارم: خودکار سازی ثبت و اجرای تمام اعتبارسنج‌های تعریف شده

و در آخر بجای معرفی دستی تک تک اعتبارسنج‌های تعریف شده به سیستم تزریق وابستگی‌ها، می‌توان تمام آن‌ها را با فراخوانی متد RegisterValidatorsFromAssemblyContaining، به صورت خودکار از یک اسمبلی خاص استخراج نمود و با طول عمر Transient، به سیستم معرفی کرد. در این حالت متد ConfigureServices به صورت زیر خلاصه می‌شود:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews().AddFluentValidation(
                fv => fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>()
            );
        }
در اینجا امکان استفاده‌ی از متد fv.RegisterValidatorsFromAssembly نیز برای معرفی اسمبلی خاصی مانند ()Assembly.GetExecutingAssembly نیز وجود دارد.


سازگاری اجرای خودکار FluentValidation با اعتبارسنج‌های استاندارد ASP.NET Core

به صورت پیش‌فرض، زمانیکه FluentValidation اجرا می‌شود، اگر اعتبارسنج دیگری نیز در سیستم تعریف شده باشد، اجرا خواهد شد. به این معنا که برای مثال می‌توان FluentValidation و DataAnnotations attributes و IValidatableObject‌ها را با هم ترکیب کرد.
اگر می‌خواهید این قابلیت را غیرفعال کنید و فقط سبب اجرای خودکار FluentValidationها شوید، نیاز است تنظیم زیر را انجام دهید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews().AddFluentValidation(
       fv =>
       {
           fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>();
           fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
       }
    );
}
اشتراک‌ها
سری ساخت یک forum با ASP.NET Core 2.0

Full Stack ASP.NET Core 2.0 MVC Forum Build

Topics Covered:
- Setting up a new ASP .NET Core 2.0 MVC web application with Identity user authentication in Visual Studio
- Separating Web, Services, and Data Access Layers in our solution
- Setting up tests with NUnit and .NET Core virtual in-memory database
- Debugging / Fixing bugs
- Implementing the MVC (Model-View-Controller) pattern
- Dependency Injection of Services into our Controllers
- Using input forms to pass data from our Views to our Controllers
- Azure file storage for Profile Image uploads
- Azure SQL database hosting
- SQL Database seeding for starting the application with a super-user
- Code-first database migrations
- Writing SQL queries to inspect data in our database
- Deploying the application to Azure.
 

سری ساخت یک forum با ASP.NET Core 2.0
نظرات مطالب
معرفی List Patterns Matching در C# 11
استفاده از List patterns matching در پیاده سازی الگوریتم‌های بازگشتی

فرض کنید قصد داریم حاصل ضرب و یا حاصل جمع اعداد یک آرایه را به روش بازگشتی محاسبه کنیم:
var values = Enumerable.Range(start: 1, count: 10).ToArray(); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

Console.WriteLine($"MultiplyAll: {MultiplyAll(values)}"); // MultiplyAll: 3628800
Console.WriteLine($"AddAll: {AddAll(values)}"); // AddAll: 55

static int MultiplyAll(params int[] values) =>
    values switch
    {
        [] => 1,
        [var first, .. var rest] => first * MultiplyAll(rest)
    };

static int AddAll(params int[] elements) =>
    elements switch
    {
        [] => 0,
        [var first, .. var rest] => first + AddAll(rest)
    };
می‌توان با استفاده از List patterns matching، به سادگی به اولین عنصر (first در اینجا) و سپس لیست مابقی عناصر (rest در اینجا)، جهت تکرار یک عملیات بازگشتی دست یافت.
مطالب
نامرئی کردن Watermarkهای صفحات فایل‌های PDF توسط iTextSharp
احتمالا بارها با PDFهایی که یک Watermark بزرگ را در میانه صفحات خود دارند، برخورد داشته‌اید و متاسفانه در اغلب اوقات استفاده ناصحیحی از این قابلیت صورت می‌گیرد. هدف از Watermark دار کردن صفحات PDF، ذکر جملاتی مانند «آزمایشی بودن» یا «محرمانه بودن» است که در هر دو حالت نباید به صورت عمومی منتشر شوند. اما اگر قرار است مطلبی را به صورت عمومی منتشر کنیم، این روش، بدترین حالت تبلیغی برای یک شخص یا شرکت خواهد بود؛ چون مانع خواندن روان متن شده و اعصاب مصرف کننده را به هم خواهد ریخت. بنابراین هیچگونه جنبه تبلیغی مثبتی را در نهایت برای تهیه کننده به همراه نخواهد داشت.
برای نمونه فایل نمونه سؤالات مصاحبات دات را از اینجا دریافت کنید. یک چنین شکلی دارد:

خوب! چطور می‌توان این Watermark را حذف یا حداقل نامرئی کرد؟
برای پاسخ به این سؤال نیاز است ابتدا با نحوه Watermark دار کردن صفحات یک فایل PDF آشناشویم.

الف) ایجاد یک فایل PDF ساده
        private static void createPdfFile(string pdfFile)
        {
            using (var fs = new FileStream(pdfFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (var doc = new Document(PageSize.A4))
                {
                    using (var witier = PdfWriter.GetInstance(doc, fs))
                    {
                        doc.Open();
                        for (int i = 1; i <= 5; i++)
                        {
                            doc.NewPage();
                            doc.Add(new Paragraph(String.Format("This is page {0}", i)));
                        }
                        doc.Close();
                    }
                }
            }
        }
در اینجا یک فایل PDF با 5 صفحه ایجاد می‌شود.

ب) افزودن Watermark به فایل PDF تهیه شده
        private static void addWatermark(string pdfFile, string watermarkedFile, string watermarkText)
        {
            FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
            var tahoma = FontFactory.GetFont("Tahoma", BaseFont.IDENTITY_H, 40, Font.NORMAL, BaseColor.BLACK);

            var reader = new PdfReader(pdfFile);
            using (var fileStream = new FileStream(watermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (var stamper = new PdfStamper(reader, fileStream))
                {
                    int pageCount = reader.NumberOfPages;
                    for (int i = 1; i <= pageCount; i++)
                    {
                        var rect = reader.GetPageSize(i);
                        var cb = stamper.GetUnderContent(i);
                        var gState = new PdfGState();
                        gState.FillOpacity = 0.25f;
                        cb.SetGState(gState);

                        ColumnText.ShowTextAligned(
                            canvas: cb,
                            alignment: Element.ALIGN_CENTER,
                            phrase: new Phrase(watermarkText, tahoma),
                            x: rect.Width / 2,
                            y: rect.Height / 2,
                            rotation: 45f,
                            runDirection: PdfWriter.RUN_DIRECTION_LTR,
                            arabicOptions: 0);
                    }
                }
            }
        }
در متد فوق pdfFile نام و مسیر فایل PDF ایی است که قرار است به صفحات آن Watermark اضافه شود. نام و مسیر فایل خروجی توسط watermarkedFile مشخص می‌شود و watermarkText متنی است که در میانه صفحه نمایش داده خواهد شد.
در اینجا توسط PdfReader، فایل موجود گشوده می‌شود. به این ترتیب می‌توان به تک تک صفحات این فایل دسترسی یافت. از PdfStamper برای نوشتن در این فایل باز شده استفاده می‌کنیم.  متد stamper.GetUnderContent، لایه زیرین متن صفحات را در اختیار ما قرار می‌دهد. اگر علاقمند به نوشتن بر روی لایه رویی متون هستید از متد stamper.GetOverContent استفاده کنید. در اینجا از PdfGState برای مشخص سازی میزان شفافیت متن درحال نمایش، با مقدار دهی FillOpacity آن استفاده شده است. نهایتا از متد ColumnText.ShowTextAligned برای نمایش متن مورد نظر در مکان و زاویه‌ای مشخص استفاده می‌کنیم. این متد با زبان فارسی سازگاری دارد و run direction آن قابل تنظیم است.


ج) آشنایی با ساختار سطح پایین Watermark اضافه شده به صفحات

تقریبا اکثر Watermarkهایی که بر روی صفحات PDF درج می‌شوند، نیمه شفاف هستند تا بتوان متن زیر آن‌ها را مطالعه کرد. این شفافیت همانطور که ذکر شد توسط مقدار دهی شیء PdfGState حاصل می‌شود. اگر فایل PDF تولیدی در قسمت ب را توسط برنامه iText Rups باز کنیم، به شکل زیر خواهیم رسید:


هدف ما این است که به شیء PdfGState موجود در هر صفحه، دسترسی یافته و مقدار FillOpacity آن‌را صفر کنیم. به این ترتیب این Watermark، کاملا شفاف یا نامرئی خواهد شد. در PDF نهایی، چیزی به نام شیء PdfGState وجود ندارد، بلکه با یک سری Dictionary و Array سر و کار داریم.
همانطور که در شکل فوق ملاحظه می‌کنید، برای رسیدن به Gstateها باید مراحل زیر طی شوند:
1- فایل PDF گشوده شده و سپس به هر صفحه دسترسی یافت.
2- نیاز است RESOURCES صفحه جاری استخراج شوند.
3- در این منابع، باید EXTGSTATE را که همان PdfGStateها هستند، بیابیم.
4- سپس مقدار ca این EXTGSTATE یافت شده را به صفر مقدار دهی کنیم.

        private static void removeWatermark(string watermarkedFile, string unwatermarkedFile)
        {
            PdfReader.unethicalreading = true;
            PdfReader reader = new PdfReader(watermarkedFile);
            reader.RemoveUnusedObjects();
            int pageCount = reader.NumberOfPages;
            for (int i = 1; i <= pageCount; i++)
            {
                var page = reader.GetPageN(i);
                PdfDictionary resources = page.GetAsDict(PdfName.RESOURCES);
                PdfDictionary extGStates = resources.GetAsDict(PdfName.EXTGSTATE);
                if (extGStates == null)
                    continue;

                foreach (PdfName name in extGStates.Keys)
                {
                    var obj = extGStates.Get(name);
                    PdfDictionary extGStateObject = (PdfDictionary)PdfReader.GetPdfObject(obj);
                    var stateNumber = extGStateObject.Get(PdfName.ca_);
                    if (stateNumber == null)
                        continue;

                    var caNumber = (PdfNumber)PdfReader.GetPdfObject(stateNumber);
                    if (caNumber.FloatValue != 1f)
                    {
                        extGStateObject.Remove(PdfName.ca_);
                        //با تنظیم مقدار به صفر، نامرئی خواهد شد
                        extGStateObject.Put(PdfName.ca_, new PdfNumber(0f));
                    }
                }
            }

            using (FileStream fs = new FileStream(unwatermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (PdfStamper stamper = new PdfStamper(reader, fs))
                {
                    stamper.SetFullCompression();
                    stamper.Close();
                }
            }
        }
نحوه پیاده سازی مراحل یاد شده را در کدهای فوق ملاحظه می‌کنید. ابتدا توسط  PdfReader فایل موجود باز شده و سپس تک تک صفحات آن‌را بررسی می‌کنیم. در این صفحات اگر EXTGSTATE ایی یافت شد، مقدار ca آن‌را به صفر تنظیم خواهیم کرد. از مقدار 1 صرفنظر شده، چون این مقدار عموما برای Watermark دار کردن صفحات استفاده نمی‌شود.
در این متد، watermarkedFile فایلی است که باید watermark آن نامرئی شود و unwatermarkedFile فایل تولیدی حاصل است.

نظرات مطالب
فراخوانی یک تابع بعد از اتمام Render در AngularJS
باتشکر مشکل حل شد. در Directive ngFinishRender  به دلیل استفاده از سرویس timeout$، فراخوانی تابع بعد از اجرای کامل چرخه Digest$ انجام می‌شود این کار باعث رندر شدن کامل داده بازگشتی می‌شود. برای شفاف شدن موضوع کد مربوطه را اینجا می‌گذارم
<script type="text/ng-template" id="menu">
    {{item.caption}} -- <b>{{level}}</b>
    <ul ng-if="item.child.length > 0">
        <li id="{{item.caption}}" ng-finish-render="doWork()" ng-repeat="item in item.child" ng-include="'menu'" ng-init="level = level + 1"></li>
    </ul>
</script>
<div class="well">
    <ul>
        <li id="{{item.caption}}" ng-repeat="item in data" ng-include="'menu'" ng-init="level = level + 1" ng-finish-render="doWork()"></li>
    </ul>
</div>
$scope.doWork = function(){
     console.log($('#item2_1'));
}
$scope.level = 0;
$scope.data = [
            {
                caption:'root',
                child:[
                    {
                        caption:'item1',
                        child:[{
                            caption:'item1_1',
                            child:[{
                                caption:'item1_1_1'
                            }]
                        },{
                            caption:'item1_1'
                        }]
                    },
                    {
                        caption:'item2',
                        child:[{
                            caption:'item2_1',
                            child:[{
                                caption:'item2_1_1'
                            }]
                        }]
                    }
                ]

            }
        ];

مطالب
Vue.js - بحث Computed Properties و خروجی بلادرنگ - قسمت سوم
با استفاده از Computed Properties ها می‌توانید برای هر خاصیت، یک شرط بنویسید و آنرا مقداردهی کنید. در این بحث بسیار مهم نیز قادر خواهید بود تا به صورت بلادرنگ (Real Time) خروجی بگیرید. در واقع به محض ارسال اطلاعات، خروجی مورد نظر برای شما نمایش داده می‌شود.
ابتدا لازم است تا یک ویو را ایجاد کنید. یک new vue جدید ساخته و مشخصه‌های لازم را وارد کنید؛ مطابق کد زیر
    <html> <body>

    <div id="dotnet"> 
       <h2>New Sample: {{ a }}</h2> 
       <input type="text" v-model="a"> 
    </div> 
  
       <script src="https://unpkg.com/vue@2.2.6">
       </script>

       <script type="text/javascript">
        new Vue({ 
        el: '#dotnet', 
        data:{ 
            a:'' 
        }, 
        computed: { 
            a:{ 
                get: function () { 
                    return this.a + ''; 
                } 
            } 
        } 
    });
        </script>

        </body>
</html>
در کد بالا ویوی جدیدی ساخته شده و مقدار data نیز مشخص شده‌است. مقدار data برابر a می‌باشد.
data:{ 
            a:'' 
        },
دقت نمائید هدف این است وقتی مقداری درون یک input وارد می‌شود، بلافاصله نمایش داده شود که این کار با استفاده از خاصیت v-model به صورت بلادرنگ امکان‌پذیر است.
در کد زیر مقدار a درون data باید به v-model نسبت داده شود تا درون input اطلاعات نمایش داده شوند.
<input type="text" v-model="a">
حال، یک property به نام computed  ایجاد کرده و مقدار a را توسط یک تابع برگشت می‌دهیم.
  computed: { 
            a:{ 
                get: function () { 
                    return this.a + ''; 
                } 
            } 
        }
دقت کنید function بالا به دلخواه نوشته شده‌است و شما قادرید هرکدی را که لازم است، جای آن بنویسید. اما بدنه کلی ساختار یک computed  باید به همین شکل حفظ شود.
پس از اجرای کد درون مرورگر هر نوع مقداری که درون input وارد شود، توسط خاصیت v-model بلافاصله نمایش داده می‌شود.
مطالب
C# 8.0 - پیشنیاز و روش راه اندازی
پیشنیاز کار با C# 8.0

هرچند بسیاری از قابلیت‌های C# 8.0 در خود کامپایلر #C پیاده سازی شده‌اند، اما برای مثال قابلیتی مانند «پیاده سازی پیش‌فرض اینترفیس‌ها» نیاز به یک runtime جدید دارد که به همراه NET Core 3.0. ارائه می‌شود. بنابراین NET Full 4x. شاهد پیاده سازی C# 8.0 نخواهد بود. همچنین یک سری از قابلیت‌های C# 8.0 وابسته‌ی به NET Standard 2.1. و  netcoreapp3.0  هستند؛ مانند نوع‌های جدید System.IAsyncDisposable و یا System.Range. به همین جهت است که برای کار با C# 8.0، حتما نیاز به نصب NET Core 3.0. نیز می‌باشد و به روز رسانی کامپایلر #C کافی نیست.


چه نگارش‌هایی از Visual Studio از NET Core 3.0. پشتیبانی می‌کنند؟

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

.NET Core SDK .NET Core Runtime Compatible Visual Studio MSBuild Notes
2.1.5nn 2.1 2017 15 Installed as part of VS 2017 version 15.9
2.1.6nn 2.1 2019 16 Installed as part of VS 2019
2.2.1nn 2.2 2017 15 Installed manually
2.2.2nn 2.2 2019 16 Installed as part of VS 2019
3.0.1nn 3.0 (Preview) 2019 16 Installed manually

بنابراین فقط VS 2019 است که قابلیت پشتیبانی از NET Core 3.0. را دارد. به همین جهت اگر قصد دارید با ویژوال استودیو کار کنید، نصب VS 2019 برای کار با C# 8.0 الزامی است.


فعالسازی C# 8.0 در ویژوال استودیو 2019

در زمان نگارش این مطلب، NET Core 3.0. در حالت پیش‌نمایش، ارائه شده‌است. به همین جهت جزء یکپارچه‌ی VS 2019 محسوب نشده و باید جداگانه نصب شود:


- برای این منظور ابتدا نیاز است آخرین نگارش NET Core 3.0 SDK. را دریافت و نصب کنید.
- سپس از منوی Tools | Options، گزینه‌ی Projects and Solutions را انتخاب و در ادامه گزینه‌ی Use previews of the .NET Core SDK را انتخاب کنید.
- پس از آن، این SDK جدید NET Core. به صورت زیر قابل انتخاب خواهد بود:


البته انتخاب شماره SDK صحیح به تنهایی برای کار با C# 8.0 کافی نیست؛ بلکه باید شماره‌ی زبان مورد استفاده را نیز صریحا انتخاب کرد:


برای اینکار بر روی پروژه کلیک راست کرده و گزینه‌ی Properties آن‌را انتخاب کنید. سپس در اینجا در برگه‌ی Build، بر روی دکمه‌ی Advanced کلیک کنید تا بتوان شماره نگارش زبان را مطابق تصویر فوق انتخاب کرد. در اینجا بجای C# 8.0 (beta)، گزینه‌ی unsupported preview را نیز می‌توانید انتخاب کنید.

یک نکته: خلاصه‌ی تمام این مراحل، منوها و تصاویر، همان تنظیمات فایل csproj است که در ادامه بررسی می‌کنیم.


فعالسازی C# 8.0 در VSCode

مدت‌ها است که برای کار با NET Core. نیازی به استفاده‌ی از نگارش کامل ویژوال استودیو نیست. همینقدر که VSCode را به همراه افزونه‌ی #C آن نصب کرده باشید، می‌توانید برنامه‌های مبتنی بر NET Core. را بر روی سیستم عامل‌های مختلفی که NET Core SDK. بر روی آن‌ها نصب شده‌است، توسعه دهید.
پشتیبانی ابتدایی از C# 8.0، با نگارش v1.18.0 افزونه‌ی #C مخصوص VSCode ارائه شد. بنابراین هم اکنون اگر آخرین نگارش آن‌را نصب کرده باشید، امکان کار با پروژه‌های NET Core 3.0 و C# 8.0 را نیز دارید.
بنابراین در اینجا به صورت خلاصه:
- ابتدا باید NET Core 3.0 SDK. را به صورت جداگانه‌ای دریافت و نصب کنید.
- سپس آخرین نگارش افزونه‌ی #C مخصوص VSCode را نیز نصب کنید.
- در آخر، یک پوشه‌ی جدید را ایجاد کرده و در خط فرمان دستور dotnet new console را صادر کنید. این دستور بر اساس آخرین شماره نگارش SDK نصب شده، یک پروژه‌ی جدید کنسول را ایجاد می‌کند که ساختار فایل csproj آن به صورت زیر است:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>
</Project>
همانطور که مشاهده می‌کنید، TargetFramework را به آخرین SDK نصب شده، تنظیم کرده‌است (معادل دومین تصویر این مطلب). مرحله‌ی بعد، تنظیم شماره نگارش زبان آن است. برای این منظور یکی از دو حالت زیر را می‌توان انتخاب کرد:
- یا معادل همان گزینه‌ی unsupported preview در تصویر سوم این مطلب:
<Project Sdk="Microsoft.NET.Sdk"> 
   <PropertyGroup> 
      <OutputType>Exe</OutputType> 
      <TargetFramework>netcoreapp3.0</TargetFramework> 
      <LangVersion>preview</LangVersion> 
   </PropertyGroup>
 </Project>
- و یا تعیین صریح شماره نگارش C# 8.0 (beta) به صورت زیر:
<Project Sdk="Microsoft.NET.Sdk"> 
   <PropertyGroup> 
      <OutputType>Exe</OutputType> 
      <TargetFramework>netcoreapp3.0</TargetFramework> 
      <LangVersion>8.0</LangVersion> 
   </PropertyGroup>
</Project>

یک نکته: در اینجا نمی‌توان LangVersion را به latest تنظیم کرد؛ چون C# 8.0 هنوز در مرحله‌ی بتا است. زمانیکه از مرحله‌ی بتا خارج شد، مقدار پیش‌فرض آن دقیقا latest خواهد بود و ذکر صریح آن غیر ضروری است. انتخاب latest در اینجا به latest minor version یا همان نگارش C# 7.3 فعلی (آخرین نگارش پایدار زبان #C در زمان نگارش این مطلب) اشاره می‌کند.



Rider و پشتیبانی از C# 8.0

Rider 2019.1 نیز به همراه پشتیبانی از C# 8.0 ارائه شده‌است و می‌تواند گزینه‌ی مطلوب دیگری برای توسعه‌ی برنامه‌های مبتنی بر NET Core. باشد.


نصب NET Core 3.0 SDK. و عدم اجرای برنامه‌های پیشین

یکی از مزایای کار با NET Core.، امکان نصب چندین نوع مختلف SDK آن، به موازت هم است؛ بدون اینکه بر روی یکدیگر تاثیری بگذارند. البته این نکته را باید درنظر داشت که برنامه‌های NET Core. بدون وجود فایل مخصوص global.json در پوشه‌ی ریشه‌ی آن‌ها، همواره از آخرین نگارش SDK نصب شده، برای اجرا استفاده خواهند کرد. اگر این مورد بر روی کار شما تاثیرگذار است، می‌توانید شماره SDK مورد استفاده‌ی برنامه‌ی خود را قفل کنید، تا SDKهای جدید نصب شده، به عنوان SDK پیش‌فرض برنامه‌های پیشین، انتخاب نشوند. بنابراین ابتدا لیست SDKهای نصب شده را با دستور زیر پیدا کنید:
> dotnet --list-sdks
سپس برای پروژه‌های قدیمی خود که فعلا قصد به روز رسانی آن‌ها را ندارید، یک فایل global.json را به صورت زیر‌، در ریشه‌ی پروژه تولید کنید:
> dotnet new globaljson --sdk-version 2.2.300
> type global.json
در اینجا 2.2.300 یکی از شماره‌هایی است که توسط دستور dotnet --list-sdks یافته‌اید و پروژه‌ی قبلی شما بر اساس آن کار می‌کند.
نظرات مطالب
ایجاد alert,confirm,prompt هایی متفاوت با jQuery Impromptu
زمانیکه از jQuery استفاده می‌کنید، دیگر نباید از Ajax خام استفاده کنید. خود jQuery تابع سازگار با انواع و اقسام مرورگرها رو برای کار با Ajax داره. این مثال تست شده و مشکلی نداره:
using System.Web.Mvc;

namespace jQueryImpromptu.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(string postId, string filename)
        {
            return Content("ok");
        }
    }
}

@{
    ViewBag.Title = "Index";
    var postUrl = Url.Action(actionName: "Index", controllerName: "Home");
}
<h2>
    Index</h2>
<input type="button" id="btnDelete" value="delete" />
<div id="infoDiv">
    Some Info ....
</div>
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $('#btnDelete').click(function () {
                $.prompt("آیا برای حذف اطلاعات موجود اطمینان دارید ؟",
             {
                 title: 'Title',
                 buttons: { "بله": true, "خیر": false },
                 focus: 2,
                 submit: function (e, v, m, f) {
                     //debugger;
                     if (!v) {
                         return;
                     }
                     var postId = 10;
                     var fileName = "test.jpg"
                     $.ajax({
                         type: "POST",
                         url: '@postUrl',
                         data: JSON.stringify({ postId: postId, fileName: fileName }),
                         contentType: "application/json; charset=utf-8",
                         dataType: "json",
                         complete: function (xhr, status) {
                             var data = xhr.responseText;
                             if (status === 'error' || !data || data == "nok") {
                                 $.prompt("! حذف فایل با خطا مواجه شده است",
                                    {
                                        title: 'Title',
                                        buttons: { "بستن": true }
                                    });
                             }
                             else {
                                 $("#infoDiv").fadeOut("slow").remove();
                             }
                         }
                     });
                 }
             });
            });
        });
    </script>
}
بازخوردهای پروژه‌ها
چند راهنمایی در مورد گروه بندی
سلام؛ همونطور که در عکس زیر مشاهده میکنید :
چند سوال و راهنمایی دارم از شما
اینکه آیا امکان داره Header ستون‌ها مثل وزن رو به جای دو سطر در یک سطر نوشت؟!
قسمت گروه سوالات که گروه بندی روی اون انجام شده آقا میشه کلمه مثلا "مدرس" merge شده در سطر‌ها و یک بار نوشته بشه در وسط و حتی بشه اون رو بصورت عمودی هم نوشت؟
و مشکل اینکه در استفاده از aggregateFunction  نتیجه به صورت عدد صفر نمایش داده میشه البته نمره به صورت یک CalculatedField معرفی شده استو عملیات روی دیگر ستون بدست میاد! 
و سوال آخر اینکه امکان داره summarySettings رو با انجام یک عملیات نمایش داد مثلا من میخوام در مثال زیر هر سطر نمره در وزن ضرب بشه و بعد میانگین اون به صورت گروهی در نتیجه نمایش داده بشه؟
در آخر از زحمات شما کمال تشکر رو دارم 

 column.ColumnItemsTemplate(template =>
                                          {
                                              template.ProgressBar(progressBarColor: Color.SkyBlue, showPercentText: true);
                                              template.DisplayFormatFormula(obj => obj == null ? string.Format("{0:P2}", 0) : string.Format("{0:P2}", obj));
                                          });
                                          column.AggregateFunction(aggregateFunction =>
                                          {
                                              aggregateFunction.NumericAggregateFunction(AggregateFunction.Average);
                                              aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Format("{0:P2}", 0) : string.Format("{0:P2}", obj));
                                          });
                                          column.CalculatedField(
                                              list =>
                                              {
                                                  if (list == null)
                                                      return string.Empty;
                                                  float sumCountPercent = 0;
                                                  float count = 1;
                                                  foreach (var item in IndexData)
                                                  {
                                                      sumCountPercent += float.Parse(list.GetValueOf(item.Value).ToString()) * (count++ * 20);
                                                  }
                                                  float sumColumnsPercent = float.Parse(list.GetValueOf("SumColumns").ToString()) * 100;
                                                  return sumCountPercent / sumColumnsPercent;
                                              });