مطالب
آشنایی با SplitQuery در EF Core 5x
در دیتابیس‌های رابطه‌ای، داده‌ها(رکوردها)ی مرتبط، با استفاده از Join بدست آورده می‌شوند و بعضا نیاز هست برای رسیدن به یک داده‌ی مورد نیاز، باید چندین Join بین جداول مختلف به کار برده شود. در Entity Framework ، زمانیکه قصد بدست آوردن داده‌های مرتبط را داریم، از Include  استفاده می‌کنیم که در نهایت منجر به همان left Join می‌شود.
برای درک بهتر و توضیح راحت‌تر، فرض کنید بر روی دیتابیس سایت جاری، قصد داریم لیست هر کاربر را به همراه مقالاتی که در سایت منتشر کرده‌است، بدست بیاوریم. برای اینکار قطعه کد زیر را خواهیم داشت :
  var users = context.Users.Include(x => x.Articles).ToList();
دستور فوق، منجر به تولید T-SQL زیر خواهد شد:
SELECT [u].[Id], [u].[FirstName], [u].[LastName], [a].[Id], [a].[Approved], [a].[AuthorId], [a].[Body], [a].[PubDate], [a].[Subject]
FROM [Users] AS [u]
LEFT JOIN [Articles] AS [a] ON [u].[Id] = [a].[AuthorId]
ORDER BY [u].[Id], [a].[Id]
اجرای این دستور، خروجی زیر را به همراه دارد:

شکل یک

همانطور که در عکس فوق مشاهده میکنید، کاربر با شناسه‌ی 1، ده مقاله را منتشر کرده‌است که به ازای تعداد مقالات، سه فیلد شناسه کاربر، نام و نام خانوادگی، تکرار شده‌است و همین اتفاق برای کاربر با شناسه‌ی 2 هم تکرار شده‌است. قطعا در اکثر نرم افزارها، نیاز به چنین کوئری‌ها و داده‌هایی زیاد است و جلوگیری از این تکرار داده‌ها، می‌تواند بر روی کارایی نرم افزار تاثیر گذار باشد.


Cartesian explosion

اجرای یک Join بین جداول با رابطه‌ی one to many، منجر به تکرار ستون‌های جدول طرف one، به تعداد رکورد‌های مرتبط می‌شود. این اتفاق باعث هدر رفت منابع و همچنین کند شدن اجرای کوئری خواهد شد که این مشکل تحت عنوان Cartesian explosion problem شناخته می‌شود.


از نسخه EF Core5.0، امکانی اضافه شده‌است که کمک می‌کند این مشکل را برطرف کنیم و سرعت اجرای کوئری‌ها سریع‌تر شود. Entity Framework به صورت پیش فرض، کوئری‌ها را در قالب یک دستور (یک رفت و برگشت) انجام میدهد، اما میتوان این رفتار را با استفاده از قابلیت SplitQuery تغییر داد.


متد ()SplitQuery

با استفاده از این متد، به Entity Framework الزام میکنیم که بجای استفاده از Join در یک کوئری، کوئری‌های جداگانه‌ای را بر روی دیتابیس اجرا کند. برای کوئری اول که در بالا نوشتیم، به صورت زیر می‌توانیم SplitQuery را اعمال کنیم:

 var users = context.Users.AsSplitQuery().Include(x => x.Articles).ToList();

کوئری حاصل از کد فوق به صورت زیر می‌باشد:

-- First Part  
 SELECT [u].[Id], [u].[FirstName], [u].[LastName]
      FROM [Users] AS [u]
      ORDER BY [u].[Id]
-- Second Part
   SELECT [a].[Id], [a].[Approved], [a].[AuthorId], [a].[Body], [a].[PubDate], [a].[Subject], [u].[Id]
      FROM [Users] AS [u]
      INNER JOIN [Articles] AS [a] ON [u].[Id] = [a].[AuthorId]
      ORDER BY [u].[Id]

همانطور که مشاهده می‌کنید، دو کوئری تولید شده است که کوئری اول برای دریافت لیست کاربران و کوئری دوم برای لیست مقالات تولید شده‌است. این تغییر باعث شده‌است که فیلدهای مورد نیاز از جدول کاربران، به تعداد مقالات هر کاربر تکرار نشود.

شکل 2- خروجی حاصل بعد از اجرا به صورت SplitQuery


فعال سازی به صورت سراسری

همانطور که بیان شد، EF به صورت پیش فرض  کوئری‌ها را در قالب یک درخواست اجرا می‌کند. اگر تمایل دارید خاصیت SplitQuery بر روی تمامی کوئری‌ها اعمال شود، می‌توانید به صورت زیر این امکان را به صورت سراسری اعمال نمایید.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=EFQuerying;",
            o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}

اگر SplitQuery را به صورت سراسری فعال کردید و نیاز داشتید جایی یک کوئری را به همان روش SignleQuery اجرا کنید، میتوانید از متد SingleQuery به صورت زیر استفاده نمایید.

var users = context.Users.AsSingleQuery().Include(x => x.Articles).ToList();


عکس زیر مقایسه ای بین اجرای کوئری‌ها به صورت Single و Split می‌باشد:

مبنع:  thinktecture  



در رابطه با SplitQuery موارد زیر مطرح می‌باشد :

  • زمانیکه کوئری تبدیل به دو یا چند کوئری می‌شود، ممکن است بعد از اجرا کوئری اول و قبل از اجرای کوئری دوم، یک به روزرسانی انجام شود که ممکن است consistency نقض شود.
  • در این حالت، چندین درخواست و رفت و برگشت اجرا می‌شود که همین می‌تواند باعث تاخیر و افزایش زمان گردد.
مطالب
C# 8.0 - Async Streams
امکان تعریف نوع‌های شمارشی async در C# 8.0

فرض کنید قصد دارید یک متد async از نوع IEnumerable را که تعدادی yield return به تاخیر افتاده را به همراه دارد (yield return‌ها فقط زمانی اجرا می‌شوند که بر روی آن‌ها متدهایی مانند ToList و یا حلقه‌ی foreach اجرا شوند) و همچنین توسط await Task.Delay، دریافت اطلاعات به صورت async را نیز شبیه سازی می‌کند، تهیه کنید:
public struct Statement
{
    public int Id { get; }
    public string Description { get; }
    public Statement(int id, string description) => (Id, Description) = (id, description);
    public override string ToString() => Description;
}

public static async Task<IEnumerable<Statement>> GetStatements(bool error)
{
    if (error)
    {
       throw new Exception("Oops, we messed up 😬");
    }

    await Task.Delay(1000); //Simulate waiting for data to come through. 

    yield return new Statement(1, "C# is cool!");
    yield return new Statement(2, "C# orginally named COOL.");
    yield return new Statement(3, "More examples...");
}
این قطعه کد حتی در C# 8.0 نیز چنین خطای کامپایلری را به همراه دارد:
The body of 'AsyncStreams.GetStatements(bool)' cannot be an iterator block because
'Task<IEnumerable<AsyncStreams.Statement>>' is not an iterator interface type (CS1624)
عنوان می‌کند که برای دریافت اطلاعات متد GetStatements باید یک iterator تشکیل شود؛ اما Task IEnumerable از این نوع نیست.

برای رفع یک چنین مشکلی، اکنون در C# 8.0 می‌توان از اینترفیس جدید IAsyncEnumerable بجای Task IEnumerable استفاده کرد. به این ترتیب تنها تغییری که در قطعه کد فوق نیاز است، تغییر امضای آن به صورت زیر است:
static async IAsyncEnumerable<Statement> GetStatements(bool error)


امکان تعریف حلقه‌های async در C# 8.0

مرحله‌ی بعد، ایجاد حلقه‌ای بر روی متد GetStatements است. اکنون مشکل دیگری وجود دارد: حلقه‌ی foreach به خودی خود، یک حلقه‌ی synchronous است و اگر از آن برای کار با یک استریم async استفاده شود، هربار که اطلاعاتی از آن بازگشت داده می‌شود، پایان یک Task نیز گزارش داده خواهد شد که می‌توان سبب خاتمه‌ی حلقه شود. بنابراین انجام اینکار نیز پیش از C# 8.0 میسر نبود که اکنون با امکان تعریف await پیش از یک حلقه‌ی foreach، ممکن شده‌است:
static async IAsyncEnumerable<Statement> GetStatementsAsync(bool error)
{
    await foreach (var statement in GetStatements(error))
    {
      await Task.Delay(1000);
      yield return statement;
    }
}
تا پیش از C# 8.0، از واژه‌ی await تنها برای دریافت یک تک مقدار استفاده می‌شد؛ اما حالا می‌توان از آن برای دریافت استریمی از نتایج (async streams) نیز استفاده کرد.


اینترفیس IAsyncEnumerable چگونه تعریف شده‌است؟

 اینترفیس IAsyncEnumerable متد GetAsyncEnumerator را تعریف می‌کند که یک IAsyncEnumerator را بازگشت می‌دهد و آن نیز به همراه متد MoveNextAsync است. اگر دقت کنید در این حالت از نگارش async اینترفیس IDisposable به نام IAsyncDisposable استفاده کرده‌است:
using System.Threading;

namespace System.Collections.Generic
{
    public interface IAsyncEnumerable<out T>
    {
        IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
    }

    public interface IAsyncEnumerator<out T> : IAsyncDisposable
    {
        T Current { get; }

        ValueTask<bool> MoveNextAsync();
    }
}

namespace System
{
    public interface IAsyncDisposable
    {
        ValueTask DisposeAsync();
    }
}
اینترفیس‌های IAsyncDisposable و IAsyncEnumerator یک ValueTask را توسط متدهای DisposeAsync و MoveNextAsync بازگشت می‌دهند و این مورد به C# 7x باز می‌گردد که امکان await را نه تنها بر روی Task، بلکه بر روی هر نوعی که متد GetAwaiter را پیاده سازی می‌کند، میسر می‌کند و ValueTask نیز یکی از آن‌ها است. ValueTask به صورت یک نوع مقدار (value type) تعریف شده‌است؛ بجای نوع ارجاعی Task که سربار کمتری را به همراه دارد.


مثالی از IAsyncDisposable و روش Dispose خودکار آن

با معرفی IAsyncDisposable، اگر یک مثال ساده از پیاده سازی آن به صورت زیر باشد:
public class AwaitUsingTest : IAsyncDisposable
{
   public async ValueTask DisposeAsync()
   {
     await Task.CompletedTask;
   }

   public void Dummy() { }
}
روش فراخوانی using declaration بر روی آن به همراه واژه‌ی کلیدی await در C# 8.0، مانند مثال زیر است:
async Task FooBar()
{
   await using var test = new AwaitUsingTest();
   test.Dummy();
}
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت نهم - تعریف سرویس‌های Open Generics
فرض کنید در برنامه‌ی خود، یک سرویس جنریک را طراحی کرده‌اید. برای مثال خود ASP.NET Core به همراه سرویس جنریک <ILogger<T است و اگر برای نمونه بخواهیم آن‌را در سازنده‌ی کنترلری مانند ValuesController تزریق کنیم، نحوه‌ی تعریف آن به صورت <ILogger<ValuesController خواهد بود. هر چند تنظیمات این سرویس پیشتر انجام شده‌است، اما اگر بخواهیم آن‌را به همین نحو <ILogger<T به متدهایی مانند services.AddScoped معرفی کنیم، کار نمی‌کند؛ نمونه‌ی دیگری از این دست Generic Repositoryها هستند:
 // does not work: services.AddScoped<IGenericRepository<T>,EFRepository<T>>();


نحوه‌ی معرفی سرویس‌های جنریک نامحدود (Open Generics و یا Unbound Generics) به سیستم تزریق وابستگی‌ها

اگر بخواهیم یک سرویس جنریک را به سیستم تزریق وابستگی‌های برنامه‌های NET Core. به نحو متداولی معرفی کنیم، نیاز است به ازای تک تک Tهای میسر و تعریف شده‌ی در برنامه، اینکار صورت گیرد:
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IStore<User>, SqlStore<User>>();
    services.AddScoped<IStore<Invoice>, SqlStore<Invoice>>();
    services.AddScoped<IStore<Payment>, SqlStore<Payment>>();
    // ...
}
و در اینجا به ازای هر T یا موجودیت جدیدی در برنامه، نیاز است یک سطر دیگر را نیز تعریف کرد. خوشبختانه در این سیستم، امکان تعریف جنریک‌های نامحدود و باز نیز وجود دارد:
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(typeof(IStore<>), typeof(SqlStore<>));
}
به این ترتیب تمام سطرهایی که پیشتر تعریف کردیم، تبدیل به یک سطر فوق می‌شوند. در اینجا از Overload غیرجنریک متد AddScoped استفاده شده؛ به همین جهت از واژه‌ی کلیدی typeof برای معرفی نوع‌های جنریک باز کمک گرفته شده‌است. ذکر <> نیز به معنای تفسیر و وهله سازی هر نوع درخواستی رسیده، در زمان اجرا می‌باشد.


محدودیت کار کردن با جنریک‌های نامحدود در سیستم تزریق وابستگی‌ها

با تعریف تک سطر فوق، هر چند برنامه بدون مشکل کامپایل می‌شود، اما اگر در زمان اجرای برنامه، <IStore<T ای را درخواست کنید که میسر نباشد (در خواست هر نوعی در زمان اجرا با جنریک‌های باز معرفی شده، میسر است)، یک استثنای زمان اجرا را دریافت می‌کنید؛ برای مثال اگر نوع T به کلاس‌ها محدود شده باشد و در قسمتی از برنامه، <IStore<int درخواست شود. هرچند این موارد با یکبار آزمایش برنامه، قابل یافت شدن و رفع می‌باشند.


کتابخانه‌ی کمکی Scrutor نیز از جنریک‌های باز پشتیبانی می‌کند

در قسمت قبل نحوه‌ی اسکن اسمبلی‌های برنامه را توسط کتابخانه‌ی کمکی Scrutor بررسی کردیم. این کتابخانه امکان یافتن و فیلتر کلاس‌ها و معرفی آن‌ها را به سیستم تزریق وابستگی‌ها، بر اساس ویژگی جنریک‌های باز نیز دارا است:
services.Scan(scan => scan 
  .FromAssemblyOf<CombinedService>()  
    .AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types 
    .AsMatchingInterface())


ساده سازی «مثال 2: وهله سازی در صورت نیاز وابستگی‌های یک سرویس به کمک Lazy loading» قسمت ششم با جنریک‌های نامحدود

در قسمت ششم نحوه‌ی تعریف پیشنیازهای وهله سازی به تاخیر افتاده را با استفاده از کلاس Lazy بررسی کردیم:
services.AddTransient<IOrderHandler, OrderHandlerLazy>();
services.AddTransient<IAccounting, Accounting>()
            .AddTransient(serviceProvider => new Lazy<IAccounting>(() => serviceProvider.GetRequiredService<IAccounting>()));
services.AddTransient<ISales, Sales>()
           .AddTransient(serviceProvider => new Lazy<ISales>(() => serviceProvider.GetRequiredService<ISales>()));
- در اینجا در ابتدا تمام سرویس‌ها (حتی آن‌هایی که قرار است به صورت Lazy استفاده شوند) یکبار به صورت متداولی معرفی می‌شوند.
- سپس سرویس‌هایی که قرار است به صورت Lazy نیز واکشی شوند، بار دیگر توسط روش factory registration با وهله سازی new Lazy از نوع سرویس مدنظر و فراهم آوردن پیاده سازی آن با استفاده از serviceProvider.GetRequiredService، مجددا معرفی خواهند شد.

اگر به شرط دوم دقت کنید،  <new Lazy<IAccounting و <new Lazy<ISales، دقیقا مانند همان سرویس‌های جنریک <IStore<User و <IStore<Invoice تعریف شده‌اند. یعنی نیاز است به ازای هر T ممکن در برنامه، یکبار <new Lazy<T را نیز به سیستم تزریق وابستگی‌ها معرفی کرد. بنابراین تمام این تعریف‌های اضافی را می‌توان با یک سطر جنریک نامحدود زیر جایگزین و خلاصه کرد:
services.AddTransient(typeof(Lazy<>), typeof(LazyFactory<>));
و نکته‌ی جالب آن، نحوه‌ی تعریف قسمت factory متدهایی مانند AddTransient در اینجا است که به صورت زیر قابل پیاده سازی است:
public class LazyFactory<T> : Lazy<T> where T : class
{
    public LazyFactory(IServiceProvider provider)
        : base(() => provider.GetRequiredService<T>())
    {
    }
}
مطالب
دات نت 4 و کلاس Lazy

یکی از الگوهای برنامه نویسی شیء گرا، Lazy Initialization Pattern نام دارد که دات نت 4 پیاده سازی آن‌را سهولت بخشیده است.
در دات نت 4 کلاس جدیدی به فضای نام System اضافه شده است به نام Lazy و هدف از آن lazy initialization است؛ من ترجمه‌اش می‌کنم وهله سازی با تاخیر یا به آن on demand construction هم گفته‌اند (زمانی که به آن نیاز هست ساخته خواهد شد).
فرض کنید در برنامه‌ی خود نیاز به شیءایی دارید و ساخت این شیء بسیار پرهزینه است. نیازی نیست تا بلافاصله پس از تعریف، این شیء ساخته شود و تنها زمانیکه به آن نیاز است باید در دسترس باشد. کلاس Lazy جهت مدیریت اینگونه موارد ایجاد شده است. تنها کاری که در اینجا باید صورت گیرد، محصور کردن آن شیء هزینه‌بر توسط کلاس Lazy است:

Lazy<ExpensiveResource> ownedResource = new Lazy<ExpensiveResource>();

در این حالت برای دسترسی به شیء ساخته شده از ExpensiveResource ، می‌توان از خاصیت Value استفاده نمود (ownedResource.Value). تنها در حین اولین دسترسی به ownedResource.Value ، شیء ExpensiveResource ساخته خواهد شد و نه پیش از آن و نه در اولین جایی که تعریف شده است. پس از آن این حاصل cache شده و دیگر وهله سازی نخواهد شد.
ownedResource دارای خاصیت IsValueCreated نیز می‌باشد و جهت بررسی ایجاد آن شیء می‌تواند مورد استفاده قرار گیرد. برای مثال قصد داریم اطلاعات ExpensiveResource را ذخیره کنیم اما تنها در حالتیکه یکبار مورد استفاده قرار گرفته باشد.
کلاس Lazy دارای دو متد سازنده‌ی دیگر نیز می‌باشد:
public Lazy(bool isThreadSafe);
public Lazy(Func<T> valueFactory, bool isThreadSafe);

و هدف از آن استفاده‌ی صحیح از این متد در محیط‌های چند ریسمانی است. بدیهی است در این نوع محیط‌ ها علاقه‌ای نداریم که در یک لحظه توسط چندین ترد مختلف، سبب ایجاد وهله‌های ناخواسته‌ا‌ی از ExpensiveResource شویم و تنها یک مورد از آن کافی است یا به قولی thread safe, lazy initialization of expensive objects
بدیهی است اگر برنامه‌ی شما چند ریسمانی نیست می‌توانید این مکانیزم را کنسل کرده و اندکی کارآیی برنامه را با حذف قفل‌های همزمانی این کلاس بالا ببرید.

مثال اول:

using System;
using System.Threading;

namespace LazyExample
{
class Program
{
static void Main()
{
Console.WriteLine("Before assignment");
var slow = new Lazy<Slow>();
Console.WriteLine("After assignment");

Thread.Sleep(1000);

Console.WriteLine(slow);
Console.WriteLine(slow.Value);

Console.WriteLine("Press a key...");
Console.Read();
}
}


class Slow
{
public Slow()
{
Console.WriteLine("Start creation");
Thread.Sleep(2000);
Console.WriteLine("End creation");
}
}
}
خروجی این برنامه به شرح زیر است:

Before assignment
After assignment
Value is not created.
Start creation
End creation
LazyExample.Slow
Press a key...

همانطور که ملاحظه می‌کنید تنها در حالت دسترسی به مقدار Value شیء slow ، عملا وهله‌ای از آن ساخته خواهد شد.

مثال دوم:
شاید نیاز به مقدار دهی خواص کلاس پرهزینه‌ وجود داشته باشد. برای مثال علاقمندیم خاصیت SomeProperty کلاس ExpensiveClass را مقدار دهی کنیم. برای این منظور می‌توان به شکل ذیل عمل کرد (یک Func<t>را می‌توان به سازنده‌ی آن ارسال نمود):

using System;

namespace LazySample
{
class Program
{
static void Main()
{
var expensiveClass =
new Lazy<ExpensiveClass>
(
() =>
{
var fobj = new ExpensiveClass
{
SomeProperty = 100
};
return fobj;
}
);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("expensiveClass.SomeProperty value {0}",
(expensiveClass.Value).SomeProperty);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("Press a key...");
Console.Read();
}
}

class ExpensiveClass
{
public int SomeProperty { get; set; }

public ExpensiveClass()
{
Console.WriteLine("ExpensiveClass constructed");
}
}
}

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

مطالب
آشنایی با آزمایش واحد (unit testing) در دات نت، قسمت 2

دلایل شانه خالی کردن از آزمایش واحد!

1- نوشتن آزمایشات زمان زیادی را به خود اختصاص خواهند داد.

مهمترین دلیلی که برنامه‌نویس‌ها به سبب آن از نوشتن آزمایشات واحد امتناع می‌کنند، همین موضوع است. اکثر افراد به آزمایش به‌عنوان مرحله آخر توسعه فکر می‌کنند. اگر این چنین است، بله! نوشتن آزمایش‌های واحد واقعا سخت و زمانگیر خواهند بود. به همین جهت برای جلوگیری از این مساله روش pay-as-you-go مطرح شده است (ماخذ: کتاب Pragmatic Unit Testing در سی شارپ). یعنی با اضافه شدن هر واحد کوچکی به سیستم، آزمایش واحد آن‌را نیز تهیه کنید. به این صورت در طول توسعه سیستم با باگ‌های کمتری نیز برخورد خواهید داشت چون اجزای آن‌را در این حین به تفصیل مورد بررسی قرار داده‌اید. اثر این روش را در شکل زیر می‌توانید ملاحظه نمائید (تصویری از همان کتاب ذکر شده)




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

بنابراین اگر عنوان می‌کنید که وقت ندارید آزمایش واحد بنویسید، به چند سؤال زیر پاسخ دهید:
الف) چه مقدار زمان را صرف دیباگ کردن کدهای خود یا دیگران می‌کنید؟
ب) چه میزان زمان را صرف بازنویسی کدی کرده‌اید که تصور می‌رفت درست کار می‌کند اما اکنون بسیار مشکل زا ظاهر شده است؟
ج) چه مقدار زمان را صرف این کرده‌اید که منشاء باگ گزارش شده در برنامه را کشف کنید؟

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



تصویری از کتاب xUnit Test Patterns ، که بیانگر کاهش زمان و هزینه کد نویسی در طول زمان با رعایت اصول آزمایشات واحد است

2- اجرای آزمایشات واحد زمان زیادی را تلف می‌کند.

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

3- امکان تهیه آزمایشات واحد برای کدهای قدیمی ( legacy code ) من وجود ندارد

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

4- کار من نیست که کدهای نوشته شده را آزمایش کنم!

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

5- تنها قسمتی از سیستم به من واگذار شده است و من دقیقا نمی‌دانم که رفتار کلی آن چیست. بنابراین آن را نمی‌توانم آزمایش کنم!

اگر واقعا نمی‌دانید که این کد قرار است چه کاری را انجام دهید به طور قطع الان زمان مناسبی برای کد نویسی آن نیست!

6- کد من کامپایل می‌شود!

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

7- من برای نوشتن آزمایشات حقوق نمی‌گیرم!

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

8- احساس گناه خواهم کرد اگر تیم فنی کنترل کیفیت و آزمایشات را از کار بی کار کنم!!

نگران نباشید، این اتفاق نخواهد افتاد! بحث ما در اینجا آزمایش کوچکترین اجزا و واحدهای یک سیستم است. موارد دیگری مانند functional testing, acceptance testing, performance & environmental testing, validation & verification, formal analysis توسط تیم‌های کنترل کیفیت و آزمایشات هنوز باید بررسی شوند.

9- شرکت من اجازه اجرای آزمایشات واحد را بر روی سیستم‌های در حال اجرا نمی‌دهد.

قرار هم نیست بدهد! چون دیگر نام آن آزمایش واحد نخواهد بود. این آزمایشات باید بر روی سیستم شما و توسط ابزار و امکانات شما صورت گیرد.


پ.ن.
در هشتمین دلیل ذکر شده، از acceptance testing نامبرده شده. تفاوت آن با unit testing به صورت زیر است:

آزمایش واحد:
توسط برنامه نویس‌ها تعریف می‌شود
سبب اطمینان خاطر برنامه نویس‌ها خواهد شد
واحدهای کوچک سیستم را مورد بررسی قرار می‌دهد
یک آزمایش سطح پائین ( low level ) به شمار می‌رود
بسیار سریع اجرا می‌شود
به صورت خودکار (100 درصد خودکار است) و با برنامه نویسی قابل کنترل است

اما در مقابل آزمایش پذیرش به صورت زیر است:
توسط مصرف کنندگان تعریف می‌شود
سبب اطمینان خاطر مصرف کنندگان می‌شود.
کل برنامه مورد آزمایش قرار می‌گیرد
یک آزمایش سطح بالا ( high level ) به شمار می‌رود
ممکن است طولانی باشد
عموما به صورت دستی یا توسط یک سری اسکریپت اجرا می‌شود
مثال : گزارش ماهیانه باید جمع صحیحی از تمام صفحات را در آخرین برگه گزارش به همراه داشته باشد


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

مطالب
بررسی کارآیی کوئری‌ها در SQL Server - قسمت ششم - بررسی عملگرهای دسترسی به داده‌ها در یک Query Plan
پس از آشنایی مقدماتی با نحوه‌ی خواندن یک Query Plan، اکنون نوبت به بررسی عملگرهایی است که در آن مشاهده می‌شوند و همچنین تغییرات در کوئری‌ها چگونه بر روی آن‌ها تاثیر گذاشته و آن‌ها را تغییر می‌دهند و این تغییرات چه تاثیری را بر روی کارآیی خواهند داشت.


عملگرهای Scans و Seeks

در حالت کلی می‌توان دو نوع جدول بدون و با ایندکس را درنظر گرفت. در حالت جداول بدون ایندکس، برای جستجوی اطلاعات نیاز به Table Scan وجود دارد و برعکس آن شامل یک Clustered index scan خواهد بود. گاهی از اوقات Clustered index scanها بهترین روش دریافت اطلاعات هستند و گاهی از اوقات خیر و نیاز به بررسی بیشتری دارند. بنابراین قانون کلی، حذف آن‌ها به محض مشاهده، نیست.
نوع دیگر عملگرهای دسترسی به داده‌ها، Seeks هستند که شامل Clustered index seeks و Non-clustered index seeks می‌شوند. در بسیاری از موارد عنوان می‌شود که Seeks کارآیی بهتری را به همراه دارند. هرچند این مورد نیاز به بررسی بیشتری دارد که در ادامه با مثال‌هایی آن‌ها را مرور خواهیم کرد.


بررسی عملگر Table scan در یک Query Plan

در ادامه تعدادی از عملگرهای مرتبط با data access را از لحاظ نحوه‌ی انتخاب و تغییر آن‌ها توسط بهینه ساز کوئری‌های SQL Server بررسی می‌کنیم. برای این منظور ابتدا در management studio از منوی Query، گزینه‌ی Include actual execution plan را انتخاب می‌کنیم. سپس کوئری‌های زیر را اجرا می‌کنیم:
SET STATISTICS IO ON;
GO
SET STATISTICS TIME ON;
GO

SELECT *
INTO [Sales].[Copy_Orders]
FROM [Sales].[Orders];
GO

SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Copy_Orders]
WHERE [CustomerID] > 550;
GO
در اینجا در ابتدا، تمام رکوردهای جدول [Sales].[Orders]، به جدول [Sales].[Copy_Orders] کپی می‌شوند. سپس یک کوئری را بر روی این جدول کپی، اجرا کرده‌ایم.


همانطور که مشاهده می‌کنید، برای برآورده کردن قسمت where این کوئری، یک Table Scan صورت گرفته‌است؛ چون این جدول کپی، به همراه هیچ ایندکسی نیست. به همین جهت برای یافتن رکوردهای مدنظر، راه دیگری بجز اسکن کل جدول بانک اطلاعاتی وجود ندارد که بسیار ناکارآمد است.
همچنین اگر به برگه‌ی messages دقت کنیم، با توجه به روشن بودن STATISTICS IO، میزان logical reads نیز قابل مشاهده‌است:
(33035 rows affected)
Table 'Copy_Orders'. Scan count 1, logical reads 689, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
به علاوه اجرای آن نیز کمی بیشتر از نیم ثانیه، طول کشیده‌است:
SQL Server Execution Times:
CPU time = 79 ms,  elapsed time = 762 ms.


بررسی عملگر Index Seek در یک Query Plan

اکنون سؤال اینجا است که آیا می‌توان این وضعیت را بهبود بخشید؟
بله. برای این منظور یک NONCLUSTERED INDEX را بر روی جدول کپی، ایجاد می‌کنیم؛ به نحوی که CustomerID لحاظ شده‌ی در قسمت where کوئری را پوشش دهد:
CREATE NONCLUSTERED INDEX [IX_Copy_Orders_CustomerID]
ON [Sales].[Copy_Orders] (
[CustomerID]
)
INCLUDE (
[OrderID], [OrderDate]
);
GO
چون مطابق کوئری، [OrderID] و [OrderDate] در قسمت where ذکر نشده‌اند، در اینجا INCLUDE شده‌اند.

در ادامه مجددا همان کوئری را اجرا می‌کنیم:
SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Copy_Orders]
WHERE [CustomerID] > 550;
GO
که سبب تولید کوئری پلن زیر می‌شود:


اینبار عملگر Table Scan قبلی به یک عملگر Index Seek بر روی NONCLUSTERED INDEX تعریف شده، تغییر کرده‌است و اگر به آمار I/O آن دقت کنیم، logical reads 106 قابل مشاهده‌است که بهبود قابل ملاحظه‌ای است نسبت به عدد 689 قبلی.


بررسی عملگر Clustered index scan در یک Query Plan

در ادامه همین کوئری را بر روی جدول [Sales].[Orders] اصلی اجرا می‌کنیم:
SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Orders]
WHERE [CustomerID] > 550;
GO
که به صورت پیش‌فرض شامل این ایندکس‌ها است:


اجرای کوئری فوق، چنین کوئری پلنی را تولید می‌کند:


جدول [Sales].[Orders]، یک CLUSTERED INDEX را بر روی [OrderID] دارد و یک NONCLUSTERED INDEX را بر روی [CustomerID].
در کوئری پلن تولید شده، یک Clustered index scan مشاهده می‌شود. علت اینجا است که هرچند در جدول [Sales].[Orders] یک NONCLUSTERED INDEX بر روی  [CustomerID] تعریف شده‌است:
CREATE NONCLUSTERED INDEX [FK_Sales_Orders_CustomerID] ON [Sales].[Orders]
(
[CustomerID] ASC
)
اما قسمت INCLUDE ایندکس قبلی را که تعریف کردیم، ندارد و به همراه [CustomerID] و [OrderDate] نیست. به همین جهت اینبار logical reads 692 است.

بنابراین وجود عملگر Clustered index scan در یک کوئری پلن، یعنی نیاز به خواندن و اسکن کل جدول وجود دارد. برای اثبات آن، همین کوئری قبلی را که بر روی [Sales].[Orders] انجام دادیم، اینبار بدون قسمت where آن اجرا کنید. یعنی کوئری بر روی کل جدول انجام شود:
SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Orders]
سپس به برگه‌ی messages مراجعه کرده و عدد logical reads آن‌را مشاهده کنید. این عدد دقیقا با عدد logical reads کوئری where دار، یکی است؛ که بیانگر اسکن کامل جدول در حالت Clustered index scan است.

سؤال: آیا Clustered index scan همواره کل یک جدول را اسکن می‌کند؟
پاسخ: خیر. اگر یک کوئری برای مثال دارای top/min/max باشد، کل جدول اسکن نخواهد شد:
SELECT TOP 10
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Orders]
WHERE [CustomerID] > 550;
تفاوت این کوئری با کوئری‌های قبلی، در داشتن یک top 10 است. اگر آن‌را اجرا کنیم، به کوئری پلن زیر خواهیم رسید:


هرچند در اینجا هم یک Clustered index scan صورت گرفته، اما اگر به برگه‌ی messages آن مراجعه کنیم، آمار I/O آن بیانگر تنها logical reads 5 است که معادل اسکن کل جدول نیست:
(10 rows affected)
Table 'Orders'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 510, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.


مقایسه‌ی عملگرهای Index Scan و Index Seek

ابتدا کوئری زیر را اجرا می‌کنیم:
SELECT
    [CustomerID],
    [OrderID]
FROM [Sales].[Orders]
WHERE [OrderID] > 30000;
این کوئری با کوئری قبلی از لحاظ قسمت select اندکی متفاوت بوده و در آن OrderDate حذف شده‌است. در قسمت where نیز کوئری بر روی OrderID صورت گرفته‌است.
در این جدول ایندکسی بر روی CustomerID وجود دارد و همچنین کلید اصلی جدول، OrderID است.

پس از اجرای این کوئری، به کوئری پلن زیر خواهیم رسید:


که بیانگر یک Index Scan است و نکته‌ی جالب آن، استفاده‌ی از ایندکس FK_Sales_Orders_CustomerID می‌باشد (نام این شیء، ذیل آیکن عملگر، مشخص است). یعنی SQL Server در اینجا از یک non-clustered index تعریف شده‌ی بر روی CustomerID استفاده کرده‌است.
اکنون اگر OrderID را تغییر دهیم چه اتفاقی رخ می‌دهد؟
SELECT
    [CustomerID],
    [OrderID]
FROM [Sales].[Orders]
WHERE [OrderID] > 60000;
اینبار به یک clustered index seek رسیدیم که بر روی کلید اصلی جدول یا همان PK_Sales_Orders که ذیل عملگر مشخص شده، رخ داده‌است:


در این مثال با دو ورودی مختلف، دو کوئری پلن مختلف تولید شده‌است؛ که مرتبط است با میزان اطلاعاتی که قرار است بازگشت داده شود.

اگر این دو کوئری را با هم اجرا کنیم (در طی یک batch)، به پلن مقایسه‌ای زیر خواهیم رسید که در آن هزینه‌ی Index Scan بیشتر است از clustered index seek:


به همراه آمار CPU و I/O ای به صورت زیر که اولی مرتبط است با index scan و دومی با clustered index seek:
(43595 rows affected)
Table 'Orders'. Scan count 1, logical reads 191, physical reads 1, read-ahead reads 182, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
 SQL Server Execution Times:
CPU time = 31 ms,  elapsed time = 754 ms.


(13595 rows affected)
Table 'Orders'. Scan count 1, logical reads 131, physical reads 0, read-ahead reads 127, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
 SQL Server Execution Times:
CPU time = 16 ms,  elapsed time = 276 ms.
به همین جهت است که عنوان می‌شود، scanها خوب نیستند و seekها بهترند.
مطالب
چرا TypeScript؟
زبان TypeScript به عنوان superset زبان JavaScript ارائه شده‌است و هدف آن، strong typing و ارائه‌ی قابلیت‌های پیشرفته‌ی زبان‌های شیءگرا، جهت نوشتن برنامه‌های کلاینت و سرور، با کمترین میزان خطاها است. زبان TypeScript چندسکویی و سورس باز است و در نهایت به نگارشی از JavaScript کامپایل می‌شود که با تمام مرورگرهای فعلی سازگاری دارد و یا در سمت سرور بدون مشکلی توسط NodeJS قابل درک است.
- TypeScript زبان توصیه شده‌ی توسعه‌ی برنامه‌های AngularJS 2 است و همچنین با سایر کتابخانه‌های معروف جاوا اسکریپتی مانند ReactJS و jQuery نیز سازگاری دارد. بنابراین اگر قصد دارید به AngularJS 2 مهاجرت کنید، اکنون فرصت خوبی است تا زبان TypeScript را نیز بیاموزید. همچنین WinJS نیز با TypeScript نوشته شده‌است.
- superset زبان JavaScript بودن به این معنا است که تمام کدهای جاوا اسکریپتی موجود، به عنوان کد معتبر TypeScript نیز شناخته می‌شوند و همین مساله مهاجرت به آن‌را ساده‌تر می‌کند. زبان‌های دیگری مانند Dart و یا CoffeeScript ، نسبت به JavaScript بسیار متفاوت به نظر می‌رسند؛ اما Syntax زبان TypeScript شباهت بسیار زیادی به جاوا اسکریپت و خصوصا ES 6 دارد. در اینجا تنها کافی است پسوند فایل‌های js را به ts تغییر دهید و از آن‌ها به عنوان کدهای معتبر TypeScript استفاده کنید.
- strong typing و معرفی نوع‌ها، کدهای نهایی نوشته شده را امن‌تر می‌کنند. به این ترتیب کامپایلر، پیش از اینکه کدهای شما در زمان اجرا به خطا بر بخورند، در زمان کامپایل، مشکلات موجود را گوشزد می‌کند. همچنین وجود نوع‌ها، سرعت توسعه را با بهبود ابزارهای مرتبط با برنامه نویسی، افزایش می‌دهند؛ از این جهت که مفهوم مهمی مانند Intellisense، با وجود نوع‌ها، پیشنهادهای بهتر و دقیق‌تری را ارائه می‌دهد. همچنین ابزارهای Refactoring نیز در صورت وجود نوع‌ها بهتر و دقیق‌تر عمل می‌کنند. این موارد مهم‌ترین دلایل طراحی TypeScript جهت توسعه و نگهداری برنامه‌های بزرگ نوشته شده‌ی با JavaScript هستند.
- Syntax زبان TypeScript به شدت الهام گرفته شده از زبان سی‌شارپ است. به همین جهت اگر با این زبان آشنایی دارید، درک مفاهیم TypeScript برایتان بسیار ساده خواهد بود.
- بهترین قسمت TypeScript، کامپایل شدن آن به ES 5 است (به این عملیات Transpile هم می‌گویند). در زبان TypeScript به تمام امکانات پیشرفته‌ی ES 6 مانند کلاس‌ها و ماژول‌ها دسترسی دارید، اما کد نهایی را که تولید می‌کند، می‌تواند ES 5 ایی باشد که هم اکنون تمام مرورگرهای عمده آن‌را پشتیبانی می‌کنند. با تنظیمات کامپایلر TypeScript، امکان تولید کدهای ES 3 تا ES 5 و همچنین ES 6 نیز وجود دارد. نمونه‌ی آنلاین این ترجمه را در TypeScript playground می‌توانید مشاهده کنید.
- TypeScript چندسکویی است. امکانات و کامپایلر این زبان، برای ویندوز، مک و لینوکس طراحی شده‌اند.
- TypeScript سورس باز است. طراحان اصلی آن، همان طراحان زبان سی‌شارپ در مایکروسافت هستند و هم اکنون این زبان به صورت سورس باز توسط این شرکت توسعه داده شده و در GitHub نگهداری می‌شود.


آماده سازی محیط‌های کار با TypeScript

برای کار با TypeScript، یک ادیتور متنی ساده، به همراه کامپایلر آن کفایت می‌کند. اما همانطور که عنوان شد، یکی از مهم‌ترین دلایل وجودی TypeScript، بهبود ابزارهای برنامه نویسی مرتبط با JavaScript است و اگر قرار باشد صرفا از یک ادیتور متنی ساده استفاده شود، فلسفه‌ی وجودی آن زیر سؤال می‌رود.

نصب TypeScript در ویژوال استودیو

در نگارش‌های جدید ویژوال استودیو، از VS 2013 Update 2 به بعد، قسمت ویژه‌ی TypeScript نیز قابل مشاهده‌است. البته این قسمت با به روز رسانی‌های TypeScript، نیاز به به روز رسانی دارد. به همین جهت به سایت رسمی آن مراجعه کرده و بسته‌های جدید مخصوص VS 2013 و یا 2015 آن‌را دریافت و نصب کنید.


همچنین افزونه‌ی Web Essentials نیز امکانات بیشتری را جهت کار با TypeScript به همراه دارد و امکان مشاهده‌ی خروجی جاوا اسکریپت تولیدی را در حین کار با فایل TypeScript فعلی میسر می‌کند. در سمت چپ صفحه TypeScript را خواهید نوشت و در سمت راست، خروجی JavaScript نهایی را بلافاصله مشاهده می‌کنید.


تصویر فوق مربوط به VS 2015 است. همچنین گزینه‌ی افزودن یک فایل و آیتم جدید نیز امکان افزودن فایل‌های TS را به همراه دارد.


نصب و تنظیم TypeScript در ویژوال استودیو کد

ویژوال استودیو کد، نگارش رایگان، سورس باز و چندسکویی ویژوال استودیو است که بر روی ویندوز، مک و لینوکس قابل اجرا است. ویژوال استودیو کد نیز به همراه پشتیبانی بسیار خوبی از TypeScript است، تا حدی که تمام ارائه‌های معرفی Anugular 2 توسط تیم مربوطه‌ی آن از گوگل، توسط ویژوال استودیو کد و یکپارچگی آن با TypeScript انجام شدند.


ویژوال استودیو کد بر مبنای فولدرها کار می‌کند و با گشودن یک پوشه در آن (با کلیک بر روی دکمه‌ی open folder آن)، امکان کار کردن با آن پوشه و فایل‌های موجود در آن را خواهیم یافت.
نکته‌ی مهم اینجا است که پس از نصب VS Code، برای فایل‌های با پسوند ts بلافاصله Intellisense مرتبط نیز مهیا است و نیاز به هیچگونه تنظیم اضافه‌تری ندارد. همچنین قابلیت‌های type safety این زبان نیز در این ادیتور به نحو واضحی مشخص هستند:


در ادامه ابتدا یک پوشه‌ی جدید خالی را ایجاد کنید و سپس این پوشه را در VS Code باز نمائید (از طریق منوی فایل، گزینه‌ی گشودن پوشه). سپس ماوس را بر روی نام این پوشه حرکت دهید:


همانطور که مشاهده می‌کنید، دکمه‌ی new file ظاهر می‌شود. در اینجا می‌توانید فایل جدیدی را به نام test.ts اضافه کنید.
در ادامه با فشردن دکمه‌های ctrl+shift+p، امکان انتخاب یک task runner را جهت کامپایل فایل‌های ts خواهیم داشت:


در اینجا ابتدا عبارت task< را وارد کنید و سپس از منوی باز شده، گزینه‌ی rub build task را انتخاب کنید:


پس از آن، در بالای صفحه مشاهده خواهید کرد که عنوان شده: «هنوز هیچ task runner ایی برای اینکار تنظیم نشده‌است»


برای این منظور بر روی دکمه‌ی configure task runner تصویر فوق که با رنگ آبی مشخص شده‌است، کلیک کنید. به این ترتیب یک فایل جدید به نام task.json ایجاد می‌شود که در پوشه‌ای به نام vscode. در ریشه‌ی پروژه (یا همان پوشه‌ی جاری) قرار می‌گیرد:


فایل task.json دارای تعاریفی است که کامپایلر TypeScript یا همان tsc را فعال می‌کند:
{
"version": "0.1.0",

// The command is tsc. Assumes that tsc has been installed using npm install -g typescript
"command": "tsc",

// The command is a shell script
"isShellCommand": true,

// Show the output window only if unrecognized errors occur.
"showOutput": "silent",

// args is the HelloWorld program to compile.
"args": ["HelloWorld.ts"],

// use the standard tsc problem matcher to find compile problems
// in the output.
"problemMatcher": "$tsc"
}
محتوای پیش فرض و ابتدایی این فایل را در قطعه کد فوق مشاهده می‌کنید. این فایل json را جهت تنظیمات کامپایلر TypeScript پروژه‌ی جاری، ویرایش خواهیم کرد. در این فایل دکمه‌ی ctrl+space را بفشارید. بلافاصله منوی تکمیل کننده‌ی این فایل ظاهر می‌شود. از ترکیب ctrl+space در قسمت‌های مختلف این فایل جهت دریافت توصیه‌های بیشتری نیز می‌توان استفاده کرد.
در اینجا قسمتی که نیاز به تنظیم دارد، خاصیت args است. مقادیر آن، پارامترهایی هستند که به کامپایلر typescript ارسال می‌شوند. برای نمونه آن‌را به صورت ذیل تغییر دهید:
"args": [
         "--target", "ES5",
         "--outdir", "js",
         "--sourceMap",
         "--watch",
         "test.ts"
    ],
پارامتر و سوئیچ target مشخص می‌کند که خروجی تولیدی باید با فرمت ES 5 باشد. همچنین فایل‌های js تولیدی را در پوشه‌ی js در ریشه‌ی پروژه یا پوشه‌ی جاری قرار دهد. پارامتر sourceMap مشخص می‌کند که علاوه بر فایل‌های js، فایل‌های map که بیانگر نگاشت بین فایل‌های ts و js هستند نیز تولید شوند. این فایل‌ها برای دیباگ برنامه بسیار مفید هستند. پارامتر watch، کلیه‌ی تغییرات پوشه‌ی جاری را تحت نظر قرار داده و به صورت خودکار کار کامپایل را انجام می‌دهد. در آخر نیز فایل و یا فایل‌های ts مدنظر ذکر می‌شوند.
برای اجرای کامپایلر، ابتدا از منوی view گزینه‌ی toggle output را انتخاب کنید تا بتوان خروجی نهایی کامپایلر را مشاهده کرد. سپس گزینه‌ی view->command pallet و اجرا tasks< را انتخاب کنید. در ادامه همانند مرحله‌ی قبل، یعنی گزینه‌ی run build task را اجرا کنید (که خلاصه‌ی این عملیات ctrl+shift+B است).

به این ترتیب پوشه‌ی js که در خاصیت args مشخص کردیم، تولید می‌شود:


البته این خطا هم در قسمت output نمایش داده می‌شود:
 error TS5023: Unknown option 'watch'
Use the '--help' flag to see options.

علت اینجا است که در تنظیمات فوق، خاصیت command به tsc تنظیم شده‌است و همانطور که در کامنت آن عنوان شده‌است، کامپایلر typescript را از طریق دستور npm install -g typescript دریافت می‌کند و نیازی به ذکر مسیر آن در اینجا نیست. بنابراین لازم است تا با npm و نصب typescript از طریق آن آشنا شد و به این ترتیب کامپایلر آن‌را به روز کرد تا دستور watch را شناسایی کند.


نصب TypeScript از طریق npm

همانطور که عنوان شد، TypeScript چندسکویی است و این مورد را از طریق npm یا NodeJS package manager انجام می‌دهد. برای این منظور به آدرس https://nodejs.org/en   مراجعه کرده و فایل نصاب آن‌را مخصوص سیستم عامل خود دریافت و سپس نصب کنید. Node.js یک runtime سمت سرور اجرای برنامه‌های جاوا اسکریپتی است. از آنجائیکه TypeScript در نهایت به JavaScript تبدیل می‌شود، استفاده از node.js انتخاب مناسبی جهت اجرا و توزیع آن در تمام سیستم عامل‌ها بوده‌است.
پس از نصب node.js، از package manager آن که npm نام دارد، جهت نصب TypeScript استفاده می‌شود. چون node.js به Path و مسیرهای اصلی ویندوز اضافه می‌شود، تنها کافی است دستور npm install -g typescript را در خط فرمان صادر کنید. در اینجا سوئیچ g به معنای global و دسترسی عمومی است.


همانطور که در این تصویر مشخص است، پس از صدور دستور نصب TypeScript، نگارش 1.8.9 آن نصب شده‌است. اما زمانیکه کامپایلر tsc را با پارامتر version اجرا می‌کنیم، شماره نگارش قدیمی 1.0.3.0 را نمایش می‌دهد. برای رفع این مشکل به مسیر C:\Program Files (x86)\Microsoft SDKs\TypeScript مراجعه کرده و پوشه‌ی 1.0 را به 1.0-old تغییر نام دهید.


اکنون اگر مجددا بررسی کنیم، نگارش صحیح قابل مشاهده است:


پس از این تغییرات اگر مجددا به VS Code باز گردیم و ctrl+shift+B را صادر کنیم (جهت اجرای مجدد task runner و اجرای tsc تنظیم شده) ، پیام ذیل مشاهده می‌شود:
 15:33:52 - Compilation complete. Watching for file changes.
به این معنا که اینبار پارامتر watch را شناسایی کرده‌است و دیگر از کامپایلر قدیمی tsc استفاده نمی‌کند. برای آزمایش آن، از منوی view گزینه‌ی split editor را انتخاب کنید و سپس در سمت چپ فایل test.ts و در سمت راست، فایل test.js کامپایل شده را باز کنید:


در اینجا چون پارامتر watch فعال شده‌است، هر تغییری که در فایل ts داده شود، بلافاصله کامپایل شده و در فایل js منعکس خواهد شد.


تنظیم VS Code جهت دیباگ کدهای TypeScript

در نوار ابزار کنار صفحه‌ی VS Code، بر روی دکمه‌ی دیباگ کلیک کنید:


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


در اینجا node.js را انتخاب کنید. با اینکار فایل جدیدی دیگری به نام launch.json به پوشه‌ی vscode. اضافه می‌شود. اگر به این فایل دقت کنید دو خاصیت name به نام‌های Launch و Attach در آن موجود هستند. این نام‌ها در یک دراپ داون، در کنار دکمه‌ی start دیباگ نیز ظاهر می‌شوند:


- در فایل launch.json، باید خاصیت "program": "${workspaceRoot}/app.js" را ویرایش کرد و app.js آن‌را به test.ts مثال جاری تغییر داد.
- سپس خاصیت "sourceMaps" آن نیز باید تغییر کرده و جهت استفاده‌ی از source mapهای تولیدی به true تنظیم شود.
- در آخر باید مسیر پوشه‌ی خروجی js را نیز تنظیم کرد: "outDir": "${workspaceRoot}/js"
همچنین باید دقت داشت چون externalConsole به false تنظیم شده‌است، خروجی این کنسول به output ویژوال استودیوکد منتقل می‌شود.

اکنون اگر بر روی دکمه‌ی سبز رنگ start کلیک کنید (دکمه‌ی F5)، امکان دیباگ سطر به سطر کد TypeScript را خواهید یافت:



فایل‌های نهایی json یاد شده‌ی در متن را از اینجا می‌توانید دریافت کنید:
 VSCodeTypeScript.zip
مطالب
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
مطالب
آشنایی و استفاده از WCF Data Services در Visualstudio 2012
مقدمه:

WCF Data Services جزئی از NET Framework است که امکان ایجاد سرویس دهنده‌های با قرارداد OData را به روی وب یا Intranet با استفاده از REST مهیا می‌سازد. OData از داده هایی که با Url آدرس پذیر هستند استفاده می‌نماید. دسترسی و تغییر داده‌ها با استفاده از استاندارد HTTP و کلمات GET، PUT، POST و DELETE صورت می‌پذیرد. برای اینکه درک بهتری داشته باشید به یک مثال می‌پردازیم.

ایجاد یک برنامه سرویس دهنده WCF Data Service در 2012 VisualStudio
  1. یک ASP.NET Web Application با نام NorthwindService ایجاد نمایید و بر روی پروژه راست کلیک کنید و از منوی Add گزینه New Item را انتخاب نمایید  از پنجره باز شده از دسته Data گزینه ADO.NET Entity Data Model  را انتخاب و نام ان را Northwind بگذارید.
  2. از پنجره باز شده Generate from Databaseرا انتخاب و با انتخاب کانکشن از نوع Sql Server Compact 4  اتصال به فایل Northwind.sdf را انتخاب تا کلاس‌های لازم تولید شود.
  3. برای تولید data service بر روی پروژه راست کلیک کنید و از منوی Add گزینه New Item را انتخاب نمایید از پنجره باز شده گزینه  WCF Data Service را انتخاب و نام آن را Northwind.svc بگذارید. کد زیر خودکار تولید می‌شود
     public class Northwind : DataService< /* TODO: put your data source class name here */ >
        {
            // This method is called only once to initialize service-wide policies.
            public static void InitializeService(DataServiceConfiguration config)
            {
                // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
                // Examples:
                // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
                // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
                config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
            }
        }
  4. برای دسترسی به موجودیت‌های Northwind بجای عبارت put your data source نام مدل را تایپ کنید
    public class Northwind : DataService<NorthwindEntities>
  5. برای فعال کردن دسترسی به منابع data source متغیر config کلاس DataServiceConfiguration را بصورت زیر تنظیم نمایید. تابع SetEntitySetAccessRule با گرفتن نام موجودیت و نحوه دسترسی امکان استفاده از این موجودیت را با استفاده از WCF Data Service فزاهم می‌نمایید. مثلا در زیر امکان دسترسی به موجودیت Orders را با امکان خواندن همه، نوشتن ادقامی و جایگزین فراهم نموده است.
    config.SetEntitySetAccessRule("Orders", EntitySetRights.AllRead 
         | EntitySetRights.WriteMerge 
         | EntitySetRights.WriteReplace );
    config.SetEntitySetAccessRule("Customers", EntitySetRights.AllRead);
  6. اگر بخواهیم امکان خواندن همه موجودیت‌ها را فراهم کنیم از کد زیر می‌توانیم استفاده نمایید که * به معنای همه موجودیت‌های data model می‌باشد
    config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);

دسترسی به WCF Data Service بوسیله مرورگر وب
برای دسترسی به وب سرویس برنامه را اجرا نمایید تا آدرس http://localhost:8358/Northwind.svc مشخصات وب سرویس را نمایش دهد
<service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xml:base="http://localhost:8358/Northwind.svc/">
<workspace>
<atom:title>Default</atom:title>
<collection href="Categories">
<atom:title>Categories</atom:title>
</collection>
<collection href="Customers">
<atom:title>Customers</atom:title>
</collection>
<collection href="Employees">
<atom:title>Employees</atom:title>
</collection>
<collection href="Order_Details">
<atom:title>Order_Details</atom:title>
</collection>
<collection href="Orders">
<atom:title>Orders</atom:title>
</collection>
<collection href="Products">
<atom:title>Products</atom:title>
</collection>
<collection href="Shippers">
<atom:title>Shippers</atom:title>
</collection>
<collection href="Suppliers">
<atom:title>Suppliers</atom:title>
</collection>
</workspace>
</service>
 حال اگر آدرس را به http://localhost:8358/Northwind.svc/Products وارد نمایید لیست کالا‌ها بصورت Atom xml قابل دسترس می‌باشد.
ایجاد یک برنامه گیرنده WCF Data Service در Visual Studio 2012
  1. بر روی Solution پروژه جاری راست کلیک و از منوی Add گزینه New Project را انتخاب و یک پروژه از نوع WPF Application با نام NorthwindClient ایجاد نمایید.
  2. در پنجره MainWindow مانند کد زیر از یک Combobox و DataGrid برای نمایش اطلاعات استفاده نمایید
        <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Northwind Orders" Height="335" Width="425" 
            Name="OrdersWindow" Loaded="Window1_Loaded">
        <Grid Name="orderItemsGrid">
            <ComboBox DisplayMemberPath="Order_ID" ItemsSource="{Binding}"
                      IsSynchronizedWithCurrentItem="true" 
                      Height="23" Margin="92,12,198,0" Name="comboBoxOrder" VerticalAlignment="Top"/>
            <DataGrid ItemsSource="{Binding Path=Order_Details}"  
                      CanUserAddRows="False" CanUserDeleteRows="False"  
                      Name="orderItemsDataGrid" Margin="34,46,34,50"
                      AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn  Header="Product" Binding="{Binding Product_ID, Mode=OneWay}" />
                    <DataGridTextColumn  Header="Quantity" Binding="{Binding Quantity, Mode=TwoWay}" />
                    <DataGridTextColumn  Header="Price" Binding="{Binding UnitPrice, Mode=TwoWay}" />
                    <DataGridTextColumn  Header="Discount" Binding="{Binding Discount, Mode=TwoWay}" />                
                </DataGrid.Columns>     
            </DataGrid>
            <Label Height="28" Margin="34,12,0,0" Name="orderLabel" VerticalAlignment="Top" 
                   HorizontalAlignment="Left" Width="65">Order:</Label>
            <StackPanel Name="Buttons" Orientation="Horizontal" HorizontalAlignment="Right" 
                        Height="40" Margin="0,257,22,0">
                <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" 
                    Name="buttonSave" VerticalAlignment="Bottom" Width="75" 
                        Click="buttonSaveChanges_Click">Save Changes
                </Button>
                <Button Height="23" Margin="0,0,12,12" 
                    Name="buttonClose" VerticalAlignment="Bottom" Width="75" 
                        Click="buttonClose_Click">Close</Button>
            </StackPanel>
        </Grid>
    </Window>

  3. برای ارجاع به wcf data service بر روی پروژه راست کلیک و گزینه Add Service Reference را انتخاب نمایید در پنجره باز شده گزینه Discover را انتخاب تا سرویس را یافته و نام Namespase را Northwind بگذارید.
  4. حال مانند کد زیر یک شی از مدل NorthwindEntities با آدرس وب سرویس ایجاد نموده ایم و نتیحه کوئری با استفاده از کلاس DataServiceCollection به DataContext گرید انتصاب داده ایم که البته پیش فرض آن آشنایی با DataBinding در WPF است.
            private NorthwindEntities context;
            private string customerId = "ALFKI";
            private Uri svcUri = new Uri("http://localhost:8358/Northwind.svc");
    
            private void Window1_Loaded(object sender, RoutedEventArgs e)
            {
                try
                {
                    context = new NorthwindEntities(svcUri);
                    var ordersQuery = from o in context.Orders.Expand("Order_Details")
                                      where o.Customers.Customer_ID == customerId
                                      select o;
                    DataServiceCollection<Orders> customerOrders = new DataServiceCollection<Orders>(ordersQuery);
                    this.orderItemsGrid.DataContext = customerOrders;
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }
            }
    
  5. با صدا زدن تابع SaveChanges مدل می‌توانید تغییرات را در پایگاه داده ذخیره نمایید.
    private void buttonSaveChanges_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            context.SaveChanges();
        }
        catch (DataServiceRequestException ex)
        {
            MessageBox.Show(ex.ToString());
    
        }
    }

  1. برنامه را اجرا نمایید تا خروجی کار را مشاهده نمایید. مقادیر Quantity را تغییر دهید و دکمه Save Changes را انتخاب تا تغییرات دخیره شود.
در اینجا در یک برنامه ویندوزی استفاده از WCF Data Service را تست نمودیم اما براحتی به همین شیوه در یک برنامه وب نیز قابل استفاده است.  
مطالب
نوشتن افزونه برای مرورگرها: قسمت اول : کروم
افزونه چیست؟
افزونه‌ها جزء مهمترین قسمت‌های یک مرورگر توسعه پذیر به شمار می‌آیند. افزونه‌ها سعی دارند تا قابلیت هایی را به مرورگر شما اضافه کنند. افزونه‌ها از آخرین فناوری‌های html,CSS و جاوااسکریپت تا به آنجایی که مرورگر آن‌ها را پشتیبانی کند، استفاده می‌کنند.
در این سری سعی خواهیم کرد برای هر مرورگر شناخته شده، یک افزونه ایجاد کنیم و ابتدا از آنجا که خودم از کروم استفاده می‌کنم، اولین افزونه را برای کروم خواهم نوشت.

این افزونه قرار است چه کاری انجام دهد؟
کاری که برای این افزونه تدارک دیده‌ام این است: موقعی‌که سایت dotnettips.info به روز شد مرا آگاه کند. این آگاه سازی را از طریق یک نوتیفیکیشن به اطلاع کاربر میرسانیم. صفحه تنظیمات این افزونه شامل گزینه‌های "آخرین مطالب"،"نظرات آخرین مطالب"،"آخرین اشتراک ها"و"آخرین نظرات اشتراک ها" خواهد بود که به طور پیش فرض تنها گزینه اول فعال خواهد بود و همچنین یک گزینه نیز برای وارد کردن یک عدد صحیح جهت اینکه به افزونه بگوییم هر چند دقیقه یکبار سایت را چک کن. چک کردن سایت هم از طریق فید RSS صورت می‌گیرد.

فایل manifest.json
این فایل برای ذخیره سازی اطلاعاتی در مورد افزونه به کار می‌رود که شامل نام افزونه، توضیح کوتاه در مورد افزونه و ورژن و ... به کار می‌رود که همه این اطلاعات در قالب یا فرمت json نوشته می‌شوند و در بالاترین حد استفاده برای تعریف اهداف افزونه و اعطای مجوز به افزونه از آن استفاده می‌کنیم. این فایل بخش‌های زیر را در یک افزونه تعریف می‌کند که به مرور با آن آشنا می‌شویم.


کد زیر را در فایل manifest.json می‌نویسیم:
{
  "manifest_version": 2,

  "name": "Dotnettips Updater",
  "description": "This extension keeps you updated on current activities on dotnettips.info",
  "version": "1.0",
  "icons": { "16": "icon.png",
           "48": "icon.png",
          "128": "icon.png" },

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "activeTab",
    "https://www.dntips.ir"
  ]
}
اطلاعات اولیه شامل نام و توضیح و ورژن افزونه است. ورژن برنامه برای به روزآوری افزونه بسیار مهم است. موقعی که ورژن جدیدی از افزونه ارائه شود، گوگل وب استور اعلان آپدیت جدیدی را برای افزونه میکند. آیکن قسمت‌های مختلف افزونه هم با icons مشخص می‌شود که در سه اندازه باید ارائه شوند و البته اگه اندازه آن نباشد scale می‌شود. قسمت بعدی تعریف UI برنامه هست که گوگل کروم، به آن Browser Action می‌گوید. در اینجا یک آیکن و همچنین یک صفحه اختصاصی برای تنظیمات افزونه معرفی می‌کنیم. این آیکن کنار نوار آدرس نمایش داده می‌شود و صفحه popup موقعی نشان داده می‌شود که کاربر روی آن کلیک می‌کند. آیکن‌ها برای browser action در دو اندازه 19 و 38 پیکسلی هستند و در صورتی که تنها یک آیکن تعریف شود، به صورت خودکار عمل scale و تغییر اندازه صورت می‌گیرد. برای تعیین عکس برای هر اندازه می‌توانید کد را به صورت زیر بنویسید:
"default_icon": {                    // optional
            "19": "images/icon19.png",           // optional
            "38": "images/icon38.png"            // optional
          }
قسمت popup برای نمایش تنظیمات به کار می‌رود و درست کردن این صفحه همانند صفحه همیشگی html هست و خروجی آن روی پنجره popup افزونه رندر خواهد شد.
گزینه default_title نیز یکی از دیگر خصیصه‌های مهم و پرکاربرد این قسمت هست که متن tooltip می‌باشد و موقعی که که کاربر، اشاره‌گر را روی آیکن ببرد نمایش داده می‌شود و در صورتی که نوشته نشود، کروم نام افزونه را نمایش می‌دهد؛ برای همین ما هم چیزی ننوشتیم.

صفحات پس‌زمینه
اگر بخواهید برای صفحه popup کد جاوااسکریت بنویسید یا از jquery استفاده کنید، مانند هر صفحه‌ی وبی که درست می‌کنید آن را کنار فایل popup قرار داده و در popup آنها را صدا کرده و از آن‌ها استفاده کنید. ولی برای پردازش هایی که نیاز به UI وجود ندارد، می‌توان از صفحات پس زمینه استفاده کرد. در این حالت ما دو نوع صفحه داریم:
  1. صفحات مصر یا Persistent Page
  2. صفحات رویدادگرا یا Events Pages
اولین نوع صفحه، همواره فعال و در حال اجراست و دومی موقعی فعال می‌شود که به استفاده از آن نیاز است. گوگل توصیه می‌کند که  تا جای ممکن از نوع دوم استفاده شود تا  مقدار حافظه مصرفی  حفظ شود  و کارآیی مروگر بهبود بخشیده شود. کد زیر یک صفحه پس زمینه را از نوع رویدادگرا می‌سازد. به وضوح روشن است در صورتی که خاصیت Persistent با true مقداردهی شود، این صفحه مصرانه در تمام وقت باز بودن مرورگر، فعال خواهد بود:
"background": {
    "scripts": ["background.js"],
    "persistent": false
}

Content Script یا اسکریپت محتوا
در صورتی که بخواهید با هر صفحه‌ای که باز یا رفرش می‌شود، به DOM آن دسترسی پیدا کنید، از این خصوصیت استفاده کنید. در کد زیر برای پردازش اطلاعات DOM از فایل جاوااسکریپت بهره برده و در قسمت matches می‌گویید که چه صفحاتی باید از این کد استفاده کنند که در اینجا از پروتکل‌های HTTP استفاده میشود و اگر مثلا نوع FTP یا file صدا زده شود کد مورد نظر اجرا نخواهد شد. در مورد اینکه matches چگونه کار می‌کند و چگونه می‌توان آن را نوشت، از این صفحه استفاده کنید.
"content_scripts": [
    {
        "matches": ["http://*/*", "https://*/*"],
        "js": ["content.js"]
    }
]

آغاز کدنویسی (رابط‌های کاربری)


اجازه دهید بقیه موارد را در حین کدنویسی تجربه کنیم و هر آنچه ماند را بعدا توضیح خواهیم داد. در اینجا من از یک صفحه با کد HTML زیر بهره برده ام که یک فرم دارد به همراه چهار چک باکس و در نهایت یک دکمه جهت ذخیره مقادیر. نام صفحه را popup.htm گذاشته ام و یک فایل popup.js هم دارم که در آن کد jquery نوشتم. قصد من این است که بتوان یک action browser به شکل زیر درست کنم:


کد html آن به شرح زیر است:
<html>
<head>
<meta charset="utf-8"/>

<script src="jquery.min.js"></script> <!-- Including jQuery -->
<script type="text/javascript" src="popup.js"></script>
</head>
<body style="direction:rtl;width:250px;">
<form >
<input type="checkbox" id="chkarticles" value="" checked="true">آخرین مطالب سایت</input><br/>
<input type="checkbox" id="chkarticlescomments" value="" >آخرین نظرات مطالب سایت</input><br/>
<input type="checkbox" id="chkshares" value="" >آخرین اشتراک‌های سایت</input><br/>
<input type="checkbox"  id="chksharescomments" value="" >آخرین نظرات اشتراک‌های سایت</input><br/>
<input id="btnsave" type="button" value="ذخیره تغییرات" />
    <div id="messageboard" style="color:green;"></div>
</form>

</body>
</html>
کد popup.js هم به شرح زیر است:
$(document).ready(function () {
    $("#btnsave").click(function() {
        var articles = $("#chkarticles").is(':checked');
        var articlesComments = $("#chkarticlescomments").is(':checked');
        var shares = $("#chkshares").is(':checked');
        var sharesComments = $("#chksharescomments").is(':checked');

        chrome.storage.local.set({ 'articles': articles, 'articlesComments': articlesComments, 'shares': shares, 'sharesComments': sharesComments }, function() {
            $("#messageboard").text( 'تنظیمات جدید اعمال شد');
        });
    });
});
در کد بالا موقعی که کاربر بر روی دکمه ذخیره، کلیک کند رویداد کلیک jquery فعال شده و مقادیر چک باکس‌ها را در متغیرهای مربوطه نگهداری می‌کند. نهایتا با استفاده از کلمه کلیدی کروم به ناحیه ذخیره سازی داده‌های کروم دست پیدا کرده و درخواست ذخیره مقادیر چک باکس را بر اساس ساختار نام و مقدار، ذخیره می‌کنیم و بعد از اعمال، توسط یک تابع callback به کاربر اعلام می‌کنیم که اطلاعات ذخیره شده است.
اولین مورد جدیدی که در بالا دیدیم، کلمه‌ی کلیدی chrome است. کروم برای توسعه دهندگانی که قصد نوشتن افزونه دارند api هایی را تدارک دیده است که میتوانید با استفاده از آنها به قسمت‌های مختلف مرورگر مثل بوک مارک یا تاریخچه فعالیت‌های مرورگر و ... دست پیدا کنید. البته برای اینکار باید در فایل manifest.json هم مجوز اینکار را درخواست نماییم. این ویژگی باید برای برنامه نویسان اندروید آشنا باشد. برای آشنایی هر چه بیشتر با مجوزها این صفحه را ببینید.
برای دریافت مجوز، کد زیر را به manifest اضافه می‌کنیم:
"permissions": [
    "storage"
  ]
مجوزی که در بالا درخواست کرده‌ایم مجوز دسترسی به ناحیه ذخیره سازی است. بعد از کلمه کلیدی chrome، کلمه‌ی local آمده است و می‌گوید که باید داده‌ها به صورت محلی و لوکال ذخیره شوند ولی اگر میخواهید داده‌ها در گوگل سینک شوند، باید به جای لوکال از کلمه کلیدی sync استفاده کنید یعنی:
chrome.storage.sync.set
فایل manifest نهایی:
{
  "manifest_version": 2,

  "name": "Dotnettips Updater",
  "description": "This extension keeps you updated on current activities on dotnettips.info",
  "version": "1.0",

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "storage"
  ]
}
الان باید 4 فایل داشته باشید: فایل آیکن، popup.htm,popup.js و manifest.json. همه را داخل یک دایرکتوری قرار داده و در مروگر کروم به قسمت extensions بروید و گزینه Developer mode را فعال کنید تا یک تستی از کد نوشته شده بگیریم. گزینه Load Unpacked Extension را بزنید و آدرس دایرکتوری ایجاد شده را به آن بدهید.
chrome://extensions

الان باید مانند تصویر بالا یک آیکن کنار نوار آدرس یا به قول گوگل، Omni box ببینید. گزینه‌ها را تیک بزنید و روی دکمه ذخیره کلیک کنید. باید پیام مقادیر ذخیره شدند، نمایش پیدا کند. الان یک مشکل وجود دارد؛ داده‌ها ذخیره می‌شوند ولی موقعی که دوباره تنظیمات افزونه را باز کنید حالت اولیه نمایش داده میشود. پس باید تنظیمات ذخیره شده را خوانده و به آن‌ها اعمال کنیم. کد زیر را جهت دریافت مقادیر ذخیره شده می‌نویسیم. اینبار به جای استفاده از متد set از متد get استفاده می‌کنیم. به صورت آرایه، رشته نام مقادیر را درخواست می‌کنیم و در تابع callback، مقادیر به صورت آرایه برای ما برگشت داده می‌شوند.
    chrome.storage.local.get(['articles', 'articlesComments', 'shares', 'sharesComments'], function ( items) {
        console.log(items[0]);
        $("#chkarticles").attr("checked", items["articles"]);
        $("#chkarticlescomments").attr("checked", items["articlesComments"]);
        $("#chkshares").attr("checked", items["shares"]);
        $("#chksharescomments").attr("checked", items["sharesComments"]);

    });
حالا برای اینکه افزونه‌ی شما متوجه تغییرات شود، به تب extensions رفته و در لیست افزونه‌ها به دنبال افزونه خود بگردید و گزینه Reload را انتخاب نمایید تا افزونه تغییرات را متوجه شود و صفحه را تست کنید.

Page Action
روش دیگر برای ارائه یک رابط کاربری، page action هست. این روش دقیقا مانند روش قبلی است، ولی جای آیکن عوض می‌شود. قبلا بیرون از نوار آدرس بود، ولی الان داخل نوار آدرس قرار می‌گیرد. جالب‌ترین نکته در این مورد این است که این آیکن در ابتدا مخفی شده است و شما تصمیم می‌گیرید که این آیکن چه موقع نمایش داده شود. مثلا آیکن RSS تنها موقعی نمایش داده می‌شود که وب سایتی که باز شده است، دارای محتوای RSS باشد یا بوک مارک کردن یک آدرس برای همه‌ی سایت‌ها باز باشد و سایر موارد.
کد زیر نحوه‌ی تعریف یک page action را در manifest نشان می‌دهد. ما در این مثال یک page action را به طور موقت اضافه می‌کنیم و موقعی هم آن را نشان میدهیم که سایت dotnettips.info باز باشد. دلیل اینکه موقت اضافه می‌کنیم این است که باید یکی از دو گزینه رابط کاربری که تا به حال گفتیم، استفاده شود. در غیر این صورت کروم در هنگام خواندن فایل manifest در هنگام افزودن افزونه به مرورگر، پیام خطا خواهد داد و این مطلب را به شما گوشزد می‌کند. پس نمی‌توان دو گزینه را همزمان داشت و من میخواهم افزونه را در حالت browser action ارائه کنم. پس در پروژه نهایی، این مطلب page action نخواهد بود. برای داشتن یک page action کد زیر را در manifest بنویسید.
  "page_action": {
    "default_icon": {
        "19": "images/icon19.png",
        "38": "images/icon38.png"
    },
    "default_popup": "popup.html"
گزینه page action تعریف شد حالا باید کاری کنیم تا هر موقع صفحه‌ای باز می‌شود چک کند آیا سایت مورد نظر است یا خیر، اینکار را توسط صفحه‌ی پردازشی انجام می‌دهیم. پس تکه کد زیر را هم به manifest اضافه می‌کنیم:
"background": {
    "scripts": ["page_action_validator.js"]
}

تا اینجا فایل جاوااسکریپت معرفی شد که کد زیر را دارد و در پس زمینه شروع به اجرا می‌کند.
function UrlValidation(tabId, changeInfo, tab) {
if (tab.url.indexOf('dotnettips.info') >-1) {
chrome.pageAction.show(tabId);
}
};
chrome.tabs.onUpdated.addListener(UrlValidation);
چون از api در این کد بهره برده‌ایم و آن هم مدیریت بر روی تب هاست، پس باید مجوز آن هم گرفته شود. کلمه "tabs" را در قسمت permissions اضافه کنید.
یک listener برای tabها ایجاد کرده‌ایم که اگر تب جدید ایجاد شد، یا تب قبلی به آدرس جدیدی تغییر پیدا کرد تابع UrlValidation را اجرا کند و در این تابع چک می‌کنیم که اگر url این تب شامل نام وب سایت می‌شود، page action روی این تب ظاهر شود. پس از انجام تغییرات، مجددا افزونه را بارگذاری می‌کنیم و تغییرات اعمال شده را می‌بینیم. سایت dotnettips را باز کنید یا صفحه را مجددا رفرش کنید تا تغییر اعمال شده را ببینید.

تغییرات موقت را حذف و کدها را به حالت قبلی یعنی browser action بر میگردانم.

OmniBox
omnibox یک کلمه کلیدی است که در نوار آدرس مرورگر وارد می‌شود و در واقع می‌توانیم آن را نوع دیگری از رابط کاربری بنامیم. موقعی که شما کلمه کلیدی رزرو شده را وارد می‌کنید، در نوار آدرس کلماتی نشان داده میشود که کاربر میتواند یکی از آن‌ها را انتخاب کند تا عملی انجام شود. ما هم قرار است این کار را انجام دهیم. به این مثال دقت کنید:
میخواهیم موقعی که کاربرکلمه net. را تایپ می‌کند، 5 عبارت آخرین مطالب و آخرین اشتراک‌ها و آخرین نظرات مطالب و آخرین نظرات اشتراک‌ها و صفحه اصلی سایت نمایش داده شود و با انتخاب هر کدام، کاربر به سمت آن صفحه هدایت شود.
برای افزودن کلمه کلیدی در manifest خطوط زیر را اضافه کنید:
"omnibox": { "keyword" : ".net" }
با نوشتن خط بالا کلمه net. در مرورگر یک کلمه‌ی کلیدی به حساب خواهد آمد و موقعی که کاربر این کلمه را وارد کند، در سمت راست نوشته خواهد شد. در این حالت باید کلید تب را بزند تا به محیط دستوری آن برود.

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

 
 برای اینکار باید کدنویسی کنیم ، پس یک فایل پس زمینه را به manifest معرفی کنید:
"background": {
    "scripts": ["omnibox.js"]
در فایل ominbox.js دستوراتی که مرتبط با omnibox است را می‌نویسیم و کد زیر را به آن اضافه می‌کنیم:
chrome.omnibox.onInputChanged.addListener(function(text, suggest) {
    suggest([
  {content: ".net tips Home Page", description: "صفحه اصلی"},
      {content: ".net tips Posts", description: "آخرین مطالب"},
      {content: ".net tips News", description: "آخرین نظرات مطالب"},
      {content: ".net tips Post Comments", description: "آخرین اشتراک ها"},
      {content: ".net tips News Comments", description: "آخرین نظرات اشتراک ها"}
    ]);
});
chrome.omnibox شامل 4 رویداد می‌شود:
 onInputStarted   بعد از اینکه کاربر کلمه کلیدی را وارد کرد اجرا می‌شود
onInputChanged 
  بعد از وارد کردن کلمه کلیدی هربار که کاربر تغییری در ورودی نوارد آدرس می‌دهد اجرا می‌شود.
 onInputEntered   کاربر ورودی خود را تایید می‌کند. مثلا بعد از وارد کردن، کلید enter را می‌فشارد
 onInputCancelled  کاربر از وارد کردن ورودی منصرف شده است؛ مثلا کلید ESC را فشرده است. 
با نوشتن chrome.omnibox.onInputChanged.addListener ما یک listener ساخته‌ایم تا هر بار کاربر ورودی را تغییر داد، یک تابع callback که دو آرگومان را دارد، صدا بزند. این آرگومان‌ها یکی متن ورودی‌است و دیگری آرایه‌ی suggest که شما با تغییر آرایه می‌توانید عباراتی که همزمان با تایپ به کاربر پیشنهاد می‌شود را نشان دهید. البته می‌توانید با تغییر کد کاری کنید تا بر اساس حروفی که تا به حال تایپ کرده‌اید، دستورات را نشان دهد؛ ولی من به دلیل اینکه 5 دستور بیشتر نبود و کاربر راحت باشد، چنین کاری نکردم. همچنین وقتی شما برای هر یک description تعریف کنید، به جای نام پیشنهادی، توضیح آن را نمایش می‌دهد.
حالا وقت این است که کد زیر را جهت اینکه اگر کاربر یکی از کلمات پیشنهادی را انتخاب کرد، به صفحه‌ی مورد نظر هدایت شود، اضافه کنیم:
chrome.omnibox.onInputEntered.addListener(function (text) {

var location="";
    switch(text)
{
case ".net tips Posts":
location="https://www.dntips.ir/postsarchive";
break;
case ".net tips News":
location="https://www.dntips.ir/newsarchive";
break;
case ".net tips Post Comments":
location="https://www.dntips.ir/commentsarchive";
break;
case".net tips News Comments":
location="https://www.dntips.ir/newsarchive/comments";
break;
default:
location="https://www.dntips.ir/";
}

    chrome.tabs.getSelected(null, function (tab) {
        chrome.tabs.update(tab.id, { url: location });
    });
});
ابتدا یک listener برای روی رویداد onInputEntered قرار داده تا وقتی کاربر عبارت وارد شده را تایید کرد، اجرا شود. در مرحله بعد چک می‌کنیم که عبارت وارد شده چیست و به ازای هر عبارت مشخص شده، آدرس آن صفحه را در متغیر location قرار می‌دهیم. در نهایت با استفاده از عبارت chrome.tabs.getSelected تب انتخابی را به یک تابع callback بر میگردانیم. اولین آرگومان windowId است، برای زمانی که چند پنجره کروم باز است که می‌توانید وارد نکنید تا پنجره فعلی و تب فعلی محسوب شود. برای همین نال رد کردیم. در تابع برگشتی، شیء tab شامل اطلاعات کاملی از آن تب مانند url و id و title می‌باشد و در نهایت با استفاده از دستور chrome.tabs.update اطلاعات تب را به روز می‌کنیم. آرگومان اول id تب را میدهیم تا بداند کدام تب باید تغییر کند و آرگومان بعدی می‌توانید هر یک از ویژگی‌های تب از قبیل آدرس فعلی یا عنوان آن و ... را تغییر دهید که ما آدرس آن را تغییر داده ایم.

Context Menu
یکی دیگر از رابط‌های کاربری، منوی کانتکست هست که توسط chrome.contextmenus ارائه می‌شود و به مجوز "contextmenus" نیاز دارد. فعال سازی منوی کانتکست در قسمت‌های زیر ممکن است:
all, page, frame, selection, link, editable, image, video, audio 
من گزینه‌ی dotenettips.info را برای باز کردن سایت، به Contextmenus اضافه می‌کنم. کد را در فایلی به اسم contextmenus.js ایجاد می‌کنم و در قسمت background آن را معرفی می‌کنم. برای باز کردن یک تب جدید برای سایت، نیاز به chrome.tabs داریم که البته  نیاز به مجوز tabs هم داریم.
محتوای فایل contextmenus.js
var root = chrome.contextMenus.create({
    title: 'Open .net tips',
    contexts: ['page']
}, function () {
    var Home= chrome.contextMenus.create({
        title: 'Home',
        contexts: ['page'],
        parentId: root,
        onclick: function (evt) {
            chrome.tabs.create({ url: 'https://www.dntips.ir' })
        }
    });
var Posts = chrome.contextMenus.create({
        title: 'Posts',
        contexts: ['page'],
        parentId: root,
        onclick: function (evt) {
            chrome.tabs.create({ url: 'https://www.dntips.ir/postsarchive/' })
        }
    });
});
در کد بالا یک گزینه به context menu اضافه میشود و دو زیر منو هم دارد که یکی صفحه‌ی اصلی سایت را باز میکند و دیگری هم صفحه‌ی مطالب سایت را باز می‌کند.

تا به اینجا ما قسمت ظاهری کار را آماده کرده ایم و به دلیل اینکه مطلب طولانی نشود، این مطلب را در دو قسمت ارائه خواهیم کرد. در قسمت بعدی نحوه خواندن RSS و اطلاع رسانی و دیگر موارد را بررسی خواهیم کرد.