مطالب
پیاده سازی برنامه‌های چند مستاجری در ASP.NET Core

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

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

 در معماری Multi-Tenancy، چندین کاربر می‌توانند از یک نمونه (Single Instance) از اپلیکیشن نرم‌افزاری استفاده کنند. یعنی این نمونه روی سرور اجرا می‌شود و به چندین کاربر سرویس می‌دهد. هر کاربر را یک Tenant می‌نامیم. می‌توان به Tenantها امکان تغییر و شخصی‌سازی بخشی از اپلیکیشن را داد؛ مثلا امکان تغییر رنگ رابط کاربری و یا قوانین کسب‌وکار، اما آنها نمی‌توانند کدهای اپلیکیشن را شخصی‌سازی کنند.

بدون داشتن دانش کافی، پیاده سازی معماری multi tenant می‌تواند تبدیل یه یک چالش بزرگ شود. مخصوصا در نسخه‌ی قبلی ASP.NET که یکپارچه نبودن فریم ورک‌های مختلف می‌توانست باعث ایجاد چندین پیاده سازی مختلف در برنامه شود. موضوع وقتی پیچیده‌تر می‌شد که شما قصد داشتید در یک برنامه چندین فریم ورک مختلف مثل SignalR, MVC, Web API را مورد استفاده قرار دهید.

خوشبختانه اوضاع با وجود OWIN بهتر شده و ما در این مطلب قصد استفاده از یک تولکیت را به نام SaasKit، برای پیاده سازی این معماری در ASP.NET Core داریم. هدف از این toolkit، ساده‌تر کردن هر چه بیشتر ساخت برنامه‌های SaaS (Software as a Service) هست. با استفاده از OWIN ما قادریم که بدون در نظر گرفتن فریم ورک مورد استفاده، رفتار مورد نظر خودمان را مستقیما در یک چرخه درخواست HTTP پیاده سازی کنیم و البته به لطف طراحی خاص ASP.NET Core 1.0 و استفاده از میان افزار‌هایی مشابه OWIN در برنامه، کار ما با SaasKit باز هم راحت‌تر خواهد بود.

شروع کار 

یک پروژه ASP.NET Core جدید را ایجاد کنید و سپس ارجاعی را به فضای نام SaasKit.Multitenancy  (موجود در Nuget) بدهید. 
PM> Install-Package SaasKit.Multitenancy
بعد از اینکار ما باید به SaasKit اطلاع دهیم که چطور مستاجر‌های ما را شناسایی کند.

شناسایی مستاجر (tenant) 

اولین جنبه در معماری multi-tenant، شناسایی مستاجر بر اساس اطلاعات درخواست جاری می‌باشد که می‌تواند از hostname ، کاربر جاری یا یک HTTP header باشد.
ابتدا به تعریف کلاس مستاجر می‌پردازیم: 
    public class AppTenant
    {
        public string Name { get; set; }
        public string[] Hostnames { get; set; }
    }
سپس از طریق پیاده سازی اینترفیس ITenantResolver  و نوشتن یک tenant resolver به SaasKit اطلاع می‌دهیم که چطور مستاجر جاری را بر اساس اطلاعات درخواست جاری شناسایی کند و در صورتیکه موفق به شناسایی شود، یک وهله از نوع <TenantContext<TTenant را بازگشت خواهد داد. 
    public class AppTenantResolver : ITenantResolver<AppTenant>
    {
        IEnumerable<AppTenant> tenants = new List<AppTenant>(new[]
        {
            new AppTenant {
                Name = "Tenant 1",
                Hostnames = new[] { "localhost:6000", "localhost:6001" }
            },
            new AppTenant {
                Name = "Tenant 2",
                Hostnames = new[] { "localhost:6002" }
            }
        });
        public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
        {
            TenantContext<AppTenant> tenantContext = null;
            var tenant = tenants.FirstOrDefault(t =>
                t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
            if (tenant != null)
            {
                tenantContext = new TenantContext<AppTenant>(tenant);
            }
            return tenantContext;
        }
    }
در نظر داشته باشید که اینجا ما اطلاعات مستاجر را از روی hostname استخراج کردیم؛ اما از آنجا که شما به شیء HttpContext دسترسی کاملی دارید، می‌توانید از هر چیزی که مایل باشید استفاده کنید؛ مثل URL، اطلاعات کاربر، هدر‌های HTTP و غیره. در اینجا فعلا مشخصات مستاجر‌های خودمان را در کد نوشتیم. اما شما می‌توانید در برنامه خودتان این اطلاعات را از فایل تنظیمات برنامه و یا یک بانک اطلاعاتی دریافت کنید.
 

سیم کشی کردن 

بعد از پیاده سازی این اینترفیس نوبت به سیم کشی‌های SaasKit میرسد. من در اینجا سعی کردم که مثل الگوی برنامه‌های ASP.NET Core عمل کنم. ابتدا نیاز داریم که وابستگی‌های SaasKit را ثبت کنیم. فایل startups.cs  را باز کنید و کدهای زیر را در متد ConfigureServices اضافه نمایید: 
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMultitenancy<AppTenant, AppTenantResolver>();
    }
سپس باید میان افزار SaasKit را ثبت کنیم. کدهای زیر را به متد Configure اضافه کنید:
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // after .UseStaticFiles()
        app.UseMultitenancy<AppTenant>();
        // before .UseMvc()
    }


دریافت مستاجر جاری 

حالا هر جا که نیاز به وهله‌ای از شیء مستاجر جاری داشتید، می‌توانید به روش زیر عمل کنید: 
    public class HomeController : Controller
    {
        private AppTenant tenant;
        public HomeController(AppTenant tenant)
        {
            this.tenant = tenant;
        }
    }
به عنوان مثال قصد داریم نام مستاجر را در عنوان سایت نمایش دهیم. برای اینکار ما از قابلیت جدید MVC Core یعنی تزریق سرویس‌ها به View استفاده خواهیم کرد.  در فایل Layout.cshtml_ تکه کد زیر را به بالای صفحه اضافه کنید:
    @inject AppTenant Tenant;
این کد، AppTenant را برای ما در تمامی View‌ها از طریق شی Tenant قابل دسترس می‌کند. حالا می‌توانیم در View خود از جزییات مستاجر به شکل زیر استفاده کنیم: 
    <a asp-controller="Home" asp-action="Index">@Tenant.Name</a>


اجرای نمونه مثال 

فایل project.json را باز کنید و مقدار web را به شکل زیر مقدار دهی کنید: (در اینجا برای سایت خود 3 آدرس را نگاشت کردیم) 
    "commands": {
      "web": "Microsoft.AspNet.Server.Kestrel --server.urls=http://localhost:6000;http://localhost:6001;http://localhost:6002",
    },
سپس کنسول را در محل ریشه پروژه باز نموده و دستور زیر را اجرا کنید: 
    dotnet run
حال اگر در مرورگر خود آدرس http://localhost:6000 را وارد کنیم، مستاجر 1 را خواهیم دید:


و اگر آدرس http://localhost:6002 را وارد کنیم، مستاجر 2 را مشاهده می‌کنیم:


قابل پیکربندی کردن مستاجر ها 

از آنجائیکه نوشتن مشخصات مستاجر‌ها در کد زیاد جالب نیست، برای همین تصمیم داریم که این مشخصات را با استفاده از قابلیت‌های ASP.NET Core از فایل appsettings.json دریافت کنیم. تنظیمات مستاجر‌ها را مطابق اطلاعات زیر به این فایل اضافه کنید:

    "Multitenancy": {
      "Tenants": [
        {
          "Name": "Tenant 1",
          "Hostnames": [
            "localhost:6000",
            "localhost:6001"
          ]
        },
        {
          "Name": "Tenant 2",
          "Hostnames": [
            "localhost:6002"
          ]
        }
      ]
    }
سپس کلاسی را که بیانگر تنظیمات چند مستاجری باشد، می‌نویسیم: 
    public class MultitenancyOptions
    {
        public Collection<AppTenant> Tenants { get; set; }
    }
حالا نیاز داریم که به برنامه اعلام کنیم تا تنظیمات مورد نیاز خود را از فایل appsettings.json بخواند. کد زیر را به ConfigureServices اضافه کنید: 
    services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
سپس کدهای resolver خود را جهت دریافت اطلاعات از MultitenancyOptions مطابق زیر تغییر می‌دهیم: 
    public class AppTenantResolver : ITenantResolver<AppTenant>
    {
        private readonly IEnumerable<AppTenant> tenants;
        public AppTenantResolver(IOptions<MultitenancyOptions> options)
        {
            this.tenants = options.Value.Tenants;
        }
        public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
        {
            TenantContext<AppTenant> tenantContext = null;
            var tenant = tenants.FirstOrDefault(t => 
                t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
            if (tenant != null)
            {
                tenantContext = new TenantContext<AppTenant>(tenant);
            }
            return Task.FromResult(tenantContext);
        }
    }
برنامه را یکبار re-build کرده و اجرا کنید . 


در آخر 

اولین قدم در پیاده سازی یک معماری multi-tenant، تصمیم گیری درباره این موضوع است که شما چطور مستاجر خود را شناسایی کنید. به محض این شناسایی شما می‌توانید عملیات‌های بعدی خود را مثل تفکیک بخشی از برنامه، فیلتر کردن داده‌ای، نمایش یک view خاص برای هر مستاجر و یا بازنویسی قسمت‌های مختلف برنامه بر اساس هر مستاجر، انجام دهید.

_ سورس مثال بالا در گیت هاب قابل دریافت می‌باشد.

_ منبع: اینجا  

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

مطالب
لینک‌های هفته‌ی دوم اسفند

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

امنیت

Visual Studio

ASP. Net

طراحی و توسعه وب

PHP

اس‌کیوال سرور

سی شارپ

VB

CPP

عمومی دات نت

مسایل اجتماعی و انسانی برنامه نویسی

متفرقه
اشتراک‌ها
معرفی پروژه‌ی GitHub Folder Downloader
 برنامه‌ی GitHubFolderDownloader قادر است یک پوشه‌ی مفروض از GitHub را دریافت کند. به این ترتیب دیگر نیازی نیست تا کل یک مخزن بزرگ را clone کنید. تنها کافی است آدرس پوشه‌ی مورد نظر را به این برنامه بدهید تا فایل‌های آن‌را برای شما دریافت کند. برای کار با آن، ابتدا آخرین نگارش برنامه را دریافت کنید. سپس نیاز است به اکانت خود در GitHub وارد شده و یک توکن را ایجاد کنید. این توکن مجوز کار با API سایت GitHub را فراهم می‌کند.
معرفی پروژه‌ی GitHub Folder Downloader
نظرات مطالب
خلاصه‌ای در مورد وضعیت فعلی MySQL
اکثر سایت‌هایی که مشاهده می‌کنید (و عموما بر مبنای PHP هستند) مثل فوروم‌ها، وبلاگ‌ها و غیره، سورس باز هستند و یا مجوز GPL دارند یا احتمالا هم خانواده‌اند و مشکلی نخواهند داشت. در غیراینصورت اگر کار نهایی تجاری و سورس بسته باشد و شخصی هم گزارش بدهد و هاست هم در کشوری باشد که مسایل کپی رایت را رعایت می‌کند، سایت را معلق می‌کنند، به علاوه سایر مسایل قانونی مرتبط.
مطالب
Blazor 5x - قسمت سوم - مبانی Razor
پیش از شروع به کار توسعه‌ی برنامه‌های مبتنی بر Blazor، باید با مبانی Razor آشنایی داشت. Razor امکان ترکیب کدهای #C و HTML را در یک فایل میسر می‌کند. دستور زبان آن از @ برای سوئیچ بین کدهای #C و HTML استفاده می‌کند. کدهای Razor را می‌توان در فایل‌های cshtml. نوشت که عموما مخصوص صفحات و Viewها هستند و یا در فایل‌های razor. که برای توسعه‌ی کامپوننت‌های Balzor بکار گرفته می‌شوند. در اینجا مهم نیست که پسوند فایل مورد استفاده چیست؛ چون اصول razor بکار گرفته شده در آن‌ها یکی است. البته در اینجا تاکید ما بیشتر بر روی فایل‌های razor. است که در برنامه‌های مبتنی بر Blazor بکار گرفته می‌شوند.


ایجاد یک پروژه‌ی جدید Blazor WASM

برای پیاده سازی و اجرای مثال‌های این قسمت، نیاز به یک پروژه‌ی جدید Blazor WASM را داریم که می‌توان آن‌را با اجرای دستور dotnet new blazorwasm --hosted در یک پوشه‌ی خالی، ایجاد کرد.

یک نکته: دستور فوق به همراه یک سری پارامتر اختیاری مانند hosted-- نیز هست. برای مشاهده‌ی لیست آن‌ها دستور dotnet new blazorwasm --help را صادر کنید. برای مثال ذکر پارامتر hosted-- سبب می‌شود تا یک ASP.NET Core host نیز برای Blazor WebAssembly app ایجاد شده تولید شود.

حالت hosted-- آن یک چنین ساختاری را دارد که از سه پروژه و پوشه‌ی Client ،Server و Shared تشکیل می‌شود:


در اینجا یک پروژه‌ی خالی WASM ایجاد شده که برخلاف حالت معمولی dotnet new blazorwasm که در قسمت قبل آن‌را بررسی کردیم، دیگر از فایل استاتیک wwwroot\sample-data\weather.json در آن خبری نیست. بجای آن، یک پروژه‌ی استاندارد ASP.NET Core Web API را در پوشه‌ی جدید Server ایجاد کرده که کار ارائه‌ی اطلاعات این سرویس آب و هوا را انجام می‌دهد و برنامه‌ی WASM ایجاد شده، این اطلاعات را توسط HTTP Client خود، از سرور Web API دریافت می‌کند.

بنابراین اگر مدل برنامه‌ای که قصد دارید تهیه کنید، ترکیبی از یک Web API و WASM است، روش hosted--، آغاز آن‌را بسیار ساده می‌کند.

نکته: روش اجرای این نوع برنامه‌ها با اجرای دستور dotnet run در داخل پوشه‌ی Server پروژه، انجام می‌شود. با اینکار هم سرور ASP.NET Core آغاز می‌شود و هم برنامه‌ی WASM توسط آن ارائه می‌گردد. در این حالت اگر آدرس https://localhost:5001 را در مرورگر باز کنیم، هم قسمت‌های بدون نیاز به سرور پروژه‌ی WASM قابل دسترسی است (مانند کار با شمارشگر آن) و هم قسمت دریافت اطلاعات از سرور آن، در منوی Fetch Data.


شروع به کار با Razor

پس از ایجاد یک پروژه‌ی جدید WASM، به فایل Client\Pages\Index.razor آن مراجعه کرده و محتوای پیش‌فرض آن‌را بجز سطر اول زیر، حذف می‌کنیم:
@page "/"
این سطر، بیانگر مسیریابی منتهی به کامپوننت جاری است. یعنی با گشودن برنامه‌ی WASM در مرورگر و مراجعه به ریشه‌ی سایت، محتوای این کامپوننت را مشاهده خواهیم کرد.
در فایل‌های razor. می‌توان ترکیبی از کدهای #C و HTML را نوشت. برای مثال:
@page "/"

<p>Hello, @name</p>

@code
{
    string name = "Vahid N.";
}
در اینجا قصد داریم مقدار یک متغیر را در یک پاراگراف درج کنیم. به همین جهت برای تعریف آن و شروع به کدنویسی می‌توان با تعریف یک قطعه کد که در فایل‌های razor با code@ شروع می‌شود، اینکار را انجام داد. در این قطعه کد، نوشتن هر نوع کد #C ای مجاز است که نمونه‌ای از آن‌را در اینجا با تعریف یک متغیر مشاهده می‌کنید. اکنون برای درج مقدار این متغیر در بین کدهای HTML از حرف @ استفاده می‌کنیم؛ مانند name@ در اینجا. نمونه‌ای از خروجی تغییرات فوق را در تصویر زیر مشاهده می‌کنید:


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

در زمان درج متغیرهای #C در بین کدهای HTML توسط razor، استفاده از تمام متدهای الحاقی زبان #C نیز مجاز هستند؛ مانند:
 <p>Hello, @name.ToUpper()</p>
بنابراین درج حرف @ در بین کدهای HTML به این معنا است که به کامپایلر razor اعلام می‌کنیم، پس از این حرف، هر عبارتی که قرار می‌گیرد، یک عبارت معتبر #C است.

یا حتی می‌توان یک متد جدید را مانند CustomToUpper در قطعه کد razor، تعریف کرد و از آن به صورت زیر استفاده نمود:
@page "/"

<p>Hello, @name.ToUpper()</p>
<p>Hello, @CustomToUpper(name)</p>

@code
{
    string name = "Vahid N.";

    string CustomToUpper(string value) => value.ToUpper();
}
در این مثال‌ها، ابتدای عبارت #C تعریف شده با حرف @ شروع می‌شود و انتهای آن‌را خود کامپایلر razor بر اساس بسته شدن تگ p تعریف شده، تشخیص می‌دهد. اما اگر قصد داشته باشیم برای مثال جمع دو عدد را در اینجا محاسبه کنیم چطور؟
<p>Let's add 2 + 2 : @2 + 2 </p>
در این حالت امکان تشخیص ابتدا و انتهای عبارت #C توسط کامپایلر میسر نیست. برای رفع این مشکل می‌توان از پرانتزها استفاده کرد:
<p>Let's add 2 + 2 : @(2 + 2) </p>
نمونه‌ی دیگر نیاز به تعریف ابتدا و انتهای یک قطعه کد، در حین تعریف مدیریت کنندگان رویدادها است:
<button @onclick="@(()=>Console.WriteLine("Test"))">Click me</button>
در اینجا onclick@ مشخص می‌کند که با کلیک بر روی این دکمه قرار است قطعه کد #C ای اجرا شود. سپس با استفاده از ()@ محدوده‌ی این قطعه کد، مشخص می‌شود و اکنون در داخل آن می‌توان یک anonymous function را تعریف کرد که خروجی آن را در قسمت console ابزارهای توسعه دهندگان مرورگر می‌توان مشاهده کرد:


در اینجا اگر از Console.WriteLine("Test")@ استفاده می‌شد، به معنای انتساب یک رشته‌ی محاسبه شده به رویداد onclick بود که مجاز نیست.
روش دیگر انجام اینکار به صورت زیر است:
@page "/"

<button @onclick="@WriteLog">Click me 2</button>

@code
{
    void WriteLog()
    {
        Console.WriteLine("Test");
    }
}
می‌توان یک متد void را تعریف کرد و سپس فقط نام آن‌را توسط @ به onlick انتساب داد. ذکر این نام، اشاره‌گری خواهد بود به متد اجرا نشده‌ی WriteLog. در این حالت اگر نیاز به ارسال پارامتری به متد WriteLog بود، چطور؟
@page "/"

<button @onclick="@(()=>WriteLogWithParam("Test 3"))">Click me 3</button>

@code
{
    void WriteLogWithParam(string value)
    {
        Console.WriteLine(value);
    }
}
در این حالت نیز می‌توان از روش بکارگیری anonymous function‌ها برای تعریف پارامتر استفاده کرد.

یک نکته: اگر به اشتباه بجای WriteLogWithParam، همان WriteLog قبلی را بنویسیم، کامپایلر (در حال اجرای توسط دستور dotnet watch run) خطای زیر را نمایش می‌دهد؛ پیش از اینکه برنامه در مرورگر اجرا شود:
BlazorRazorSample\Client\Pages\Index.razor(12,25): error CS1501: No overload for method 'WriteLog' takes 1 arguments


امکان تعریف کلاس‌ها در فایل‌های razor.

در فایل‌های razor.، محدود به تعریف یک سری متدها و متغیرهای ساده نیستیم. در اینجا امکان تعریف کلاس‌ها نیز وجود دارد و همچنین می‌توان از کلاس‌های خارجی (کلاس‌هایی که خارج از فایل razor جاری تعریف شده‌اند) نیز استفاده کرد.
@page "/"

<p>Hello, @StringUtils.MyCustomToUpper(name)</p>

@code
{
    public class StringUtils
    {
        public static string MyCustomToUpper(string value) => value.ToUpper();
    }
}
برای نمونه در اینجا یک کلاس کمکی را جهت تعریف متد MyCustomToUpper، اضافه کرده‌ایم. در ادامه نحوه‌ی استفاده از این متد را در پاراگراف تعریف شده، مشاهده می‌کنید که همانند کار با کلاس و متدهای متداول #C است.
البته این کلاس را تنها می‌توان داخل همین کامپوننت استفاده کرد. برای اینکه بتوان از امکانات این کلاس، در سایر کامپوننت‌ها نیز استفاده کرد، می‌توان آن‌را در پروژه‌ی Shared قرار داد. اگر به تصویر ابتدای مطلب جاری دقت کنید، سه پروژه ایجاد شده‌است:
الف) پروژه‌ی کلاینت: که همان WASM است.
ب) پروژه‌ی سرور: که یک پروژه‌ی ASP.NET Core Web API ارائه کننده‌ی سرویس و API آب و هوا است و همچنین هاست کننده‌ی WASM ما.
ج) پروژه‌ی Shared: کدهای این پروژه، بین هر دو پروژه به اشتراک گذاشته می‌شوند و برای مثال محل مناسبی است برای تعریف DTO ها. برای نمونه WeatherForecast.cs قرار گرفته‌ی در آن، DTO یا data transfer object سرویس API برنامه است که قرار است به کلاینت بازگشت داده شود. به این ترتیب دیگر نیازی نخواهد بود تا این تعاریف را در پروژه‌های سرور و کلاینت تکرار کنیم و می‌توان کدهای اینگونه را به اشتراک گذاشت.
کاربرد دیگر آن تعریف کلاس‌های کمکی است؛ مانند StringUtils فوق. به همین به پروژه‌ی Shared مراجعه کرده و کلاس StringUtils را به صورت زیر در آن تعریف می‌کنیم (و یا حتی می‌توان این قطعه کد را داخل یک پوشه‌ی جدید، در همان پروژه‌ی WASM نیز قرار داد):
namespace BlazorRazorSample.Shared
{
    public class StringUtils
    {
        public static string MyNewCustomToUpper(string value) => value.ToUpper();
    }
}
اگر به فایل‌های csproj دو پروژه‌ی سرور و کلاینت جاری مراجعه کنیم، از پیش، مدخلی را به فایل Shared\BlazorRazorSample.Shared.csproj دارند. بنابراین جهت معرفی این اسمبلی به آن‌ها، نیاز به کار خاصی نیست و از پیش، ارجاعی به آن تعریف شده‌است.

پس از آن روش استفاده‌ی از این کلاس کمکی خارجی اشتراکی به صورت زیر است:
@page "/"

@using BlazorRazorSample.Shared

<p>Hello, @StringUtils.MyNewCustomToUpper(name)</p>
ابتدا فضای نام این کلاس را با استفاده از using@ مشخص می‌کنیم و سپس امکان دسترسی به امکانات آن میسر می‌شود.

یک نکته: می‌توان به فایل Client\_Imports.razor مراجعه و مدخل زیر را به انتهای آن اضافه کرد:
@using BlazorRazorSample.Shared
به این ترتیب دیگر نیازی به ذکر این using@ تکراری، در هیچکدام از فایل‌های razor. پروژه‌ی کلاینت نخواهد بود؛ چون تعاریف درج شده‌ی در فایل Client\_Imports.razor سراسری هستند.


کار با حلقه‌ها در فایل‌های razor.

همانطور که عنوان شد، یکی از کاربردهای پروژه‌ی Shared، امکان به اشتراک گذاشتن مدل‌ها، در برنامه‌های کلاینت و سرور است. برای مثال یک پوشه‌ی جدید Models را در این پروژه ایجاد کرده و کلاس MovieDto را به صورت زیر در آن تعریف می‌کنیم:
using System;

namespace BlazorRazorSample.Shared.Models
{
    public class MovieDto
    {
        public string Title { set; get; }

        public DateTime ReleaseDate { set; get; }
    }
}
سپس به فایل Client\_Imports.razor مراجعه کرده و فضای نام این پوشه را اضافه می‌کنیم؛ تا دیگر نیازی به تکرار آن در تمام فایل‌های razor. برنامه‌ی کلاینت نباشد:
@using BlazorRazorSample.Shared.Models
اکنون می‌خواهیم لیستی از فیلم‌ها را در فایل Client\Pages\Index.razor نمایش دهیم:
@page "/"

<div>
    <h3>Movies</h3>
    @foreach(var movie in movies)
    {
        <p>Title: <b>@movie.Title</b></p>
        <p>ReleaseDate: @movie.ReleaseDate.ToString("dd MMM yyyy")</p>
    }
</div>

@code
{
    List<MovieDto> movies = new List<MovieDto>
    {
        new MovieDto
        {
            Title = "Movie 1",
            ReleaseDate = DateTime.Now.AddYears(-1)
        },
        new MovieDto
        {
            Title = "Movie 2",
            ReleaseDate = DateTime.Now.AddYears(-2)
        },
        new MovieDto
        {
            Title = "Movie 3",
            ReleaseDate = DateTime.Now.AddYears(-3)
        }
    };
}
در اینجا در ابتدا لیستی از MovieDto‌ها در قسمت code@ تعریف شده و سپس روش استفاده‌ی از یک حلقه‌ی foreach سی‌شارپ را در کدهای razor نوشته شده، مشاهده می‌کنید که این خروجی را ایجاد می‌کند:


یک نکته: در حین تعریف فیلدهای code@، امکان استفاده‌ی از var وجود ندارد؛ مگر اینکه از آن بخواهیم در داخل بدنه‌ی یک متد استفاده کنیم.

و یا نمونه‌ی دیگری از حلقه‌های #‍C مانند for را می‌توان به صورت زیر تعریف کرد:
    @for(var i = 0; i < movies.Count; i++)
    {
        <div style="background-color: @(i % 2 == 0 ? "blue" : "red")">
            <p>Title: <b>@movies[i].Title</b></p>
            <p>ReleaseDate: @movies[i].ReleaseDate.ToString("dd MMM yyyy")</p>
        </div>
    }
در اینجا روش تغییر پویای background-color هر ردیف را نیز به کمک کدهای razor، مشاهده می‌کنید. اگر شماره‌ی ردیفی زوج بود، با آبی نمایش داده می‌شود؛ در غیراینصورت با قرمز. در اینجا نیز از ()@ برای تعیین محدوده‌ی کدهای #C نوشته شده، کمک گرفته‌ایم.


نمایش شرطی عبارات در فایل‌های razor.

اگر به مثال توکار Client\Pages\FetchData.razor مراجعه کنیم (مربوط به حالت host-- که در ابتدای مطلب عنوان شد)، کدهای زیر قابل مشاهده هستند:
@page "/fetchdata"
@using BlazorRazorSample.Shared
@inject HttpClient Http

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }

}
در این مثال، روش کار با یک سرویس تزریق شده‌ی async که قرار است از Web API اطلاعاتی را دریافت کند، مشاهده می‌کنید. در اینجا برخلاف مثال قبلی ما، از روال رویدادگردان OnInitializedAsync برای مقدار دهی لیست یا آرایه‌ای از اطلاعات وضعیت هوا استفاده شده‌است (و نه به صورت مستقیم در یک فیلد قسمت code@). این مورد جزو life-cycle‌های کامپوننت‌های razor است که در قسمت‌های بعد بیشتر بررسی خواهد شد. متد OnInitializedAsync برای بارگذاری اطلاعات یک سرویس از راه دور استفاده می‌شود و در اولین بار اجرای کامپوننت فراخوانی خواهد شد. نکته‌ی مهمی که در اینجا وجود دارد، نال بودن فیلد forecasts در زمان رندر اولیه‌ی کامپوننت جاری است؛ از این جهت که کار دریافت اطلاعات از سرور زمان‌بر است ولی رندر کامپوننت، به صورت آنی صورت می‌گیرد. در این حالت زمانیکه نوبت به اجرای foreach (var forecast in forecasts)@ می‌رسد، برنامه با یک استثنای نال بودن forecasts، متوقف خواهد شد؛ چون هنوز کار OnInitializedAsync به پایان نرسیده‌است:


 برای رفع این مشکل، ابتدا یک if@ مشاهده می‌شود، تا نال بودن forecasts را بررسی کند:
@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
و همچنین عبارت در حال بارگذاری را نمایش می‌دهد. سپس در قسمت else آن، نمایش اطلاعات دریافت شده را توسط یک حلقه‌ی foreach مشاهده می‌کنید. با مقدار دهی forecasts در متد OnInitializedAsync، مجددا کار رندر جدول انجام خواهد شد.


روش نمایش عبارات HTML در فایل‌های razor.

فرض کنید عنوان اول فیلم مثال جاری، به همراه یک تگ HTML هم هست:
new MovieDto
{
   Title = "<i>Movie 1</i>",
   ReleaseDate = DateTime.Now.AddYears(-1)
},
در این حالت اگر برنامه را اجرا کنیم، خروجی آن دقیقا به صورت <Title: <i>Movie 1</i خواهد بود. این مورد به دلایل امنیتی انجام شده‌است. اگر پیشتر تگ‌های HTML را تمیز کرده‌اید و مطمئن هستید که خطری را ایجاد نمی‌کنند، می‌توانید با استفاده از روش زیر، آن‌ها را رندر کرد:
<p>Title: <b>@((MarkupString)movie.Title)</b></p>


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-03.zip
برای اجرای آن وارد پوشه‌ی Server شده و دستور dotnet run را اجرا کنید.
مطالب
اطلاع از بروز رسانی نرم افزار ساخته شده
برای شما هم پیش آمده که نرم افزاری را تهیه و منتشر کرده باشید و تمایل داشته باشید که استفاده کنندگان از وجود نسخه بروز شده مطلع شوند. یک راه ساده این است که اطلاعات نسخه جدید نرم افزار را داخل فایلی ذخیره کنیم و در وب سایت پشتیبانی نرم افزار قرار دهیم. حال بایستی اطلاعات این فایل را در زمان اجرای برنامه بررسی کنیم و در صورت وجود نسخه جدید از نرم افزار به کاربر اطلاع رسانی کنیم.
ابتدا فایل اطلاعات بروز رسانی نرم افزار را تهیه می‌کنیم و در وب سایت پشتیبانی نرم افزار قرار میدهیم. در اینجا از قالب Xml استفاده شده. که در آن Version نسخه در دسترس نرم افزار است و URL هم مسیر وب سایت و یا فایل بروز رسانی است. 
<?xml version="1.0" encoding="utf-8"?>
<AccountingApplication>
  <Version>1.5.2</Version>
  <URL>http://www.myappsupport.ir</URL>
</AccountingApplication>
نرم افزار را ساخته و کد زیر را در محل مناسبی کد نویسی می‌کنیم. این کد در ابتدا فایل Xml را خوانده و اطلاعات مورد نیاز را از آن دریافت می‌کند. سپس با استخراج نسخه اسمبلی برنامه و مقایسه این دو با هم از وجود نسخه جدید نرم افزار مطلع میشود.  
...
using System.Xml;
namespace CheckUpdateApplication
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void CheckUpdate_Click(object sender, EventArgs e)
        {
            Version NewVersion = null;
            string DownloadPath = "";
            try
            {
                XmlTextReader xmlRead = new XmlTextReader("http://www.myappsupport.ir/AccUpdateVersion.xml");
                xmlRead.MoveToContent();
                string elmName = "";
                if ((xmlRead.NodeType == XmlNodeType.Element) && (xmlRead.Name == "AccountingApplication"))
                {
                    while (xmlRead.Read())
                    {
                        if (xmlRead.NodeType == XmlNodeType.Element)
                        {
                            elmName = xmlRead.Name;
                        }
                        else 
                        {
                            if ((xmlRead.NodeType == XmlNodeType.Text) && (xmlRead.HasValue))
                            {
                                switch (elmName)
                                {
                                    case "Version":
                                        NewVersion = new Version(xmlRead.Value);
                                        break;
                                    case "URL":
                                        DownloadPath = xmlRead.Value;
                                        break;
                                }
                            }
                        }
                    }
                }
                Version AppVertion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
                if (AppVertion.CompareTo(NewVersion) < 0)
                {
                    DialogResult Result = MessageBox.Show("نسخه " + 
                        NewVersion.Major.ToString() + "." + 
                        NewVersion.Minor.ToString() + "." + 
                        NewVersion.Build.ToString() + " در دسترس میباشد مایل به دانلود هستید؟", "نسخه جدید", 
                        MessageBoxButtons.YesNo,MessageBoxIcon.Question);
                    if (Result == DialogResult.Yes)
                    {
                        System.Diagnostics.Process.Start(DownloadPath);
                    }
                }
                else
                {
                    MessageBox.Show("نرم افزار بروز میباشد");
                }
            }
            catch (Exception E)
            {
                MessageBox.Show(E.Message); 
            }
        }
    }
}

به روش زیر هم نسخه اسمبلی برنامه را می‌شود تغییر داد.

سورس برنامه نمونه
پروژه‌ها
فروشگاه IrisStore
پروژه IrisStore، یک سیستم فروشگاهی متن باز برای راه اندازی فروشگاه‌های اینترنتی کوچک است که سورس آن را می‌توانید از آدرس زیر دریافت کنید و برای اجرای آن نیاز به VS 2015 دارید: 

https://github.com/MehdiSaeedifar/IrisStore 

همچنین نمونه‌ی آنلاین آن‌را می‌توانید در فروشگاه آیریس مشاهده کنید.


در ادامه برخی از قابلیت‌های این سیستم را مشاهده می‌کنید: 
 

جست و جو با قابلیت دسته بندی نتایج
 

به هنگام جست و جو، لیستی از موارد پیشنهادی به صورت دسته بندی شده نمایش داده می‌شود. 



جست و جوی پیشرفته کالا‌ها 

جست و جو بر اساس قیمت، گروه، کلمات کلیدی و مرتب سازی نتایج انجام می‌گیرد. همچنین نتایج جست و جو بدون رفرش شدن صفحه و به صورت AJAX ای به همراه تغییر URL صفحه صورت می‌گیرد. 



نمایش نمودار تغییرات قیمت 
 
امکان نمایش نمودار تغییرات قیمت کالا در بازه‌ی زمانی نیز پیش بینی شده است. 


ویرایش اطلاعات به صورت inline 
 
امکان ویرایش قیمت و تاریخ به صورت inline وجود دارد.



 

مدیریت تصاویر کالا

  
در این قسمت امکان آپلود همزمان چندین فایل به همراه پیش نمایش آن‌ها وجود دارد. همچنین امکان کشیدن و رها کردن برای تغییر ترتیب چیدمان عکس‌ها نیز مهیا است.( تصویر اول به عنوان کاور کالا در نظر گرفته می‌شود.)


 

قابلیت‌های دیگر:

  
- مدیریت تصاویر اسلایدشو و تغییر ترتیب آن‌ها از طریق کشیدن و رها کردن (drag & drop)
- تعریف برگه و تغییر ترتیب نمایش آن‌ها از طریق کشیدن و رها کردن
- امکان ارسال پست
- تعریف دسته بندی
- مدیریت کاربران
- تعریف تنظیمات سایت
- نمایش کالا و پست‌های مشابه


تصویر پنل مدیریت


تصویر صفحه‌ی اصلی:



همچنین به راحتی می‌توان با طراحی قالب جدیدی، از این سیستم برای کاری غیر از فروشگاه اینترنتی استفاده کرد؛ سایت‌های زیر نمونه‌های آنلاین دیگری از این سیستم هستند:

http://www.petrapars.ir 
http://www.ava-tarh.ir 

در نهایت فهرستی از کتاب خانه‌ها و فناوری‌های استفاده شده و همچنین مقالات مرتبط با این پروژه را قرار داده‌ام.

کتابخانه‌ها و فریم ورک‌های سمت سرور:

 فناوری یا کتابخانه   توضیحات
مقالات مرتبط
 ASP.NET MVC 5.x 
 فریم ورک و موتور اصلی سایت
-ASP.NET MVC 
-How to handle repeating form fields in ASP MVC 
-How to dynamically (via AJAX) add new items to a bound list model, in ASP MVC.NET  
 Entity Framework 6.x 
 فریم ورک دسترسی به داده
-Entity framework code-first 
-Update One-to-Many Entity using DBContext
-مدیریت اطلاعات وابسته به زمان در بانک‌های اطلاعاتی رابطه‌ای 
EFSecondLevelCache 
کش سطح دوم EF 6
 -بازنویسی سطح دوم کش برای Entity framework 6
 AutoMapper 
 نگاشت اطلاعات یک شی به شی دیگر به صورت خودکار  دوره AutoMapper 
خودکارسازی فرآیند نگاشت اشیاء در AutoMapper
 StructureMap 
 تزریق وابستگی‌ها 
-EF Code First #12
 MvcCheckBoxList 
 اضافه کردن CheckBoxList  به HtmlHelper

 DNTScheduler 
 برای انجام کارهای زمان بندی شده
-انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
 Lucene.Net 
 موتور جستجوی سایت  -جستجوی سریع و پیشرفته با لوسین Lucene.net
 AspNet.Identity 
 سیستم مدیریت کاربران
-اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
 ELMAH.MVC 
 کتابخانه ثبت وقایع و خطا‌های سیستم  -معرفی ELMAH
 PagedList 
 نمایش اطلاعات به صورت صفحه بندی شده

PersianDateTime 
جایگزینی است برای System.DateTime برای تاریخ‌های شمسی 
-PersianDateTime جایگزینی برای System.DateTime
T4MVC 
تعاریف Strongly typed مسیرها
-T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
Dynamic LINQ 
نوشتن کوئری‌های LINQ به صورت رشته ای
-انتخاب پویای فیلد‌ها در LINQ 
-فعال سازی و پردازش جستجوی پویای jqGrid در ASP.NET MVC

کتابخانه‌های جاوا اسکریپتی سمت کلاینت:

 فناوری یا کتابخانه
  توضیحات     مقالات مرتبط
 jQuery  کتاب خانه‌ی پایه جاوا اسکرپتی سایت
 -آموزش (jQuery) جی کوئری 
-آموزش JQuery Plugin و مباحث پیشرفته جی کوئری

 jQuery UI  ویجت‌های رابط کاربری
نمایش رکوردها به ترتیب اولویت به کمک jQuery UI sortable در ASP.NET MVC 
jQuery UI Sortable 
-Categorized search result with jQuery UI Autocomplete 
jQuery UI Slider 
-rtl jQuery UI Slider 
-jquery UI Sortable with table and tr width
jQuery Validation اعتبار سنجی سمت کلاینت
-مشکل اعتبار سنجی jQuery validator در Bootstrap tabs 
-نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Popover به کمک Twitter bootstrap
toastr نمایش پیام و اطلاع رسانی

PersianDatePicker یک DatePicker شمسی کم حجم
-PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
CKEDITOR ادیتور متن
-استفاده از ادیتور CKEditor در صفحات ASP.NET 
-یکپارچه سازی CKEditor با Lightbox 
Roxy Fileman مدیریت فایل ها  -افزونه مدیریت فایل‌های رایگان Roxy FileMan برای TinyMce و CkEditor  
Magnific Popup نمایش عکس‌ها به صورت پاپ آپ

Select2 تغییر شکل drop down list‌ها برای انتخاب گزینه‌ها

jqGrid v4.6 نمایش اطلاعات در قالب جدول
آموزش jqGrid
Bootstrap Star Rating امتیاز دهی ستاره ای
-پیاده سازی امتیاز دهی ستاره‌ای به مطالب به کمک jQuery در ASP.NET MVC
jQuery File Upload Plugin آپلود فایل به صورت AJAX ای

HIGHCHARTS نمایش نمودار

jQuery Number Plugin برای فرمت کردن اعداد

X-editable ویرایش اطلاعات به صورت inline
-قابل ویرایش کننده‌ی فوق العاده x-editable ؛ قسمت اول
bootstrap-confirmation نمایش فرم تایید در قالب popover

PathJS برای تغییر URL صفحه برای اعمال Ajax ای
-پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC

فریمورک‌های CSS:

فناوری یا کتابخانه 
 توضیحات
 مقالات مرتبط
 Bootstrap 3.x
 فریم ورک پایه ای css سایت
 - Bootstrap 3 RTL Theme 
Twitter Bootstrap 
-سازگارسازی کلاس‌های اعتبارسنجی Twitter Bootstrap 3 با فرم‌های ASP.NET MVC
-ساخت قالب‌های نمایشی و ادیتور دکمه سه وضعیتی سازگار با Twitter bootstrap در ASP.NET MVC 
-نمایش اخطارها و پیام‌های بوت استرپ به کمک TempData در ASP.NET MVC
 AdminLTE
 قالب مدیریت سایت
 - نسخه راستچین شده AdminLTE 2.2.1
Animate.css انیمیشن‌های css3 سایت

Font Awesome پک آیکون‌های برداری

Awesome Bootstrap Checkbox زیبا سازی چک باکس ها

فونت فارسی وزیر قلم فارسی