اشتراکها
اشتراکها
SQLite و پشتیبانی از CTE
SQLite compiled to javascript
For the impatients, try the demo here: http://kripken.github.io/sql.js/examples/GUI
sql.js is a port of SQLite to Webassembly, by compiling the SQLite C code with Emscripten. It uses a virtual database file stored in memory, and thus doesn't persist the changes made to the database. However, it allows you to import any existing sqlite file, and to export the created database as a javascript typed array.
نظرات مطالب
اجرای برنامههای ASP.NET توسط Mono در Ubuntu
- MySQL هم خوبه (البته مجوز استفادهاش رو هم باید مدنظر داشته باشید). هرچند از زمانیکه به تملک اوراکل درآمده، خیلی از شرکتها دارند به سمت MariaDB کوچ میکنند: ^ و ^
+ Oracle نسخه لینوکسی هم دارد.
- و برای کارهای سبک SQLite نسخه لینوکسی دارد.
+ Oracle نسخه لینوکسی هم دارد.
- و برای کارهای سبک SQLite نسخه لینوکسی دارد.
Blazor، چارچوبی است ارائه شده توسط مایکروسافت که به ما اجازه میدهد برنامههای تعاملی وب را با CSharp و بدون JavaScript بنویسیم. Blazor از اساس، Component based بوده و در برنامههایی که Backend نیز با CSharp نوشته شده باشد ( مثلا با ASP.NET Core) امکان به اشتراک گذاری کد بین کلاینت و سرور را نیز فراهم میکند. معماری Blazor معماریای مدرن است که در دل خود از امکانات CSharp نیز به خوبی بهره برده است تا بتوان پروژه را بهصورتی که قابلیت نگهداری بالایی داشته باشد، توسعه داد. Blazor در ذات معماری خود امکان نوشتن برنامههای Native موبایل را نیز میدهد و برای اثبات این مهم چندین دمو برای Proof of concept ارائه شده است؛ اما تا به امروز امکانی که به صورت Production ready باشد، ارائه نشده است.
Blazor به دو شکل کار میکند. Blazor Server و Blazor Client
در Blazor Client با استفاده از پشتیبانی نزدیک به 90% ای مرورگرها از Web Assembly که اجازه اجرای کدهای غیر JavaScript ای را در مرورگر میدهد، ابتدا DLLها به همراه HTML-CSS و عکسها و ... دانلود شده و برنامه به صورت Single Page App کار میکند. در این مدل شما میتوانید از تکنیک Pre rendering یا SSR نیز استفاده کنید تا تجربه کاربری بهتری را نیز ارائه دهید و یا در بحث SEO بهتر عمل کنید.
Blazor در سمت کلاینت از NET Standard 2.1 پشتیبانی میکند که عملا به شما اجازه میدهد بازهی گستردهای از Nuget Packageها را استفاده کنید.
البته Blazor Client به صورت Preview ارائه شده است و "فعلا" دو مشکل را دارد:
۱- امکان دیباگ خوبی ندارد.
۲- در عمل باید فایلهای اجرایی برنامه به صورت wasm یا web assembly دانلود شوند که در این حالت سرعتی بسیار بالا و بیش از جاوااسکریپت خواهند داشت؛ اما تا این لحظه این فایلها به صورت DLL دانلود میشوند و در سمت مرورگر "تفسیر" میشوند که باعث میشود سرعت گاه تا 70 برابر کمتر شود.
البته این دو مشکل در زمان ارائه نسخه NET 5. حل خواهند شد و در پروژه نسخه نهایی خود این دو مشکل را نخواهد داشت.
Blazor مدل اجرای دومی نیز دارد که به آن Blazor Server نیز میگوییم. در این مدل کدها تماما سمت سرور اجرا میشوند و تعاملات UI به صورت Web Socket به کلاینت منتقل و یا از آن دریافت میشوند که در این مدل، امکان Debug بدون کوچکترین مشکلی کار میکند و مشکلی از بابت کندی دانلود و اجرای DLLها ندارد. فقط چون کدها تماما سمت سرور اجرا میشوند، در زمانیکه ارتباط شبکهای، مناسب نباشد، میتوانیم در سناریوهای مختلف، شاهد لگ و کندی باشیم.
آیندهی Blazor در مدل اول آن تعریف شدهاست و در گذر زمان برای مدل Blazor Server، نمیتوان آیندهی زیادی را متصور بود. اما نکتهی مهم و جالب اینجاست که کد پروژه در هر دو مدل یکی است و فقط Configuration این دو از هم متفاوت است. پس اگر علاقمند به شروع به استفاده از Blazor هستید، یک راه این است که با مدل Blazor Server شروع و بعدا با تغییر Configuration، به Blazor Client سوئیچ کنید. البته در این میان باید به نکاتی دقت کنید:
۱- Blazor Server از NET Core 3.1. پشتیبانی میکند و Blazor Client از NET Standard 2.1. در مواقعی خاص ممکن است یک کلاس یا یک متد در NET Core 3.1. باشد و در NET Standard 2.1. نباشد.
۲- در Blazor Server شما حتی میتوانید با Entity Framework Core مثلا به Sql Server وصل شوید؛ ولی طبیعتا Browser امکان اتصال مستقیم به Sql Server را در مدل Client به شما نخواهد داد.
این موارد باعث میشود که اگر پروژه را با Blazor Server شروع کنید، شاید در آن کارهایی را انجام دهید که در BlazorServer کار میکنند، ولی در BlazorClient کار نمیکنند و این باعث شود بعدا که نسخه نهایی و کامل BlazorClient آماده شد، شاید نتوانید به آن، به صورت ساده و آسانی سوئیچ کنید.
ایده آل این است که پروژهای داشته باشید که به سادگی عوض کردن یک کلمه، بین این دو مدل سوئیچ کنید و بر صحت عملکرد پروژه در هر دو حالت نظارت داشته باشید. برای رسیدن به این مهم، پروژهای را ساختهام به نام BlazorDualMode که عمدهی بررسیها را به صورت اتوماتیک انجام میدهد و عملا تضمینی بر مهاجرت آسان شما از BlazorServer به BlazorClient در آینده خواهد بود
اگر در نت جستجو کنید، پروژههایی با این اسم را خواهید دید؛ اما این پروژه دو مزیت مهم را دارد که الباقی فاقد آن هستند:
۱- مدل Blazor Client آن دارای تکنیک Pre rendering یا SSR است.
۲- پروژه Api آن، از پروژهی Blazor آن جداست. اگر پروژهی Api را پروژهای بدانیم که به همراه Model، Data و ... امکان دسترسی به DbContext و دیتابیس را دارد، جدا بودن پروژهی Blazor باعث میشود که حتی در مدل Server آن نیز مجبور باشیم دیتا را از Api به صورت Rest Api call بگیریم و به واسطه پروژه Shared مابین Api و Blazor نهایت Dtoها و سایر کدهای مشترک را ببینیم.
البته در پروژه DualMode فقط پروژه Api درست شدهاست و ما کاری با جزئیات آن، اینکه مثلا CQRS میخواهیم یا نه، آیا میخواهیم لایهای کار کنیم و ... نداریم و فقط روی موارد مرتبط با Blazor متمرکز شدهایم.
در پروژه یک فایل داریم به نام Directory.build.props. زمانیکه چنین فایلی در فولدری قرار بگیرد، تمامی موارد نوشته شده در آن به صورت ضمنی در تمامی فایلهای csproj زیر مجموعه آن اعمال میشود.
در این فایل داریم:
<Project> <PropertyGroup> <BlazorMode>Client</BlazorMode> <DefineConstants Condition=" '$(BlazorMode)' == 'Client' ">$(DefineConstants);BlazorClient</DefineConstants> <DefineConstants Condition=" '$(BlazorMode)' == 'Server' ">$(DefineConstants);BlazorServer</DefineConstants> <LangVersion>8.0</LangVersion> </PropertyGroup> </Project>
در ادامه Define Constant کردهایم که اجازه میدهد کدهای CSharp دخیل در Configuration پروژه Blazor را شرطی کنیم. برای مثال بنویسیم:
#if BlazorClient ... #elif BlazorServer ... #endif
نسخه CSharp تمامی پروژهها نیز 8.0 قرار داده شده است.
پروژهی BlazorDualMode.Api آن پروژهای است که Api Controllerها در آن قرار میگیرند و همچنین در حالت Blazor Client، پروژه را برای مرورگرها ارائه یا Serve میکند. به واقع در این مدل، درخواستی که به هیچ Api Controller ای نرسد، به Blazor تحویل میشود. Blazor نیز ابتدا به دنبال Component ای میگردد که برای Route مربوطه نوشته شده باشد و آن را اجرا میکند و سپس Response آماده، به کلاینت ارسال میشود. در ادامه، مرورگر فایلهای DLL را دانلود و برنامه به صورت Single Page App به کار خود ادامه میدهد.
پروژه BlazorDualMode.Shared پروژهای است که کد مشترک بین Api و Blazor در آن قرار میگیرد. برای مثال میتوانید Dtoها را در این قسمت قرار دهید.
پروژه BlazorDualMode.Web پروژهای است که در آن Componentهای Blazor قرار دارند. در حالت Server این پروژه نیز قابلیت اجرا مییابد و باید با امکانات Visual Studio یا IDE دلخواه خود پروژه Web و Api را به صورت همزمان اجرا کنید تا به درستی کار کند.
فایلهای Program.cs، Startup.cs و همچنین خود csprojها و در نهایت فایل Host.cshtml، نهایت تفاوت این دو حالت بوده و کدهای بیزینسی پروژه و حتی Componentها و Api Controllerها در هر دو حالت یکی هستند. Configuration با شرط if server یا if client هندل شدهاند و درک جزئیات تنظیمات مربوطه نیاز به تسلط بر روی خود Blazor را دارد که از موضوع این پست خارج است؛ ولی در صورت داشتن هر گونه سوالی، میتوانید از قسمت پرسش و پاسخ همین سایت استفاده کنید.
در این مقاله نمیخواهیم به طور عمیقی وارد جزییاتی مثل توضیح Redis یا کش بشویم؛ فرض شدهاست که کاربر با این مفاهیم آشناست. به طور خلاصه کش کردن یعنی همیشه به دیتابیس یا هارددیسک برای گرفتن اطلاعاتی که میخواهیم و گرفتنش هم کند است، وصل نشویم و بجای آن، اطلاعات را در یک محل موقتی که گرفتنش خیلی سریعتر بوده قرار دهیم و برای استفاده به آنجا برویم و اطلاعات را با سرعت بالا بخوانیم. کش کردن هم دسته بندیهای مختلفی دارد که بر حسب سناریوهای مختلفی که وجود دارد، کاربرد خود را دارند. مثلا سادهترین کش در ASP.NET Core، کش محلی (In-Memory Cache) میباشد که اینترفیس IMemoryCache را اعمال میکند و نیازی به هیچ پکیجی ندارد و به صورت درونی در ASP.NET Core در دسترس است که برای حالت توسعه، یا حالتیکه فقط یک سرور داشته باشیم، مناسب است؛ ولی برای برنامههای چند سروری، نوع دیگری از کش که به اصطلاح به آن Distributed Cache میگویند، بهتر است استفاده شود. چند روش برای پیادهسازی با این ساختار وجود دارد که نکته مشترکشان اعمال اینترفیس واحد IDistributedCache میباشد. در نتیجهی آن، تغییر ساختار کش به روشهای دیگر، که اینترفیس مشابهی را اعمال میکنند، با کمترین زحمت صورت میگیرد. این روشها به طور خیلی خلاصه شامل موارد زیر میباشند:
سپس اینترفیس IResponseCacheService را میسازیم تا از این اینترفیس به جای IDistributedCache استفاده کنیم. البته میتوان از IDistributedCache به طور مستقیم استفاده کرد؛ ولی چون همهی ویژگیهای این اینترفیس را نمیخواهیم و هم اینکه میخواهیم serialize کردن نتایج API را در کلاسی که از این اینترفیس ارثبری میکند (ResponseCacheService) بیاوریم (تا آن را کپسولهسازی (Encapsulation) کرده باشیم تا بعدا بتوانیم مثلا بجای پکیج Newtonsoft.Json، از System.Text.Json برای serialize کردنها استفاده کنیم):
در این کلاس، تزریق وابستگیهای IResponseCacheService و RedisCacheSettings به روش خاصی انجام شده است و نمیتوانستیم از روش Constructor Dependency Injection استفاده کنیم چون در این حالت میبایستی این ورودی در Controller مورد استفاده هم تزریق شود و سپس در اتریبیوت [Cached] بیاید که مجاز به اینکار نیستیم؛ بنابراین از این روش خاص استفاده کردیم. مورد دیگر فرمول ساخت کلید کش میباشد تا بتواند کش بودن یک Endpoint خاص را به طور خودکار تشخیص دهد که این متد در همین کلاس آمده است.
1- Distributed Memory Cache: در واقع Distributed نیست و کش معمولی است؛ فقط برای اعمال اینترفیس IDistributedCache که امکان تغییر آن در ادامهی توسعه نرمافزار میسر باشد، این روش توسط مایکروسافت اضافه شدهاست. نیاز به نصب پکیجی را ندارد و به صورت توکار در ASP.NET Core در دسترس است.
2- Distributed SQL Server Cache: کاربرد چندانی ندارد. با توجه به اینکه هدف اصلی از کش کردن، افزایش سرعت و عدم اتصال به دیتابیس است، استفاده از حافظهی رم، بجای دیتابیس ترجیح داده میشود.
3- Distributed Redis Cache: استفاده از Redis که به طور خلاصه یک دیتابیس Key/Value در حافظه است. سرعت بالایی دارد و محبوبترین روش بین برنامهنویسان است. برای اعمال آن در ASP.NET Core نیاز به نصب پکیج میباشد.
موارد بالا انواع زیرساخت و ساختار (Cache Provider) برای پیادهسازی کش میباشند. روشهای مختلفی برای استفاده از این Cache Providerها وجود دارد. مثلا یک روش، استفاده مستقیم در کدهای درونی متد یا کلاسمان میباشد و یا در روش دیگر میتوانیم به صورت یک Middleware این پروسه را مدیریت کنیم، یا در روش دیگر (که موضوع این مقاله است) از ActionFilterAttribute استفاده میکنیم. یکی از روشهای جالب دیگر کش کردن، اگر از Entity Framework به عنوان ORM استفاده میکنیم، استفاده از سطح دوم کش آن (EF Second Level Cache) میباشد. EF دو سطح کش دارد که سطح اول آن توسط خود Context به صورت درونی استفاده میشود و ما میتوانیم از سطح دوم آن استفاده کنیم. مزیت آن به نسبت روشهای قبلی این است که نتیجهی کوئری ما (که با عبارات لامبدا نوشته میشود) را کش میکند و علاوه بر امکان تنظیم زمان انقضا برای این کش، در صورت تغییر یک entity خاص (انجام عملیات Update/Insert/Delete) خود به خود، کش کوئری مربوط به آن entity پاک میشود تا با مقدار جدید آن جایگزین شود که روشهای دیگر این مزیت را ندارند. در این مقاله قرار نیست در مورد این روش کش صحبت کنیم. استفاده از این روش کش به صورت توکار در EF Core وجود ندارد و برای استفاده از آن در صورتی که از EF Core قبل از ورژن 3 استفاده میکنید میتوانید از پکیج EFSecondLevelCache.Core و در صورت استفاده از EF Core 3 از پکیج EF Core Second Level Cache Interceptor استفاده نمایید که در هر دو حالت میتوان هم از Memory Cache Provider و هم از Redis Cache Provider استفاده نمود.
در این مقاله میخواهیم Responseهای APIهایمان را در یک پروژهی Web API، به سادهترین حالت ممکن کش کنیم. زیرساخت این کش میتواند هر کدام از موارد ذکر شدهی بالا باشد. در این مقاله از Redis برای پیادهسازی آن استفاده میکنیم که با نصب پکیج Microsoft.Extensions.Caching.StackExchangeRedis انجام میگیرد. این بستهی نیوگت که متعلق به مایکروسافت بوده و روش پایهی استفاده از Redis در ASP.NET Core است، اینترفیس IDistributedCache را اعمال میکند:
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
public interface IResponseCacheService { Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive); Task<string> GetCachedResponseAsync(string cacheKey); }
یادآوری: Redis قابلیت ذخیرهی دادههایی از نوع آرایهی بایتها را دارد (و نه هر نوع دلخواهی را). بنابراین اینجا ما بجای ذخیرهی مستقیم نتایج APIهایمان (که ممکن نیست)، میخواهیم ابتدا آنها را با serialize کردن به نوع رشتهای (که فرمت json دارد) تبدیل کنیم و سپس آن را ذخیره نماییم.
حالا کلاس ResponseCacheService که این اینترفیس را اعمال میکند میسازیم:
public class ResponseCacheService : IResponseCacheService, ISingletonDependency { private readonly IDistributedCache _distributedCache; public ResponseCacheService(IDistributedCache distributedCache) { _distributedCache = distributedCache; } public async Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive) { if (response == null) return; var serializedResponse = JsonConvert.SerializeObject(response); await _distributedCache.SetStringAsync(cacheKey, serializedResponse, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = timeToLive }); } public async Task<string> GetCachedResponseAsync(string cacheKey) { var cachedResponse = await _distributedCache.GetStringAsync(cacheKey); return string.IsNullOrWhiteSpace(cachedResponse) ? null : cachedResponse; } }
دقت کنید که اینترفیس IDistributedCache در این کلاس استفاده شده است. اینترفیس ISingletonDependency صرفا یک اینترفیس نشان گذاری برای اعمال خودکار ثبت سرویس به صورت Singleton میباشد (اینترفیس را خودمان ساختهایم و آن را برای رجیستر راحت سرویسهایمان تنظیم کردهایم). اگر نمیخواهید از این روش برای ثبت این سرویس استفاده کنید، میتوانید به صورت عادی این سرویس را رجیستر کنید که در ادامه، در قسمت مربوطه به صورت کامنت شده آمده است.
حالا کدهای لازم برای رجیستر کردن Redis و تنظیمات آن را در برنامه اضافه میکنیم. قدم اول ایجاد یک کلاس POCO به نام RedisCacheSettings است که به فیلدی به همین نام در appsettings.json نگاشت میشود:
public class RedisCacheSettings { public bool Enabled { get; set; } public string ConnectionString { get; set; } public int DefaultSecondsToCache { get; set; } }
این فیلد را در appsettings.json هم اضافه میکنیم تا در استارتاپ برنامه، با مپ شدن به کلاس RedisCacheSettings، قابلیت استفاده شدن در تنظیمات Redis را داشته باشد.
"RedisCacheSettings": { "Enabled": true, "ConnectionString": "192.168.1.107:6379,ssl=False,allowAdmin=True,abortConnect=False,defaultDatabase=0,connectTimeout=500,connectRetry=3", "DefaultSecondsToCache": 600 },
حالا باید سرویس Redis را در متد ConfigureServices، به همراه تنظیمات آن رجیستر کنیم. میتوانیم کدهای مربوطه را مستقیم در متد ConfigureServices بنویسیم و یا به صورت یک متد الحاقی در کلاس جداگانه بنویسیم و از آن در ConfigureServices استفاده کنیم و یا اینکه از روش Installer برای ثبت خودکار سرویس و تنظیماتش استفاده کنیم. اینجا از روش آخر استفاده میکنیم. برای این منظور کلاس CacheInstaller را میسازیم:
public class CacheInstaller : IServiceInstaller { public void InstallServices(IServiceCollection services, AppSettings appSettings, Assembly startupProjectAssembly) { var redisCacheService = appSettings.RedisCacheSettings; services.AddSingleton(redisCacheService); if (!appSettings.RedisCacheSettings.Enabled) return; services.AddStackExchangeRedisCache(options => options.Configuration = appSettings.RedisCacheSettings.ConnectionString); // Below code applied with ISingletonDependency Interface // services.AddSingleton<IResponseCacheService, ResponseCacheService>(); } }
خب تا اینجا اینترفیس اختصاصی خودمان را ساختیم و Redis را به همراه تنظیمات آن، رجیستر کردیم. برای اعمال کش، چند روش وجود دارد که همانطور که گفته شد، اینجا از روش ActionFilterAttribute استفاده میکنیم که یکی از راحتترین راههای اعمال کش در APIهای ماست. کلاس CachedAttribute را ایجاد میکنیم:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class CachedAttribute : Attribute, IAsyncActionFilter { private readonly int _secondsToCache; private readonly bool _useDefaultCacheSeconds; public CachedAttribute() { _useDefaultCacheSeconds = true; } public CachedAttribute(int secondsToCache) { _secondsToCache = secondsToCache; _useDefaultCacheSeconds = false; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var cacheSettings = context.HttpContext.RequestServices.GetRequiredService<RedisCacheSettings>(); if (!cacheSettings.Enabled) { await next(); return; } var cacheService = context.HttpContext.RequestServices.GetRequiredService<IResponseCacheService>(); // Check if request has Cache var cacheKey = GenerateCacheKeyFromRequest(context.HttpContext.Request); var cachedResponse = await cacheService.GetCachedResponseAsync(cacheKey); // If Yes => return Value if (!string.IsNullOrWhiteSpace(cachedResponse)) { var contentResult = new ContentResult { Content = cachedResponse, ContentType = "application/json", StatusCode = 200 }; context.Result = contentResult; return; } // If No => Go to method => Cache Value var actionExecutedContext = await next(); if (actionExecutedContext.Result is OkObjectResult okObjectResult) { var secondsToCache = _useDefaultCacheSeconds ? cacheSettings.DefaultSecondsToCache : _secondsToCache; await cacheService.CacheResponseAsync(cacheKey, okObjectResult.Value, TimeSpan.FromSeconds(secondsToCache)); } } private static string GenerateCacheKeyFromRequest(HttpRequest httpRequest) { var keyBuilder = new StringBuilder(); keyBuilder.Append($"{httpRequest.Path}"); foreach (var (key, value) in httpRequest.Query.OrderBy(x => x.Key)) { keyBuilder.Append($"|{key}-{value}"); } return keyBuilder.ToString(); } }
حالا ما میتوانیم با استفاده از attributeی به نام [Cached] که روی APIهای از نوع HttpGet قرار میگیرد آنها را براحتی کش کنیم. کلاس بالا هم طوری طراحی شده (با دو سازنده متفاوت) که در حالت استفاده به صورت [Cached] از مقدار زمان پیشفرضی استفاده میکند که در فایل appsettings.json تنظیم شده است و یا اگر زمان خاصی را مد نظر داشتیم (مثال 1000 ثانیه) میتوانیم آن را به صورت [(Cached(1000] بیاوریم. کلاس زیر نمونهی استفادهی از آن میباشد:
بنابراین وقتی تنظیمات اولیه، برای پیادهسازی این کش انجام شود، اعمال کردن آن به سادگی قرار دادن یک اتریبیوت سادهی [Cached] روی هر apiی است که بخواهیم خروجی آن را کش کنیم. فقط توجه نمایید که این روش فقط برای اکشنهایی که کد 200 را بر میگردانند، یعنی متد Ok را return میکنند (OkObjectResult) کار میکند. بعلاوه اگر از اتریبیوت ApiResultFilter یا مفهوم مشابه آن برای تغییر خروجی API به فرمت خاص استفاده میکنید، باید در آن تغییرات کوچکی را انجام دهید تا با این حالت هماهنگ شود.
[Cached] [HttpGet] public IActionResult Get() { var rng = new Random(); var weatherForecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); return Ok(weatherForecasts); }