مطالب
Blazor 5x - قسمت 21 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 1 - افزودن قالب ابتدایی Identity
در ادامه‌ی مثال این سری، می‌خواهیم امکان ثبت و ویرایش اتاق‌ها را (و یا امکانات رفاهی یک هتل را که به صورت تمرینی دقیقا مشابه افزودن مشخصات اتاق‌ها، اضافه شده و کدهای آن از فایل پیوستی انتهای بحث قابل دریافت است) به کاربران اعتبارسنجی شده‌ی دارای نقش مدیریتی، محدودیت کنیم و نمی‌خواهیم عموم کاربران برنامه بتوانند در این قسمت‌ها، تغییری را ایجاد کنند. برای این منظور از امکانات توکار و استاندارد ASP.NET Core Identity استفاده خواهیم کرد و این کتابخانه را از صفر و بدون تغییری، به پروژه‌ی جاری از نوع Blazor Server، به همان نحوی که طراحی شده، اضافه می‌کنیم و در مراحل بعدی، بر اساس نیازهای برنامه، قسمت‌های مختلف آن‌را سفارشی سازی خواهیم کرد.


تغییر نوع DbContext برنامه

پیش از شروع به یکپارچه کردن ASP.NET Core Identity با برنامه‌ی جاری، نیاز است نوع DbContext آن‌را به صورت زیر تغییر داد:
using BlazorServer.Entities;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace BlazorServer.DataAccess
{
    public class ApplicationDbContext : IdentityDbContext
    {
      // ...
- بنابراین به فایل BlazorServer\BlazorServer.DataAccess\ApplicationDbContext.cs مراجعه کرده و برای شروع، بجای DbContext، از IdentityDbContext استفاده می‌کنیم.
- این تغییر، نیاز به نصب بسته‌ی نیوگت Microsoft.AspNetCore.Identity.EntityFrameworkCore را نیز در پروژه‌ی جاری دارد تا IdentityDbContext آن شناسایی شده و قابل استفاده شود.


نصب ابزار تولید کدهای ASP.NET Core Identity

اگر از ویژوال استودیوی کامل استفاده می‌کنید، گزینه‌ی افزودن کدهای ASP.NET Core Identity به صورت زیر قابل دسترسی است:
project -> right-click > Add > New Scaffolded Item -> select Identity > Add
اما از آنجائیکه قصد داریم این مطلب، برای کاربران VSCode و همچنین سایر سیستم عامل‌ها نیز قابل استفاده باشد، از NET Core CLI. استفاده خواهیم کرد. برای این منظور، ابتدا ابزار سراسری dotnet-aspnet-codegenerator را نصب می‌کنیم:
dotnet tool install -g dotnet-aspnet-codegenerator
سپس به پروژه‌ی اصلی Blazor Server مراجعه کرده (BlazorServer.App.csproj در این مثال) و در پوشه‌ی آن، دستورات زیر را اجرا می‌کنیم تا بسته‌های نیوگت مورد نیاز ASP.NET Core Identity و UI آن، نصب شوند:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
پس از نصب این وابستگی‌ها، اکنون در همین ریشه‌ی پروژه‌ی اصلی، دستور زیر را اجرا می‌کنیم تا فایل‌های ASP.NET Core Identity اضافه شوند:
dotnet aspnet-codegenerator identity --dbContext BlazorServer.DataAccess.ApplicationDbContext --force
در اینجا ذکر فضای نام کامل کلاس ApplicationDbContext ضروری است.
حال اگر به پروژه دقت کنیم، پوشه‌ی جدید Areas که به همراه فایل‌های مدیریتی ASP.NET Core Identity است، اضافه شده و حاوی کدهای صفحات لاگین، ثبت نام کاربر و غیره است.


اعمال تغییرات ابتدایی مورد نیاز جهت استفاده از ASP.NET Core Identity

تا اینجا کدهای پیش‌فرض مدیریتی ASP.NET Core Identity را به پروژه اضافه کردیم. در ادامه نیاز است تغییرات ذیل را به پروژه‌ی اصلی Blazor Server اعمال کنیم تا بتوان از این فایل‌ها استفاده کرد:
- به فایل BlazorServer.App\Startup.cs مراجعه کرده و UseAuthentication و UseAuthorization را دقیقا در محلی که مشاهده می‌کنید، اضافه می‌کنیم. همچنین در اینجا نیاز است مسیریابی‌های razor pages را نیز فعال کرد.
namespace BlazorServer.App
{
    public class Startup
    {
        // ...
 
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            // ...

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                // ...
            });
        }
    }
}
- سپس به پوشه‌ی BlazorServer.DataAccess برنامه وارد شده و دستورات Migrations را یکبار دیگر اجرا می‌کنیم، تا جداول پیش‌فرض Identity، بر اساس Context جدید آن ایجاد شوند:
dotnet tool update --global dotnet-ef --version 5.0.4
dotnet build
dotnet ef migrations --startup-project ../BlazorServer.App/ add AddIdentity --context ApplicationDbContext
dotnet ef --startup-project ../BlazorServer.App/ database update --context ApplicationDbContext
پس از اجرای این دستورات، جداول جدید زیر به بانک اطلاعاتی برنامه اضافه می‌شوند:



افزودن گزینه‌ی منوی لاگین به برنامه‌ی Blazor Server


پس از این تغییرات، به برنامه‌ای رسیده‌ایم که مدیریت قسمت Identity آن، توسط قالب استاندارد مایکروسافت که در پوشه‌ی Areas\Identity\Pages\Account نصب شده و بر اساس فناوری ASP.NET Core Razor Pages کار می‌کند، انجام می‌شود.
اکنون می‌خواهیم در منوی برنامه‌ی Blazor Server خود که با صفحات Identity یکی شده‌است، لینکی را به صفحه‌ی لاگین این Area اضافه کنیم. اگر به فایل Shared\MainLayout.razor آن مراجعه کنیم، به صورت پیش‌فرض، لینکی به صفحه‌ی About، قرار دارد. به همین جهت این مورد را به صورت زیر اصلاح می‌کنیم:
ابتدا کامپوننت جدید BlazorServer.App\Shared\LoginDisplay.razor را با محتوای زیر ایجاد می‌کنیم:
<a href="Identity/Account/Register">Register</a>
<a href="Identity/Account/Login">Login</a>

@code {

}
که لینک‌هایی را به صفحات لاگین و ثبت نام یک کاربر جدید، تعریف می‌کند.
سپس از این کامپوننت در فایل BlazorServer.App\Shared\MainLayout.razor استفاده می‌کنیم:
<div class="top-row px-4">
    <LoginDisplay></LoginDisplay>
    <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>


ثبت و فعالسازی سرویس‌های کار با ASP.NET Core Identity

البته اگر در این حال برنامه را اجرا کنیم، با کلیک بر روی لینک‌های فوق، استثنائی را مانند یافت نشدن سرویس UserManager، مشاهده خواهیم کرد. برای رفع این مشکل، به فایل BlazorServer.App\Startup.cs مراجعه کرده و سرویس‌های Identity را ثبت می‌کنیم:
namespace BlazorServer.App
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
           // ...

            services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders()
                .AddDefaultUI();

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


همانطور که مشاهده می‌کنید، قالب این قسمت Identity، با قالب قسمت Blazor Server یکی نیست؛ چون توسط Razor Pages و Area آن تامین می‌شود که master page خاص خودش را دارد. زمانیکه قالب Identity را اضافه می‌کنیم، علاوه بر Area خاص خودش، پوشه‌ی جدید Pages\Shared را نیز ایجاد می‌کند که قالب صفحات Identity را به کمک فایل Pages\Shared\_Layout.cshtml تامین می‌کند:


بنابراین سفارشی سازی قالب این قسمت، شبیه به قالبی که برای کامپوننت‌های Blazor مورد استفاده قرار می‌گیرد، باید در اینجا انجام شود و سفارشی سازی قالب کامپوننت‌های Blazor، در پوشه‌ی Shared ای که در ریشه‌ی پروژه‌است (BlazorServer.App\Shared\MainLayout.razor) انجام می‌شود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-21.zip
مطالب
استفاده از 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 تفکیک شود و در لایه مجزایی قرار گیرد.

اشتراک‌ها
طراحی افزونه پذیر و ماژولار در Asp.net core

ExtCore allows you to decouple your application into the modules (or extensions) and reuse that modules in other applications in various combinations. Each ExtCore extension may consist of one or more projects and each project may include everything you want (as any other ASP.NET Core project). Controllers, view components, views (added as resources and/or precompiled), static content (added as resources) will be resolved automatically. These projects (extension pieces) may be added to the application directly as dependencies in project.json of your main application project (as source code or NuGet packages), or by copying compiled DLL-files to the Extensions folder. ExtCore supports both of these approaches out of the box and at the same time. 

طراحی افزونه پذیر و ماژولار در Asp.net core
اشتراک‌ها
NuGet 2.8.5 منتشر شد

اگر قصد استفاده‌ی از بسته‌های فریم ورک‌های جدیدتر را دارید، این به روز رسانی ضروری است. همچنین پوشه‌های core50, dnx452, dnx46, dnxcore50 از این پس در فایل spec قابل تعریف هستند (قابل توجه تهیه کنندگان بسته‌های نیوگت).

NuGet 2.8.5 منتشر شد
اشتراک‌ها
اسرارِ یک دات نت کار حرفه ای

Working as a .NET Professional is a tumultuous rollercoaster ride of emotional highs and crushing lows. It’s likely the same for other communities, with different flavors of success and failures. I have over a decade of .NET development work, and I am here to share some general mantras that have served me well. 

اسرارِ یک دات نت  کار حرفه ای
اشتراک‌ها
پیاده سازی الگوی ریپازیتوری و تزریق وابستگی در ado.net

Nowadays, I am trying to learn different design patterns in object oriented paradigm that are pretty useful to implement generic solutions for different scenarios. Few weeks ago for a job hunt, I got an assignment to do which was a web application that would interact with database, so I took it up as a challenge and decided to make it loosely coupled using design patterns which were applicable in that scenario.

 
پیاده سازی الگوی ریپازیتوری و تزریق وابستگی در ado.net
مطالب
آموزش BrightStarDb (قسمت اول)
در طی این پست ها با مفاهیم NoSql آشنا شدید. همچنین در این دوره مفاهیم و مبانی RavenDb (یکی از بی نقص‌ترین دیتابیس‌های NoSql) بررسی شد. اما قرار است در طی چند پست با یکی دیگر از انواع دیتابیس‌های NoSql  طراحی شده برای دات نت به نام  BrightStarDb یا به اختصار  B*Db آشنا شویم.

*در دنیای NoSql پیاده سازی‌های متفاوتی از دیتابیس‌ها انجام شده است و هر دیتابیس، ویژگی‌ها و مزایا و معایب خاص خودش را دارد. باید قبول کرد که همیشه و همه جا نمی‌توان از یک پایگاه داده NoSql مشخص استفاده نماییم (دلایلی نظیر محدودیت‌های License، هزینه پیاده سازی و...). در نتیجه بررسی یک دیتابیس دیگر با شرایط و توانمندی‌های خاص آن خالی از سود نیست.
از ویژگی مهم این دیتابیس می‌توان به عناوین زیر اشاره کرد.
» این دیتاییس در گروه Graph databases‌ها قرار دارد و از  زبان SPARQL (بخوانید Sparkle) برای  کوئری گرفتن و کار با داده‌ها بهره می‌برد؛
» متن باز و رایگان است
» پشتیبانی از دات نت چهار به بعد؛
» قابل استفاده در Mobile Application - Windows phone 7 , 8؛
» بدون شما (Schema Less) و با قابیلت ذخیره سازی triple و به فرمت RDF
» پشتیبانی از Linq و  OData؛
» پشتیبانی از تراکنش‌ها ؛
» پیاده سازی مدل برنامه به صورت Code First؛
» سرعت بالا جهت ذخیره سازی و لود اطلاعات؛
» نیاز به پیکربندی‌های خاص جهت پیاده سازی ندارد؛
» ارائه افزونه رایگان Polaris جهت کوئری گفتن و نمایش Visual داده ها.
و غیره که در ادامه با آن‌ها آشنا خواهید شد.

در B*Db دو روش برای ذخیره سازی اطلاعات وجود دارد:
» Append Only : در این روش تمامی تغییرات (عملیات نوشتن) در انتهای فایل index اضافه خواهد شد. این روش مزایای زیر را به دنبال خواهد داشت:
  • عملیات نوشتن هیچگاه عملیات خواندن اطلاعات را block نمی‌کند. در نتیجه هر تعداد عملیات خواندن فایل (منظور اجرای کوئری‌های SPQRL است) می‌تواند به صورت موازی بر روی Store‌ها اجرا شود.
  • به دلیل اینکه عمل ویرایش واقعی هیچ گاه انجام نمی‌شود (داده‌ها فقط اضافه خواهند شد) همیشه می‌توانید وضعیت Store را به حالت‌های قبلی بازگردانید.
  • عملیات نوشتن اطلاعات بسیار سریع خواهد بود.
از معایب این روش این است که حجم Store‌ها فقط با افزایش داده‌ها زیاد نمی‌شود، بلکه با هر عملیات ویرایش نیز حجم فایل‌های Store افزایش پیاده خواهد کرد. در نتیجه از این روش فقط زمانی که از نظر فضای هارد دیسک محدودیت ندارید استفاده کنید(روش پیش فرض در B*Db نیز همین حالت است)

» Rewritable : در این روش در هنگام اجرای عملیات نوشتن، ابتدا یک کپی از اطلاعات گرفته میشود. سپس داده‌های مورد نظر به کپی گرفته شده اعمال می‌شوند. تا زمانیکه عملیات نوشتن اطلاعات به پایان نرسد، هر گونه دسترسی به اطلاعات جهت عملیات Read بر روی داده اصلی اجرا می‌شود. با استفاده از این روش عملیات Read و Write هیچ گونه تداخلی با هم نخواهند داشت. (چیزی شبیه به ^)

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

همان طور که پیشتر گفته شد B*Db  برای ذخیره سازی اطلاعات از سند RDF بهره می‌برد. البته با RDF Syntax‌های متفاوت :

هم چنین در B*Db چهار روش برای دست یابی با داده‌ها (پیاده سازی عملیات CRUD) وجود دارد از قبیل:
» B*Db EntityFramewok
» Data Object Layer
» RDF Client Api
» Dynamic API
که هر کدام در طی پست‌های متفاوت بررسی خواهد شد.

بررسی یک مثال با روش B*Db EntityFramework

برای شروع ابتدا یک پروژه جدید از نوع Console Application ایجاد کنید. سپس از طریق Nuget اقدام به نصب Package  زیر نمایید:
pm> install-Package BirghtStarDb
پکیج بالا تمام کتابخانه‌های لازم جهت کار با B*Db را شامل می‌شود. اگر قصد ندارید از افزونه‌های مربوط به EntityFramework و Code First استفاده نمایید می‌توانید Package زیر را نصب نمایید:
PM> Install-Package BirghtStarDbLibs
این پکیج فقط شامل کتابخانه‌های لازم جهت کار با استفاده از SPRQL است.
بعد از نصب پکیج‌های بالا یک فایل Text Template با نام MyEntityContext.tt  نیز به پروژه افزوده خواهد شد. این فایل جهت تولید خودکار مدل‌های برنامه استفاده می‌شود. اما برای این کار لازم است به ازای هر مدل ابتدا یک اینترفیس ایجاد نمایید. برای مثال:
 [Entity]
    public interface IBook
    {
        public int Code { get; set; }
        public string Title { get; set; }
    }
نکته:
» نیاز به ایجاد یک خاصیت به عنوان Id وجود ندارد. به صورت پیش فرض خاصیت Id با نوع string برای هر مدل پیاده سازی می‌شود. اما اگر قصد دارید این نام خاصیت را تغییر دهید می‌توانید به صورت زیر عمل کنید:
[Entity]
    public interface IBook
    {
        [Identifier]
        public string MyId { get;  }
        public int Code { get; set; }   
        public string Title { get; set; }
    }
در مثال بالا خاصیت MyId به جای خاصیت Id در نظر گرفته می‌شود. مزین شدن با Identifier  و همچنین نداشتن متد set را فراموش نکنید. بعد از ایجاد اینترفیس مورد نظر و اجرای Run Custom Tool بر روی فایل Text Template.tt کلاسی به نام Book به صورت زیر ساخته می‌شود:

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

 MyEntityContext context = new MyEntityContext("type=embedded;storesdirectory=c:\brightstar;storename=test");
            var book = context.Books.Create();
            book.Code = 1;
            book.Title = "Test";

            context.Books.Add(book);

            context.SaveChanges();
با یک نگاه می‌توان به شباهت مدل پیاده سازی شده بالا به EntityFramework پی برد. اما نکته مهم در مثال بالا ConnectionString پاس داده شده به Context پروژه است. در B*Db چهار روش برای دستیابی به اطلاعات ذخیره شده وجود دارد:
»embedded : در این حالت از طریق آدرس فیزیکی فایل مورد نظر می‌توان یک Connection ایجاد کرد.
»rest : یا استفاده از HTTP یا HTTPS می‌توان به سرویس B*Db دسترسی داشت.
»dotNetRdf : برای ارتباط با یک Store دیگر با استفاده از اتصال دهنده‌های DotNetRDf.
»sparql : اتصال به منبع داده ای دیگر با استفاده از پروتکل SPARQL
در هنگام ایجاد اتصال باید نوع مورد نظر را از حتما تعیین نمایید. با استفاده از storedirctory مکان فیزیکی فایل تعیین خواهد شد.
مطالب
فلسفه وجودی بخش finally در try catch چیست؟
حتما شما هم متوجه شدید که وقتی رخداد یک استثناء را با استفاده از try و catch کنترل می‌کنیم، هر چیزی که بعد از بسته شدن تگ catch بنویسیم، در هر صورت اجرا می‌شود.

 try {
     int i=0;
     string s = "hello";
     i = Convert.ToInt32(s);
} catch (Exception ex)
{
     Console.WriteLine("Error");
}
Console.WriteLine("I am here!");

پس فلسفه استفاده از بخش finally چیست؟
در قسمت finally منابع تخصیص داده شده در try را آزاد می‌کنیم. کد موجود در این قسمت به هر روی اجرا می‌شود چه استثناء رخ دهد چه ندهد. البته اگر استثناء رخ داده شده در لیست استثناء هایی که برای آنها catch انجام دادیم نباشد، قسمت finally هم عمل نخواهد کرد مگر اینکه از catch به صورت سراسری استفاده کنیم.
اما مهمترین مزیتی که finally ایجاد می‌کند در این است که حتی اگر در قسمت try با استفاده از دستوراتی مثل return یا break یا continue از ادامه کد منصرف شویم و مثلا مقداری برگردانیم، چه خطا رخ دهد یا ندهد کد موجود در finally اجرا می‌شود در حالی که کد نوشته شده بعد از try catch finally فقط در صورتی اجرا می‌شود که به طور منطقی اجرای برنامه به آن نقطه برسد. اجازه بدهید با یک مثال توضیح دهم. اگر کد زیر را اجرا کنیم:
  public static int GetMyInt()
 {
     try {
          for (int i=10;i>=0;i--)
             Console.WriteLine(10/i);
         return 1;
     } catch
     {
         Console.WriteLine("Error!");
     }
     finally {
         Console.WriteLine("ok");
     }
     Console.WriteLine("can you reach here?");  
     return -1;  
 }

برنامه خطای تقسیم بر صفر می‌دهد اما با توجه به کدی که نوشتیم، عدد -1 به خروجی خواهد رفت. در عین حال عبارت ok و can you reach here در خروجی چاپ شده است. اما حال اگر مشکل تقسیم بر صفر را حل کنیم، آیا باز هم عبارت can you reach here در خروجی چاپ خواهد شد؟
 
public static int GetMyInt()
 {
     try {
          for (int i=10;i>=1;i--)
             Console.WriteLine(10/i);
         return 1;
     } catch
     {
         Console.WriteLine("Error!");
     }
     finally {
         Console.WriteLine("ok");
     }
     Console.WriteLine("can you reach here?");  
     return -1;  
 }

مشاهده می‌کنید که مقدار 1 برگردانده می‌شود و عبارت can you reach here در خروجی چاپ نمی‌شود ولی همچنان عبارت ok که در finally ذکر شده در خروجی چاپ می‌شود. یک مثال خوب استفاده از چنین وضعیتی، زمانی است که شما یک ارتباط با بانک اطلاعاتی باز می‌کنید، و نتیجه یک عملیات را با دستور return به کاربر بر می‌گردانید. مسئله این است که در این وضعیت چگونه ارتباط با دیتابیس بسته شده و منابع آزاد می‌گردند؟ اگر در حین عملیات بانک اطلاعاتی، خطایی رخ دهد یا ندهد، و شما دستور آزاد سازی منابع و بستن ارتباط را در داخل قسمت finally نوشته باشید، وقتی دستور return فراخوانی می‌شود، ابتدا منابع آزاد و سپس مقدار به خروجی بر می‌گردد.
public int GetUserId(string nickname)
{
     SqlConnection connection = new SqlConnection(...);
     SqlCommand command = connection.CreateCommand();
     command.CommandText = "select id from users where nickname like @nickname";
     command.Parameters.Add(new SqlParameter("@nickname", nickname));
      try {
         connection.Open();
          return Convert.ToInt32(command.ExecuteScalar());
      } 
      catch(SqlException exception)
      {
      // some exception handling
         return -1;
      } finally {
          if (connection.State == ConnectionState.Open)
         connection.Close();
      }
      // if all things works, you can not reach here
}


اشتراک‌ها
1.30 Visual Studio Code منتشر شد

Welcome to the November 2018 release of Visual Studio Code. There are a number of significant updates in this version that we hope you will like, some of the key highlights include:

1.30 Visual Studio Code منتشر شد