مطالب
تنظیمات JSON در ASP.NET Web API
ASP.NET Web API در سمت سرور، برای مدیریت ApiControllerها و در سمت کلاینت‌های دات نتی آن، برای مدیریت HttpClient، به صورت پیش فرض از JSON.NET استفاده می‌کند. در ادامه نگاهی خواهیم داشت به تنظیمات JSON در سرور و کلاینت‌های ASP.NET Web API.


آماده سازی یک مثال Self host

برای اینکه خروجی‌های JSON را بهتر و بدون نیاز به ابزار خاصی مشاهده کنیم، می‌توان یک پروژه‌ی کنسول جدید را آغاز کرده و سپس آن‌را تبدیل به Host مخصوص Web API کرد. برای اینکار تنها کافی است در کنسول پاور شل نیوگت دستور ذیل را صادر کنید:
 PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
سپس کنترلر Web API ما از کدهای ذیل تشکیل خواهد شد که در آن در متد Post، قصد داریم اصل محتوای دریافتی از کاربر را نمایش دهیم. توسط متد GetAll آن، خروجی نهایی JSON آن در سمت کاربر بررسی خواهد شد.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;

namespace WebApiSelfHostTests
{
    public class UsersController : ApiController
    {
        public IEnumerable<User> GetAllUsers()
        {
            return new[]
            {
                new User{ Id = 1, Name = "User 1", Type = UserType.Admin },
                new User{ Id = 2, Name = "User 2", Type = UserType.User }
            };
        }

        public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
        {
            var jsonContent = await request.Content.ReadAsStringAsync();
            Console.WriteLine("JsonContent (Server Side): {0}", jsonContent);
            return new HttpResponseMessage(HttpStatusCode.Created);
        }
    }
}
که در آن شیء کاربر چنین ساختاری را دارد:
namespace WebApiSelfHostTests
{
    public enum UserType
    {
        User,
        Admin,
        Writer
    }

    public class User
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public UserType Type { set; get; }
    }
}
برای اعمال تنظیمات self host ابتدا نیاز است یک کلاس Startup مخصوص Owin را تهیه کرد:
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Owin;

namespace WebApiSelfHostTests
{
    /// <summary>
    /// PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
    /// </summary>
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
                );

            appBuilder.UseWebApi(config);
        }
    }
}
که سپس با فراخوانی چند سطر ذیل، سبب راه اندازی سرور Web API، بدون نیاز به IIS خواهد شد:
 var server = WebApp.Start<Startup>(url: BaseAddress);


Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
server.Dispose();
در ادامه اگر در سمت کلاینت، دستورات ذیل را برای دریافت لیست کاربران صادر کنیم:
 using (var client = new HttpClient())
{
   var response = client.GetAsync(BaseAddress + "api/users").Result;
   Console.WriteLine("Response: {0}", response);
   Console.WriteLine("JsonContent (Client Side): {0}", response.Content.ReadAsStringAsync().Result);
}
به این خروجی خواهیم رسید:
 JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":1},{"Id":2,"Name":"User 2","Type":0}]
همانطور که ملاحظه می‌کنید، مقدار Type مساوی صفر است. در اینجا چون Type را به صورت enum تعریف کرده‌ایم، به صورت پیش فرض مقدار عددی عضو انتخابی در JSON نهایی درج می‌گردد.


تنظیمات JSON سمت سرور Web API

برای تغییر این خروجی، در سمت سرور تنها کافی است به کلاس Startup مراجعه و HttpConfiguration را به صورت ذیل تنظیم کنیم:
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings
            {
                Converters = { new StringEnumConverter() }
            };
در اینجا با انتخاب StringEnumConverter، سبب خواهیم شد تا کلیه مقادیر enum، دقیقا مساوی همان مقدار اصلی رشته‌ای آن‌ها در JSON نهایی درج شوند.
اینبار اگر برنامه را اجرا کنیم، چنین خروجی حاصل می‌گردد و در آن دیگر Type مساوی صفر نیست:
 JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":"Admin"},{"Id":2,"Name":"User 2","Type":"User"}]


تنظیمات JSON سمت کلاینت Web API

اکنون در سمت کلاینت قصد داریم اطلاعات یک کاربر را با فرمت JSON به سمت سرور ارسال کنیم. روش متداول آن توسط کتابخانه‌ی HttpClient، استفاده از متد PostAsJsonAsync است:
var user = new User
{
   Id = 1,
   Name = "User 1",
   Type = UserType.Writer
};

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

var response = client.PostAsJsonAsync(BaseAddress + "api/users", user).Result;
Console.WriteLine("Response: {0}", response);
با این خروجی سمت سرور
 JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":2}
در اینجا نیز Type به صورت عددی ارسال شده‌است. برای تغییر آن نیاز است به متدی با سطح پایین‌تر از PostAsJsonAsync مراجعه کنیم تا در آن بتوان JsonMediaTypeFormatter را مقدار دهی کرد:
            var jsonMediaTypeFormatter = new JsonMediaTypeFormatter
            {
                SerializerSettings = new JsonSerializerSettings
                {
                    Converters = { new StringEnumConverter() }
                }
            };
            var response = client.PostAsync(BaseAddress + "api/users", user, jsonMediaTypeFormatter).Result;
            Console.WriteLine("Response: {0}", response);
خاصیت SerializerSettings کلاس JsonMediaTypeFormatter برای اعمال تنظیمات JSON.NET پیش بینی شده‌است.
اینبار مقدار دریافتی در سمت سرور به صورت ذیل است و در آن، Type دیگر عددی نیست:
 JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":"Writer"}

مثال کامل این بحث را از اینجا می‌توانید دریافت کنید:
UsersController.zip
مطالب
آشنایی با NHibernate - قسمت هفتم

مدیریت بهینه‌ی سشن فکتوری

ساخت یک شیء SessionFactory بسیار پر هزینه و زمانبر است. به همین جهت لازم است که این شیء یکبار حین آغاز برنامه ایجاد شده و سپس در پایان کار برنامه تخریب شود. انجام اینکار در برنامه‌های معمولی ویندوزی (WinForms ،WPF و ...)، ساده است اما در محیط Stateless وب و برنامه‌های ASP.Net ، نیاز به راه حلی ویژه وجود خواهد داشت و تمرکز اصلی این مقاله حول مدیریت صحیح سشن فکتوری در برنامه‌های ASP.Net است.

برای پیاده سازی شیء سشن فکتوری به صورتی که یکبار در طول برنامه ایجاد شود و بارها مورد استفاده قرار گیرد باید از یکی از الگوهای معروف طراحی برنامه نویسی شیء گرا به نام Singleton Pattern استفاده کرد. پیاده سازی نمونه‌ی thread safe آن که در برنامه‌های ذاتا چند ریسمانی وب و همچنین برنامه‌های معمولی ویندوزی می‌تواند مورد استفاده قرار گیرد، در آدرس ذیل قابل مشاهده است:



از پنجمین روش ذکر شده در این مقاله جهت ایجاد یک lazy, lock-free, thread-safe singleton استفاده خواهیم کرد.

بررسی مدل برنامه

در این مدل ساده ما یک یا چند پارکینگ داریم که در هر پارکینگ یک یا چند خودرو می‌توانند پارک شوند.


یک برنامه ASP.Net را آغاز کرده و ارجاعاتی را به اسمبلی‌های زیر به آن اضافه نمائید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHibernate.Linq.dll
و همچنین ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

تصویر نهایی پروژه ما به شکل زیر خواهد بود:



پروژه ما دارای یک پوشه domain ، تعریف کننده موجودیت‌های برنامه جهت تهیه نگاشت‌های لازم از روی ‌آن‌ها است. سپس یک پوشه جدید را به نام NHSessionManager به آن جهت ایجاد یک Http module مدیریت کننده سشن‌های NHibernate در برنامه اضافه خواهیم کرد.

ساختار دومین برنامه (مطابق کلاس دیاگرام فوق):

namespace NHSample3.Domain
{
public class Car
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Color { get; set; }
}
}

using System.Collections.Generic;

namespace NHSample3.Domain
{
public class Parking
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Location { get; set; }
public virtual IList<Car> Cars { get; set; }

public Parking()
{
Cars = new List<Car>();
}
}
}
مدیریت سشن فکتوری در برنامه‌های وب

در این قسمت قصد داریم Http Module ایی را جهت مدیریت سشن‌های NHibernate ایجاد نمائیم.

در ابتدا کلاس Config را در پوشه مدیریت سشن NHibernate با محتویات زیر ایجاد کنید:

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;

namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.ExposeConfiguration(
x => x.SetProperty("current_session_context_class", "managed_web")
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample3.Domain.Car).Assembly))
);
}

public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
با این کلاس در قسمت‌های قبل آشنا شده‌اید. در این کلاس با کمک امکانات Auto mapping موجود در Fluent Nhibernate (مطلب قسمت قبلی این سری آموزشی) اقدام به تهیه نگاشت‌های خودکار از کلاس‌های قرار گرفته در پوشه دومین خود خواهیم کرد (فضای نام این پوشه به دومین ختم می‌شود که در متد GetConfig مشخص است).
دو نکته جدید در متد GetConfig وجود دارد:
الف) استفاده از متد FromConnectionStringWithKey ، بجای تعریف مستقیم کانکشن استرینگ در متد مذکور که روشی است توصیه شده. به این صورت فایل وب کانفیگ ما باید دارای تعریف کلید مشخص شده در متد GetConfig به نام DbConnectionString باشد:

<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true" />
</connectionStrings>
ب) قسمت ExposeConfiguration آن نیز جدید است.
در اینجا به AutoMapper خواهیم گفت که قصد داریم از امکانات مدیریت سشن مخصوص وب فریم ورک NHibernate استفاده کنیم. فریم ورک NHibernate دارای کلاسی است به نام NHibernate.Context.ManagedWebSessionContext که جهت مدیریت سشن‌های خود در پروژه‌های وب ASP.Net پیش بینی کرده است و از این متد در Http module ایی که ایجاد خواهیم کرد جهت ردگیری سشن جاری آن کمک خواهیم گرفت.

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



سپس کلاس SingletonCore را جهت تهیه تنها و تنها یک وهله از شیء سشن فکتوری در کل برنامه ایجاد خواهیم کرد (همانطور که عنوان شده، ایده پیاده سازی این کلاس thread safe ، از مقاله معرفی شده در ابتدای بحث گرفته شده است):

using NHibernate;

namespace NHSessionManager
{
/// <summary>
/// lazy, lock-free, thread-safe singleton
/// </summary>
public class SingletonCore
{
private readonly ISessionFactory _sessionFactory;

SingletonCore()
{
_sessionFactory = Config.GetConfig().BuildSessionFactory();
}

public static SingletonCore Instance
{
get
{
return Nested.instance;
}
}

public static ISession GetCurrentSession()
{
return Instance._sessionFactory.GetCurrentSession();
}

public static ISessionFactory SessionFactory
{
get { return Instance._sessionFactory; }
}

class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly SingletonCore instance = new SingletonCore();
}
}
}
اکنون می‌توان از این Singleton object جهت تهیه یک Http Module کمک گرفت. برای این منظور کلاس SessionModule را به برنامه اضافه کنید:

using System;
using System.Web;
using NHibernate;
using NHibernate.Context;

namespace NHSessionManager
{
public class SessionModule : IHttpModule
{
public void Dispose()
{ }

public void Init(HttpApplication context)
{
if (context == null)
throw new ArgumentNullException("context");

context.BeginRequest += Application_BeginRequest;
context.EndRequest += Application_EndRequest;
}

private void Application_BeginRequest(object sender, EventArgs e)
{
ISession session = SingletonCore.SessionFactory.OpenSession();
ManagedWebSessionContext.Bind(HttpContext.Current, session);
session.BeginTransaction();
}

private void Application_EndRequest(object sender, EventArgs e)
{
ISession session = ManagedWebSessionContext.Unbind(
HttpContext.Current, SingletonCore.SessionFactory);
if (session == null) return;

try
{
if (session.Transaction != null &&
!session.Transaction.WasCommitted &&
!session.Transaction.WasRolledBack)
{
session.Transaction.Commit();
}
else
{
session.Flush();
}
}
catch (Exception)
{
session.Transaction.Rollback();
}
finally
{
if (session != null && session.IsOpen)
{
session.Close();
session.Dispose();
}
}
}
}
}
کلاس فوق کار پیاده سازی اینترفیس IHttpModule را جهت دخالت صریح در request handling pipeline برنامه ASP.Net جاری انجام می‌دهد. در این کلاس مدیریت متدهای استاندارد Application_BeginRequest و Application_EndRequest به صورت خودکار صورت می‌گیرد.
در متد Application_BeginRequest ، در ابتدای هر درخواست یک سشن جدید ایجاد و به مدیریت سشن وب NHibernate بایند می‌شود، همچنین یک تراکنش نیز آغاز می‌گردد. سپس در پایان درخواست، این انقیاد فسخ شده و تراکنش کامل می‌شود، همچنین کار پاکسازی اشیاء نیز صورت خواهد گرفت.

با توجه به این موارد، دیگر نیازی به ذکر using جهت dispose کردن سشن جاری در کدهای ما نخواهد بود، زیرا در پایان هر درخواست اینکار به صورت خودکار صورت می‌گیرد. همچنین نیازی به ذکر تراکنش نیز نمی‌باشد، چون مدیریت آن‌را خودکار کرده‌ایم.

جهت استفاده از این Http module تهیه شده باید چند سطر زیر را به وب کانفیگ برنامه اضافه کرد:

<httpModules>
<!--NHSessionManager-->
<add name="SessionModule" type="NHSessionManager.SessionModule"/>
</httpModules>
بدیهی است اگر نخواهید از Http module استفاده کنید باید این کدها را در فایل Global.asax برنامه قرار دهید.

اکنون مثالی از نحوه‌ی استفاده از امکانات فراهم شده فوق به صورت زیر می‌تواند باشد:
ابتدا کلاس ParkingContext را جهت مدیریت مطلوب‌تر LINQ to NHibernate تشکیل می‌دهیم.

using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample3.Domain;

namespace NHSample3
{
public class ParkingContext : NHibernateContext
{
public ParkingContext(ISession session)
: base(session)
{ }

public IOrderedQueryable<Car> Cars
{
get { return Session.Linq<Car>(); }
}

public IOrderedQueryable<Parking> Parkings
{
get { return Session.Linq<Parking>(); }
}
}
}
سپس در فایل Default.aspx.cs برنامه ، برای نمونه تعدادی رکورد را افزوده و نتیجه را در یک گرید ویوو نمایش خواهیم داد:

using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHSample3.Domain;
using NHSessionManager;

namespace NHSample3
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//ایجاد دیتابیس در صورت نیاز
//Config.CreateDb();

//ثبت یک سری رکورد در دیتابیس
ISession session = SingletonCore.GetCurrentSession();

Car car1 = new Car() { Name = "رنو", Color = "مشکلی" };
session.Save(car1);
Car car2 = new Car() { Name = "پژو", Color = "سفید" };
session.Save(car2);

Parking parking1 = new Parking()
{
Location = "آدرس پارکینگ مورد نظر",
Name = "پارکینگ یک",
Cars = new List<Car> { car1, car2 }
};

session.Save(parking1);

//نمایش حاصل در یک گرید ویوو
ParkingContext db = new ParkingContext(session);
var query = from x in db.Cars select new { CarName = x.Name, CarColor = x.Color };
GridView1.DataSource = query.ToList();
GridView1.DataBind();
}
}
}
مدیریت سشن فکتوری در برنامه‌های غیر وب

در برنامه‌های ویندوزی مانند WinForms ، WPF و غیره، تا زمانیکه یک فرم باز باشد، کل فرم و اشیاء مرتبط با آن به یکباره تخریب نخواهند شد، اما در یک برنامه ASP.Net جهت حفظ منابع سرور در یک محیط چند کاربره، پس از پایان نمایش یک صفحه وب، اثری از آثار اشیاء تعریف شده در کدهای آن صفحه در سرور وجود نداشته و همگی بلافاصله تخریب می‌شوند. به همین جهت بحث‌های ویژه state management در ASP.Net در اینباره مطرح است و مدیریت ویژه‌ای باید روی آن صورت گیرد که در قسمت قبل مطرح شد.
از بحث فوق، تنها استفاده از کلاس‌های Config و SingletonCore ، جهت استفاده و مدیریت بهینه‌ی سشن فکتوری در برنامه‌های ویندوزی کفایت می‌کنند.

دریافت سورس برنامه قسمت هفتم

ادامه دارد ....

مطالب
توصیف فیلدها توسط Tag Helper و Data annotation

همه ما با DisplayAttribute در DataAnnotaion آشنا هستیم. چیزی شبیه زیر برای یک موجودیت:

public class Student{
    [Display(Name="نام خانوادگی")]
    public string FamilyName { get; set;}
}

با استفاده از tag helper ای به نام asp-for می‌توان متادیتای Name را به کاربر، در سمت رابط کاربری نشان داد؛ برای مثال:

<label asp-for="FamilyName"></label>

و یا موقع اعتبارسنجی می‌توان به جای نشان دادن نام FamilyName از نام مفهوم‌تری مانند نام خانوادگی استفاده نمود.

چه خوب بود اگر می‌شد علاوه بر نام، توصیفی از فیلد نیز برای آن در این قسمت وجود داشته باشد؛ به عبارت دیگر اگر کد زیر را داشتیم:

[Display(
     Name = "نام خانوادگی",
     Description = "بهتر است فقط در اینجا نام خانوادگی شخص وارد شود")]
public string FamilyName{ get; set; }

بتوان از tag helper ای مانند زیر استفاده نمود:

<span asp-description-for="FamilyName"></span>

که در نهایت چنین خروجی html ای داشته باشیم:

<span>بهتر است فقط در اینجا نام خانوادگی شخص وارد شود</span>

برای این منظور می‌توان از کلاس زیر بهره برد:

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

[HtmlTargetElement("div", Attributes = ForAttributeName)]
[HtmlTargetElement("p", Attributes = ForAttributeName)]
[HtmlTargetElement("span", Attributes = ForAttributeName)]
public sealed class DescriptionForTagHelper : TagHelper
{
    private const string ForAttributeName = "asp-description-for";

    [HtmlAttributeName(ForAttributeName)] 
    public ModelExpression For { get; set; } = default!;

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (output == null)
        {
            throw new ArgumentNullException(nameof(output));
        }

        var description = For.Metadata.Description;
        if (description != null)
        {
            // Do not update the content if another tag helper
            // targeting this element has already done so.
            if (!output.IsContentModified)
            {
                var childContent = await output.GetChildContentAsync();
                if (childContent.IsEmptyOrWhiteSpace)
                {
                    output.Content.SetHtmlContent(description);
                }
                else
                {
                    output.Content.SetHtmlContent(childContent);
                }
            }
        }
    }
}

کلاس DescriptionForTagHelper از کلاس پایه TagHelper ارث بری نموده است و متد ProcessAsync آن به نحوی که  asp-description-for را بپذیرد override شده است.

حوزه اعمال این tag helper به span، p و div محدود شده است؛ اما می‌توان با گذاشتن یک ستاره (*) آن را به کل المان‌های html اعمال کرد.

مطالب
ASP.NET MVC #13

اعتبار سنجی اطلاعات ورودی در فرم‌های ASP.NET MVC

زمانیکه شروع به دریافت اطلاعات از کاربران کردیم، نیاز خواهد بود تا اعتبار اطلاعات ورودی را نیز ارزیابی کنیم. در ASP.NET MVC، به کمک یک سری متادیتا، نحوه‌ی اعتبار سنجی، تعریف شده و سپس فریم ورک بر اساس این ویژگی‌ها، به صورت خودکار اعتبار اطلاعات انتساب داده شده به خواص یک مدل را در سمت کلاینت و همچنین در سمت سرور بررسی می‌نماید.
این ویژگی‌ها در اسمبلی System.ComponentModel.DataAnnotations.dll قرار دارند که به صورت پیش فرض در هر پروژه جدید ASP.NET MVC لحاظ می‌شود.

یک مثال کاربردی

مدل زیر را به پوشه مدل‌های یک پروژه جدید خالی ASP.NET MVC اضافه کنید:

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication9.Models
{
public class Customer
{
public int Id { set; get; }

[Required(ErrorMessage = "Name is required.")]
[StringLength(50)]
public string Name { set; get; }

[Display(Name = "Email address")]
[Required(ErrorMessage = "Email address is required.")]
[RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
ErrorMessage = "Please enter a valid email address.")]
public string Email { set; get; }

[Range(0, 10)]
[Required(ErrorMessage = "Rating is required.")]
public double Rating { set; get; }

[Display(Name = "Start date")]
[Required(ErrorMessage = "Start date is required.")]
public DateTime StartDate { set; get; }
}
}

سپس کنترلر جدید زیر را نیز به برنامه اضافه نمائید:
using System.Web.Mvc;
using MvcApplication9.Models;

namespace MvcApplication9.Controllers
{
public class CustomerController : Controller
{
[HttpGet]
public ActionResult Create()
{
var customer = new Customer();
return View(customer);
}

[HttpPost]
public ActionResult Create(Customer customer)
{
if (this.ModelState.IsValid)
{
//todo: save data
return Redirect("/");
}
return View(customer);
}
}
}

بر روی متد Create کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه باز شده، گزینه Create a strongly typed view را انتخاب کرده و مدل را Customer انتخاب کنید. همچنین قالب Scaffolding را نیز بر روی Create قرار دهید.

توضیحات تکمیلی

همانطور که در مدل برنامه ملاحظه می‌نمائید، به کمک یک سری متادیتا یا اصطلاحا data annotations، تعاریف اعتبار سنجی، به همراه عبارات خطایی که باید به کاربر نمایش داده شوند، مشخص شده است. ویژگی Required مشخص می‌کند که کاربر مجبور است این فیلد را تکمیل کند. به کمک ویژگی StringLength، حداکثر تعداد حروف قابل قبول مشخص می‌شود. با استفاده از ویژگی RegularExpression، مقدار وارد شده با الگوی عبارت باقاعده مشخص گردیده، مقایسه شده و در صورت عدم تطابق، پیغام خطایی به کاربر نمایش داده خواهد شد. به کمک ویژگی Range، بازه اطلاعات قابل قبول، مشخص می‌گردد.
ویژگی دیگری نیز به نام System.Web.Mvc.Compare مهیا است که برای مقایسه بین مقادیر دو خاصیت کاربرد دارد. برای مثال در یک فرم ثبت نام، عموما از کاربر درخواست می‌شود که کلمه عبورش را دوبار وارد کند. ویژگی Compare در یک چنین مثالی کاربرد خواهد داشت.
در مورد جزئیات کنترلر تعریف شده در قسمت 11 مفصل توضیح داده شد. برای مثال خاصیت this.ModelState.IsValid مشخص می‌کند که آیا کارmodel binding موفق بوده یا خیر و همچنین اعتبار سنجی‌های تعریف شده نیز در اینجا تاثیر داده می‌شوند. بنابراین بررسی آن پیش از ذخیره سازی اطلاعات ضروری است.
در حالت HttpGet صفحه ورود اطلاعات به کاربر نمایش داده خواهد شد و در حالت HttpPost، اطلاعات وارد شده دریافت می‌گردد. اگر دست آخر، ModelState معتبر نبود، همان اطلاعات نادرست وارد شده به کاربر مجددا نمایش داده خواهد شد تا فرم پاک نشود و بتواند آن‌ها را اصلاح کند.
برنامه را اجرا کنید. با مراجعه به مسیر http://localhost/customer/create، صفحه ورود اطلاعات کاربر نمایش داده خواهد شد. در اینجا برای مثال در قسمت ورود اطلاعات آدرس ایمیل، مقدار abc را وارد کنید. بلافاصله خطای اعتبار سنجی عدم اعتبار مقدار ورودی نمایش داده می‌شود. یعنی فریم ورک، اعتبار سنجی سمت کاربر را نیز به صورت خودکار مهیا کرده است.
اگر علاقمند باشید که صرفا جهت آزمایش، اعتبار سنجی سمت کاربر را غیرفعال کنید، به فایل web.config برنامه مراجعه کرده و تنظیم زیر را تغییر دهید:

<appSettings>
<add key="ClientValidationEnabled" value="true"/>

البته این تنظیم تاثیر سراسری دارد. اگر قصد داشته باشیم که این تنظیم را تنها به یک view خاص اعمال کنیم، می‌توان از متد زیر کمک گرفت:

@{ Html.EnableClientValidation(false); }

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

نحوه تعریف عناصر مرتبط با اعتبار سنجی در Viewهای برنامه نیز به شکل زیر است:

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Customer</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>

همانطور که ملاحظه می‌کنید به صورت پیش فرض از jQuery validator در سمت کلاینت استفاده شده است. فایل jquery.validate.unobtrusive متعلق به تیم ASP.NET MVC است و کار آن وفق دادن سیستم موجود، با jQuery validator می‌باشد (validation adapter). در نگارش‌های قبلی، از کتابخانه‌های اعتبار سنجی مایکروسافت استفاده شده بود، اما از نگارش سه به بعد، jQuery به عنوان کتابخانه برگزیده مطرح است.
Unobtrusive همچنین در اینجا به معنای مجزا سازی کدهای جاوا اسکریپتی، از سورس HTML صفحه و استفاده از ویژگی‌های data-* مرتبط با HTML5 برای معرفی اطلاعات مورد نیاز اعتبار سنجی است:
<input data-val="true" data-val-required="The Birthday field is required." id="Birthday" name="Birthday" type="text" value="" />

اگر خواستید این مساله را بررسی کنید، فایل web.config قرار گرفته در ریشه اصلی برنامه را باز کنید. در آنجا مقدار UnobtrusiveJavaScriptEnabled را false کرده و بار دیگر برنامه را اجرا کنید. در این حالت کلیه کدهای اعتبار سنجی، به داخل سورس View رندر شده، تزریق می‌شوند و مجزا از آن نخواهند بود.
نحوه‌ی تعریف این اسکریپت‌ها نیز جالب توجه است. متد Url.Content، یک متد سمت سرور می‌باشد که در زمان اجرای برنامه، مسیر نسبی وارد شده را بر اساس ساختار سایت اصلاح می‌کند. حرف ~ بکارگرفته شده، در ASP.NET به معنای ریشه سایت است. بنابراین مسیر نسبی تعریف شده از ریشه سایت شروع و تفسیر می‌شود.
اگر از این متد استفاده نکنیم، مجبور خواهیم شد که مسیرهای نسبی را به شکل زیر تعریف کنیم:

<script src="../../Scripts/customvaildation.js" type="text/javascript"></script>

در این حالت بسته به محل قرارگیری صفحات و همچنین برنامه در سایت، ممکن است آدرس فوق صحیح باشد یا خیر. اما استفاده از متد Url.Content، کار مسیریابی نهایی را خودکار می‌کند.
البته اگر به فایل Views/Shared/_Layout.cshtml، مراجعه کنید، تعریف و الحاق کتابخانه اصلی jQuery در آنجا انجام شده است. بنابراین می‌توان این دو تعریف دیگر مرتبط با اعتبار سنجی را به آن فایل هم منتقل کرد تا همه‌جا در دسترس باشند.
توسط متد Html.ValidationSummary، خطاهای اعتبار سنجی مدل که به صورت دستی اضافه شده باشند نمایش داده می‌شود. این مورد در قسمت 11 توضیح داده شد (چون پارامتر آن true وارد شده، فقط خطاهای سطح مدل را نمایش می‌دهد).
متد Html.ValidationMessageFor، با توجه به متادیتای یک خاصیت و همچنین استثناهای صادر شده حین model binding خطایی را به کاربر نمایش خواهد داد.



اعتبار سنجی سفارشی

ویژگی‌های اعتبار سنجی از پیش تعریف شده، پر کاربردترین‌ها هستند؛ اما کافی نیستند. برای مثال در مدل فوق، StartDate نباید کمتر از سال 2000 وارد شود و همچنین در آینده هم نباید باشد. این موارد اعتبار سنجی سفارشی را چگونه باید با فریم ورک، یکپارچه کرد؟
حداقل دو روش برای حل این مساله وجود دارد:
الف) نوشتن یک ویژگی اعتبار سنجی سفارشی
ب) پیاده سازی اینترفیس IValidatableObject


تعریف یک ویژگی اعتبار سنجی سفارشی

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication9.CustomValidators
{
public class MyDateValidator : ValidationAttribute
{
public int MinYear { set; get; }

public override bool IsValid(object value)
{
if (value == null) return false;

var date = (DateTime)value;
if (date > DateTime.Now || date < new DateTime(MinYear, 1, 1))
return false;

return true;
}
}
}

برای نوشتن یک ویژگی اعتبار سنجی سفارشی، با ارث بری از کلاس ValidationAttribute شروع می‌کنیم. سپس باید متد IsValid آن‌را تحریف کنیم. اگر این متد false برگرداند به معنای شکست اعتبار سنجی می‌باشد.
در ادامه برای بکارگیری آن خواهیم داشت:
[Display(Name = "Start date")]
[Required(ErrorMessage = "Start date is required.")]
[MyDateValidator(MinYear = 2000,
ErrorMessage = "Please enter a valid date.")]
public DateTime StartDate { set; get; }

اکنون مجددا برنامه را اجرا نمائید. اگر تاریخ غیرمعتبری وارد شود، اعتبار سنجی سمت سرور رخ داده و سپس نتیجه به کاربر نمایش داده می‌شود.


اعتبار سنجی سفارشی به کمک پیاده سازی اینترفیس IValidatableObject

یک سؤال: اگر اعتبار سنجی ما پیچیده‌تر باشد چطور؟ مثلا نیاز باشد مقادیر دریافتی چندین خاصیت با هم مقایسه شده و سپس بر این اساس تصمیم گیری شود. برای حل این مشکل می‌توان از اینترفیس IValidatableObject کمک گرفت. در این حالت مدل تعریف شده باید اینترفیس یاد شده را پیاده سازی نماید. برای مثال:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using MvcApplication9.CustomValidators;

namespace MvcApplication9.Models
{
public class Customer : IValidatableObject
{
//... same as before

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var fields = new[] { "StartDate" };
if (StartDate > DateTime.Now || StartDate < new DateTime(2000, 1, 1))
yield return new ValidationResult("Please enter a valid date.", fields);

if (Rating > 4 && StartDate < new DateTime(2003, 1, 1))
yield return new ValidationResult("Accepted date should be greater than 2003", fields);
}
}
}

در اینجا در متد Validate، فرصت خواهیم داشت تا به مقادیر کلیه خواص تعریف شده در مدل دسترسی پیدا کرده و بر این اساس اعتبار سنجی بهتری را انجام دهیم. اگر اطلاعات وارد شده مطابق منطق مورد نظر نباشند، کافی است توسط yield return new ValidationResult، یک پیغام را به همراه فیلدهایی که باید این پیغام را نمایش دهند، بازگردانیم.
به این نوع مدل‌ها، self validating models هم گفته می‌شود.


یک نکته:

از MVC3 به بعد، حین کار با ValidationAttribute، امکان تحریف متد IsValid به همراه پارامتری از نوع ValidationContext نیز وجود دارد. به این ترتیب می‌توان به اطلاعات سایر خواص نیز دست یافت. البته در این حالت نیاز به استفاده از Reflection خواهد بود و پیاده سازی IValidatableObject، طبیعی‌تر به نظر می‌رسد:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var info = validationContext.ObjectType.GetProperty("Rating");
//...
return ValidationResult.Success;
}




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

اعتبار سنجی‌های سفارشی تولید شده تا به اینجا، تنها سمت سرور است که فعال می‌شوند. به عبارتی باید یکبار اطلاعات به سرور ارسال شده و در بازگشت، نتیجه عملیات به کاربر نمایش داده خواهد شد. اما ویژگی‌های توکاری مانند Required و Range و امثال آن، علاوه بر سمت سرور، سمت کاربر هم فعال هستند و اگر جاوا اسکریپت در مرورگر کاربر غیرفعال نشده باشد، نیازی به ارسال اطلاعات یک فرم به سرور جهت اعتبار سنجی اولیه، نخواهد بود.
در اینجا باید سه مرحله برای پیاده سازی اعتبار سنجی سمت کلاینت طی شود:
الف) ویژگی سفارشی اعتبار سنجی تعریف شده باید اینترفیس IClientValidatable را پیاده سازی کند.
ب) سپس باید متد jQuery validation متناظر را پیاده سازی کرد.
ج) و همچنین مانند تیم ASP.NET MVC، باید unobtrusive adapter خود را نیز پیاده سازی کنیم. به این ترتیب متادیتای ASP.NET MVC به فرمتی که افزونه jQuery validator آن‌را درک می‌کند، وفق داده خواهد شد.

در ادامه، تکمیل کلاس سفارشی MyDateValidator را ادامه خواهیم داد:
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using System.Collections.Generic;

namespace MvcApplication9.CustomValidators
{
public class MyDateValidator : ValidationAttribute, IClientValidatable
{
// ... same as before

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata,
ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "mydatevalidator",
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName())
};
yield return rule;
}
}
}

در اینجا نحوه پیاده سازی اینترفیس IClientValidatable را ملاحظه می‌نمائید. ValidationType، نام متدی خواهد بود که در سمت کلاینت، کار بررسی اعتبار داده‌ها را به عهده خواهد گرفت.
سپس برای مثال یک فایل جدید به نام customvaildation.js به پوشه اسکریپت‌های برنامه با محتوای زیر اضافه خواهیم کرد:

/// <reference path="jquery-1.5.1-vsdoc.js" />
/// <reference path="jquery.validate-vsdoc.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

jQuery.validator.addMethod("mydatevalidator",
function (value, element, param) {
return Date.parse(value) < new Date();
});

jQuery.validator.unobtrusive.adapters.addBool("mydatevalidator");

توسط referenceهایی که مشاهده می‌کنید، intellisense جی‌کوئری در VS.NET فعال می‌شود.
سپس به کمک متد jQuery.validator.addMethod، همان مقدار ValidationType پیشین را معرفی و در ادامه بر اساس مقدار value دریافتی، تصمیم گیری خواهیم کرد. اگر خروجی false باشد، به معنای شکست اعتبار سنجی است.
همچنین توسط متد jQuery.validator.unobtrusive.adapters.addBool، این متد جدید را به مجموعه وفق دهنده‌ها اضافه می‌کنیم.
و در آخر این فایل جدید باید به View مورد نظر یا فایل master page سیستم اضافه شود:

<script src="@Url.Content("~/Scripts/customvaildation.js")" type="text/javascript"></script>




تغییر رنگ و ظاهر پیغام‌های اعتبار سنجی

اگر از رنگ پیش فرض قرمز پیغام‌های اعتبار سنجی خرسند نیستید، باید اندکی CSS سایت را ویرایش کرد که شامل اعمال تغییرات به موارد ذیل خواهد شد:

1. .field-validation-error
2. .field-validation-valid
3. .input-validation-error
4. .input-validation-valid
5. .validation-summary-errors
6. .validation-summary-valid




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

فرض کنید مدل‌های برنامه شما به کمک یک code generator تولید می‌شوند. در این حالت هرگونه ویژگی اضافی تعریف شده در این کلاس‌ها پس از تولید مجدد کدها از دست خواهند رفت. به همین منظور امکان تعریف مجزای متادیتاها نیز پیش بینی شده است:

[MetadataType(typeof(CustomerMetadata))]
public partial class Customer
{
class CustomerMetadata
{

}
}

public partial class Customer : IValidatableObject
{


حالت کلی روش انجام آن هم به شکلی است که ملاحظه می‌کنید. کلاس اصلی، به صورت partial معرفی خواهد شد. سپس کلاس partial دیگری نیز به همین نام که در برگیرنده یک کلاس داخلی دیگر برای تعاریف متادیتا است، به پروژه اضافه می‌گردد. به کمک ویژگی MetadataType، کلاسی که قرار است ویژگی‌های خواص از آن خوانده شود، معرفی می‌گردد. موارد عنوان شده، شکل کلی این پیاده سازی است. برای نمونه اگر با WCF RIA Services کار کرده باشید، از این روش زیاد استفاده می‌شود. کلاس خصوصی تو در توی تعریف شده صرفا وظیفه ارائه متادیتاهای تعریف شده را به فریم ورک خواهد داشت و هیچ کاربرد دیگری ندارد.
در ادامه کلیه خواص کلاس Customer به همراه متادیتای آن‌ها باید به کلاس CustomerMetadata منتقل شوند. اکنون می‌توان تمام متادیتای کلاس اصلی Customer را حذف کرد.



اعتبار سنجی از راه دور (remote validation)

فرض کنید شخصی مشغول به پر کردن فرم ثبت نام، در سایت شما است. پس از اینکه نام کاربری دلخواه خود را وارد کرد و مثلا به فیلد ورود کلمه عبور رسید، در همین حال و بدون ارسال کل صفحه به سرور، به او پیغام دهیم که نام کاربری وارد شده، هم اکنون توسط شخص دیگری در حال استفاده است. این مکانیزم از ASP.NET MVC3 به بعد تحت عنوان Remote validation در دسترس است و یک درخواست Ajaxایی خودکار را به سرور ارسال خواهد کرد و نتیجه نهایی را به کاربر نمایش می‌دهد؛ کارهایی که به سادگی توسط کدهای جاوا اسکریپتی قابل مدیریت نیستند و نیاز به تعامل با سرور، در این بین وجود دارد. پیاده سازی آن هم به نحو زیر است:
برای مثال خاصیت Name را در مدل برنامه به نحو زیر تغییر دهید:

[Required(ErrorMessage = "Name is required.")]
[StringLength(50)]
[System.Web.Mvc.Remote(action: "CheckUserNameAndEmail",
controller: "Customer",
AdditionalFields = "Email",
HttpMethod = "POST",
ErrorMessage = "Username is not available.")]
public string Name { set; get; }

سپس متد زیر را نیز به کنترلر Customer اضافه کنید:

[HttpPost]
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public ActionResult CheckUserNameAndEmail(string name, string email)
{
if (name.ToLowerInvariant() == "vahid") return Json(false);
if (email.ToLowerInvariant() == "name@site.com") return Json(false);
//...
return Json(true);
}


توضیحات:
توسط ویژگی System.Web.Mvc.Remote، نام کنترلر و متدی که در آن قرار است به صورت خودکار توسط jQuery Ajax فراخوانی شود، مشخص خواهند شد. همچنین اگر نیاز بود فیلدهای دیگری نیز به این متد کنترلر ارسال شوند، می‌توان آن‌ها را توسط خاصیت AdditionalFields، مشخص کرد.
سپس در کدهای کنترلر مشخص شده، متدی با پارامترهای خاصیت مورد نظر و فیلدهای اضافی دیگر، تعریف می‌شود. در اینجا فرصت خواهیم داشت تا برای مثال پس از بررسی بانک اطلاعاتی، خروجی Json ایی را بازگردانیم. return Json false به معنای شکست اعتبار سنجی است.
توسط ویژگی OutputCache، از کش شدن نتیجه درخواست‌های Ajaxایی جلوگیری کرده‌ایم. همچنین نوع درخواست هم جهت امنیت بیشتر، به HttpPost محدود شده است.
تمام کاری که باید انجام شود همین مقدار است و مابقی مسایل مرتبط با اعمال و پیاده سازی آن خودکار است.


استفاده از مکانیزم اعتبار سنجی مبتنی برمتادیتا در خارج از ASP.Net MVC

مباحثی را که در این قسمت ملاحظه نمودید، منحصر به ASP.NET MVC نیستند. برای نمونه توسط متد الحاقی زیر نیز می‌توان یک مدل را مثلا در یک برنامه کنسول هم اعتبار سنجی کرد. بدیهی است در این حالت نیاز خواهد بود تا ارجاعی را به اسمبلی System.ComponentModel.DataAnnotations، به برنامه اضافه کنیم و تمام عملیات هم دستی است و فریم ورک ویژه‌ای هم وجود ندارد تا یک سری از کارها را به صورت خودکار انجام دهد.

using System.ComponentModel.DataAnnotations;

namespace MvcApplication9.Helper
{
public static class ValidationHelper
{
public static bool TryValidateObject(this object instance)
{
return Validator.TryValidateObject(instance, new ValidationContext(instance, null, null), null);
}
}
}



مطالب
کار با اسکنر در برنامه های تحت وب (قسمت دوم و آخر)

در قسمت قبل « کار با اسکنر در برنامه‌های تحت وب (قسمت اول) » دیدی از کاری که قرار است انجام دهیم، رسیدیم. حالا سراغ یک پروژه‌ی عملی و پیاده سازی مطالب مطرح شده می‌رویم.

ابتدا پروژه‌ی   WCF را شروع می‌کنیم. ویژوال استودیو را باز کرده و از قسمت New Project > Visual C# > WCF یک پروژه‌ی WCF Service Application جدید را مثلا با نام "WcfServiceScanner" ایجاد نمایید. پس از ایجاد، دو فایل IService1.cs و Service1.scv موجود را به IScannerService و ScannerService تغییر نام دهید. سپس ابتدا محتویات کلاس اینترفیس IScannerService را به صورت زیر تعریف نمایید :

    [ServiceContract]
    public interface IScannerService
    {
        [OperationContract]
        [WebInvoke(Method = "GET",
           BodyStyle = WebMessageBodyStyle.Wrapped,
           RequestFormat = WebMessageFormat.Json,
           ResponseFormat = WebMessageFormat.Json,
           UriTemplate = "GetScan")]
        string GetScan();
    }
در اینجا ما فقط اعلان متدهای مورد نیاز خود را ایجاد کرده‌ایم. علت استفاده از Attribute ایی با نام WebInvoke ، مشخص نمودن نوع خروجی به صورت Json است و همچنین عنوان آدرس مناسبی برای صدا زدن متد. پس از آن کلاس ScannerService را مطابق کدهای زیر تغییر دهید:
    public class ScannerService : IScannerService
    {
        public string GetScan()
        {
            // TODO Add code here
        }
    }
تا اینجا فقط یک WCF Service معمولی ساخته‌ایم .در ادامه به سراغ کلاس WIA برای ارتباط با اسکنر می‌رویم.
بر روی پروژه‌ی خود راست کلیک کرده و Add Reference را انتخاب نموده و سپس در قسمت COM، گزینه‌ی Microsoft Windows Image Acquisition Library v2.0 را به پروژه‌ی خود اضافه نمایید.
با اضافه شدن این ارجاع به پروژه، دسترسی به فضای نام WIA برای ما امکان پذیر می‌شود که ارجاعی از آن را در کلاس ScannerService قرار می‌دهیم.
using WIA;
اکنون متد GetScan را مطابق زیر اصلاح می‌نماییم:
        public string GetScan()
        {
            var imgResult = String.Empty;
            var dialog = new CommonDialogClass();
            try
            {
                // نمایش فرم پیشفرض اسکنر
                var image = dialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType);
                
                // ذخیره تصویر در یک فایل موقت
                var filename = Path.GetTempFileName();
                image.SaveFile(filename);
                var img = Image.FromFile(filename);

                // img جهت ارسال سمت کاربر و نمایش در تگ Base64 تبدیل تصویر به 
                imgResult = ImageHelper.ImageToBase64(img, ImageFormat.Jpeg);
            }
            catch
            {
                // از آنجاییه که امکان نمایش خطا وجود ندارد در صورت بروز خطا رشته خالی 
                // بازگردانده می‌شود که به معنای نبود تصویر می‌باشد
            }

            return imgResult;
        }
دقت داشته باشید که کدها را در زمان توسعه بین Try..Catch قرار ندهید چون ممکن‌است در این زمان به خطاهایی برخورد کنید که نیاز باشد در مرورگر آنها را دیده و رفع خطا نمایید.
CommonDialogClass  کلاس اصلی در اینجا جهت نمایش فرم کار با اسکنر می‌باشد و متد‌های مختلفی را جهت ارتباط با اسکنر در اختیار ما قرار می‌دهد که بسته به نیاز خود می‌توانید از آنها استفاده کنید. برای نمونه در مثال ما نیز متد اصلی که مورد استفاده قرار گرفته، ShowAcquireImage می‌باشد که این متد، فرم پیش فرض دریافت اسکنر را به کاربر نمایش می‌دهد و کاربر از طریق آن می‌تواند قبل از شروع اسکن، یکسری تنظیمات را انجام دهد.
این متد ابتدا به صورت خودکار فرم تعیین دستگاه اسکنر ورودی را نمایش داده :


و سپس فرم پیش فرض اسکنر‌های TWAIN را جهت تعیین تنظیمات اسکن نمایش می‌دهد که این امکان نیز در این فرم فراهم است تا دستگاه‌های Feeder یا Flated انتخاب گردند.

خروجی این متد همان عکس اسکن شده است که از نوع WIA.ImageFile می‌باشد و ما پس از دریافتش، ابتدا آن را در یک فایل موقت ذخیره نموده و سپس با استفاده از یک متد کمکی آن را به فرمت Base64 برای درخواست کننده اسکن ارسال می‌نماییم.

کدهای کلاس کمکی ImageHelper:

        public static string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format)
        {
            if (image != null)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    // Convert Image to byte[]
                    image.Save(ms, format);
                    byte[] imageBytes = ms.ToArray();

                    // Convert byte[] to Base64 String
                    string base64String = Convert.ToBase64String(imageBytes);
                    return base64String;
                }
            }
            return String.Empty;
        }
توجه داشته باشید که خروجی این متد قرار است توسط callBack یک متد جاوا اسکریپتی مورد استفاده قرار گرفته و احیانا عکس مورد نظر در صفحه نمایش داده شود. پس بهتر است که از قالب تصویر به شکل Base64 استفاده گردد. ضمن اینکه پلاگین‌های Jquery مرتبط با ویرایش تصویر هم از این قالب پشتیبانی می‌کنند. (اینجا )

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

کار ما تا اینجا در پروژه‌ی WCF Service تقریبا تمام است. اگر پروژه را یکبار Build نمایید برای اولین بار احتمالا پیغام خطاهای زیر ظاهر خواهند شد:


جهت رفع این خطا، در قسمت Reference‌های پروژه خود، WIA را انتخاب نموده و از Properties‌های آن خصوصیت Embed Interop Types را به False تغییر دهید؛ مشکل حل می‌شود.

به سراغ پروژه‌ی ویندوز فرم جهت هاست کردن این WCF سرویس می‌رویم. می‌توانید این سرویس را بر روی یک Console App یا Windows Service هم هاست کنید که در اینجا برای سادگی مثال، از WinForm استفاده می‌کنیم.
یک پروژه‌ی WinForm جدید را ایجاد کنید و سپس از قسمت Add Reference > Solution به مسیر پروژه‌ی قبلی رفته و dll‌‌های آن را به پروژه خود اضافه نمایید.
Form1.cs  را باز کرده و ابتدا دو متغیر زیر را در آن به صورت عمومی تعریف نمایید:
        private readonly Uri _baseAddress = new Uri("http://localhost:6019");
        private ServiceHost _host;
برای استفاده از کلاس ServiceHost لازم است تا ارجاعی به فضای نام System.ServiceModel داده شود. متغیر baseAddress_ نگه دارنده‌ی آدرس ثابت سرویس اسکنر در سمت کلاینت می‌باشد و به این ترتیب ما دقیقا می‌دانیم باید سرویس را با کدام آدرس در کدهای جاوا اسکریپتی خود فراخوانی نماییم.
حال در رویداد Form_Load برنامه، کدهای زیر را جهت هاست کردن سرویس اضافه می‌نماییم:
        private void Form1_Load(object sender, EventArgs e)
        {
            _host = new ServiceHost(typeof(WcfServiceScanner.ScannerService), _baseAddress);
            _host.Open();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            _host.Close();
        }
همین چند خط برای هاست کردن سرویس روی آدرس localhost و پورت 8010 کامپیوتر کلاینت کافی است. اما یکسری تنظیمات مربوط به خود سرویس هم وجود دارد که باید در زمان پیاده سازی سرویس، در خود پروژه‌ی سرویس، ایجاد می‌گردید. اما از آنجا که ما قرار است سرویس را در یک پروژه‌ی دیگر هاست کنیم، بنابراین این تنظیمات را باید در همین پروژه‌ی WinForm قرار دهیم.
فایل App.Config پروژه‌ی WinForm را باز کرده و کدهای آنرا مطابق زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="BehaviourMetaData">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="WcfServiceScanner.ScannerService"
               behaviorConfiguration="BehaviourMetaData">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="WcfServiceScanner.IScannerService" />
      </service>
    </services>
  </system.serviceModel>

</configuration>
اکنون پروژه‌ی هاست آماده اجرا می‌باشد. اگر آنرا اجرا کنید و در مرورگر خود آدرس ذکر شده را وارد کنید، صفحه‌ی زیر را مشاهده خواهد کرد که به معنی صحت اجرای سرویس اسکنر می‌باشد.

اگر موفق به اجرا نشدید و احیانا با خطای زیر مواجه شدید، اطمینان حاصل کنید که ویژوال استودیو Run as Administrator باشد. مشکل حل خواهد شد.


به سراغ پروژه‌ی بعدی، یعنی وب سایت خود می‌رویم. یک پروژه‌ی MVC جدید ایجاد نمایید و در View مورد نظر خود، کدهای زیر را جهت صدا زدن متد GetScan اضافه می‌کنیم.
( از آنجا که کدها به صورت جاوا اسکریپت می‌باشد، پس مهم نیست که حتما پروژه MVC باشد؛ یک صفحه‌ی HTML ساده هم کافی است).
<a href="#" id="get-scan">Get Scan</a>
<img src="" id="img-scanned" />
<script>
    $("#get-scan").click(function () {
        var url = 'http://localhost:6019/';
        $.get(url, function (data) {
            $("#img-scanned").attr("src","data:image/Jpeg;base64,  "+ data.GetScanResult);
        });
    });
</script>
دقت کنید در هنگام دریافت اطلاعات از سرویس، نتیجه به شکل GetScanResult خواهد بود. الان اگر پروژه را اجرا نمایید و روی لینک کلیک کنید، اسکنر شروع به دریافت اسکن خواهد کرد اما نتیجه‌ای بازگشت داده نخواهد شد و علت هم مشکل امنیتی CORS می‌باشد که به دلیل دریافت اطلاعات از یک دامین دیگر رخ می‌دهد و اگر با Firebug درخواست را بررسی کنید متوجه خطا به شکل زیر خواهید شد.


راه حل‌های زیادی برای این مشکل ارائه شده است، و متاسفانه بسیاری از آنها در شرایط پروژه‌ی ما جوابگو نمی‌باشد (به دلیل هاست روی یک پروژه ویندوزی). تنها راه حل مطمئن (تست شده) استفاده از یک کلاس سفارشی در پروژه‌ی WCF Service  می‌باشد که مثال آن در اینجا آورده شده است.
برای رفع مشکل به پروژه WcfServiceScanner بازگشته و کلاس جدیدی را به نام CORSSupport ایجاد کرده و کدهای زیر را به آن اضافه کنید:
    public class CORSSupport : IDispatchMessageInspector
    {
        Dictionary<string, string> requiredHeaders;
        public CORSSupport(Dictionary<string, string> headers)
        {
            requiredHeaders = headers ?? new Dictionary<string, string>();
        }

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            var httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty;
            if (httpRequest.Method.ToLower() == "options")
                instanceContext.Abort();
            return httpRequest;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            var httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
            var httpRequest = correlationState as HttpRequestMessageProperty;

            foreach (var item in requiredHeaders)
            {
                httpResponse.Headers.Add(item.Key, item.Value);
            }
            var origin = httpRequest.Headers["origin"];
            if (origin != null)
                httpResponse.Headers.Add("Access-Control-Allow-Origin", origin);

            var method = httpRequest.Method;
            if (method.ToLower() == "options")
                httpResponse.StatusCode = System.Net.HttpStatusCode.NoContent;

        }
    }

    // Simply apply this attribute to a DataService-derived class to get
    // CORS support in that service
    [AttributeUsage(AttributeTargets.Class)]
    public class CORSSupportBehaviorAttribute : Attribute, IServiceBehavior
    {
        #region IServiceBehavior Members

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            var requiredHeaders = new Dictionary<string, string>();

            //Chrome doesn't accept wildcards when authorization flag is true
            //requiredHeaders.Add("Access-Control-Allow-Origin", "*");
            requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
            requiredHeaders.Add("Access-Control-Allow-Headers", "Accept, Origin, Authorization, X-Requested-With,Content-Type");
            requiredHeaders.Add("Access-Control-Allow-Credentials", "true");
            foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.MessageInspectors.Add(new CORSSupport(requiredHeaders));
                }
            }
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }
فضاهای نام لازم برای این کلاس به شرح زیر می‌باشد:
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
کلاس ScannerService را باز کرده و آنرا به ویژگی
    [CORSSupportBehavior]
    public class ScannerService : IScannerService
    {
مزین نمایید.

کار تمام است، یکبار دیگر ابتدا پروژه‌ی WcfServiecScanner و سپس پروژه هاست را Build کرده و برنامه‌ی هاست را اجرا کنید. اکنون مشاهده می‌کنید که با زدن دکمه‌ی اسکن، اسکنر فرم تنظیمات اسکن را نمایش می‌دهد که پس از زدن دکمه‌ی Scan، پروسه آغاز شده و پس از اتمام، تصویر اسکن شده در صفحه‌ی وب سایت نمایش داده می‌شود.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت هشتم - تنظیمات پروژه‌های React برای کار با Mobx decorators
تا اینجا دو مثالی را که از Mobx بررسی کردیم (مثال ورود متن و مثال کامپوننت شمارشگر)، به عمد به همراه decoratorهای @ دار آن نبودند. برای مثال در قسمت قبل، یک کلاس را با یک خاصیت ایجاد کردیم که روش مزین سازی خاصیت value آن را با  observable decorator، توسط متد decorate انجام دادیم و این هم یک روش کار با MobX است؛ بدون اینکه نیاز به تنظیمات خاصی را داشته باشد:
import { decorate } from "mobx";

class Count {
  value = 0;
}

decorate(Count, { value: observable });
const count = new Count();
اما اگر همان مثال بسیار ساده‌ی ورود متن را بخواهیم توسط decoratorهای @ دار MobX پیاده سازی کنیم ... پروژه‌ی استاندارد React ما کامپایل نخواهد شد که در این قسمت، روش رفع این مشکل را بررسی می‌کنیم.


بازنویسی مثال ورود متن و نمایش آن با Mobx decorators

در اینجا یک text-box، به همراه دو div در صفحه رندر خواهند شد که قرار است با ورود اطلاعاتی در text-box، یکی از آن‌ها (text-display) این اطلاعات را به صورت معمولی و دیگری (text-display-uppercase) آن‌را به صورت uppercase نمایش دهد. روش کار انجام شده هم مستقل از React است و به صورت مستقیم با استفاده از DOM API عمل شده‌است. این مثال را پیشتر در اولین قسمت بررسی MobX، ملاحظه کردید. اکنون اگر بخواهیم بجای شیءای که توسط متد observable کتابخانه‌ی MobX محصور شده‌است:
const text = observable({
  value: "Hello world!",
  get uppercase() {
    return this.value.toUpperCase();
  }
});
از یک کلاس ES6 به همراه Mobx decorators استفاده کنیم، به یک چنین پروژه‌ی جدیدی خواهیم رسید:
ابتدا یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app state-management-with-mobx-part3
> cd state-management-with-mobx-part3
> npm start
در ادامه کتابخانه‌ی mobx را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save mobx
در ادامه، ابتدا فایل public\index.html را جهت نمایش دو div و یک text-box، ویرایش می‌کنیم:
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>MobX Basics, part 3</title>
    <meta charset="UTF-8" />
    <link href="src/styles.css" />
  </head>

  <body>
    <main>
      <input id="text-input" />
      <p id="text-display"></p>
      <p id="text-display-uppercase"></p>
    </main>

    <script src="src/index.js"></script>
  </body>
</html>
سپس محتویات فایل src\index.js را نیز به نحو زیر تغییر می‌دهیم:
import { autorun, computed, observable } from "mobx";

const input = document.getElementById("text-input");
const textDisplay = document.getElementById("text-display");
const loudDisplay = document.getElementById("text-display-uppercase");

class Text {
  @observable value = "Hello World";
  @computed get uppercase() {
    return this.value.toUpperCase();
  }
}

const text = new Text();

input.addEventListener("keyup", event => {
  text.value = event.target.value;
});

autorun(() => {
  input.value = text.value;
  textDisplay.textContent = text.value;
  loudDisplay.textContent = text.uppercase;
});
تنها تفاوت این نگارش با نگارش قبلی آن، استفاده از کلاس Text که یک کلاس ES6 به همراه MobX Decorators است، بجای یک شیء ساده‌ی جاوا اسکریپتی می‌باشد. در اینجا خاصیت value به صورت observable@ تعریف شده و در نتیجه‌ی تغییر مقدار آن در کدهای برنامه، خاصیت محاسباتی وابسته‌ی به آن یا همان uppercase که با computed@ تزئین شده، به صورت خودکار به روز رسانی خواهد شد. متد autorun نیز به این تغییرات که حاصل فشرده شدن کلیدها هستند، واکنش نشان داده و متن دو div موجود در صفحه را به روز رسانی می‌کند.

اکنون اگر در همین حال، برنامه را با دستور npm start اجرا کنیم، با خطای زیر متوقف خواهیم شد:
./src/index.js
SyntaxError: \src\index.js: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (8:3):

   6 | 
   7 | class Text {
>  8 |   @observable value = "Hello World";
     |   ^
   9 |   @computed get uppercase() {
  10 |     return this.value.toUpperCase();
  11 |   }

راه حل اول: از Decorators استفاده نکنیم!

یک راه حل مشکل فوق این است که بدون هیچ تغییری در ساختار پروژه‌ی React خود، اصلا از decorator syntax استفاده نکنیم. برای مثال اگر یک کلاس متداول MobX ای چنین شکلی را دارد:
import { observable, computed, action } from "mobx";

class Timer {
  @observable start = Date.now();
  @observable current = Date.now();

  @computed
  get elapsedTime() {
    return this.current - this.start + "milliseconds";
  }

  @action
  tick() {
    this.current = Date.now();
  }
}
می‌توان آن‌را بدون استفاده از decorator syntax، به صورت زیر نیز تعریف کرد:
import { observable, computed, action, decorate } from "mobx";

class Timer {
  start = Date.now();
  current = Date.now();

  get elapsedTime() {
    return this.current - this.start + "milliseconds";
  }

  tick() {
    this.current = Date.now();
  }
}

decorate(Timer, {
  start: observable,
  current: observable,
  elapsedTime: computed,
  tick: action
});
نمونه‌ی این روش را در قسمت قبل با تعریف شیء شمارشگر مشاهده کرده‌اید. در اینجا با توجه به اینکه Decorators در جاوا اسکریپت چیزی نیستند بجز بیان زیبای higher-order functions و higher-order functions هم توابعی هستند که توابع دیگر را با ارائه‌ی قابلیت‌های بیشتری، محصور می‌کنند، به همین جهت هر کاری را که بتوان با تزئین کننده‌ها انجام داد، همان را با توابع معمولی جاوا اسکریپتی نیز می‌توان انجام داد. اینکار را در مثال فوق توسط متد decorate مشاهده می‌کنید. این متد ابتدا نوع کلاس خاصی را دریافت کرده و سپس در پارامتر دوم آن می‌توان شیءای را تعریف کرد که خواص آن، همان خواص کلاس پارامتر اول است و مقادیر این خواص، تزئین کننده‌هایی هستند که قرار است برای آن‌ها بکار گرفته شوند. مزیت این روش بدون تغییر باقی ماندن تعریف کلاس Timer در اینجا و همچنین انجام هیچگونه تغییری در ساختار پروژه‌ی React، بدون نیاز به نصب بسته‌های کمکی اضافی است.

همچنین در این حالت بجای استفاده از کامپوننت‌های کلاسی، باید از روش بکارگیری متد observer برای محصور کردن کامپوننت تابعی تعریف شده استفاده کرد (تا دیگر نیازی به ذکر observer class@ نباشد):
const Counter = observer(({ count }) => {
  return (
   // ...
  );
});


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

create-react-app امکان ایجاد پروژه‌های React تایپ‌اسکریپتی را با ذکر سوئیچ typescript نیز دارد:
> create-react-app my-proj1 --typescript
پس از ایجاد پروژه، فایل tsconfig.json آن‌را یافته و experimentalDecorators آن‌را به true تنظیم کنید:
{
  "compilerOptions": {
    // ...
    "experimentalDecorators": true
  }
}
این تنها تغییری است که مورد نیاز می‌باشد و پس از آن برنامه‌ی React جاری، بدون مشکلی می‌تواند با decorators کار کند.


فعالسازی MobX Decorators در پروژه‌های استاندارد React مبتنی بر ES6

MobX از legacy" decorators spec" پشتیبانی می‌کند. یعنی اگر پروژه‌ای از spec جدید استفاده کند، دیگر نخواهد توانست با MobX فعلی کار کند. این هم مشکل MobX نیست. مشکل اینجا است که باید دانست کلا decorators در زبان جاوااسکریپت هنوز در مرحله‌ی آزمایشی قرار دارند و تکلیف spec نهایی و تائید شده‌ی آن مشخص نیست.
برای فعالسازی decorators در یک پروژه‌ی React استاندارد مبتنی بر ES6، شاید کمی جستجو کنید و به نتایجی مانند افزودن فایل babelrc. به ریشه‌ی پروژه و نصب افزونه‌هایی مانند babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties@ برسید. اما ... این‌ها بدون اجرای دستور npm run eject کار نمی‌کنند و اگر این دستور را اجرا کنیم، در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است. به همین جهت نیاز به روشی را داریم تا بتوانیم تنظیمات Webpack و کامپایلر Babel را بدون اجرای دستور npm run eject، تغییر دهیم تا در نتیجه، decorators را در آن فعال کنیم و خوشبختانه پروژه‌ی react-app-rewired دقیقا برای همین منظور طراحی شده‌است.

بنابراین ابتدا بسته‌های زیر را نصب می‌کنیم:
> npm i --save-dev customize-cra react-app-rewired
بسته‌ی react-app-rewired، امکان بازنویسی تنظیمات webpack پروژه‌ی react را بدون eject آن میسر می‌کند. customize-cra نیز با استفاده از امکانات همین بسته، نگارش‌های جدیدتر create-react-app را پشتیبانی می‌کند.

پس از نصب این پیشنیازها، فایل جدید config-overrides.js را به ریشه‌ی پروژه، جائیکه فایل package.json قرار گرفته‌است، با محتوای زیر اضافه کنید تا پشتیبانی ازlegacy" decorators spec" فعال شوند:
const {
  override,
  addDecoratorsLegacy,
  disableEsLint
} = require("customize-cra");

module.exports = override(
  // enable legacy decorators babel plugin
  addDecoratorsLegacy(),

  // disable eslint in webpack
  disableEsLint()
);
در ادامه فایل package.json را گشوده و قسمت scripts آن‌را برای استفاده‌ی از react-app-rewired، به صورت زیر بازنویسی کنید تا امکان تغییر تنظیمات webpack به صورت پویا در زمان اجرای برنامه، میسر شود:
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },
پس از این تغییرات، نیاز است دستور npm start را یکبار دیگر از ابتدا اجرا کنید. اکنون برنامه بدون مشکل کامپایل شده و خروجی بدون خطایی در مرورگر نمایش داده خواهد شد.


تنظیمات ESLint مخصوص کار با decorators

فایل ویژه‌ی eslintrc.json. که در ریشه‌ی پروژه قرار می‌گیرد (این فایل بدون نام است و فقط از پسوند تشکیل شده)، برای پروژه‌های MobX، باید حداقل تنظیم زیر را داشته باشد تا ESLint بتواند legacyDecorators را نیز پردازش کند:
{
  "extends": "react-app",
  "parserOptions": {
    "ecmaFeatures": {
      "legacyDecorators": true
    }
  }
}
و یا یک نمونه‌ی غنی شده‌ی فایل eslintrc.json. مخصوص برنامه‌های React به صورت زیر است:
{
    "env": {
        "node": true,
        "commonjs": true,
        "browser": true,
        "es6": true,
        "mocha": true
    },
    "settings": {
        "react": {
            "version": "detect"
        }
    },
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true,
            "legacyDecorators": true
        },
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "plugins": [
        "babel",
        "react",
        "react-hooks",
        "react-redux",
        "no-async-without-await",
        "css-modules",
        "filenames",
        "simple-import-sort"
    ],
    "rules": {
        "no-const-assign": "warn",
        "no-this-before-super": "warn",
        "constructor-super": "warn",
        "strict": [
            "error",
            "safe"
        ],
        "no-debugger": "error",
        "brace-style": [
            "error",
            "1tbs",
            {
                "allowSingleLine": true
            }
        ],
        "no-trailing-spaces": "error",
        "keyword-spacing": "error",
        "space-before-function-paren": [
            "error",
            "never"
        ],
        "spaced-comment": [
            "error",
            "always"
        ],
        "vars-on-top": "error",
        "no-undef": "error",
        "no-undefined": "warn",
        "comma-dangle": [
            "error",
            "never"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ],
        "guard-for-in": "error",
        "no-eval": "error",
        "no-with": "error",
        "valid-typeof": "error",
        "no-unused-vars": "error",
        "no-continue": "warn",
        "no-extra-semi": "warn",
        "no-unreachable": "warn",
        "no-unused-expressions": "warn",
        "max-len": [
            "warn",
            80,
            4
        ],
        "react/prefer-es6-class": "warn",
        "react/jsx-boolean-value": "warn",
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn",
        "react/prop-types": "off",
        "react-redux/mapDispatchToProps-returns-object": "off",
        "react-redux/prefer-separate-component-file": "off",
        "no-async-without-await/no-async-without-await": "warn",
        "css-modules/no-undef-class": "off",
        "filenames/match-regex": [
            "off",
            "^[a-zA-Z]+\\.*\\b(typescript|module|locale|validate|test|action|api|reducer|saga)?\\b$",
            true
        ],
        "filenames/match-exported": "off",
        "filenames/no-index": "off",
        "simple-import-sort/sort": "error"
    },
    "extends": [
        "react-app",
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:react-redux/recommended",
        "plugin:css-modules/recommended"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly",
        "process": true
    }
}
البته برای اینکه این تنظیمات کار کند، باید افزونه‌های زیر را نیز به صورت محلی در ریشه‌ی پروژه‌ی جاری نصب کنید (این مورد از ESLint 6x به بعد اجباری است و از بسته‌های global استفاده نمی‌کند):
>npm i --save-dev eslint babel-eslint eslint-config-react-app eslint-loader eslint-plugin-babel eslint-plugin-react eslint-plugin-css-modules eslint-plugin-filenames eslint-plugin-flowtype eslint-plugin-import eslint-plugin-no-async-without-await eslint-plugin-react-hooks eslint-plugin-react-redux eslint-plugin-redux-saga eslint-plugin-simple-import-sort eslint-loader typescript
پس از آن می‌توان فایل config-overrides.js را به صورت زیر نیز بر اساس تنظیمات فوق، بهبود بخشید:
const {
  override,
  addDecoratorsLegacy,
  useEslintRc
} = require("customize-cra");

module.exports = override(
  addDecoratorsLegacy(),
  useEslintRc(".eslintrc.json")
);


رفع اخطار مرتبط با decorators در VSCode

تا اینجا کار تنظیم کامپایلر babel، جهت پردازش decorators انجام شد. اما خود VSCode نیز چنین اخطاری را در پروژه‌هایی که از decorates استفاده می‌کنند، نمایش می‌دهد:
Experimental support for decorators is a feature that is subject to change in a future release.
Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.ts(1219)
برای رفع آن، فایل جدید tsconfig.json را در ریشه‌ی پروژه ایجاد کرده و آن‌را به صورت زیر تکمیل کنید تا ادیتور تایپ‌اسکریپتی VSCode، دیگر خطاهای مرتبط با decorators را نمایش ندهد:
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "allowJs": true
    }
}

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-with-mobx-part3.zip
نظرات مطالب
الگوی PRG در ASP.NET MVC
البته روش توضیح داده شده همان روش متداول PRG است. اگر حتما نیاز به 303 دارید به روش زیر باید عمل کرد:
using System.Web.Mvc;

namespace TestMvcPRG.Helper
{
    public class Redirect303 : ActionResult
    {
        private string _url;

        public Redirect303(string url)
        {
            _url = url;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.StatusCode = 303; // redirect using GET
            context.HttpContext.Response.RedirectLocation = _url;
        }
    }

    public abstract class BaseController : Controller
    {
        public Redirect303 Redirect303(string actionName)
        {
            return new Redirect303(Url.Action(actionName));
        }

        public Redirect303 Redirect303(string actionName, object routeValues)
        {
            return new Redirect303(Url.Action(actionName, routeValues));
        }

        public Redirect303 Redirect303(string actionName, string controllerName)
        {
            return new Redirect303(Url.Action(actionName, controllerName));
        }

        public Redirect303 Redirect303(string actionName, string controllerName, object routeValues)
        {
            return new Redirect303(Url.Action(actionName, controllerName, routeValues));
        }
    }
}
و بعد برای استفاده:
using System.Web.Mvc;
using TestMvcRPG.Helper;

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

        [HttpPost]
        public ActionResult Index(string data)
        {
            if (ModelState.IsValid)
            {
                return Redirect303("Index"); // post-redirect-get
            }
            return View();
        }
    }
}
مطالب
نمایش بلادرنگ اعلامی به تمام کاربران در هنگام درج یک رکورد جدید به صورت notification
در ادامه می‌خواهیم مثالی را که در این مطلب مورد بررسی قرار گرفت، به صورتی تغییر دهیم که با ثبت یک آیتم جدید درون دیتابیس، یک notification، به تمامی کاربران متصل به هاب ارسال شود. همچنین با کلیک بر روی Notification سطر جدید نیز بلافاصله نمایش داده شود:

در این مثال برای نمایش پیام به صورت notification، از کتابخانه toastr استفاده می‌کنیم که از طریق nuget می‌توانید آن را به پروژه اضافه کنید:
PM> Install-Package toastr
کار با این کتابخانه خیلی ساده است؛ کافی است فایل‌های js و css آن را به فایل layout اضافه کرده و به این صورت از آن استفاده کنیم:
toastr.info("نمایش یک پیام - info");
toastr.success("نمایش یک پیام - success");
toastr.error("نمایش یک پیام - error");
toastr.warning("نمایش یک پیام - warning");
دستورات فوق خروجی‌های زیر را نمایش می‌دهد:

برای پیام‌های فوق نیز می‌توانید عنوانی را انتخاب کنید:
toastr.success("نمایش یک پیام - success", "عنوان");
اگر به فایل js این کتابخانه مراجعه کنید، می‌توانید مقادیر پیش‌فرض آن را برای نمایش یک پیام مشاهده کنید. برای سفارشی‌سازی آن نیز می‌توانید به این صورت عمل کنید:
toastr.options = {
            tapToDismiss: true,
            toastClass: 'toast',
            containerId: 'toast-container',
            debug: false,

            showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
            showDuration: 300,
            showEasing: 'swing', //swing and linear are built into jQuery
            onShown: undefined,
            hideMethod: 'fadeOut',
            hideDuration: 1000,
            hideEasing: 'swing',
            onHidden: undefined,

            extendedTimeOut: 1000,
            iconClasses: {
                error: 'toast-error',
                info: 'toast-info',
                success: 'toast-success',
                warning: 'toast-warning'
            },
            iconClass: 'toast-info',
            positionClass: 'toast-top-right',
            timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
            titleClass: 'toast-title',
            messageClass: 'toast-message',
            target: 'body',
            closeHtml: '<button>&times;</button>',
            newestOnTop: true,
            preventDuplicates: false,
            progressBar: false
};
اکنون برای نمایش این نوع پیام‌ها در زمان اتصال به هاب (در واقع در زمان ثیت یک رکورد جدید) نیاز به ارسال پارامتر خاصی به سرور (از سمت کلاینت) نمی‌باشد. تنها باید کدهای سمت سرور یعنی هاب را به گونه‌ایی تغییر دهیم تا به محض فراخوانی SendNotification، آخرین رکورد ثبت شده در دیتابیس را به تمامی کلاینت‌های متصل به هاب ارسال کند:
public class NotificationHub : Hub
{
        private readonly IProductService _productService;

        public NotificationHub(IProductService productService)
        {
            _productService = productService;
        }

        public void SendNotification()
        {
            Clients.Others.ShowNotification(_productService.GetLastProduct());
        }
}
در سمت کلاینت نیز کدها همانند مثال قبل هستند؛ با این تفاوت که در متد سمت کلاینت باید اطلاعات ارسال شده از سمت سرور را با نمایش یک notification به کاربران اطلاع دهیم:
var notify = $.connection.notificationHub;
notify.client.showNotification = function (data) {
toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید");
};
$.connection.hub.start().done(function () {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
});
تا اینجا همانند مثال قبلی عمل کردیم. یعنی به جای نمایش یک alert بوت‌استرپ، از کتابخانه toastr استفاده گردید. در مثال قبلی کاربر برای دیدن تغییرات می‌بایستی یکبار صفحه را ریفرش کند، اکنون می‌خواهیم کاربر بعد از کلیک بر روی پیام، بلافاصله سطر جدید را نیز مشاهده کند:
var positionClasses = {
            topRight: 'toast-top-right',
            bottomRight: 'toast-bottom-right',
            bottomLeft: 'toast-bottom-left',
            topLeft: 'toast-top-left',
            topCenter: 'toast-top-center',
            bottomCenter: 'toast-bottom-center'
        };
        var notify = $.connection.notificationHub;
        notify.client.showNotification = function (data) {
            toastr.options = {
                showDuration: 300,
                positionClass: positionClasses.bottomRight,
                onclick: function () {
                    $('#table tr:last').after("<tr>" +
                    "<td>" + data.Title + "</td>" +
                    "<td>" + data.Description + "</td>" +
                    "<td>" + data.Price + "</td>" +
                    "<td>" + data.Category + "</td>" +
                    "<td>  </td>" +
                    "</tr>");

                }
            };
            toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید");
            
            
        };
        $.connection.hub.start().done(function () {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
});
همانطور که مشاهده می‌کنید از onClick برای toastr استفاده کرده‌ایم. با این callback گفته‌ایم که اگر بر روی پیام کلیک شد، اطلاعات را به صورت یک سطر جدید به جدول اضافه کن:
onclick: function () {
                    $('#table tr:last').after("<tr>" +
                    "<td>" + data.Title + "</td>" +
                    "<td>" + data.Description + "</td>" +
                    "<td>" + data.Price + "</td>" +
                    "<td>" + data.Category + "</td>" +
                    "<td>  </td>" +
                    "</tr>");

}
مقادیر به صورت یک شیء جاوااسکریپتی برگردانده خواهند شد:
data {Id: 12, Title: "Item1", Description: "Des", Price: 100000, Category: 0}
که توسط data می‌توانیم به هر کدام از فیلدها، جهت نمایش در خروجی، دسترسی داشته باشیم.
دریافت سورس مثال جاریShowAlertSignalR 
نظرات مطالب
EF Code First #12
به این ترتیب باید پیاده سازی بشه. یک حالت عمومی است و به کلاس و شیء خاصی گره نخورده:
using System;
using System.ComponentModel.DataAnnotations;
namespace Test
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class CompareAttribute : ValidationAttribute
    {
        public CompareAttribute(string originalProperty, string confirmProperty)
        {
            OriginalProperty = originalProperty;
            ConfirmProperty = confirmProperty;
        }
        public string ConfirmProperty { get; private set; }
        public string OriginalProperty { get; private set; }
        protected override ValidationResult IsValid(object value, ValidationContext ctx)
        {
            if (value == null)
                return new ValidationResult("لطفا فیلدها را تکمیل نمائید");
            var confirmProperty = ctx.ObjectType.GetProperty(ConfirmProperty);
            if (confirmProperty == null)
                throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", ConfirmProperty));
            var confirmValue = confirmProperty.GetValue(ctx.ObjectInstance, null) as string;
            if (string.IsNullOrWhiteSpace(confirmValue))
                return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", ConfirmProperty));
            var originalProperty = ctx.ObjectType.GetProperty(OriginalProperty);
            if (originalProperty == null)
                throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", OriginalProperty));
            var originalValue = originalProperty.GetValue(ctx.ObjectInstance, null) as string;
            if (string.IsNullOrWhiteSpace(originalValue))
                return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", OriginalProperty));
            return originalValue == confirmValue ? ValidationResult.Success : new ValidationResult("مقادیر وارد شده یکسان نیستند");
        }
    }
}

مطالب
مدیریت Instance در WCF
نحوه پیاده سازی و مدیریت Instance در پروژه‌های مبتنی بر WCF

نکته : آشنایی اولیه با مفاهیم WCF جهت درک صحیح مطالب الزامی است.

تشریح مسئله :  در صورتی که نیاز باشد که نمونه ساخته شده از سرویس (سمت سرور) به صورت Singleton  باشد بهترین روش برای پیاده سازی به چه صورت است.

برای شروع ابتدا مثال زیر را پیاده سازی می‌کنیم.
یک Contract به صورت زیر تعریف می‌کنیم:
[ServiceContract(SessionMode=SessionMode.Allowed)]
    public interface IMyService
    {
        [OperationContract]
        int GetData();             
    }

حالا یک سرویس برای پیاده سازی Interface بالا می‌نویسیم.
[ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall )]
    public class PerCallService : IMyService
    {
        int count;
        public int GetData()
        {
            return ++count;
        }
    }
همانطور که از نام سرویس مشخص است از این سرویس به ازای هر فراخوانی یک نمونه سمت سرور ساخته می‌شود.
حالا برای مشاهده نتیجه یک پروژه ConsoleApplication ایجاد کنید و سرویس مورد نظر را از روش AddServiceReference به پروژه اضافه کرده در فایل Program کد‌های زیر را کپی کنید.
 static void Main( string[] args )
        {
            Console.WriteLine( "PerCall Service" );

            MyPerCallService.MyServiceClient client = new MyPerCallService.MyServiceClient();
            int count = 0;
            for ( int i = 0 ; i < 5 ; i++ )
            {
                count = client.GetData();              
            }          
            Console.WriteLine( count );
            Console.ReadLine();         
        }
بعد از اجرا خروجی به صورت زیر است:

بعد از 5 بار فراخوانی متد GetData باز خروجی دارای مقدار 1 است. یعنی به ازای هر بار فراخوانی متد GetData یک نمونه از سرویس مورد نظر ساخته می‌شود.این عمل توسط خصوصیت InstanceContextMode که از نوع PerCall است به سرویس اعمال میشود.

حالا یک سرویس دیگر به صورت زیر ایجاد کنید.

 [ServiceBehavior( InstanceContextMode = InstanceContextMode.Single )]
    public class SingleService : IMyService
    {
        int count;
        public int GetData()
        {
            return ++count;
        }
    }
تنها تفاوت این سرویس با سرویس قبلی در این است که InstanceContextMode این سرویس  به صورت Single معرفی شده است. یعنی به ازای n فراخوانی فقط یک نمونه از کلاس ساخته می‌شود. این سرویس رو هم مثل روش قبلی به Client Application اضافه کنید.
کد کلاس Program رو به صورت زیر تغییر دهید.

static void Main( string[] args )
        {
            Console.WriteLine( "Single Service" );

            MySingleService.MyServiceClient client = new MySingleService.MyServiceClient();
            int count = 0;
            for ( int i = 0 ; i < 5 ; i++ )
            {
                count = client.GetData();              
            }          
            Console.WriteLine("Result is : {0}", count );
            Console.ReadLine();         
        }
که بعد از اجرا خروجی به صورت زیر است.

به ازای 5 بار فراخوانی سرویس متغیر Count سمت سرور مقدار قبلی خود را حفظ کرده است.