نظرات مطالب
اعمال توابع تجمعی بر روی چند ستون در Entity framework
چگونه توسط EF Core، چندین کوئری را یکجا به بانک اطلاعاتی ارسال کنیم؟

روشی را که در این مطلب مشاهده کردید، در موارد مشابه دیگری هم قابل استفاده‌است. برای مثال فرض کنید اطلاعات یک مشتری را قرار است به صورت زیر ذخیره کنیم:
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public CustomerType Type { get; set; }
}

public enum CustomerType
{
    Individual,
    Institution,
}

حالت عادی کوئری گرفتن از اطلاعات جدول آن که به همراه صفحه بندی، نمایش تعداد رکوردها و یک کوئری دلخواه دیگر باشد، به صورت زیر است:
void ManyQueriesManyCalls()
{
    using var scope = serviceProvider.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<CustomerContext>();

    var baseQuery = context.Customers.Select(customer => new
                                                         {
                                                             customer.Name,
                                                             customer.Type,
                                                             customer.Id,
                                                         });
    var total = baseQuery.Count();
    var types = baseQuery.GroupBy(x => x.Type)
                         .Select(x => x.Key).ToList();
    var pageSize = 10;
    var pageIndex = 0;
    var results = baseQuery
                  .OrderBy(x => x.Id)
                  .Skip(pageSize * pageIndex)
                  .Take(pageSize)
                  .ToList();
    Console.WriteLine($"Total:{total}, First Type: {types.First()}, First Item: {results.First().Name}");
}
که سبب می‌شود سه کوئری و سه بار رفت و برگشت را به بانک اطلاعاتی داشته باشیم:
      SELECT COUNT(*)
      FROM [Customers] AS [c]

      SELECT [c].[Type]
      FROM [Customers] AS [c]
      GROUP BY [c].[Type]
  
      SELECT [c].[Name], [c].[Type], [c].[Id]
      FROM [Customers] AS [c]
      ORDER BY [c].[Id]
      OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY

اگر بخواهیم این سه کوئری را یکبار به سمت بانک اطلاعاتی ارسال کنیم، می‌توان از همان ترفند گروه بندی مطرح شده‌ی در این مثال برای ترکیب کوئری‌ها استفاده کرد:
void ManyQueriesOnCall()
{
    using var scope = serviceProvider.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<CustomerContext>();
    var baseQuery = context.Customers.Select(customer => new
                                                         {
                                                             customer.Name,
                                                             customer.Type,
                                                             customer.Id,
                                                         });
    var pageSize = 10;
    var pageIndex = 0;
    var allTogether = baseQuery
                      .GroupBy(x => 1)
                      .Select(bq => new
                                    {
                                        Total = baseQuery.Count(),
                                        Types = baseQuery.GroupBy(x => x.Type)
                                                         .Select(x => x.Key)
                                                         .ToList(),
                                        Results = baseQuery
                                                  .OrderBy(x => x.Id)
                                                  .Skip(pageSize * pageIndex)
                                                  .Take(pageSize)
                                                  .ToList(),
                                    })
                      .FirstOrDefault();

    Console.WriteLine($"Total:{allTogether.Total}, First Type: {allTogether.Types.First()}, First Item: {allTogether.Results.First().Name}");
}
که اینبار فقط یک کوئری outer apply دار را تولید می‌کند و فقط یکبار، رفت و برگشت به بانک اطلاعاتی را شاهد خواهیم بود:
      SELECT [t0].[Key], [t1].[Type], [t2].[Name], [t2].[Type], [t2].[Id]
      FROM (
          SELECT TOP(1) [t].[Key]
          FROM (
              SELECT 1 AS [Key]
              FROM [Customers] AS [c]
          ) AS [t]
          GROUP BY [t].[Key]
      ) AS [t0]
      OUTER APPLY (
          SELECT [c0].[Type]
          FROM [Customers] AS [c0]
          GROUP BY [c0].[Type]
      ) AS [t1]
      OUTER APPLY (
          SELECT [c1].[Name], [c1].[Type], [c1].[Id]
          FROM [Customers] AS [c1]
          ORDER BY [c1].[Id]
          OFFSET @__p_1 ROWS FETCH NEXT @__pageSize_2 ROWS ONLY
      ) AS [t2]
      ORDER BY [t0].[Key], [t1].[Type], [t2].[Id]

کدهای این مثال را از اینجا می‌توانید دریافت کنید: EF7ManyQueriesOneCall.zip
نظرات مطالب
متدهای کمکی مفید در پروژه های asp.net mvc
public static class HtmlHelperExtensions
    {
        private const string Nbsp = "&nbsp;";
        private const string SelectedAttribute = " selected='selected'";
 
        public static MvcHtmlString NbspIfEmpty(this HtmlHelper helper, string value)
        {
            return new MvcHtmlString(string.IsNullOrEmpty(value) ? Nbsp : value);
        }
 
        public static MvcHtmlString SelectedIfMatch(this HtmlHelper helper, object expected, object actual)
        {
            return new MvcHtmlString(Equals(expected, actual) ? SelectedAttribute : string.Empty);
        }
    }



و یک مثال جهت استفاده

<select>
   @foreach (var item in ViewBag.Items)
   {
      <option@Html.SelectedIfMatch((string)ViewBag.SelectedItem,
(string)item.ItemName)>@item.ItemName</option> } </select>

مطالب
شروع کار با GraphQL در ASP.NET Core
در این مقاله هدف این است که GraphQL را در ASP.NET Core راه اندازی کنیم. از یک کتابخانه ثالث برای آسان‌تر کردن یکپارچگی استفاده می‌کنیم و همچنین با جزئیات، توضیح خواهیم داد که چگونه می‌توان از element‌های مربوط به GraphQL مثل (Type ،Query و Schema) برای کامل کردن فرآیند یکپارچگی در ASP.NET Core استفاده کنیم.

GraphQL  و تفاوت‌های آن با REST
GraphQl یک query language می‌باشد که query‌ها را با استفاده از type system‌‌‌هایی که ما برای داده‌ها تعریف می‌کنیم، اجرا می‌کند. GraphQL  به هیچ زبان یا پایگاه داده مشخصی گره نخورده است. 

  •  GraphQL نیازمند رفت و برگشت‌های کمتری به server، به منظور بازیابی داده‌ها برای template یا view است. همراه با REST ما مجبور هستیم که چندیدن endpoint مثلا  (... api/students, api/courses, api/instructors ) را برای گرفتن همه داده‌های که برای template یا view نیاز داریم ملاقات کنیم؛ ولی این شرایط در GraphQL برقرار نیست. با GraphQL ما تنها یک query را ایجاد می‌کنیم که چندین تابع (resolver) را در سمت سرور فراخوانی می‌کند و همه داده‌ها را از منابع مختلفی، در یک درخواست برگشت می‌دهد. 

  • همراه با REST، همانطور که Application ما رشد می‌کند، تعداد endpoint‌ها هم زیاد می‌شوند که این نیازمند زمان بیشتری برای نگهداری می‌باشد. اما با GraphQL ما تنها یک endpoint  داریم؛ همین! 
  • با استفاده از GraphQL، ما هرگز به مشکل گرفتن داده‌هایی کم یا زیاد از منبع روبرو نخواهیم شد. به این خاطر است که ما query‌ها را با فیلد‌هایی که چیز‌هایی را که نیاز داریم، نشان می‌دهند، تعریف می‌کنیم. در این صورت ما همیشه چیز‌هایی را که درخواست داده‌ایم، دریافت می‌کنیم.

بنابراین اگر یک query شبیه زیر را ارسال کنیم :
query OwnersQuery {
  owners {
    name
    account {
      type
    }
  } 
}
صد درصد مطمئن هستیم که خروجی زیر برگشت داده می‌شود:
{
  "data": {
    "owners": [
     {
      "name": "John Doe",
      "accounts": [
        {
          "type": "Cash"
        },
        {
          "type": "Savings"
        }
      ]
     }
    ]
  }
}

همراه با REST  این شرایط برقرار نمی‌باشد. بعضی از مواقع ما چیزی بیشتر یا کمتر از آنچه که نیاز داریم دریافت می‌کنیم؛ که این بستگی به اینکه چگونه action‌ها در یک endpoint مشخص، پیاده سازی شده‌اند، دارد.  

شروع کار 
یک پروژه جدید را با استفاده از دستور زیر ایجاد می‌کنیم: 
dotnet new api -n ASPCoreGraphQL
سپس ساختار زیر را ایجاد می‌کنیم :


پوشه Contracts شامل واسط‌های مورد نیاز برای repository logic می‌باشد:

namespace ASPCoreGraphQL.Contracts
{
    public interface IOwnerRepository
    {
    }
}
namespace ASPCoreGraphQL.Contracts
{
    public interface IAccountRepository
    {
    }
}


در پوشه Models، کلاس‌های مدل را نگه داری می‌کنیم؛ به همراه یک کلاس context و کلاس‌های configuration:
public class Owner
{
    [Key]
    public Guid Id { get; set; }
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }
    public string Address { get; set; }
 
    public ICollection<Account> Accounts { get; set; }
}

public class Account
{
    [Key]
    public Guid Id { get; set; }
    [Required(ErrorMessage = "Type is required")]
    public TypeOfAccount Type { get; set; }
    public string Description { get; set; }
 
    [ForeignKey("OwnerId")]
    public Guid OwnerId { get; set; }
    public Owner Owner { get; set; }
}

public enum TypeOfAccount
{
    Cash,
    Savings,
    Expense,
    Income
}
    public class ApplicationContext : DbContext
    {
        public ApplicationContext(DbContextOptions options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {

        }

        public DbSet<Owner> Owners { get; set; }
        public DbSet<Account> Accounts { get; set; }
    }

در پوشه Repository، کلاس‌های مرتبط با منطق بازیابی داده‌ها را داریم:
public class OwnerRepository : IOwnerRepository
{
    private readonly ApplicationContext _context;
 
    public OwnerRepository(ApplicationContext context)
    {
        _context = context;
    }
}
public class AccountRepository : IAccountRepository
{
    private readonly ApplicationContext _context;
 
    public AccountRepository(ApplicationContext context)
    {
        _context = context;
    }
}
repository logic، یک راه اندازی ابتدایی بدون هیچ لایه اضافه‌تری است.

کلاس context و کلاس‌های repository، در فایل Startup.cs ثبت می‌شوند:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationContext>(opt =>
         opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 
 
    services.AddScoped<IOwnerRepository, OwnerRepository>();
    services.AddScoped<IAccountRepository, AccountRepository>();
 
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
}


Integration of GraphQL in ASP.NET Core 
برای کار کردن با GraphQL در ابتدا نیاز است که کتابخانه GraphQL را نصب کنیم. به همین منظور در ترمینال مربوط به VS Code، دستور زیر را وارد می‌کنیم: 
dotnet add package GraphQL
و همچنین کتابخانه‌ی زیر که به ما کمک می‌کند تا GraphQL.NET را به عنوان یک وابستگی دریافت کنیم:
dotnet add package GraphQL.Server.Transports.AspNetCore
و در نهایت نصب کتابخانه زیر که کمک می‌کند query‌های GraphQL را به سرور ارسال کنیم:
dotnet add package GraphQL.Server.Ui.Playground

(Creating GraphQL Specific Objects (Type, Query, Schema

کار را با ایجاد کردن یک پوشه جدید به نام GraphQL و سپس در آن ایجاد یک پوشه دیگر به نام GraphQLSchema، شروع می‌کنیم. در پوشه GraphQLSchema، یک کلاس را به نام AppSchema، ایجاد می‌کنیم.
کلاس AppSchema باید از کلاس Schema ارث بری کند تا در فضای نام GraphQL.Types قرار گیرد. در سازنده این کلاس، IDependencyResolver را تزریق می‌کنیم که قرار است به ما کمک کند تا اشیاء Query ،Mutation یا Subscription را resolve کنیم:
public class AppSchema : Schema
{
    public AppSchema(IDependencyResolver resolver)
        :base(resolver)
    {
 
    }
}
چیزی که مهم است بدانیم این است که خصوصیات schema، مثل Query ،Mutation و Subscription، واسط IObjectGraphType را پیاده سازی می‌کنند تا اشیائی را که قرار است resolve کنیم، همین type را پیاده سازی کرده باشند. در ضمن GraphQL API نمی‌تواند مدل را به عنوان نتیجه به صورت مستقیم بازگشت دهد.
فعلا این کلاس را در همین حالت می‌گذاریم و سپس یک پوشه را به نام GraphQLTypes در پوشه GraphQL ایجاد می‌کنیم. در پوشه GraphQLTypes یک کلاس را به نام OwnerType ایجاد می‌کنیم:
public class OwnerType : ObjectGraphType<Owner>
{
    public OwnerType()
    {
        Field(x => x.Id, type: typeof(IdGraphType)).Description("Id property from the owner object.");
        Field(x => x.Name).Description("Name property from the owner object.");
        Field(x => x.Address).Description("Address property from the owner object.");
    }
}
از کلاس OwnerType به عنوان یک جایگزین برای مدل Owner درون یک GraphQL API استفاده می‌کنیم. این کلاس از نوع جنریک ObjectGraphType ارث بری می‌کند. با متد Field، فیلد‌هایی را که بیانگر خصوصیات مدل Owner می‌باشند، مشخص می‌کنیم.
در ابتدا واسط IOwnerRepository و کلاس  OwnerRepository را به حالت زیر ویرایش می‌کنیم:
public interface IOwnerRepository
{
    IEnumerable<Owner> GetAll();
}


public class OwnerRepository : IOwnerRepository
{
    private readonly ApplicationContext _context;
 
    public OwnerRepository(ApplicationContext context)
    {
        _context = context;
    }
 
    public IEnumerable<Owner> GetAll() => _context.Owners.ToList();
}

در ادامه یک پوشه دیگر را به نام GraphQLQueries در پوشه‌ی GraphQL ایجاد و سپس در آن یک کلاس را به نام AppQuery ایجاد و آن را به حالت زیر ویرایش می‌کنیم:
public class AppQuery : ObjectGraphType
{
    public AppQuery(IOwnerRepository repository)
    {
        Field<ListGraphType<OwnerType>>(
           "owners",
           resolve: context => repository.GetAll()
       );
    }
}

توضیحات AppQuery
همانطور که می‌بینیم این کلاس از ObjectGraphType ارث بری می‌کند. در سازنده کلاس، IOwnerRepository را تزریق می‌کنیم و یک فیلد را به منظور برگشت دادن نتیجه برای یک Query مشخص، ایجاد می‌کنیم. در این کلاس، از نوع جنریک متد Field، استفاده کرده‌ایم که تعدادی type را به عنوان یک پارامتر جنریک پذیرش می‌کند که این بیانگر GraphQL.NET برای type ‌های معمول در NET. می‌باشد. علاوه بر ListGraphType،  نوع‌هایی مثل IntGraphType  و StringGraphType و ... وجود دارند (لیست کامل).
پارامتر owners نام فیلد می‌باشد (query مربوط به کلاینت باید با این نام مطابقت داشته باشد) و پارامتر دوم نتیجه می‌باشد.
بعد از انجام این مقدمات، اکنون کلاس AppSchema  را باز می‌کنیم و به حالت زیر آن را ویرایش می‌کنیم:
public class AppSchema : Schema
{
    public AppSchema(IDependencyResolver resolver)
        :base(resolver)
    {
        Query = resolver.Resolve<AppQuery>();
    }
}

Libraries and Schema Registration 
 در کلاس Startup نیاز است کتابخانه‌های نصب شده و هم چنین  کلاس schema ایجاد شده را ثبت کنیم. این کار را با ویرایش کردن متد ConfigureServices  و Configure  انجام میدهیم:
public void ConfigureServices(IServiceCollection services)
{
    ...
 
    services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
    services.AddScoped<AppSchema>();
 
    services.AddGraphQL(o => { o.ExposeExceptions = false; })
        .AddGraphTypes(ServiceLifetime.Scoped);

   ...
}
در متد ConfigureServices، در ابتدا DependencyResolver و سپس کلاس schema را ثبت می‌کنیم. علاوه بر آن، GraphQL را با متد AddGraphQL ثبت می‌کنیم. سپس همه تایپ‌های GraphQL را با متد AddGraphTypes ثبت می‌کنیم. بدون این متد (AddGraphTypes)، ما مجبور بودیم که همه type ‌ها و query ‌ها را به صورت دستی در APIامان ثبت کنیم.
در نهایت در متد schema  Configure را به خط لوله درخواست‌ها (request’s pipeline) اضافه می‌کنیم و هم چنین ابزار Playground UI:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   ...
    app.UseGraphQL<AppSchema>();
    app.UseGraphQLPlayground(options: new GraphQLPlaygroundOptions());
    app.UseMvc();
}

برای آزمایش GraphQL API امان، از ابزار GraphQL.UI.Playground استفاده می‌کنیم. در ابتدا پروژه را با دستور زیر اجرا می‌کنیم:
dotnet run
و سپس در مرورگر به این آدرس می‌رویم: 
https://localhost:5001/ui/playground
اکنون یک query را با نام owners ارسال می‌کنیم (این نام باید با نام query که در AppQuery در نظر گرفتیم، مطابقت داشته باشد). سپس نتیجه لازم را دریافت می‌کنیم. همانطور که مشاهده می‌کنیم، همه چیز بر اساس انتظاری که می‌رفت کار می‌کند. 



در قسمت بعد درابطه با  کوئری‌های پیشرفته، Error Handling و Data Loader در GraphQL  صحبت خواهیم کرد. 

کد‌های مربوط به این قسمت را از اینجا دریافت کنید .ASPCoreGraphQL.zip
اشتراک‌ها
بهبود ArgumentExceptions در C# 10 و ‎.NET 6
public bool DoSomething(string id, string name, string favouriteColour)
{
    //if (id is null)
    //{
    //   throw new ArgumentNullException(nameof(id));
    //}
    
    ArgumentNullException.ThrowIfNull(id);
   //...
}


بهبود ArgumentExceptions در C# 10 و ‎.NET 6
مطالب
رمزنگاری فایل‌های PDF با استفاده از کلید عمومی توسط iTextSharp

دو نوع رمزنگاری را می‌توان توسط iTextSharp به PDF تولیدی و یا موجود، اعمال کرد:
الف) رمزنگاری با استفاده از کلمه عبور
ب) رمزنگاری توسط کلید عمومی

الف) رمزنگاری با استفاده از کلمه عبور
در اینجا امکان تنظیم read password و edit password به کمک متد SetEncryption شیء pdfWrite وجود دارد. همچنین می‌توان مشخص کرد که مثلا آیا کاربر می‌تواند فایل PDF را چاپ کند یا خیر (PdfWriter.ALLOW_PRINTING).
ذکر read password اختیاری است؛ اما جهت اعمال permissions حتما نیاز است تا edit password ذکر گردد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Text;

namespace EncryptPublicKey
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

var readPassword = Encoding.UTF8.GetBytes("123");//it can be null.
var editPassword = Encoding.UTF8.GetBytes("456");
int permissions = PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY;
pdfWriter.SetEncryption(readPassword, editPassword, permissions, PdfWriter.STRENGTH128BITS);

pdfDoc.Open();

pdfDoc.Add(new Phrase("tst 0"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("tst 1"));
}

Process.Start("TestEnc.pdf");
}
}
}


اگر read password ذکر شود، کاربران برای مشاهده محتویات فایل نیاز خواهند داشت تا کلمه‌ی عبور مرتبط را وارد نمایند:


این روش آنچنان امنیتی ندارد. هستند برنامه‌هایی که این نوع فایل‌ها را «آنی» به نمونه‌ی غیر رمزنگاری شده تبدیل می‌کنند (حتی نیازی هم ندارند که از شما کلمه‌ی عبوری را سؤال کنند). بنابراین اگر کاربران شما آنچنان حرفه‌ای نیستند، این روش خوب است؛ در غیراینصورت از آن صرفنظر کنید.


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

برای شروع به کار با public key encryption نیاز است یک فایل PFX یا Personal Information Exchange داشته باشیم. یا می‌توان این نوع فایل‌ها را از CA's یا Certificate Authorities خرید، که بسیار هم نیکو یا اینکه می‌توان فعلا برای آزمایش، نمونه‌ی self signed این‌ها را هم تهیه کرد. مثلا با استفاده از این برنامه.


در ادامه نیاز خواهیم داشت تا اطلاعات این فایل PFX را جهت استفاده توسط iTextSharp استخراج کنیم. کلاس‌های زیر اینکار را انجام می‌دهند و نهایتا کلیدهای عمومی و خصوصی ذخیره شده در فایل PFX را بازگشت خواهند داد:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
/// <summary>
/// A Personal Information Exchange File Info
/// </summary>
public class PfxData
{
/// <summary>
/// Represents an X509 certificate
/// </summary>
public X509Certificate[] X509PrivateKeys { set; get; }

/// <summary>
/// Certificate's public key
/// </summary>
public ICipherParameters PublicKey { set; get; }
}
}

using System;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
/// <summary>
/// A Personal Information Exchange File Reader
/// </summary>
public class PfxReader
{
X509Certificate[] _chain;
AsymmetricKeyParameter _asymmetricKeyParameter;

/// <summary>
/// Reads A Personal Information Exchange File.
/// </summary>
/// <param name="pfxPath">Certificate file's path</param>
/// <param name="pfxPassword">Certificate file's password</param>
public PfxData ReadCertificate(string pfxPath, string pfxPassword)
{
using (var stream = new FileStream(pfxPath, FileMode.Open, FileAccess.Read))
{
var pkcs12Store = new Pkcs12Store(stream, pfxPassword.ToCharArray());
var alias = findThePublicKey(pkcs12Store);
_asymmetricKeyParameter = pkcs12Store.GetKey(alias).Key;
constructChain(pkcs12Store, alias);
return new PfxData { X509PrivateKeys = _chain, PublicKey = _asymmetricKeyParameter };
}
}

private void constructChain(Pkcs12Store pkcs12Store, string alias)
{
var certificateChains = pkcs12Store.GetCertificateChain(alias);
_chain = new X509Certificate[certificateChains.Length];

for (int k = 0; k < certificateChains.Length; ++k)
_chain[k] = certificateChains[k].Certificate;
}

private static string findThePublicKey(Pkcs12Store pkcs12Store)
{
string alias = string.Empty;
foreach (string entry in pkcs12Store.Aliases)
{
if (pkcs12Store.IsKeyEntry(entry) && pkcs12Store.GetKey(entry).Key.IsPrivate)
{
alias = entry;
break;
}
}

if (string.IsNullOrEmpty(alias))
throw new NullReferenceException("Provided certificate is invalid.");

return alias;
}
}
}


اکنون رمزنگاری فایل PDF تولیدی توسط کلید عمومی، به سادگی چند سطر کد زیر خواهد بود:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
pdfWriter.SetEncryption(
certs: certs.X509PrivateKeys,
permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
encryptionType: PdfWriter.ENCRYPTION_AES_128);

pdfDoc.Open();

pdfDoc.Add(new Phrase("tst 0"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("tst 1"));
}

Process.Start("Test.pdf");
}
}
}

پیش از فراخوانی متد Open باید تنظیمات رمزنگاری مشخص شوند. در اینجا ابتدا فایل PFX خوانده شده و کلیدهای عمومی و خصوصی آن استخراج می‌شوند. سپس به متد SetEncryption جهت استفاده نهایی ارسال خواهند شد.

نحوه استفاده از این نوع فایل‌های رمزنگاری شده:
اگر سعی در گشودن این فایل رمزنگاری شده نمائیم با خطای زیر مواجه خواهیم شد:


کاربران برای اینکه بتوانند این فایل‌های PDF را بار کنند نیاز است تا فایل PFX شما را در سیستم خود نصب کنند. ویندوز فایل‌های PFX را می‌شناسد و نصب آن‌ها با دوبار کلیک بر روی فایل و چندبار کلیک بر روی دکمه‌ی Next و وارد کردن کلمه عبور آن، به پایان می‌رسد.

سؤال: آیا می‌توان فایل‌های PDF موجود را هم به همین روش رمزنگاری کرد؟
بله. iTextSharp علاوه بر PdfWriter دارای PdfReader نیز می‌باشد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
class Program
{
static void Main(string[] args)
{
PdfReader reader = new PdfReader("TestDec.pdf");
using (var stamper = new PdfStamper(reader, new FileStream("TestEnc.pdf", FileMode.Create)))
{
var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
stamper.SetEncryption(
certs: certs.X509PrivateKeys,
permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
encryptionType: PdfWriter.ENCRYPTION_AES_128);
stamper.Close();
}

Process.Start("TestEnc.pdf");
}
}
}


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

مطالب
پیاده سازی Open Search در ASP.NET MVC
اگر به امکانات مرورگرهای جدید دقت کرده باشید، امکان تعریف منبع جستجوی جدید، نیز برای آن‌ها وجود دارد. برای نمونه تصاویر ذیل مرتبط به مرورگرهای فایرفاکس و کروم هستند:




این مرورگرها در صورتیکه پیاده سازی پروتکل Open Search را در سایت شما پیدا کنند، به صورت خودکار امکان افزودن آن‌را به عنوان منبع جستجوی جدیدی جهت جعبه متنی جستجوی خود ارائه می‌دهند. در ادامه قصد داریم با جزئیات پیاده سازی آن آشنا شویم.


تهیه OpenSearchResult سفارشی

برنامه باید بتواند محتوای XML ایی ذیل را مطابق پروتکل Open Search به صورت پویا تهیه و در اختیار مرورگر قرار دهد:
<?xml version="1.0" encoding="UTF-8" ? />
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
    <ShortName>My Site's Asset Finder</ShortName>
    <Description>Find all your assets</Description>
    <Url type="text/html"
        method="get"
        template="http://MySite.com/Home/Search/?q=searchTerms"/>
    <InputEncoding>UTF-8</InputEncoding>
    <SearchForm>http://MySite.com/</SearchForm>
</OpenSearchDescription>
به همین جهت کلاس OpenSearchResult ذیل تهیه شده است تا انجام آن‌را با روشی سازگار با ASP.NET MVC سهولت بخشد:
using System;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Xml;

namespace WebToolkit
{
    public class OpenSearchResult : ActionResult
    {
        public string ShortName { set; get; }
        public string Description { set; get; }
        public string SearchForm { set; get; }
        public string FavIconUrl { set; get; }
        public string SearchUrlTemplate { set; get; }

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

            var response = context.HttpContext.Response;
            writeToResponse(response);
        }

        private void writeToResponse(HttpResponseBase response)
        {
            response.ContentEncoding = Encoding.UTF8;
            response.ContentType = "application/opensearchdescription+xml";
            using (var xmlWriter = XmlWriter.Create(response.Output, new XmlWriterSettings { Indent = true }))
            {
                xmlWriter.WriteStartElement("OpenSearchDescription", "http://a9.com/-/spec/opensearch/1.1/");

                xmlWriter.WriteElementString("ShortName", ShortName);
                xmlWriter.WriteElementString("Description", Description);
                xmlWriter.WriteElementString("InputEncoding", "UTF-8");
                xmlWriter.WriteElementString("SearchForm", SearchForm);

                xmlWriter.WriteStartElement("Url");
                xmlWriter.WriteAttributeString("type", "text/html");
                xmlWriter.WriteAttributeString("template", SearchUrlTemplate);                
                xmlWriter.WriteEndElement();

                xmlWriter.WriteStartElement("Image");
                xmlWriter.WriteAttributeString("width", "16");
                xmlWriter.WriteAttributeString("height", "16");
                xmlWriter.WriteString(FavIconUrl);
                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndElement();
                xmlWriter.Close();
            }
        }
    }
}
کار این Action Result، تهیه محتوایی XML ایی مطابق نمونه‌ای است که در ابتدای توضیحات ملاحظه نمودید. توضیحات خواص آن‌، در ادامه مطلب ارائه شده‌اند.


تهیه OpenSearchController

در ادامه برای استفاده از Action Result سفارشی تهیه شده، نیاز است یک کنترلر را نیز به برنامه اضافه کنیم:
using System.Web.Mvc;

namespace Readers
{
    public partial class OpenSearchController : Controller
    {
        public virtual ActionResult Index()
        {
            var fullBaseUrl = Url.Action(result: MVC.Home.Index(), protocol: "http");
            return new OpenSearchResult
            {
                ShortName = ".NET Tips",
                Description = ".NET Tips Contents Search",
                SearchForm = fullBaseUrl,
                FavIconUrl = fullBaseUrl + "favicon.ico",
                SearchUrlTemplate = Url.Action(result: MVC.Search.Index(), protocol: "http") + "?term={searchTerms}"
            };
        }
    }
}
برای استفاده از OpenSearchResult به چند نکته باید دقت داشت:
الف) آدرس‌های مطرح شده در آن باید مطلق باشند و نه نسبی. به همین جهت پارامتر protocol در اینجا ذکر شده است تا سبب تولید یک چنین آدرس‌هایی گردد.
ب) Url.Action ایی که در اینجا استفاده شده است مطابق تعاریف T4MVC است؛ ولی کلیات آن با نمونه پیش فرض ASP.NET MVC تفاوتی نمی‌کند. توسط T4MVC بجای ذکر نام اکشن متد و کنترلر مد نظر به صورت رشته‌ای، می‌توان به صورت Strongly typed به این موارد ارجاع داد.
ج) تنها نکته مهم این کلاس، خاصیت SearchUrlTemplate است. قسمت انتهایی آن یعنی ={searchTerms} همیشه ثابت است. اما ابتدای این آدرس باید به کنترلر جستجوی شما که قادر است پارامتری را به شکل کوئری استرینگ دریافت کند، اشاره نماید.
د) FavIconUrl به آدرس یک آیکن در سایت شما اشاره می‌کند. برای نمونه ذکر favicon.ico پیش فرض سایت می‌تواند مفید باشد.


معرفی OpenSearchController به Header سایت

<link href="@Url.Action(result: MVC.OpenSearch.Index(), protocol: "http")" rel="search"
        title=".NET Tips Search"  type="application/opensearchdescription+xml" />
مرحله نهایی افزودن پروتکل Open search به سایت، مراجعه به فایل layout پروژه و افزودن link خاص فوق به آن است. در این لینک، href آن باید به مسیر کنترلر OpenSearchایی که در قسمت قبل تعریف کردیم، اشاره کند. این مسیر نیز باید مطلق باشد. به همین جهت پارامتر protocol آن مقدار دهی شده است.
مطالب
شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها
در مورد «امکانات توکار تزریق وابستگی‌ها در ASP.NET Core» پیشتر بحث شد. همچنین «نحوه‌ی تعریف Context، تزریق سرویس‌های EF Core و تنظیمات رشته‌ی اتصالی آن» را نیز بررسی کردیم. به علاوه مباحث «به روز رسانی ساختار بانک اطلاعاتی» و «انتقال مهاجرت‌ها به یک اسمبلی دیگر» نیز مرور شدند. بنابراین در این قسمت برای لایه بندی برنامه‌های EF Core، صرفا یک مثال را مرور خواهیم کرد که این قسمت‌ها را در کنار هم قرار می‌دهد و عملا نکته‌ی اضافه‌تری را ندارد.


تزریق مستقیم کلاس Context برنامه، تزریق وابستگی‌ها نام ندارد!

در همان قسمت اول سری شروع به کار با EF Core 1.0، مشاهده کردیم که پس از انجام تنظیمات اولیه‌ی آن در کلاس آغازین برنامه:
public void ConfigureServices(IServiceCollection services)
{    
   services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
Context برنامه را در تمام قسمت‌های آن می‌توان تزریق کرد و کار می‌کند:
    public class TestDBController : Controller
    {
        private readonly ApplicationDbContext _ctx;

        public TestDBController(ApplicationDbContext ctx)
        {
            _ctx = ctx;
        }

        public IActionResult Index()
        {
            var name = _ctx.Persons.First().FirstName;
            return Json(new { firstName = name });
        }
    }
این روشی است که در بسیاری از مثال‌های گوشه و کنار اینترنت قابل مشاهده‌است. یا کلاس Context را مستقیما در سازنده‌ی کنترلرها تزریق می‌کنند و از آن استفاده می‌کنند (روش فوق) و یا لایه‌ی سرویسی را ایجاد کرده و مجددا همین تزریق مستقیم را در آنجا انجام می‌دهند و سپس اینترفیس‌های آن سرویس را در کنترلرهای برنامه تزریق کرده و استفاده می‌کنند. به این نوع تزریق وابستگی‌ها، تزریق concrete types و یا concrete classes می‌گویند.
مشکلاتی را که تزریق مستقیم کلاس‌ها و نوع‌ها به همراه دارند به شرح زیر است:
- اگر نام این کلاس تغییر کند، باید این نام، در تمام کلاس‌هایی که به صورت مستقیم از آن استفاده می‌کنند نیز تغییر داده شود.
- اگر سازنده‌ای به آن اضافه شد و یا امضای سازنده‌ی موجود آن، تغییر کرد، باید نحوه‌ی وهله سازی این کلاس را در تمام کلاس‌های وابسته نیز اصلاح کرد.
- یکی از مهم‌ترین دلایل استفاده‌ی از تزریق وابستگی‌ها، بالابردن قابلیت تست پذیری برنامه است. زمانیکه از اینترفیس‌ها استفاده می‌شود، می‌توان در مورد نحوه‌ی تقلید (mocking) رفتار کلاسی خاص، مستقلا تصمیم گیری کرد. اما هنگامیکه یک کلاس را به همان شکل اولیه‌ی آن تزریق می‌کنیم، به این معنا است که همواره دقیقا همین پیاده سازی خاص مدنظر ما است و این مساله، نوشتن آزمون‌های واحد را با مشکل کردن mocking آن‌ها، گاهی از اوقات غیرممکن می‌کند. هرچند تعدادی از فریم ورک‌های پیشرفته‌ی mocking گاهی از اوقات امکان تقلید رفتار کلاس‌ها و نوع‌ها را نیز فراهم می‌کنند، اما با این شرط که تمام خواص و متدهای آن‌ها را virtual تعریف کنید؛ تا بتوانند متدهای اصلی را با نمونه‌های مدنظر شما بازنویسی (override) کنند.

به همین جهت در ادامه، به همان طراحی EF Code First #12 با نوشتن اینترفیس IUnitOfWork خواهیم رسید. یعنی کلاس Context برنامه را با این اینترفیس نشانه گذاری می‌کنیم (در انتهای لیست تمام اینترفیس‌های دیگری که ممکن است در اینجا ذکر شده باشند):
 public class ApplicationDbContext :  IUnitOfWork
و سپس اینترفیس IUnitOfWork را به لایه سرویس برنامه و یا هر لایه‌ی دیگری که به Context آن نیاز دارد، تزریق خواهیم کرد.


طراحی اینترفیس IUnitOfWork

برای اینکه دیگر با کلاس ApplicationDbContext مستقیما کار نکرده و وابستگی به آن‌را در تمام قسمت‌های برنامه پخش نکنیم، اینترفیسی را ایجاد می‌کنیم که تنها قسمت‌های مشخصی از DbContext را عمومی کند:
public interface IUnitOfWork : IDisposable
{
    DbSet<TEntity> Set<TEntity>() where TEntity : class;
 
    void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
    void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
 
    EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
    void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;
 
    void ExecuteSqlCommand(string query);
    void ExecuteSqlCommand(string query, params object[] parameters);
 
    int SaveAllChanges();
    Task<int> SaveAllChangesAsync();
}
توضیحات
- در این طراحی شاید عنوان کنید که DbSet، اینترفیس نیست. تعریف DbSet در EF Core به صورت زیر است و در حقیقت همانند اینترفیس‌ها یک abstraction به حساب می‌آید:
 public abstract class DbSet<TEntity> : IQueryable<TEntity>, IEnumerable<TEntity>, IEnumerable, IQueryable, IAsyncEnumerableAccessor<TEntity>, IInfrastructure<IServiceProvider> where TEntity : class
علت اینکه در پروژه‌های بزرگی مانند EF، تمایل زیادی به استفاده‌ی از کلاس‌های abstract وجود دارد (بجای اینترفیس‌ها) این است که اگر این نوع پرکاربرد را به صورت اینترفیس تعریف کنند، با تغییر متدی در آن، باید تمام کدهای خود را به اجبار بازنویسی کنید. اما در حالت استفاده‌ی از کلاس‌های abstract، می‌توان پیاده سازی پیش فرضی را برای متدهایی که قرار است در آینده اضافه شوند، ارائه داد (یکی از تفاوت‌های مهم آن‌ها با اینترفیس‌ها)، بدون اینکه تمام استفاده کنندگان از این کتابخانه، با ارتقاء نگارش EF خود، دیگر نتوانند برنامه‌ی خود را کامپایل کنند.
- این اینترفیس به عمد به صورت IDisposable تعریف شده‌است. این مساله به IoC Containers کمک خواهد کرد که بتوانند پاکسازی خودکار نوع‌های IDisposable را در انتهای هر درخواست انجام دهند و برنامه مشکلی نشتی حافظه را پیدا نکند.
- اصل کار این اینترفیس، تعریف DbSet و متدهای SaveChanges است. سایر متدهایی را که مشاهده می‌کنید، صرفا جهت بیان اینکه چگونه می‌توان قابلیتی از DbContext را بدون عمومی کردن خود کلاس DbContext، در کلاس‌هایی که از اینترفیس IUnitOfWork استفاده می‌کنند، میسر کرد.

پس از اینکه این اینترفیس تعریف شد، اعمال آن به کلاس Context برنامه به صورت ذیل خواهد بود:
public class ApplicationDbContext : DbContext, IUnitOfWork
{
    private readonly IConfigurationRoot _configuration;
 
    public ApplicationDbContext(IConfigurationRoot configuration)
    {
        _configuration = configuration;
    }
 
    //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    //{
    //}
 
    public virtual DbSet<Blog> Blog { get; set; }

 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(
            _configuration["ConnectionStrings:ApplicationDbContextConnection"]
            , serverDbContextOptionsBuilder =>
             {
                 var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                 serverDbContextOptionsBuilder.CommandTimeout(minutes);
             }
            );
    }
 
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
 
        base.OnModelCreating(modelBuilder);
    }
 
    public void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
    {
        base.Set<TEntity>().AddRange(entities);
    }
 
    public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
    {
        base.Set<TEntity>().RemoveRange(entities);
    }
 
    public void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class
    {
        base.Entry(entity).State = EntityState.Modified; // Or use ---> this.Update(entity);
    }
 
    public void ExecuteSqlCommand(string query)
    {
        base.Database.ExecuteSqlCommand(query);
    }
 
    public void ExecuteSqlCommand(string query, params object[] parameters)
    {
        base.Database.ExecuteSqlCommand(query, parameters);
    }
 
    public int SaveAllChanges()
    {
        return base.SaveChanges();
    }
 
    public Task<int> SaveAllChangesAsync()
    {
        return base.SaveChangesAsync();
    }
}
در ابتدا اینترفیس IUnitOfWork به کلاس Context برنامه اعمال شده‌است:
 public class ApplicationDbContext : DbContext, IUnitOfWork
و سپس متدهای آن منهای پیاده سازی اینترفیس IDisposable اعمالی به IUnitOfWork :
 public interface IUnitOfWork : IDisposable
پیاده سازی شده‌اند. علت اینجا است که چون کلاس پایه DbContext از همین اینترفیس مشتق می‌شود، دیگر نیاز به پیاده سازی اینترفیس IDisposable نیست.
در مورد تزریق IConfigurationRoot به سازنده‌ی کلاس Context برنامه، در مطلب اول این سری در قسمت «یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه» پیشتر بحث شده‌است.


ثبت تنظیمات تزریق وابستگی‌های IUnitOfWork

پس از تعریف و پیاده سازی اینترفیس IUnitOfWork، اکنون نوبت به معرفی آن به سیستم تزریق وابستگی‌های ASP.NET Core است:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
  services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
  services.AddScoped<IUnitOfWork, ApplicationDbContext>();
در اینجا هم ApplicationDbContext و هم IUnitOfWork با طول عمر Scoped به تنظیمات IoC Container مربوط به ASP.NET Core اضافه شده‌اند. به این ترتیب هر زمانیکه وهله‌ای از نوع IUnitOfWork درخواست شود، تنها یک وهله از ApplicationDbContext در طول درخواست وب جاری، در اختیار مصرف کننده قرار می‌گیرد و همچنین مدیریت Dispose این وهله‌ها نیز خودکار است. به همین جهت اینترفیس IUnitOfWork را با IDisposable علامتگذاری کردیم.


استفاده از IUnitOfWork در لایه سرویس‌های برنامه

اکنون لایه سرویس برنامه و فایل project.json آن چنین شکلی را پیدا می‌کند:
{
  "version": "1.0.0-*",
 
    "dependencies": {
        "Core1RtmEmptyTest.DataLayer": "1.0.0-*",
        "Core1RtmEmptyTest.Entities": "1.0.0-*",
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*",
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0",
        "Microsoft.Extensions.Options": "1.0.0",
        "NETStandard.Library": "1.6.0"
    },
 
  "frameworks": {
    "netstandard1.6": {
      "imports": "dnxcore50"
    }
  }
}
در اینجا ارجاعاتی را به اسمبلی‌های موجودیت‌ها و DataLayer برنامه مشاهده می‌کنید. در مورد این اسمبلی‌ها در مطلب «شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر» پیشتر بحث شد.
پس از تنظیم وابستگی‌های این اسمبلی، اکنون یک کلاس نمونه از لایه سرویس برنامه، به شکل زیر خواهد بود: 
namespace Core1RtmEmptyTest.Services
{
    public interface IBlogService
    {
        IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage);
    }
 
    public class BlogService : IBlogService
    {
        private readonly IUnitOfWork _uow;
        private readonly DbSet<Blog> _blogs;
 
        public BlogService(IUnitOfWork uow)
        {
            _uow = uow;
            _blogs = _uow.Set<Blog>();
        }
 
        public IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage)
        {
            var skipRecords = pageNumber * recordsPerPage;
            return _blogs
                        .AsNoTracking()
                        .Skip(skipRecords)
                        .Take(recordsPerPage)
                        .ToList();
        }
    }
}
در اینجا اکنون می‌توان IUnitOfWork را به سازنده‌ی کلاس سرویس Blog تنظیم کرد و سپس به نحو متداولی از امکانات EF Core استفاده نمود.


استفاده از امکانات لایه سرویس برنامه، در دیگر لایه‌های آن

خروجی لایه سرویس، توسط اینترفیس‌هایی مانند IBlogService در قسمت‌های دیگر برنامه قابل استفاده و دسترسی می‌شود.
به همین جهت نیاز است مشخص کنیم، این اینترفیس را کدام کلاس ویژه قرار است پیاده سازی کند. برای این منظور همانند قبل در متد ConfigureServices کلاس آغازین برنامه این تنظیم را اضافه خواهیم کرد:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
  services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
  services.AddScoped<IUnitOfWork, ApplicationDbContext>();
  services.AddScoped<IBlogService, BlogService>();
پس از آن، امضای سازنده‌ی کلاس کنترلری که در ابتدای بحث عنوان شد، به شکل زیر تغییر پیدا می‌کند:
public class TestDBController : Controller
{
    private readonly IBlogService _blogService;
    private readonly IUnitOfWork _uow;
 
    public TestDBController(IBlogService blogService, IUnitOfWork uow)
    {
        _blogService = blogService;
        _uow = uow;
    }
در اینجا کنترلر برنامه تنها با اینترفیس‌های IUnitOfWork و IBlogService کار می‌کند و دیگر ارجاع مستقیمی را به کلاس ApplicationDbContext ندارد.
مطالب
آشنایی با Refactoring - قسمت 12


قبلا در مورد تبدیل switch statement به الگوی استراتژی مطلبی را در این سایت مطالعه کرده‌اید (^) و بیشتر مربوط است به حالتی که داخل هر یک از case های یک switch statement چندین و چند سطر کد و یا فراخوانی یک تابع وجود دارد. حالت ساده‌تری هم برای refactoring یک عبارت switch وجود دارد و آن هم زمانی است که هر case، تنها از یک سطر تشکیل می‌شود؛ مانند:

namespace Refactoring.Day12.RefactoringSwitchStatement.Before
{
public class Translator
{
public string ToPersian(string englishWord)
{
switch (englishWord)
{
case "zero":
return "صفر";
case "one":
return "یک";
default:
return string.Empty;
}
}
}
}

در اینجا می‌توان از امکانات ساختار داده‌های توکار دات نت استفاده کرد و این switch statement را به یک dictionary تبدیل نمود:

using System.Collections.Generic;

namespace Refactoring.Day12.RefactoringSwitchStatement.After
{
public class Translator
{
IDictionary<string, string> Words = new Dictionary<string, string>
{
{ "zero", "صفر" },
{ "one", "یک" }
};

public string ToPersian(string englishWord)
{
string persianWord;
if (Words.TryGetValue(englishWord, out persianWord))
{
return persianWord;
}

return string.Empty;
}
}
}

همانطور که ملاحظه می‌کنید هر case به یک key و هر return به یک value در Dictionary تعریف شده، تبدیل گشته‌اند. در اینجا هم بهتر است از متد TryGetValue جهت دریافت مقدار کلیدها استفاده شود؛ زیرا در صورت فراخوانی یک Dictionary با کلیدی که در آن موجود نباشد یک استثناء بروز خواهد کرد.
برای حذف این متد TryGetValue، می‌توان یک enum را بجای کلیدهای تعریف شده، معرفی کرد. به صورت زیر:

using System.Collections.Generic;

namespace Refactoring.Day12.RefactoringSwitchStatement.After
{
public enum EnglishWord
{
Zero,
One
}

public class Translator2
{
IDictionary<EnglishWord, string> Words = new Dictionary<EnglishWord, string>
{
{ EnglishWord.Zero, "صفر" },
{ EnglishWord.One, "یک" }
};

public string ToPersian(EnglishWord englishWord)
{
return Words[englishWord];
}
}
}


به این ترتیب از یک خروجی پر از if و else و switch به یک خروجی ساده و بدون وجود هیچ شرطی رسیده‌ایم.
مطالب
مروری بر طراحی Schema less بانک اطلاعاتی SisoDb
اس کیوال سرور، از سال 2005 به بعد، به صورت توکار امکان تعریف و ذخیره سازی اطلاعات schema less و یا schema free را به کمک فیلدهایی از نوع XML ارائه داده است؛ به همراه یکپارچگی آن با زبان XQuery برای تهیه کوئری‌های سریع سمت سرور. در فیلدهای XML می‌توان اطلاعات انواع و اقسام اشیاء را بدون اینکه نیازی به تعریف تک تک فیلدهای مورد نیاز، در بانک اطلاعاتی وجود داشته باشد، ذخیره کرد. یک نمونه از کاربرد چنین امکانی، نوشتن برنامه‌های «فرم ساز» است. برنامه‌هایی که کاربران آن می‌توانند فیلد اضافه و کم کرده و نهایتا اطلاعات را ذخیره و از آن‌ها کوئری بگیرند.
خوب، این فیلد کمتر بحث شده XML، فقط در اس کیوال سرور و نگارش‌های اخیر آن وجود دارد. اگر نیاز به کار با بانک‌های اطلاعاتی سبک‌تری وجود داشت چطور؟ یک راه حل عمومی برای این مساله مراجعه به روش‌های NoSQL است. یعنی بطور کلی بانک‌های اطلاعاتی رابطه‌ای کنار گذاشته شده و به یک سکوی کاری دیگر سوئیچ کرد. در این بین، SisoDb راه حل میانه‌ای را ارائه داده است. با کمک SisoDb می‌توان اطلاعات را به صورت schema less و بدون نیاز به تعریف فیلدهای متناظر آن‌ها، در انواع و اقسام بانک‌های اطلاعاتی SQL Server با فرمت JSON ذخیره و بازیابی کرد. این انواع و اقسام، شامل SQL Server CE نیز می‌شود.


دریافت و نصب SisoDb

دریافت و نصب SisoDb بسیار ساده است. به کمک package manager و امکانات NuGet، کلمه Sisodb را جستجو کنید. در بین مداخل ظاهر شده، پروایدر مورد علاقه خود را انتخاب و نصب نمائید. برای مثال اگر قصد دارید با SQL Server CE کار کنید، SisoDb.SqlCe4 را انتخاب و یا اگر SQL Server 2008 مدنظر شما است، SisoDb.Sql2008 را انتخاب و نصب نمائید.


ثبت و بازیابی اطلاعات به کمک SisoDb

کار با SisoDb بسیار روان است. نیازی به تعاریف نگاشت‌ها و ORM خاصی نیست. یک مثال مقدماتی آن‌را در ادامه ملاحظه می‌کنید:
using SisoDb.Sql2008;

namespace SisoDbTests
{
    public class Customer 
    {
        public int Id { get; set; } 
        public int CustomerNo { get; set; }
        public string Name { get; set; } 
    }

    class Program
    {
        static void Main(string[] args)
        {
            /*var cnInfo = new SqlCe4ConnectionInfo(@"Data source=sisodb2013.sdf;");
            var db = new SqlCe4DbFactory().CreateDatabase(cnInfo);
            db.EnsureNewDatabase();*/

            var cnInfo = new Sql2008ConnectionInfo(@"Data Source=(local);Initial Catalog=sisodb2013;Integrated Security = true");            
            var db = new Sql2008DbFactory().CreateDatabase(cnInfo);            
            db.EnsureNewDatabase();

            var customer = new Customer 
            {
                CustomerNo = 20,
                Name = "Vahid" 
            };
            db.UseOnceTo().Insert(customer);

            using (var session = db.BeginSession()) 
            {
                var info = session.Query<Customer>().Where(c => c.CustomerNo == 20).FirstOrDefault();

                var info2 = session.Query<Customer>().Where(c => c.CustomerNo == 20 && c.Name=="Vahid").FirstOrDefault();
            }
        }
    }
}
در این مثال، ابتدا اتصال به بانک اطلاعاتی برقرار شده و سپس بانک اطلاعاتی جدید تهیه می‌شود. سپس یک وهله از شیء مشتری ایجاد و ذخیره می‌گردد. در ادامه دو کوئری بر روی بانک اطلاعاتی انجام شده است.


ساختار داخلی SisoDb

SisoDb به ازای هر کلاس، حداقل 9 جدول را ایجاد می‌کند. در ادامه نحوه ذخیره سازی شیء مشتری ایجاد شده و مقادیر خواص آن‌را نیز مشاهده می‌نمائید:


ذخیره سازی جداگانه خواص عددی

ذخیره سازی جداگانه خواص رشته‌ای

ذخیره سازی کلی شیء مشتری با فرمت JSON به صورت یک رشته


همانطور که ملاحظه می‌کنید، یک جدول کلی SisoDbIdentities ایجاد شده است که اطلاعات نام اشیاء را در خود نگهداری می‌کند. سپس اطلاعات خواص اشیاء یکبار به صورت JSON ذخیره می‌شوند؛ با تمام اطلاعات تو در توی ذخیره شده در آن‌ها و همچنین یکبار هم هر خاصیت را به صورت یک رکورد جداگانه، بر اساس نوع کلی آن‌ها، در جداول رشته‌ای، عددی و امثال آن ذخیره می‌کند.
شاید بپرسید که چرا به همان فیلد رشته‌ای JSON اکتفاء نشده است؟ از این جهت که پردازشگر سمت بانک اطلاعاتی آن همانند فیلدهای XML در SQL Server و نگارش‌های مختلف آن وجود ندارد (برای مثال به کمک زبان T-SQL می‌توان از زبان XQuery در خود بانک اطلاعاتی، بدون نیاز به واکشی کل اطلاعات در سمت کلاینت، به صورت یکپارچه استفاده کرد). به همین جهت برای کوئری گرفتن و یا تهیه ایندکس، نیاز است این موارد جداگانه ذخیره شوند.
به این ترتیب زمانیکه کوئری تهیه می‌شود، برای مثال:
 var info = session.Query<Customer>().Where(c => c.CustomerNo == 20).FirstOrDefault();
به کوئری زیر ترجمه می‌گردد:
SELECT DISTINCT TOP(1) (s.[StructureId]),
                           s.[Json]
                    FROM   [CustomerStructure] s
                           LEFT JOIN [CustomerIntegers] mem0
                                ON  mem0.[StructureId] = s.[StructureId]
                                AND mem0.[MemberPath] = 'CustomerNo'
                    WHERE  (mem0.[Value] = 20);
و یا کوئری ذیل:
var info2 = session.Query<Customer>().Where(c => c.CustomerNo == 20 && c.Name=="Vahid").FirstOrDefault();
معادل زیر را خواهد داشت:
SELECT DISTINCT TOP(1) (s.[StructureId]),
                           s.[Json]
                    FROM   [CustomerStructure] s
                           LEFT JOIN [CustomerIntegers] mem0
                                ON  mem0.[StructureId] = s.[StructureId]
                                AND mem0.[MemberPath] = 'CustomerNo'
                           LEFT JOIN [CustomerStrings] mem1
                                ON  mem1.[StructureId] = s.[StructureId]
                                AND mem1.[MemberPath] = 'Name'
                    WHERE  ((mem0.[Value] = 20) AND (mem1.[Value] = 'Vahid'));
در هر دو حالت از جداول کمکی تعریف شده برای تهیه کوئری استفاده کرده و نهایتا فیلد JSON اصلی را برای نگاشت نهایی به اشیاء تعریف شده در برنامه بازگشت می‌دهد.

در کل این هم یک روش تفکر و طراحی Schema less است که با بسیاری از بانک‌های اطلاعاتی موجود سازگاری دارد.
برای مشاهده اطلاعات بیشتری در مورد جزئیات این روش می‌توان به Wiki آن مراجعه کرد.
نظرات مطالب
EF Code First #12
اگر از VS 2013 و EF 6 استفاده می‌کنید، حالت DB First آن در حقیقت مهندسی معکوس دیتابیس موجود به حالت Code first است. برای مثال اگر این فایل DbModel.edmx را تولید کند، ذیل آن فایل DbModel.Context.tt مشخص است که حاصل آن تولید خودکار یک چنین کلاسی است:
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from a template.
//
//     Manual changes to this file may cause unexpected behavior in your application.
//     Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace EFDbFirstDependencyInjection.DataLayer
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    
    public partial class TestDbIdentityEntities : DbContext
    {
        public TestDbIdentityEntities()
            : base("name=TestDbIdentityEntities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public virtual DbSet<Category> Categories { get; set; }
        public virtual DbSet<Product> Products { get; set; }
    }
}
این کلاس دقیقا از DbContext حالت Code first استفاده می‌کند و کلا ObjectContext قدیمی را کنار گذاشته‌اند (حتی برای حالت DB First).


بنابراین تمام نکات مطلب جاری در مورد حالت DB First موجود در VS 2013 صادق است. فقط باید فایل DbModel.Context.tt آن‌را اصلاح کنید تا IUnitOfWork را به صورت خودکار به انتهای تعریف کلاس Context اضافه کند. مابقی مسایل آن یکی است.