مهمترین عیب این روش این است که در این حالت تمام متدهای این سرویس رو هم به صورت 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 ); }
[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; } } }
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; } }
#کدهای سمت کلاینت:
اکثر برنامه نویسان با استفاده از روش 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(); } }
System.ServiceModel.ClientBase<UI.BookService.IBookService>
یک پروژه 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 رو انتخاب کنید.