در حالت پیشرفتهی تزریق وابستگیها در دات نت، با توجه به اینکه کار وهله سازی کلاسها به یک کتابخانه جانبی به نام IoC Container واگذار میشود، امکان یک سری دخل و تصرف نیز در این میان فراهم میگردد. برای مثال الان که ما میتوانیم یک کلاس را توسط IoC container به صورت خودکار وهله سازی کنیم، خوب، چرا اجرای متدهای آنرا تحت نظر قرار ندهیم. مثلا حاصل آنها را بتوانیم پیش از اینکه به فراخوان بازگشت داده شود، کش کنیم یا کلا تغییر دهیم. به این کار AOP یا Aspect orinted programming نیز گفته میشود.
واقعیت این است که یک چنین مفهومی از سالهای دور به نام Hooking در برنامههای WIN32 API خالص نیز وجود داشته است. Hookها یا قلابها دقیقا کار Interception دنیای AOP را انجام میدهند. به این معنا که خودشان را بجای یک متد ثبت کرده و کار ردیابی یا حتی تغییر عملکرد آن متد خاص را میتوانند انجام دهند. برای مثال اگر برای متد gethostbyname ویندوز یک Hook بنویسیم، برنامه استفاده کننده، تنها متد ما را بجای متد اصلی gethostbyname واقع در Kernel32 ویندوز، مشاهده میکند و درخواستهای DNS خودش را به این متد ویژه ما ارسال خواهد کرد؛ بجای ارسال درخواستها به متد اصلی. در این بین میتوان درخواستهای DNS را لاگ کرد و یا حتی تغییر جهت داد.
انجام Interception در دنیای دات نت با استفاده از امکانات Reflection و Reflection.Emit قابل انجام است و یا حتی بازنویسی اسمبلیها و افزودن کدهای IL مورد نیاز به آنها که به آن IL Weaving هم گفته میشود. اما در دنیای WIN32 انجام چنین کاری ساده نیست و ترکیبی است از زبان اسمبلی و کتابخانههای نوشته شده به زبان C.
برای ساده سازی نوشتن Hookهای ویندوزی، کتابخانهای به نام easy hook ارائه شده است که امکان تزریق Hookهای دات نتی را به درون پروسه برنامههای Native ویندوز دارد. این قلابها که در اینجا متدهای دات نتی هستند، نهایتا بجای توابع اصلی ویندوز جا زده خواهند شد. بنابراین میتوانند عملیات آنها را ردیابی کنند و یا حتی پارامترهای آنها را دریافت و مقدار دیگری را بجای تابع اصلی، بازگشت دهند. در ادامه قصد داریم اصول و نکات کار با easy hook را در طی یک مثال بررسی کنیم.
صورت مساله
میخواهیم کلیه درخواستهای تاریخ اکسپلورر ویندوز را ردیابی کرده و بجای ارائه تاریخ استاندارد میلادی، تاریخ شمسی را جایگزین آن کنیم.
از کجا شروع کنیم؟
ابتدا باید دریابیم که اکسپلورر ویندوز از چه توابع API ایی برای پردازشهای درخواستهای تاریخ و ساعت خودش استفاده میکند، تا بتوانیم برای آنها Hook بنویسیم. برای این منظور میتوان از برنامهی بسیار مفیدی به نام API Monitor استفاده کرد. این برنامهی رایگان را از آدرس ذیل میتوانید دریافت کنید:
اگر علاقمند به ردیابی برنامههای 32 بیتی هستید باید apimonitor-x86.exe را اجرا کنید و اگر نیاز به ردیابی برنامههای 64 بیتی دارید باید apimonitor-x64.exe را اجرا نمائید. بنابراین اگر پس از اجرای این برنامه، برای مثال فایرفاکس را در لیست پروسههای آن مشاهده نکردید، یعنی apimonitor-x64.exe را اجرا کردهاید؛ از این جهت که فایرفاکس عمومی تا این تاریخ، نسخه 32 بیتی است و نه 64 بیتی.
پس از اجرای برنامه API Monitor، در قسمت API Filter آن باید مشخص کنیم که علاقمند به ردیابی کدامیک از توابع API ویندوز هستیم. در اینجا چون نمیدانیم دقیقا کدام تابع کار ارائه تاریخ را به اکسپلورر ویندوز عهده دار است، شروع به جستجو میکنیم و هر تابعی را که نام date یا time در آن وجود داشت، تیک میزنیم تا در کار ردیابی لحاظ شود.
سپس نیاز است بر روی نام اکسپلورر در لیست پروسههای این برنامه کلیک راست کرده و گزینه Start monitoring را انتخاب کرد:
اندکی صبر کنید یا یک صفحه جدید اکسپلورر ویندوز را باز کنید تا کار ردیابی شروع شود:
همانطور که مشاهده میکنید، ویندوز برای ردیابی تاریخ در اکسپلورر خودش از توابع GetDateFormatW و GetTimeFormatW استفاده میکند. ابتدا یک تاریخ را توسط آرگومان lpDate یا lpTime به یکی از توابع یاد شده ارسال کرده و سپس خروجی را از آرگومان lpDateStr یا lpTimeStr دریافت میکند.
خوب؛ به نظر شما اگر این خروجیها را با یک ساعت و تاریخ شمسی جایگزین کنیم بهتر نیست؟!
نوشتن Hook برای توابع GetDateFormatW و GetTimeFormatW ویندوز اکسپلورر
ابتدا کتابخانه easy hook را از مخزن کد CodePlex آن دریافت کنید:
سپس یک پروژه کنسول ساده را آغاز کنید. همچنین به این Solution، یک پروژه Class library جدید را نیز اضافه نمائید. پروژه کنسول، کار نصب Hook را انجام میدهد و پروژه کتابخانهای اضافه شده، کار مدیریت هوکها را انجام خواهد داد. سپس به هر دو پروژه، ارجاعی را به اسمبلی EasyHook.dll اضافه کنید.
الف) ساختار کلی کلاس Hook
کلاس Hook واقع در پروژه Class library باید یک چنین امضایی را داشته باشد:
namespace ExplorerPCal.Hooks { public class GetDateTimeFormatInjection : IEntryPoint { public GetDateTimeFormatInjection(RemoteHooking.IContext context, string channelName) { // connect to host... _interface = RemoteHooking.IpcConnectClient<MessagesReceiverInterface>(channelName); _interface.Ping(); } public void Run(RemoteHooking.IContext context, string channelName) { } } }
ب) نوشتن توابع Hook
کار نوشتن قلاب برای توابع API ویندوز در متد Run انجام میشود. سپس باید توسط متد LocalHook.Create کار را شروع کرد. در اینجا مشخص میکنیم که نیاز است تابع GetDateFormatW واقع در kernel32.dll ردیابی شود.
public void Run(RemoteHooking.IContext context, string channelName) { GetDateFormatHook = LocalHook.Create( InTargetProc: LocalHook.GetProcAddress("kernel32.dll", "GetDateFormatW"), InNewProc: new GetDateFormatDelegate(getDateFormatInterceptor), InCallback: this);
ج) نحوه مشخص سازی امضای delegateهای Hook
اگر امضای متد GetDateFormatW به نحو ذیل باشد:
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)] public static extern int GetDateFormatW( uint locale, uint dwFlags, // NLS_DATE_FLAGS SystemTime lpDate, [MarshalAs(UnmanagedType.LPWStr)] string lpFormat, StringBuilder lpDateStr, int sbSize);
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)] private delegate int GetDateFormatDelegate( uint locale, uint dwFlags, SystemTime lpDate, [MarshalAs(UnmanagedType.LPWStr)] string lpFormat, StringBuilder lpDateStr, int sbSize);
private int getDateFormatInterceptor( uint locale, uint dwFlags, SystemTime lpDate, string lpFormat, StringBuilder lpDateStr, int sbSize) { }
د) نصب Hook نوشته شده
باید دقت داشت که هر دو برنامه نصاب Hook و همچنین کتابخانه Hook، باید دارای امضای دیجیتال باشند. بنابراین به برگه signing خواص پروژه مراجعه کرده و یک فایل snk را به هر دو پروژه اضافه نمائید.
سپس در برنامه نصاب، یک کلاس را با امضای ذیل تعریف کنید:
public class MessagesReceiverInterface : MarshalByRefObject { public void Ping() { } }
سپس در ابتدای برنامه نصاب، یک کانال Remoting باز شده (که آرگومان جنریک آن دقیقا همین نام کلاس MessagesReceiverInterface فوق را دریافت میکند)
var channel = RemoteHooking.IpcCreateServer<MessagesReceiverInterface>(ref _channelName, WellKnownObjectMode.SingleCall);
RemoteHooking.Inject( explorer.Id, InjectionOptions.Default | InjectionOptions.DoNotRequireStrongName, "ExplorerPCal.Hooks.dll", // 32-bit version (the same, because of using AnyCPU) "ExplorerPCal.Hooks.dll", // 64-bit version (the same, because of using AnyCPU) _channelName );
پارامتر و پارامترهای بعدی، اطلاعاتی هستند که به سازنده کلاس هوک ارسال میشوند. بنابراین این سازنده میتواند تعداد پارامترهای متغیری داشته باشد:
.ctor(IContext, %ArgumentList%) void Run(IContext, %ArgumentList%)
چند نکته تکمیلی مهم برای کار با کتابخانه Easy hook
- کتابخانه easy hook فعلا با ویندوز 8 سازگار نیست.
- برای توزیع هوکهای خود باید تمام فایلهای همراه کتابخانه easy hook را نیز توزیع کنید و فقط به چند DLL ابتدایی آن بسنده نباید کرد.
- اگر هوک شما بلافاصله سبب کرش پروسه هدف شد، یعنی امضای تابع API شما ایراد دارد و نیاز است چندین و سایت را جهت یافتن امضایی صحیح بررسی کنید. برای مثال در امضای عمومی متد GetDateFormatW، پارامتر SystemTime به صورت struct تعریف شده است؛ درحالیکه ویندوز ممکن است برای دریافت زمان جاری به این پارامتر نال ارسال کند. اما struct دات نت برخلاف struct زبان C یک value type است و نال پذیر نیست. به همین جهت کلیه امضاهای عمومی که در مورد این متد در اینترنت یافت میشوند، در عمل غلط هستند و باید SystemTime را یک کلاس دات نتی که Refrence type است، تعریف کرد تا نال پذیر شود و hook کرش نکرده یا اشتباه عمل نکند.
- زمانیکه یک هوک easy hook بر روی پروسه هدف نصب میشود، دیگر قابل unload کامل نیست و نیاز است برای کارهای برنامه نویسی و به روز رسانی فایل dll جدید، پروسه هدف را خاتمه داد.
- متد Run هوک باید همیشه در حال اجرا باشد تا توسط CLR بلافاصه خاتمه نیافته و هوک از حافظه خارج نشود. اینکار را توسط روش ذیل انجام دهید:
try { while (true) { Thread.Sleep(500); _interface.Ping(); } } catch { _interface = null; // .NET Remoting will raise an exception if host is unreachable }
- استفاده همزمان از API Monitor ذکر شده در ابتدای بحث و یک هوک نصب شده، سبب کرش برنامه هدف خواهد شد.
سورس کامل این پروژه را در اینجا میتوانید دریافت کنید
@using (Ajax.BeginForm(actionName: "Index", controllerName: "Home", ajaxOptions: new AjaxOptions { HttpMethod = "POST", OnSuccess = "doUpload(data, status, xhr)" }, routeValues: null, htmlAttributes: new { id = "uploadForm" })) {
function doUpload(data, status, xhr) { alert(data.result); // مابقی کدهای آپلود فایل
جهت تکمیل بحث
• OnBegin – xhr • OnComplete – xhr, status • OnSuccess – data, status, xhr • OnFailure – xhr, status, error
با سلام و عرض خسته نباشید به همهی دوستان و همکاران
سوالی که بنده داشتم این بود که چرا و به چه علتی با وجود ابزاری مثل visual studio team foundation server 2010 , 2012 باید با ابزاری مثل git کار کرد و البته با توجه به اینکه دوستان این سایت یا وبلاگ عموما در حوضه دات نت هستند این سوال مهمتر هم میشه ، در مورد مطالب در خصوص git باید بگم طرز کار کردن با این ابزار بسیار پیچیدهتر و غیر اصولیتر از tfs هست ، مثلا اینکه خود فایل رو پس از تغییر نگهداری میکنه یک نقطه ضعفه ولی نویسنده مطلب از اون به عنوان نقطه قوت یاد کرده ، اگر فایل به صورت مجموعه تغییرات ذخیره بشه هم حجم اطلاعات ذخیره شده کاهش پیدا میکنه و هم منبع نگهداری سورسها میتونه مثل ماشین زمان ما رو به جلو و عقب ببره و محدودیتی نخواهد داشت ، در هر حال با توجه به محصول مایکروسافت بودن tfs و رایگان بودن git فکر کنم حتی مقایسه این دو حتی درست هم نباشه.
با تشکر از تمامی زحمات شما دوستان عزیز
روش ایجاد یک پروژهی کتابخانهای، از کامپوننتهای Blazor
اگر از ویژوال استودیو استفاده میکنید، نوع «Razor Class Library»، پروژههای مخصوص کتابخانههای کامپوننتهای Blazor را آغاز میکند و اگر میخواهید از CLI استفاده کنید، باید از دستور «dotnet new razorclasslib» استفاده کرد؛ که قابلیت تبدیل به بستههای نیوگت را با دستور dotnet pack داشته و به این ترتیب میتوان آنها را به اشتراک گذاشت و یا حتی میتوان ارجاعی از این پروژه را در سایر پروژههای شخصی، مورد استفاده قرار داد.
ساختار پیشفرض فایل csproj اینگونه پروژهها به صورت زیر است:
<Project Sdk="Microsoft.NET.Sdk.Razor"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <SupportedPlatform Include="browser" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.6" /> </ItemGroup> </Project>
روش افزودن فایلهای ثابت مورد استفادهی در کتابخانه
پروژهی پیشفرضی که در اینجا ایجاد میشود، به همراه یک پوشهی wwwroot نیز هست. این پوشه محلی است که باید فایلهای ثابت اشتراکی پروژه را مانند فایلهای CSS و JS مورد استفاده، قرار داد.
این نوع پروژهها از ویژگی Isolated CSS و یا Isolated JS ارائه شدهی در Blazor 5x نیز پشتیبانی میکنند.
روش استفادهی از فایلهای ثابت موجود در یک کتابخانه
این فایلهای ثابت به همراه بستهی نهایی پروژه، به صورت خودکار توزیع میشوند (و نیازی به ارائهی مجزای آنها نیست) و برای استفادهی از آنها در پروژههای دیگر، باید از روش مسیردهی زیر استفاده کرد:
/_content/PackageId/MyImage.png
- PackageId عموما همان نام پروژهی مورد استفادهاست (نام فایل csproj مانند MyBlazorComponentLibrary). هرچند میتوان آنرا به صورت مجزایی در فایل csproj نیز مقدار دهی کرد.
- در این مثال MyImage.png، نام منبعی است که قرار است از آن استفاده کنیم و پیشتر در پوشهی wwwroot کتابخانه، کپی شدهاست و یا حتی میتوان زیر پوشههایی را نیز در اینجا ایجاد و از آنها استفاده کرد؛ مانند:
/_content/MyBlazorComponentLibrary/scripts/HelloWorld.js
<script src="_content/MyBlazorComponentLibrary/exampleJsInterop.js"></script>
روش استفادهی از کتابخانهی نهایی تولید شده
برای استفادهی از کتابخانهی نهایی تولید شده یا میتوان ارجاعی را به فایل csproj آن، به پروژهی خود افزود:
<ItemGroup> <ProjectReference Include="..\MyBlazorComponentLibrary\MyBlazorComponentLibrary.csproj" /> </ItemGroup>
پس از آن، جهت سهولت استفادهی از این کامپوننتهای اشتراکی، بهتر است فضای نام آنها را به فایل Imports.razor_ پروژهی خود افزود؛ تا نیازی به تعریف آنها در تمام کامپوننتهای مورد استفاده نباشد.
از آنجاکه نهایت اندازهی یک سند BSON نمیتواند بیشتر از 16 مگابایت باشد، قابلیتی به نام GridFS ایجاد شدهاست تا بتوان فایلهای باینری را در آن ذخیره کرد. GridFS شامل دو بخش مختلف برای ذخیره اطلاعات یک فایل باینری است:
- fs.chunks که برای ذخیره اطلاعات قطعههای یک فایل باینری به کار میرود.
- fs.files که برای ذخیره اطلاعات و متادیتاها به کار میرود.
قبل از هر چیزی باید بدانید که کتابخانه مربوط به GridFs در یک پکیج جداگانه عرضه شده است و باید آن از طریق nuget نصب کنیم:
install-package MongoDB.Driver.GridFS
برای آپلود یک فایل باینری به داخل سیستم از کد زیر استفاده میکنیم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db);
byte[] source=File.ReadAllBytes(@"D:\Untitled.png"); var options = new GridFSUploadOptions { ChunkSizeBytes = 64512, // 63KB Metadata = new BsonDocument { { "CoverType", "Front" }, { "copyrighted", true } } };
مورد دومی که مقداردهی شدهاست، متادیتاها هستند و این قابلیت را داریم که پرس و جوی خود را بر اساس آنها نیز فیلتر کنیم. این خصوصیت مقدار دریافتی از جنس BsonDocument را دریافت میکند. ولی اگر شما برای فایل خود، کلاس اختصاصی برای متادیتاها در نظر گرفتهاید میتوانید از یک Extension Method به نام ToBsonDocument استفاده کنید و شیء خود را به این نوع تبدیل کنید:
var options = new GridFSUploadOptions { ChunkSizeBytes = 64512, // 63KB Metadata = metaData.ToBsonDocument() };
در نهایت آن را آپلود میکنیم:
var id = bucket.UploadFromBytes("GoneWithTheWind", source, options);
نکته: اگر در یک کلاس، چند فیلد از جنس ObjectId دارید، مونگو در بین تشخیص شناسه اصلی سند و شناسه تصاویر، با توجه به نام خصوصیتها و غیره، تا حد زیادی هوشمند عمل میکند. ولی اگر خواستید صریحا شناسه اصلی را ذکر کنید و آن را متمایز از بقیه نشان دهید، میتوانید از خصوصیت BsonId در بالای نام فیلد ID استفاده کنید:
[BsonId] public ObjectId Id { get; set; }
جهت خواندن فایل آپلود شده، تنها کافی است از طریق شناسهی دریافتی در مرحلهی آپلود، اقدام نماییم:
var bytes = bucket.DownloadAsBytes(id);
نکته: تمام متدهای آپلود و دانلود دیتا، هم به صورت آرایه ای از بایتها و هم به صورت استریم میتوانند مورد استفاده قرار بگیرند و به ازای هر کدام، متدهای همزمان و غیرهمزمان نیز موجود هستند.
اگر قصد دارید بر اساس نام داده شده، فایل را دریافت کنید، ممکن است که چندین فایل، تحت یک نام ذخیره شده باشند که میتوانید در حالتهای مختلفی این تصاویر را واکشی نمایید:
0 : فایل اصلی
1: اولین نسخه فایل
2: دومین نسخه فایل
و الی آخر...
1-: جدیدترین نسخه فایل (مقدار پیش فرض)
2-: نسخه ماقبل جدیدترین نسخه فایل
و الی آخر...
منظور از نسخه، فایلهایی با نامی موجود و از قبل ذخیره شده هستند که نسخه جدیدی از فایل قبلی بوده و فایل اول، فایل اصلی محسوب میشود.
برای درک بهتر مسئله، من تصاویر زیر را به ترتیب از سمت راست به سمت چپ، تحت یک نام، وارد سیستم میکنم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db); var image=bucket.DownloadAsBytesByName("City of Glass-cover"); File.WriteAllBytes(@"D:\a.jpg",image);
برای مقداردهی خواص بالا به شکل زیر عمل میکنیم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db); var options = new GridFSDownloadByNameOptions { Revision = 0 }; var image=bucket.DownloadAsBytesByName("City of Glass-cover",options); File.WriteAllBytes(@"D:\a.jpg",image);
برای بازگردانی تصاویر از طریق مقادیر موجود در متادیتا، باید از کلاس ویژهای به نام GridFSFileInfo استفاده کنیم. در اینجا هم همانند روزهای اول، از کلاس بیلدر جهت ایجاد شرط استفاده میکنیم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db); var filter = Builders<GridFSFileInfo>.Filter.Gte(x => x.Length , 600); var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime); var options =new GridFSFindOptions() { Limit = 3, Sort = sort }; var cursor = bucket.Find(filter, options); var list = cursor.ToList();
برای یافتن تصاویر بر اساس متادیتاهای تعریف شده، از کد زیر استفاده میکنیم:
var filter = Builders<GridFSFileInfo>.Filter.Eq("metadata.CoverType","Front");
تغییر نام تصاویر
جهت ویرایش یک نام فایل از طریق متدهای زیر اقدام مینماییم:
bucket.Rename(id, newFilename); //یا در حالت غیرهمزمان await bucket.RenameAsync(id, newFilename);
حذف تصویر
bucket.Delete(id); //یا await bucket.DeleteAsync(id);
برای حذف کل bucket از طریق کد زیر اقدام مینماییم:
bucket.Drop(); //یا await bucket.DropAsync();
با توجه به پیشرفتی که در حوزه اپلیکشنهای وابسته به فریمورک دات نت بوجود آمده، ولی شاید حرکت عملی بزرگی از سمت تولیدکندگان در حوزه کامپکت صورت نگرفته و همچنان شاهد فرمانروایی سیستم عاملهایی چون Windows Compact 6.0 با استفاده از دات نت فریمورکهایی نهایت با نسخه 3.5 هستیم. البته میتوان ارزانتر بودن در خارج و مسئله تحریم در داخل را هم در نظر داشت و نمونه عینی این مورد را میتوان در دستگاههای وارد شده در حوزه Compact، دید. البته شرکتهای تولید کننده خارجی که عمدتا در کشورهای جنوب شرق و شرق آسیا هستند، جزو شرکتهای مطرح در این زمینه هستند که بازارهای خوبی هم در کشورهای توسعه یافتهای چون آمریکا پیدا کردهاند.
در این بین برای عقب نماندن از تکنولوژیهای جدید بوجود آمده در حوزه دات نت مانند WCF این مقاله کمکی هر چند کوچک برای استفاده از این قابلیت موثر در فریمورک کامپکت میتواند باشد.
پیشنیازهای لازم:- Microsoft Visual Studio 2008 + Service Pack 1
- نصب Power Toys for .NET Compact Framework 3.5
پیاده سازی سرویس (بر روی سیستمی غیر از ویندوز کامپکت):
در ویژوال استودیو 2008 سرویس پک یک، پروژه ای از نوعclass library را ایجاد کرده و سرویسی تستی را برای استفاده ایجاد میکنیم:
[ServiceContract(Namespace = "http://samples.wcf.cfnet.sample")] public interface ICalculator { [OperationContract] int Add(int a, int b); }
و پیاده سازی آن:
public class CalculatorService : ICalculator { public static int count; public int Add(int a, int b) { count++; Console.WriteLine(string.Format("{3}\tReceived 'Add({0}, {1})' returning {2}", a, b, a + b, count)); return a + b; }
سرور سرویس:
برای هاست این سرویس از یک برنامهی کنسول که در سلوشن ایجاد میکنیم استفاده میکنیم. البته امکانهای دیگر برای هاست سرویس در هر پروسس دات نتی را میتوان یاد آور شد. برای هاست کردن شروع یک سرویس WCF باید یک IP درون شبکه را که قابل دسترسی از سمت ویندوز کامپکت بوده و به سیستم انتساب داده شده، دریافت و استفاده کنیم:
var addressList = Dns.GetHostEntry(Dns.GetHostName()); string hostIP = addressList.AddressList.Single(x=>x.ToString().StartsWith("192.168.10.")).ToString(); Uri address = new Uri(string.Format("http://{0}:8000/Calculator", hostIP));
در قطعه بالا IP در رنج مناسب و قابل دسترسی انتخاب میشود چون ویندوز کامپکت (فارق از اینکه در شبیه ساز باشد یا واقعی) از طریق شبکه به سرور دسترسی پیدا میکند باید IP مناسب انتساب داده شده انتخاب شود.
ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService),address); serviceHost.AddServiceEndpoint(typeof(ICalculator), new BasicHttpBinding(), "Calculator");
در ادامه یک سرویس هاست را new کرده و سرویس و بایندینگ را به آن در سازنده پاس میدهیم.
var serviceMetadataBehavior = new ServiceMetadataBehavior { HttpGetEnabled = true }; serviceHost.Description.Behaviors.Add(serviceMetadataBehavior);
این قسمت برای ادامه کارکرد سرویس لازم نیست ولی در ادامهی مقاله برای تولید کدهای سمت کلاینت باید این قابلیت فعال باشد و پس از آن دیگر احتیاجی نیست و میتوان این چند خط کد را کامنت کرد.
serviceHost.Open(); Console.WriteLine("CalculatorService is running at " + address.ToString()); Console.WriteLine("Press <ENTER> to terminate"); Console.ReadLine(); serviceHost.Close();
و در نهایت، شروع سرویس با فرمان Open و خاتمه آن با فرمان Close .
کلاینت سرویس (در داخل ویندوز کامپکت):
همراه با ارائه دات نت فریمورک 3.5 برای کار با سرویس WCF که از آن یک نسخهی ارائه شده برای کامپکت نیز تهیه شدهاست، ابزاری مانند netcfSvcUtil.exe که در SDK نسخهی کامپکت موجود است و کاربرد هندل کردن بعضی از موارد مانند تولید کد پروکسیهای سمت کلاینت را دارد که در ادامه طرز استفاده از آن را بررسی خواهیم کرد. بعد از اجرای سرویس WCF با رفتار HttpGetEnabled = true برای بررسی سریع کارکرد صحیح سرویس، آدرس آن را در مرورگر میبینیم. تصویر زیر نتیجهی آن در مرورگر است:
در خط فرمان به آدرس مربوط به این ابزار رفته (بسته به نسخهی سیستم عامل ممکن است در پوشههای زیر یافت شود ( :
(Windows Drive)\Program Files (x86)\Microsoft.NET\SDK\CompactFramework\v3.5\bin (Windows Drive)\Program Files\Microsoft.NET\SDK\CompactFramework\v3.5\bin
و فرمان زیر را اجرا میکنیم:
netcfSvcUtil.exe /language:C# /target:code /directory:D:\GeneratedCode\CF\CaculatorService http://192.168.10.189:8000/BooksService.svc?wsdl
البته ذکر IP شبکه در اینجا الزامی نیست؛ زیرا در صورت استفاده از آدرسهای داخلی سیستم، این فرمان به مشکلی بر نخواهد خورد. در این فرمان تولید کد با زبان c# و تولید کد که بصورت پیش فرض نیز وجود دارد و محل ذخیره سازی کدهای تولیدی را مشخص میکنیم و بعد از اجرای این فرمان، باید دو فایل در مسیر اشاره شده در فرمان تولید شود که اساس کار ما در سمت کلاینت خواهد بود:
کلاینت سرویس نیز با استفاده کدهای تولیدی بصورت زیر آماده سازی و اجرا میشود:
var addressList = Dns.GetHostEntry(Dns.GetHostName()); var localAddress = addressList.AddressList.Single(x => x.ToString().StartsWith("192.168.10.")).ToString();
دوباره IP مناسب در شبکه جاری استخراج میشود. بایندیگ مورد نیاز برای ارتباط با سرور ساخته میشود:
var binding = CalculatorClient.CreateDefaultBinding();
نکتهای که دراین قسمت باید مدنظر قرار گیرد این است که در زمان تولید کدها اگر از localhost یا 127.0.0.1 و یا آدرسهای دیگر انتساب داده شده به سرور استفاده کرده باشید در متد CreateDefaultBinding از همان آدرس استفاده میشود و برای اصلاح آن بصورت زیر عمل میکنیم:
string remoteAddress = CalculatorClient.EndpointAddress.Uri.ToString(); remoteAddress = remoteAddress.Replace("localhost", serviceAddress.Text);
یک EndpointAddress با استفاده از این آدرس ساخته و بههمراه بایندینگ، یک آبجکت از جنس CalculatorClient که در کدهای تولیدی داریم میسازیم:
CalculatorClient _client = new CalculatorClient(binding, endpoint);
برای تست نیز تنها متد این سرویس را با یک جفت عدد، صدا میزنیم:
var result = _client.Add(82, 18).ToString(CultureInfo.InvariantCulture);
به این ترتیب خروجی مورد نظر زیر را در کنسول سرویس مشاهده خواهیم کرد: