به روز رسانی VS 2015 update 1
در طی چند مقاله قصد بررسی نحوهی تولید برنامههای توسعه پذیر (extensible) را با استفاده از plug-ins و یا add-ins داریم.
افزونهها عموما در سه گروه قرار میگیرند:
الف) افزونه، سرویسی را به هاست ارائه میدهد. برای مثال یک میل سرور نیاز به افزونههایی برای ویروس یابی یا فیلتر کردن هرزنامهها دارد؛ یا یک برنامه پردازش متنی نیاز به افزونهای جهت بررسی غلطهای املایی میتواند داشته باشد و یا یک مرورگر وب میتواند با کمک افزونهها قابلیتهای پیش فرض خود را به شدت توسعه و افزایش دهد (نمونهی بارز آن فایرکس است که عمدهترین دلیل اقبال عمومی به آن سهولت توسعه پذیری آن میباشد).
ب) در گروه دوم، هاست، رفتار مشخصی را ارائه داده و سپس افزونه بر اساس آن، نحوهی عملکرد هاست را مشخص میکند. در این حالت هاست است که سرویسی را به افزونه ارائه میدهد. نمونهی بازر آن افزونههای آفیس هستند که امکان اتوماسیون فرآیندهای مختلف آنرا میسر میسازند. به این صورت امکان توسعهی یک برنامه به شکلی که در طراحی اولیه آن اصلا انتظار آن نمیرفته وجود خواهد داشت. همچنین در اینجا نیازی به داشتن سورس کد برنامهی اصلی نیز نمیباشد.
ج) گروه سوم افزونهها تنها از هاست جهت نمایش خود استفاده کرده و عملا استفادهی خاصی از هاست ندارد. برای مثال نوار ابزاری که خود را به windows explorer متصل میکند و تنها از آن جهت نمایش خود بهره میجوید.
در حال حاضر حداقل دو فریم ورک عمده جهت انجام اینکار و تولید افزونهها برای دات نت فریم ورک مهیا است:
الف) managed addin framework یا MAF
ب) managed extensibility framework یا MEF
فضای نام جدیدی به دات نت فریم ورک سه و نیم به نام System.AddIn اضافه شده است که به آن Managed AddIn Framework یا MAF نیز اطلاق میشود. از این فریم ورک در VSTO (تولید افزونه برای مجموعهی آفیس) توسط خود مایکروسافت استفاده شده است.
فریم ورک توسعهی افزونههای مدیریت شده در دات نت فریم ورک سه و نیم، مزایای زیر را در اختیار ما خواهد گذاشت:
- امکانات load و unload افزونههای تولید شده
- امکان تغییر افزونهها در زمان اجرای برنامه اصلی بدون نیاز به بستن آن
- ارائهی محیطی ایزوله با ترسیم مرزی بین افزونه و برنامه اصلی
- مدیریت طول عمر افزونه
- مدیریت سازگاری با نگارشهای قبلی و یا بعدی یک افزونه
- امکانات به اشتراک گذاری افزونهها با برنامههای دیگر
- تنظیمات امنیتی و مشخص سازی سطح دسترسی افزونهها
و ...
یک راه حل مبتنی بر MAF میتواند شامل 7 پروژه باشد (که به روابط تعریف شده در آن pipeline هم گفته میشود):
Host : همان برنامهی اصلی است که توسط یک سری افزونه، توسعه یافته است.
Host View : بیانگر انتظارات هاست از افزونهها است. به عبارت دیگر افزونهها باید موارد لیست شده در این پروژه را پیاده سازی کنند.
Host Side Adapter : پل ارتباطی Host View و پروژهی Contract است.
Contract: اینترفیسی است که کار برقراری ارتباط بین Host و افزونهها را برعهده دارد.
Add-In Side Adapter : پل ارتباطی بین Add-In View و Contract است.
Add-In View : حاوی متدها و اشیایی است که جهت برقراری ارتباط با هاست از آنها استفاده میشود.
Add-In : اسمبلی است که توسط هاست جهت توسعهی قابلیتهای خود بارگذاری میشود (به آن Add-On ، Extension ، Plug-In و Snap-In هم گفته میشود).
هدف از این جدا سازیها ارائهی راه حل loosely-coupledایی است که امکان ایزوله سازی، اعمال شرایط امنیتی ویژه و همچنین کنترل نگارشهای مختلف را تسهیل میبخشد و این امر با استفاده از interface های معرفی شده میسر گردیده است. این pipeline از قسمتهای ذیل تشکیل میشود:
قرار داد یا Contract
برای تولید یک افزونه نیاز است تا بین هاست و افزونه قراردادی بسته شود. با توجه به استفاده از MAF ، روش تعریف این قرار داد برای مثال در یک افزونهی مترجم به صورت زیر باید باشد:
[AddInContract]
public interface ITranslator : IContract
{
string Translate(string input);
}
استفاده از ویژگی AddInContract و پیاده سازی اینترفیس IContract جزو مراحل کاری استفاده از MAF است. MAF هنگام تولید پویای pipeline ذکر شده به دنبال ویژگی AddInContract میگردد. این موارد در فضای نام System.AddIn.Pipeline تعریف شدهاند.
دیدگاهها یا Views
دیدگاهها کدهایی هستند که کار تعامل مستقیم بین افزونه و هاست را بر عهده دارند. هاست یا افزونه هر کدام میتوانند دیدگاه خود را نسبت به قرار داد بسته شده داشته باشند. این موارد نیز همانند قرار داد در اسمبلیهای مجزایی نگهداری میشوند.
دیدگاه هاست نسبت به قرار داد:
public abstract class TranslatorHostView
{
public abstract string Translate(string input);
}
[AddInBase]
public abstract class TranslatorHostView
{
public abstract string Translate(string input);
}
هر دو کلاس فوق بر اساس قرار موجود بنا میشوند اما وابسته به آن نیستند. به همین جهت به صورت کلاسهایی abstract تعریف شدهاند. در سمت افزونه، کلاس تعریف شده دیدگاه آن با کلاس دیدگاه سمت هاست تقریبا یکسان میباشد؛ اما با ویژگی AddInBase تعریف شده در فضای نام System.AddIn.Pipeline مزین گردیده است.
وفق دهندهها یا Adapters
آخرین قسمت pipeline ، وفق دهندهها هستند که کار آنها اتصال قرار داد به دیدگاهها است و توسط آن مدیریت طول عمر افزونه و همچنین تبدیل اطلاعات بین قسمتهای مختلف انجام میشود. شاید در نگاه اول وجود آنها زائد به نظر برسد اما این جدا سازی کدها سبب تولید افزونههایی خواهد شد که به نگارش هاست و برنامه اصلی وابسته نبوده و بر عکس (version tolerance). به دو کلاس زیر دقت نمائید:
کلاس زیر با ویژگی [HostAdapter] تعریف شده در فضای نام System.AddIn.Pipeline، مزین شده است و کار آن اتصال HostView به Contract میباشد. برای این منظور TranslatorHostView ایی را که پیشتر معرفی کردیم باید پیاده سازی نماید. علاوه بر این با ایجاد وهلهای از کلاس ContractHandle ، کار مدیریت طول عمر افزونه را نیز میتوان انجام داد.
[HostAdapter]
public class TranslatorHostViewToContract : TranslatorHostView
{
ITranslator _contract;
ContractHandle _lifetime;
public TranslatorHostViewToContract(ITranslator contract)
{
_contract = contract;
_lifetime = new ContractHandle(contract);
}
public override string Translate (string inp)
{
return _contract.Translate(inp);
}
}
[AddInAdapter]
public class TranslatorAddInViewToContract : ContractBase, ITranslator
{
TranslatorAddInView _view;
public TranslatorAddInViewToContract(TranslatorView view)
{
_view = view;
}
public string Translate(string inp)
{
return _view.Translate(inp);
}
}
قسمت عمدهای از این کدها تکراری است. جهت سهولت تولید این کلاسها و پروژههای مرتبط، تیم مربوطه برنامهای را به نام pipeline builder ارائه داده است که از آدرس زیر قابل دریافت است:
این برنامه با دریافت اسمبلی مربوط بهcontract ، کار ساخت خودکار کلاسهای adapters و views را انجام خواهد داد.
ایجاد افزونه
پس از ساخت قسمتهای مختلف pipeline ، اکنون میتوان افزونه را ایجاد نمود. هر افزونه باید add-in view را پیاده سازی کرده و با ویژگی AddIn مزین شود. برای مثال:
[AddIn("GoogleTranslator", Description="Universal translator",
Version="1.0.0.0", Publisher="YourName")]
public class GoogleAddIn : TranslatorAddInView
{
public string Translate(string input)
{
...
}
}
ادامه دارد ....
برای شما که علاقه دارید (عجله دارید!) تا امکانات C# 7.0 را همین الان امتحان کنید، قبل از اینکه نسخه نهایی آن منتشر شود، لینک زیر توضیح میدهد که چطور میتوانید در Visual Studio 2015 و یا Visual Studio '15' (نسخه بعدی) نسخه جدید زبان C# را امتحان کنید. با توجه به اینکه کامپایلر جدید این زبان (Roslyn) دیگر Open Source شده، شما میتوانید حتی آخرین نسخه موجود را امتحان کنید.
ویژوال استدیو 14 CTP
- بررسی ابزار Advanced Group Policy Management (قسمت سوم) | (رضا علیخانی) | www.iransec.ir
- برطرف کردن مشکل افزونه Regionerate در Visual Studio | محمد صاحب | www.dotnetdev.info
- برنامه نویسی تجاری و چندلایه در دات نت | ebook.veyq.ir
- تغییر روش | (Afshar Mohebbi) | blog.afsharm.com
- چرا از استیو جابزها خوششان نمیاید؟ | علی مختاری | www.mywindows.ir
- Paint.NET v3.5.10 منتشر شد | blog.getpaint.net
- SQL-CE Toolbox 2.4 منتشر شد | sqlcetoolbox.codeplex.com
//برای ذخیره مقادیر از ساختار نام و مقدار استفاده میکنیم که نامها را اینجا ثبت کرده ام var Variables={ posts:"posts", postsComments:"postsComments", shares:"shares", sharesComments:"sharesComments", } //برای ذخیره زمان آخرین تغییر سایت برای هر یک از مطالب به صورت جداگانه نیاز به یک ساختار نام و مقدار است که نامها را در اینجا ذخیره کرده ام var DateContainer={ posts:"dtposts", postsComments:"dtpostsComments", shares:"dtshares", sharesComments:"dtsharesComments", interval:"interval" } //برای نمایش پیامها به کاربر var Messages={ SettingsSaved:"تنظیمات ذخیره شد", SiteUpdated:"سایت به روز شد", PostsUpdated:"مطلب ارسالی جدید به سایت اضافه شد", CommentsUpdated:"نظری جدیدی در مورد مطالب سایت ارسال شد", SharesUpdated:"اشتراک جدید به سایت ارسال شد", SharesCommentsUpdated:"نظری برای اشتراکهای سایت اضافه شد" } //لینکهای فید سایت var Links={ postUrl:"https://www.dntips.ir/feeds/posts", posts_commentsUrl:"https://www.dntips.ir/feeds/comments", sharesUrl:"https://www.dntips.ir/feed/news", shares_CommentsUrl:"https://www.dntips.ir/feed/newscomments" } //لینک صفحات سایت var WebLinks={ Home:"https://www.dntips.ir", postUrl:"https://www.dntips.ir/postsarchive", posts_commentsUrl:"https://www.dntips.ir/commentsarchive", sharesUrl:"https://www.dntips.ir/newsarchive", shares_CommentsUrl:"https://www.dntips.ir/newsarchive/comments" }
chrome.runtime.onInstalled.addListener(function(details) { var now=String(new Date()); var params={}; params[Variables.posts]=true; params[Variables.postsComments]=false; params[Variables.shares]=false; params[Variables.sharesComments]=false; params[DateContainer.interval]=1; params[DateContainer.posts]=now; params[DateContainer.postsComments]=now; params[DateContainer.shares]=now; params[DateContainer.sharesComments]=now; chrome.storage.local.set(params, function() { if(chrome.runtime.lastError) { /* error */ console.log(chrome.runtime.lastError.message); return; } }); });
chrome.storage.local.set('mykey':myvalue,....
chrome.storage.local.set(mykey:myvalue,...
"background": { "scripts": ["const.js","init.js"] }
نکته:نمی توان در تعریف بک گراند هم فایل اسکریپت معرفی کرد و هم فایل html
"background": { "page": "background.htm" }
<html> <head> <script type="text/javascript" src="const.js"></script> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript" src="init.js"></script> <script type="text/javascript" src="omnibox.js"></script> <script type="text/javascript" src="rssreader.js"></script> <script type="text/javascript" src="contextmenus.js"></script> </head> <body> </body> </html>
- کدنویسی راحتتر و خلاصهتر برای خواندن RSS
- استفاده اجباری از یک پروکسی به خاطر Content Security Policy و حتی CORS
"content_security_policy": "script-src 'self' https://*.google.com; object-src 'self'"
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
استفاده از این Api در rssreader.js
google.load("feeds", "1"); google.setOnLoadCallback(alarmManager);
function alarmManager() { chrome.storage.local.get(DateContainer.interval,function ( items) { period_time==items[DateContainer.interval]; chrome.alarms.create('RssInterval', {periodInMinutes: period_time}); }); chrome.alarms.onAlarm.addListener(function (alarm) { console.log(alarm); if (alarm.name == 'RssInterval') { var boolposts,boolpostsComments,boolshares,boolsharesComments; chrome.storage.local.get([Variables.posts,Variables.postsComments,Variables.shares,Variables.sharesComments],function ( items) { boolposts=items[Variables.posts]; boolpostsComments=items[Variables.postsComments]; boolshares=items[Variables.shares]; boolsharesComments=items[Variables.sharesComments]; chrome.storage.local.get([DateContainer.posts,DateContainer.postsComments,DateContainer.shares,DateContainer.sharesComments],function ( items) { var Vposts=new Date(items[DateContainer.posts]); var VpostsComments=new Date(items[DateContainer.postsComments]); var Vshares=new Date(items[DateContainer.shares]); var VsharesComments=new Date(items[DateContainer.sharesComments]); if(boolposts){var result=RssReader(Links.postUrl,Vposts,DateContainer.posts,Messages.PostsUpdated);} if(boolpostsComments){var result=RssReader(Links.posts_commentsUrl,VpostsComments,DateContainer.postsComments,Messages.CommentsUpdated); } if(boolshares){var result=RssReader(Links.sharesUrl,Vshares,DateContainer.shares,Messages.SharesUpdated);} if(boolsharesComments){var result=RssReader(Links.shares_CommentsUrl,VsharesComments,DateContainer.sharesComments,Messages.SharesCommentsUpdated);} }); }); } }); }
- آدرس فیدی که قرار است از روی آن بخواند
- آخرین به روزسانی که از سایت داشته متعلق به چه تاریخی است.
- نام کلید ذخیره سازی تاریخ آخرین تغییر سایت که اگر بررسی شد و مشخص شد سایت به روز شده است، تاریخ جدید را روی آن ذخیره کنیم.
- در صورتی که سایت به روز شده باشد نیاز است پیامی را برای کاربر نمایش دهیم که این پیام را در اینجا قرار میدهیم.
function RssReader(URL,lastupdate,datecontainer,Message) { var feed = new google.feeds.Feed(URL); feed.setResultFormat(google.feeds.Feed.XML_FORMAT); feed.load(function (result) { if(result!=null) { var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent; var RssUpdate=new Date(strRssUpdate); if(RssUpdate>lastupdate) { SaveDateAndShowMessage(datecontainer,strRssUpdate,Message) } } }); }
var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent;
function SaveDateAndShowMessage(DateField,DateValue,Message) { var params={ } params[DateField]=DateValue; chrome.storage.local.set( params,function(){ var options={ type: "basic", title: Messages.SiteUpdated, message: Message, iconUrl: "icon.png" } chrome.notifications.create("",options,function(){ chrome.notifications.onClicked.addListener(function(){ chrome.tabs.create({'url': WebLinks.Home}, function(tab) { }); }); }); }); }
"permissions": [ "storage", "tabs", "alarms", "notifications" ]
chrome.notifications.create("",options,function(){ chrome.notifications.onClicked.addListener(function(){ chrome.tabs.create({'url': WebLinks.Home}, function(tab) { });}); });
"web_accessible_resources": [ "icon.png" ]
خوب؛ کار افزونه تمام شده است ولی اجازه دهید این بار امکانات افزونه را بسط دهیم:
"options_page": "popup.html"
جایزگزینی صفحات یا Override Pages
"chrome_url_overrides": { "newtab": "newtab.htm" }
ایجاد یک تب اختصاصی در Developer Tools
"devtools_page": "devtools.htm"
<script src="devtools.js"></script>
chrome.devtools.panels.create( "Dotnettips Updater Tools", "icon.png", "devtoolsui.htm", function(panel) { } );
- One-Time Requests یا درخواستهای تک مرتبهای
- Long-Lived Connections یا اتصالات بلند مدت یا مصر
درخواستهای تک مرتبه ای
window.addEventListener("load", function() { chrome.extension.sendMessage({ type: "dom-loaded", data: { myProperty : "value" } }); }, true);
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { switch(request.type) { case "dom-loaded": alert(request.data.myProperty ); break; } return true; });
var port = chrome.runtime.connect({name: "my-channel"}); port.postMessage({myProperty: "value"}); port.onMessage.addListener(function(msg) { // do some stuff here });
chrome.runtime.onConnect.addListener(function(port) { if(port.name == "my-channel"){ port.onMessage.addListener(function(msg) { // do some stuff here }); } });
نمونه کد
آپلود نهایی کار در Google web store
برای آپلود نهایی کار به google web store که در آن تمامی برنامهها و افزونههای کروم قرار دارند بروید. سمت راست آیکن تنظیمات را بزنید و گزینه developer dashboard را انتخاب کنید تا صفحهی آپلود کار برای شما باز شود. دایرکتوری محتویات اکستنشن را zip کرده و آپلود نمایید. توجه داشته باشید که محتویات و سورس خود را باید آپلود کنید نه فایل crx را. بعد از آپلود موفقیت آمیز، صفحهای ظاهر میشود که از شما آیکن افزونه را در اندازه 128 پیکسل میخواهد بعلاوه توضیحاتی در مورد افزونه، قیمت گذاری که به طور پیش فرض به صورت رایگان تنظیم شده است، لینک وب سایت مرتبط، لینک محل پرسش و پاسخ برای افزونه، اگر لینک یوتیوبی در مورد افزونه دارید، یک شات تصویری از افزونه و همینطور چند تصویر برای اسلایدشو سازی که در همان صفحه استاندارد آنها را توضیح میدهد و در نهایت گزینهی جالبتر هم اینکه اکستنشن شما برای چه مناطقی تهیه شده است که متاسفانه ایران را ندیدم که میتوان همه موارد را انتخاب کرد. به خصوص در مورد ایران که آی پیها هم صحیح نیست، انتخاب ایران چنان تاثیری ندارد و در نهایت گزینهی publish را میزنید که متاسفانه بعد از این صفحه درخواست میکند برای اولین بار باید 5 دلار آمریکا پرداخت شود که برای بسیاری از ما این گزینه ممکن نیست.
سورس پروژه را میتوانید از اینجا ببینید و خود افزونه را از اینجا دریافت کنید.