‏نمایش پست‌ها با برچسب MAF. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب MAF. نمایش همه پست‌ها

۱۳۸۸/۱۰/۰۲

آشنايي با M.A.F - قسمت دوم


قسمت قبل بيشتر آشنايي با يك سري از اصطلاحات مرتبط با فريم ورك MAF بود و همچنين نحوه‌ي كلي استفاده از آن. در اين قسمت يك مثال ساده را با آن پياده سازي خواهيم كرد و فرض قسمت دوم بر اين است كه افزونه‌ي Visual Studio Pipeline Builder را نيز نصب كرده‌ايد.

يك نكته پيش از شروع:
- اگر افزونه‌ي Visual Studio Pipeline Builder پس از نصب به منوي Tools اضافه نشده است، يك پوشه‌ي جديد را به نام Addins در مسير Documents\Visual Studio 2008 ايجاد كرده و سپس فايل‌هاي آن‌را در اين مسير كپي كنيد.

ساختار اوليه يك پروژه MAF

- پروژ‌ه‌هايي كه از MAF استفاده مي‌كنند، نياز به ارجاعاتي به دو اسمبلي استاندارد System.AddIn.dll و System.AddIn.Contract.dll دارند (مطابق شكل زير):



- ساختار آغازين يك پروژه MAF از سه پروژه تشكيل مي‌شود كه توسط افزونه‌ي Visual Studio Pipeline Builder به 7 پروژه بسط خواهد يافت.
اين سه پروژه استاندارد آغازين شامل موارد زير هستند:



- هاست: همان برنامه‌ي اصلي كه قرار است از افزونه استفاده كند.
- قرار داد: نحو‌ه‌ي تعامل هاست و افزونه در اين پروژه تعريف مي‌شود. (يك پروژه از نوع class library)
- افزونه: كار پياده سازي قرار داد را عهده دار خواهد شد. (يك پروژه از نوع class library)

- همچنين مرسوم است جهت مديريت بهتر خروجي‌هاي حاصل شده يك پوشه Output را نيز به اين solution اضافه كنند:



اكنون با توجه به اين محل خروجي، به خواص Build سه پروژه موجود مراجعه كرده و مسير Build را اندكي اصلاح خواهيم كرد (هر سه مورد بهتر است اصلاح شوند)، براي مثال:



نكته‌ي مهم هم اينجا است كه خروجي host بايد به ريشه اين پوشه تنظيم شود و ساير پروژه‌ها هر كدام خروجي خاص خود را در پوشه‌اي داخل اين ريشه بايد ايجاد كنند.



تا اينجا قالب اصلي كار آماده شده است. قرارداد ما هم به شكل زير است (ويژگي AddInContract آن نيز نبايد فراموش شود):

using System.AddIn.Pipeline;
using System.AddIn.Contract;

namespace CalculatorConract
{
[AddInContract]
public interface ICalculatorContract : IContract
{
double Operate(string operation, double a, double b);
}
}

به عبارت ديگر برنامه‌اي محاسباتي داريم (هاست) كه دو عدد double را در اختيار افزونه‌هاي خودش قرار مي‌دهد و سپس اين افزونه‌ها يك عمليات رياضي را بر روي آن‌ها انجام داده و خروجي را بر مي‌گردانند. نوع عمليات توسط آرگومان operation مشخص مي‌شود. اين آرگومان به كليه افزونه‌هاي موجود ارسال خواهد شد و احتمالا يكي از آن‌ها اين مورد را پياده سازي كرده است. در غير اينصورت يك استثناي عمليات پياده سازي نشده صادر مي‌شود.
البته روش بهتر طراحي اين افزونه، اضافه كردن متد يا خاصيتي جهت مشخص كردن نوع و يا انواع عمليات پشتيباني شده توسط افزونه‌ است كه جهت سادگي اين مثال، به اين طراحي ساده اكتفا مي‌شود.

ايجاد pipeline

اگر قسمت قبل را مطالعه كرده باشيد، يك راه حل مبتني بر MAF از 7 پروژه تشكيل مي‌شود كه عمده‌ترين خاصيت آن‌ها مقاوم كردن سيستم در مقابل تغييرات نگارش قرارداد است. در اين حالت اگر قرار داد تغيير كند، نه هاست و نه افزونه‌ي قديمي، نيازي به تغيير در كدهاي خود نخواهند داشت و اين پروژه‌هاي مياني هستند كه كار وفق دادن (adapters) نهايي را برعهده مي‌گيرند.


براي ايجاد خودكار View ها و همچنين Adapters ، از افزونه‌ي Visual Studio Pipeline Builder كه پيشتر معرفي شد استفاده خواهيم كرد.



سه گزينه‌ي آن هم مشخص هستند. نام پروژه‌ي قرارداد، مسير پروژه‌ي هاست و مسير خروجي نهايي معرفي شده. پيش از استفاده از اين افزونه نياز است تا يكبار solution مورد نظر كامپايل شود. پس از كليك بر روي دكمه‌ي OK، پروژه‌هاي ذكر شده ايجاد خواهند شد:


پس از ايجاد اين پروژه‌ها، نياز به اصلاحات مختصري در مورد نام اسمبلي و فضاي نام هر كدام مي‌باشد؛ زيرا به صورت پيش فرض هر كدام به نام template نامگذاري شده‌اند:



پياده سازي افزونه

قالب كاري استفاده از اين فريم ورك آماده است. اكنون نوبت به پياده سازي يك افزونه مي‌باشد. به پروژه AddIn مراجعه كرده و ارجاعي را به اسمبلي AddInView خواهيم افزود. به اين صورت افزونه‌ي ما به صورت مستقيم با قرارداد سروكار نداشته و ارتباطات، در راستاي همان pipeline تعريف شده، جهت مقاوم شدن در برابر تغييرات صورت مي‌گيرد:
using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
throw new NotImplementedException();
}
}
}

در اينجا افزونه‌ي ما بايد اينترفيس ICalculator مربوط به AddInView را پياده سازي نمايد كه براي مثال خواهيم داشت:

using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn("افزونه يك", Description = "توضيحات", Publisher = "نويسنده", Version = "نگارش يك")]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
switch (operation)
{
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
default:
throw new NotSupportedException("عمليات مورد نظر توسط اين افزونه پشتيباني نمي‌شود");
}
}
}
}

همانطور كه در قسمت قبل نيز ذكر شد، اين كلاس بايد با ويژگي AddIn مزين شود كه توسط آن مي‌توان توضيحاتي در مورد نام ، نويسنده و نگارش افزونه ارائه داد.


استفاده از افزونه‌ي توليد شده

هاست براي استفاده از افزونه‌هايي با قرارداد ذكر شده، مطابق pipeline پروژه، نياز به ارجاعي به اسمبلي HostView دارد و در اينجا نيز هاست به صورت مستقيم با قرارداد كاري نخواهد داشت. همچنين هاست هيچ ارجاع مستقيمي به افزونه‌ها نداشته و بارگذاري و مديريت آن‌ها به صورت پويا انجام خواهد شد.

نكته‌ي مهم!
در هر دو ارجاع به HostView و يا AddInView بايد خاصيت Copy to local به false تنظيم شود، در غير اينصورت افزونه‌ي شما بارگذاري نخواهد شد.



پس از افزودن ارجاعي به HostView، نمونه‌اي از استفاده از افزونه‌ي توليد شده به صورت زير مي‌تواند باشد كه توضيحات مربوطه به صورت كامنت آورده شده است:

using System;
using System.AddIn.Hosting;
using CalculatorConract.HostViews;

namespace Calculator
{
class Program
{
private static ICalculator _calculator;

static void doOperation()
{
Console.WriteLine("1+2: {0}", _calculator.Operate("+", 1, 2));
}

static void Main(string[] args)
{
//مسير پوشه ريشه مربوطه به خط لوله افزونه‌ها
string path = Environment.CurrentDirectory;

//مشخص سازي مسير خواندن و كش كردن افزونه‌ها
AddInStore.Update(path);

//يافتن افزونه‌هايي سازگار با شرايط قرارداد پروژه
//در اينجا هيچ افزونه‌اي بارگذاري نمي‌شود
var addIns = AddInStore.FindAddIns(typeof(ICalculator), path);

//اگر افزونه‌اي يافت شد
if (addIns.Count > 0)
{
var addIn = addIns[0]; //استفاده از اولين افزونه
Console.WriteLine("1st addIn: {0}", addIn.Name);

//فعال سازي افزونه و همچنين مشخص سازي سطح دسترسي آن
_calculator = addIn.Activate<ICalculator>(AddInSecurityLevel.Intranet);

//يك نمونه از استفاده آن
doOperation();
}

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

چند نكته جالب توجه در مورد قابليت‌هاي ارائه شده:
- مديريت load و unload پويا
- امكان تعريف سطح دسترسي و ويژگي‌هاي امنيتي اجراي يك افزونه
- امكان ايزوله سازي پروسه اجراي افزونه از هاست (در ادامه توضيح داده خواهد شد)
- مقاوم بودن پروژه به نگارش‌هاي مختلف قرارداد


اجراي افزونه در يك پروسه مجزا

حتما با امكانات مرورگر كروم و يا IE8 در مورد اجراي هر tab آن‌ها در يك پروسه‌ي مجزا از پروسه اصلي هاست مطلع هستيد. به اين صورت پروسه‌ي هاست از رفتار tab ها محافظت مي‌شود، همچنين پروسه‌ي هر tab نيز از tab ديگر ايزوله خواهد بود. يك چنين قابليتي در اين فريم ورك نيز پيش بيني شده است.

//فعال سازي افزونه و همچنين مشخص سازي سطح دسترسي آن
//همچنين جدا سازي پروسه اجرايي افزونه از هاست
_calculator = addIn.Activate<ICalculator>(
new AddInProcess(),
AddInSecurityLevel.Intranet);

در اين حالت اگر پس از فعال شدن افزونه، يك break point قرار دهيم و به task manager ويندوز مراجعه نمائيم، پروسه‌ي مجزاي افزونه قابل مشاهده است.



براي مطالعه بيشتر + ، + ، + و +

۱۳۸۸/۰۹/۳۰

آشنايي با M.A.F - قسمت اول


در طي چند مقاله قصد بررسي نحوه‌ي توليد برنامه‌هاي توسعه پذير (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);
}
}
كلاس سمت افزونه نيز بسيار شبيه قسمت قبل است و كار آن اتصال AddInView به Contract مي‌باشد كه با پياده سازي ContractBase و Itranslator صورت خواهد گرفت. همچنين اين كلاس به ويژگي AddInAdapter مزين گرديده است.

[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)
{
...
}
}

ادامه دارد ....