مطالب
Repository ها روی UnitOfWork ایده خوبی نیستند
در دنیای دات نت گرایشی برای تجزیه (abstract) کردن EF پشت الگوی Repository وجود دارد. این تمایل اساسا بد است و در ادامه سعی می‌کنم چرای آن را توضیح دهم.


پایه و اساس

عموما این باور وجود دارد که با استفاده از الگوی Repository می‌توانید (در مجموع) دسترسی به داده‌ها را از لایه دامنه (Domain) تفکیک کنید و "داده‌ها را بصورت سازگار و استوار عرضه کنید".

اگر به هر کدام از پیاده سازی‌های الگوی Repository در کنار (UnitOfWork (EF دقت کنید خواهید دید که تفکیک (decoupling) قابل ملاحظه ای وجود ندارد.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class StudentRepository : IStudentRepository, IDisposable
    {
        private SchoolContext context;

        public StudentRepository(SchoolContext context)
        {
            this.context = context;
        }

        public IEnumerable<Student> GetStudents()
        {
            return context.Students.ToList();
        }

        public Student GetStudentByID(int id)
        {
            return context.Students.Find(id);
        }

        //<snip>
        public void Save()
        {
            context.SaveChanges();
        }
    }
}

این کلاس بدون SchoolContext نمی‌تواند وجود داشته باشد، پس دقیقا چه چیزی را در اینجا decouple کردیم؟ هیچ چیز را!

در این قطعه کد - از MSDN - چیزی که داریم یک پیاده سازی مجدد از LINQ است که مشکل کلاسیک Repository API‌های بی انتها را بدست می‌دهد. منظور از Repository API‌های بی انتها، متدهای جالبی مانند GetStudentById, GetStudentByBirthday, GetStudentByOrderNumber و غیره است.

اما این مشکل اساسی نیست. مشکل اصلی روتین ()Save است. این متد یک دانش آموز (Student) را ذخیره می‌کند .. اینطور بنظر می‌رسد. دیگر چه چیزی را ذخیره می‌کند؟ آیا می‌توانید حدس بزنید؟ من که نمی‌توانم .. بیشتر در ادامه.


UnitOfWork تراکنشی است

یک UnitOfWork همانطور که از نامش بر می‌آید برای انجام کاری وجود دارد. این کار می‌تواند به سادگی واکشی اطلاعات و نمایش آنها، و یا به پیچیدگی پردازش یک سفارش جدید باشد. هنگامی که شما از EntityFramework استفاده می‌کنید و یک DbContext را وهله سازی می‌کنید، در واقع یک UnitOfWork می‌سازید.

در EF می‌توانید با فراخوانی ()SubmitChanges تمام تغییرات را فلاش کرده و بازنشانی کنید (flush and reset). این کار بیت‌های مقایسه change tracker را تغییر می‌دهد. افزودن رکوردهای جدید، بروز رسانی و حذف آنها. هر چیزی که تعیین کرده باشید. و تمام این دستورات در یک تراکنش یا Transaction انجام می‌شوند.


یک Repository مطلقا یک UnitOfWork نیست
هر متد در یک Repository قرار است فرمانی اتمی (Atomic) باشد - چه واکشی اطلاعات و چه ذخیره آنها. مثلا می‌توانید یک Repository داشته باشید با نام SalesRepository که اطلاعات کاتالوگ شما را واکشی می‌کند، و یا یک سفارش جدید را ثبت می‌کند. منظور از فرمان‌های اتمیک این است، که هر متد تنها یک دستور را باید اجرا کند. تراکنشی وجود ندارد و امکاناتی مانند ردیابی تغییرات و غیره هم جایی ندارند.

یکی دیگر از مشکلات استفاده از Repository‌ها این است که بزودی و به آسانی از کنترل خارج می‌شوند و نیاز به ارجاع دیگر مخازن پیدا می‌کنند. به دلیل اینکه مثلا نمی‌دانستید که SalesRepository نیاز به ارجاع ReportRepository داشته است (یا چیزی مانند این).

این مشکل به سرعت مشکل ساز می‌شود، و نیز به همین دلیل است که به UnitOfWork تمایل پیدا می‌کنیم.


بدترین کاری که می‌توانید انجام دهید: <Repository<T

این الگو دیوانه وار است. این کار عملا انتزاعی از یک انتزاع دیگر است (abstraction of an abstraction). به قطعه کد زیر دقت کنید، که به دلیلی نامشخص بسیار هم محبوب است.

public class CustomerRepository : Repository < Customer > {
  public CustomerRepository(DbContext context){
    //a property on the base class
    this.DB = context;
  }

  //base class has Add/Save/Remove/Get/Fetch
}

در نگاه اول شاید بگویید مشکل این کلاس چیست؟ همه چیز را کپسوله می‌کند و کلاس پایه Repository هم به کانتکست دسترسی دارد. پس مشکل کجاست؟

مشکلات عدیده اند .. بگذارید نگاهی بیاندازیم.

آیا می‌دانید این DbContext از کجا آمده است؟
خیر، نمی‌دانید. این آبجکت به کلاس تزریق (Inject) می‌شود، و نمی‌دانید که چه متدی آن را باز کرده و به چه دلیلی. ایده اصلی پشت الگوی Repository استفاده مجدد از کد است. بدین منظور که مثلا برای عملیات CRUD از کلاسی پایه استفاده کنید تا برای هر موجودیت و فرمی نیاز به کدنویسی مجدد نباشد. برگ برنده این الگو نیز دقیقا همین است. مثلا اگر بخواهید از کدی در چند فرم مختلف استفاده کنید از این الگو استفاده میشد.

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


شناسه مشتری جدید را نیاز داشتم
کد بالا در CustomerRepository را در نظر بگیرید - که یک مشتری جدید را به دیتابیس اضافه می‌کند. اما CustomerID جدید چه می‌شود؟ مثلا به این شناسه نیاز دارید تا یک log بسازید. چه می‌کنید؟ گزینه‌های شما اینها هستند:

  • متد ()SubmitChanges را صدا بزنید تا تغییرات ثبت شوند و بتوانید به CustomerID جدید دسترسی پیدا کنید
  • CustomerRepository خود را باز کنید و متد پایه Add را بازنویسی (override) کنید. بدین منظور که پیش از بازگشت دادن، متد ()SubmitChanges را فراخوانی کند. این راه حلی است که MSDN به آن تشویق می‌کند، و بمبی ساعتی است که در انتظار انفجار است
  • تصمیم بگیرید که تمام متدهای Add/Remove/Save در مخازن شما باید ()SubmitChanges را فراخوانی کنند

مشکل را می‌بینید؟ مشکل در خود پیاده سازی است. در نظر بگیرید که چرا New Customer ID را نیاز دارید؟ احتمالا برای استفاده از آن در ثبت یک سفارش جدید، و یا ثبت یک ActivityLog.

اگر بخواهیم از StudentRepository بالا برای ایجاد دانش آموزان جدید پس از خرید آنها از فروشگاه کتاب مان استفاده کنیم چه؟ اگر DbContext خود را به مخزن تزریق کنید و دانش آموز جدید را ذخیره کنید .. اوه .. تمام تراکنش شما فلاش شده و از بین رفته!

حالا گزینه‌های شما اینها هستند: 1) از StudentRepository استفاده نکنید (از OrderRepository یا چیز دیگری استفاده کنید). و یا 2) فراخوانی ()SubmitChanges را حذف کنید و به باگ‌های متعددی اجازه ورود به کد تان را بدهید.

اگر تصمیم بگیرید که از StudentRepository استفاده نکنید، حالا کدهای تکراری (duplicate) خواهید داشت.

شاید بگویید که برای دستیابی به شناسه رکورد جدید نیازی به ()SubmitChanges نیست، چرا که خود EF این عملیات را در قالب یک تراکنش انجام می‌دهد!

دقیقا درست است، و نکته من نیز همین است. در ادامه به این قسمت باز خواهیم گشت.

متدهای Repositories قرار است اتمیک باشند

به هر حال تئوری اش که چنین است. چیزی که در Repository‌ها داریم حتی اصلا Repository هم نیست. بلکه یک abstraction برای عملیات CRUD است که هیچ کاری مربوط به منطق تجاری اپلیکیشن را هم انجام نمی‌دهد. مخازن قرار است روی دستورات مشخصی تمرکز کنند (مثلا ثبت یک رکورد یا واکشی لیستی از اطلاعات)، اما این مثال‌ها چنین نیستند.

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

خوب، راه حل چیست؟

برای جلوگیری از این abstraction‌های غیر منطقی دو راه وجود دارد. اولین راه استفاده از Command/Query Separation است که ممکن است در ابتدا کمی عجیب و بنظر برسند اما لازم نیست کاملا CQRS را دنبال کنید. تنها از سادگی انجام کاری که مورد نیاز است لذت ببرید، و نه بیشتر.

آبجکت‌های Command/Query

Jimmy Bogard مطلب خوبی در اینباره نوشته است و با تغییراتی جزئی برای بکارگیری Properties کدی مانند لیست زیر خواهیم داشت. مثلا برای مطالعه بیشتر درباره آبجکت‌های Command/Query به این لینک سری بزنید.

public class TransactOrderCommand {
  public Customer NewCustomer {get;set;}
  public Customer ExistingCustomer {get;set;}
  public List<Product> Cart {get;set;}
  //all the parameters we need, as properties...
  //...

  //our UnitOfWork
  StoreContext _context;
  public TransactOrderCommand(StoreContext context){
    //allow it to be injected - though that's only for testing
    _context = context;
  }

  public Order Execute(){
    //allow for mocking and passing in... otherwise new it up
    _context = _context ?? new StoreContext();

    //add products to a new order, assign the customer, etc
    //then...
    _context.SubmitChanges();

    return newOrder;
  }
}
همین کار را با یک آبجکت Query نیز می‌توانید انجام دهید. می‌توانید پست Jimmy را بیشتر مطالعه کنید، اما ایده اصلی این است که آبجکت‌های Query و Command برای دلیل مشخصی وجود دارند. می‌توانید آبجکت‌ها را در صورت نیاز تغییر دهید و یا mock کنید.


DataContext خود را در آغوش بگیرید

ایده ای که در ادامه خواهید دید را شخصا بسیار می‌پسندم (که توسط Ayende معرفی شد). چیزهایی که به آنها نیاز دارید را در قالب یک فیلتر wrap کنید و یا از یک کلاس کنترلر پایه استفاده کنید (با این فرض که از اپلیکیشن‌های وب استفاده می‌کنید).

using System;
using System.Web.Mvc;

namespace Web.Controllers
{
  public class DataController : Controller
  {
    protected StoreContext _context;

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
      //make sure your DB context is globally accessible
      MyApp.StoreDB = new StoreDB();
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
      MyApp.StoreDB.SubmitChanges();
    }
  }
}

این کار به شما اجازه می‌دهد که از DataContext خود در خلال یک درخواست واحد (request) استفاده کنید. تنها کاری که باید بکنید این است که از این کلاس پایه ارث بری کنید. این بدین معنا است که هر درخواست به اپلیکیشن شما یک UnitOfWork خواهد بود. که بسیار هم منطقی و قابل قبول است. در برخی موارد هم شاید این فرض درست یا کارآمد نباشد، که در این هنگام می‌توانید از آبجکت‌های Command/Query استفاده کنید.


ایده‌های بعدی: چه چیزی بدست آوردیم؟

چیزهای متعددی بدست آوردیم.

  • تراکنش‌های روشن و صریح: دقیقا می‌دانیم که DbContext ما از کجا آمده و در هر مرحله روی چه UnitOfWork ای کار می‌کنیم. این امر هم الان، و هم در آینده بسیار مفید خواهد بود
  • انتزاع کمتر == شفافیت بیشتر: ما Repository‌ها را از دست دادیم، که دلیلی برای وجود داشتن نداشتند. به جز اینکه یک abstraction از abstraction دیگر باشند. رویکرد آبجکت‌های Command/Query تمیز‌تر است و دلیل وجود هرکدام و مسئولیت آنها نیز روشن‌تر است
  • شانس کمتر برای باگ ها: رویکردهای مبتنی بر Repository باعث می‌شوند که با تراکنش‌های ناموفق یا پاره ای (partially-executed) مواجه شویم که نهایتا به یکپارچگی و صحت داده‌ها صدمه می‌زند. لازم به ذکر نیست که خطایابی و رفع چنین مشکلاتی شدیدا زمان بر و دردسر ساز است

برای مطالعه بیشتر 

ایجاد Repositories بر روی UnitOfWork
به الگوی Repository در لایه DAL خود نه بگویید!
پیاده سازی generic repository یک ضد الگو است 
نگاهی به generic repositories
بدون معکوس سازی وابستگی‌ها، طراحی چند لایه شما ایراد دارد  

مطالب
پَرباد - آموزش پیاده‌سازی پرداخت آنلاین در دات نت - آموزش پیشرفته
در قسمت قبل، با تنظیمات پَرباد آشنا شدیم. در این مقاله قصد داریم سایر امکانات قابل استفاده را آموزش دهیم.

آنچه شما در این مقاله یاد خواهید گرفت:

  • ایجاد صورت حساب پرداخت با استفاده از InvoiceBuilder
  • درگاه مجازی
  • استفاده از پروکسی
  • توکن پرداخت
  • تزریق وابستگی
  • Logging


ایجاد صورت حساب با استفاده از InvoiceBuilder

InvoiceBuilder به شما کمک می‌کند تا یک صورت حساب را جهت پرداخت آماده کنید.
مثال زیر را در نظر بگیرید:
var result = _onlinePayment.Request(Gateways.Mellat, 123, 25000, "http://www.mywebsite.com/foo/bar/");
همانطور که مشخص است، در این مثال یک صورت حساب با شماره رهگیری ۱۲۳ به مبلغ ۲۵۰۰۰ با یک آدرس بازگشتی به درگاه بانک ملت درخواست داده می‌شود.

اما همین دستور را با کمک InvoiceBuilder نیز می‌توان ایجاد کرد.
نمونه مثال بالا با استفاده از InvoiceBuilder
var result = _onlinePayment.Request(invoice =>
{
    invoice
          .SetTrackingNumber(123)
          .SetAmount(25000)
          .SetCallbackUrl("http://www.mywebsite.com/foo/bar/")
          .UseGateway(Gateways.Mellat);
});

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

در زیر نمونه‌هایی از کارایی آن را بررسی می‌کنیم.

  • تولید اتوماتیک کد رهگیری به صورت افزایشی
  • تولید اتوماتیک کد رهگیری به صورت تصادفی
  • ایجاد یک تولید کننده کد رهگیری توسط شما
  • صورت حساب سفارشی برای امکانات اختصاصی درگاه‌های بانکی

تولید اتوماتیک کد رهگیری به صورت افزایشی

در این روش، کد رهگیری (TrackingNumber) که مورد نیاز درگاه‌های بانکی است، به صورت اتوماتیک در هنگام ایجاد درخواست پرداخت، توسط پَرباد تولید می‌شود.
var result = _onlinePayment.Request(invoice =>
{
    invoice
          .UseAutoIncrementTrackingNumber()
          .SetAmount(25000)
          .SetCallbackUrl("http://www.mywebsite.com/foo/bar/")
          .UseGateway(Gateways.Mellat);
});
کد تولید شده، به صورت افزایشی است. در واقع در هر درخواست پرداخت جدید، یک کد رهگیری تولید می‌شود که یک واحد از کد تولید شده‌ی قبلی بیشتر است. 

شما همچنین می‌توانید مقدار اولیه این عدد را جهت شروع تولید، در پارامتر متد تعیین کنید. این مقدار همچنین در قسمت تنظیمات پَرباد نیز توسط متد ConfigureAutoTrackingNumber قابل تنظیم است.

تولید اتوماتیک کد رهگیری به صورت تصادفی

var result = _onlinePayment.Request(invoice =>
{
    invoice
          .UseAutoRandomTrackingNumber()
          .SetAmount(25000)
          .SetCallbackUrl("http://www.mywebsite.com/foo/bar/")
          .UseGateway(Gateways.Mellat);
});
در این روش کد رهگیری، به صورت تصادفی در محدوده Int64 توسط پَرباد تولید خواهد شد. کد‌های تولید شده در این روش تقریبا ٪۹۹.۹ غیر تکراری هستند. اما اگر به تمیز بودن کدهای تولید شده اهمیت می‌دهید، بهتر است از روش AutoIncrement که بالاتر توضیح داده شد، استفاده کنید.

ایجاد یک تولید کننده کد رهگیری توسط شما

اگر بنا به دلایلی قصد دارید خودتان نیز یک منبع تولید کد رهگیری را ایجاد کنید، می‌توانید به روش زیر عمل کنید.
public class MyTrackingNumberProvider : ITrackingNumberProvider
{
    public Task<long> ProvideAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        // تولید و برگشت کد در اینجا
    }
}

نکته ۱: شما همچنین می‌توانید در منبع خود، از تزریق وابستگی‌ها نیز استفاده کنید. بدیهی است سرویسی را که تزریق می‌کنید، باید از قبل توسط سیستم تزریق وابستگی‌های اپلیکیشن شما، ثبت شده باشد.
نکته ۲؛ شما همچنین می‌توانید بدون ایجاد هیچ منبعی، به راحتی از متد SetTrackingNumber در InvoiceBuilder (که بالاتر توضیح داده شده) استفاده کنید.

سپس در هنگام ایجاد درخواست پرداخت به روش زیر از منبع خود استفاده کنید:
var result = _onlinePayment.Request(invoice =>
{
    invoice
          .UseTrackingNumberProvider<MyTrackingNumberProvider>()
          //  یا 
          .UseTrackingNumberProvider(new MyTrackingNumberProvider())
          //  یا 
          .UseTrackingNumberProvider(services => new MyTrackingNumberProvider())
});
همانطور که می‌بینید، متد‌های مختلفی جهت استفاده از منبع مورد نظر شما موجود است.

صورت حساب سفارشی برای امکانات اختصاصی درگاه‌های بانکی

درگاه‌های بانکی علاوه بر سرویس‌های پرداخت عادی، امکانات مختلفی دیگری را نیز ارائه می‌کنند. برای مثال بانک ملت دارای سرویسی به نام "باشگاه مشتریان بانک ملت" است که امکان واریز مبلغ را از حساب مشتری، به چندین حساب مختلف می‌دهد.
نکته مهم: همانطور که می‌دانید قبل از استفاده از این گونه سرویس‌ها، کلیه شماره حساب‌هایی که قصد واریز مبلغ به آنها را دارید، باید از قبل (در هنگام عقد قرارداد با بانک) به بانک مورد نظر داده شده باشد. در غیر اینصورت امکان استفاده از این گونه سرویس‌ها را ندارید.
نکته: در هنگام نوشتار این مقاله، این سرویس هنوز در پَرباد آماده نیست و در حال توسعه است.


درگاه مجازی

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

ابتدا در قسمت تنظیمات پَرباد،  آدرس مورد نظر برای درگاه مجازی را مانند کد زیر مشخص می‌کنیم:
services.AddParbad()
        .ConfigureGateways(gateways =>
        {
            gateways
               .AddParbadVirtual()
               .WithOptions(options => options.GatewayPath = "/MyVirtualGateway");
        });


در مثال بالا، درگاه مجازی توسط آدرس داده شده در دسترس خواهد بود. توجه داشته باشید که این آدرس حتما باید با یک ( / ) آغاز شده باشد.
سپس درگاه مجازی را در اپلیکیشن خود ثبت می‌کنید:
ASP.NET CORE (Startup.cs)
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMvc(routes =>
    {
        routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
    });

    // ثبت درگاه مجازی
    app.UseParbadVirtualGateway();
}

ASP.NET WebForms, ASP.NET MVC (Startup.cs)
public void Configuration(IAppBuilder app)
{
    var parbad = ParbadBuilder.CreateDefaultBuilder()
        .ConfigureGateways(gateways =>
        {
            gateways
               .AddParbadVirtual()
               .WithOptions(options => options.GatewayPath = "/MyVirtualGateway");
        })
        .Build();

    app.UseParbadVirtualGateway(parbad.Services);
}


تنظیمات درگاه مجازی تا اینجا به پایان رسیده و فقط در هنگام ایجاد درخواست پرداخت، از میان درگاه‌ها، درگاه مجازی پَرباد را انتخاب کنید.
var result = _onlinePayment.Request(Gateways.ParbadVirtualGateway, 123, 25000, "http://www.mywebsite.com/foo/bar/");

و در نهایت به درگاه مجازی هدایت خواهید شد:

نمونه پروژه‌های کامل را در انتهای مقاله می‌توانید مشاهده کنید.

استفاده از پروکسی

با توجه به تغییرات اخیر در بانکداری کشور، احتمال آن وجود دارد که در آینده، بعضی از درگاه‌های بانکی فقط به IP‌های داخل کشور سرویس دهی کنند. اگر وب سایت شما بنا به دلایلی در سروری خارج از کشور میزبانی می‌شود، شما جهت استفاده از درگاه‌های بانکی، نیاز به یک سرور در داخل کشور دارید که نقش پروکسی را برای شما بازی کند. پَرباد این امکان را برای شما محیا کرده و کافیست اطلاعات پروکسی سرور خود را به شکل زیر برای درگاه بانکی مورد نظر ثبت کنید.
services.AddParbad()
        .ConfigureGateways(gateways =>
        {
            gateways
               .AddMellat()
               .WithOptions(options => 
               {
                    options.TerminalId = 123;
                    options.UserName = "abc";
                    options.UserPassword = "xyz;
               )
               .WithProxy(new Uri("Proxy Server URL"), "UserName", "Password");
        });
در مثال بالا، درگاه بانک ملت (صرفا جهت مثال) با یک پروکسی تنظیم شده است. برای سایر درگاه‌های بانکی، فرمت تنظیمات، کاملا مشابه مثال بالا است.

توکن پرداخت

پَرباد جهت شناسایی و تبادل اطلاعات پرداخت با خارج از سیستم خود و بانک‌ها، از یک توکن به ازاء هر پرداخت استفاده می‌کند. به عبارت دیگر، به ازاء هر درخواست پرداخت یک توکن تولید می‌شود. به این صورت، درخواست‌های پرداخت، غیر قابل دستکاری و غیر قابل حدس زدن توسط کاربران می‌شود.
نکته: پَرباد از یک تولید کننده پیش فرض توکن استفاده می‌کند و شما نیازی به انجام هیچگونه تنظیماتی ندارید. تولید کننده پیش فرض، از یک GUID در Query String استفاده می‌کند.
اگر قصد دارید روش مورد نظر خود را برای تولید توکن جهت شناسایی یک پرداخت پیاده‌سازی کنید، می‌توانید به روش زیر عمل کنید:
ابتدا تولید کننده توکن را تعریف کنید.
public class MyTokenProvider : IPaymentTokenProvider
{
    public Task<string> ProvideTokenAsync(Invoice invoice, CancellationToken cancellationToken = new CancellationToken())
    {
        // تولید و برگرداندن توکن در اینجا
    }

    public Task<string> RetrieveTokenAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        // خواندن و برگرداندن توکن در اینجا
    }
}

نکته: شما همچنین می‌توانید از تزریق وابستگی‌ها نیز استفاده کنید. بدیهی است که در اینصورت باید سرویسی که تزریق می‌کنید، از قبل در سیستم تزریق وابستگی‌های اپلیکیشن شما ثبت شده باشد.
سپس تولید کننده توکن خود را در تنظیمات به پَرباد معرفی کنید:
services.AddParbad()
        .ConfigurePaymentToken(builder => builder.AddPaymentTokenProvider<MyTokenProvider>(ServiceLifetime.Transient));

ServiceLifetime، تایین کننده طول عمر سرویس شما است.
به این صورت پَرباد، شناسایی و ردیابی یک صورت حساب را با استفاده از تولید کننده توکن شما انجام خواهد داد.


تزریق وابستگی

همانطور که قبلا در مقاله آموزش تنظیمات نیز گفته شد، پَرباد به صورت توکار، از تزریق وابستگی استاندارد مایکروسافت استفاده می‌کند. بنابراین اگر اپلیکیشن شما نیز از تزریق وابستگی مشابهی استفاده می‌کند، نیازی به خواندن و یاد گرفتن این بخش ندارید و به راحتی می‌توانید از اینترفیس IOnlinePayment در هر کجا که نیاز داشتید جهت عملیات پرداخت استفاده کنید.
اما در صورتیکه در اپلیکیشن خود از تزریق وابستگی دیگری ( مانند Autofac ) استفاده می‌کنید، باید این دو سیستم را با یکدیگر هماهنگ کنید. خوشبختانه تمام کتابخانه‌های معروف تزریق وابستگی ( مانند Autofac )  از قبل این کار را برای شما محیا کرده‌اند و شما فقط نیاز به افزودن چند خط کد به اپلیکیشن فعلی خود را دارید.
جهت فهم بهتر و آموزش عملی، یک اپلیکیشن کامل ASP.NET MVC برای شما تهیه شده که از Autofac جهت تزریق وابستگی استفاده می‌کند. در این پروژه خواهید دید چگونه به راحتی پَرباد و Autofac را با یکدیگر هماهنگ کرده و هچنین اینترفیس IOnlinePayment را درون کنترلر تزریق می‌کنیم.
لینک پروژه‌ها در انتهای همین مقاله قابل مشاهده هستند.


Logging
لاگ کردن در پَرباد توسط سیستم استاندارد مایکروسافت انجام می‌شود. این بدان معنی است که شما امکان استفاده از کتابخانه‌های Logging بسیار زیادی را دارید. نحوه استفاده و تنظیم کتابخانه‌های معروف Logging در وب سایت آنها آورده شده و به راحتی می‌توانید آنها را با لاگ مایکروسافت تطابق دهید.

نمونه پروژه‌ها
مقاله‌های مرتبط
مطالب
سیستم‌های توزیع شده در NET. - بخش سوم- مهمترین فاکتورها در انتخاب سیستمهای توزیع شده
همیشه نیازمندی‌های ما باعث انتخاب نوع طراحی و پیاده سازی ما می‌شوند و لزوما چیزی که برای ما جذابتر و پیچیده‌تر است، باعث موفقیت سیستمی که طراحی می‌کنیم نمی‌شود. چه بسا که یک انتخاب نادرست و نادیده گرفتن یک یا چند نیازمندی، باعث شود هیچ یک از مواردی که شما برای انتخاب آن نوع طراحی در نظر گرفته بودید، محقق نشوند. هدف من از ارائه این بخش، معرفی مهمترین فاکتورهایی است که شما می‌توانید با استفاده از آنها تصمیم بگیرید که آیا باید سیستم خود را بصورت توزیع شده پیاده سازی کنید یا خیر و شاید بهترین راه برای بدست آوردن درک بهتری از این فاکتور‌ها، ارائه مثالی واقعی از یک سیستم توزیع شده باشد.

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


نمایی از زنجیره تامین فرآورده‌های دارویی و نحوه فراخوانی سرویس آمارنامه



در این سیستم چالش‌های بسیار مهمی وجود دارند که پس از بررسی‌های انجام شده، برای هر یک راه حلی ارائه خواهد شد:

چالش اول: در دسترس بودن سیستم

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

ارائه راه حل:

بدلیل اینکه احتمال از دسترس خارج شدن یک سرور همیشه وجود دارد، این چالش به تنهایی می‌تواند دلیل محکمی برای پیاده سازی سیستم بصورت توزیع شده باشد. برای حل این مشکل می‌توانیم از روش Active/Standby استفاده کنیم. به این صورت که چند کپی از سرویس روی چند سرور داشته باشیم که هر لحظه یکی از این سرور‌ها فعال باشد. با از دسترس خارج شدن سرور Active، یکی از سرور‌های Standby فعال شود و درخواست‌های جدید برای این سرور ارسال شوند.


این روش تنها قابلیت در دسترس بودن سیستم را افزایش می‌دهد و هیچ تاثیری روی کارآیی سیستم ندارد.

 برای رفع مشکل فوق، از روش Replicate روی یک یا چند Cluster استفاده می‌کنیم. یعنی چند کپی از سرویس، روی چند سرور داشته باشیم؛ به این صورت که همه آنها فعال باشند. درخواست‌ها با الگوریتمی که انتخاب می‌کنیم، از طریق Load Balancer بین این Node‌ها پخش می‌شوند. با این روش، هم کارآیی سیستم بالا می‌رود و هم همیشه Nodeهایی وجود دارند که جای Node‌های از دسترس خارج شده را بگیرند.


این روش کارآیی سیستم را افزایش چشمگیری می‌دهد. اما بدلیل اینکه یک Load Balancer داریم، در صورتیکه به هر دلیلی Load balancer از دسترس خارج شود، کل سیستم از دسترس خارج می‌شود.
برای رفع مشکل فوق بصورت ترکیبی، از هر دو روش در قسمتهای مختلف استفاده می‌کنیم که در این روش احتمال از دسترس خارج شدن سیستم به حداقل ممکن می‌رسد و کارآیی سیستم نیز به حداکثر ممکن می‌رسد.



(در هر صورت بهترین راه حل برای این چالش، استفاده از سیستم‌های توزیع شده است.)


چالش دوم: تعداد کاربران و تعداد درخواست بسیار زیاد و همیشه رو به افزایشند

کاربران این سیستم شامل تمام داروخانه‌های کشور، تمام توزیع کنندگان و شعب آنها، تمام تولید کنندگان، تمام وارد کنندگان، دانشگاه‌های مرتبط، یکسری از ارگان‌های دولتی و عموم جامعه هستند. یعنی سیستم شامل تعداد کاربران بسیار زیادی است که چیزی در حدود 15000 کاربر از این مجموعه وظیفه دارند بصورت فعال و متناوب با این سیستم کار کنند. کاربران این سیستم همیشه رو به افزایشند.

به نسبت تعدادکاربران و رو به افزایش بودن آنها، درخواست از این سیستم، هیچگاه قطع نمی‌شود و همیشه رو به افزایش است. با رخ دادن هر Event، یک درخواست برای سیستم ارسال می‌شود. بطور مثال تنها در آخرین مرحله به ازای هر رخداد داروخانه، درخواستی برای سیستم ارسال می‌شود (تنها یکی از رخدادهای داروخانه، رخداد فروش است که با ارائه هر نسخه توسط مشتری اتفاق می‌افتد). با توجه به اینکه در کشور چیزی در حدود 12000 داروخانه وجود دارند، سیستم باید توانایی پاسخ دادن به 12000 درخواست بصورت همزمان و متناوب، آن هم فقط برای رخداد فروش داروخانه‌ها را داشته باشد.

ارائه راه حل:

بدلیل تعداد بسیار زیاد درخواست‌ها و بالا رفتن این تعداد، بصورت لحظه‌ای و حیاتی بودن دسترسی به این سیستم، سیستم باید قابلیت این را داشته باشد که بدون از دسترس خارج شدن، اولا درخواست‌های جاری را پاسخ دهد، دوما همیشه آمادگی لازم را برای افزایش تعداد درخواست‌ها، داشته باشد. یعنی به هیچ وجه Scale-up به‌تنهایی پاسخگوی نیاز ما نیست و برای رفع این مشکل باید از Scale-out کمک بگیریم. یعنی با افزایش تعداد درخواست‌ها، بدون از دسترس خارج شدن سیستم و با کمترین هزینه و پیچیدگی، Node‌هایی به سیستم اضافه کنیم که قسمتی از بار پردازشی در آنها انجام شود.


در این روش ما می‌توانیم به راحتی و با کمترین هزینه، با افزایش تعداد درخواست، Nodeهایی را به Cluster اضافه کنیم تا بار پردازشی اضافی در آنها رفع شود. همچنین برای استفاده بهینه از منابع، با کاهش درخواست، Nodeهایی را از Cluster خارج کنیم. همچنین قابلیت در دسترس بودن این سیستم نیز در بالاترین سطح خود قرار دارد.


چالش سوم: حجم زیاد هر درخواست و زمان زیاد مورد نیاز برای پردازش آن 

روال پاسخ دادن به هر درخواست، شامل دریافت درخواست، گرفتن Log از درخواست، اعمال دسترسی‌های ارسال کننده درخواست، اعتبارسنجی درخواست، پردازش درخواست، ذخیره آن و پاسخ به کاربر  است و بدلیل اینکه هر رخداد می‌تواند شامل اطلاعات بسیار زیادی باشد، انجام همه این اعمال، زمان زیادی را می‌طلبد. همچنین با توجه به تعداد کاربران، تعداد درخواست و حجم داده‌ای که باید ذخیره کنیم - در صورتی که هر درخواست نیز بخواهد در مدت زمان زیادی پردازش شود - سیستم با حجم بسیار زیادی از درخواست مواجه است که هر یک زمانی زیادی را نیز برای پردازش نیاز دارد.

ارائه راه حل: 

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


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


چالش چهارم: حجم بسیار زیاد و رو به افزایش داده‌های سیستم

داده‌های این سیستم ذاتا همیشه و در هر شرایطی رو به افزایش هستند و هیچگاه جریان داده، در این سیستم قطع نمی‌شود. با توجه به تعداد کاربران، تعداد درخواست و نوع داده، ما با حجم داده‌ی بسیار زیادی روبرو هستیم که پایانی ندارند.

ارائه راه حل:

با توجه به حیاتی بودن دسترسی به سیستم و سایر چالش‌هایی که در قسمت‌های قبلی ذکر شد، در صورتیکه حتی تمام قسمتهای قبل را به‌درستی طراحی و پیاده سازی کنیم، اگر برای این چالش راه حل درستی را ارائه ندهیم، تمامی راه حل‌های قبلی که ارائه کردیم، بی فایده می‌باشند. چون با از دسترس خارج شدن Database، کل سیستم از دسترس خارج می‌شود.

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

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



این طراحی تقریبا تمامی قابلیتهای طراحی سرویسمان را دارد. یعنی با افزایش تعداد درخواست، یا کم شدن فضای ذخیره سازی در هر یک از Nodeها، ما این قابلیت را داریم که Nodeهایی را به آن اضافه کنیم. همچنین بدلیل اینکه داده‌های ما در دو یا چند Node کپی شده‌اند، با از دسترس خارج شدن هر Node همیشه Nodeهایی وجود دارند که جای Node معیوب را بگیرند؛ تا زمانیکه Node معیوب دوباره به سیستم بازگردد.

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


مهمترین فاکتورهای انتخاب سیستمهای توزیع شده:

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

2- در دسترس بودن سیستم: گاهی ممکن است یک لحظه از دسترس خارج شدن سیستم، عواقب جبران ناپذیری را برای کل سیستم به‌وجود بیاورد. در این حالت بهترین انتخاب، سیستم‌های توزیع شده است.

3- تعداد یا نوع کاربران سیستم: تعداد کاربرانی که همیشه رو به افزایشند، می‌تواند فاکتور بسیار مهمی در انتخاب یک سیستم توزیع شده باشد. اما مشکلی که وجود دارد این است که همیشه در ابتدای طراحی این تعداد مشخص نیست. گاهی نیاز است نوع طراحی خود را با توجه به نوع کاربران سیستم انتخاب کنید. بطور مثال سیستم شما نیازهای کاربران یک مکان یا سازمان خاص را رفع می‌کند، یا نیازهای یک جامعه را رفع می‌کند. در صورتیکه سیستم شما نیاز کاربران یک محیط بزرگ را رفع کند، همیشه باید منتظر بالا رفتن میزان کاربران سیستم نیز باشید.

4- تعداد درخواست‌های از سیستم: تعداد درخواست‌ها در اکثر موارد وابستگی بسیار زیادی به تعداد یا نوع کاربران دارد. پوشش دادن تعداد زیاد درخواست، بصورت متناوب و رو به افزایش می‌تواند فاکتور بسیار مهمی در انتخاب یک سیستم توزیع شده باشد.

5- نوع و حجم عملیاتی که انجام می‌دهیم: برخی عملیات ممکن است زمان بسیار زیادی برای اجرا نیاز داشته باشند که می‌تواند روی سیستم ما تاثیر بسیار زیادی بگذارند. برای افزایش کارآیی و پردازش تعداد بیشتر درخواست‌ها، گاهی بهتر است یک عملیات را تبدیل به عملیاتی کوچکتر کرد و هرکدام از این عملیات کوچکتر را در یک سخت افزار جداگانه اجرا کرد.

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

7- کارآیی: با یک طراحی و تقسیم بندی درست در قسمتهای مختلف سیستم می‌توان حجم و تعداد بسیار زیادی از پردازش‌ها را بصورت همزمان اجرا کرد. البته کاملا بصورت انعطاف پذیر؛ به صورتیکه با بیشتر شدن تعداد و حجم پردازش، سیستم بدون از دسترس خارج شدن، قادر به پوشش دادن آنها باشد.

8- امنیت: پردازش شما می‌تواند تقسیم بندی شود. بصورتیکه هر قسمت در سرور جداگانه‌ای که از قبل مشخص نیست، اجرا شود. سروری که حتی به اینترنت هم وصل نیست. با طراحی درست می‌توان امنیت سیستم را بسیار افزایش داد.

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

علاوه بر موارد فوق مواردی را مانند Internet of things یا همان IOT  که پایه و اساس آن سیستم‌های توزیع شده‌است، یا مواردی را مانند Machine learning که می‌تواند بصورت توزیع شده پیاده سازی شود، نیز در نظر بگیرید.

با در نظر گرفتن تمام موارد فوق و شرایط اختصاصی سیستمی که طراحی می‌کنید، سعی کنید بهترین انتخاب را انجام دهید.
مطالب
آموزش MDX Query - قسمت هشتم – کار بر روی ساختار های سلسله مراتبی

اگر دایمنشنی بیشتر از یک ساختار سلسله مراتبی باشد ، باید حتما مشخص شود که اعضای کدام ساختار سلسله مراتبی مورد نظر ما می باشد . بنابراین کوئری‌های زیر دارای خطا می‌باشند.

Select
[Customer].members on columns
From [Adventure Works]
GO
Select
[Scenario].members on columns
From [Adventure Works]

دو کوئری زیر را در نظر بگیرید :

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].members on rows
From [Adventure Works]

و

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Country].members on rows
From [Adventure Works]

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

در کوئری اول تمامی سطوح موجود در ساختار سلسله مراتبی دایمنشن مورد نظر واکشی می‌شود. در کوئری دوم فقط سطح کشور از ساختار سلسله مراتبی واکشی می‌شود.

ساختار سلسله مراتبی دایمنشن [ Customer ] در شکل زیر نمایش داده شده است.


امکان واکشی عضوهای یک عضو از ساختار سلسله مراتبی وجود ندارد

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Country].[France].members on rows
From [Adventure Works]

دقت کنید که France خودش عضو سطح Country در ساختار سلسله مراتبی Customer Geography در دایمنشن Customer می‌باشد و دیگر امکان واکشی اعضای این عضو وجود ندارد

بچه‌های یک عضو را به شکل زیر می‌توان واکشی نمود.

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Country].[France].children on rows
From [Adventure Works]

برای بدست آوردن بچه های یک عضو در یک ساختار سلسله مراتبی، از  children استفاده می شود .


دقت داشته باشید، همانگونه که عضو، دارای Members نمی‌باشد، یک بچه هم دارای Children نمی‌باشد. بنابراین کوئری زیر با خطا مواجه می‌شود.

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Country].[France].children.children on rows
From [Adventure Works]

البته شاید تصور می‌شد که باید به عنوان یک ساختار سلسله مراتبی بعد از کشور، استان و سپس شهر‌ها واکشی شوند. اما برای واکشی ساختار سلسله مراتبی باید به صورت زیر عمل کرد.

Select
[Measures].[Internet Sales Amount] on columns,
descendants(
[Customer].[Customer Geography].[Country].[France],
[Customer].[Customer Geography].[City]
) on rows
From [Adventure Works]

عملا برای یافتن شهر‌های کشور فرانسه، باید دو سطح در ساختار سلسله مراتبی پایین برویم. برای این منظور از تابع descendants () استفاده کرده‌ایم و به خاطر داشته باشید که نوشتن [France].children.children مجاز نمی‌باشد. همچنین در تابع فوق دو پارامتر ورودی داریم؛ اولی مشخص کننده‌ی یک عضو از سطح مبدا می‌باشد و دومین پارامتر مشخص کننده‌ی سطحی است که باید واکشی در آن صورت بگیرد.


برای مشخص‌تر شدن موضوع به مثال زیر توجه کنید

Select
[Measures].[Internet Sales Amount] on columns,
descendants(
[Customer].[Customer Geography].[Country].[France],
[Customer].[Customer Geography].[Customer]
) on rows
From [Adventure Works]

در این مثال واکشی در چهار سطح پایین‌تر انجام شده است و به این معنی می‌باشد که ما مشتریان کشور فرانسه را در ردیف‌ها واکشی کرده‌ایم.

عدم وجود یک Member :

در صورت عدم وجود یک عضو در واکشی، خروجی خالی می‌باشد و خطا نمایش داده نمی‌شود.

Select
[Customer].[Customer Geography].[Customer].[Crystal Zhen] on columns
From [Adventure Works]

مشتری با نام Crystal Zhen وجود ندارد؛ بنابراین نتیجه خالی می‌باشد.

پیدا کردن یک عضو در مجموعه ایی از اعضا، البته اگر این عضو درآ  ن مجموعه وجود نداشته باشد خروجی خالی می باشد.

Select
exists(
[Customer].[Customer Geography].[Customer].[Crystal Zheng],
[Customer].[Customer Geography].members
  )on columns
From [Adventure Works]

در نوشتن کوئری‌ها دقت کنید که حتما بین دایمنشن‌ها و شاخص‌ها در SSAS ارتباط ایجاد شده باشد. در غیر این صورت خروجی نامعتبر خواهد بود. به عنوان مثال در کوئری زیر بین شاخص [Measures].[Reseller Sales Amount] و مشتری ارتباطی وجود ندارد (این ارتباط باید در هنگام ساخت Cube  در SSAS  ایجاد می‌گردید تا در هنگام Deploy  کردن محاسبات لازم صورت بگیرد) بنابر این در خروجی شاهد نمایش شاخص پیش فرض Cube می‌باشیم.

Select
[Measures].[Reseller Sales Amount] on columns,
[Customer].[Customer Geography].[Customer].members on rows
From [Adventure Works]

قبلا گفته شد که هر سطح دارای Members می‌باشد و میتوان برای هر عضو Children‌های آن عضو را نمایش داد، با این وجود دقت کنید که اعضای آخرین سطح ساختار سلسله مراتبی دارای Children نمی‌باشند.

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Customer].[Crystal Zheng].children on rows
From [Adventure Works]

خروجی خالی می‌باشد.

بدست آوردن پدر یک عضو در ساختار سلسله مراتبی :

کوئری زیر را اجرا کنید

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Customer].[Crystal Zheng].parent on rows
From [Adventure Works]

با توجه به ساختار سلسله مراتبی تعریف شده، پدر این مشتری یک کد پستی می باشد .

برای بدست آوردن پدربزرگ یک ساختار سلسله مراتبی داریم :

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.parent on rows
From [Adventure Works]

و برای بدست آوردن پدر در 4 سطح بالاتر داریم

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.parent.parent.parent
on rows
From [Adventure Works]

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

Select
[Measures].[Internet Sales Amount] on columns,
[Customer].[Customer Geography].[Customer].[Crystal Zheng].parent
.parent.parent.parent.parent.parent
on rows
From [Adventure Works]

برای بدست آوردن نیا می‌توانیم از تابع ancestor() استفاده کنیم.

بدست آوردن نیا برای یک مشتری در سطح شهر

Select
[Measures].[Internet Sales Amount] on columns,
ancestor(
[Customer].[Customer Geography].[Customer].[Crystal Zheng],
[Customer].[Customer Geography].[City]
)on rows
From [Adventure Works]

بدست آوردن نیا برای یک مشتری در سطح شهر

Select
[Measures].[Internet Sales Amount] on columns,
ancestor(
[Customer].[Customer Geography].[Customer].[Crystal Zheng],
[Customer].[Customer Geography].[City]
)on rows
From [Adventure Works]

پارامتر دوم در تابع ancestor می‌تواند هم مشخص کننده‌ی یک سطح باشد توسط نام آن سطح و هم می‌تواند یک عدد باشد که مشخص کننده‌ی تعداد سطحی است که باید در واکشی اطلاعات از ساختار سلسله مراتبی بالا برویم.

Select
[Measures].[Internet Sales Amount] on columns,
ancestor(
[Customer].[Customer Geography].[Customer].[Crystal Zheng],
2
) on rows
From [Adventure Works]

بدست آوردن نیا برای یک مشتری در دو سطح بالاتر

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

Select
[Measures].[Internet Sales Amount] on columns,
ascendants(
[Customer].[Customer Geography].[Customer].[Crystal Zheng]
  ) on rows
From [Adventure Works]

دقت داشته باشید که در این تابع تمامی سطوح از سطح مشخص شده با سمت ریشه باز گردانده می‌شوند. در حالیکه در تابع ancestor فقط سطح مشخص شده در ساختار سلسله مراتبی به سمت ریشه باز گردانده می‌شد.

مرتب سازی اعضای یک ساختار سلسله مراتبی از بالا به پایین به صورت نزولی

select
[Measures].[Internet Sales Amount] on columns,
hierarchize(
ascendants(
[Customer].[Customer Geography].[Customer].[Crystal Zheng]
)
) on rows
From [Adventure Works]

برای مرتب سازی یک ساختار سلسله مراتبی از تابع hierarchize() استفاده می‌کنیم. دقت داشته باشید که کار برروی ساختار‌های سلسله مراتبی یکی از قسمت‌های اصلی در MDX Query ‌ها می‌باشد.

ادامه‌ی این مطالب و کار با ساختار‌های سلسله مراتبی را در مقالات بعدی دنبال خواهیم کرد.

مطالب
خلاصه‌ای در مورد روش‌های دریافت فایل از سایت NuGet
بهبود سرعت دریافت بسته‌های نیوگت

در کشور بسیاری از اوقات دسترسی به پروتکل HTTPS به کندی صورت می‌گیرد. گاهی از اوقات نیز این دسترسی غیر ممکن می‌شود تا حد دریافت چند بایت در دقیقه. همین مساله تاکنون بر روی بسیاری از مسایل دیگر نیز تاثیر گذار بوده است؛ برای مثال اگر یک مخزن کد را مثلا در CodePlex یا GitHub داشته باشید، چون تمام Commitها از طریق همین پروتکل امن صورت می‌گیرد، کار کردن با آن‌ها بسیار مشکل خواهد شد. نمونه‌ی دیگر آن دسترسی به NuGet است. فید NuGet در VS.NET به Https تنظیم شده است. اگر دسترسی به Https برای شما به کندی صورت می‌گیرد فقط کافی است مسیر فید آن‌را در منوی Tools، گزینه‌ی Options، ذیل قسمت Package manager یافته و به http://nuget.org/api/v2 تغییر دهید؛ یعنی به Http خالی، بجای Https؛ تا سرعت دریافت بسته‌های NuGet مورد نظر افزایش یابند.


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

برای دریافت بسته‌های نیوگت که دارای پسوند nupkg هستند، اما در اصل یک فایل zip بیشتر نیستند، الزامی به استفاده از ابزار و افزونه نیوگت در VS.NET نیست. می‌توان این بسته‌ها را به صورت مستقیم نیز دریافت کرد. برای مثال اگر آدرس بسته‌ای در سایت NuGet به صورت زیر است:
https://www.nuget.org/packages/PropertyChanged.Fody
برای دریافت مستقیم آن کافی است آدرس ذیل را درخواست کنید:
https://www.nuget.org/api/v2/package/PropertyChanged.Fody/1.42.0
یک api/v2 به این لینک اضافه می‌شود به همراه شماره نگارش مدنظر برای دریافت:
 https://www.nuget.org/api/v2/package/{packageID}/{packageVersion}
و یا برای مثال در سایت نیوگت عضو شوید و سپس به آن لاگین کنید. به این ترتیب با مراجعه به هر کتابخانه‌ای که در آنجا آپلود شده، یک لینک download در کنار صفحه، سمت چپ ظاهر می‌شود. با کلیک بر روی آن فایل nupkg آن کتابخانه قابل دریافت خواهد بود. این فایل در حقیقت یک فایل zip است. بنابراین کار کردن با محتویات آن ساده‌است.
به صورت خلاصه:
لینک اصلی کتابخانه: https://www.nuget.org/packages/Twitter.Bootstrap.RTL.Less/3.0.0
لینک دانلود آن: https://www.nuget.org/api/v2/package/Twitter.Bootstrap.RTL.Less/3.0.0

راه دیگر، ساخت دستی این آدرس است:
https://az320820.vo.msecnd.net/packages/propertychanged.fody.1.42.0.nupkg
که در حقیقت تشکیل شده است از:
 https://az320820.vo.msecnd.net/packages/{name}.{version}.nupkg
اگر نام آخرین بسته ارسالی PropertyChanged.Fody 1.42.0 باشد. فقط کافی است این دو قسمت را، یعنی نام و شماره نگارش را با یک نقطه به هم متصل کنید و سپس به انتهای آن، پسوند nupkg را اضافه نمائید. این فایل در آدرس https://az320820.vo.msecnd.net/packages/ به صورت مستقیم قابل دریافت است.


اهمیت تنظیمات IE

اگر پیام قطع شدن اتصال یا مشکلات DNS را در کنسول NuGet در VS.NET دریافت می‌کنید، ابتدا سعی کنید همان روش ذکر شده در ابتدای بحث را امتحان کنید. اگر کار نکرد احتمالا مشکل از تنظیمات IE است. برای مثال اگر بر روی تنظیمات اتصالی شما در IE یک پروکسی غیرقابل دسترسی در زمان جاری، تنظیم شده باشد، این مساله مستقیما بر روی اتصالات برنامه‌های دات نتی نیز تاثیر گذار است. بنابراین ابتدا لینک nupkg را که ساخته‌اید یکبار با IE امتحان کنید. اگر قابل دریافت نبود یعنی تنظیمات آن به هم ریخته است و این مساله بر روی بسیاری از برنامه‌های دیگر نیز تاثیر گذار است.
مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت هفتم
قسمت ششم 

20.اسکریپت در پایین صفحه
لینک‌های مربوطه به javascript‌های خود را تا جای ممکن در پایین صفحه قرار دهید. وقتی parser مرورگر به فایل‌های javascript می‌رسد، تمامی فعالیت‌ها را متوقف کرده و سعی در دانلود و سپس اجرای آن دارد. برخلاف اینکه مرورگرها امکان دانلود چند فایل را به صورت همزمان از سرور دارند، هنگامی که به اسکریپت‌ها می‌رسند، تنها یک فایل را دانلود می‌کنند. یعنی اجرای برنامه و دانلودهای مرتبط با صفحه شما متوقف شده و پس از دانلود و اجرای اسکریپت اجرای آنها ادامه پیدا می‌کند. این مسئله وقتی نمود بیشتری پیدا می‌کند که شما فایل هغای اسکریپت با حجم و تعداد بالا در برنامه خود استفاده می‌کنید.
برای فرار از این مشکل می‌توانید تگهای مربوط به اسکریپت را در آخر صفحات خود بگذارید. فقط دقت کنید اگر نیاز است که قبل از نمایش صفحه تغییری ذر DOM ایجاد کنید، باید حتما اسکریپت‌های مربوطه را بالای صفحه قرار دهید.
روش دیگر دانلود فایل‌های اسکریپت به وسیله AJAX است که انشاء الله در آینده مقاله ای در این رابطه خواهم نوشت.

21.CDN
CDN یا Content Delivery Network سرورهای توزیع شده ای در سطح دنیا هستند که یک نسخه از برنامه شما برای اجرا بر روی آن قرار دارد. هنگامی که کاربر می‌خواهد به سایت شما دسترسی پیدا کند به صورت خودکار به نزدیکترین سرور منتقل می‌شود تا بتواند سرعت بیشتری را تجربه کند. علاوه بر این CDN باعث بالانس شدن بار ترافیک شبکه شما شده خط حملات D.O.S و D.D.O.S را به حداقل می‌رساند.

زمانیکه شما یک سیستم CDN را فعال میکنید تاثیر آن بصورت زیر خواهد بود:
۱- شبکه توزیع محتوا یا همان CDN تمامی سرورهای شبکه جهانی اینترنت را پوشش میدهد. بنابراین زمانیکه شما این سیستم را برای سایت خود فعال میکنید اطلاعات شما بر روی تمامی این سرورها کپی و ذخیره میشود و زمانیکه یک بازدیدکننده به سایت یا وبلاگ شما وارد میشود محتوای سایت شامل تصاویر و متون را از نزدیک‌ترین سرور نزدیک به خود دریافت میکند و مستقیما به هاست یا سرور شما متصل نمیشود. این کار موجب بهبودی چشمگیر در عملکرد سایت شما خواهد شد.
۲- CDN تمام اطلاعات ثابت شما مانند تصاویر، کدهای CSS و javascript، mp3، pdf و فایلهای ویدئویی شما را پشتیبانی میکند و تنها اطلاعاتی که قابل تغییر و بروزرسانی هستند مانند متون و کدهای HTML از سرور اصلی شما فراخوان میشوند. با این کار مصرف پهنای باند هاست شما کاهش یافته و هزینه ای که سالانه برای آن میپردازید کاهش چشمگیری خواهد داشت.
۳- تفاوت سرعت و عملکرد برای خودتان یا افرادی که در نزدیکی سرور اصلی شما هستند تفاوت زیادی نخواهد داشت ولی برای کسانی که ار نقاط مختلف جهان به سایت شما وارد میشوند این افزایش سرعت ناشی از CDN کاملا محسوس خواهد بود، با توجه به اینکه سایتهای ایرانی معمولا سرور و هاست خود را از خارج و کشورهایی مانند آلمان و آمریکا تهیه میکنند و عموم بازدیدکنندگان از داخل کشور هستند استفاده از CDN میتواند بسیار موثر باشد. برای تعیین تاثیر CDN بر سرعت سایت میتوانید عملکرد خود را با ابزارهایی مانند Pingdom و GTmetrix بعد و قبل از فعال سازی CDN بررسی و مقایسه کنید. 
ابزارها، تکنیک‌ها و روش‌های متفاوتی برای راه اندازی CDN وجود دارد که نیازمند مقاله ای مجزا می‌باشد.
مطالب
خلاصه اشتراک‌های روز یک شنبه 1390/06/27

مطالب
Cookie - قسمت سوم

Cookie - قسمت اول: مقدمه، تاریخچه، معرفی، و شرح کامل

Cookie - قسمت دوم: کوکی در جاوا اسکریپت

نکته مهم: خواندن قسمت‌های قبلی این سری (مخصوصا قسمت اول) برای درک بهتر مطالب پیشنهاد می‌شود.



کوکی در ASP.NET - بخش اول

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

در ادامه این سری مطالب به نحوه برخورد ASP.NET با کوکی‌ها و چگونگی کار کردن با کوکی در سمت سرور آشنا خواهیم شد. در بخش اول این قسمت مباحث ابتدایی و اولیه برای کار با کوکی‌ها در ASP.NET ارائه می‌شود. در بخش دوم مباحث پیشرفته‌تر همچون SubCookieها در ASP.NET و نیز سایر نکات ریز کار با کوکی‌ها در ASP.NET بحث خواهد شد.

.


Response و Request در ASP.NET

در قسمت اول این سری به مفاهیم Http Response و Http Request اشاره کوتاهی شده بود. به‌صورت خلاصه، درخواستی که از سمت یک کلاینت به یک وب سرور ارسال می‌شود Request و پاسخی که وب سرور به آن درخواست می‌دهد Response نامیده می‌شود.

در ASP.NET، کلیه اطلاعات مرتبط با درخواست رسیده از سمت یک کلاینت در نمونه‌ای منحصر به فرد از کلاس HttpRequest نگه‌داری می‌شود. محل اصلی نگه‌داری این نمونه در پراپرتی Request از نمونه جاری کلاس System.Web.HttpContext (قابل دسترسی ازطریق HttpContext.Current) است. البته کلاس Page هم یک پراپرتی با نام Request دارد که دقیقا از همین پراپرتی کلاس HttpContext استفاده می‌کند.

هم‌چنین کلیه اطلاعات مرتبط با پاسخ ارسالی وب سرور به سمت کلاینت مربوطه در نمونه‌ای از کلاس HttpResponse ذخیره می‌شود. محل اصلی نگه‌داری این نمونه نیز در پراپرتی Response از نمونه جاری کلاس HttpContext است. همانند Request، کلاس Page یک پراپرتی با نام Response برای نگه‌داری این نمونه دارد که این هم دقیقا از پراپرتی متناظر در کلاس HttpContext استفاده می‌کند.



کوکی‌ها در Response و Request

هر دو کلاس HttpResponse و HttpRequest یک پراپرتی با عنوان Cookies (^ و ^) دارند که مخصوص نگهداری کوکی‌های مربوطه هستند. این پراپرتی از نوع System.Web.HttpCookieCollection است که یک کالکشن مخصوص برای ذخیره کوکی‌هاست.

- این پراپرتی (Cookies) در کلاس HttpRequest محل نگه‌داری کوکی‌های ارسالی توسط مرورگر در درخواست متناظر آن است. کوکی‌هایی که مرورگر با توجه به شرایط جاری و تنظیمات کوکی‌ها اجازه ارسال به سمت سرور را به آن‌ها داده و در درخواست ارسالی ضمیمه کرده است (با استفاده از هدر :Cookie که در قسمت اول شرح داده شد) و ASP.NET پس از پردازش و Parse داده‌ها، درون این پراپرتی اضافه کرده است.

- این پراپرتی (Cookies) در کلاس HttpResponse محل ذخیره کوکی‌های ارسالی از وب سرور به سمت مرورگر کلاینت در پاسخ به درخواست متناظر است. کوکی‌های درون این پراپرتی پس از بررسی و استخراج داده‌های موردنیاز توسط ASP.NET در هدر پاسخ ارسالی ضمیمه خواهند شد (با استفاده از هدر :Set-Cookie که در قسمت اول توضیح داده شد).

.


ایجاد و به‌روزرسانی کوکی در ASP.NET

برای ایجاد یک کوکی و ارسال آن به سمت کلاینت همان‌طور که در بالا نیز اشاره شد، باید از پراپرتی Response.Cookies از کلاس HttpContext استفاده کرد. برای ایجاد یک کوکی روش‌های مختلفی وجود دارد.

  • در روش اول با استفاده از ویژگی مخصوص ایندکسر کلاس HttpCookieCollection عملیات تولید کوکی انجام می‌شود. در این روش، ابتدا بررسی می‌شود که کوکی موردنظر در لیست کوکی‌های جاری وجود دارد یا خیر. درصورتی‌که با این نام قبلا یک کوکی ثبت شده باشد، مقدار کوکی موجود بروزرسانی خواهد شد. اما اگر این نام وجود نداشته باشد یک کوکی جدید با این نام به لیست افزوده شده و مقدار آن ثبت می‌شود. مثال:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myCookieValue";
  • روش بعدی استفاده از متد Add در کلاس HttpCookieCollection است. در این روش ابتدا یک نمونه از کلاس HttpCookie ایجاد شده و سپس این نمونه به لیست کوکی‌ها اضافه می‌شود. کد زیر چگونگی استفاده از این روش را نشان می‌دهد:
var myCookie = new HttpCookie("myCookie", "myCookieValue");
HttpContext.Current.Response.Cookies.Add(myCookie);
  • روش دیگر استفاده از متد Set کلاس HttpCookieCollection است. تفاوت این متد با متد Add در این است که متد Set ابتدا سعی می‌کند عملیات update انجام دهد. یعنی عملیات افزودن تنها وقتی‌که نام کوکی موردنظر در لیست کوکی‌ها یافته نشود انجام خواهد شد. برای مثال:
HttpContext.Current.Response.Cookies.Set(new HttpCookie("myCookie", "myCookieValue"));

نکته: باتوجه به توضیحات بالا، متد Set اجازه افزودن دو کوکی با یک نام را نمی‌دهد. برای اینکار باید از متد Add استفاده کرد. درباره این موضوع در قسمت بعدی بیشتر توضیح داده خواهد شد.

  • روش دیگری که برای ایجاد یکی کوکی می‌توان از آن استفاده کرد، بکارگیری متد AppnedCookie از کلاس HttpResponse است. در این روش نیز ابتدا باید یک نمونه از کلاس HttpCookie تولید شود. این روش همانند استفاده از متد Add از کلاس HttpCookieCollection است. کد زیر مثالی از این روش را نشان می‌دهد:

HttpContext.Current.Response.AppendCookie(new HttpCookie("myCookie", "myCookieValue"));
  • روش بعدی استفاده از متد SetCookie از کلاس HttpResponse است. فرق این متد با متد AppendCookie در این است که در متد SetCookie ابتدا وجود یک کوکی با نام ارائه شده بررسی می‌شود و درصورت وجود، مقدار این کوکی بروزرسانی می‌شود. درصورتی‌که قبلا یک کوکی با این نام وجود نداشته باشد، یک کوکی جدید به لیست کوکی‌ها اضافه می‌شود. این روش همانند استفاده از متد Set از کلاس HttpCookieCollection است. نمونه‌ای از نحوه استفاده از این متد در زیر آورده شده است:
HttpContext.Current.Response.SetCookie(new HttpCookie("myCookie", "myCookieValue"));

نکته: تمامی فرایندهای نشان داده شده در بالا تنها موجب تغییر محتویات کالکشن کوکی‌ها درون HttpContext می‌شود و تا زمانی‌که توسط وب سرور با استفاده از دستور Set-Cookie به سمت مرورگر ارسال نشوند تغییری در کلاینت بوجود نخواهند آورد.

برای آشنایی بیشتر با این روند کد زیر را برای تعریف یک کوکی جدید درنظر بگیرید:

HttpContext.Current.Response.Cookies["myCookie"].Value = "myValue";
برای مشاهده هدر تولیدی توسط وب سرور می‌توان از نرم افزار محبوب Fiddler استفاده کرد (از اواخر سال 2012 که نویسنده این ابزار به Telerik پیوسته، توسعه آن بسیار فعال‌تر شده و نسخه‌های جدید با لوگوی جدید! ارائه شده است).
تصویر زیر مربوط به مثال بالاست:

همانطور که مشاهده می‌کنید دستور ایجاد یک کوکی با نام و مقدار وارده در هدر پاسخ تولیدی توسط وب سرور گنجانیده شده است.

نکته: در ASP.NET به صورت پیش فرض از مقدار "/" برای پراپرتی Path استفاده می‌شود.


خواص کوکی در ASP.NET

برای تعیین یا تغییر خواص یک کوکی در ASP.NET باید به نمونه HttpCookie مربوطه دست یافت. سپس با استفاده از پراپرتی‌های این کلاس می‌توان خواص موردنظر را تعیین کرد. برای مثال:

var myCookie = new HttpCookie(string.Empty);
myCookie.Name = "myCookie";
myCookie.Value = "myCookieValue";
myCookie.Domain = "dotnettip.info";
myCookie.Path = "/post";
myCookie.Expires = new DateTime(2015, 1, 1);
myCookie.Secure = true;
myCookie.HttpOnly = true;

نکته مهم: امکان تغییر خواص یک کوکی به صورت مستقیم در سمت سرور وجود ندارد. درواقع برای اعمال این تغییرات در سمت کلاینت باید به ازای هر کوکی موردنظر یک کوکی جدید با مقادیر جدید ایجاد و به کالکشن کوکی‌ها در Http Response مربوطه اضافه شود تا پس از قرار دادن دستور Set-Cookie متناظر در هدر پاسخ ارسالی به سمت کلاینت و اجرای آن توسط مرورگر، مقادیر خواص مورنظر در سمت کلاینت بروزرسانی شوند. دقت کنید که تمامی نکات مرتبط با هویت یک کوکی که در قسمت اول شرح داده شد در اینجا نیز کاملا صادق است.

روش دیگری نیز برای تعیین برخی خواص کوکی‌ها به صورت کلی در فایل وب کانفیگ وجود دارد. برای اینکار از تگ httpCookies در قسمت system.web استفاده می‌شود. برای مثال:

<httpCookies domain="www.example.com" httpOnlyCookies="true" requireSSL="true" />

این امکان از ASP.NET 2.0 به بعد اضافه شده است. با استفاده از این تگ، تنظیمات اعمال شده برای تمامی کوکی‌ها درنظر گرفته می‌شود. البته درصورتی‌که تنظیم موردنظر برای کوکی به صورت صریح آورده نشده باشد. برای نمونه به کد زیر دقت کنید:

var myCookie = new HttpCookie("myCookie", "myCookieValue");
myCookie.Domain = "test.com";
HttpContext.Current.Response.Cookies.Add(myCookie);
var myCookie2 = new HttpCookie("myCookie2", "myCookieValue2");
myCookie2.HttpOnly = false;
myCookie2.Secure = false;
HttpContext.Current.Response.Cookies.Add(myCookie2);

با استفاده از تنظیمات تگ httpCookies که در بالا نشان داده شده است، هدر پاسخ تولیدی توسط وب سرور به صورت زیر خواهد بود:

همان‌طور که می‌بینید تنها مقادیر پراپرتی‌هایی که صراحتا برای کوکی آورده نشده است از تنظیمات وب کانفیگ خوانده می‌شود.

. 


حذف کوکی در ASP.NET

برای حذف یک کوکی در ASP.NET یک روش کلی وجود دارد که در قسمت‌های قبلی نیز شرح داده شده است، یعنی تغییر خاصیت Expires کوکی به تاریخی در گذشته. برای نمونه داریم:

var myCookie = new HttpCookie("myCookie", "myCookieValue");
myCookie.Expires = DateTime.Now.AddYears(-1);

نکته مهم: در کلاس HttpCookieCollection یک متد با نام Remove وجود دارد. از این متد برای حذف یک کوکی از لیست موجود در این کلاس استفاده می‌شود. دقت کنید که حذف یک کوکی از لیست کوکی‌ها با استفاده از این متد تاثیری بر موجودیت آن کوکی در سمت کلاینت نخواهد گذاشت و تنها روش موجود برای حذف یک کوکی در سمت کلاینت همان تنظیم مقدار خاصیت Expires است.


خواندن کوکی در ASP.NET

برای خواندن مقدار یک کوکی ارسالی از مرورگر کلاینت در ASP.NET، باتوجه به توضیحات ابتدای این مطلب، طبیعی است که باید از پراپرتی Request.Cookies در نمونه جاری از کلاس HttpContext استفاده کرد. برای این کار نیز چند روش وجود دارد.

  • روش اول استفاده از ایندکسر کلاس HttpCookieCollection است. برای اینکار نیاز به نام یا ایندکس کوکی موردنظر در لیست مربوطه داریم. برای مثال:
var myCookie = HttpContext.Current.Request.Cookies["myCookie"];
  • یا این نمونه با استفاده از ایندکسر عددی:
var myCookie = HttpContext.Current.Request.Cookies[0];
  • روش دیگری که برای خواند مقدار یک کوکی می‌توان بکار برد، استفاده از متد Get از کلاس HttpCookieCollection است. این متد همانند ایندکسر این کلاس نیاز به نام یا ایندکس کوکی موردنظر دارد. برای نمونه:
var myCookie = HttpContext.Current.Request.Cookies.Get("myCookie");
var myCookie = HttpContext.Current.Request.Cookies.Get(0);

.


بحث و نتیجه گیری

تا اینجا با مفاهیم اولیه درباره نحوه برخورد ASP.NET با کوکی‌ها آشنا شدیم. روش‌های مختلف ایجاد و یا به‌روزرسانی کوکی‌ها نشان داده شد. با تعیین انواع خواص کوکی‌ها آشنا شدیم. نحوه حذف یک کوکی در ASP.NET را دیدیم. روش‌های خواندن مقادیر کوکی‌ها را نیز مشاهده کردیم.

باز هم تاکید می‌کنم که تمامی تغییرات اعمالی در سمت سرور تا زمانی‌که به‌صورت دستورات Set-Cookie در هدر پاسخ وب سرور قرار نگیرند هیچ کاری در سمت کلاینت انجام نمی‌دهند.

در قسمت بعدی این سری مطالب به مباحث پیشرفته‌تری چون SubCookieها در ASP.NET و هویت منحصر به فرد کوکی‌ها در سمت سرور پرداخته می‌شود.



منابع

http://msdn.microsoft.com/en-us/library/ms178194(v=vs.100).aspx

http://msdn.microsoft.com/en-us/library/aa289495(v=vs.71).aspx

http://www.codeproject.com/Articles/31914/Beginner-s-Guide-To-ASP-NET-Cookies

http://www.codeproject.com/Articles/244904/Cookies-in-ASP-NET
نظرات مطالب
مفاهیم برنامه نویسی ـ آشنایی با سازنده‌ها
ضمن تشکر از دوستانی که در بحث شرکت کردند و پوزش به دلیل اینکه چند هفته ای در سفر هستم و تهیه مطالب با تأخیر انجام خواهد شد.
برای پاسخ به پرسش دوست گرامی آقای نجف زاده ابتدا بخشی از این مطلب را یادآوری می‌کنم.
"... مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی می‌کنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی می‌کنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString) ..."

بنابراین به نکات زیر توجه فرمایید.
۱. در این مطالب سعی شده است امکان پیاده سازی یک مفهوم به دو صورت متد و پروپرتی نشان داده شود تا در ذهن خواننده زمینه ای برای بررسی بیشتر مفهوم متد و پروپرتی و تفاوت آن‌ها فراهم گردد. این زمینه برای کنجکاوی بیشتر معمولاً با انجام یک جستجوی ساده سبب توسعه و تثبیت علم شخص می‌گردد.
۲. در متن بالاً به صورت کلی اشاره شده است هر یک از دو مفهوم متد و پروپرتی در کجا باید استفاده شوند و نیز خاطرنشان شده است در مطالب بعدی در مورد این موضوع بیشتر صحبت خواهد شد.
۳. نکته مهم در طراحی کلاس، پایگاه داده و ... خرد جهان واقع یا محیط عملیاتی مورد نظر طراح است. به عبارت دیگر گسی نمی‌تواند به یک طراح بگوید به طور مثال مساحت باید متد باشد یا باید پروپرتی باشد. طراح با توجه به مفهوم و کارکردی که برای هر مورد در ذهن دارد بر اساس اصول و قواعد، متد یا پروپرتی را بر می‌گزیند. مثلاً در خرد جهان واقع موجود در ذهن یک طراح مساحت به عنوان یک عمل یا اکشنی که شیء انجام می‌دهد است و بنابراین متد را انتخاب می‌کند. طراح دیگری در خرد جهان واقع دیگری در حال طراحی است و مثلاً متراژ یک شیء خانه را به عنوان یک ویژگی ذاتی و داده ای می‌نگرد و گمان می‌کند خانه نیازی به انجام عملی برای بدست آوردن مساحت خود ندارد بلکه یکی از ویژگی‌های خود را می‌تواند به اطلاع استفاده کننده برساند. پس شما به طراح دیگر نگویید اکشن تلقی میشه پس باید متد استفاده شود. اگر خود در پروژه ای چیزی را اکشن تلقی نمودید بله باید متد به کار ببرید. تلقی‌ها بر اساس خرد جهان واقع معنا دارند.
۴. پروپرتی و متد از نظر شیوه استفاده و ... با هم تفاوت دارند. اما یک تفاوت مهم بین آنها بیان نوع مفاهیم موجود در ذهن طراح به کد مشتری است. فراموش نکنید خود پروپرتی دارای اکسسور است که چیزی مانند متد است. در خیلی از موارد صحیح‌تر بودن پیاده سازی با متد یا با پروپرتی معنا ندارد. انتخاب ما بین متد یا پروپرتی بر اساس نحوه استفاده مطلوب در کد مشتری و نیز اطلاع به مشتری که مثلاً فلان مفهوم از دید ما یک اکشن است و فلان چیز داده صورت می‌گیرد.

نظرات مطالب
تفاوت‌های پروژه‌های ما و پروژه‌های اونا!
من اتفاقاً از همون روزهای اول در دسترس قرار گرفتن ویراستیار اون رو دریافت و نصب و تست کردم. واقعیتش اینه که به قضاوت من این کار، حاصل زحمت یک یا چند برنامه‌نویس تازه‌کار (اما خوش‌آتیه) است که در کارشون خیلی از نیازمندیهای اولیۀ چنین پروژه‌ای پیش‌بینی نشده. من به عنوان یه کاربر Word در گام اول دوست دارم غلطیابی به صورت همزمان با تایپ صورت بگیره (امکانی که خود ورد داره) و با تایپ هر کلمۀ غلط، ورد اون رو برام مشخص کنه، ویراستیار چنین کاری نمی‌کنه، امکاناتی که از طریق ویراستیار در دسترسه به درد مرور بعد از تایپ یا تصحیح سندهای موجود می‌خوره و حتی این کار رو هم (وقتی که قراره مثلاً کل سند را تصحیح کنه) به جای این که توی Thread مجزا انجام بده توی رشتۀ اصلی و با به حالت غیرپاسخگو بردن ورد انجام میده. پنجرۀ غلطیابیش حالت Modal داره و ... و نهایتاً این که بعد از نصبش (من با آفیس 2007 چک کردم) متوالیاً ورد کرش می‌کنه. از لحاظ داده‌ها هم که انتظار میره با یه مجموعۀ کامل روبرو باشیم من با همون تستهای اولیه متوجه نقصهای زیادی شدم.
من شخصاً ویراستیار رو در حد یه پروژۀ دانشجویی می‌پذیرم و نمرۀ 20 بهش میدم اما اعتقاد دارم انجام چنین کاری -جدا از فاز جمع‌آوری داده‌هاش- بدون این که اشکالات فعلی رو داشته باشه برای یه برنامه‌نویس متوسط چیزی شاید حدود یک هفته بیشتر کار روزانۀ 8 ساعته زمان نبره.
و اعتقاد دارم ساختن یه پروژۀ کاربردی غلطیابی می‌بایست با ارائۀ یه دیکشنری با قواعد نحوی برای hunspell یا پیاده‌سازی چنین قابلیتی مطابق استاندارد غلطیاب املایی خود آفیس انجام میشده.
مهمترین اشکال این پروژه اینه که یه سازمان دولتی خودش تصمیم گرفته پیاده‌سازیش کنه (و بعد همین سازمان میاد توی سایتش اعلام می‌کنه ما تمایل داریم پروژه‌ها رو برونسپاری کنیم اما ویراستیار به ما ثابت کرذ که بعضی کارها رو فقط ما خودمون می‌تونیم انجام بدیم!) و تمام هدفش -به نظر من- نه برآورده کردن یه نیاز که افتخارآفرینی برای خودشه.