اشتراک‌ها
API نویسی اصولی و حرفه ای در ASP.NET Core

Professional REST API design with ASP.NET Core and WebAPI

This project is an example of lightweight and extensible infrastructure for building RESTful Web API with ASP.NET Core.

This example contains a number of tricks and techniques which I've learned while building APIs in ASP.NET Core.

Techniques and Features

  •  JWT Authentication
  • Secure JWT using Encryption (JWE)
  • Logging to File, Console and Database using Elmah & NLog
  • Logging to sentry.io (Log Management System)
  • Exception Handling using Custom Middleware
  • Automatic Validation
  • Standard API Resulting
  • Dependency Injection using Autofac
  • Map resources using AutoMapper
  • Async/Await Best Practices
  • Versioning Management
  • Using Swagger (Swashbuckle)
  • Auto Document Generator for Swagger
  • Integrate Swagger and Versioning
  • Integrate Swagger and JWT/OAuth Authentication
  • Best Practices for Performance and Security 
API نویسی اصولی و حرفه ای در ASP.NET Core
مطالب
ASP.NET Web API - قسمت چهارم
آشنایی با مفهوم مسیریابی در Web API
در این قسمت با نحوه‌ی تناظر آدرس‌ها توسط Web API به متدهای موجود در Controller آشنا می‌شوید.
در هر درخواستی که ارسال می‌شود، Web API، انتخاب Controller مناسب را با رجوع به جدولی با نام جدول مسیرها انجام می‌دهد. زمانی که یک پروژه‌ی جدید با استفاده از ASP.NET MVC 4 ایجاد می‌کنید، یک route پیش فرض به صورت ذیل در متد RegisterRoutes قرار می‌گیرد.
routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);
عبارت api، ثابت است و قسمت‌های {controller} و {id} توسط آدرس مقداردهی می‌شوند. زمانی که آدرسی با این الگو تطبیق داشته باشد، کارهای ذیل انجام می‌گیرد:
  • {controller} به نام Controller تناظر پیدا می‌کند.
  • نوع درخواست ارسالی (GET، POST، PUT، DELETE) به نام متد تناظر پیدا می‌کند.
  • اگر قسمت {id} در آدرس وجود داشته باشد، به پارامتر id متد انتخاب شده پاس داده می‌شود.
  • اگر آدرس دارای Query String باشد، به پارامترهای همنام خود در متد، تناظر پیدا می‌کنند.

در ذیل، مثال هایی را از چند آدرس درخواستی و نتیجه‌ی حاصل از فراخوانی آنها مشاهده می‌کنید. 

  • آدرس api/products/ با نوع درخواست GET به متد ()GetAllProducts
  • آدرس api/products/1/ با نوع درخواست GET به متد (1)GetProductById
  • آدرس api/products?category=hardware/ با نوع درخواست GET به متد ("GetProductByCategory("hardware


در آدرس اول، عبارت "products" به ProductsController تطبیق پیدا می‌کند. درخواست نیز از نوع GET است، بنابراین Web API به دنبال متدی در Controller می‌گردد که نام آن با عبارت GET "آغاز" شده باشد. همچنین، آدرس شامل قسمت {id} نیز نیست. بنابراین، Web API متدی را انتخاب می‌کند که پارامتر ورودی ندارد. متد GetAllProducts در ProductsController، تمامی این شروط را دارد، پس انتخاب می‌شود.
در دومین آدرس، همان حالت قبل وجود دارد، با این تفاوت که در آدرس درخواستی، قسمت {id} وجود دارد. از آنجا که نوع قسمت {id} در متد int ،GetProductById تعریف شده است، باید یک عدد صحیح بعد از آدرس /api/products وجود داشته باشد تا متد GetProductById فراخوانی شود. این عدد به طور خودکار به نوع int تبدیل شده و در پارامتر اول متد GetProductById قرار می‌گیرد. در ذیل، برخی آدرس‌ها را ملاحظه می‌کنید که معتبر نیستند و باعث بروز خطا می‌شوند.

  • آدرس api/products/ با نوع درخواست POST، باعث خطای 405Method Not Allowed می‌شود.
  • آدرس api/users/ با نوع درخواست GET، باعث خطای 404Not Found می‌شود.
  • آدرس api/products/abc/ با نوع درخواست GET، باعث خطای 400Bad Request می‌شود.


در آدرس اول، Client یک درخواست از نوع POST ارسال کرده است. Web API به دنبال متدی می‌گردد که نام آن با عبارت Post آغاز می‌شود. اما متدی با این شرط در ProductsController وجود ندارد. بنابراین، پاسخی که دریافت می‌شود، عبارت "405 Method Not Allowed" است. درخواست برای آدرس /api/users/ نیز معتبر نیست، چون Controllerیی با نام UsersController وجود ندارد. و سومین آدرس نیز بدین دلیل نامعتبر است که قسمت abc نمی‌تواند به یک عدد صحیح تبدیل شود. 

مشاهده‌ی درخواست ارسالی و پاسخ دریافتی
زمانی که با یک وب سرویس کار می‌کنید، مشاهده‌ی محتویات درخواست ارسالی و پاسخ دریافتی می‌تواند کاربرد زیادی در درک نحوه‌ی تعامل بین Client و وب سرویس و کشف خطاهای احتمالی داشته باشد. در Firefox با استفاده از افزونه‌ی Firebug و در Internet Explorer 9 به بالا با ابزار Developer Tools آن می‌توان درخواست‌ها و پاسخ‌ها را مشاهده کرد. در Internet Explorer، کلید F12 را برای اجرای Developer Tools فشار دهید. از قسمت Network بر روی دکمه‌ی Start Capturing کلیک کنید. حال کلید F5 را برای بارگذاری مجدد صفحه فشار دهید. Internet Explorer، درخواست و پاسخ رد و بدل شده بین مرورگر و Web Server  را مانیتور کرده و گزارشی را نشان می‌دهد (شکل ذیل).

از ستون URL، آدرس /api/products/ را انتخاب و بر روی دکمه‌ی Go to detailed view کلیک کنید. در قسمتی که باز می‌شود، گزینه هایی برای مشاهده‌ی هدرهای درخواست، پاسخ و همچنین بدنه‌ی هر یک وجود دارد. به عنوان مثال، اگر قسمت Request headers را انتخاب کنید، خواهید دید که Internet Explorer از طریق هدر Accept، تقاضای پاسخ در قالب JSON را کرده است (شکل ذیل).

اگر قسمت Response body را انتخاب کنید، پاسخ دریافت شده در قالب JSON را خواهید دید. 

در قسمت بعد، با مدیریت کدهای وضعیت HTTP برای اعمال چهارگانه‌ی CRUD آشنا می‌شوید.
  
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت اول
تمام اپلیکیشن‌ها را نمی‌توان در یک پروسس بسته بندی کرد، بدین معنا که تمام اپلیکیشن روی یک سرور فیزیکی قرار گیرد. در عصر حاظر معماری بسیاری از اپلیکیشن‌ها چند لایه است و هر لایه روی سرور مجزایی توزیع می‌شود. بعنوان مثال یک معماری کلاسیک شامل سه لایه نمایش (presentation)، اپلیکیشن (application) و داده (data) است. لایه بندی منطقی (logical layering) یک اپلیکیشن می‌تواند در یک App Domain واحد پیاده سازی شده و روی یک کامپیوتر میزبانی شود. در این صورت لازم نیست نگران مباحثی مانند پراکسی ها، مرتب سازی (serialization)، پروتوکل‌های شبکه و غیره باشیم. اما اپلیکیشن‌های بزرگی که چندین کلاینت دارند و در مراکز داده میزبانی می‌شوند باید تمام این مسائل را در نظر بگیرند. خوشبختانه پیاده سازی چنین اپلیکیشن هایی با استفاده از Entity Framework و دیگر تکنولوژی‌های مایکروسافت مانند WCF, Web API ساده‌تر شده است. منظور از n-Tier معماری اپلیکیشن هایی است که لایه‌های نمایش، منطق تجاری و دسترسی داده هر کدام روی سرور مجزایی میزبانی می‌شوند. این تفکیک فیزیکی لایه‌ها به بسط پذیری، مدیریت و نگهداری اپلیکیشن‌ها در دراز مدت کمک می‌کند، اما معمولا تاثیری منفی روی کارایی کلی سیستم دارد. چرا که برای انجام عملیات مختلف باید از محدوده ماشین‌های فیریکی عبور کنیم.

معماری N-Tier چالش‌های بخصوصی را برای قابلیت‌های change-tracking در EF اضافه می‌کند. در ابتدا داده‌ها توسط یک آبجکت EF Context بارگذاری می‌شوند اما این آبجکت پس از ارسال داده‌ها به کلاینت از بین می‌رود. تغییراتی که در سمت کلاینت روی داده‌ها اعمال می‌شوند ردیابی (track) نخواهند شد. هنگام بروز رسانی، آبجکت Context جدیدی برای پردازش اطلاعات ارسالی باید ایجاد شود. مسلما آبجکت جدید هیچ چیز درباره Context پیشین یا مقادیر اصلی موجودیت‌ها نمی‌داند.

در نسخه‌های قبلی Entity Framework توسعه دهندگان با استفاده از قالب ویژه ای بنام Self-Tracking Entities می‌توانستند تغییرات موجودیت‌‌ها را ردیابی کنند. این قابلیت در نسخه EF 6 از رده خارج شده است و گرچه هنوز توسط ObjectContext پشتیبانی می‌شود، آبجکت DbContext از آن پشتیبانی نمی‌کند.

در این سری از مقالات روی عملیات پایه CRUD تمرکز می‌کنیم که در اکثر اپلیکیشن‌های n-Tier استفاده می‌شوند. همچنین خواهیم دید چگونه می‌توان تغییرات موجودیت‌ها را ردیابی کرد. مباحثی مانند همزمانی (concurrency) و مرتب سازی (serialization) نیز بررسی خواهند شد. در قسمت یک این سری مقالات، به بروز رسانی موجودیت‌های منفصل (disconnected) توسط سرویس‌های Web API نگاهی خواهیم داشت.


بروز رسانی موجودیت‌های منفصل با Web API

سناریویی را فرض کنید که در آن برای انجام عملیات CRUD از یک سرویس Web API استفاده می‌شود. همچنین مدیریت داده‌ها با مدل Code-First پیاده سازی شده است. در مثال جاری یک کلاینت Console Application خواهیم داشت که یک سرویس Web API را فراخوانی می‌کند. توجه داشته باشید که هر اپلیکیشن در Solution مجزایی قرار دارد. تفکیک پروژه‌ها برای شبیه سازی یک محیط n-Tier انجام شده است.

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

همانطور که می‌بینید مدل جاری، سفارشات یک اپلیکیشن فرضی را معرفی می‌کند. می‌خواهیم مدل و کد دسترسی به داده‌ها را در یک سرویس Web API پیاده سازی کنیم، تا هر کلاینتی که از HTTP استفاده می‌کند بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید.

  • در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe1.Service تغییر دهید.
  • کنترلر جدیدی از نوع WebApi Controller با نام OrderController به پروژه اضافه کنید.
  • کلاس جدیدی با نام Order در پوشه مدل‌ها ایجاد کنید و کد زیر را به آن اضافه نمایید.
public class Order
{
    public int OrderId { get; set; }
    public string Product { get; set; }
    public int Quantity { get; set; }
    public string Status { get; set; }
    public byte[] TimeStamp { get; set; }
}
  • با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
  • حال کلاسی با نام Recipe1Context ایجاد کنید و کد زیر را به آن اضافه نمایید.
public class Recipe1Context : DbContext
{
    public Recipe1Context() : base("Recipe1ConnectionString") { }
    
    public DbSet<Order> Orders { get; set; }
    
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>().ToTable("Orders");
        // Following configuration enables timestamp to be concurrency token
        modelBuilder.Entity<Order>().Property(x => x.TimeStamp)
            .IsConcurrencyToken()
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
    }
}

  • فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings>
  <add name="Recipe1ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
</connectionStrings>
  • فایل Global.asax را باز کنید و کد زیر را به آن اضافه نمایید. این کد بررسی Entity Framework Compatibility را غیرفعال می‌کند.
protected void Application_Start()
{
    // Disable Entity Framework Model Compatibilty
    Database.SetInitializer<Recipe1Context>(null);
    ...
}
  • در آخر کد کنترلر Order را با لیست زیر جایگزین کنید.
public class OrderController : ApiController
{
    // GET api/order
    public IEnumerable<Order> Get()
    {
        using (var context = new Recipe1Context())
        {
            return context.Orders.ToList();
        }
    }

    // GET api/order/5
    public Order Get(int id)
    {
        using (var context = new Recipe1Context())
        {
            return context.Orders.FirstOrDefault(x => x.OrderId == id);
        }
    }

    // POST api/order
    public HttpResponseMessage Post(Order order)
    {
        // Cleanup data from previous requests
        Cleanup();
        
        using (var context = new Recipe1Context())
        {
            context.Orders.Add(order);
            context.SaveChanges();
            // create HttpResponseMessage to wrap result, assigning Http Status code of 201,
            // which informs client that resource created successfully
            var response = Request.CreateResponse(HttpStatusCode.Created, order);
            // add location of newly-created resource to response header
            response.Headers.Location = new Uri(Url.Link("DefaultApi",
                new { id = order.OrderId }));
            return response;
        }
    }

    // PUT api/order/5
    public HttpResponseMessage Put(Order order)
    {
        using (var context = new Recipe1Context())
        {
            context.Entry(order).State = EntityState.Modified;
            context.SaveChanges();
            // return Http Status code of 200, informing client that resouce updated successfully
            return Request.CreateResponse(HttpStatusCode.OK, order);
        }
    }

    // DELETE api/order/5
    public HttpResponseMessage Delete(int id)
    {
        using (var context = new Recipe1Context())
        {
            var order = context.Orders.FirstOrDefault(x => x.OrderId == id);
            context.Orders.Remove(order);
            context.SaveChanges();
            // Return Http Status code of 200, informing client that resouce removed successfully
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }

    private void Cleanup()
    {
        using (var context = new Recipe1Context())
        {
            context.Database.ExecuteSqlCommand("delete from [orders]");
        }
    }
}

قابل ذکر است که هنگام استفاده از Entity Framework در MVC یا Web API، بکارگیری قابلیت Scaffolding بسیار مفید است. این فریم ورک‌های ASP.NET می‌توانند کنترلرهایی کاملا اجرایی برایتان تولید کنند که صرفه جویی چشمگیری در زمان و کار شما خواهد بود.

در قدم بعدی اپلیکیشن کلاینت را می‌سازیم که از سرویس Web API استفاده می‌کند.

  • در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe1.Client تغییر دهید.
  • کلاس موجودیت Order را به پروژه اضافه کنید. همان کلاسی که در سرویس Web API ساختیم.

نکته: قسمت هایی از اپلیکیشن که باید در لایه‌های مختلف مورد استفاده قرار گیرند - مانند کلاس‌های موجودیت‌ها - بهتر است در لایه مجزایی قرار داده شده و به اشتراک گذاشته شوند. مثلا می‌توانید پروژه ای از نوع Class Library بسازید و تمام موجودیت‌ها را در آن تعریف کنید. سپس لایه‌های مختلف این پروژه را ارجاع خواهند کرد.

فایل program.cs را باز کنید و کد زیر را به آن اضافه نمایید.

private HttpClient _client;
private Order _order;

private static void Main()
{
    Task t = Run();
    t.Wait();
    
    Console.WriteLine("\nPress <enter> to continue...");
    Console.ReadLine();
}

private static async Task Run()
{
    // create instance of the program class
    var program = new Program();
    program.ServiceSetup();
    program.CreateOrder();
    // do not proceed until order is added
    await program.PostOrderAsync();
    program.ChangeOrder();
    // do not proceed until order is changed
    await program.PutOrderAsync();
    // do not proceed until order is removed
    await program.RemoveOrderAsync();
}

private void ServiceSetup()
{
    // map URL for Web API cal
    _client = new HttpClient { BaseAddress = new Uri("http://localhost:3237/") };
    // add Accept Header to request Web API content
    // negotiation to return resource in JSON format
    _client.DefaultRequestHeaders.Accept.
        Add(new MediaTypeWithQualityHeaderValue("application/json"));
}

private void CreateOrder()
{
    // Create new order
    _order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" };
}

private async Task PostOrderAsync()
{
    // leverage Web API client side API to call service
    var response = await _client.PostAsJsonAsync("api/order", _order);
    Uri newOrderUri;
    
    if (response.IsSuccessStatusCode)
    {
        // Capture Uri of new resource
        newOrderUri = response.Headers.Location;
        // capture newly-created order returned from service,
        // which will now include the database-generated Id value
        _order = await response.Content.ReadAsAsync<Order>();
        Console.WriteLine("Successfully created order. Here is URL to new resource: {0}",  newOrderUri);
    }
    else
        Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}

private void ChangeOrder()
{
    // update order
    _order.Quantity = 10;
}

private async Task PutOrderAsync()
{
    // construct call to generate HttpPut verb and dispatch
    // to corresponding Put method in the Web API Service
    var response = await _client.PutAsJsonAsync("api/order", _order);
    
    if (response.IsSuccessStatusCode)
    {
        // capture updated order returned from service, which will include new quanity
        _order = await response.Content.ReadAsAsync<Order>();
        Console.WriteLine("Successfully updated order: {0}", response.StatusCode);
    }
    else
        Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}

private async Task RemoveOrderAsync()
{
    // remove order
    var uri = "api/order/" + _order.OrderId;
    var response = await _client.DeleteAsync(uri);

    if (response.IsSuccessStatusCode)
        Console.WriteLine("Sucessfully deleted order: {0}", response.StatusCode);
    else
        Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}

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

Successfully created order: http://localhost:3237/api/order/1054
Successfully updated order: OK
Sucessfully deleted order: OK

شرح مثال جاری

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

حال اپلیکیشن کنسول را باز کنید. روی خط اول کد program.cs یک breakpoint تعریف کرده و اپلیکیشن را اجرا کنید. ابتدا آدرس سرویس Web API را پیکربندی کرده و خاصیت Accept Header را مقدار دهی می‌کنیم. با این کار از سرویس مورد نظر درخواست می‌کنیم که داده‌ها را با فرمت JSON بازگرداند. سپس یک آبجکت Order می‌سازیم و با فراخوانی متد PostAsJsonAsync آن را به سرویس ارسال می‌کنیم. این متد روی آبجکت HttpClient تعریف شده است. اگر به اکشن متد Post در کنترلر Order یک breakpoint اضافه کنید، خواهید دید که این متد سفارش جدید را بعنوان یک پارامتر دریافت می‌کند و آن را به لیست موجودیت‌ها در Context جاری اضافه می‌نماید. این عمل باعث می‌شود که آبجکت جدید بعنوان Added علامت گذاری شود، در این مرحله Context جاری شروع به ردیابی تغییرات می‌کند. در آخر با فراخوانی متد SaveChanges داده‌ها را ذخیره می‌کنیم. در قدم بعدی کد وضعیت 201 (Created) و آدرس منبع جدید را در یک آبجکت HttpResponseMessage قرار می‌دهیم و به کلاینت ارسال می‌کنیم. هنگام استفاده از Web API باید اطمینان حاصل کنیم که کلاینت‌ها درخواست‌های ایجاد رکورد جدید را بصورت POST ارسال می‌کنند. درخواست‌های HTTP Post بصورت خودکار به اکشن متد متناظر نگاشت می‌شوند.

در مرحله بعد عملیات بعدی را اجرا می‌کنیم، تعداد سفارش را تغییر می‌دهیم و موجودیت جاری را با فراخوانی متد PutAsJsonAsync به سرویس Web API ارسال می‌کنیم. اگر به اکشن متد Put در کنترلر سرویس یک breakpoint اضافه کنید، خواهید دید که آبجکت سفارش بصورت یک پارامتر دریافت می‌شود. سپس با فراخوانی متد Entry و پاس دادن موجودیت جاری بعنوان رفرنس، خاصیت State را به Modified تغییر می‌دهیم، که این کار موجودیت را به Context جاری می‌چسباند. حال فراخوانی متد SaveChanges یک اسکریپت بروز رسانی تولید خواهد کرد. در مثال جاری تمام فیلدهای آبجکت Order را بروز رسانی می‌کنیم. در شماره‌های بعدی این سری از مقالات، خواهیم دید چگونه می‌توان تنها فیلدهایی را بروز رسانی کرد که تغییر کرده اند. در آخر عملیات را با بازگرداندن کد وضعیت 200 (OK) به اتمام می‌رسانیم.

در مرحله بعد، عملیات نهایی را اجرا می‌کنیم که موجودیت Order را از منبع داده حذف می‌کند. برای اینکار شناسه (Id) رکورد مورد نظر را به آدرس سرویس اضافه می‌کنیم و متد DeleteAsync را فراخوانی می‌کنیم. در سرویس Web API رکورد مورد نظر را از دیتابیس دریافت کرده و متد Remove را روی Context جاری فراخوانی می‌کنیم. این کار موجودیت مورد نظر را بعنوان Deleted علامت گذاری می‌کند. فراخوانی متد SaveChanges یک اسکریپت Delete تولید خواهد کرد که نهایتا منجر به حذف شدن رکورد می‌شود.

در یک اپلیکیشن واقعی بهتر است کد دسترسی داده‌ها از سرویس Web API تفکیک شود و در لایه مجزایی قرار گیرد.

اشتراک‌ها
CSS Nesting چیست؟

In September 2019, the W3C (World Wide Web Consortium) introduced a new CSS nesting feature that simplifies the way we apply styles to our web pages. In this blog post, we will explore this new feature and how it can enhance the Vanilla CSS experience. 

CSS Nesting چیست؟
اشتراک‌ها
معرفی Minimal Real-Time API برای دات نت

This article provides an introduction to a lightweight API for ASP.NET web services provided by DotNetify.SignalR NuGet package that can reduce a lot of boilerplate code when implementing real-time updates, and provide an example of how to create a real-time web component with VueJS without using Node.JS build. 

معرفی Minimal Real-Time API برای دات نت
اشتراک‌ها
جزئیات روش‌های بکار گرفته شده در بهبود کارآیی سایت Smashing Magazine

In this article, we’ll take a close look at some of the changes we made on this very site — running on JAMStack with React — to optimize the web performance and improve the Core Web Vitals metrics. With some of the mistakes we’ve made, and some of the unexpected changes that helped boost all the metrics across the board. 

جزئیات روش‌های بکار گرفته شده در بهبود کارآیی سایت Smashing Magazine
اشتراک‌ها
کتابخانه WebApiThrottle

ASP.NET Web API Throttling handler, OWIN middleware and filter are designed to control the rate of requests that clients can make to a Web API based on IP address, client API key and request route.

public static void Register( HttpConfiguration config )
{
    config.MessageHandlers.Add( new ThrottlingHandler
                                {
                                    Policy = new ThrottlePolicy( perSecond: 1,
                                                                    perMinute: 20,
                                                                    perHour: 200,
                                                                    perDay: 1500,
                                                                    perWeek: 3000 )
                                                {
                                                    IpThrottling = true
                                                },
                                    Repository = new CacheRepository()
                                } );
}
کتابخانه WebApiThrottle
اشتراک‌ها
ساخت اولین پروژه WebAPI با MVC 6
Previous versions of ASP.NET included the Web API framework for creating web APIs. In ASP.NET 5, this functionality has been merged into the MVC 6 framework. Unifying the two frameworks makes it simpler to build apps that include both UI (HTML) and APIs, because now they share the same code base and pipeline. 
ساخت اولین پروژه WebAPI با MVC 6
اشتراک‌ها
کتابخانه togetherjs
TogetherJS is a service for your website that makes it surprisingly easy to collaborate in real-time.
Using TogetherJS two people can interact on the same page, seeing each other's cursors, edits, and browsing a site together. The TogetherJS service is included by the web site owner, and a web site can customize and configure aspects of TogetherJS's behavior on the site.  Demo
کتابخانه togetherjs