در این نوع راه حلها مستقیما به سرور دیتابیس وصل نمیشن. از یک واسط مثل ASP.NET Web API یا یک سرویس WCF استفاده میکنن تا ضریب امنیت رو بالا ببرن. برای کار با اینها یک REST Client کافی هست و در تمام سکوهای کاری یک نمونه از آن موجود هست.
نظرات مطالب
EF Code First #1
در هر فناوری مرتبط با دات نت که کل دات نت فریم ورک در دسترس باشد، قابل استفاده است. از WPF تا WinForms تا WCF و انواع و اقسام برنامههای وب؛ از ویندوز سرویس تا یک برنامهی کنسول ساده.
با درود و سپاس از همهی همراهان.
همانسان که پیشتر هم نوشته ام میتوانید سرویس WCF را در IIS یک سرور دیگر راهاندازی کنید و آدرس آیپی و یا DNS مربوط به آنرا در WinApp خود استفاده کنید.
هنوز به تنظیمات خاص Web.Config نرسیده ایم در آنجا به امنیت و محدودیتها خواهم پرداخت.
پیروز باشید.
نظرات مطالب
EF Code First #1
سیلورلایت به صورت مستقیم با هیچ نوع ORM ایی کار نمیکند (چون یک فناوری سمت کاربر است). اما شما در سمت سرور میتونید به کمک یک WCF سرویس و مشتقات مشابه آن با EF یا NH و غیره کار کنید و سپس نتیجه را در یک برنامه سیلورلایت مصرف کنید.
نظرات مطالب
آموزش سیلورلایت 4 - قسمتهای 21 تا 27
WCF RIA Services این امکان رو به شما میده که به منطق پیاده سازی شده در سمت سرور در برنامهی سمت کلاینت خودتون (یعنی همان برنامه سیلورلایت) به شکل شکیلی دسترسی داشته باشید. به این صورت دیگر نیازی نخواهد بود مدلی را سمت کاربر پیاده سازی کنید، اعتبار سنجی را این طرف هم مجددا اعمال کنید، هر بار که WCF Service تغییر کرد، در سمت کلاینت مجبور به به روز رسانی باشید و خیلی موارد دیگر. تمام اینها یکبار در سمت سرور پیاده سازی میشود و سپس توسط فریم ورک WCF RIA Services در سمت کلاینت قابل دسترسی خواهد بود.
بنابراین مدل قابل استفاده در سمت کلاینت هم از این سرویس سمت سرور دریافت میشود و جالب اینجا است که تمام مباحث مطلع سازی تغییرات خواص، اعتبار سنجی، نکات ریز binding و غیره در این مدلهای WCF RIA Services به صورت خودکار گنجانده شده و بار کدنویسی شما بسیار کمتر میشود.
برای MVVM ، کنترل domain data source را با کد نویسی تولید میکنم تا بتونم در View Model استفاده کنم و از حالت متداول کشیدن و رها کردن این کنترل روی فرم که با اصول MVVM سازگار نیست به این صورت رها خواهم شد.
بنابراین مدل قابل استفاده در سمت کلاینت هم از این سرویس سمت سرور دریافت میشود و جالب اینجا است که تمام مباحث مطلع سازی تغییرات خواص، اعتبار سنجی، نکات ریز binding و غیره در این مدلهای WCF RIA Services به صورت خودکار گنجانده شده و بار کدنویسی شما بسیار کمتر میشود.
برای MVVM ، کنترل domain data source را با کد نویسی تولید میکنم تا بتونم در View Model استفاده کنم و از حالت متداول کشیدن و رها کردن این کنترل روی فرم که با اصول MVVM سازگار نیست به این صورت رها خواهم شد.
تشریح مسئله : KnownTypeAttribute چیست و چگونه از آن استفاده کنیم؟
پیش نیاز : آشنایی اولیه با مفاهیم WCF برای فهم بهتر مطالب
در ابتدا یک WCf Service Application ایجاد کنید و مدل زیر را بسازید:
یک کلاس پایه برای Person ایجاد کردیم به صورت abstract که وهله سازی از آن میسر نباشد و 2 کلاس دیگر میسازیم که از کلاس بالا ارث ببرند:
کلاس #1
کلاس #2
فرض کنید قصد داریم سرویسی ایجاد کنیم که لیست تمام اشخاص موجود در سیستم را در اختیار ما قرار دهد. (هم Student و هم Teacher). ابتدا Contract مربوطه را به صورت زیر تعریف میکنیم:
همان طور که میبینید خروجی متد GetAll از نوع Person است (نوع پایه کلاس Student , Teacher) . سرویس مربوطه بدین شکل خواهد شد.
در این سرویس در متد GetAll لیستی از تمام اشخاص رو ایجاد میکنیم . 3 تا Student و 3 تا Teacher رو به این لیست اضافه میکنیم. برای نمایش اطلاعات در خروجی یک پروژه Console Application ایجاد کنید و سرویس بالا رو از روش AddServiceReference به پروژه اضافه کنید سپس در کلاس Program کدهای زیر رو کپی کنید.
پروژه رو کامپایل کنید. تا اینجا هیچ گونه مشکلی مشاهده نشد و انتظار داریم که خروجی مورد نظر رو مشاهده کنیم. بعد از اجرای پروژه با خطای زیر متوقف میشویم:
مشکل از اینجا ناشی میشود که هنگام عمل سریالایز ، WCF Runtime با توجه به وهله سازی از کلاس Person میدونه که باید کلاس Student یا Teacher رو سریالایز کنه ولی در هنگام عمل دی سریالایز، WCF Runtime این موضوع رو درک نمیکنه به همین دلیل یک Communication Exception پرتاب میکنه. برای حل این مشکل و برای اینکه WCF Deserialize Engine رو متوجه نوع وهله سازی کلاسهای مشتق شده از کلاس پایه کنیم باید از KnownTypeAttribute استفاده کنیم. فقط کافیست که این Attribute رو بالای کلاس Person به ازای تمام کلاسهای مشتق شده از اون قرار بدید.بدین صورت:
حالا پروژه سمت سرور رو دوباره کامپایل کنید و سرویس سمت کلاینت رو Update کنید. بعد پروژه رو دوباره اجرا کرده تا خروجی زیر رو مشاهده کنید.
با وجود KnownType دیگه WCF Deserialize Engine میدونه که باید از کدام DataContact برای عمل دی سریالاز نمونه ساخته شده از کلاس Person استفاده کنه. دانستن این مطلب هنگام پیاده سازی مفاهیم ارث بری در ORM ها زمانی که از WCF استفاده میکنیم ضروری است.
پیش نیاز : آشنایی اولیه با مفاهیم WCF برای فهم بهتر مطالب
در ابتدا یک WCf Service Application ایجاد کنید و مدل زیر را بسازید:
[DataContract]
public abstract class Person
{
[DataMember]
public int Code { get; set; }
[DataMember]
public string Name { get; set; }
}
کلاس #1
[DataContract] public class Student : Person { [DataMember] public int StudentId { get; set; } }
[DataContract] public class Teacher : Person { public int TeacherId { get; set; } }
[ServiceContract] public interface IStudentService { [OperationContract] IEnumerable<Person> GetAll(); }
public class StudentService : IStudentService { public IEnumerable<Person> GetAll() { List<Person> listOfPerson = new List<Person>(); listOfPerson.Add( new Student() { Code = 1, StudentId = 123, Name = "Masoud Pakdel" } ); listOfPerson.Add( new Student() { Code = 1, StudentId = 123, Name = "Mostafa Asgari"} ); listOfPerson.Add( new Student() { Code = 1, StudentId = 123, Name = "Saeed Alizadeh"} );
listOfPerson.Add( new Teacher() { Code = 1, TeacherId = 321, Name = "Mahdi Rad"} ); listOfPerson.Add( new Teacher() { Code = 1, TeacherId = 321, Name = "Mohammad Heydari" } ); listOfPerson.Add( new Teacher() { Code = 1, TeacherId = 321, Name = "Saeed Khatami"} ); return listOfPerson; }
class Program { static void Main( string[] args ) { StudentService.StudentServiceClient client = new StudentService.StudentServiceClient(); client.GetAll().ToList().ForEach( _record => { Console.Write( "Name : {0}", _record.Name ); Console.WriteLine( "Code : {0}", _record.Code ); } ); Console.ReadLine(); } }
مشکل از اینجا ناشی میشود که هنگام عمل سریالایز ، WCF Runtime با توجه به وهله سازی از کلاس Person میدونه که باید کلاس Student یا Teacher رو سریالایز کنه ولی در هنگام عمل دی سریالایز، WCF Runtime این موضوع رو درک نمیکنه به همین دلیل یک Communication Exception پرتاب میکنه. برای حل این مشکل و برای اینکه WCF Deserialize Engine رو متوجه نوع وهله سازی کلاسهای مشتق شده از کلاس پایه کنیم باید از KnownTypeAttribute استفاده کنیم. فقط کافیست که این Attribute رو بالای کلاس Person به ازای تمام کلاسهای مشتق شده از اون قرار بدید.بدین صورت:
[DataContract] [KnownType( typeof( Student ) )] [KnownType( typeof(Teacher) )] public abstract class Person { [DataMember] public int Code { get; set; } [DataMember] public string Name { get; set; } }
با وجود KnownType دیگه WCF Deserialize Engine میدونه که باید از کدام DataContact برای عمل دی سریالاز نمونه ساخته شده از کلاس Person استفاده کنه. دانستن این مطلب هنگام پیاده سازی مفاهیم ارث بری در ORM ها زمانی که از WCF استفاده میکنیم ضروری است.
پیشتر در مورد ELMAH مطلبی را منتشر کرده بودم و اگر برنامه نویس ASP.NET هستید و با ELMAH آشنایی ندارید، جدا نیمی از عمر کاری شما بر فنا است!
هاست پیش فرض یک WCF RIA Service هم یک برنامهی ASP.NET است. بنابراین کلیهی خطاهای رخ داده در سمت سرور را باید بتوان به نحوی لاگ کرد تا بعدا با مطالعهی آنها اطلاعات ارزشمندی را از نقایص برنامه در عمل و پیش از گوشزد شدن آنها توسط کاربران، دریافت، بررسی و رفع کرد.
کلیه خطاها را لاگ میکنم تا:
- بدانم معنای جملهی "برنامه کار نمیکنه" چی هست.
- بدون روبرو شدن با کاربران یا حتی سؤال و جوابی از آنها بدانم دقیقا مشکل از کجا ناشی شده.
- بدانم رفتارهای عمومی کاربران که منجر به بروز خطا میشوند کدامها هستند.
- بدانم در کدامیک از قسمتهای برنامه تعیین اعتبار ورودی کاربران یا انجام نشده یا ضعیف و ناکافی است.
- بدانم زمانیکه دوستی (!) قصد پایین آوردن برنامه را با تزریق SQL داشته، دقیقا چه چیزی را وارد کرده، در کجا و چه زمانی؟
- بتوانم Remote worker خوبی باشم.
ELMAH هم برای لاگ کردن خطاهای مدیریت نشدهی یک برنامهی ASP.NET ایجاد شده است. بنابراین باید بتوان این دو (WCF RIA Services و ELMAH) را به نحوی با هم سازگار کرد. برای اینکار نیاز است تا یک مدیریت کنندهی خطای سفارشی را با پیاده سازی اینترفیس IErrorHandler تهیه کنیم (تا خطاهای مدیریت نشدهی حاصل را به سمت ELMAH هدایت کند) و سپس آنرا به کمک یک ویژگی یا Attribute به DomainService خود جهت لاگ کردن خطاها اعمال نمائیم. روش تعریف این Attribute را در کدهای بعد ملاحظه خواهید نمود (در اینجا نیاز است تا دو ارجاع را به اسمبلیهای Elmah.dll که دریافت کردهاید و اسمبلی استاندارد System.ServiceModel نیز به پروژه اضافه نمائید):
//add a reference to "Elmah.dll"
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Web;
namespace ElmahWcf
{
public class HttpErrorHandler : IErrorHandler
{
#region IErrorHandler Members
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error == null)
return;
if (HttpContext.Current == null) //In case we run outside of IIS
return;
Elmah.ErrorSignal.FromCurrentContext().Raise(error);
}
#endregion
}
}
//add a ref to "System.ServiceModel" assembly
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace ElmahWcf
{
public class ServiceErrorBehaviorAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;
public ServiceErrorBehaviorAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}
#region IServiceBehavior Members
public void AddBindingParameters(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{ }
public void ApplyDispatchBehavior(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
cd.ErrorHandlers.Add(errorHandler);
}
}
public void Validate(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{ }
#endregion
}
}
[ServiceErrorBehavior(typeof(HttpErrorHandler))] //Integrating with ELMAH
[EnableClientAccess()]
public partial class MyDomainService : LinqToEntitiesDomainService<myEntities>
در ادامه نحوهی افزودن تعاریف متناظر با ELMAH به Web.Config برنامه ذکر شده است. این تعاریف برای IIS6 و 7 به بعد هم تکمیل گردیده است. خطاها هم به صورت فایلهای XML در پوشهای به نام Errors که به ریشهی سایت اضافه خواهید نمود (یا هر پوشهی دلخواه دیگری)، لاگ میشوند.
به نظر من این روش، از ذخیره سازی اطلاعات لاگها در دیتابیس بهتر است. چون اساسا زمانیکه خطایی رخ میدهد شاید مشکل اصلی همان ارتباط با دیتابیس باشد.
قسمت ارسال خطاها به صورت ایمیل نیز comment شده است که در صورت نیاز میتوان آنرا فعال نمود:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>
<elmah>
<security allowRemoteAccess="1" />
<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/Errors" />
<!-- <errorMail
from="errors@site.net"
to="nasiri@site.net"
subject="prj-error"
async="true"
smtpPort="25"
smtpServer="mail.site.net"
noYsod="true" /> -->
</elmah>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="DomainServiceModule"
preCondition="managedHandler"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add name="Elmah" verb="POST,GET,HEAD" path="myelmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</handlers>
</system.webServer>
<system.web>
<globalization
requestEncoding="utf-8"
responseEncoding="utf-8"
/>
<authentication mode="Forms">
<!--one month ticket-->
<forms name=".403AuthV"
cookieless="UseCookies"
slidingExpiration="true"
protection="All"
path="/"
timeout="43200" />
</authentication>
<httpHandlers>
<add verb="POST,GET,HEAD" path="myelmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="DomainServiceModule"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</httpModules>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</assemblies>
</compilation>
</system.web>
<connectionStrings>
</connectionStrings>
<system.serviceModel>
<serviceHostingEnvironment
aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
throw new Exception("This is an ELMAH test");
سپس به آدرس http://localhost/myelmah.axd مراجعه نموده و اطلاعات لاگ شده حاصل را بررسی کنید:
این روش با WCF Services های متداول هم کار میکند. فقط در این سرویسها باید aspNetCompatibilityEnabled مطابق تگهای ذکر شدهی system.serviceModel فوق در web.config لحاظ شوند (این مورد به صورت پیش فرض در WCF RIA Services وجود دارد). همچنین ویژگی زیر نیز باید به سرویس شما اضافه گردد:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
منابع مورد استفاده:
Integrating ELMAH for a WCF Service
Making WCF and ELMAH play nice together
Getting ELMAH to work with WCF services
پ.ن.
اگر به خطاهای ASP.NET دقت کرده باشید که به yellow screen of death هم مشهور هستند (در مقابل صفحات آبی ویندوز!)، ابتدای آن خیلی بزرگ نوشته شده Server Error و سپس ادامهی خطا. همین مورد دقیقا یادم هست که هر بار سبب بازخواست مدیران شبکه بجای برنامه نویسها میشد! (احتمالا این هم یک نوع بدجنسی تیم ASP.NET برای گرفتن حال ادمینهای شبکه است! و گرنه مثلا میتوانستند همان ابتدا بنویسند program/application error بجای server error)
ممنون ولی سوال بنده کلی بود؟ وقتی یک معماری دارم بگونه ای گفته شد یا مثل
cms IRIS آقای سعیدی فر و خواستم به پروژه پروژه دیگری اضافه کنم از نوع
webapi یا wcf که به نوعی از لایه service هم برای اتصال به بانک استفاده
میکنه DI را باید چگونه برای آن اعمال کرد ؟ آیا میبایست تنظیمات و
mapping های داخل global مربوط به structuremap درون ui را در داخل
پروژه webapi یا wcf هم قرار داد یا خیر؟ اگر webapi را جدا هاست کنیم چه
تضمینی وجود دارد دیگر به پروژه webapi دسترسی نداشته باشد
هنگام تولید و توسعه سیستمهای مبتنی بر WCF حتما
نیاز به سرویس هایی داریم که متدها را به صورت Async اجرا کنند. در دات نت
4.5 از Async&Await استفاده میکنیم(^). ولی در پروژه هایی که تحت دات نت 4 هستند این امکان وجود ندارد(البته میتونید Async&Await CTP رو برای دات نت 4 هم نصب کنید(^
)). فرض کنید پروژه ای داریم تحت دات نت 3.5 یا 4 و قصد داریم یکی از
متدهای سرویس WCF آن را به صورت Async پیاده سازی کنیم. سادهترین روش این
است که هنگام Add Service Reference از پنجره Advanced به صورت زیر عمل
کنیم:
مهمترین عیب این روش این است که در این حالت تمام متدهای این سرویس رو هم به صورت Sync و هم به صورت Async تولید میکنه در حالی که ما فقط نیاز به یک متد Async داریم.
در این پست قصد دارم پیاده سازی این متد رو بدون استفاده از Async&Await و Code Generation توکار دات نت شرح بدم که با دات نت 3.5 هم سازگار است.
ابتدا یک پروژه از نوع WCF Service Application ایجاد کنید.
یک ClassLibrary جدید به نام Model بسازید و کلاس زیر را به عنوان مدل در آن قرار دهید.(این اسمبلی باید هم به پروژههای کلاینت و هم به پروژههای سرور رفرنس داده شود)
[DataContract] public class Book { [DataMember] public int Code { get; set; } [DataMember] public string Title { get; set; } [DataMember] public string Author { get; set; } }
#Class Library به نام Contract بسازید. قصد داریم از این لایه به عنوان قراردادهای سمت کلاینت و سرور استفاده کنیم. اینترفیس زیر را به عنوان BookContract در آن بسازید.
[ServiceContract] public interface IBookService { [OperationContract( AsyncPattern = true )] IAsyncResult BeginGetAllBook( AsyncCallback callback, object state ); IEnumerable<Book> EndGetAllBook( IAsyncResult asyncResult ); }
برای پیاده سازی متدهای Async به این روش باید دو
متد داشته باشیم. یکی به عنوان شروع عملیات و دیگری اتمام. دقت کنید نام
گذاری به صورت Begin و End کاملا اختیاری است و برای خوانایی بهتر از این
روش نام گذاری استفاده میکنم. متدی که به عنوان شروع عملیات استفاده
میشود باید حتما OperationContractAttribute رو داشته باشد و مقدار خاصیت
AsyncPattern اون هم true باشد. همان طور که میبیند این متد دارای 2
آرگومان وروردی است. یکی از نوع AsyncCallback و دیگری از نوع object. تمام
متدهای Async به این روش باید این دو آرگومان ورودی را حتما داشته باشند.
خروجی این متد حتما باید از نوع IAsyncResult باشد. متد دوم که به عنوان
اتمام عملیات استفاده میشود نباید
OperationContractAttribute را داشته باشد. ورودی اون هم فقط یک آرگومان از
نوع IAsyncResult است. خروجی اون هم هر نوعی که سمت کلاینت احتیاج دارید
میتونه باشه . در صورت عدم رعایت نکات فوق، هنگام ساخت ChannelFactory یا
خطا روبرو خواهید شد. اگر نیاز به پارامتر دیگری هم داشتید باید آنها را
قبل از این دو پارامتر قرار دهید. برای مثال:
[OperationContract] IEnumerable<Book> GetAllBook(int code , AsyncCallback callback, object state );
public class CompletedAsyncResult<TEntity> : IAsyncResult where TEntity : class , new() { public IList<TEntity> Result { get { return _result; } set { _result = value; } } private IList<TEntity> _result; public CompletedAsyncResult( IList<TEntity> data ) { this.Result = data; } public object AsyncState { get { return ( IList<TEntity> )Result; } } public WaitHandle AsyncWaitHandle { get { throw new NotImplementedException(); } } public bool CompletedSynchronously { get { return true; } } public bool IsCompleted { get { return true; } } }
در کلاس بالا یک خاصیت به نام Result درنظر گرفتم که لیستی از نوع TEntity
است.(TEntityبه صورت generic تعریف شده و نوع ورودی آن هر نوع کلاس غیر
abstract میتواند باشد). این کلاس برای تمام سرویسهای Async یک پروژه
مورد استفاده قرار خواهد گرفت برای همین ورودی آن به صورت generic در نظر
گرفته شده است.
#پیاده سازی سرویسpublic class BookService : IBookService { public BookService() { ListOfBook = new List<Book>(); } public List<Book> ListOfBook { get; private set; } private List<Book> CreateListOfBook() { Parallel.For( 0, 10000, ( int counter ) => { ListOfBook.Add( new Book() { Code = counter, Title = String.Format( "Book {0}", counter ), Author = "Masoud Pakdel" } ); } ); return ListOfBook; } public IAsyncResult BeginGetAllBook( AsyncCallback callback, object state ) { var result = CreateListOfBook(); return new CompletedAsyncResult<Book>( result ); } public IEnumerable<Book> EndGetAllBook( IAsyncResult asyncResult ) { return ( ( CompletedAsyncResult<Book> )asyncResult ).Result; } }
*در متد BeginGetAllBook ابتدا به تعداد 10,000 کتاب در یک لیست ساخته
میشوند و بعد این لیست در کلاس CompletedAsyncResult که ساختیم به عنوان
ورودی سازنده پاس داده میشوند. چون CompletedAsyncResult از IAsyncResult
ارث برده است پس return آن به عنوان خروجی مانعی ندارد. با فراخوانی متد
EndGetAllBook سمت کلاینت مقدار asyncResult به عنوان خروجی برگشت داده
میشود. به عملیات casting برای دستیابی به مقدار Result در
CompletedAsyncResult دقت کنید.
#کدهای سمت کلاینت:
اکثر برنامه نویسان با استفاده از روش AddServiceReference یک سرویس کلاینت در اختیار خواهند داشت که با وهله سازی از این کلاس یک ChannelFactory ایجاد میشود. در این پست به جای استفاده از Code Generation توکار دات نت برای ساخت ChannelFactory از روش دیگری استفاده خواهیم کرد. به عنوان برنامه نویس باید بدانیم که در پشت پرده عملیات ساخت ChannelFactory چگونه است.
روش AddServiceReference#کدهای سمت کلاینت:
اکثر برنامه نویسان با استفاده از روش AddServiceReference یک سرویس کلاینت در اختیار خواهند داشت که با وهله سازی از این کلاس یک ChannelFactory ایجاد میشود. در این پست به جای استفاده از Code Generation توکار دات نت برای ساخت ChannelFactory از روش دیگری استفاده خواهیم کرد. به عنوان برنامه نویس باید بدانیم که در پشت پرده عملیات ساخت ChannelFactory چگونه است.
بعد از اضافه شدن سرویس سمت کلاینت کدهای زیر برای سرویس Book به صورت زیر تولید میشود.
[System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] public partial class BookServiceClient : System.ServiceModel.ClientBase<UI.BookService.IBookService>, UI.BookService.IBookService { public BookServiceClient() { } public BookServiceClient(string endpointConfigurationName) : base(endpointConfigurationName) { } public BookServiceClient(string endpointConfigurationName, string remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public BookServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public BookServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { } public UI.BookService.Book[] BeginGetAllBook() { return base.Channel.BeginGetAllBook(); } }
همانطور که میبینید سرویس بالا از کلاس ClientBase
ارث برده است. ClientBase دارای خاصیتی به نام ChannelFactory است که فقط
خواندنی میباشد. با استفاده از مقادیر EndPointConfiguration یک وهله از
کلاس ChannelFactory با توجه به مقدار generic کلاس clientBase ایجاد خواهد
شد. در کد زیر مقدار TChannel برابر IBookService است:
System.ServiceModel.ClientBase<UI.BookService.IBookService>
وهله سازی از ChannelFactory به صورت دستی
یک پروژه ConsoleApplication سمت کلاینت ایجاد کنید. برای فراخوانی متدهای سرویس سمت سرور باید ابتدا تنظیمات EndPoint رو به درستی انجام دهید. سپس با استفاده از EndPoint به راحتی میتوانیم Channel مربوطه را بسازیم.
کلاسی به نام ServiceMapper ایجاد میکنیم که وظیفه آن ساخت ChannelFactory به ازای درخواستها است.
در متد CreateChannel یک وهله از کلاس EndPointAddress ایجاد شده است. پارامتر ورودی آن آدرس سرویس هاست شده میباشد برای مثال:
دستور Remove برای حذف I از ابتدای نام سرویس است. پارامترهای ورودی برای
سازنده کلاس ChannelFactory ابتدا یک نمونه از کلاس BasicHttpBinding میباشد. میتوان از WSHttpBinding یا NetTCPBinding یا WSDLHttpBinding هم استفاده
کرد. البته هر کدام از انواع Bindingها تنظیمات خاص خود را میطلبد که در
مقاله ای جداگانه بررسی خواهم کرد. پارامتر دوم هم EndPoint ساخته شده میباشد. در نهایت با دستور CreateChannel عملیات ساخت Channel به پایان میرسد.
بعد از اعمال تغییرات زیر در فایل Program پروژه Console و اجرای آن، خروجی به صورت زیر میباشد.
همان طور که میبینید ورودی متد BeginGtAllBook یک AsyncCallback است که در داخل آن متد EndGetAllBook صدا زده شده است. مقدار برگشتی متد EndGetAllBook خروجی مورد نظر ماست.
خروجی :
یک پروژه ConsoleApplication سمت کلاینت ایجاد کنید. برای فراخوانی متدهای سرویس سمت سرور باید ابتدا تنظیمات EndPoint رو به درستی انجام دهید. سپس با استفاده از EndPoint به راحتی میتوانیم Channel مربوطه را بسازیم.
کلاسی به نام ServiceMapper ایجاد میکنیم که وظیفه آن ساخت ChannelFactory به ازای درخواستها است.
public class ServiceMapper<TChannel> { public static TChannel CreateChannel() { TChannel proxy; var endPointAddress = new EndpointAddress( "http://localhost:7000/" + typeof( TChannel ).Name.Remove( 0, 1 ) + ".svc" ); var httpBinding = new BasicHttpBinding(); ChannelFactory<TChannel> factory = new ChannelFactory<TChannel>( httpBinding, endPointAddress ); proxy = factory.CreateChannel(); return proxy; } }
"http://localhost:7000/" + "BookService.svc"
بعد از اعمال تغییرات زیر در فایل Program پروژه Console و اجرای آن، خروجی به صورت زیر میباشد.
var channel = ServiceMapper<Contract.IBookService>.CreateChannel(); channel.BeginGetAllBook( new AsyncCallback( ( asyncResult ) => { channel.EndGetAllBook( asyncResult ).ToList().ForEach( _record => { Console.WriteLine( _record.Title ); } ); } ) , null ); Console.WriteLine( "Loading..." ); Console.ReadLine();
خروجی :
نکته: برای اینکه مطمئن شوید که سرویس مورد نظر در آدرس "http"//localhost:7000/" هاست شده است(یعنی همان آدرسی که در EndPointAddress از آن استفاه کردیم) کافیست از پنجره Project Properties برای پروژه سرویس وارد برگه Web شده و از بخش Servers گزینه Use Visual Studio Development Server و Specific Port 7000 رو انتخاب کنید.
اشتراکها
پروژه WebApiProxy
هنگام استفاده از web service یا wcf کلاسهای proxy فراخوانی سرویسها با استفاده از metadata سرویس تولید میشد. بنابراین کار خیلی راحت بود. اما برای webapi قضیه چنین نیست... این پروژه یک بخش سمت سروری دارد که و یک کتابخانه کلاینتی. سمت سرور یک سرویس متادیتا برای تمامی apiها ایجاد میکند و سمت کلاینت با متصل شدن به آن تمامی کلاسهای مربوطه را generate میکند. این عمل ایضا در سطح javascript هم انجام میشود. مزیت فوق العاده ای است.