مطالب
مونیتور کردن وضعیت یک سایت در دات نت

فرض کنید می‌خواهیم وضعیت یک سایت را از لحاظ قابلیت دسترسی مونیتور کنیم، آیا Up است، Down است و امثال آن. یک سری از وب سرورها ping را بسته‌اند (ICMP Replies). بنابراین الزاما با استفاده از این روش ساده نمی‌توان به مقصود رسید.
خوشبختانه انجام این‌کار با استفاده از فضای نام استاندارد System.Net و کلاس HttpWebRequest ، بدون نیاز به هیچگونه کلاس یا کامپوننت خارجی، به سادگی قابل انجام است. کلاس زیر به همین منظور تهیه شده است:

using System;
using System.Net;

public class CSiteMonitor
{
public struct UrlHeaderInfo
{
public DateTime _lastModified;
public string _statusCode;
public string _errorMessage;
}

/// <summary>
/// آیا آدرس اینترنتی وارد شده معتبر است؟
/// </summary>
/// <param name="url">آدرس مورد نظر جهت بررسی</param>
/// <returns></returns>
public static bool IsValidURL(string url)
{
try
{
Uri uri = new Uri(url);
return (uri.Scheme == Uri.UriSchemeHttp) || (uri.Scheme == Uri.UriSchemeHttps);
}
catch { return false; }
}
/// <summary>
/// آدرس اینترنتی جهت بررسی
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">آدرس اینترنتی وارد شده معتبر نیست</exception>
public static UrlHeaderInfo GetSiteHeaderInfo(string url)
{
if (!IsValidURL(url))
throw new ArgumentException("آدرس اینترنتی وارد شده معتبر نیست", "url");

UrlHeaderInfo hhi = new UrlHeaderInfo { _lastModified = DateTime.Now, _statusCode = "NOK!", _errorMessage = string.Empty };

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
//request.Proxy
request.Method = "HEAD";
request.AllowAutoRedirect = true;
request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.0; ; rv:1.8.0.7) Gecko/20060917 Firefox/1.9.0.1";
request.Timeout = 1000 * 300;
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

try
{
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
hhi._statusCode = response.StatusCode.ToString();
hhi._lastModified = response.LastModified;
}
}
catch (Exception ex)
{
hhi._errorMessage = ex.Message;
}

return hhi;
}
}

توضیحات:
- در متد GetSiteHeaderInfo نیاز بود تا از یک تابع بیش از یک خروجی داشته باشیم. راه‌های زیاد برای انجام این‌کار هست.برای مثال:
الف)ارائه خروجی‌ها به صورت یک آرایه. زیاد جالب نیست، چون اگر شخصی دقیقا مستندات متد شما را مطالعه نکند نمی‌داند که ترتیب خروجی‌ها چگونه است و هر کدام چه معنایی دارند.
ب)ارائه خروجی‌ها با استفاده از آرگومان‌هایی از نوع out یا ref . در دنیای شیء گرایی این نوع روش‌ها را باید منسوخ شده در نظر گرفت و صرف سازگاری با زبان‌هایی مانند C که این روش در آن‌ها رواج دارد (استفاده از آرگومان‌هایی از نوع اشاره‌گر) باید به آن نگاه کرد و نه بیشتر.
ج)خروجی‌ها را به صورت یک کلاس یا struct درنظر گرفت تا استفاده کننده دقیقا بداند که فیلد خروجی چه معنایی دارد و هم چنین دقیقا چه تعداد خروجی مد نظر است.

- حتما باید از try/finally جهت اطمینان حاصل نمودن از بسته شدن response استفاده شود، در غیر اینصورت پس از دو خطای متوالی حاصل شده عملا دیگر نمی‌توان از شیء response استفاده کرد. البته همانطور که پیش تر نیز ذکر شد، عبارت using توسط کامپایلر به try/finally بست داده می‌شود، بنابراین جهت خوانایی بیشتر کد بهتر است از این روش استفاده شود.
- جهت بلاک نشدن درخواست بهتر است از یک UserAgent کمک گرفته شود.
- جهت بررسی اعتبار یک آدرس اینترنتی یا می‌توان از Regular expressions استفاده کرد یا از شیء Uri که روش آن‌را ملاحظه می‌کنید.
- اگر در شبکه داخلی خود از پروکسی استفاده می‌شود، می‌توان قسمت request.Proxy را با شیء پروکسی تنظیم شده مطابق مشخصات پروکسی سرور خود، بکار برد.
- در این مثال بیشتر هدف پیاده سازی کلاس دریافت اطلاعات هدر سایت بود و از ارائه کدهای مربوط به تایمر یا یک ترد جهت بررسی متوالی وضعیت سایت صرفنظر شد.

مثالی در مورد نحوه‌ی استفاده از کلاس فوق:

CSiteMonitor.UrlHeaderInfo info = CSiteMonitor.GetSiteHeaderInfo("http://www.google.com");
MessageBox.Show(info._statusCode);

نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت دوم
البته نگفتید که منظورتون desktop یا وب هست، با این فرض که در مورد desktop می‌پرسید، یکی از روش‌ها اینه که یک متغیر عمومی تعریف کنید که ارجاعی به فرمی که قرار هست آپدیت باشه داشته باشه:
using System.Windows.Forms;

public static class GlobalData
{
    public static Form ScheduleForm { get; set; }
}
در سازنده‌ی فرم می‌تونید اون رو به فرم جاری مقداردهی کنید:
GlobalData.ScheduleForm = this;
با این فرض که قرار هست عنوان یک دکمه در فرم با نام myButton به My Text تغییر کنه، کلاس پیاده ساز اینترفیس IJob به صورت زیر خواهد بود.
namespace SchedulerDemo.Jobs
{
    using System.Linq;
    using System.Windows.Forms;
    using Quartz;

    public class HelloJob : IJob
    {
        private delegate void ButtonTextWriter(string buttonId, string text);
        
        MainForm form = GlobalData.ScheduleForm as MainForm;

        private void SetButtonText(string buttonId, string text)
        {
            (form.Controls.Find(buttonId, true).FirstOrDefault() as Button).Text = text;
        }

        public void Execute(IJobExecutionContext context)
        {
            form.BeginInvoke(new ButtonTextWriter(SetButtonText), new object[] { "myButton", "My Text" });
        }
    }
}

مطالب
استفاده از JSON.NET در ASP.NET MVC
تا نگارش فعلی ASP.NET MVC، یعنی نگارش 5 آن، به صورت توکار از JavaScriptSerializer برای پردازش JSON کمک گرفته می‌شود. این کلاس نسبت به JSON.NET هم کندتر است و هم قابلیت سفارشی سازی آنچنانی ندارد. برای مثال مشکل Self referencing loop را نمی‌تواند مدیریت کند.
برای استفاده از JSON.NET در یک اکشن متد، به صورت معمولی می‌توان به نحو ذیل عمل کرد:
        [HttpGet]
        public ActionResult GetSimpleJsonData()
        {
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(new { id = 1 }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
در اینجا با استفاده از متد JsonConvert.SerializeObject، اطلاعات شیء مدنظر تبدیل به یک رشته شده و سپس با content type مناسبی در اختیار مصرف کننده قرار می‌گیرد.
اگر بخواهیم این عملیات را کمی بهینه‌تر کنیم، نیاز است بتوانیم از استریم‌ها استفاده کرده و خروجی JSON را بدون تبدیل به رشته، مستقیما در استریم response.Output بنویسیم. با اینکار به سرعت بیشتر و همچنین مصرف منابع کمتری خواهیم رسید.
نمونه‌ای از این پیاده سازی را در ذیل مشاهده می‌کنید:
using System;
using System.Web.Mvc;
using Newtonsoft.Json;

namespace MvcJsonNetTests.Utils
{
    public class JsonNetResult : JsonResult
    {
        public JsonNetResult()
        {
            Settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Error };
        }

        public JsonSerializerSettings Settings { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
                string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("To allow GET requests, set JsonRequestBehavior to AllowGet.");
            }

            if (this.Data == null)
                return;

            var response = context.HttpContext.Response;
            response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType;

            if (this.ContentEncoding != null)
                response.ContentEncoding = this.ContentEncoding;

            var serializer = JsonSerializer.Create(this.Settings);
            using (var writer = new JsonTextWriter(response.Output))
            {
                serializer.Serialize(writer, Data);
                writer.Flush();
            }
        }
    }
}
اگر دقت کنید، کار با ارث بری از JsonResult توکار ASP.NET MVC شروع شده‌است. کدهای ابتدای متد ExecuteResult با کدهای اصلی JsonResult یکی هستند. فقط انتهای کار بجای استفاده از JavaScriptSerializer، از JSON.NET استفاده شده‌است.
در این حالت برای استفاده از این Action Result جدید می‌توان نوشت:
        [HttpGet]
        public ActionResult GetJsonData()
        {
            return new JsonNetResult
            {
                Data = new
                {
                    Id = 1,
                    Name = "Test 1"
                },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                Settings = { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }
            };
        }
طراحی آن با توجه به ارث بری از JsonResult اصلی، مشابه نمونه‌ای است که هم اکنون از آن استفاده می‌کنید. فقط اینبار قابلیت تنظیم Settings پیشرفته‌ای نیز به آن اضافه شده‌است.


تا اینجا قسمت ارسال اطلاعات از سمت سرور به سمت کاربر بازنویسی شد. امکان بازنویسی و تعویض موتور پردازش JSON دریافتی از سمت کاربر، در سمت سرور نیز وجود دارد. خود ASP.NET MVC به صورت استاندارد توسط کلاسی به نام JsonValueProviderFactory، اطلاعات اشیاء JSON دریافتی از سمت کاربر را پردازش می‌کند. در اینجا نیز اگر دقت کنید از کلاس JavaScriptSerializer استفاده شده‌است.
برای جایگزینی آن باید یک ValueProvider جدید را تهیه کنیم:
using System;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.Web.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace MvcJsonNetTests.Utils
{
    public class JsonNetValueProviderFactory : ValueProviderFactory
    {
        public override IValueProvider GetValueProvider(ControllerContext controllerContext)
        {
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext");

            if (controllerContext.HttpContext == null ||
                controllerContext.HttpContext.Request == null ||
                controllerContext.HttpContext.Request.ContentType == null)
            {
                return null;
            }

            if (!controllerContext.HttpContext.Request.ContentType.StartsWith(
                    "application/json", StringComparison.OrdinalIgnoreCase))
            {
                return null;
            }
            
            using (var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
            {
                var bodyText = reader.ReadToEnd();
                return string.IsNullOrEmpty(bodyText)
                    ? null
                    : new DictionaryValueProvider<object>(
                        JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new JsonSerializerSettings
                        {
                            Converters = { new ExpandoObjectConverter() }
                        }),
                        CultureInfo.CurrentCulture);
            }
        }
    }
}
در اینجا ابتدا بررسی می‌شود که آیا اطلاعات دریافتی دارای هدر application/json است یا خیر. اگر خیر، توسط این کلاس پردازش نخواهند شد.
در ادامه، اطلاعات JSON دریافتی به شکل یک رشته‌ی خام دریافت شده و سپس به متد JsonConvert.DeserializeObject ارسال می‌شود. با استفاده از تنظیم ExpandoObjectConverter، می‌توان محدودیت کلاس JavaScriptSerializer را در مورد خواص و یا پارامترهای dynamic، برطرف کرد.
   [HttpPost]
  public ActionResult TestValueProvider(string data1, dynamic data2)
برای مثال اینبار می‌توان اطلاعات دریافتی را همانند امضای متد فوق، به یک پارامتر از نوع dynamic، بدون مشکل نگاشت کرد.

و در آخر برای معرفی این ValueProvider جدید می‌توان در فایل Global.asax.cs به نحو ذیل عمل نمود:
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using MvcJsonNetTests.Utils;

namespace MvcJsonNetTests
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            ValueProviderFactories.Factories.Remove(
                ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
            ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory());
        }
    }
}
ابتدا نمونه‌ی قدیمی آن یعنی JsonValueProviderFactory حذف می‌شود و سپس نمونه‌ی جدیدی که از JSON.NET استفاده می‌کند، معرفی خواهد شد.


البته نگارش بعدی ASP.NET MVC موتور پردازشی JSON خود را از طریق تزریق وابستگی‌ها دریافت می‌کند و از همان ابتدای کار قابل تنظیم و تعویض است. مقدار پیش فرض آن نیز به JSON.NET تنظیم شده‌است.


دریافت یک مثال کامل
MvcJsonNetTests.zip

مطالب
تنظیمات کش توزیع شده‌ی مبتنی بر SQL Server در ASP.NET Core
ASP.NET Core به همراه زیر ساختی‌است جهت خارج کردن امکانات Caching درون حافظه‌ای آن از سرور جاری و انتقال آن به سرورهای دیگر جهت کاهش بار سرور و برنامه. این کش توزیع شده را می‌توان به عنوان زیرساختی برای مدیریت سشن‌ها، مدیریت اطلاعات کش و همچنین مدیریت کوکی‌های حجیم ASP.NET Core Identity نیز بکار گرفت. برای مثال بجای ارسال یک کوکی حجیم بالای 5 کیلوبایت به کلاینت، فقط ID رمزنگاری شده‌ی آن‌را ارسال کرد و اصل کوکی را در داخل دیتابیس ذخیره و بازیابی نمود. این مساله هم مقیاس پذیری برنامه را افزایش خواهد داد و هم امنیت آن‌را با عدم ارسال اصل محتوای کوکی به سمت کلاینت‌ها و یا ذخیره سازی اطلاعات سشن‌ها در بانک اطلاعاتی، مشکلات راه اندازی مجدد برنامه را به طور کامل برطرف می‌کنند و در این حالت بازیابی Application pool و یا کرش برنامه و یا ری استارت شدن کل سرور، سبب از بین رفتن سشن‌های کاربران نخواهند شد. بنابراین آشنایی با نحوه‌ی راه اندازی این امکانات، حداقل از بعد امنیتی بسیار مفید هستند؛ حتی اگر سرور ذخیره کننده‌ی این اطلاعات، همان سرور و بانک اطلاعاتی اصلی برنامه باشند.


پیشنیازهای کار با کش توزیع شده‌ی مبتنی بر SQL Server

برای کار با کش توزیع شده‌ی با قابلیت ذخیره سازی در یک بانک اطلاعاتی SQL Server، نیاز است دو بسته‌ی ذیل را به فایل project.json برنامه اضافه کرد:
{
    "dependencies": {
        "Microsoft.Extensions.Caching.SqlServer": "1.1.0"
    },
    "tools": {
        "Microsoft.Extensions.Caching.SqlConfig.Tools": "1.1.0-preview4-final"
    }
}
وابستگی که در قسمت dependencies ذکر شده‌است، کلاس‌های اصلی کار با کش توزیع شده را به برنامه اضافه می‌کند. ذکر وابستگی قسمت tools، اختیاری است و کار آن، ایجاد جدول مورد نیاز برای ذخیره سازی اطلاعات، در یک بانک اطلاعاتی SQL Server می‌باشد.


ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار sql-cache

پس از افزودن و بازیابی ارجاعات فوق، با استفاده از خط فرمان، به پوشه‌ی جاری برنامه وارد شده و دستور ذیل را صادر کنید:
 dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;" "dbo" "AppSqlCache"
توضیحات:
- در اینجا می‌توان هر نوع رشته‌ی اتصالی معتبری را به انواع و اقسام بانک‌های SQL Server ذکر کرد. برای نمونه در مثال فوق این رشته‌ی اتصالی به یک بانک اطلاعاتی از پیش ایجاد شده‌ی LocalDB اشاره می‌کند. نام دلخواه این بانک اطلاعاتی در اینجا sql_cache ذکر گردیده و نام دلخواه جدولی که قرار است این اطلاعات را ثبت کند AppSqlCache تنظیم شده‌است و dbo، نام اسکیمای جدول است:


در اینجا تصویر ساختار جدولی را که توسط ابزار dotnet sql-cache ایجاد شده‌است، مشاهده می‌کنید. اگر خواستید این جدول را خودتان دستی ایجاد کنید، یک چنین کوئری را باید بر روی دیتابیس مدنظرتان اجرا نمائید:
CREATE TABLE AppSqlCache (
    Id                         NVARCHAR (449)  COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
    Value                      VARBINARY (MAX) NOT NULL,
    ExpiresAtTime              DATETIMEOFFSET  NOT NULL,
    SlidingExpirationInSeconds BIGINT          NULL,
    AbsoluteExpiration         DATETIMEOFFSET  NULL,
    CONSTRAINT pk_Id PRIMARY KEY (Id)
);

CREATE NONCLUSTERED INDEX Index_ExpiresAtTime
    ON AppSqlCache(ExpiresAtTime);


ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار Migrations در EF Core

زیر ساخت کش توزیع شده‌ی مبتنی بر SQL Server هیچگونه وابستگی به EF Core ندارد و تمام اجزای آن توسط Async ADO.NET نوشته شده‌اند. اما اگر خواستید قسمت ایجاد جدول مورد نیاز آن‌را به ابزار Migrations در EF Core واگذار کنید، روش کار به صورت زیر است:
- ابتدا یک کلاس دلخواه جدید را با محتوای ذیل ایجاد کنید:
    public class AppSqlCache
    {
        public string Id { get; set; }
        public byte[] Value { get; set; }
        public DateTimeOffset ExpiresAtTime { get; set; }
        public long? SlidingExpirationInSeconds { get; set; }
        public DateTimeOffset? AbsoluteExpiration { get; set; }
    }
- سپس تنظیمات ایجاد جدول متناظر با آن را به نحو ذیل تنظیم نمائید:
    public class MyDBDataContext : DbContext
    {
        public virtual DbSet<AppSqlCache> AppSqlCache { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<AppSqlCache>(entity =>
            {
                entity.ToTable(name: "AppSqlCache", schema: "dbo");
                entity.HasIndex(e => e.ExpiresAtTime).HasName("Index_ExpiresAtTime");
                entity.Property(e => e.Id).HasMaxLength(449);
                entity.Property(e => e.Value).IsRequired();
            });
        }
    }
به این ترتیب این جدول جدید به صورت خودکار در کنار سایر جداول برنامه ایجاد خواهند شد.
البته این مورد به شرطی است که بخواهید از یک دیتابیس، هم برای برنامه و هم برای ذخیره سازی اطلاعات کش استفاده کنید.


معرفی تنظیمات رشته‌ی اتصالی و نام جدول ذخیره سازی اطلاعات کش به برنامه

پس از ایجاد جدول مورد نیاز جهت ذخیره سازی اطلاعات کش، اکنون نیاز است این اطلاعات را به برنامه معرفی کرد. برای این منظور به کلاس آغازین برنامه مراجعه کرده و متد الحاقی AddDistributedSqlServerCache را بر روی مجموعه‌ی سرویس‌های موجود فراخوانی کنید؛ تا سرویس‌های این کش توزیع شده نیز به برنامه معرفی شوند:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;";
        options.SchemaName = "dbo";
        options.TableName = "AppSqlCache";
    });
باتوجه به توزیع شده بودن این کش، هیچ الزامی ندارد که ConnectionString ذکر شده‌ی در اینجا با رشته‌ی اتصالی مورد استفاده‌ی توسط EF Core یکی باشد (هرچند مشکلی هم ندارد).


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

سشن‌ها را همانند نکات ذکر شده‌ی در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 16 - کار با Sessions» فعال کنید و سپس مقداری را در آن بنویسید:
public IActionResult Index()
{
   HttpContext.Session.SetString("User", "VahidN");
   return Json(true);
}

public IActionResult About()
{
   var userContent = HttpContext.Session.GetString("User");
   return Json(userContent);
}
اکنون از جدول AppSqlCache کوئری بگیرید:


همانطور که مشاهده می‌کنید، سیستم سشن اینبار بجای حافظه، به صورت خودکار از جدول بانک اطلاعاتی SQL Server تنظیم شده‌، برای ذخیره سازی اطلاعات خود استفاده کرده‌است.


کار با کش توزیع شده از طریق برنامه نویسی

همانطور که در مقدمه‌ی بحث نیز عنوان شد، استفاده‌ی از زیر ساخت کش توزیع شده منحصر به استفاده‌ی از آن جهت ذخیره سازی اطلاعات سشن‌ها نیست و از آن می‌توان جهت انواع و اقسام سناریوهای مختلف مورد نیاز استفاده کرد. در این حالت روش دسترسی به این زیر ساخت، از طریق اینترفیس IDistributedCache است. زمانیکه متد AddDistributedSqlServerCache را فراخوانی می‌کنیم، در حقیقت کار ثبت یک چنین سرویسی به صورت خودکار انجام خواهد شد:
 services.Add(ServiceDescriptor.Singleton<IDistributedCache, SqlServerCache>());
به عبارتی کلاس SqlServerCache به صورت singleton به مجموعه‌ی سرویس‌های برنامه اضافه شده‌است و برای دسترسی به آن تنها کافی است اینترفیس IDistributedCache را به کنترلرها و یا سرویس‌های برنامه تزریق و از امکانات آن استفاده کنیم.

در اینجا یک نمونه از این تزریق وابستگی و سپس استفاده‌ی از متدهای Set و Get اینترفیس IDistributedCache را مشاهده می‌کنید:
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class CacheTestController : Controller
    {
        readonly IDistributedCache _cache;
        public CacheTestController(IDistributedCache cache)
        {
            _cache = cache;
        }
 
        public IActionResult SetCacheData()
        {
            var time = DateTime.Now.ToLocalTime().ToString();
            var cacheOptions = new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTime.Now.AddYears(1)
 
            };
            _cache.Set("Time", Encoding.UTF8.GetBytes(time), cacheOptions);
            return View();
        }
 
        public IActionResult GetCacheData()
        {
            var time = Encoding.UTF8.GetString(_cache.Get("Time"));
            ViewBag.data = time;
            return View();
        }
 
        public bool RemoveCacheData()
        {
            _cache.Remove("Time");
            return true;
        }
    }
}
در ابتدای بحث که ساختار جدول ذخیره سازی اطلاعات کش را بررسی کردیم، فیلد value آن یک چنین نوعی را دارد:
  Value  VARBINARY (MAX) NOT NULL,
که در سمت کدهای دات نتی، به شکل آرایه‌ای از بایت‌ها قابل بیان است.
  public byte[] Value { get; set; }
به همین جهت متد Set آن مقدار مدنظر را به صورت آرایه‌ای از بایت‌ها قبول می‌کند.
در این حالت اگر برنامه را اجرا و مسیر http://localhost:7742/CacheTest/SetCacheData را فراخوانی کنیم، اطلاعات ذخیره شده‌ی با کلید Test را می‌توان در بانک اطلاعاتی مشاهده کرد:



Tag helper مخصوص کش توزیع شده

در ASP.NET Core، می‌توان از یک Tag Helper جدید به نام distributed-cache برای کش سمت سرور توزیع شده‌ی محتوای قسمتی از یک View به نحو ذیل استفاده کرد:
<distributed-cache name="MyCacheItem2" expires-sliding="TimeSpan.FromMinutes(30)">
    <p>From distributed-cache</p>
    @DateTime.Now.ToString()
</distributed-cache>
که اطلاعات آن در بانک اطلاعاتی به نحو ذیل ذخیره می‌شود:


در اینجا name به صورت هش شده به صورت کلید کش مورد استفاده قرار می‌گیرد. سپس محتوای تگ distributed-cache رندر شده، تبدیل به آرایه‌ای از بایت‌ها گردیده و در بانک اطلاعاتی ذخیره می‌گردد.
ذکر name در اینجا اجباری است و باید دقت داشت که چون به عنوان کلید بازیابی کش مورد استفاده قرار خواهد گرفت، نباید به اشتباه در قسمت‌های دیگر برنامه با همین نام وارد شود. در غیر اینصورت دو قسمتی که name یکسانی داشته باشند، یک محتوا را نمایش خواهند داد.
نظرات مطالب
ساخت ربات تلگرامی با #C
1. آپدیتهای دریافتی همیشه فقط شامل Message نمیشه و ممکنه آپدیت دریافتی از نوع CallbackQuery هم باشه
2. کمی بالاتر توضیح داده شده که برای دریافت آپدیت‌های جدید باید پارامتر offset رو هم ارسال کنی. مقدار این پارامتر باید رقم بعدی Id آخرین آپدیت دریافتی باشه یعنی update_id  +1
برای طراحی ربات تلگرام هم بهتر هست که از پکیج‌های آماده استفاده بشه که بالاتر عرض کردم.
یه پروژه کنسول ایجاد کن پکیج telegram.bot رو هم از Nuget به برنامه اضافه کن و کلاس program.cs  رو به صورت زیر پیاده کن
به جای BOT_TOKEN هم توکن ربات خودت رو کپی کن و برنامه رو اجرا کن
using System.Threading.Tasks;
using Telegram.Bot;

namespace Bot.Engine.Console
{
    public class Program
    {
        Api bot;
        string botToken = "BOT_TOKEN";

        public static void Main(string[] args)
        {
            Task.Run(() => RunBot(botToken));

            System.Console.ReadLine();
        }


        /// <summary>
        /// 
        /// </summary>
        public static async Task RunBot(string botToken)
        {
            #region راه اندازی ربات

             bot = new Api(botToken);
            var me = await bot.GetMe();
            if (me != null)
            {
                System.Console.WriteLine("bot started {0}", me.Username);
            }
            else
            {
                System.Console.WriteLine("get bot failed ");
            }

          
            #endregion

            #region شروع گوش دادن به درخواست‌ها var whileCount = 0;
            var offset = 0;

            while (true)
            {
                System.Console.WriteLine("while no {0}", whileCount);

                whileCount += 1;
                try
                {
                    var updates = await bot.GetUpdatesAsync(offset);
                    var updatesCount = updates.Count();
                    System.Console.WriteLine("updates count is {0}", updatesCount);
                    System.Console.WriteLine("================================================================");

                    if (updatesCount > 0)
                    {
                        foreach (var update in updates)
                        {
                            try
                            {
                                offset = update.Id + 1;
                                if (update.Message.Text!=null)
                                {
                                    //echo msg
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, update.Message.Text);
                                }
                                else
                                {
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, "لطفا یک پیام متنی بفرستید");
                                }

                            }
                            catch (Exception ex)
                            {
                                bot.SendTextMessage(update.Message.Chat.Id, ex.ToString());
                            }
                        }
                        continue;
                    }


                }
                catch (Exception ex)
                {
                    System.Console.WriteLine("Error Msg = {0}",ex.Message);
                }

            }

            #endregion
        }

    }
}

نظرات مطالب
ایجاد کپچایی (captcha) سریع و ساده در ASP.NET MVC 5
با سلام و با تشکر؛ با اجازه بنده کد فوق رو کامل‌تر کردم و یک سری کد جدید بهش اضافه کردم و برخی بخش‌ها رو هم تغییر داده ام:
1- به جای سوال ، بنده یک عبارت رو نمایش میدم
2- ارسال دیتا از طریق کوئری استرینگ که باعث میشه سشن دیگه نیاز نباشه و از مصرف حافظه رو تا حد زیادی کاسته بشه.
البته این مورد برای سایت‌های پربازدید خیلی قابل لمس است و ممکنه روی سایت‌های معمولی تفاوت زیادی احساس نشه.
3- ارسال داده بصورت هش شده ، که این رو بنده خودم با یک کلاس دست ساز معمولی به روش TripleDes انجام داده ام که دوستان به هر روشی می‌تونن داده هاشون رو هش کنن.
4- یکم حروف رو چرخوندم و فاصله بین حروف رو هم طوری تنظیم کردم که در عرض تصویر پخش بشن (از کل عرض تصویر استفاده بشه)
* شایان ذکر است که به نظر من روش فوق در ایجاد نویز‌های دایره ای بسیار زیبا بود، چون همیشه همه جا با یک سری خط ساده نویز ایجاد می‌کنن ولی روش فوق واقعا خلاقانه و قشنگ بود :)
ساختار کنترلر ریکپچای من :
public class CaptchaController : Controller
    {
        private static readonly Brush ForeColor = Brushes.Black;
        private const string FontName = "tahoma";
        private const int FontSize = 14;
        private const int Width = 130;
        private const int Height = 35;

        [HttpGet]
        public ActionResult Image(string cc)
        {
            if (string.IsNullOrEmpty(cc) || string.IsNullOrWhiteSpace(cc))
                return null;

            var captchaData = CustomHashing.DecryptTpl(cc);

            var rand = new Random((int)DateTime.Now.Ticks);

            // image stream
            FileContentResult img = null;

            using (var mem = new MemoryStream())
            using (var bmp = new Bitmap(Width, Height))
            using (var mtrx = new Matrix())
            using (var gfx = Graphics.FromImage((Image)bmp))
            {
                gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                gfx.SmoothingMode = SmoothingMode.AntiAlias;
                gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height));

                //add noise
                int rn, xn, yn;
                var pen = new Pen(Color.Yellow);

                for (int i = 1; i < 10; i++)
                {
                    pen.Color = Color.FromArgb((rand.Next(0, 255)), (rand.Next(0, 255)), (rand.Next(0, 255)));

                    rn = rand.Next(0, (130 / 3));
                    xn = rand.Next(0, 130);
                    yn = rand.Next(0, 30);

                    gfx.DrawEllipse(pen, xn - rn, yn - rn, rn, rn);
                }

                //add chars
                #region draw pic

                float x = 1, y = 1;
                int degree = 10;

                for (int i = 0; i < captchaData.Length; i++)
                {
                    mtrx.Reset();

                    x = (float)(Width * (0.19 * i));

                    y = (float)(Height * 0.19);

                    degree = rand.Next(-25, 25);

                    if (i == 0 && degree > 20)
                    {
                        x += (FontSize + 5);
                        y -= 15;
                    }

                    mtrx.RotateAt(degree, new PointF(x, y));

                    gfx.Transform = mtrx;

                    gfx.DrawString(captchaData[i].ToString(), new Font(FontName, FontSize), ForeColor, x, y);

                    gfx.ResetTransform();
                }
                #endregion

                //render as Jpeg
                bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg);
                img = this.File(mem.GetBuffer(), "image/Jpeg");
            }

            return img;
        }
برای استفاده هم داریم :
@{
    var r = new Web.Tools.CustomRandom();
    string hash = Web.Tools.CustomHashing.EncryptTpl(r.CraeteCapchaNumericData(4));
} 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>test Index</title>
</head>
<body>
<div>

    <img src="@Url.Action("Image", "Captcha", new { cc = hash })" />

</div>
</body>
</html>
محتوای کلاس CustomRandom :
این کلاس به تعداد مورد نیاز کاراکتر عددی/عددی-حروفی می‌سازه و به شما تحویل میده
public class CustomRandom
 {
        /// <summary>
        /// ساخت یک عبارت عددی رندوم
        /// </summary>
        public string CraeteCapchaNumericData(int length)
        {
            var rnd = new Random((int) DateTime.Now.Ticks);
            var temp = new StringBuilder();

            for (var i = 0; i < length; i++)
                temp.Append(Convert.ToChar(rnd.Next(49, 58)));

            return temp.ToString();
        }

        /// <summary>
        /// ساخت یک عبارت رندوم
        /// </summary>
        public string CreateRandomName(int length)
        {
            var rnd = new Random((int) DateTime.Now.Ticks);
            var temp = new StringBuilder();
            var flag = 1;

            for (var i = 0; i < length; i++)
            {
                flag = rnd.Next(0, 15);

                if (flag < 5)
                    temp.Append(Convert.ToChar(rnd.Next(97, 123))); // lower
                else if (flag >= 5 && flag < 10)
                    temp.Append(Convert.ToChar(rnd.Next(49, 58))); // numeric
                else
                    temp.Append(Convert.ToChar(rnd.Next(65, 91))); // biger
            }

            return temp.ToString();
        } 
}
همانطور که گفتم پیاده سازی متد های DecryptTpl   و EncryptTpl  کلاس CustomHashing   رو به خود دوستان واگذار می‌کنم تا با هر الگوریتمی که دوست دارن این کار رو انجام بدن. (^)
امیدوارم کد بنده به دوستان کمک کنه.
موفق باشید
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت هفتم
در قسمت قبلی مدیریت همزمانی در بروز رسانی‌ها را بررسی کردیم. در این قسمت مرتب سازی (serialization) پراکسی‌ها در سرویس‌های WCF را بررسی خواهیم کرد.


مرتب سازی پراکسی‌ها در سرویس‌های WCF

فرض کنید یک پراکسی دینامیک (dynamic proxy) از یک کوئری دریافت کرده اید. حال می‌خواهید این پراکسی را در قالب یک آبجکت CLR سریال کنید. هنگامی که آبجکت‌های موجودیت را بصورت POCO-based پیاده سازی می‌کنید، EF بصورت خودکار یک آبجکت دینامیک مشتق شده را در زمان اجرا تولید می‌کند که dynamix proxy نام دارد. این آبجکت برای هر موجودیت POCO تولید می‌شود. این آبجکت پراکسی بسیاری از خواص مجازی (virtual) را بازنویسی می‌کند تا عملیاتی مانند ردیابی تغییرات و بارگذاری تنبل را انجام دهد.

فرض کنید مدلی مانند تصویر زیر دارید.


ما از کلاس ProxyDataContractResolver برای deserialize کردن یک آبجکت پراکسی در سمت سرور و تبدیل آن به یک آبجکت POCO روی کلاینت WCF استفاده می‌کنیم. مراحل زیر را دنبال کنید.


  • در ویژوال استودیو پروژه جدیدی از نوع WCF Service Application بسازید. یک Entity Data Model به پروژه اضافه کنید و جدول Clients را مطابق مدل مذکور ایجاد کنید.
  • کلاس POCO تولید شده توسط EF را باز کنید و کلمه کلیدی virtual را به تمام خواص اضافه کنید. این کار باعث می‌شود EF کلاس‌های پراکسی دینامیک تولید کند. کد کامل این کلاس در لیست زیر قابل مشاهده است.
public class Client
{
    public virtual int ClientId { get; set; }
    public virtual string Name { get; set; }
    public virtual string Email { get; set; }
}
نکته: بیاد داشته باشید که هرگاه مدل EDMX را تغییر می‌دهید، EF بصورت خودکار کلاس‌های موجودیت‌ها را مجددا ساخته و تغییرات مرحله قبلی را بازنویسی می‌کند. بنابراین یا باید این مراحل را هر بار تکرار کنید، یا قالب T4 مربوطه را ویرایش کنید. اگر هم از مدل Code-First استفاده می‌کنید که نیازی به این کار‌ها نخواهد بود.

  • ما نیاز داریم که DataContractSerializer از یک کلاس ProxyDataContractResolver استفاده کند تا پراکسی کلاینت را به موجودیت کلاینت برای کلاینت سرویس WCF تبدیل کند. یعنی client proxy به client entity تبدیل شود، که نهایتا در اپلیکیشن کلاینت سرویس استفاده خواهد شد. بدین منظور یک ویژگی operation behavior می‌سازیم و آن را به متد ()GetClient در سرویس الحاق می‌کنیم. برای ساختن ویژگی جدید از کدی که در لیست زیر آمده استفاده کنید. بیاد داشته باشید که کلاس ProxyDataContractResolver در فضای نام Entity Framework قرار دارد.
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Data.Objects;

namespace Recipe8
{
    public class ApplyProxyDataContractResolverAttribute : 
        Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription description,
            BindingParameterCollection parameters)
        {
        }
        public void ApplyClientBehavior(OperationDescription description,
            ClientOperation proxy)
        {
            DataContractSerializerOperationBehavior
                dataContractSerializerOperationBehavior =
                    description.Behaviors
                    .Find<DataContractSerializerOperationBehavior>();
                dataContractSerializerOperationBehavior.DataContractResolver =
                    new ProxyDataContractResolver();
        }
        public void ApplyDispatchBehavior(OperationDescription description,
            DispatchOperation dispatch)
        {
            DataContractSerializerOperationBehavior
                dataContractSerializerOperationBehavior =
                    description.Behaviors
                    .Find<DataContractSerializerOperationBehavior>();
            dataContractSerializerOperationBehavior.DataContractResolver =
                new ProxyDataContractResolver();
        }
        public void Validate(OperationDescription description)
        {
        }
    }
}
  • فایل IService1.cs را باز کنید و کد آن را مطابق لیست زیر تکمیل نمایید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    void InsertTestRecord();
    [OperationContract]
    Client GetClient();
    [OperationContract]
    void Update(Client client);
}
  • فایل IService1.svc.cs را باز کنید و پیاده سازی سرویس را مطابق لیست زیر تکمیل کنید.
public class Client
{
    [ApplyProxyDataContractResolver]
    public Client GetClient()
    {
        using (var context = new EFRecipesEntities())
        {
            context.Cofiguration.LazyLoadingEnabled = false;
            return context.Clients.Single();
        }
    }
    public void Update(Client client)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(client).State = EntityState.Modified;
            context.SaveChanges();
        }
    }
    public void InsertTestRecord()
    {
        using (var context = new EFRecipesEntities())
        {
            // delete previous test data
            context.ExecuteSqlCommand("delete from [clients]");
            // insert new test data
            context.ExecuteStoreCommand(@"insert into
                [clients](Name, Email) values ('Armin Zia','armin.zia@gmail.com')");
        }
    }
}
  • حال پروژه جدیدی از نوع Console Application به راه حل جاری اضافه کنید. این اپلیکیشن، کلاینت تست ما خواهد بود. پروژه سرویس را ارجاع کنید و کد کلاینت را مطابق لیست زیر تکمیل نمایید.
using Recipe8Client.ServiceReference1;

namespace Recipe8Client
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var serviceClient = new Service1Client())
            {
                serviceClient.InsertTestRecord();
                
                var client = serviceClient.GetClient();
                Console.WriteLine("Client is: {0} at {1}", client.Name, client.Email);
                
                client.Name = "Armin Zia";
                client.Email = "arminzia@live.com";
                serviceClient.Update(client);
                
                client = serviceClient.GetClient();
                Console.WriteLine("Client changed to: {0} at {1}", client.Name, client.Email);
            }
        }
    }
}
اگر اپلیکیشن کلاینت را اجرا کنید با خروجی زیر مواجه خواهید شد.

Client is: Armin Zia at armin.zia@gmail.com
Client changed to: Armin Zia at arminzia@live.com



شرح مثال جاری

مایکروسافت استفاده از آبجکت‌های POCO با WCF را توصیه می‌کند چرا که مرتب سازی (serialization) آبجکت موجودیت را ساده‌تر می‌کند. اما در صورتی که از آبجکت‌های POCO ای استفاده می‌کنید که changed-based notification دارند (یعنی خواص موجودیت را با virtual علامت گذاری کرده اید و کلکسیون‌های خواص پیمایشی از نوع ICollection هستند)، آنگاه EF برای موجودیت‌های بازگشتی کوئری‌ها پراکسی‌های دینامیک تولید خواهد کرد.

استفاده از پراکسی‌های دینامیک با WCF دو مشکل دارد. مشکل اول مربوط به سریال کردن پراکسی است. کلاس DataContractSerializer تنها قادر به serialize/deserialize کردن انواع شناخته شده (known types) است. در مثال جاری این یعنی موجودیت Client. اما از آنجا که EF برای موجودیت‌ها پراکسی می‌سازد، حالا باید آبجکت پراکسی را سریال کنیم، نه خود کلاس Client را. اینجا است که از DataContractResolver استفاده می‌کنیم. این کلاس می‌تواند حین سریال کردن آبجکت ها، نوعی را به نوع دیگر تبدیل کند. برای استفاده از این کلاس ما یک ویژگی سفارشی ساختیم، که پراکسی‌ها را به کلاس‌های POCO تبدیل می‌کند. سپس این ویژگی را به متد ()GetClient اضافه کردیم. این کار باعث می‌شود که پراکسی دینامیکی که توسط متد ()GetClient برای موجودیت Client تولید می‌شود، به درستی سریال شود.

مشکل دوم استفاده از پراکسی‌ها با WCF مربوط به بارگذاری تبل یا Lazy Loading می‌شود. هنگامی که DataContractSerializer موجودیت‌ها را سریال می‌کند، تمام خواص موجودیت را دستیابی خواهد کرد که منجر به اجرای lazy-loading روی خواص پیمایشی می‌شود. مسلما این رفتار را نمی‌خواهیم. برای رفع این مشکل، مشخصا قابلیت بارگذاری تنبل را خاموش یا غیرفعال کرده ایم.

مطالب
ساده سازی و بالا بردن سرعت عملیات Reflection با استفاده از Dynamic Proxy
فرض کنید یک چنین کلاسی طراحی شده‌است:
public class NestedClass
{
    private int _field2;
    public NestedClass()
    {
        _field2 = 12;
    }
}
 
public class MyClass
{
    private int _field1;
    private NestedClass _nestedClass;
 
    public MyClass()
    {
        _field1 = 1;
        _nestedClass = new NestedClass();
    }
 
    private string GetData()
    {
        return "Test";
    }
}
می‌خواهیم از طریق Reflection مقادیر فیلدها و متدهای مخفی آن‌را بخوانیم.
حالت متداول دسترسی به فیلد خصوصی آن از طریق Reflection، یک چنین شکلی را دارد:
var myClass = new MyClass();
 
var field1Obj = myClass.GetType().GetField("_field1", BindingFlags.NonPublic | BindingFlags.Instance);
if (field1Obj != null)
{
    Console.WriteLine(Convert.ToInt32(field1Obj.GetValue(myClass)));
}
و یا دسترسی به مقدار خروجی متد خصوصی آن، به نحو زیر است:
var getDataMethod = myClass.GetType().GetMethod("GetData", BindingFlags.NonPublic | BindingFlags.Instance);
if (getDataMethod != null)
{
    Console.WriteLine(getDataMethod.Invoke(myClass, null));
}
در اینجا دسترسی به مقدار فیلد مخفی NestedClass، شامل مراحل زیر است:
var nestedClassObj = myClass.GetType().GetField("_nestedClass", BindingFlags.NonPublic | BindingFlags.Instance);
if (nestedClassObj != null)
{
    var nestedClassFieldValue = nestedClassObj.GetValue(myClass);
    var field2Obj = nestedClassFieldValue.GetType()
        .GetField("_field2", BindingFlags.NonPublic | BindingFlags.Instance);
    if (field2Obj != null)
    {
        Console.WriteLine(Convert.ToInt32(field2Obj.GetValue(nestedClassFieldValue)));
    }
}
البته این مقدار کد فقط برای دسترسی به دو سطح تو در تو بود.

چقدر خوب بود اگر می‌شد بجای این همه کد، نوشت:
myClass._field1
myClass._nestedClass._field2
myClass.GetData()
نه؟!
برای این مشکل راه حلی معرفی شده‌است به نام Dynamic Proxy که در ادامه به معرفی آن خواهیم پرداخت.


معرفی Dynamic Proxy

Dynamic Proxy یکی از مفاهیم AOP است. به این معنا که توسط آن یک محصور کننده‌ی نامرئی، اطراف یک شیء تشکیل خواهد شد. از این غشای نامرئی عموما جهت مباحث ردیابی اطلاعات، مانند پروکسی‌های Entity framework، همانجایی که تشخیص می‌دهد کدام خاصیت به روز شده‌است یا خیر، استفاده می‌شود و یا این غشای نامرئی کمک می‌کند که در حین دسترسی به خاصیت یا متدی، بتوان منطق خاصی را در این بین تزریق کرد. برای مثال فرآیند تکراری logging سیستم را به این غشای نامرئی منتقل کرد و به این ترتیب می‌توان به کدهای تمیزتری رسید.
یکی دیگر از کاربردهای این محصور کننده یا غشای نامرئی، ساده سازی مباحث Reflection است که نمونه‌ای از آن در پروژه‌ی EntityFramework.Extended بکار رفته‌است.
در اینجا، کار با محصور سازی نمونه‌ای از کلاس مورد نظر با Dynamic Proxy شروع می‌شود. سپس کل عملیات Reflection فوق در همین چند سطر ذیل به نحوی کاملا عادی و طبیعی قابل انجام است:
 // Accessing a private field
dynamic myClassProxy = new DynamicProxy(myClass);
dynamic field1 = myClassProxy._field1;
Console.WriteLine((int)field1);
 
// Accessing a nested private field
dynamic field2 = myClassProxy._nestedClass._field2;
Console.WriteLine((int)field2);
 
// Accessing a private method
dynamic data = myClassProxy.GetData();
Console.WriteLine((string)data);
خروجی Dynamic Proxy از نوع dynamic دات نت 4 است. پس از آن می‌توان در اینجا هر نوع خاصیت یا متد دلخواهی را به شکل dynamic تعریف کرد و سپس به مقادیر آن‌ها دسترسی داشت.

بنابراین با استفاده از Dynamic Proxy فوق می‌توان به دو مهم دست یافت:
 1) ساده سازی و زیبا سازی کدهای کار با Reflection
 2) استفاده‌ی ضمنی از مباحث Fast Reflection. در کتابخانه‌ی Dynamic Proxy معرفی شده، دسترسی به خواص و متدها، توسط کدهای IL بهینه سازی شده‌است و در دفعات آتی کار با آن‌ها، دیگر شاهد سربار بالای Reflection نخواهیم بود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
DynamicProxyTests.zip
نظرات مطالب
غیرمعتبر شدن کوکی‌های برنامه‌های ASP.NET Core هاست شده‌ی در IIS پس از ری‌استارت آن
یک نکته‌ی تکمیلی: روش ذخیره سازی کلید موقتی تولید شده در بانک اطلاعاتی بجای حافظه‌ی سرور

سیستم data protection به همراه اینترفیسی است به نام IXmlRepository که از آن می‌توان برای مشخص سازی محل ذخیره سازی XML ایی اطلاعات کلید تولید شده استفاده کرد. این امکان هم وجود دارد که این اینترفیس را طوری پیاده سازی کرد تا اطلاعات را درون بانک اطلاعاتی ذخیره کند. به صورت ذیل:
ابتدا کلاس AppDataProtectionKey را به عنوان یک موجودیت جدید به سیستم EF معرفی می‌کنیم:
public class AppDataProtectionKey
{
    public int Id { get; set; }
    public string FriendlyName { get; set; }
    public string XmlData { get; set; }
}
کار این جدول، ذخیره سازی اطلاعات کلید موقتی است تا پس از ری استارت سرور، این اطلاعات از دست نروند و قابلیت بازیابی خودکار را داشته باشند.


سپس آن‌را به Context برنامه به صورت ذیل اضافه می‌کنیم:
 public virtual DbSet<AppDataProtectionKey> AppDataProtectionKeys { get; set; }
با این تنظیمات:
modelBuilder.Entity<AppDataProtectionKey>(builder =>
{
   builder.ToTable("AppDataProtectionKeys");
   builder.HasIndex(e => e.FriendlyName).IsUnique();
});

در ادامه پیاده سازی ویژه‌ی ذیل را از IXmlRepository، که از اطلاعات فوق استفاده می‌کند، تهیه خواهیم کرد:
    public class DataProtectionKeyService : IXmlRepository
    {
        private readonly IServiceProvider _serviceProvider;

        public DataProtectionKeyService(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            _serviceProvider.CheckArgumentIsNull(nameof(_serviceProvider));
        }

        public IReadOnlyCollection<XElement> GetAllElements()
        {
            return _serviceProvider.RunScopedContext<ReadOnlyCollection<XElement>>(context =>
            {
                var dataProtectionKeys = context.Set<AppDataProtectionKey>();
                return new ReadOnlyCollection<XElement>(dataProtectionKeys.Select(k => XElement.Parse(k.XmlData)).ToList());
            });
        }

        public void StoreElement(XElement element, string friendlyName)
        {
            // We need a separate context to call its SaveChanges several times,
            // without using the current request's context and changing its internal state.
            _serviceProvider.RunScopedContext(context =>
            {
                var dataProtectionKeys = context.Set<AppDataProtectionKey>();
                var entity = dataProtectionKeys.SingleOrDefault(k => k.FriendlyName == friendlyName);
                if (null != entity)
                {
                    entity.XmlData = element.ToString();
                    dataProtectionKeys.Update(entity);
                }
                else
                {
                    dataProtectionKeys.Add(new AppDataProtectionKey
                    {
                        FriendlyName = friendlyName,
                        XmlData = element.ToString()
                    });
                }
                context.SaveChanges();
            });
        }
    }
در این اینترفیس نحوه‌ی دسترسی به یک context جدید، اندکی متفاوت است از حالت‌های متداول. در اینجا چون می‌خواهیم این کلاس تاثیری را بر روی واحد کار درخواست جاری نگذارد، یک context جدید را برای آن وهله سازی می‌کنیم و از context موجود در طی طول عمر درخواست جاری استفاده نخواهیم کرد.
اطلاعات متدهای سرویس فوق به صورت خودکار توسط سیستم data-protection تامین می‌شوند. تنها کاری را که در اینجا انجام داده‌ایم، گوش فرادادن به این تغییرات و ذخیره سازی آن‌ها در بانک اطلاعاتی است.

مرحله‌ی آخر کار، معرفی این تغییرات به سیستم است که نحوه‌ی انجام آن‌را در ذیل مشاهده می‌کنید:
        private static void addCustomDataProtection(this IServiceCollection services, SiteSettings siteSettings)
        {
            services.AddScoped<IXmlRepository, DataProtectionKeyService>();
            services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(serviceProvider =>
            {
                return new ConfigureOptions<KeyManagementOptions>(options =>
                {
                    var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
                    using (var scope = scopeFactory.CreateScope())
                    {
                        options.XmlRepository = scope.ServiceProvider.GetService<IXmlRepository>();
                    }
                });
            });
            services
                .AddDataProtection()
                .SetDefaultKeyLifetime(siteSettings.CookieOptions.ExpireTimeSpan)
                .SetApplicationName(siteSettings.CookieOptions.CookieName)
                .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
                {
                    EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
                    ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
                });
        }
ابتدا محل تامین سرویس IXmlRepository مشخص شده‌است. سپس روش مقدار دهی XmlRepository  را ملاحظه می‌کنید که باید به این صورت باشد. مقدار آن نیز از سرویس DataProtectionKeyService سفارشی ما تامین می‌شود. در انتها طول عمر کلید تولید شده، نام برنامه و الگوریتم‌های مدنظر تنظیم شده‌اند.

همین مقدار تنظیم سبب خواهد شد تا به صورت خودکار اطلاعات موقتی کلیدهای رمزنگاری سیستم data-protection در بانک اطلاعاتی ذخیره شده و یا بازیابی شوند.

این تغییرات به پروژه‌ی DNTIdentity اعمال شده‌اند.
مطالب
پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش سوم (پایانی)
در بخش پیشین  به بررسی جزئی‌تر ایجاد پایگاه داده و همچنین توسعه Custom Filter Attribute پرداختیم که وظیفه تایید صلاحیت کاربر جاری و بررسی دسترسی وی به API Method مورد نظر را بررسی می‌کرد. در این مقاله به این بحث می‌پردازیم که در Filter Attribute توسعه داده شده، قصد داریم یک سرویس Access Control ایجاد نماییم.
این سرویس وظیفه تمامی اعمال مربوط به نقش‌ها و دسترسی‌های کاربر را بر عهده خواهد داشت. این سرویس به صورت زیر تعریف می‌گردد:
public class AccessControlService
{
        private DbContext db;

        public AccessControlService()
        {
            db = new DbContext();
        }

        public IEnumerable<Permission> GetUserPermissions(string userId)
        {
            var userRoles = this.GetUserRoles(userId);
            var userPermissions = new List<Permission>();
            foreach (var userRole in userRoles)
            {
                foreach (var permission in userRole.Permissions)
                {
                    // prevent duplicates
                    if (!userPermissions.Contains(permission))
                        userPermissions.Add(permission);
                }
            }
            return userPermissions;
        }
        public IEnumerable<Role> GetUserRoles(string userId)
        {
            return db.Users.FirstOrDefault(x => x.UserId == userId).Roles.ToList();
        }

        public bool HasPermission(string userId, string area, string control)
        {
            var found = false;
            var userPermissions = this.GetUserPermissions(userId);
            var permission = userPermissions.FirstOrDefault(x => x.Area == area && x.Control == control);
            if (permission != null)
                found = true;
            return found;
        }
{
همانطور که ملاحظه می‌کنید، ما سه متد GetUserPermissions، GetUserRoles و HasPermission را توسعه داده‌ایم. حال اینکه بر حسب نیاز، می‌توانید متدهای بیشتری را نیز به این سرویس اضافه نمایید. متد اول، وظیفه‌ی واکشی تمامی permissionهای کاربر را عهده دار می‌باشد. متد GetUserRoles نیز تمامی نقش‌های کاربر را در سیستم، بازمی‌گرداند و در نهایت متد سوم، همان متدی است که ما در Filterattribute از آن استفاده کرد‌ه‌ایم. این متد با دریافت پارامترها و بازگردانی یک مقدار درست یا نادرست، تعیین می‌کند که کاربر جاری به آن محدوده دسترسی دارد یا خیر.
تمامی حداقل‌هایی که برای نگارش سمت سرور نیاز بود، به پایان رسید. حال به بررسی این موضوع خواهیم پرداخت که چگونه این سطوح دسترسی را با سمت کاربر همگام نماییم. به طوری که به عنوان مثال اگر کاربری حق دسترسی به ویرایش مطالب یک سایت را ندارد، دکمه مورد نظر که او را به آن صفحه هدایت می‌کند نیز نباید به وی نشان داده شود. سناریویی که ما برای این کار در نظر گرفته‌ایم، به این گونه می‌باشد که در هنگام ورود کاربر، لیستی از تمامی دسترسی‌های او به صورت JSON به سمت کلاینت ارسال می‌گردد. حال وظیفه مدیریت نمایش یا عدم نمایش المان‌های صفحه، بر عهده زبان سمت کلاینت، یعنی AngularJs خواهد بود. بنابراین در ابتدایی‌ترین حالت، ما نیاز به یک کنترلر و متد Web API داریم تا لیست دسترسی‌های کاربر را بازگرداند. این کنترلر در حال حاضر شامل یک متد است. اما بر حسب نیاز، می‌توانید متدهای بیشتری را به کنترلر اضافه نمایید.
    [RoutePrefix("َAuth/permissions")]
    public class PermissionsController : ApiController
    {
        private AccessControlService _AccessControlService = null;
        public PermissionsController()
        {
            _AccessControlService = new AccessControlService();
        }

        [Route("GetUserPermissions")]
        public async Task<IHttpActionResult> GetUserPermissions()
        {
            if (!User.Identity.IsAuthenticated)
            {
                return Unauthorized();
            }
            return Ok(_AccessControlService.GetPermissions(User.Identity.GetCurrentUserId()));
        }        
    }
در متد فوق ما از متد سرویس Access Control که لیست تمامی permissionهای کاربر را باز می‌گرداند، کمک گرفتیم. متد GetUserPermissions پس از ورود کاربر توسط کلاینت فراخوانی می‌گردد و لیست تمامی دسترسی‌ها در سمت کلاینت، در rootScope انگیولار ذخیره سازی می‌گردد. حال نوبت به آن رسیده که به بررسی عملیات سمت کلاینت بپردازیم.

توسعه سرویس‌ها و فرآیندهای سمت وب کلاینت AngularJS

در ابتدا در سمت کلاینت نیاز به سرویسی داریم که دسترسی‌های سمت سرور را دریافت نماید. از این رو ما نام این سرویس را permissionService می‌نامیم.
'use strict';
angular.module('App').factory('permissionService', ['$http', '$q', function ($http, $q) {

    var _getUserPermissions = function () {
        return $http.get(serviceBaseUrl + '/api/permissions/GetUserPermissions/');
    }

    var _isAuthorize = function (area, control) {
        return _.some($scope.permissions, { 'area': area, 'control': control });
    }

    return {
        getUserPermissions: _getUserPermissions,
        isAuthorize: _isAuthorize
    };
}]);
اگر بخواهیم مختصری درباره‌ی این سرویس صحبت کنیم، متد اول که یک دستور GET ساده است و لیست دسترسی‌ها را از PermissionController دریافت می‌کند. متد بعدی که در آینده بیشتر با آن آشنا می‌شویم، عملیات تایید صلاحیت کاربر را به ناحیه مورد نظر، انجام می‌دهد (همان مثال دسترسی به دکمه ویرایش مطلب در یک صفحه). در این متد برای جستجوی لیست permissions از کتابخانه‌ای با نام Lodash کمک گرفته‌ایم. این کتابخانه کاری شبیه به دستورات Linq را در کالکشن‌ها و آرایه‌های جاوااسکریپتی، انجام می‌دهد. متد some یک مقدار درست یا نادرست را بازمی‌گرداند. بازگردانی مقدار درست، به این معنی است که کاربر به ناحیه‌ی مورد نظر اجازه‌ی دسترسی را خواهد داشت.
حال باید متد‌های این سرویس را در کنترلر لاگین فراخوانی نماییم. در این مرحله ما از rootScope dependency استفاده می‌کنیم. برای نحوه‌ی عملکرد rootScope میتوانید به مقاله‌ای در این زمینه در وب سایت toddomotto مراجعه کنید. در این مقاله ویژگی‌ها و اختلاف‌های scope و rootScope به تفصیل بیان شده است. مقاله‌ای دیگر در همین زمینه نوشته شده است که در انتهای مقاله به بررسی چند نکته در مورد کدهای مشترک پرداخته شده‌است. تکه کد زیر، متد login را نمایش می‌دهد که در loginController قرار گرفته است و ما در آن از نوشتن کل بلاک loginController چشم پوشی کرده‌ایم. متد savePermissions تنها یک کار را انجام می‌دهد و آن هم این است که در ابتدا، به سرویس permissionService متصل شده و تمامی دسترسی‌ها را واکشی می‌نماید و پس از آن، آنها را درون rootScope قرار می‌دهد تا در تمامی کنترلرها قابل دسترسی باشد.
    $scope.login = function () {
        authService.login($scope.loginData).then(function (response) {
            savePermissions();
            $location.path('/userPanel');
        },
         function (err) {
             $scope.message = err.error_description;
         });
    };

    var savePermissions = function () {
        permissionService.getUserPermissions().then(function (response) {
            $rootScope.permissions = response.data;
        },
        function (err) {
        });
    }
}

   حال تمامی اطلاعات دسترسی، در سمت کلاینت نیز قابل دسترسی می‌باشد. تنها کاری که نیاز است، broadCast کردن متد isAuthorize است که آن هم باید در rootScope قرار بگیرد. ما برای این انتساب یک راهکار را ارائه کرده‌ایم. معماری سیستم کلاینت به این صورت است که تمامی کنترلرها درون یک parentController قرار گرفته‌اند. از این رو می‌توان در parentController این انتساب (ایجاد دسترسی عمومی برایisAuthorize) صورت گیرد. برای این کار در parentController تغییرات زیر صورت می‌گیرد:
App.controller('parentController', ['$rootScope', '$scope', 'authService', 'permissionService', function ($rootScope, $scope, authService, permissionService) {
        $scope.authentication = authService.authentication;

        // isAuthorize Method
        $scope.isAuthorize = permissionService.isAuthorize();

        // rest of codes
}]);
در کد فوق ما isAuthorize را درون scope قرار داده‌ایم. دلیل آن هم این است که هر چه که در scope قرار بگیرد، تمامی کنترلر‌های child نیز به آن دسترسی خواهند داشت. البته ممکن است که این بهترین نوع پیاده سازی برای به اشتراک گذاری یک منبع نباشد.
 در گام بعدی کافیست المان‌های صفحه را بر اساس همین دسترسی‌ها فعال یا غیر فعال کنیم. برای این کار از دستور ng-if میتوان استفاده کرد. برای این کار به مثال زیر توجه کنید:
<div ng-controller="childController">
    <div ng-if="isAuthorize('articles', 'edit')" >
    <!-- the block that we want to not see unauthorize person -->
    </div>
</div>
همانطور که مشاهده می‌کنید، تمامی المان‌ها را می‌توان با دستور ساده ng-if، از دید کاربران بدون صلاحیت، پنهان نمود. البته توجه داشته باشید که شما نمی‌توانید تنها به پنهان کردن این اطلاعات اکتفا کنید. بلکه باید تمامی متدهای کنترلرهای سمت سرور را هم با همین روش (فیلتر کردن با Filter Attribute) بررسی نمایید. به ازای هر درخواست کاربر باید بررسی شود که او به منبع مورد نظر دسترسی دارد یا خیر.
تنها نکته‌ای که باقی می‌ماند این است که طول عمر scope و rootScope چقدر است؟! برای پاسخ به این سوال باید بگوییم هر بار که صفحه refresh می‌شود، تمامی مقادیر scope و rootScope خالی می‌شوند. برای این کار هم یک راهکار خیلی ساده (و شاید کمی ناشیانه) در نظر گرفته شده‌است. میتوان بلاک مربوط به پر کردن rootScope.permissions را که در loginController نوشته شده بود، به درون parentController انتقال داد و آن را با استفاده از emit اجرا کرد و در حالت عادی، در هنگام refresh شدن صفحه نیز چون parentController در اولین لحظه اجرا می‌شود، میتوان تمامی مقادیر rootScope.permissions را دوباره از سمت سرور دریافت کرد.