مطالب
استفاده از jQuery Ajax جهت تعیین اعتبار یک فرم

فرض کنید تعیین اعتبار یکی از فیلدهای فرم نیاز به انجام محاسباتی در سمت سرور دارد و این‌کار را می‌خواهیم با استفاده از jQuery Ajax‌ انجام دهیم. مشکلی که در اینجا وجود دارد، این است که A در Ajax به معنای asynchronous است. یعنی زمانیکه کاربر دکمه submit را فشرد، دیگر برنامه منتظر این نخواهد شد که پاسخ کامل دریافت شود ، سایر پردازش‌ها صورت گیرد و سپس فرم را به سرور ارسال نماید (شبیه به ایجاد یک ترد جدید در برنامه‌های ویندوزی). مثال زیر را در نظر بگیرید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestCustomValidation.aspx.cs"
Inherits="TestJQueryAjax.TestCustomValodation" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

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

<script type="text/javascript">
function validate() {
var number1 = $("#<%=txtNumber1.ClientID %>").val();
var number2 = $("#<%=txtNumber2.ClientID %>").val();
var result = false;
$.ajax({
type: "POST",
url: 'AjaxSrv.asmx/ValidateIt',
data: '{"number1":' + number1 + ',"number2":' + number2 + '}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
if (msg.d) {
result = true;
alert('بسیار خوب');
}
else {
result = false;
alert('دوباره سعی کنید');
}
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
result = false;
alert("خطایی رخ داده است");
}
});
//debugger;
return result;
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
number 1 :
<asp:TextBox runat="server" ID="txtNumber1" />
<br />
number 2 :
<asp:TextBox runat="server" ID="txtNumber2" />
<br />
<asp:Button ID="btnSubmit" Text="Submit" UseSubmitBehavior="false" runat="server"
OnClientClick="if(!validate()){ return false;}" OnClick="btnSubmitClick" />
</div>
</form>
</body>
</html>

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Script.Services;

namespace TestJQueryAjax
{
/// <summary>
/// Summary description for AjaxSrv
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class AjaxSrv : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public bool ValidateIt(int number1, int number2)
{
return number1 * number2 == 10;
}
}
}
در این مثال هنگام submit ، عملیات اعتبار سنجی با توجه به وضعیت asynchronous عملیات Ajax ، تمام و کمال رخ نداده ، یا اعتبار سنجی انجام نمی‌شود و یا پیغام خطایی را دریافت خواهیم کرد.

راه حل چیست؟
راه حل‌های فضایی بسیاری را در وب در این مورد می‌توان پیدا کرد؛ اما راه حل استاندارد آن در این حالت ویژه، استفاده از Ajax در حالت غیرهمزمان است. یعنی این فریم ورک Ajax را وادار کنیم که تا پایان عملیات مورد نظر، منتظر بماند و سپس فرم را ارسال کند. برای این منظور تنها کافی است یک سطر زیر را پیش از فراخوانی تابع Ajax ، اضافه و فراخوانی نمائیم:

$.ajaxSetup({async: false}) ;
نکته:
UseSubmitBehavior دکمه ما را به شکل زیر رندر می‌کند (دکمه به یک button معمولی (بجای حالت submit) تبدیل شده و سپس یک doPostBack را اضافه خواهد کرد):

<input id="btnSubmit" type="button" onclick="if(!validate()){ return false;};__doPostBack('btnSubmit','')" value="Submit" name="btnSubmit"/>


مطالب
EF Code First #7

مدیریت روابط بین جداول در EF Code first به کمک Fluent API

EF Code first بجای اتلاف وقت شما با نوشتن فایل‌های XML تهیه نگاشت‌ها یا تنظیم آن‌ها با کد، رویه Convention over configuration را پیشنهاد می‌دهد. همین رویه، جهت مدیریت روابط بین جداول نیز برقرار است. روابط one-to-one، one-to-many، many-to-many و موارد دیگر را بدون یک سطر تنظیم اضافی، صرفا بر اساس یک سری قراردادهای توکار می‌تواند تشخیص داده و اعمال کند. عموما زمانی نیاز به تنظیمات دستی وجود خواهد داشت که قراردادهای توکار رعایت نشوند و یا برای مثال قرار است با یک بانک اطلاعاتی قدیمی از پیش موجود کار کنیم.


مفاهیمی به نام‌های Principal و Dependent

در EF Code first از یک سری واژه‌های خاص جهت بیان ابتدا و انتهای روابط استفاده شده است که عدم آشنایی با آن‌ها درک خطاهای حاصل را مشکل می‌کند:
الف) Principal : طرفی از رابطه است که ابتدا در بانک اطلاعاتی ذخیره خواهد شد.
ب) Dependent : طرفی از رابطه است که پس از ثبت Principal در بانک اطلاعاتی ذخیره می‌شود.
Principal می‌تواند بدون نیاز به Dependent وجود داشته باشد. وجود Dependent بدون Principal ممکن نیست زیرا ارتباط بین این دو توسط یک کلید خارجی تعریف می‌شود.


کدهای مثال مدیریت روابط بین جداول

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

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
public int Id { set; get; }
public string FirstName { set; get; }
public string LastName { set; get; }

public virtual AlimentaryHabits AlimentaryHabits { set; get; }
public virtual ICollection<CustomerAlias> Aliases { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual Address Address { get; set; }
}
}

namespace EF_Sample35.Models
{
public class CustomerAlias
{
public int Id { get; set; }
public string Aka { get; set; }

public virtual Customer Customer { get; set; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Role
{
public int Id { set; get; }
public string Name { set; get; }

public virtual ICollection<Customer> Customers { set; get; }
}
}

namespace EF_Sample35.Models
{
public class AlimentaryHabits
{
public int Id { get; set; }
public bool LikesPasta { get; set; }
public bool LikesPizza { get; set; }
public int AverageDailyCalories { get; set; }

public virtual Customer Customer { get; set; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Address
{
public int Id { set; get; }
public string City { set; get; }
public string StreetAddress { set; get; }
public string PostalCode { set; get; }

public virtual ICollection<Customer> Customers { set; get; }
}
}



همچنین تعاریف نگاشت‌های برنامه نیز مطابق کد‌های زیر است:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerAliasConfig : EntityTypeConfiguration<CustomerAlias>
{
public CustomerAliasConfig()
{
// one-to-many
this.HasRequired(x => x.Customer)
.WithMany(x => x.Aliases)
.WillCascadeOnDelete();
}
}
}

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// one-to-one
this.HasOptional(x => x.AlimentaryHabits)
.WithRequired(x => x.Customer)
.WillCascadeOnDelete();

// many-to-many
this.HasMany(p => p.Roles)
.WithMany(t => t.Customers)
.Map(mc =>
{
mc.ToTable("RolesJoinCustomers");
mc.MapLeftKey("RoleId");
mc.MapRightKey("CustomerId");
});

// many-to-one
this.HasOptional(x => x.Address)
.WithMany(x => x.Customers)
.WillCascadeOnDelete();
}
}
}


به همراه Context زیر:

using System.Data.Entity;
using System.Data.Entity.Migrations;
using EF_Sample35.Mappings;
using EF_Sample35.Models;

namespace EF_Sample35.DataLayer
{
public class Sample35Context : DbContext
{
public DbSet<AlimentaryHabits> AlimentaryHabits { set; get; }
public DbSet<Customer> Customers { set; get; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CustomerConfig());
modelBuilder.Configurations.Add(new CustomerAliasConfig());

base.OnModelCreating(modelBuilder);
}
}

public class Configuration : DbMigrationsConfiguration<Sample35Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}

protected override void Seed(Sample35Context context)
{
base.Seed(context);
}
}
}


که نهایتا منجر به تولید چنین ساختاری در بانک اطلاعاتی می‌گردد:



توضیحات کامل کدهای فوق:

تنظیمات روابط one-to-one و یا one-to-zero

زمانیکه رابطه‌ای 0..1 و یا 1..1 است، مطابق قراردادهای توکار EF Code first تنها کافی است یک navigation property را که بیانگر ارجاعی است به شیء دیگر، تعریف کنیم (در هر دو طرف رابطه).
برای مثال در مدل‌های فوق یک مشتری که در حین ثبت اطلاعات اصلی او، «ممکن است» اطلاعات جانبی دیگری (AlimentaryHabits) نیز از او تنها در طی یک رکورد، دریافت شود. قصد هم نداریم یک ComplexType را تعریف کنیم. نیاز است جدول AlimentaryHabits جداگانه وجود داشته باشد.

namespace EF_Sample35.Models
{
public class Customer
{
// ...
public virtual AlimentaryHabits AlimentaryHabits { set; get; }
}
}

namespace EF_Sample35.Models
{
public class AlimentaryHabits
{
// ...
public virtual Customer Customer { get; set; }
}
}

در اینجا خواص virtual تعریف شده در دو طرف رابطه، به EF خواهد گفت که رابطه‌ای، 1:1 برقرار است. در این حالت اگر برنامه را اجرا کنیم، به خطای زیر برخواهیم خورد:

Unable to determine the principal end of an association between 
the types 'EF_Sample35.Models.Customer' and 'EF_Sample35.Models.AlimentaryHabits'.
The principal end of this association must be explicitly configured using either
the relationship fluent API or data annotations.

EF تشخیص داده است که رابطه 1:1 برقرار است؛ اما با قاطعیت نمی‌تواند طرف Principal را تعیین کند. بنابراین باید اندکی به او کمک کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// one-to-one
this.HasOptional(x => x.AlimentaryHabits)
.WithRequired(x => x.Customer)
.WillCascadeOnDelete();
}
}
}


همانطور که ملاحظه می‌کنید در اینجا توسط متد WithRequired طرف Principal و توسط متد HasOptional، طرف Dependent تعیین شده است. به این ترتیب EF می‌توان یک رابطه 1:1 را تشکیل دهید.
توسط متد WillCascadeOnDelete هم مشخص می‌کنیم که اگر Principal حذف شد، لطفا Dependent را به صورت خودکار حذف کن.

توضیحات ساختار جداول تشکیل شده:
هر دو جدول با همان خواص اصلی که در دو کلاس وجود دارند، تشکیل شده‌اند.
فیلد Id جدول AlimentaryHabits اینبار دیگر Identity نیست. اگر به تعریف قید FK_AlimentaryHabits_Customers_Id دقت کنیم، در اینجا مشخص است که فیلد Id جدول AlimentaryHabits، به فیلد Id جدول مشتری‌ها متصل شده است (یعنی در آن واحد هم primary key است و هم foreign key). به همین جهت به این روش one-to-one association with shared primary key هم گفته می‌شود (کلید اصلی جدول مشتری با جدول AlimentaryHabits به اشتراک گذاشته شده است).


تنظیمات روابط one-to-many

برای مثال همان مشتری فوق را درنظر بگیرید که دارای تعدادی نام مستعار است:

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
// ...
public virtual ICollection<CustomerAlias> Aliases { get; set; }
}
}

namespace EF_Sample35.Models
{
public class CustomerAlias
{
// ...
public virtual Customer Customer { get; set; }
}
}

همین میزان تنظیم کفایت می‌کند و نیازی به استفاده از Fluent API برای معرفی روابط نیست.
در طرف Principal، یک مجموعه یا لیستی از Dependent وجود دارد. در Dependent هم یک navigation property معرف طرف Principal اضافه شده است.
جدول CustomerAlias اضافه شده، توسط یک کلید خارجی به جدول مشتری مرتبط می‌شود.

سؤال: اگر در اینجا نیز بخواهیم CascadeOnDelete را اعمال کنیم، چه باید کرد؟
پاسخ: جهت سفارشی سازی نحوه تعاریف روابط حتما نیاز به استفاده از Fluent API به نحو زیر می‌باشد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerAliasConfig : EntityTypeConfiguration<CustomerAlias>
{
public CustomerAliasConfig()
{
// one-to-many
this.HasRequired(x => x.Customer)
.WithMany(x => x.Aliases)
.WillCascadeOnDelete();
}
}
}

اینکار را باید در کلاس تنظیمات CustomerAlias انجام داد تا بتوان Principal را توسط متد HasRequired به Customer و سپس dependent را به کمک متد WithMany مشخص کرد. در ادامه می‌توان متد WillCascadeOnDelete یا هر تنظیم سفارشی دیگری را نیز اعمال نمود.
متد HasRequired سبب خواهد شد فیلد Customer_Id، به صورت not null در سمت بانک اطلاعاتی تعریف شود؛ متد HasOptional عکس آن است.


تنظیمات روابط many-to-many

برای تنظیم روابط many-to-many تنها کافی است دو سر رابطه ارجاعاتی را به یکدیگر توسط یک لیست یا مجموعه داشته باشند:

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Role
{
// ...
public virtual ICollection<Customer> Customers { set; get; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
// ...
public virtual ICollection<Role> Roles { get; set; }
}
}

همانطور که مشاهده می‌کنید، یک مشتری می‌تواند چندین نقش داشته باشد و هر نقش می‌تواند به چندین مشتری منتسب شود.
اگر برنامه را به این ترتیب اجرا کنیم، به صورت خودکار یک رابطه many-to-many تشکیل خواهد شد (بدون نیاز به تنظیمات نگاشت‌های آن). نکته جالب آن تشکیل خودکار جدول ارتباط دهنده واسط یا اصطلاحا join-table می‌باشد:

CREATE TABLE [dbo].[RolesJoinCustomers](
[RoleId] [int] NOT NULL,
[CustomerId] [int] NOT NULL,
)

سؤال: نام‌های خودکار استفاده شده را می‌خواهیم تغییر دهیم. چکار باید کرد؟
پاسخ: اگر بانک اطلاعاتی برای بار اول است که توسط این روش تولید می‌شود شاید این پیش فرض‌ها اهمیتی نداشته باشد و نسبتا هم مناسب هستند. اما اگر قرار باشد از یک بانک اطلاعاتی موجود که امکان تغییر نام فیلدها و جداول آن وجود ندارد استفاده کنیم، نیاز به سفارشی سازی تعاریف نگاشت‌ها به کمک Fluent API خواهیم داشت:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// many-to-many
this.HasMany(p => p.Roles)
.WithMany(t => t.Customers)
.Map(mc =>
{
mc.ToTable("RolesJoinCustomers");
mc.MapLeftKey("RoleId");
mc.MapRightKey("CustomerId");
});
}
}
}


تنظیمات روابط many-to-one

در تکمیل مدل‌های مثال جاری، به دو کلاس زیر خواهیم رسید. در اینجا تنها در کلاس مشتری است که ارجاعی به کلاس آدرس او وجود دارد. در کلاس آدرس، یک navigation property همانند حالت 1:1 تعریف نشده است:

namespace EF_Sample35.Models
{
public class Address
{
public int Id { set; get; }
public string City { set; get; }
public string StreetAddress { set; get; }
public string PostalCode { set; get; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
// …
public virtual Address Address { get; set; }
}
}

این رابطه توسط EF Code first به صورت خودکار به یک رابطه many-to-one تفسیر خواهد شد و نیازی به تنظیمات خاصی ندارد.
زمانیکه جداول برنامه تشکیل شوند، جدول Addresses موجودیتی مستقل خواهد داشت و جدول مشتری با یک فیلد به نام Address_Id به جدول آدرس‌ها متصل می‌گردد. این فیلد نال پذیر است؛ به عبارتی ذکر آدرس مشتری الزامی نیست.
اگر نیاز بود این تعاریف نیز توسط Fluent API سفارشی شوند، باید خاصیت public virtual ICollection<Customer> Customers به کلاس Address نیز اضافه شود تا بتوان رابطه زیر را توسط کدهای برنامه تعریف کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// many-to-one
this.HasOptional(x => x.Address)
.WithMany(x => x.Customers)
.WillCascadeOnDelete();
}
}
}

متد HasOptional سبب می‌شود تا فیلد Address_Id اضافه شده به جدول مشتری‌ها، null پذیر شود.

اشتراک‌ها
اطلاعات خود در مورد ارث‌بری را محک بزنید
امشب به تاریخ ۲۰ مهر، سؤالی در مورد ارث‌بری، interface و abstract class در سایت محبوب همگی ما، StackOverFlow منتشر شد. سؤال حاکی از رفتار عجیب قطعه کد زیر در نسخه ۸ سی‌شارپ است. تا به اینجا برای سؤال مذکور، یک پاسخ با اعتماد به نقس بالایی ارائه شد و پس از مدت اندکی حذف گردیده است.

interface I {
    string M1() => "I.M1";
    string M2() => "I.M2";
}

abstract class A : I {}

class C : A {
    public string M1() => "C.M1";
    public virtual string M2() => "C.M2";
}

class Program {
    static void Main() {
        I obj = new C();
        System.Console.WriteLine(obj.M1());
        System.Console.WriteLine(obj.M2());
    }
}
نظر شما در مورد نحوه عملکرد چیست؟ در صورت استفاده از کلمه کلیدی‌های new و override چه خروجی‌هایی خواهیم گرفت.
اگر برای شما هم این مبحث جذاب شده است، به سؤال اصلی  مراجعه کنید.
اطلاعات خود در مورد ارث‌بری را محک بزنید
نظرات مطالب
C# 6 - String Interpolation
معرفی «Constant interpolated strings» در C# 10.0

C# 10.0 به همراه تغییر و بهبود جزئی در مورد interpolated strings است. تا پیش از آن، امکان تعریف یک interpolated strings به صورت const وجود نداشت؛ اما اکنون این محدودیت برطرف شده‌است و قطعه کد زیر مجاز است:
const string constStrFirst = "FirstStr";
const string summaryConstStr = $"SecondStr {constStrFirst}";

محدودیت: این نکته تنها در مورد const string‌ها صادق است. برای مثال اگر از const char استفاده شود:
const char a = 'a';
const string constStrFirst = "FirstStr";
const string summaryConstStr = $"SecondStr {constStrFirst} {a}";
با خطای زیر مواجه می‌شویم:
// Error CS0133
// The expression being assigned to
// 'summaryConstStr' must be constant
مطالب
معرفی سایت ExtensionMethod.NET

سایت ExtensionMethod.NET بانک اطلاعاتی است از قابلیتی تحت عنوان extension methods معرفی شده در C# 3.0 و Visual Basic 2008 . هدف اصلی از extension methods ، بسط کلاس‌های دات نت فریم ورک توسط جامعه‌ی برنامه نویس‌ها می‌باشد.



مثلا در کلاس پایه string ، متدی به نام Right وجود ندارد. برای اضافه کردن آن می‌توان به صورت زیر عمل کرد:

public static string Right(this string s, int length)
{
length = Math.Max(length, 0);
if (s.Length > length)
{
return s.Substring(s.Length - length, length);
}
else
{
return s;
}
}

و پس از آن هم استفاده از این متد که کلاس پایه string را بسط داده است به شکل زیر خواهد بود (همانند یکی از متدهای کلاس string می‌توان از آن استفاده کرد):
string s = "abcde";
s = s.Right(3); //s becomes "cde"

برای پیگیری سایت هم می‌توان از فید آن استفاده نمود.

نظرات مطالب
ارتقاء از WinForms به WPF
البته از یک لحاظ به سیر اسد حق میدم , مهاجرت به یک تکنولوژی مستلزم زمانی برای یادگیری و اون هست و ترس از Backward Compatibility که خیلی ها از رفتن به سمت تکنولوژی های جدید باز داشته ولی خوب اینها همش ناشی از عدم آشنایی با چیزی و ابعاد اون هست شما به WPF این مشکل رو نخواهید داشت WPF می تونه خیلی راحت با سیستمهای قبلی شما سازگار بشه حتی شما در سیستمهایی که با ++C نوشتید می تونید از اون استفاده کنید ولی خوب این مستلزم اینه که شما به ++C دات نت کوج کنید , خوب ممکنه برنامه های شما با زبانهای دیگه نوشته باشه مثل VB نه VB.NET شما بازهم نگران استفاده از WPF در اونها نباشید بدون مشکل این امر محقق میشه , یک مورد دیگری که خیلی مساله ساز میشه برای شرکتها و خیلی از برنامه نویسها هزینه های آموزش تکنولوژی های جدید هست , ولی خوب به نظر من این امر هم خیلی مساله بغرنجی نیست که نگران اون هستند اگر تیم برنامه نویسی واقعا" اصولی مطالب رو آموخته باشن می تونند براحتی به سمت تکنولوژی های جدید برن این امر خیلی دور از واقعیت نیست , ولی خوب بهر حال عامه جامعه برنامه نویسها زیر با تکنولوژی جدید نمی رن و جدای از چیزهایی که گفتم آنها یک شعار دارن که می گن "اینی که داریم کفایت کار ما رو میکنه و پول ساز هست " , پس دلیلی بر کوچ کردن به تکنولوژی های جدید وجود نداره.
مطالب
اس کیوال سرور 2008 و عملگرهای C مانند

اگر با زبان C و مشتقات آن آشنایی داشته باشید، حتما با عملگرهای ترکیبی آن‌ها که جهت خلاصه نویسی بکار می‌روند، نیز کار کرده‌اید. برای مثال:

int i =5;
i += 15; // i = i + 15;

اس کیوال سرور 2008 نیز از اینگونه عملگرها پشتیبانی به عمل می‌آورد. برای نمونه:
DECLARE @x1 int = 27;
SET @x1 += 2 ;
SELECT @x1 AS Added_2;
در دستورات T-SQL فوق دو نکته قابل توجه است:
الف) امکان تعریف و مقدار دهی همزمان یک متغیر (مقدار دهی همزمان با تعریف، تا قبل از اس کیوال سرور 2008 پشتیبانی نمی‌شد)
ب) امکان استفاده از عملگرهای C مانند در عبارات T-SQL

لیست این عملگرهای جدید به شرح زیر است:
+= (Add EQUALS) (Transact-SQL)
-= (Subtract EQUALS) (Transact-SQL)
*= (Multiply EQUALS) (Transact-SQL)
/= (Divide EQUALS) (Transact-SQL)
%= (Modulo EQUALS) (Transact-SQL)
&= (Bitwise AND EQUALS) (Transact-SQL)
^= (Bitwise Exclusive OR EQUALS) (Transact-SQL)
|= (Bitwise OR EQUALS) (Transact-SQL)

مطالب
رمزنگاری خودکار فیلدها توسط Entity Framework Core
از EF Core 2.1 به بعد، قابلیت جدیدی تحت عنوان «تبدیلگرهای مقدار»، به آن اضافه شده‌است. برای مثال در EF Core، زمانیکه اطلاعات Enums، در بانک اطلاعاتی ذخیره می‌شوند، معادل عددی آن‌ها درج خواهند شد. اگر علاقمند باشید تا بجای این مقادیر عددی دقیقا همان رشته‌ی تعریف کننده‌ی Enum درج شود، می‌توان یک «تبدیلگر مقدار» را برای آن نوشت. برای مثال در موجودیت Rider زیر، خاصیت Mount از نوع یک enum است.
public class Rider
{
    public int Id { get; set; }
    public EquineBeast Mount { get; set; }
}

public enum EquineBeast
{
    Donkey,
    Mule,
    Horse,
    Unicorn
}
برای اینکه در حین درج رکوردهای Rider در بانک اطلاعاتی دقیقا از مقادیر رشته‌ای EquineBeast استفاده شود، می‌توان به صورت زیر عمل کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(
            v => v.ToString(),
            v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}
در اینجا در حین تعریف جزئیات نگاشت یک مدل می‌توان متد جدید HasConversion را نیز فراخوانی کرد. پارامتر اول آن، روش تبدیل مقدار enum را به یک رشته، جهت درج در بانک اطلاعاتی و پارامتر دوم آن، روش تبدیل مقدار رشته‌ای خوانده شده‌ی از بانک اطلاعاتی را جهت وهله سازی یک Rider داری خاصیت enum، مشخص می‌کند.

نکته 1: مقادیر نال، هیچگاه به تبدیلگرهای مقدار، ارسال نمی‌شوند. اینکار پیاده سازی آن‌ها را ساده‌تر می‌کند و همچنین می‌توان آن‌ها را بین خواص نال‌پذیر و نال‌نپذیر، به اشتراک گذاشت. بنابراین برای مقادیر نال نمی‌توان تبدیلگر نوشت.

نکته 2: کاری که در متد HasConversion فوق انجام شده‌است، در حقیقت وهله سازی ضمنی یک ValueConverter و استفاده از آن است. می‌توان اینکار را به صورت صریح نیز انجام داد:
var converter = new ValueConverter<EquineBeast, string>(
    v => v.ToString(),
    v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion(converter);
مزیت اینکار این است که اگر قرار شد برای چندین خاصیت از تبدیلگر مقدار مشابهی استفاده کنیم، می‌توان از یک converter تعریف شده بجای تکرار کدهای آن استفاده کرد.


تبدیلگرهای مقدار توکار EF Core

برای بسیاری از اعمال متداول، در فضای نام Microsoft.EntityFrameworkCore.Storage.ValueConversion، تعدادی تبدیلگر مقدار تدارک دیده شده‌اند که به این شرح می‌باشند:
BoolToZeroOneConverter: تبدیلگر bool به صفر و یک
BoolToStringConverter: تبدیلگر bool به Y و یا N
BoolToTwoValuesConverter: تبدیلگر bool به دو مقداری دلخواه
BytesToStringConverter: تبدیلگر آرایه‌ای از بایت‌ها به یک رشته‌ی Base64-encoded
CastingConverter: تبدیلگر یک نوع به نوعی دیگر
CharToStringConverter: تبدیلگر char به string
DateTimeOffsetToBinaryConverter: تبدیلگر DateTimeOffset به یک مقدار 64 بیتی باینری
DateTimeOffsetToBytesConverter: تبدیلگر DateTimeOffset به آرایه‌ای از بایت‌ها
DateTimeOffsetToStringConverter: تبدیلگر DateTimeOffset به رشته
DateTimeToBinaryConverter: تبدیلگر DateTime به یک مقدار 64 بیتی با درج DateTimeKind
DateTimeToStringConverter: تبدیلگر DateTime به یک رشته
DateTimeToTicksConverter: تبدیلگر DateTime به ticks آن
EnumToNumberConverter: تبدیلگر Enum به عدد متناظر با آن
EnumToStringConverter: تبدیلگر Enum به رشته
GuidToBytesConverter: تبدیلگر Guid به آرایه‌ای از بایت‌ها
GuidToStringConverter: تبدیلگر Guid به رشته
NumberToBytesConverter: تبدیلگر اعداد به آرایه‌ای از بایت‌ها
NumberToStringConverter: تبدیلگر اعداد به رشته
StringToBytesConverter: تبدیلگر رشته به آرایه‌ای از بایت‌های UTF8 معادل آن
TimeSpanToStringConverter: تبدیلگر TimeSpan به رشته
TimeSpanToTicksConverter: تبدیلگر TimeSpan به ticks آن

برای نمونه در این لیست، EnumToStringConverter نیز وجود دارد. بنابراین نیازی به تعریف دستی آن مانند مثال ابتدای بحث نیست و می‌توان به صورت زیر از آن استفاده کرد:
var converter = new EnumToStringConverter<EquineBeast>();
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion(converter);
نکته: تمام تبدیل کننده‌های مقدار توکار EF Core، بدون حالت هستند. بنابراین می‌توان یک تک وهله‌ی از آن‌ها را بین چندین خاصیت به اشتراک گذاشت.


تعیین نوع تبدیلگر مقدار، جهت ساده سازی تعاریف

برای حالاتی که تبدیلگر مقدار توکاری تعریف شده‌است، صرفا تعریف نوع تبدیل، کفایت می‌کند:
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion<string>();
برای نمونه در اینجا با ذکر نوع رشته، تبدیل enum به string به صورت خودکار انجام خواهد شد. معادل اینکار، تعریف نوع سمت بانک اطلاعاتی این خاصیت است:
public class Rider
{
    public int Id { get; set; }

    [Column(TypeName = "nvarchar(24)")]
    public EquineBeast Mount { get; set; }
}
در این حالت حتی نیازی به تعریف HasConversion هم نیست.


نوشتن تبدیلگر خودکار مقادیر خواص، به نمونه‌ای رمزنگاری شده

پس از آشنایی با مفهوم «تبدیلگرهای مقدار» در +EF Core 2.1، اکنون می‌توانیم یک نمونه‌ی سفارشی از آن‌را نیز طراحی کنیم:
namespace DbConfig.Web.DataLayer.Context
{
    public class MyAppContext : DbContext
    {
      // …

        protected override void OnModelCreating(ModelBuilder builder)
        {
            var encryptedConverter = new ValueConverter<string, string>(
               convertToProviderExpression: v => new string(v.Reverse().ToArray()), // encrypt
               convertFromProviderExpression: v => new string(v.Reverse().ToArray()) // decrypt
            );

            // Custom application mappings
            builder.Entity<ConfigurationValue>(entity =>
            {
                entity.Property(e => e.Value).IsRequired().HasConversion(encryptedConverter);
            });
        }
    }
}
در اینجا معکوس کردن رشته‌ها به عنوان الگوریتم ساده‌ی رمزنگاری اطلاعات انتخاب شده‌است. نحوه‌ی اعمال این ValueConverter جدید را نیز ملاحظه می‌کنید.
می‌توان قسمت HasConversion را به صورت زیر خودکار کرد:
ابتدا یک Attribute جدید را به نام Encrypted به برنامه اضافه می‌کنیم:
using System;

namespace Test
{
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public sealed class EncryptedAttribute : Attribute
    { }
}
هدف از این Attribute خالی، صرفا نشانه گذاری خاصیت‌هایی است که قرار است به صورت رمزنگاری شده در بانک اطلاعاتی ذخیره شوند؛ مانند خاصیت Value زیر:
namespace DbConfig.Web.DomainClasses
{
    public class ConfigurationValue
    {
        public int Id { get; set; }
        public string Key { get; set; }

        [Encrypted]
        public string Value { get; set; }
    }
}
پس از آن، متد OnModelCreating را به صورت زیر اصلاح می‌کنیم تا به کمک Reflection و اطلاعات موجودیت‌های ثبت شده‌ی در سیستم، متد SetValueConverter را بر روی خواصی که دارای EncryptedAttribute هستند، به صورت خودکار فراخوانی کند:
namespace DbConfig.Web.DataLayer.Context
{
    public class MyAppContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder builder)
        {
            var encryptedConverter = new ValueConverter<string, string>(
               convertToProviderExpression: v => new string(v.Reverse().ToArray()), // encrypt
               convertFromProviderExpression: v => new string(v.Reverse().ToArray()) // decrypt
            );

            foreach (var entityType in builder.Model.GetEntityTypes())
            {
                foreach (var property in entityType.GetProperties())
                {
                    var attributes = property.PropertyInfo.GetCustomAttributes(typeof(EncryptedAttribute), false);
                    if (attributes.Any())
                    {
                        property.SetValueConverter(encryptedConverter);
                    }
                }
            }
        }


تاثیر ValueConverter‌ها بر روی اعمال متداول کار با بانک اطلاعاتی

از دیدگاه برنامه، ValueConverterهای تعریف شده، هیچگونه تاثیری را بر روی کوئری نوشتن و یا ثبت و ویرایش اطلاعات ندارند و عملکرد آن‌ها کاملا از دیدگاه سایر قسمت‌های برنامه مخفی است. برای مثال در برنامه، فرمان به روز رسانی خاصیت Value را با مقدار .A new value to test صادر کرده‌ایم (مقدار دهی متداول)، اما همانطور که ملاحظه می‌کنید، نمونه‌ی رمزنگاری شده‌ی آن به صورت خودکار در بانک اطلاعاتی درج شده‌است (پارامتر p0):
 Executed DbCommand (22ms) 
   [Parameters=[@p1='1', 
                @p0='.tset ot eulav wen A' (Nullable = false) (Size = 4000)],
CommandType='Text', CommandTimeout='180']
SET NOCOUNT ON;
UPDATE [Configurations] SET [Value] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;

و یا کوئری زیر
 db.Set<ConfigurationValue>().Where(x => x.Value.EndsWith("world!"))
به این نحو ترجمه خواهد شد:
SELECT [x].[Id], [x].[Key], [x].[Value]
FROM [Configurations] AS [x]
WHERE RIGHT([x].[Value], LEN(N'world!')) = N'!dlrow'
یعنی نیازی نیست تا مقداری را که در حال جستجوی آن هستیم، خودمان به صورت دستی رمزنگاری کرده و سپس در کوئری قرار دهیم. اینکار به صورت خودکار انجام می‌شود.
مطالب
رمزنگاری JWT و افزایش امنیت آن در ASP.NET Core
آموزش JSON Web Token (به اختصار JWT) و پیاده سازی آن در برنامه‌های ASP.NET Core درسایت موجود است.
توکن JWT در حالت عادی به صورت Base64 رمزنگاری می‌شود که این نوع رمزنگاری به راحتی قابل رمزگشایی و خواندن است. سایت‌های آنلاین زیادی برای رمزگشایی base64 موجود است؛ برای مثال کافی است توکن خود را در سایت jwt.io کپی کنید و به راحتی محتوای بدنه توکن (Payload) را مشاهده کنید.

پس توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد.
ساده‌ترین راه حل، رمزنگاری دستی بدنه توکن می‌باشد که مثلا بر اساس کلیدی (که فقط سمت سرور نگهداری و مراقبت می‌شود) توکن را رمزنگاری کرده و به هنگام خواندن، آن را با همان کلید رمزگشایی کنیم. ولی این روش ضمن استاندارد نبودن، مشکلات خاص خودش را دارد و نیاز به سفارشی سازی زیادی، هم به هنگام تولید توکن و هم به هنگام خواندن توکن دارد.
 اصولی‌ترین راه، استفاده از رمزنگاری توکن به روش JSON Web Encryption (یا به اختصار JWE) است که در آن مشابه روش بالا ولی به صورت استاندارد تعریف شده (و قابل فهم برای همه استفاده کنندگانی که با این استاندارد آشنایی دارند) است.
نکته :
  1. اگر از JWE استفاده نمی‌کنید، بهتر است اطلاعات حساسی مانند شماره تلفن کاربر (و شاید در مواردی حتی آیدی کاربر) را در بدنه توکن قرار ندهیم چرا که قابل خوانده شدن است (که در این صورت استفاده از Guid برای آیدی کاربر می تواند کمی مفید باشد چرا که حداقل آیدی بقیه کاربران قابل پیش بینی نمی‌باشد).
  2. توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد؛ ولی به لطف امضای (signature) آن، در برابر تغییر محتوا، ایمن است؛ چرا که در صورت تغییر محتوای آن، دیگر مقدار hash محتوا با امضای آن همخوانی نداشته و عملا از اعتبار ساقط می‌گردد.
برای رمزنگاری JWT باید در هر دو مرحله‌ی "تولید توکن" و "اعتبارسنجی توکن" کلید و الگوریتم لازم برای رمزنگاری را مشخص کنیم. بدین منظور در جایی که توکن را تولید می‌کنیم، خواهیم داشت :
var secretKey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey"); // must be 16 character or longer 
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);

var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey"); //must be 16 character
var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);

var claims = new List<Claim>
{
   new Claim(ClaimTypes.Name, "UserName"), //user.UserName
   new Claim(ClaimTypes.NameIdentifier, "123"), //user.Id
};

var descriptor = new SecurityTokenDescriptor
{
   Issuer = _siteSetting.JwtSettings.Issuer,
   Audience = _siteSetting.JwtSettings.Audience,
   IssuedAt = DateTime.Now,
   NotBefore = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.NotBeforeMinutes),
   Expires = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.ExpirationMinutes),
   SigningCredentials = signingCredentials,
   EncryptingCredentials = encryptingCredentials,
   Subject = new ClaimsIdentity(claims)
};

var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(descriptor);
string encryptedJwt = tokenHandler.WriteToken(securityToken);
کد بالا، مانند کد تولید یک توکن jwt معمولی است؛ تنها تفاوت آن، ایجاد و معرفی شیء encryptingCredentials است.
در خط چهارم، آرایه بایتی کلید لازم برای رمزنگاری (encryptionkey) گرفته شده و از روی آن encryptingCredentials ایجاد شده‌است. این کلید باید 16 کاراکتر باشد؛ در غیر اینصورت به هنگام تولید توکن، خطا دریافت خواهید کرد. رمزنگاری توکن، توسط این کلید و الگوریتم مشخص شده انجام خواهد شد.
سپس شیء تولید شده، به خاصیت EncryptingCredentials کلاس SecurityTokenDescriptor معرفی شده‌است و  نهایتا متد tokenHandler.WriteToken توکن رمزنگاری شده‌ای را تولید می‌کند.
نتیجه کار این است که توکن تولید شده، بدون کلید مربوطه (که سمت سرور نگهداری می‌شود) قابل رمز گشایی نیست و اگر آن را در سایت jwt.io کپی کنید، جوابی دریافت نخواهید کرد.

در ادامه لازم است در مرحله اعتبار سنجی و رمزگشایی توکن در سمت سرور، کلید و الگوریتم لازم را به آن معرفی کنیم تا middleware مربوطه بتواند توکن دریافتی را رمزگشایی و سپس اعتبار سنجی کند. بدین منظور در متد ConfigureServices کلاس Startup.cs خواهیم داشت:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
   var secretkey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey");
   var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey");

   var validationParameters = new TokenValidationParameters
   {
      ClockSkew = TimeSpan.Zero, // default: 5 min
      RequireSignedTokens = true,

      ValidateIssuerSigningKey = true,
      IssuerSigningKey = new SymmetricSecurityKey(secretkey),

      RequireExpirationTime = true,
      ValidateLifetime = true,

      ValidateAudience = true, //default : false
      ValidAudience = "MyWebsite",

      ValidateIssuer = true, //default : false
      ValidIssuer = "MyWebsite",

      TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey)
   };

   options.RequireHttpsMetadata = false;
   options.SaveToken = true;
   options.TokenValidationParameters = validationParameters;
});

کد بالا مانند کد فعال سازی احراز هویت توسط JWT معمولی در ASP.NET Core است؛ با این تفاوت که:

ابتدا آرایه بایتی همان کلید رمزنگاری (encryptionkey) که قبلا توکن را با آن رمزنگاری کرده بودیم، گرفته شده و سپس توسط مقداردهی خاصیت TokenDecryptionKey کلاس TokenValidationParameters، معرفی شده است. 

ولی شاید این سؤال برایتان پیش آید که چرا الگوریتم رمزنگاری مشخص نشده است؟ پس سرور از کجا می‌فهمد که این توکن بر اساس چه الگوریتمی رمزنگاری شده است؟ 

دلیل آن این است که به هنگام تولید توکن، اسم الگوریتم مربوطه، داخل بخش header توکن نوشته می‌شود. اگر تصویر قبل را مشاهده کنید مقدار header توکن به شرح زیر است.

{
  "alg": "A128KW",
  "enc": "A128CBC-HS256",
  "typ": "JWT"
}

پس سرور بر اساس این قسمت از توکن (header)، که هیچگاه رمزنگاری نمی‌شود، می‌فهمد که توسط چه الگوریتمی باید توکن را رمزگشایی کند که در اینجا A128CBC-HS256 (اختصار AES-128-CBC و HMAC-SHA256) است.

مثال کامل و قابل اجرای این مطلب را می‌توانید از این ریپازیتوری دریافت کنید.