نظرات مطالب
تفاوت Desktop Application با Web Application
سلام لطفا در مورد این جمله بیشتر توضیح دهید
 در کنار این راهکار، استفاده از C#/XAML برای نوشتن برنامه برای اکثر پلتفرم‌های مطرح بازار اعم از اندروید، iOS و Windows Phone و ویندوز، نیز به عنوان راهکاری دیگر قابلیت استفاده را دارا است.  
آیا منظور شما استفاده از نرم افزارهای شرکت ثالث نظیر xamarin و .... می‌باشد یا تکنولوژی جدیدی از سمت مایکروسافت ارائه شده که این امکان رو میسر می‌کنه
نظرات مطالب
یکی کردن اسمبلی‌های یک پروژه‌ی WPF
نمونه ارسالی شما به خوبی کار می‌کند.
اما برای یک پروژه عملیاتی که از کامپوننت‌های ثالث استفاده می‌کند این روش پاسخ گو نبود و با پیام Windows unhandled error متوقف می‌شود. (تمامی اسمبلی هایی که تا پیش از این کنار فایل EXE مستقر بودند و برنامه کنار آنها صحیح کار می‌کرد، با روش گفته شده در فایل EXE برنامه مدفون شدند.)
 همچنین ILSpy صحت وجود را تایید کرد:

بررسی لاگ Application ویندوز خبر از پیدا نشدن فایلی (System.IO.Exception) در متد Main می‌دهد و نکته دیگری در آن ذکر نشده.

آیا راه حلی وجود دارد؟

نظرات مطالب
خلاصه‌ای کوتاه در مورد WinRT
در مورد این HTML که در بالا در مورد آن صحبت شد لازم است بیشتر توضیح داده شود:
برخلاف تصور عموم برنامه‌های HTML/CSS/JavaScript ویندوز 8 منحصرا برای WinRT تهیه می‌شوند و ... و ... قابل انتقال به سایر سکوهای کاری «نیستند». این‌ها از API مخصوص WinRT استفاده می‌کنند تا معنا پیدا کنند. بنابراین این مورد اصلا web programming متداول نیست. به آن‌ باید به چشم یک  standalone Windows 8 app نگاه کرد.
نظرات مطالب
مروری بر چند تجربه‌ی کاری با SQLite
- خیر. تمام نگارش‌های Access مد نظر بودند. برای نمونه به برنامه معروف زیر مراجعه کنید :
http://www.elcomsoft.com/aopr.html
- در مورد رمزنگاری هم بله. یک ماژول تجاری همانطور که عرض کردم توسط نویسنده‌ی اصلی آن فروخته می‌شود یا پروایدری که شما عنوان کردید هم با استفاده از windows crypto api ، رمزنگاری را اعمال می‌کند که در این حالت دیتابیس شما فقط منحصر به ویندوز خواهد شد و برای استفاده از آن در MONO مشکل خواهید داشت.
مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
ASP.NET Core Identity به همراه دو قابلیت جدید است که پیاده سازی سطوح دسترسی پویا را با سهولت بیشتری میسر می‌کند:
الف) Policies
ب) Role Claims


سیاست‌های دسترسی یا Policies در ASP.NET Core Identity

ASP.NET Core Identity هنوز هم از مفهوم Roles پشتیبانی می‌کند. برای مثال می‌توان مشخص کرد که اکشن متدی و یا تمام اکشن متدهای یک کنترلر تنها توسط کاربران دارای نقش Admin قابل دسترسی باشند. اما نقش‌ها نیز در این سیستم جدید تنها نوعی از سیاست‌های دسترسی هستند.
[Authorize(Roles = ConstantRoles.Admin)]
public class RolesManagerController : Controller
برای مثال در اینجا دسترسی به امکانات مدیریت نقش‌های سیستم، به نقش ثابت و از پیش تعیین شده‌ی Admin منحصر شده‌است و تمام کاربرانی که این نقش به آن‌ها انتساب داده شود، امکان استفاده‌ی از این قابلیت‌ها را خواهند یافت.
اما نقش‌های ثابت، بسیار محدود و غیر قابل انعطاف هستند. برای رفع این مشکل مفهوم جدیدی را به نام Policy اضافه کرده‌اند.
[Authorize(Policy="RequireAdministratorRole")]
public IActionResult Get()
{
   /* .. */
}
سیاست‌های دسترسی بر اساس Requirements و یا نیازهای سیستم تعیین می‌شوند و تعیین نقش‌ها، تنها یکی از قابلیت‌های آن‌ها هستند.
برای مثال اگر بخواهیم تک نقش Admin را به صورت یک سیاست دسترسی جدید تعریف کنیم، روش کار به صورت ذیل خواهد بود:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
services.AddAuthorization(options =>
    {
        options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Admin"));
    });
}
در تنظیمات متد AddAuthorization، یک سیاست دسترسی جدید تعریف شده‌است که جهت برآورده شدن نیازمندی‌های آن، کاربر سیستم باید دارای نقش Admin باشد که نمونه‌ای از نحوه‌ی استفاده‌ی از آن‌را با ذکر [Authorize(Policy="RequireAdministratorRole")] ملاحظه کردید.
و یا بجای اینکه چند نقش مجاز به دسترسی منبعی را با کاما از هم جدا کنیم:
 [Authorize(Roles = "Administrator, PowerUser, BackupAdministrator")]
می‌توان یک سیاست دسترسی جدید را به نحو ذیل تعریف کرد که شامل تمام نقش‌های مورد نیاز باشد و سپس بجای ذکر Roles، از نام این Policy جدید استفاده کرد:
options.AddPolicy("ElevatedRights", policy => policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));
به این صورت
[Authorize(Policy = "ElevatedRights")]
public IActionResult Shutdown()
{
   return View();
}

سیاست‌های دسترسی تنها به نقش‌ها محدود نیستند:
services.AddAuthorization(options =>
{
   options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});
برای مثال می‌توان دسترسی به یک منبع را بر اساس User Claims یک کاربر به نحوی که ملاحظه می‌کنید، محدود کرد:
[Authorize(Policy = "EmployeeOnly")]
public IActionResult VacationBalance()
{
   return View();
}


سیاست‌های دسترسی پویا در ASP.NET Core Identity

مهم‌ترین مزیت کار با سیاست‌های دسترسی، امکان سفارشی سازی و تهیه‌ی نمونه‌های پویای آن‌ها هستند؛ موردی که با نقش‌های ثابت سیستم قابل پیاده سازی نبوده و در نگارش‌های قبلی، جهت پویا سازی آن، یکی از روش‌های بسیار متداول، تهیه‌ی فیلتر Authorize سفارشی سازی شده بود. اما در اینجا دیگر نیازی نیست تا فیلتر Authorize را سفارشی سازی کنیم. با پیاده سازی یک AuthorizationHandler جدید و معرفی آن به سیستم، پردازش سیاست‌های دسترسی پویای به منابع، فعال می‌شود.
پیاده سازی سیاست‌های پویای دسترسی شامل مراحل ذیل است:
1- تعریف یک نیازمندی دسترسی جدید
public class DynamicPermissionRequirement : IAuthorizationRequirement
{
}
ابتدا باید یک نیازمندی دسترسی جدید را با پیاده سازی اینترفیس IAuthorizationRequirement ارائه دهیم. این نیازمندی مانند روشی که در پروژه‌ی DNT Identity بکار گرفته شده‌است، خالی است و صرفا به عنوان نشانه‌ای جهت یافت AuthorizationHandler استفاده کننده‌ی از آن استفاده می‌شود. در اینجا در صورت نیاز می‌توان یک سری خاصیت اضافه را تعریف کرد تا آن‌ها را به صورت پارامترهایی ثابت به AuthorizationHandler ارسال کند.

2- پیاده سازی یک AuthorizationHandler استفاده کننده‌ی از نیازمندی دسترسی تعریف شده
پس از اینکه نیازمندی DynamicPermissionRequirement را تعریف کردیم، در ادامه باید یک AuthorizationHandler استفاده کننده‌ی از آن را تعریف کنیم:
    public class DynamicPermissionsAuthorizationHandler : AuthorizationHandler<DynamicPermissionRequirement>
    {
        private readonly ISecurityTrimmingService _securityTrimmingService;

        public DynamicPermissionsAuthorizationHandler(ISecurityTrimmingService securityTrimmingService)
        {
            _securityTrimmingService = securityTrimmingService;
            _securityTrimmingService.CheckArgumentIsNull(nameof(_securityTrimmingService));
        }

        protected override Task HandleRequirementAsync(
             AuthorizationHandlerContext context,
             DynamicPermissionRequirement requirement)
        {
            var mvcContext = context.Resource as AuthorizationFilterContext;
            if (mvcContext == null)
            {
                return Task.CompletedTask;
            }

            var actionDescriptor = mvcContext.ActionDescriptor;
            var area = actionDescriptor.RouteValues["area"];
            var controller = actionDescriptor.RouteValues["controller"];
            var action = actionDescriptor.RouteValues["action"];

            if(_securityTrimmingService.CanCurrentUserAccess(area, controller, action))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }

            return Task.CompletedTask;
        }
    }
کار با ارث بری از AuthorizationHandler شروع شده و آرگومان جنریک آن، همان نیازمندی است که پیشتر تعریف کردیم. از این آرگومان جنریک جهت یافتن خودکار AuthorizationHandler متناظر با آن، توسط ASP.NET Core Identity استفاده می‌شود. بنابراین در اینجا DynamicPermissionRequirement تهیه شده صرفا کارکرد علامتگذاری را دارد.
در کلاس تهیه شده باید متد HandleRequirementAsync آن‌را بازنویسی کرد و اگر در این بین، منطق سفارشی ما context.Succeed را فراخوانی کند، به معنای برآورده شدن سیاست دسترسی بوده و کاربر جاری می‌تواند به منبع درخواستی، بلافاصله دسترسی یابد و اگر context.Fail فراخوانی شود، در همینجا دسترسی کاربر قطع شده و HTTP status code مساوی 401 (عدم دسترسی) را دریافت می‌کند.

منطق سفارشی پیاده سازی شده نیز به این صورت است:
نام ناحیه، کنترلر و اکشن متد درخواستی کاربر از مسیریابی جاری استخراج می‌شوند. سپس توسط سرویس سفارشی ISecurityTrimmingService تهیه شده، بررسی می‌کنیم که آیا کاربر جاری به این سه مؤلفه دسترسی دارد یا خیر؟

3- معرفی سیاست دسترسی پویای تهیه شده به سیستم
معرفی سیاست کاری پویا و سفارشی تهیه شده، شامل دو مرحله‌ی زیر است:
        private static void addDynamicPermissionsPolicy(this IServiceCollection services)
        {
            services.AddScoped<IAuthorizationHandler, DynamicPermissionsAuthorizationHandler>();
            services.AddAuthorization(opts =>
            {
                opts.AddPolicy(
                    name: ConstantPolicies.DynamicPermission,
                    configurePolicy: policy =>
                    {
                        policy.RequireAuthenticatedUser();
                        policy.Requirements.Add(new DynamicPermissionRequirement());
                    });
            });
        }
ابتدا باید DynamicPermissionsAuthorizationHandler تهیه شده را به سیستم تزریق وابستگی‌ها معرفی کنیم.
سپس یک Policy جدید را با نام دلخواه DynamicPermission تعریف کرده و نیازمندی علامتگذار خود را به عنوان یک policy.Requirements جدید، اضافه می‌کنیم. همانطور که ملاحظه می‌کنید یک وهله‌ی جدید از DynamicPermissionRequirement در اینجا ثبت شده‌است. همین وهله به متد HandleRequirementAsync نیز ارسال می‌شود. بنابراین اگر نیاز به ارسال پارامترهای بیشتری به این متد وجود داشت، می‌توان خواص مرتبطی را به کلاس DynamicPermissionRequirement نیز اضافه کرد.
همانطور که مشخص است، در اینجا یک نیازمندی را می‌توان ثبت کرد و نه Handler آن‌را. این Handler از سیستم تزریق وابستگی‌ها، بر اساس آرگومان جنریک AuthorizationHandler پیاده سازی شده، به صورت خودکار یافت شده و اجرا می‌شود (بنابراین اگر Handler شما اجرا نشد، مطمئن شوید که حتما آن‌را به سیستم تزریق وابستگی‌ها معرفی کرده‌اید).

پس از آن هر کنترلر یا اکشن متدی که از این سیاست دسترسی پویای تهیه شده استفاده کند:
[Authorize(Policy = ConstantPolicies.DynamicPermission)]
[DisplayName("کنترلر نمونه با سطح دسترسی پویا")]
public class DynamicPermissionsSampleController : Controller
به صورت خودکار توسط DynamicPermissionsAuthorizationHandler مدیریت می‌شود.


سرویس ISecurityTrimmingService چگونه کار می‌کند؟

کدهای کامل ISecurityTrimmingService را در کلاس SecurityTrimmingService می‌توانید مشاهده کنید.
پیشنیاز درک عملکرد آن، آشنایی با دو قابلیت زیر هستند:
الف) «روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامه‌ی ASP.NET Core»
دقیقا از همین سرویس توسعه داده شده‌ی در مطلب فوق، در اینجا نیز استفاده شده‌است؛ با یک تفاوت تکمیلی:
public interface IMvcActionsDiscoveryService
{
    ICollection<MvcControllerViewModel> MvcControllers { get; }
    ICollection<MvcControllerViewModel> GetAllSecuredControllerActionsWithPolicy(string policyName);
}
از متد GetAllSecuredControllerActionsWithPolicy جهت یافتن تمام اکشن متدهایی که مزین به ویژگی Authorize هستند و دارای Policy مساوی DynamicPermission می‌باشند، در کنترلر DynamicRoleClaimsManagerController برای لیست کردن آن‌ها استفاده می‌شود. اگر این اکشن متد مزین به ویژگی DisplayName نیز بود (مانند مثال فوق و یا کنترلر نمونه DynamicPermissionsSampleController)، از مقدار آن برای نمایش نام این اکشن متد استفاده خواهد شد.
بنابراین همینقدر که تعریف ذیل یافت شود، این اکشن متد نیز در صفحه‌ی مدیریت سطوح دسترسی پویا لیست خواهد شد.
 [Authorize(Policy = ConstantPolicies.DynamicPermission)]

ابتدا به مدیریت نقش‌های ثابت سیستم می‌رسیم. سپس به هر نقش می‌توان یک ‍Claim جدید را با مقدار area:controller:action انتساب داد.
به این ترتیب می‌توان به یک نقش، تعدادی اکشن متد را نسبت داد و سطوح دسترسی به آن‌ها را پویا کرد. اما ذخیره سازی آن‌ها چگونه است و چگونه می‌توان به اطلاعات نهایی ذخیره شده دسترسی پیدا کرد؟


مفهوم جدید Role Claims در ASP.NET Core Identity

تا اینجا موفق شدیم تمام اکشن متدهای دارای سیاست دسترسی سفارشی سازی شده‌ی خود را لیست کنیم، تا بتوان آن‌ها را به صورت دلخواهی انتخاب کرد و سطوح دسترسی به آن‌ها را به صورت پویا تغییر داد. اما این اکشن متدهای انتخاب شده را در کجا و به چه صورتی ذخیره کنیم؟
برای ذخیره سازی این اطلاعات نیازی نیست تا جدول جدیدی را به سیستم اضافه کنیم. جدول جدید AppRoleClaims به همین منظور تدارک دیده شده‌است.



وقتی کاربری عضو یک نقش است، به صورت خودکار Role Claims آن نقش را نیز به ارث می‌برد. هدف از نقش‌ها، گروه بندی کاربران است. توسط Role Claims می‌توان مشخص کرد این نقش‌ها چه کارهایی را می‌توانند انجام دهند. اگر از قسمت قبل بخاطر داشته باشید، سرویس توکار UserClaimsPrincipalFactory دارای مرحله‌ی 5 ذیل است:
«5) اگر یک نقش منتسب به کاربر دارای Role Claim باشد، این موارد نیز واکشی شده و به کوکی او به عنوان یک Claim جدید اضافه می‌شوند. در ASP.NET Identity Core نقش‌ها نیز می‌توانند Claim داشته باشند (امکان پیاده سازی سطوح دسترسی پویا).»
به این معنا که با لاگین شخص به سیستم، تمام اطلاعات مرتبط به او که در جدول AppRoleClaims وجود دارند، به کوکی او به صورت خودکار اضافه خواهند شد و دسترسی به آن‌ها فوق العاده سریع است.

در کنترلر DynamicRoleClaimsManagerController، یک Role Claim Type جدید به نام DynamicPermissionClaimType اضافه شده‌است و سپس ID اکشن متدهای انتخابی را به نقش جاری، تحت Claim Type عنوان شده، اضافه می‌کند (تصویر فوق). این ID به صورت area:controller:action طراحی شده‌است. به همین جهت است که در  DynamicPermissionsAuthorizationHandler همین سه جزء از سیستم مسیریابی استخراج و در سرویس SecurityTrimmingService مورد بررسی قرار می‌گیرد:
 return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType &&
claim.Value == currentClaimValue);
در اینجا user همان کاربرجاری سیستم است. HasClaim جزو متدهای استاندارد آن است و Type انتخابی، همان نوع سفارشی مدنظر ما است. currentClaimValue دقیقا همان ID اکشن متد جاری است که توسط کنار هم قرار دادن area:controller:action تشکیل شده‌است.
متد HasClaim هیچگونه رفت و برگشتی را به بانک اطلاعاتی ندارد و اطلاعات خود را از کوکی شخص دریافت می‌کند. متد user.IsInRole نیز به همین نحو عمل می‌کند.


Tag Helper جدید SecurityTrimming

اکنون که سرویس ISecurityTrimmingService را پیاده سازی کرده‌ایم، از آن می‌توان جهت توسعه‌ی SecurityTrimmingTagHelper نیز استفاده کرد:
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            context.CheckArgumentIsNull(nameof(context));
            output.CheckArgumentIsNull(nameof(output));

            // don't render the <security-trimming> tag.
            output.TagName = null;

            if(_securityTrimmingService.CanCurrentUserAccess(Area, Controller, Action))
            {
                // fine, do nothing.
                return;
            }

            // else, suppress the output and generate nothing.
            output.SuppressOutput();
        }
عملکرد آن نیز بسیار ساده است. اگر کاربر، به area:controller:action جاری دسترسی داشت، این Tag Helper کاری را انجام نمی‌دهد. اگر خیر، متد SuppressOutput را فراخوانی می‌کند. این متد سبب خواهد شد، هر آنچه که داخل تگ‌های این TagHelper قرار گرفته، در صفحه رندر نشوند و از خروجی آن حذف شوند. به این ترتیب، کاربر به اطلاعاتی که به آن دسترسی ندارد (مانند لینک به مدخلی خاص را) مشاهده نخواهد کرد. به این مفهوم security trimming می‌گویند.
نمونه‌ای از کاربرد آن‌را در ReportsMenu.cshtml_ می‌توانید مشاهده کنید:
            <security-trimming asp-area="" asp-controller="DynamicPermissionsTest" asp-action="Products">
                <li>
                    <a asp-controller="DynamicPermissionsTest" asp-action="Products" asp-area="">
                        <span class="left5 fa fa-user" aria-hidden="true"></span>
                        گزارش از لیست محصولات
                    </a>
                </li>
            </security-trimming>
در اینجا اگر کاربر جاری به کنترلر DynamicPermissionsTest و اکشن متد Products آن دسترسی پویا نداشته باشد، محتوای قرارگرفته‌ی داخل تگ security-trimming را مشاهده نخواهد کرد.

برای آزمایش آن یک کاربر جدید را به سیستم DNT Identity اضافه کنید. سپس آن‌را در گروه نقشی مشخص قرار دهید (منوی مدیریتی،‌گزینه‌ی مدیریت نقش‌های سیستم). سپس به این گروه دسترسی به تعدادی از آیتم‌های پویا را بدهید (گزینه‌ی مشاهده و تغییر لیست دسترسی‌های پویا). سپس با این اکانت جدید به سیستم وارد شده و بررسی کنید که چه تعدادی از آیتم‌های منوی «گزارشات نمونه» را می‌توانید مشاهده کنید (تامین شده‌ی توسط ReportsMenu.cshtml_).


مدیریت اندازه‌ی حجم کوکی‌های ASP.NET Core Identity

همانطور که ملاحظه کردید، جهت بالابردن سرعت دسترسی به اطلاعات User Claims و Role Claims، تمام اطلاعات مرتبط با آن‌ها، به کوکی کاربر وارد شده‌ی به سیستم، اضافه می‌شوند. همین مساله در یک سیستم بزرگ با تعداد صفحات بالا، سبب خواهد شد تا حجم کوکی کاربر از 5 کیلوبایت بیشتر شده و توسط مرورگرها مورد قبول واقع نشوند و عملا سیستم از کار خواهد افتاد.
برای مدیریت یک چنین مساله‌ای، امکان ذخیره سازی کوکی‌های شخص در داخل بانک اطلاعاتی نیز پیش بینی شده‌است. زیر ساخت آن‌را در مطلب «تنظیمات کش توزیع شده‌ی مبتنی بر SQL Server در ASP.NET Core» پیشتر در این سایت مطالعه کردید و در پروژه‌ی DNT Identity بکارگرفته شده‌است.
اگر به کلاس IdentityServicesRegistry مراجعه کنید، یک چنین تنظیمی در آن قابل مشاهده است:
 var ticketStore = provider.GetService<ITicketStore>();
identityOptionsCookies.ApplicationCookie.SessionStore = ticketStore; // To manage large identity cookies
در ASP.NET Identity Core، امکان تدارک SessionStore سفارشی برای کوکی‌ها نیز وجود دارد. این SessionStore  باید با پیاده سازی اینترفیس ITicketStore تامین شود. دو نمونه پیاده سازی ITicketStore را در لایه سرویس‌های پروژه می‌توانید مشاهده کنید:
الف) DistributedCacheTicketStore
ب) MemoryCacheTicketStore

اولی از همان زیرساخت «تنظیمات کش توزیع شده‌ی مبتنی بر SQL Server در ASP.NET Core» استفاده می‌کند و دومی از IMemoryCache توکار ASP.NET Core برای پیاده سازی مکان ذخیره سازی محتوای کوکی‌های سیستم، بهره خواهد برد.
باید دقت داشت که اگر حالت دوم را انتخاب کنید، با شروع مجدد برنامه، تمام اطلاعات کوکی‌های کاربران نیز حذف خواهند شد. بنابراین استفاده‌ی از حالت ذخیره سازی آن‌ها در بانک اطلاعاتی منطقی‌تر است.


نحوه‌ی تنظیم سرویس ITicketStore را نیز در متد setTicketStore می‌توانید مشاهده کنید و در آن، در صورت انتخاب حالت بانک اطلاعاتی، ابتدا تنظیمات کش توزیع شده، صورت گرفته و سپس کلاس DistributedCacheTicketStore به عنوان تامین کننده‌ی ITicketStore به سیستم تزریق وابستگی‌ها معرفی می‌شود.
همین اندازه برای انتقال محتوای کوکی‌های کاربران به سرور کافی است و از این پس تنها اطلاعاتی که به سمت کلاینت ارسال می‌شود، ID رمزنگاری شده‌ی این کوکی است، جهت بازیابی آن از بانک اطلاعاتی و استفاده‌ی خودکار از آن در برنامه.


کدهای کامل این سری را در مخزن کد DNT Identity می‌توانید ملاحظه کنید.
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
- بجای اینکار (اضافه و حذف کردن سرویس‌ها در زمان اجرای برنامه که با ری‌استارت برنامه در سمت سرور غیرمعتبر می‌شود)، بهتر است با توجه به اینکه این وظایف به صورت یک سرویس ثبت می‌شوند، یک سرویس سفارشی فعال یا غیرفعالسازی را تعریف کنید و آن‌را به سازنده‌ی این وظایف تزریق و استفاده کنید. سپس زمانیکه حلقه‌ی انجام وظایف به RunAsync رسید، در اینجا فرصت خواهید داشت تا سرویس سفارشی جدید تزریق شده را بررسی کرده و از فعال بودن یا نبودن این وظیفه مطلع شوید (برای مثال این سرویس بر اساس نامی که به آن ارسال می‌کنید، به بانک اطلاعاتی مراجعه کرده و روشن و یا خاموش بودن آن‌را بررسی کند. تنظیم بانک اطلاعاتی آن‌را هم واگذار کنید به قسمت مدیریتی برنامه).  
+ این نوع Taskهای پویا را باید در ابتدای کار برنامه و در کلاس آغازین آن، به صورت زیر معرفی کنید:
services.AddTransient<DoEnableProductTasks>();
مطالب
غیرفعال سازی کش مرورگر در مورد تصاویر ایستا

امروز هنگام نمایش تصاویر در یک گرید با مشکل زیر مواجه شده بودم. (گرفتار شده بودم!!)
فرض کنید یک فرم درست کرده‌اید که یک تصویر به همراه توضیحاتی توسط آن ثبت می‌شود. جهت اطمینان خاطر ثبت کننده، تصاویر ثبت شده در یک گرید پائین فرم نمایش داده خواهند شد. نام این تصاویر همواره ثابت است (برای مثال نام تصویر تشکیل شده است از یک شماره پروژه بعلاوه پسوند فایل).
مشکلی در ثبت تصاویر یا توضیحات آنها وجود نداشت. مشکل در این بود که پس از ویرایش رکورد و انتخاب تصویری جدید و ثبت آن، همان تصویر قبلی در مرورگر نمایش داده می‌شد. این مورد را می‌شد با فشردن دکمه‌های ctrl+f5 حل کرد (به روز کردن کش مرورگر)، اما این راه حل اصولی حل این مشکل نیست. (تصورش را بکنید که به کاربر گفته شود پس از هر بار فشردن دکمه ثبت، یکبار هم دکمه‌های ctrl+f5 را فشار دهید!)
راه ‌حل‌های استاندارد غیرفعال کردن کش در ASP.Net هم هیچکدام افاقه نکردند. ترکیبی از موارد زیر در page_load صفحه تست شدند:
            //do not cache...
Response.CacheControl = "no-cache";
Response.AddHeader("Pragma", "no-cache");
Response.Expires = -1;
Response.Cache.SetNoStore();
Response.Cache.SetCacheability(HttpCacheability.NoCache);

چون نام تصاویر تغییری نمی‌کرد، برای مثال 1234 همیشه همان 1234 باقی می‌ماند (صرفنظر از محتوای جدید آن)، مرورگر این تصویر غیرپویا را کش می‌کرد و فقط با ctrl+f5 این کش به روز می‌شد. (روش فوق در مورد غیرفعال کردن کش کردن یک صفحه پویای ASP.Net مؤثر است (برای مثال توصیه می‌شود که کش کردن صفحات لاگین را حتما به این صورت غیرفعال کنید)، اما در مورد اشیاء غیرپویای صفحه مطابق آزمایش من اثری نداشت)

این مشکل به صورت زیر حل شد: (یک ستون GridView است)
<asp:TemplateField HeaderText="لوگو">
<ItemTemplate>
<asp:Image ID="Image2" runat="server"
ImageUrl='<%# "pics/"+Eval("filename")+"?uid="+Guid.NewGuid().ToString("N") %>' />
</ItemTemplate>
<ItemStyle HorizontalAlign="Center" />
</asp:TemplateField>

یک عبارت منحصر بفرد به صورت کوئری استرینگ به انتهای نام تصویر اضافه شد. مشکلی هم در نمایش تصویر ایجاد نمی‌کند، مرورگر آن‌را یک تصویر جدید به حساب آورده و دوباره همان تصویر قبلی موجود در کش را نمایش نخواهد داد. (با IE و فایرفاکس تست شد و اینجا دیگر مهم نیست که وضعیت تنظیمات به روز رسانی کش مرورگرهای تک تک کاربران به چه صورتی است و آیا باید نگران همه‌ی آنها بود یا خیر یا این‌که اصلا ارزش آن‌را دارد که برای یک صفحه کلا کش مرورگر را غیرفعال کرد؟)

زمانیکه GridView رندر شود، تصویر ما به صورت زیر خواهد بود:
<img id="GridView1__ctl2_Image2" src="pics/123456.PNG?uid=2a2c4247ae264ef49be46a2436ae03c9" border="0" />

نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
برای کار با اکتیو دایرکتوری کار زیادی لازم نیست انجام شود. مرحله‌ی اول آن فعالسازی Windows Authentication در برنامه‌های ASP.NET Core است. در این حالت همینقدر که کاربر، به ویندوز متصل به شبکه و اکتیو دایرکتوری وارد شده باشد، به سیستم لاگین خواهد شد. نیازی به تغییری هم در هیچ قسمتی از برنامه نیست. مرحله‌ی بعد آن کار با نقش‌های سیستم است. این نقش‌ها هم در User.Identity هم اکنون موجود هستند و این شیء توسط Windows Authentication و گروه‌های اکتیو دایرکتوری پیش‌تر به صورت خودکار مقدار دهی شده‌است:
- نحوه‌ی یکپارچه سازی با سیستم اعتبارسنجی ویندوز  (با این فعالسازی، ویژگی‌های [Authorize] و [AllowAnonymous] بلافاصله قابل استفاده خواهند بود و User.Identity هم به صورت خودکار مقداردهی می‌شود.)
- یک نمونه مثال از دسترسی به نقش‌های اکتیودایرکتوری (با این تنظیمات برای مثال دسترسی به نقش‌های [Authorize(Roles = "Domain Admins")] و [Authorize(Roles = "Domain Users")] را خواهید داشت. یعنی برای مثال هرکاربری که عضو گروه Domain admins در اکتیو دایرکتوری باشد، امکان دسترسی به مورد اول را خواهد یافت.)
مطالب
از NET Standard. به NET 5.
 ارائه‌ی NET 5. یا پایان NET Standard.

تا پیش از ارائه‌ی NET 5.، پیاده سازی‌های مجزایی از دات نت مانند Full .NET Framework ،.NET Core ،Xamarin و غیره وجود داشتند و دارند. در این حالت برای اینکه بتوان یک class library قابل اجرای بر روی تمام این‌ها را ارائه داد، نیاز به ارائه‌ی API ای بود که بین تمام آن‌ها به اشتراک گذاشته شود و این دقیقا هدف وجودی NET Standard. است؛ اما ... مشکلات زیر را نیز به همراه دارد:
هر زمانیکه نیاز به افزودن یک API جدید باشد، نیاز خواهد بود تا یک نگارش جدید از NET Standard. ارائه شود. سپس تمام نگارش‌های مختلف دات نت باید سعی کنند به صورت مجزایی این API جدید را پیاده سازی کنند. این پروسه بسیار کند است. همچنین همواره باید مراجع مختلف را دقیق بررسی کنید که برای مثال کدام نگارش از دات نت، کدام نگارش از NET Standard. را پیاده سازی کرده‌است.
با ارائه‌ی NET 5.، وضعیت کاملا فرق کرده‌است. در اینجا یک «کد پایه‌ی اشتراکی» را برای تمام نگارش‌های مختلف دات نت داریم و این نگارش می‌خواهد مخصوص دسکتاپ یا برنامه‌های موبایل باشد، تفاوتی نمی‌کند. اکنون که تمام نگارش‌های مختلف دات نت بر فراز یک کد اشتراکی پایه کار می‌کنند، دیگر نیازی به ارائه‌ی مجزای یک API استاندارد و سپس پیاده سازی مجزای دیگری از آن، نیست.


نگارش‌های ویژه‌ی NET 5.، مخصوص سکوهای کاری مختلف

همانطور که عنوان شد، NET 5. در اصل یک «مجموعه کد اشتراکی» بین NET Core ،Mono ،Xamarin. و سایر پیاده سازی‌های دات نت است. اما سکوهای کاری مختلف، مانند Android، iOS، ویندوز و غیره، به همراه کدهای قابل توجهی که مختص به آن سیستم عامل‌های خاص باشند نیز هستند. برای رفع این مشکل، یکسری TFM یا target framework name/Target Framework Moniker ارائه شده‌اند. برای مثال net5.0 یکی از آن‌ها است. زمانیکه از این TFM استفاده می‌کنید، یعنی در حال کار با API ای هستید که در تمام نگارش‌های چندسکویی مختلف دات نت، مهیا و قابل استفاده‌است. نام جدید «net5.0» جایگزین کننده‌ی نام‌های قدیمی «netcoreapp» و «netstandard» است.
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
</Project>
در اینجا اگر نیاز به کار با API مخصوص ویندوز را نیز داشتید، می‌توانید از TFM مخصوص آن که net5.0-windows نام دارد استفاده کنید. «net5.0-windows» به این معنا است که به تمام امکانات net5.0 دسترسی دارید؛ به علاوه‌ی API ای که مختص به سیستم عامل ویندوز است (مانند Windows Forms و WPF). در دات نت 6، دو TFM جدید net6.0-android و net6.0-ios را نیز شاهد خواهیم بود.
کار کردن با این اسامی و درک آن‌ها نیز ساده‌است. برای مثال net6.0-ios به این معنا است که این قطعه کد و اسمبلی آن، می‌تواند تمام کتابخانه‌های مخصوص net5.0 و net6.0 را نیز استفاده کند؛ اما نه کتابخانه‌هایی که به صورت اختصاصی برای net5.0-windows و یا net6.0-android کامپایل شده‌اند.
این توضیحات به معنای پایان کار «NET Standard.» است. پس از NET Standard 2.1. فعلی، دیگر هیچ نگارش جدیدتری از آن ارائه نخواهد شد و البته net5.0 و تمام نگارش‌های پس از آن، قابلیت استفاده‌ی از کتابخانه‌های مبتنی بر NET Standard 2.1. و پیش از آن‌را نیز دارا هستند. بنابراین از این پس، net5.0 را به عنوان پایه‌ی به اشتراک گذاری کدها مدنظر داشته باشید.


سؤال: اگر امروز خواستیم کتابخانه‌ای را تولید کنیم، باید از کدام TFM استفاده کرد؟

net5.0 و تمام نگارش‌های پس از آن، از کتابخانه‌های مبتنی بر NET Standard 2.1. و قبل از آن نیز پیشیبانی می‌کنند. در این حالت تنها دلیل تغییر TFM کتابخانه‌های قدیمی موجود به net5.0، می‌تواند دسترسی به یکسری API جدید باشد. اما در مورد کتابخانه‌های جدید چطور؟ آیا باید برای مثال از netstandard2.0 استفاده کرد و یا از net5.0؟ این مورد بستگی به نوع پروژه‌ی در حال تهیه دارد:
- اجزای مختلف یک برنامه: اگر از کتابخانه‌ها برای شکستن برنامه‌ی خود به چندین قسمت استفاده می‌کنید، بهتر است از نام جدید netX.Y استفاده کنید (مانند net5.0). که در اینجا X.Y، منظور پایین‌ترین شماره نگارش NET. ای است که برنامه‌ی شما قرار است بر مبنای آن تهیه شود.
- کتابخانه‌های با قابلیت استفاده‌ی مجدد: اگر قرار است از کتابخانه‌ی شما در NET 4x. نیز استفاده شود، با netstandard2.0 شروع کنید. بهتر است netstandard1x را فراموش کنید؛ چون دیگر پشتیبانی نمی‌شود. اگر نیازی به پشتیبانی از NET 4x. ندارید، می‌توانید یا از netstandard2.1 و یا از net5.0 استفاده کنید و اگر در این حالت نیازی به پشتیبانی از NET Core 3x. ندارید، می‌توانید مستقیما با net5.0 شروع کنید.
بنابراین به صورت خلاصه:
- netstandard2.0 امکان اشتراک کدها را بین NET 4x. و سایر سکوهای کاری میسر می‌کند.
- netstandard2.1 امکان اشتراک کدها را بین Mono ،Xamarin و NET Core 3x. میسر می‌کند.
- net5.0، مخصوص نگارش فعلی و آینده‌ی دات نت است.

و یا حتی می‌توانید یک کتابخانه را به صورت multi-targeting با پشتیبانی از تمام TFMهای یاد شده‌ی فوق نیز تولید کنید:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net5.0;netstandard2.1;netstandard2.0;net461</TargetFrameworks>
  </PropertyGroup>
</Project>
مطالب
گوش دادن به تغییرات تم ویندوز 10 بدون نیاز به WinRT در سی شارپ
امروز میخواستم برای یکی از پروژه‌هایم، قابلیتی را پیاده سازی کنم که هماهنگ با تم ویندوز، تم برنامه را عوض کند (تیره/روشن). به این منظور که وقتی تم ویندوز Dark می‌شد، تم برنامه‌ی من هم Dark بشود و برعکس. ساده‌ترین کار این بود که از کدهای WinRT که توسط بسته‌ی نیوگت SDK Contract ارائه میشود استفاده کرد. در این صورت کافیست فقط از کلاس ThemeManager استفاده کنیم و بدون کوچکترین خونریزی، برنامه را به این ویژگی مجهز کنیم😁 اما خب، هرچیزی هزینه‌ی خودش را دارد و من به شخصه علاقه‌ای به استفاده از 25 مگابایت، فقط برای شناسایی وضعیت تم ویندوز را ندارم! پس خودم دست به کار شدم تا یک Listener برای این منظور بنویسم.
در دات نت یکسری رخ‌داد وجود دارند که مربوط به سیستم عامل میشوند و از کلاس SystemEvents قابل دسترسی هستند. در اینجا ایونتی داریم به اسم UserPreferenceChanged که شامل مواردی میشود که کاربر، تنظیمات ویندوز را تغییر می‌دهد. هر تغییری که در تنظیمات ویندوز اعمال بشود، درون یکی از Category‌ها صدا زده میشود. پس اگر ما این ایونت را رجیستر کنیم، هر موقع تغییری در تنظیمات ویندوز اعمال بشود، برنامه‌ی ما نیز متوجه میشود.
برای این منظور یک کلاس را ایجاد می‌کنیم و در متد سازنده‌ی آن، این رخ‌داد را ثبت میکنیم:
pubic class ThemeHelper
{
    public ThemeHelper()
    {
        SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
    }

    private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
    {
        switch (e.Category)
        {
            case UserPreferenceCategory.Accessibility:
                break;
            case UserPreferenceCategory.Color:
                break;
            case UserPreferenceCategory.Desktop:
                break;
            case UserPreferenceCategory.General:
                break;
            case UserPreferenceCategory.Icon:
                break;
            case UserPreferenceCategory.Keyboard:
                break;
            case UserPreferenceCategory.Menu:
                break;
            case UserPreferenceCategory.Mouse:
                break;
            case UserPreferenceCategory.Policy:
                break;
            case UserPreferenceCategory.Power:
                break;
            case UserPreferenceCategory.Screensaver:
                break;
            case UserPreferenceCategory.Window:
                break;
            case UserPreferenceCategory.Locale:
                break;
            case UserPreferenceCategory.VisualStyle:
                break;
        }
    }
}
 همینطور که می‌بینید، این دسته بندی شامل موارد مختلفی میشود که به بخش‌های مختلف تنظیمات ویندوز مربوط است. تنظیمات مربوط به تم، درون General صدا زده میشود. پس کدهای ما قرار است وارد این قسمت بشود. متاسفانه این رخ‌داد اطلاعات کاملتری را به ما نمی‌دهد و فقط اطلاع می‌دهد که تغییری رخ داده (همینطور که قبلا گفتم، هرچیزی هزینه‌ای دارد). پس ما باید مشخصات تم فعلی را دریافت کنیم؛ اما از کجا؟ معلوم است رجیستری! همه چیز در رجیستری ثبت میشود و به‌راحتی قابل دسترسی هست. پس یک متد می‌نویسیم که کلید رجیستری مربوطه را بخواند و آن را بصورت یک مدل (Light یا Dark) برگرداند:   
    private const string RegistryKeyPathTheme = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
    private const string RegSysMode = "SystemUsesLightTheme";

    public static UITheme GetWindowsTheme()
    {
        return GetThemeFromRegistry(RegSysMode);
    }

    private static UITheme GetThemeFromRegistry(string registryKey)
    {
        using var key = Registry.CurrentUser.OpenSubKey(RegistryKeyPathTheme);
        var themeValue = key?.GetValue(registryKey) as int?;
        return themeValue != 0 ? UITheme.Light : UITheme.Dark;
    }
    
    public enum UITheme
    {
        Light,
        Dark
    }
حالا ما نیاز به یک رخ‌داد داریم که کاربر بتواند در برنامه‌ی خودش آن‌را ثبت کند و نیازی به پیاده سازی این کدها نداشته باشد. پس یک EventHandler را به اسم WindowsThemeChanged ایجاد میکنیم: 
    public event EventHandler<FunctionEventArgs<UIWindowTheme>> WindowsThemeChanged;
    
    protected virtual void OnWindowsThemeChanged(UIWindowTheme theme)
    {
        EventHandler<FunctionEventArgs<UIWindowTheme>> handler = WindowsThemeChanged;
        handler?.Invoke(this, new FunctionEventArgs<UIWindowTheme>(theme));
    }
من میخواهم که کاربر، مقدار تم فعلی و رنگ Accent فعلی را نیز بتواند از طریق این رخ‌داد، دریافت کند. پس ما باید یک EventArgs را ایجاد کنیم که پراپرتی‌های دلخواهی را داشته باشد. برای همین کلاس FunctionEventArgs را ایجاد میکنیم:
public class FunctionEventArgs<T> : RoutedEventArgs
{
    public FunctionEventArgs(T theme)
    {
        Theme = theme;
    }
    public FunctionEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }
    public T Theme { get; set; }
}
این کلاس از RoutedEventArgs ارث بری کرده و بصورت جنریک پیاده سازی شده‌است. به این معنا که ما میتوانیم هر نوع دلخواهی را که خواستیم، به عنوان arg استفاده کنیم. اگر دقت کنید من یک مدل دلخواه را به عنوان arg مشخص کرده‌ام:
FunctionEventArgs<UIWindowTheme>
میتوانستیم از همان UITheme هم استفاده کنیم؛ ولی نمی‌توانستیم مقدار Accent را به کاربر برگردانیم. برای همین، مدلی را به اسم UIWindowTheme ایجاد میکنیم:
public class UIWindowTheme
{
    public Brush AccentBrush { get; set; }
    public UITheme CurrentTheme { get; set; }
}
کار تمام است. حالا باید کدهای داخل متد SystemEvents_UserPreferenceChanged را بنویسیم (دقت کنید که کد، باید داخل بخش General نوشته شود):
case UserPreferenceCategory.General:
   var changedTheme = new UIWindowTheme()
   {
         AccentBrush = SystemParameters.WindowGlassBrush,
         CurrentTheme = GetWindowsTheme()
   };
   OnWindowsThemeChanged(changedTheme);
break;
یک مدل را ایجاد کرده و مقدار AccentBrush را برابر با WindowGlassBrush قرار می‌دهیم. این پراپرتی هم مانند ایونتی که اول معرفی کردیم، مربوط به سیستم عامل بوده و رنگ فعلی Accent را بر می‌گرداند. برای مقدار CurrentTheme نیز متدی را که بالاتر برای دریافت تم فعلی از رجیستری نوشتیم، صدا می‌زنیم و در پایان این مدل را به ایونت، پاس می‌دهیم. در پایان می‌توانیم به این صورت ایونت خود را پیاده سازی کنیم:
    ThemeHelper tm = new ThemeHelper();
    tm.WindowsThemeChanged +=OnWindowsThemeChanged;

    private void OnWindowsThemeChanged(object? sender, FunctionEventArgs<RegistryThemeHelper.UIWindowTheme> e)
    {
        rec.Fill = e.Theme.AccentBrush;
        if (e.Theme.CurrentTheme == ThemeHelper.UITheme.Light)
        {
            Background = Brushes.White;
        }
        else
        {
            Background = Brushes.Black;
        }
    }