بازخوردهای دوره
متدهای async تقلبی
برای استفاده از یک وب سرویس، اگر به شکل زیر در یک ClassLibrary استفاده شود، آیا واقعا غیر همزمان عمل می‌شود یا تقلبی محسوب می‌شود؟
public bool SendSms()
{
    try
    {
        var result = false;
        foreach (var nomber in _fromNombers)
        {
            if (result) continue;
            if (_smsService.SendGroupSmsSimple(Model.Signature, nomber, Model.Numbers, Model.Body, false,
                string.Empty) == 1)
                result = true;
        }
        return result;
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception.Message);
        return false;
    }
}

private Task SendSmsAsync()
{
    return Task.Run(() => SendSms());
}
و مثلا در یک ConsoleApp:
static void Main(string[] args)
{
    var r = SendAsync();
    r.Wait();
}

private static async Task SendAsync()
{
    var model = new SmsModel
    {
        Body = "سلام",
        Numbers = new[] { "0917*******" }
    };

    var service = new SmsService(model);

    await service.SendSmsAsync();
}

نظرات مطالب
Blazor 5x - قسمت 15 - کار با فرم‌ها - بخش 3 - ویرایش اطلاعات
در اصلاحیه متد IsRoomUniqueAsync  اگر بیش از دو رکورد ثبت شده باشد، هم در حالت Create و هم در حالت Update  با خطای ثبت اطلاعات  مواجه می‌شود
نسخه اصلاح شده به شرح زیر می‌باشد:
        public Task<bool> IsRoomUniqueAsync (string name, int roomId) {
            if (roomId == 0) {
                // Create Mode
                return _dbContext.HotelRooms
                    .ProjectTo<HotelRoomDTO> (_mapperConfiguration)
                    .AllAsync (x => x.Name != name);
            } else {
                // Edit Mode
                var temp = _dbContext.HotelRooms
                    .ProjectTo<HotelRoomDTO> (_mapperConfiguration)
                    .AnyAsync (x => (x.Name == name && x.Id == roomId));
                if (temp.Result == false) {
                    return _dbContext.HotelRooms
                        .ProjectTo<HotelRoomDTO> (_mapperConfiguration)
                        .AllAsync (x => x.Name != name);
                } else return temp;
            }
        }

نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
زمانی که خصوصیتی بصورت زیر تعریف می‌کنم:
[DisplayName("تاریخ شروع قرارداد")]
[Required(ErrorMessage = "تاریخ شروع قرارداد مشخص نشده است")]
public DateTime ContractStartDate { get; set; }
و در View از partialview تعریف شده بصورت زیر استفاده می‌کنم :
 @Html.EditorFor(model => model.ContractStartDate, MVC.Shared.Views.EditorTemplates.PersianDatePicker)
در زمانی که تاریخ خالی وارد میشه (مقدار min تاریخ در خصوصیت ذخیره میشه) و IsValid مقدار false رو بازگشت میده به علت اجرای مجدد partialview سیستم خطا صادر می‌کنه،و به اعتبار سنجی تعریف شده در Viewmodel نمیرسه .اگر ممکنه راهنمائی کنید.
[HttpPost]
        [ValidateAntiForgeryToken()]
        public virtual ActionResult Create(AddContractVM vm)
        {
            if (!ModelState.IsValid)
            {
                return View(vm);
            }
            else
            {
                if (new Services.Contract().Add(vm))
                {
                    return RedirectToAction("Index");
                }
                else
                {
                    return View(vm);
                }
            }
        }
نظرات مطالب
ثبت استثناهای مدیریت شده توسط ELMAH
باتشکر؛
با توجه به این که لایه‌های زیرین میبایست با Message با لایه بالاتر در ارتباط باشند و این خطاها در لایه بالاتر لاگ شوند ؛آیا ارجاع به اسمبلی Elmah در DataLayer کار درستی میباشد. برای مثال در یک برنامه Asp.net MVC برای نمایش خطاهای همزمانی به شکل زیر عمل کردن موردی ندارد؟
public async Task<string> SaveAllChangesAsync(bool invalidateCacheDependencies = true, Guid? auditUserId = null)
        {
            try
            {
                if (auditUserId.HasValue)
                    AuditFields(auditUserId.Value);
                await SaveChangesAsync();
                if (!invalidateCacheDependencies) return string.Empty;
                var changedEntityNames = GetChangedEntityNames();
                new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames);
            }
            catch (DbUpdateConcurrencyException ex)
            {
                Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
                return ConcurrencyMessage;
            }
            return string.Empty;
        }

نظرات مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
برای ارسال پارامترهای دلخواه به سرور از خاصیت postData استفاده کنید:
        function getHiddenColumnsList() {
            var colModel = $("#list").jqGrid('getGridParam', 'colModel');
            var hiddenColumns = new Array();
            
            if (!colModel)
                return hiddenColumns;

            for (var i = 0; i < colModel.length; i++) {
                if (colModel[i].hidden) {
                    hiddenColumns.push(colModel[i].name) ;
                }
            }
            return hiddenColumns;
        }

      $(document).ready(function () {
            $('#list').jqGrid({
                    // ... 
                    postData: { 'hiddenColumns': function() { return getHiddenColumnsList(); } }
                    // ... 
                });
       });
سمت سرور در اکشن متد GetProducts، خاصیت جدید hiddenColumns به صورت یک رشته که عناصر آن با کاما از هم جدا شده‌اند، قابل دریافت و آنالیز است.
و برای گزارش‌گیری با Pdf Report در تعریف ستون‌ها (مثلا ستون Id):
column.IsVisible(hiddenColumns.Split(',').All(col => col != "Id"));

یک نکته: ذکر function در postData ضروری است؛ وگرنه فقط یکبار محاسبه می‌شود.
مطالب
بررسی روش مشاهده خروجی SQL حاصل از کوئری‌های Entity framework Core
هنوز تا Entity framework Core 1.1، مفهوم interceptors موجود در EF 6.x پیاده سازی نشده‌است. اما شبیه به مفاهیم «ارتقاء به ASP.NET Core 1.0 - قسمت 17 - بررسی فریم ورک Logging»، در EF Core نیز زیرساختی جهت مشاهده‌ی SQL نهایی تولیدی وجود دارد.


ایجاد یک ثبت کننده‌ی وقایع EF Core

مرحله‌ی اول مشاهده‌ی خروجی‌های نهایی EF Core، پیاده سازی اینترفیس ILoggerProvider است که در آن قرار است وهله‌ی از نوع ILogger بازگشت داده شود. به همین جهت یک کلاس تو در توی خصوصی را در اینجا مشاهده می‌کنید که اینترفیس ILogger را نیز پیاده سازی کرده‌است:
using System;
using Microsoft.Extensions.Logging;

namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        public ILogger CreateLogger(string categoryName)
        {
            return new MyLogger();
        }

        public void Dispose()
        { }

        private class MyLogger : ILogger
        {
            public bool IsEnabled(LogLevel logLevel)
            {
                return true;
            }

            public void Log<TState>(
                        LogLevel logLevel, 
                        EventId eventId, 
                        TState state, 
                        Exception exception, 
                        Func<TState, Exception, string> formatter)
            {
                //File.AppendAllText(@"C:\temp\log.txt", formatter(state, exception));
                Console.WriteLine("");
                Console.WriteLine(formatter(state, exception));
            }

            public IDisposable BeginScope<TState>(TState state)
            {
                return null;
            }
        }
    }
}
در اینجا خروجی‌هایی نهایی توسط Console.WriteLine نمایش داده شده‌اند و مناسب برنامه‌های کنسول هستند و یا می‌توان برای مثال توسط File.AppendAllText، اطلاعات رسیده را در یک فایل نیز ذخیره کرد.
در متد Log:
- پارامتر logLevel، سطح اهمیت اطلاعات رسیده را به همراه دارد. برای مثال اطلاعات است یا خطا؟
برای مثال شاید نیاز به ذخیره سازی اطلاعاتی با سطح‌های بحرانی، خطا و یا اخطار در یک بانک اطلاعاتی وجود داشته باشد:
   if (logLevel == LogLevel.Critical || logLevel == LogLevel.Error || logLevel == LogLevel.Warning)
- eventId: نوع رخداد رسیده را مشخص می‌کند.
- state: می‌تواند هر نوع شیءایی، حاوی اطلاعات وضعیت رخداد رسیده باشد.
- exception: بیانگر استثنای احتمالی رخ داده است.
- formatter: کار آن تولید یک رشته‌ی قابل خواندن، توسط اطلاعات حالت و استثناء است.


معرفی Logger تهیه شده به برنامه

پس از تهیه‌ی Logger فوق، جهت معرفی آن به یک برنامه‌ی کنسول، می‌توان به صورت ذیل عمل کرد:
using (var db = new MyContext())
{
   var loggerFactory = (ILoggerFactory)db.GetInfrastructure().GetService(typeof(ILoggerFactory));
   loggerFactory.AddProvider(new MyLoggerProvider());
 }
این ثبت تنها باید یکبار در آغاز برنامه انجام شود و پس از آن تمام وهله‌ی دیگر Context از آن استفاده خواهند کرد.

در برنامه‌های ASP.NET Core، کار معرفی MyLoggerProvider در متد Configure کلاس آغازین برنامه انجام می‌شود:
public void Configure(ILoggerFactory loggerFactory)
{
   loggerFactory.AddProvider(new MyLoggerProvider());


اختصاصی سازی ثبت وقایع رسیده

کلاس MyLoggerProvider، هر نوع اطلاعات داخلی EF Core را نیز لاگ می‌کند. اگر هدف صرفا بررسی خروجی SQL نهایی تولیدی است، می‌توان در متد ذیل:
public ILogger CreateLogger(string categoryName)
بر اساس categoryName رسیده، یا new MyLogger را بازگشت داده و یا یک NullLogger.Instance را که کاری را انجام نمی‌دهد. به این ترتیب می‌توان کار فیلتر کردن اطلاعات رسیده را انجام داد.
برای این منظور، ابتدای Logger تهیه شده چنین شکلی را پیدا می‌کند:
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging;
using System.Linq;
using System;
 
namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        private static readonly string[] _categories =
            {
                typeof(RelationalCommandBuilderFactory).FullName,
                typeof(SqlServerConnection).FullName
            };
        public ILogger CreateLogger(string categoryName)
        {
            if (_categories.Contains(categoryName))
            {
                return new MyLogger();
            }
 
            return NullLogger.Instance;
        }
نظرات اشتراک‌ها
تبدیلگر ایران سیستم به یونیکد
من متد UnicodeFrom را به شکل زیر تغییر دادم الان درست کار میکنه: 
   public static string UnicodeFrom(TextEncoding textEncoding, string iranSystemEncodedString)
        {
            // وهله سازی از انکودینگ صحیح برای تبدیل رشته ایران سیستم به بایت
            Encoding encoding = Encoding.GetEncoding((int)textEncoding);

            // حذف فاصله‌های موجود در رشته
            iranSystemEncodedString = iranSystemEncodedString.Replace(" ", "");
            if (iranSystemEncodedString.Length <= 0)
                return "";
            // تبدیل رشته به بایت
            byte[] stringBytes = encoding.GetBytes(iranSystemEncodedString.Trim());

            // تغییر ترتیب بایت هااز آخر به اول در صورتی که رشته تماماً عدد نباشد
            if (!IsNumber(iranSystemEncodedString))
            {
                stringBytes = stringBytes.Reverse().ToArray();
            }

            // آرایه ای که بایت‌های معادل را در آن قرار می‌دهیم
            // مجموع تعداد بایت‌های رشته + بایت‌های اضافی محاسبه شده
            byte[] newStringBytes = new byte[stringBytes.Length + CountCharactersRequireTwoBytes(stringBytes)];

            int index = 0;

            // بررسی هر بایت و پیدا کردن بایت (های) معادل آن
            for (int i = 0; i < stringBytes.Length; ++i)
            {
                byte charByte = stringBytes[i];

                // اگر جز 128 بایت اول باشد که نیازی به تبدیل ندارد چون کد اسکی است
                if (charByte < 128)
                {
                    newStringBytes[index] = charByte;
                }
                else
                {
                    // اگر جز حروف یا اعداد بود معادلش رو قرار می‌دیم
                    if (CharactersMapper.ContainsKey(charByte))
                    {
                        newStringBytes[index] = CharactersMapper[charByte];
                    }
                }

                // اگر کاراکتر ایران سیستم "لا" بود چون کاراکتر متناظرش در عربی 1256 "ل" است و باید یک "ا" هم بعدش اضافه کنیم
                if (charByte == 242)
                {
                    newStringBytes[++index] = 199;
                }

                // اگر کاراکتر یکی از انواعی بود که بعدشان باید یک فاصله باشد
                // و در عین حال آخرین کاراکتر رشته نبود
                if (charactersWithSpaceAfter.Contains(charByte) && Array.IndexOf(stringBytes, charByte) != stringBytes.Length - 1)
                {
                    // یک فاصله بعد ان اضافه می‌کنیم
                    newStringBytes[++index] = 32;
                }

                index += 1;
            }

            // تبدیل به رشته و ارسال به فراخواننده
            byte[] unicodeContent = Encoding.Convert(encoding, Encoding.Unicode, newStringBytes);

            string result = Encoding.Unicode.GetString(unicodeContent).Trim();
            result = result.Replace("ڑ", "ء").Replace("ؤ", "ئ");

            //در صورتی که عدد داخل رشته نیست نیاز به ادامه کار نمی‌باشد
            if (!Regex.IsMatch(result, @"\d"))
                return result;

            bool isLastDigit = false;
            string tempForDigits = "";
            string str="";
            for (int i = 0; i < result.Length; i++)
            {
                if (Regex.IsMatch(result[i].ToString(), @"\d") || (i+1<result.Length && Regex.IsMatch(result[i].ToString() + result[i+1].ToString(), @"/\d")))
                {
                    isLastDigit = true;
                    tempForDigits += result[i];
                }
                else
                {
                    if (isLastDigit && tempForDigits.Length > 0)
                    {
                        str += new string(tempForDigits.Reverse().ToArray());
                        isLastDigit = false;
                        tempForDigits = "";
                    }
                     str += result[i];
                }
                if (!String.IsNullOrWhiteSpace(tempForDigits) && i == result.Length - 1)
                {
                    str += new string(tempForDigits.Reverse().ToArray());
                }
            }
            return str;
        }
مطالب
امنیت در LINQ to SQL

سؤال: LINQ to SQL تا چه میزان در برابر حملات تزریق SQL امن است؟
جواب کوتاه: بسیار زیاد!

توضیحات:
string query = @"SELECT * FROM USER_PROFILE
WHERE LOGIN_ID = '"+loginId+@"' AND PASSWORD = '"+password+@"'";
گاهی از اوقات هر چقدر هم در مورد خطرات کوئری‌هایی از نوع فوق مقاله نوشته شود کافی نیست و باز هم شاهد این نوع جمع زدن‌ها و نوشتن کوئری‌هایی به شدت آسیب پذیر در حالت استفاده از ADO.Net کلاسیک هستیم. مثال فوق یک نمونه کلاسیک از نمایش آسیب پذیری در مورد تزریق اس کیوال است. یا نمونه‌ی بسیار متداول دیگری از این دست که با ورودی خطرناک می‌تواند تا نمایش کلیه اطلاعات تمامی جداول موجود هم پیش برود:
protected void btnSearch_Click(object sender, EventArgs e)
{
String cmd = @"SELECT [CustomerID], [CompanyName], [ContactName]
FROM [Customers] WHERE CompanyName ='" + txtCompanyName.Text
+ @"'";

SqlDataSource1.SelectCommand = cmd;

GridView1.Visible = true;
}
در اینجا فقط کافی است مهاجم با تزریق عبارت SQL مورد نظر خود، کوئری اولیه را کاملا غیرمعتبر کرده و از یک جدول دیگر در سیستم کوئری تهیه کند!
راه حلی که برای مقابله با آن در دات نت ارائه شده نوشتن کوئری‌های پارامتری است و در این حالت کار encoding اطلاعات ورودی به صورت خودکار توسط فریم ورک مورد استفاده انجام خواهد شد؛ همچنین برای مثال اس کیوال سرور، execution plan این نوع کوئری‌های پارامتری را همانند رویه‌های ذخیره شده، کش کرده و در دفعات آتی فراخوانی آن‌ها به شدت سریعتر عمل خواهد کرد. برای مثال:
SqlCommand cmd = new SqlCommand("SELECT UserID FROM Users WHERE UserName=@UserName AND Password=@Password");
cmd.Parameters.Add(new SqlParameter("@UserName", System.Data.SqlDbType.NVarChar, 255, UserName));
cmd.Parameters.Add(new SqlParameter("@Password", System.Data.SqlDbType.NVarChar, 255, Password));
dr = cmd.ExecuteReader();
if (dr.Read()) userId = dr.GetInt32(dr.GetOrdinal("UserID"));
زمانیکه از کوئری پارامتری استفاده شود، مقدار پارامتر، هیچگاه فرصت و قدرت اجرا پیدا نمی‌کند. در این حالت صرفا به آن به عنوان یک مقدار معمولی نگاه خواهد شد و نه جزء قابل تغییر بدنه کوئری وارد شده که در حالت جمع زدن رشته‌ها همانند اولین کوئری معرفی شده، تا حد انحراف کوئری به یک کوئری دلخواه مهاجم قابل تغییر است.

اما در مورد LINQ to SQL چطور؟
این سیستم به صورت پیش فرض طوری طراحی شده است که تمام کوئری‌های SQL نهایی حاصل از کوئری‌های LINQ نوشته شده توسط آن، پارامتری هستند. به عبارت دیگر این سیستم به صورت پیش فرض برای افرادی که دارای حداقل اطلاعات امنیتی هستند به شدت امنیت بالایی را به همراه خواهد آورد.
برای مثال کوئری LINQ زیر را در نظر بگیرید:
var products = from p in db.products
where p.description.StartsWith(_txtSearch.Text)
select new
{
p.description,
p.price,
p.stock

};
اکنون فرض کنید کاربر به دنبال کلمه sony باشد، آنچه که بر روی اس کیوال سرور اجرا خواهد شد، دستور زیر است (ترجمه نهایی کوئری فوق به زبان T-SQL) :
exec sp_executesql N'SELECT [t0].[description], [t0].[price], [t0].[stock]
FROM [dbo].[products] AS [t0]
WHERE [t0].[description] LIKE @p0',N'@p0 varchar(5)',@p0='sony%'
برای لاگ کردن این عبارات SQL یا می‌توان از SQL profiler استفاده نمود و یا خاصیت log زمینه مورد استفاده را باید مقدار دهی کرد:
 db.Log = Console.Out;
و یا می‌توان بر روی کوئری مورد نظر در VS.Net یک break point قرار داد و سپس از debug visualizer مخصوص آن استفاده نمود.

همانطور که ملاحظه می‌کنید، کوئری نهایی تولید شده پارامتری است و در صورت ورود اطلاعات خطرناک در پارامتر p0 ، هیچ اتفاق خاصی نخواهد افتاد و صرفا رکوردی بازگشت داده نمی‌شود.

و یا همان مثال کلاسیک اعتبار سنجی کاربر را در نظر بگیرید:
public bool Validate(string loginId, string password)
{
DataClassesDataContext db = new DataClassesDataContext();

var validUsers = from user in db.USER_PROFILEs
where user.LOGIN_ID == loginId
&& user.PASSWORD == password
select user;

if (validUsers.Count() > 0) return true;
else return false;
}
کوئری نهایی T-SQL تولید شده توسط این ORM از کوئری LINQ فوق به شکل زیر است:
SELECT [t0].[LOGIN_ID], [t0].[PASSWORD]
FROM [dbo].[USER_PROFILE] AS [t0]
WHERE ([t0].[LOGIN_ID] = @p0) AND ([t0].[PASSWORD] = @p1)
و این کوئری پارامتری نیز در برابر حملات تزریق اس کیوال امن است.

تذکر مهم هنگام استفاده از سیستم LINQ to SQL :

اگر با استفاده از LINQ to SQL مجددا به روش قدیمی اجرای مستقیم کوئری‌های SQL خود همانند مثال زیر روی بیاورید (این امکان نیز وجود دارد)، نتیجه این نوع کوئری‌های حاصل از جمع زدن رشته‌ها، پارامتری "نبوده" و مستعد به تزریق اس کیوال هستند:
string sql = "select * from Trade where DealMember='" + this.txtParams.Text + "'";
var trades = driveHax.ExecuteQuery<Trade>(sql);
در اینجا باید در نظر داشت که اگر شخصی مجددا بخواهد از این نوع روش‌های کلاسیک استفاده کند شاید همان ADO.Net کلاسیک برای او کافی باشد و نیازی به تحمیل سربار یک ORM را به سیستم نداشته باشد. در این حالت برنامه از type safety کوئری‌های LINQ نیز محروم شده و یک لایه بررسی مقادیر و پارامترها را توسط کامپایلر نیز از دست خواهد داد.

اما روش صحیحی نیز در مورد بکارگیری متد ExecuteQuery وجود دارد. استفاده از این متد به شکل زیر مشکل را حل خواهد کرد:
IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
"SELECT contactname FROM customers WHERE city = {0}", "Tehran");
در این حالت، پارامترهای بکارگرفته شده (همان {0} ذکر شده در کوئری) به صورت خودکار به پارامترهای T-SQL ترجمه خواهند شد و مشکل تزریق اس کیوال برطرف خواهد شد (به عبارت دیگر استفاده از +، علامت مستعد بودن به تزریق اس کیوال است و بر عکس).

Vote on iDevCenter
نظرات مطالب
Blazor 5x - قسمت 33 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 3- بهبود تجربه‌ی کاربری عدم دسترسی‌ها
یک نکته‌ی تکمیلی: کار با Policy‌ها در برنامه‌های Blazor WASM

در این مطلب، روشی را برای برقراری دسترسی نقش Admin، به تمام قسمت‌های محافظت شده‌ی برنامه، با معرفی نقش آن به یک ویژگی Authorize سفارشی شده، مشاهده کردید. هرچند این روش کار می‌کند، اما روش جدیدتر برقراری یک چنین دسترسی‌های ترکیبی در برنامه‌های ASP.NET Core و سایر فناوری‌های مشتق شده‌ی از آن، کار با Policyها است که برای نمونه در مثال فوق، به صورت زیر قابل پیاده سازی است:

الف) تعریف Policyهای مشترک بین برنامه‌های Web API و WASM
Policyهای تعریف شده، باید قابلیت اعمال به اکشن متدهای کنترلرها و همچنین کامپوننت‌های WASM را داشته باشند. به همین جهت آن‌ها را در پروژه‌ی اشتراکی BlazorServer.Common که در هر دو پروژه استفاده می‌شود، قرار می‌دهیم:
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization; // dotnet add package Microsoft.AspNetCore.Authorization

namespace BlazorServer.Common
{
    public static class PolicyTypes
    {
        public const string RequireAdmin = nameof(RequireAdmin);
        public const string RequireCustomer = nameof(RequireCustomer);
        public const string RequireEmployee = nameof(RequireEmployee);
        public const string RequireEmployeeOrCustomer = nameof(RequireEmployeeOrCustomer);

        public static AuthorizationOptions AddAppPolicies(this AuthorizationOptions options)
        {
            options.AddPolicy(RequireAdmin, policy => policy.RequireRole(ConstantRoles.Admin));
            options.AddPolicy(RequireCustomer, policy =>
                    policy.RequireAssertion(context =>
                        context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
                            && (claim.Value == ConstantRoles.Admin || claim.Value == ConstantRoles.Customer))
                    ));
            options.AddPolicy(RequireEmployee, policy =>
                    policy.RequireAssertion(context =>
                        context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
                            && (claim.Value == ConstantRoles.Admin || claim.Value == ConstantRoles.Employee))
                    ));

            options.AddPolicy(RequireEmployeeOrCustomer, policy =>
                                policy.RequireAssertion(context =>
                                    context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
                                        && (claim.Value == ConstantRoles.Admin ||
                                            claim.Value == ConstantRoles.Employee ||
                                            claim.Value == ConstantRoles.Customer))
                                ));
            return options;
        }
    }
}
در اینجا یکسری Policy جدید را مشاهده می‌کنید که در آن‌ها همواره نقش Admin حضور دارد و همچین روش or آن‌ها را توسط policy.RequireAssertion مشاهده می‌کنید. این تعاریف، نیاز به نصب بسته‌ی Microsoft.AspNetCore.Authorization را نیز دارند. با کمک Policyها می‌توان ترکیب‌های پیچیده‌ای از دسترسی‌های موردنیاز را ساخت؛ بدون اینکه نیاز باشد مدام AuthorizeAttribute سفارشی را طراحی کرد.

ب) افزودن Policyهای تعریف شده به پروژه‌های Web API و WASM
پس از تعریف Policyهای مورد نیاز، اکنون نوبت به افزودن آن‌ها به برنامه‌های Web API:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            services.AddAuthorization(options => options.AddAppPolicies());

            // ...
و همچنین WASM است:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            // ...

            builder.Services.AddAuthorizationCore(options => options.AddAppPolicies());

            // ...
        }
    }
}
به این ترتیب Policyهای یک‌دستی را بین برنامه‌های کلاینت و سرور، به اشتراک گذاشته‌ایم.

ج) استفاده از Policyهای تعریف شده در برنامه‌ی WASM
اکنون که برنامه قابلیت کار با Policyها را پیدا کرده، می‌توان فیلتر Roles سفارشی را حذف و با فیلتر Authorize پالیسی دار جایگزین کرد:
@page "/hotel-room-details/{Id:int}"

// ...

@*
@attribute [Roles(ConstantRoles.Customer, ConstantRoles.Employee)]
*@

@attribute [Authorize(Policy = PolicyTypes.RequireEmployeeOrCustomer)]

حتی می‌توان از پالیسی‌ها در حین تعریف AuthorizeViewها نیز استفاده کرد:
<AuthorizeView  Policy="@PolicyTypes.RequireEmployeeOrCustomer">
    <p>You can only see this if you're an admin or an employee or a customer.</p>
</AuthorizeView>
مطالب
اعتبار سنجی سمت کاربر wysiwyg-editor ها در ASP.NET MVC
تفاوتی نمی‌کند که از کدامیک از HTML Editorها یا به عبارتی wysiwyg-editorهای موجود، جهت ورود اطلاعات استفاده می‌کنید. هیچکدام از آن‌ها سبب فراخوانی اعتبارسنجی Required سمت کاربر نمی‌شوند. چرا؟
علت اینجا است که با فعال سازی wysiwyg-editorهای موجود، المان HTML پیش فرض مانند یک TextArea حالت مخفی پیدا می‌کند:


و اگر به سورس کد فایل jquery.validate.js مراجعه کنید، یک چنین تعریف پیش فرضی در آن موجود است:
$.extend( $.validator, {
defaults: {
ignore: ":hidden",
به این معنا که در حین اعتبارسنجی سمت کاربر، از کلیه المان‌های hidden بر روی صفحه صرفنظر خواهد شد.
برای رفع این مشکل کافی است بنویسیم:
 jQuery.validator.setDefaults({ ignore: ":hidden:not(textarea)" });
در اینجا پیش فرض‌های jQuery validator بازنویسی شده و در آن از textareaهای مخفی صرفنظر نمی‌شود.
اگر می‌خواهید کلا از چیزی صرفنظر نشود، مقدار آن‌را به "" تنظیم کنید.

مشکل دوم! متد required پیش فرض، فقط به رشته‌های خالی یا نال واکنش نشان می‌دهد. اما اگر کاربری در اینجا <p><br/></p> را وارد کرد، چطور؟
در این حالت نیاز است متد rqeuired پیش فرض jQuery validator را به نحو ذیل بازنویسی کنیم:
    <script type="text/javascript">
        // حذف تمام تگ‌های یک قطعه متن
        function removeAllTagsAndTrim(html) {
            return !html ? "" : jQuery.trim(html.replace(/(<([^>]+)>)/ig, ""));
        }

        // این تنظیم برای پردازش ادیتور مخفی وب لازم است
        jQuery.validator.setDefaults({ ignore: ":hidden:not(textarea)" });

        // متد اصلی اعتبارسنجی را ابتدا ذخیره می‌کنیم
        jQuery.validator.methods.originalRequired = jQuery.validator.methods.required;
        // نحوه بازنویسی متد توکار اعتبار سنجی جهت استفاده از یک متد سفارشی
        jQuery.validator.addMethod("required", function (value, element, param) {
            value = removeAllTagsAndTrim(value);
            if (!value) {
                return false;
            }
            //  فراخوانی متد اصلی اعتبار سنجی در صورت شکست تابع سفارشی
            return jQuery.validator.methods.originalRequired.call(this, value, element, param);
        }, jQuery.validator.messages.required);
    </script>
متد removeAllTagsAndTrim کلیه تگ‌های یک عبارت HTML ایی را حذف می‌کند. متد trim جی‌کوئری نیز سبب حذف فواصل خالی در ابتدا و انتهای یک رشته می‌شود. اکنون نیاز است این متد سفارشی را جهت تمیزسازی عبارت ورودی به متد توکار required اعمال کنیم.
برای اینکار نیاز است متد اصلی required را در جایی ذخیره کنیم تا در صورت شکست اعتبارسنجی سفارشی، همان متد اصلی فراخوانی گردد. در ادامه با فراخوانی jQuery.validator.addMethod و بکارگیری نام required، دقیقا همان متد اصلی required موجود بازنویسی خواهد شد. در ابتدای کار تگ‌های ورودی پاک شده و سپس اعتبارسنجی می‌شوند.
در ادامه فراخوانی متد اصلی required را ملاحظه می‌کنید. همچنین با استفاده از jQuery.validator.messages.required اصل پیام خطای تنظیم شده به کاربر نمایش داده خواهد شد.