اگر SQL Server و MySQL بر روی سیستم شما نصب است، روشی ساده برای انتقال اطلاعات بین این دو وجود دارد که نیازی به دخالت هیچ نوع برنامهی جانبی نداشته و با امکانات موجود قابل مدیریت است.
ایجاد یک Linked server
برای اینکه SQL Server را به MySQL متصل کنیم میتوان بین این دو یک Linked server تعریف کرد و سپس دسترسی به بانکهای اطلاعاتی MySQL همانند یک بانک اطلاعاتی محلی SQL Server خواهد شد که شرح آن در ادامه ذکر میشود.
ابتدا نیاز است تا درایور ODBC مربوط به MySQL دریافت و نصب شود. آنرا میتوانید از اینجا دریافت کنید : (+)
سپس management studio را گشوده و در قسمت Server objects ، بر روی گزینهی Linked servers کلیک راست نمائید. از منوی ظاهر شده، گزینهی New linked server را انتخاب کنید:
در ادامه، باید تنظیمات زیر را در صفحهی باز شده وارد کرد:
در قسمت Linked server و Product name ، نام دلخواهی را وارد کنید.
Provider انتخابی باید از نوع Microsoft OLE DB Provider for ODBC Drivers باشد.
مهمترین تنظیم آن، قسمت Provider string است که باید به صورت زیر وارد شود (در غیر اینصورت کار نمیکند):
DRIVER={MySQL ODBC 5.1 Driver}; SERVER=localhost; DATABASE=testdb; USER=root; PASSWORD=mypass; OPTION=3;PORT=3306; CharSet=UTF8;
پس از انجام این تنظیمات بر روی دکمهی Ok کلیک کنید تا Linked server ساخته شود:
اگر لیست بانکهای اطلاعاتی را مشاهده نمودید، یعنی اتصال به درستی برقرار شده است.
تنظیمات ثانویه:
تا اینجا اس کیوال سرور به MySQL متصل شده است، اما برای استفاده بهینه از امکانات موجود نیاز است تا یک سری تغییرات دیگر را هم اعمال کرد.
تنظیم MSDASQL Provider :
در همان قسمت Linked provider ، ذیل قسمت Providers ، گزینهی MSDASQL را انتخاب کرده و بر روی آن کلیک راست نمائید. سپس صفحهی خواص آنرا انتخاب کنید تا بتوان تنظیمات زیر را به آن اعمال کرد. این پروایدر جهت اتصال به MySQL مورد استفاده قرار میگیرد.
فعال سازی RPC :
برای اینکه بتوان از طریق SQL Server رکوردی را در یکی از جداول بانکهای اطلاعاتی MySQL متصل شده ثبت نمود، میتوان از دستور زیر استفاده کرد:
EXECUTE('insert into testdb.testtable(f1,f1) values(1,''data'')') at mysql
اینجا testdb نام بانک اطلاعاتی اتصالی MySQL است و testTable هم نام جدول مورد نظر. MySQL ایی که در آخر عبارت ذکر شده همان نام linked server ایی است که پیشتر تعریف کردیم.
به محض سعی در اجرای این کوئری خطای زیر ظاهر میشود:
Server 'mysql' is not configured for RPC.
برای رفع این مشکل، مجددا به صفحهی خواص همان liked server ایجاد شده مراجعه کنید. در قسمت Server options دو گزینه مرتبط به RPC باید فعال شوند:
و اکنون برای کوئری گرفتن از اطلاعات ثبت شده هم از عبارت زیر میتوان استفاده کرد:
SELECT * FROM OPENQUERY(mysql, 'SELECT * FROM testdb.testtable')
در این کوئری، MySQL نام Linked server ثبت شده است و testdb هم یکی از بانکهای اطلاعاتی MySQL مورد نظر.
انتقال تمام اطلاعات یک جدول از بانک اطلاعاتی MySQL به SQL Server
پس از برقراری اتصال، اکنون import کامل یک جدول MySQL به SQL Server به سادگی اجرای کوئری زیر میباشد:
SELECT * INTO MyDb.dbo.testtable FROM openquery(MYSQL, 'SELECT * FROM testdb.testtable')
در این کوئری، MySQL همان Linked server تعریف شده است. MyDB نام بانک اطلاعاتی موجود در SQL Server جاری است و testtable هم جدولی است که قرار است اطلاعات testdb.testtable بانک اطلاعاتی MySQL به آن وارد شود.
با اطلاعات فارسی هم (در سمت SQL Server) مشکلی ندارد. همانطور که مشخص است، در اطلاعات provider string ذکر شده، مقدار charset به utf8 تنظیم شده و همچنین اگر نوع collation فیلدهای تعریف شده در MySQL نیز به utf8_persian_ci تنظیم شده باشد، با مشکل ثبت اطلاعات فارسی به صورت ???? مواجه نخواهید شد.
نکته:
اگر بانک اطلاعاتی MySQL شما بر روی local host نصب نیست، جهت فعال سازی دسترسی ریموت به آن، میتوان به یکی از نکات زیر مراجعه کرد و سپس این اطلاعات جدید باید در همان قسمت provider string مرتبط با تعریف linked server وارد شوند:
مطالب مشابه:
هیچکدام از این روشها قابل استفاده نبودند چون provider string صحیحی را نهایتا تولید نمیکنند. همچنین تمام این روشها مبتنی است بر ایجاد DSN در کنترل پنل که اصلا نیازی به آن نیست و اضافی است.
netsh http add sslcert ipport=0.0.0.0:6001 certhash=<thumbprint of certificate> appid={new guid here} certstorename=Root
SSL Certificate add failed, Error: 1312 A specified logon session does not exist. It may already have been terminated.
یکی کردن و فشرده سازی فایلهای استاتیک در ASP.NET Core
هدف از یکی کردن و فشرده سازی فایلهای استاتیک مانند اسکریپتها و فایلهای CSS، بهبود کارآیی برنامه با کاهش حجم نهایی ارائهی آن و همچنین کاهش تعداد رفت و برگشتهای به سرور برای دریافت فایلهای متعدد مرتبط به آن است. در عملیات Bundling، چندین فایل، به یک تک فایل تبدیل میشوند تا اتصالات مرورگر به وب سرور، جهت دریافت آنها به نحو چشمگیری کاهش پیدا کند و در عملیات Minification، مراحل متعددی بر روی کدهای نوشته شده صورت میگیرد تا حجم نهایی آنها کاهش پیدا کنند. مایکروسافت در ASP.NET Core RTM، ابزاری را به نام BundlerMinifier.Core جهت برآورده کردن این اهداف ارائه کردهاست. بنابراین اولین قدم، نصب وابستگیهای آن است.
برای اینکار یک سطر ذیل را به فایل project.json اضافه کنید. این بسته باید به قسمت tools اضافه شود تا قابلیت فراخوانی از طریق خط فرمان را نیز پیدا کند:
"tools": { "BundlerMinifier.Core": "2.1.258" },
No executable found matching command "dotnet-bundle" Version for package `BundlerMinifier.Core` could not be resolved.
اسکریپت نویسی برای کار با BundlerMinifier.Core
روشهای زیادی برای کار با ابزار BundlerMinifier.Core وجود دارند؛ منجمله انتخاب فایلها در solution explorer و سپس کلیک راست بر روی فایلهای انتخاب شده و انتخاب گزینهی bundler & minifier برای یکی کردن و فشرده سازی خودکار این فایلها. برای این منظور افزونهی Bundler & Minifier را نیاز است نصب کنید.
اما روشی که قابلیت خودکارسازی را دارد، استفاده از فایل ویژهی bundleconfig.json این ابزار است. برای این منظور فایل جدید bundleconfig.json را به ریشهی پروژه اضافه کرده و سپس محتوای ذیل را به آن اضافه کنید:
[ { "outputFileName": "wwwroot/css/site.min.css", "inputFiles": [ "wwwroot/css/site.css" ] }, { "outputFileName": "wwwroot/js/site.min.js", "inputFiles": [ "bower_components/jquery/dist/jquery.min.js", "bower_components/jquery-validation/dist/jquery.validate.min.js", "bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" ], "minify": { "enabled": true, "renameLocals": true }, "sourceMap": false } ]
با ذخیره سازی این فایل، کار یکی سازی و فشرده کردن مداخل آن به صورت خودکار صورت خواهد گرفت.
خودکار سازی فرآیند یکی کردن و فشرده سازی فایلهای استاتیک
برای خودکار سازی این فرآیند، میتوان به صورت زیر عمل کرد. فایل project.json را گشوده و قسمت scripts آنرا به نحو ذیل تغییر دهید:
"scripts": { "precompile": [ "dotnet bundle" ], "prepublish": [ "bower install" ], "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }
یک نکته: به منوی Build گزینهی Update all bundles نیز با نصب افزونهی Bundler & Minifier اضافه میشود. همچنین اگر از منوی Tools گزینهی Task runner explorer را انتخاب کنید، فایل bundleconfig.json توسط آن شناسایی شده و گزینهی update all files را نیز در اینجا مشاهده خواهید کرد.
ساده سازی تعاریف فایل Layout برنامه
در یک چنین حالتی دیگر نباید در فایل layout شما، ارجاعات مستقیمی به پوشهی مثلا bower_components وجود داشته باشند و یا در کلاس آغازین برنامه، نیازی نیست تا این پوشه را عمومی کنید. لیست مداخلی را که نیاز دارید، به ترتیب از پوشههای مختلفی تهیه و در فایل bundleconfig.json ذکر کنید تا یکی شده و خروجی js/site.min.js را تشکیل دهند. این مورد تنها مدخلی است که نیاز است در فایل layout برنامه ذکر شود (بجای چندین و چند مدخل مورد نیاز):
<script src="~/js/site.min.js" asp-append-version="true" type="text/javascript"></script>
- Configuration
- Routing
- MVC
- Application
- و ...
- کاربر یک درخواست Http را توسط مرورگر ارسال میکند.
- یکی از اولین میان افزارها یعنی میان افزار Routing، آدرس درخواست را میخواند، کنترلر و اکشن مورد نظر را مییابد و بهوسیلهی Activator Utility، سعی در فعال سازی آن کنترلر میکند.
- DI Container لیست پارامترهای سازندهی کنترلر را مشاهده میکند و سرویسهای مورد نیاز را از درون خود واکشی کرده، از آنها نمونه سازی میکند و نمونههای ساخته شده را به درون شیء کنترلر تزریق میکند.
- Routing درخواست HttpRequest را تجزیه کرده و اکشن متد مورد نظر را برای اجرای آن فراخوانی کرده
- و نتیجهی اجرای اکشن را به درخواست دهنده بر میگرداند.
هر چند که کنترلرها درون DI Container ثبت نشدهاند، ولی توسط کلاسهایی درون فریم ورک، از آنها نمونه سازی میشود و در حین نمونه سازی، DI Container سرویسهای مورد نظر آنها را در صورت وجود، فراهم میکند.
ثبت تنظیمات وبسایت و فراخوانی آنها در برنامه
در تمام برنامههای ASP.NET Core شما نیاز به تنظیماتی برای پیکربندی کار برنامهی خود دارید. این تنظیمات میتوانند شامل Connection String اتصال به پایگاه داده، تنظیمات اتصال به سرویسهای خارجی مثل درگاههای پرداخت آنلاین بانکها و ... باشند. در اینجا ما تنظیمات اختصاصی را درون فایل AppSetting اضافه میکنیم. بعد برای هر بخش از تنظیمات، در پوشهی Configs یک کلاس سادهی سی شارپ را میسازیم و سپس با گرفتن و تزریق کردن این فایلهای Config درون DI Container، هر زمانی خواستیم، از آنها استفاده میکنیم.
ابتدا به سراغ تنظیمات کلی میرویم و دو تنظیم نام برنامه و پیغام خوش آمد گویی را به برنامه اضافه میکنیم (فایل appSettings را به صورت زیر تغییر میدهیم) :
"ApplicationName": "Dependency Injection Demo", "GreetingMessage": "Welcome to Dependency Injection Demo", "AllowedHosts": "*", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } },
برای سادگی کار، با بخش Logging
کاری نداریم . اکنون فایل AppConfig.cs را به برنامه اضافه میکنیم:
namespace AspNetCoreDependencyInjection.Configs { public class AppConfig { public string ApplicationName { get; set; } public string GreetingMessage { get; set; } public string AllowedHosts { get; set; } } }
برای دسترسی بهتر میتوانیم سازندهی کلاس Startup را تغییر دهیم:
public IWebHostEnvironment Environment { get; } public IConfiguration Configuration { get; } public IServiceCollection Services { get; set; } public Startup(IWebHostEnvironment environment) { var builder = new ConfigurationBuilder() .SetBasePath(environment.ContentRootPath) .AddJsonFile("appsettings.json", optional: true) .AddEnvironmentVariables(); this.Environment = environment; this.Configuration = builder.Build(); }
var appSettingsFile = environment.IsProduction() ? "appsettings.json" : "appsettings_dev.json"; var builder = new ConfigurationBuilder() .SetBasePath(environment.ContentRootPath) .AddJsonFile( appSettingsFile , optional: true) .AddEnvironmentVariables();
services.AddSingleton(services => new AppConfig { ApplicationName = this.Configuration["ApplicationName"], GreetingMessage = this.Configuration["GreetingMessage"], AllowedHosts = this.Configuration["AllowedHosts"] });
در کد بالا در هنگام اجرای برنامه، یک نمونه از کلاس AppConfig را با طول حیات Singleton ثبت کردیم و Property های این شیء را به وسیلهی ایندکس Configuration[“FieldName”]، تک تک پر کردیم.
حالا میتوانیم سرویس AppConfig را
در هر کلاسی از برنامهی خودمان تزریق و از آن استفاده کنیم. برای مثل در اینجا یک
کنترلر به نام AppSettingsController ساختم و کلاس فوق را به آن تزریق کردم:
public class AppSettingsController : Controller { private readonly AppConfig _appConfig; public AppSettingsController(AppConfig appConfig) { _appConfig = appConfig; } // codes here … }
می توانیم از همین الگو برای تعریف، ثبت و استفاده از سایر تنظیمات نیز استفاده کنیم:
"UserOptionConfig": { "UsersAvatarsFolder": "avatars", "UserDefaultPhoto": "icon-user-default.png", "UserAvatarImageOptions": { "MaxWidth": 150, "MaxHeight": 150 } }, "LiteDbConfig": { "ConnectionString": "Filename=\\Data\\DependencyInjectionDemo.db;Connection=direct;Password=@123456;" }
برای LiteDbConfig
مانند AppConfig عمل میکنیم، ولی در هنگام ثبت آن، به روش زیر عمل میکنیم. تنها تفاوتی
که وجود دارد، نحوهی دستیابی به فیلدهای درونی فایل JSON به وسیلهی شیء Configuration
است:
services.AddSingleton(services => new LiteDbConfig { ConnectionString = this.Configuration["LiteDbConfig:ConnectionString"], });
اکنون برای استفادهی از مدخل UserOptionConfig،
کلاسهای زیر را میسازیم:
namespace AspNetCoreDependencyInjection.Configs { public class UserOptionConfig { public string UsersAvatarsFolder { get; set; } public string UserDefaultPhoto { get; set; } public UserAvatarImageOptions UserAvatarImageOptions { get; set; } } public class UserAvatarImageOptions { public int MaxHeight { get; set; } public int MaxWidth { get; set; } } }
جداسازی بخشهای مختلف تنظیمات پیکربندی باعث میشود تا بتوانیم دو اصل اساسی از طراحی نرم افزار را رعایت کنیم :
- Interface Segregation Principle (ISP) or Encapsulation : کلاسهایی که به تنظیمات نیاز دارند، فقط به آن بخشی از تنظیمات دسترسی خواهند داشتند که واقعا مورد نیازشان باشد.
- Separation Of Concerns : تنظیمات بخشهای مختلف برنامه، به یکدیگر وابسته و جفت شده نیستند.
در اینجا نیاز به استفاده از پکیج Microsoft.Extensions.Options.ConfigurationExtensions را داریم که به صورت درونی در ASP.NET Core تعبیه شده است.
برای ثبت این تنظیمات درون DI Container، از نمونهی جنریک متد Configure در IServiceCollection به صورت زیر استفاده میکنیم:
services.Configure<UserOptionConfig>(this.Configuration.GetSection("UserOptionConfig"));
متد GetSection بر اساس نام بخش تنظیمات، خود آن تنظیم و تمامی تنظیمات درونی آن را به صورت یک IConfigurationSection بر میگرداند و متد Configure<TOption> یک IConfiguration را گرفته و به صورت خودکار به TOption اتصال میدهد و سپس این شیء را درون DI Container به عنوان یک IConfigurationOptions<TOption> و با طول حیات Singleton ثبت میکند.
برای دسترس به UserOptionConfig درون کلاس مورد نظر ما، اینترفیس <IOptionMonitor<TOption را به سازندهی کلاس مورد نظر تزریق میکنیم. کد زیر را که نسخهی تغییر یافتهی کلاس AppSettingsController است را مشاهده کنید:private readonly LiteDbConfig _liteDbConfig; private readonly AppConfig _appConfig; private readonly UserOptionConfig _userOptionConfig; public AppSettingsController(AppConfig appConfig , LiteDbConfig liteDbConfig , IOptionsMonitor<UserOptionConfig> userOptionConfig) { _appConfig = appConfig; _liteDbConfig = liteDbConfig; _userOptionConfig = userOptionConfig.CurrentValue; }
نکته ای که وجود دارد، کلاسهای تعریف شده برای استفادهی از این الگو باید شرایط زیر را داشته باشند ( مثل کلاس UserOptionConfig ) :
- باید سطح دسترسی public داشته باشند.
- باید دارای سازندهی پیش فرض باشند.
- باید نام Property های آنها دقیقا همنام فیلدهای تنظیمات باشد تا فرایند mapping خودکار به درستی انجام شود.
- باید Property ها و Setter آنها ، سطح دسترسی public داشته باشند.
هر دو روش بالا که یکی به
صورت عادی تنظیمات را ثبت میکند و دیگری با استفاده از Option Pattern بخشهای مختلف را ثبت میکند،
مناسب هستند. البته گاهی اوقات فایلهای تنظیمات پروژهی شما در لایههای زیرین (یا درونیتر اگر از onion architecture استفاده میکنید) قرار دارند و شما نمیخواهید
در آن لایهها و لایههای درونیتر، وابستگی به پکیجهای ASP.NET Core ایجاد کنید. در این حالت با در
نظر گرفتن دو اصل ISP و Separation of Concerns ،
به ازای هر بخش مختلف از تنظیمات، فایلهای تنظیمات را در لایههای زیرین/درونی
تعریف کرده، بعد در لایههای بالاتر/بیرونیتر آنها را به درون سرویسها یا کلاسهای مورد نیاز، تزریق کنید. البته مثل همین مثال، ثبت این سرویسها درون برنامهی ASP.NET Core که
معمولا بالاترین/بیرونیترین لایه از پروژهی ما هست، انجام میشود.
- نصب پایتون 2.5 یا 2.6 یا 2.7 که فعلا در سایت آن، نسخهی 2.7 در دسترس هست. توجه داشته باشید که هنوز برای نسخهی 3 پایتون پشتیبانی صورت نگرفته است.
- آخرین نسخهی sdk را هم میتوانید از این آدرس به صورت zip و یا از این آدرس به صورت tar دانلود کنید و در صورتیکه دوست دارید به سورس آن دسترسی داشته باشید یا اینکه از سورسهای مشارکت شده یا غیر رسمی استفاده کنید، از این صفحه آن را دریافت کنید.
(C:\Users\aym\Downloads\addon-sdk-1.17) C:\Users\aym\Downloads\addon-sdk-1.17\bin>
source bin/activate
bash bin/activate
(addon-sdk)~/mozilla/addon-sdk >
آغاز به کار
mkdir fxaddon cd fxaddon cfx init
* lib directory created * data directory created * test directory created * doc directory created * README.md written * package.json written * test/test-main.js written * lib/main.js written * doc/main.md written Your sample add-on is now ready for testing: try "cfx test" and then "cfx run". Have fun!"
{ "name": "fxaddon", "title": "fxaddon", "id": "jid1-QfyqpNby9lTlcQ", "description": "a basic add-on", "author": "", "license": "MPL 2.0", "version": "0.1" }
{ "name": "dotnettips", "title": ".net Tips Updater", "id": "jid1-QfyqpNby9lTlcQ", "description": "This extension keeps you updated on current activities on dotnettips.info", "author": "yeganehaym@gmail.com", "license": "MPL 2.0", "version": "0.1" }
var button= require('sdk/ui/button/action');
buttons.ActionButton({...});
var tgbutton = require('sdk/ui/button/toggle'); var panels = require("sdk/panel"); var self = require("sdk/self"); var button = tgbutton.ToggleButton({ id: "updaterui", label: ".Net Updater", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onChange: handleChange }); var panel = panels.Panel({ contentURL: self.data.url("./popup.html"), onHide: handleHide }); function handleChange(state) { if (state.checked) { panel.show({ position: button }); } } function handleHide() { button.state('window', {checked: false}); }
tgbutton.ToggleButton
require('sdk/ui/button/toggle').ToggleButton
Context Menus
var contextMenu = require("sdk/context-menu"); var home = contextMenu.Item({ label: "صفحه اصلی", data: "https://www.dntips.ir/" }); var postsarchive = contextMenu.Item({ label: "مطالب سایت", data: "https://www.dntips.ir/postsarchive" }); var menuItem = contextMenu.Menu({ label: "Open .Net Tips", context: contextMenu.PageContext(), items: [home, postsarchive], image: self.data.url("icon-16.png"), contentScript: 'self.on("click", function (node, data) {' + ' window.location.href = data;' + '});' });
SelectorContext("img")
SelectorContext("img,a[href]")
var tabs = require("sdk/tabs"); var menuItem = contextMenu.Menu({ label: "Open .Net Tips", context: contextMenu.PageContext(), items: [home, postsarchive], image: self.data.url("icon-16.png"), contentScript: 'self.on("click", function (node, data) {' + ' self.postMessage(data);' + '});', onMessage: function (data) { tabs.open(data); } });
var Url="https://www.dntips.ir/search?term="; var searchMenu = contextMenu.Item({ label: "search for", context: [contextMenu.PredicateContext(checkText),contextMenu.SelectionContext()], image: self.data.url("icon-16.png"), contentScript: 'self.on("click", function () {' + ' var text = window.getSelection().toString();' + ' if (text.length > 20)' + ' text = text.substr(0, 20);' + ' self.postMessage(text);'+ '})', onMessage: function (data) { tabs.open(Url+data); } }); function checkText(data) { if(data.selectionText === null) return false; console.log('selectionText: ' + data.selectionText); //handle showing or hiding of menu items based on the text content. menuItemToggle(data.selectionText); return true; }; function menuItemToggle(text){ var searchText="جست و جو برای "; searchMenu.label=searchText+text; };
در قسمت آینده موارد بیشتری را در مورد افزونه نویسی در فایرفاکس بررسی خواهیم کرد و افزونه را تکمیل خواهیم کرد
Interceptors پایهی پروکسیهای پویا هستند
برای پیاده سازی پروکسیهای پویا نیاز است با مفهوم Interceptors آشنا شویم. به کمک Interceptors فرآیند فراخوانی متدها و خواص یک کلاس، تحت کنترل و نظارت قرار خواهند گرفت. زمانیکه یک IOC Container کار وهله سازی کلاس سرویس خاصی را انجام میدهد، در همین حین میتوان مراحل شروع، پایان و خطاهای متدها یا فراخوانیهای خواص را نیز تحت نظر قرار داد و به این ترتیب مصرف کننده، امکان تزریق کدهایی را در این مکانها خواهد یافت. مزیت مهم استفاده از Interceptors، عدم نیاز به کامپایل ثانویه اسمبلیهای موجود، برای تغییری در کدهای آنها است (برای تزریق نواحی تحت کنترل قرار دادن اعمال) و تمام کارها به صورت خودکار در زمان اجرای برنامه مدیریت میگردند.
با اضافه کردن Interception به پروسه وهله سازی سرویسها توسط یک IoC Container، مراحل کار اینبار به صورت زیر تغییر میکنند:
الف) در اینجا نیز در ابتدا فراخوان، درخواست وهلهای را بر اساس اینترفیسی خاص، به IOC Container ارائه میدهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده را بر اساس تنظیمات اولیهی خود میکند.
ج) اما در این حالت IOC Container تشخیص میدهد نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه میدهد.
د) در ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت میدهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Decorator را سبب خواهد شد.
یعنی به صورت خلاصه، فراخوان سرویسی را درخواست میدهد، اما وهلهای را که دریافت میکند، یک لایهی اضافهتر تزئین کننده را نیز به همراه دارد که کاملا از دید فراخوان مخفی است و نحوهی کار کردن با آن سرویس، با و بدون این تزئین کننده، دقیقا یکی است. وجود این لایهی تزئین کننده سبب میشود تا فراخوانی هر متد این سرویس، از این لایه گذشته و سبب اجرای یک سری کد سفارشی، پیش و پس از اجرای این متد نیز گردد.
پیاده سازی پروکسیهای پویا توسط کتابخانهی Castle.Core در برنامههای NET Core.
در ادامه از کتابخانهی بسیار معروف Castle.Core برای پیاده سازی پروکسیهای پویا استفاده خواهیم کرد. از این کتابخانه در پروژهی EF Core، برای پیاده سازی Lazy loading نیز استفاده شدهاست.
برای دریافت آن یکی از دستورات زیر را اجرا نمائید:
> Install-Package Castle.Core > dotnet add package Castle.Core
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="castle.core" Version="4.3.1" /> </ItemGroup> </Project>
تبدیل ExceptionHandlingDecorator مثال قسمت قبل، به یک Interceptor مخصوص Castle.Core
در قسمت قبل، کلاس MyTaskServiceDecorator را جهت اعمال یک try/catch به همراه logging، به متد Run سرویس MyTaskService، تهیه کردیم. در اینجا قصد داریم نگارش عمومیتر این تزئین کننده را با طراحی یک Interceptor مخصوص Castle.Core انجام دهیم:
using System; using Castle.DynamicProxy; using Microsoft.Extensions.Logging; namespace CoreIocSample02.Utils { public class ExceptionHandlingInterceptor : IInterceptor { private readonly ILogger<ExceptionHandlingInterceptor> _logger; public ExceptionHandlingInterceptor(ILogger<ExceptionHandlingInterceptor> logger) { _logger = logger; } public void Intercept(IInvocation invocation) { try { invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت میگیرد } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } } } }
public void Run() { try { _decorated.Run(); } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } }
اتصال ExceptionHandlingInterceptor تهیه شده به سیستم تزریق وابستگیها
در ادامه روش معرفی ExceptionHandlingInterceptor تهیه شده را به سیستم تزریق وابستگیها، توسط متد Decorate کتابخانهی Scrutor که آنرا در قسمت قبل بررسی کردیم، ملاحظه میکنید:
namespace CoreIocSample02 { public class Startup { private static readonly ProxyGenerator _dynamicProxy = new ProxyGenerator(); public void ConfigureServices(IServiceCollection services) { services.AddTransient<ITaskService, MyTaskService>(); services.AddTransient<ExceptionHandlingInterceptor>(); services.Decorate(typeof(ITaskService), (target, serviceProvider) => _dynamicProxy.CreateInterfaceProxyWithTargetInterface( interfaceToProxy: typeof(ITaskService), target: target, interceptors: serviceProvider.GetRequiredService<ExceptionHandlingInterceptor>()) );
- سپس نیاز است خود سرویس اصلی غیر تزئین شده، به نحو متداولی به سیستم معرفی شود.
- در ادامه توسط متد الحاقی Decorate، کار تزئین ITaskService را با یک dynamicProxy که ExceptionHandlingInterceptor را به صورت پویا تبدیل به یک Decorator کرده و بر روی تک تک متدهای سرویس ITaskService اجرا میکند، انجام میدهیم.
- کاری که Scrutor در اینجا انجام میدهد، یافتن سرویس ITaskService معرفی شدهی پیشین و تعویض آن با dynamicProxy میباشد. بنابراین نیاز است تعریف services.AddTransient، پیش از تعریف services.Decorate انجام شده باشد.
یک نکته: چون ExceptionHandlingInterceptor دارای پارامتر تزریق شدهای در سازندهی آن است، بهتر است خود آنرا نیز به صورت یک سرویس ثبت کنیم و سپس وهلهای از آنرا از طریق serviceProvider.GetRequiredService در قسمت interceptors متد CreateInterfaceProxyWithTargetInterface معرفی کنیم تا نیازی به مقدار دهی دستی تک تک پارامترهای سازندهی آن نباشد.
اکنون اگر برنامه را اجرا کنیم و برای مثال ITaskService را در سازندهی یک کنترلر تزریق کنیم، بجای دریافت وهلهای از کلاس MyTaskService، اینبار وهلهای از Castle.Proxies.ITaskServiceProxy را دریافت میکنیم.
به این معنا که Castle.Core به صورت پویا وهلهی سرویس MyTaskService را داخل یک Castle.Proxies پیچیدهاست و از این پس ما از طریق این واسط، با سرویس اصلی MyTaskService ارتباط برقرار خواهیم کرد. برای درک بهتر این مراحل، بر روی سازندهی کلاس کنترلر و همچنین متد Intercept، تعدادی break-point را قرار دهید.
ایجاد پروژههای React مبتنی بر TypeScript
برای ایجاد ساختار ابتدایی پروژههای React که جهت استفادهی از TypeScript تنظیم شدهاند، دستور زیر را در خط فرمان اجرا میکنیم:
npx create-react-app tssample --template typescript
بنابراین npx create-react-app کار اجرای آخرین نسخهی برنامهی ایجاد ساختار پروژههای React را انجام میدهد. پس از آن یک نام دلخواه ذکر شدهاست و در آخر توسط سوئیچ template typescript-- سبب خواهیم شد تا این ساختار بجای استفادهی از ES6 پیشفرض، بر اساس TypeScript ایجاد و تنظیم شود.
بررسی ساختار پروژهی TypeScript ای ایجاد شده
در تصویر فوق، نمونهای از این ساختار ابتدایی ایجاد شدهی مبتنی بر TypeScript را مشاهده میکنید. اولین تفاوت مهم این ساختار، با ساختار پیشفرض پروژههای React مبتنی بر ES6، وجود فایل جدید tsconfig.json است. کار آن تنظیم پارامترهای کامپایلر TypeScript است. همچنین اینبار بجای پسوندهای js و jsx، پسوندهای ts و tsx قابل مشاهده هستند؛ مانند فایلهای serviceWorker.ts ، index.tsx و App.tsx. البته اگر به ساختار این فایلها دقت کنید، آنچنان تفاوت مهمی را با نمونههای قبلی ES6 خود ندارند و تقریبا یکی هستند. روش اجرای آنها نیز مانند قبل است و با همان دستور npm start صورت میگیرد:
قابلیت استفادهی از کدهای جاوا اسکریپتی موجود، در پروژههای تایپ اسکریپتی جدید
داخل پوشهی src، پوشهی جدید components را ایجاد کرده و داخل آن فایل جدید Head.js را اضافه میکنیم. سپس داخل آن rfac را نوشته و دکمهی tab را فشار میدهیم تا ساختار ابتدایی یک react arrow functional component جدید ایجاد شود:
import React from "react"; export const Head = () => { return ( <div> <h1>Hello</h1> </div> ); };
import { Head } from "./components/Head"; // ... function App() { return ( <div className="App"> <Head /> // ... </div> ); } export default App;
به همین جهت پس از مشاهدهی این قابلیت، پسوند فایل کامپوننت جدید js ایجاد شده را به tsx تغییر میدهیم (src\components\Head.tsx). البته در یک چنین حالتی اگر هنوز دستور npm start در حال اجرا است، نیاز خواهید داشت یکبار آنرا بسته و مجددا اجرا کنید. پس از آن، باز هم برنامه بدون مشکل کامپایل میشود و نشان دهندهی این است که کدهای نوشته شدهی در کامپوننت Head، کدهای کاملا معتبر تایپ اسکریپتی نیز هستند. علت اینجا است که TypeScript، در حقیقت Superset جاوا اسکریپت بهشمار میرود و قابلیتهای جدیدی را به TypeScript استفاده میکند. بنابراین کدهای جاوااسکریپتی موجود، کدهای معتبر تایپ اسکریپتی نیز بهشمار میروند.
مشخص کردن نوع props کامپوننتها توسط TypeScript
اولین استفادهی ما از TypeScript در اینجا، مشخص کردن نوع props یک کامپوننت است:
import React from "react"; export const Head = ({ title, isActive }) => { return ( <div> <h1>{title}</h1> {isActive && <h3>Active</h3>} </div> ); };
علت اینجا است که این فایل، tsx است و نه js. به همین جهت نوع این متغیرها را، همان حالت پیشفرض جاوا اسکریپت که any است، درنظر گرفتهاست و ... این مورد بر اساس تنظیمات فایل tsconfig.json برنامه، ممنوع است. البته اگر به این فایل دقت کنید، شاید چنین گزینهای را به صورت صریح نتوانید در آن پیدا کنید. علت اینجا است که تعداد گزینههای قابل تنظیم در فایل tsconfig روز به روز بیشتر میشوند. به همین جهت برای ساده سازی فعالسازی آنها، از TypeScript 2.3 به بعد، پرچم strict نیز به این تنظیمات اضافه شدهاست. کار آن فعالسازی یکجای تمام بررسیهای strict است؛ مانند noImplicitAny، strictNullChecks و غیره.
{ "compilerOptions": { "strict": true /* Enable all strict type-checking options. */ } }
{ "compilerOptions": { "strict": true, "noImplicitAny": false } }
بنابراین چون در فایل tsconfig.json برنامهی React ما گزینهی strict به true تنظیم شدهاست، گزینهی فعال noImplicitAny نیز جزئی از آن است و دیگر نمیتوان متغیر یا خاصیتی را بدون ذکر صریح نوع آن، رها کرد.
برای رفع خطای noImplicitAny موجود، به ابتدای فایل src\components\Head.tsx، نوع جدید Props را اضافه میکنیم (نام آن کاملا دلخواه است):
type Props = { title: string; isActive: boolean; };
export const Head = ({ title, isActive }: Props) => {
یک نکته: البته اگر از سری React 16x بخاطر داشته باشید، میتوان یک چنین قابلیتی را توسط propTypes خود React نیز پیاده سازی کرد:
Head.propTypes = { title: PropTypes.string, isActive: PropTypes.bool }
پس از این تغییر، اگر به فایل src\App.tsx مراجعه کنیم، مشاهده میکنیم که ذیل تعریف المان کامپوننت Head، مجددا خط قرمزی کشیده شدهاست:
عنوان میکند که بر اساس نوع جدید Props ای که تعریف کردهاید، نیاز است دو خاصیت اجباری title و isActive را نیز در اینجا ذکر کنید؛ وگرنه تعریف این المان، بدون آنها ناقص است.
امکان جالب دیگری که با تعریف نوع props توسط تایپاسکریپت رخ میدهد، فعال شدن intellisense متناظر با تعریف این خواص و ویژگیها است:
در ادامه با تعریف این دو ویژگی جدید، خط قرمز رنگ ذیل کامپوننت Head برطرف میشود:
<Head title="Hello" isActive={true} />
همچنین در این حالت، کد برنامه نیز کامپایل نمیشود و ذکر این خطاها صرفا منحصر به ادیتور مورد استفاده نیست.
بنابراین به صورت خلاصه مزیتهای کار با TypeScript برای تعاریف نوع props به شرح زیر است:
- auto-complete و داشتن intellisense خودکار.
- اگر نام المان کامپوننتی و یا نام یکی از props را به اشتباه وارد کنیم، بلافاصله خطای یافت نشدن آنها را نمایش میدهد.
- اگر ذکر یک prop اجباری را فراموش کنیم، بلافاصله خطای متناظری را دریافت میکنیم.
- اگر نوع مقدار یکی از props را به اشتباه وارد کنیم، باز هم خطایی را جهت گوشزد کردن آن مشاهده خواهیم کرد.
- فعال بودن TypeScript، امکان refactoring بسیار قویتری را میسر میکند. برای مثال با فشردن دکمهی F2 میتوان نام یک کامپوننت را در کل برنامه به سادگی تغییر داد. همچنین یک چنین قابلیتی برای تغییر نام props نیز میسر است و به صورت خودکار تمام کاربردهای آنرا نیز به روز میکند.
- اگر نوع prop ای را در تعریف آن تغییر دادیم، اما مقدار منتسب به آنرا خیر، باز هم بلافاصله متوجه این مشکل خواهیم شد.
به این ترتیب با دسترسی به بررسیهای دقیق زمان کامپایل برنامه، میتوان مشکلات بسیار کمتری را در زمان اجرای آن شاهد بود.