کار کردن با پروتکل Ping-back آنچنان ساده نیست؛ از این جهت که تبادل ارتباطات آن با پروتکل XML-RPC انجام میشود. XML-RPC نیز توسط PHP کارها بیشتر مورد استفاده قرار میگیرد؛ بجای استفاده از پروتکلهای استاندارد وب سرویسها مانند Soap و امثال آن. پیاده سازیهای ابتدایی Pingback نیز مرتبط است به Wordpress معروف که با PHP تهیه شدهاست. در ادامه نگاهی خواهیم داشت به جزئیات پیاده سازی ارسال ping-back توسط برنامههای ASP.NET.
یافتن آدرس وب سرویس سایت پذیرای Pingback
اولین قدم در پیاده سازی Pingback، یافتن آدرسی است که باید اطلاعات مورد نظر را به آن ارسال کرد. این آدرس عموما به دو طریق ارائه میشود:
الف) در هدری به نام x-pingback و یا pingback
ب) در قسمتی از کدهای HTML صفحه به شکل
<link rel="pingback" href="pingback server">
همانطور که ملاحظه میکنید، نیاز است Response header را آنالیز کنیم.
private Uri findPingbackServiceUri() { var request = (HttpWebRequest)WebRequest.Create(_targetUri); request.UserAgent = UserAgent; request.Timeout = Timeout; request.ReadWriteTimeout = Timeout; request.Method = WebRequestMethods.Http.Get; request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (var response = request.GetResponse() as HttpWebResponse) { if (response == null) return null; var url = extractPingbackServiceUriFormHeaders(response); if (url != null) return url; if (!isResponseHtml(response)) return null; using (var reader = new StreamReader(response.GetResponseStream())) { return extractPingbackServiceUriFormPage(reader.ReadToEnd()); } } } private static Uri extractPingbackServiceUriFormHeaders(WebResponse response) { var pingUrl = response.Headers.AllKeys.FirstOrDefault(header => header.Equals("x-pingback", StringComparison.OrdinalIgnoreCase) || header.Equals("pingback", StringComparison.OrdinalIgnoreCase)); return getValidAbsoluteUri(pingUrl); } private static Uri extractPingbackServiceUriFormPage(string content) { if (string.IsNullOrWhiteSpace(content)) return null; var regex = new Regex(@"(?s)<link\srel=""pingback""\shref=""(.+?)""", RegexOptions.IgnoreCase); var match = regex.Match(content); return (!match.Success || match.Groups.Count < 2) ? null : getValidAbsoluteUri(match.Groups[1].Value); } private static Uri getValidAbsoluteUri(string url) { Uri absoluteUri; return string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out absoluteUri) ? null : absoluteUri; } private static bool isResponseHtml(WebResponse response) { var contentTypeKey = response.Headers.AllKeys.FirstOrDefault(header => header.Equals("content-type", StringComparison.OrdinalIgnoreCase)); return !string.IsNullOrWhiteSpace(contentTypeKey) && response.Headers[contentTypeKey].StartsWith("text/html", StringComparison.OrdinalIgnoreCase); }
targetUri، آدرسی است از یک سایت دیگر که در سایت ما درج شدهاست. زمانیکه این صفحه را درخواست میکنیم، response.Headers.AllKeys حاصل میتواند حاوی کلید x-pingback باشد یا خیر. اگر بلی، همینجا کار پایان مییابد. فقط باید مطمئن شد که این آدرس مطلق است و نه نسبی. به همین جهت در متد getValidAbsoluteUri، بررسی بر روی UriKind.Absolute انجام شدهاست.
اگر هدر فاقد کلید x-pingback باشد، قسمت ب را باید بررسی کرد. یعنی نیاز است محتوای Html صفحه را برای یافتن link rel=pingback بررسی کنیم. همچنین باید دقت داشت که پیش از اینکار نیاز است حتما بررسی isResponseHtml صورت گیرد. برای مثال در سایت شما لینکی به یک فایل 2 گیگابایتی SQL Server درج شدهاست. در این حالت نباید ابتدا 2 گیگابایت فایل دریافت شود و سپس بررسی کنیم که آیا محتوای آن حاوی link rel=pingback است یا خیر. اگر محتوای ارسالی از نوع text/html بود، آنگاه کار دریافت محتوای لینک انجام خواهد شد.
ارسال Ping به آدرس سرویس Pingback
اکنون که آدرس سرویس pingback یک سایت را یافتهایم، کافی است ping ایی را به آن ارسال کنیم:
public void Send() { var pingUrl = findPingbackServiceUri(); if (pingUrl == null) throw new NotSupportedException(string.Format("{0} doesn't support pingback.", _targetUri.Host)); sendPing(pingUrl); } private void sendPing(Uri pingUrl) { var request = (HttpWebRequest)WebRequest.Create(pingUrl); request.UserAgent = UserAgent; request.Timeout = Timeout; request.ReadWriteTimeout = Timeout; request.Method = WebRequestMethods.Http.Post; request.ContentType = "text/xml"; request.ProtocolVersion = HttpVersion.Version11; makeXmlRpcRequest(request); using (var response = (HttpWebResponse)request.GetResponse()) { response.Close(); } } private void makeXmlRpcRequest(WebRequest request) { var stream = request.GetRequestStream(); using (var writer = new XmlTextWriter(stream, Encoding.ASCII)) { writer.WriteStartDocument(true); writer.WriteStartElement("methodCall"); writer.WriteElementString("methodName", "pingback.ping"); writer.WriteStartElement("params"); writer.WriteStartElement("param"); writer.WriteStartElement("value"); writer.WriteElementString("string", Uri.EscapeUriString(_sourceUri.ToString())); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteStartElement("param"); writer.WriteStartElement("value"); writer.WriteElementString("string", Uri.EscapeUriString(_targetUri.ToString())); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); } }
در اینجا sourceUri آدرس صفحهای در سایت ما است که targetUri ایی (آدرسی از سایت دیگر) در آن درج شدهاست. در یک pinback، صرفا این دو آدرس به سرویس دریافت کنندهی pingback ارسال میشوند.
سپس سایت دریافت کنندهی ping، ابتدا sourceUri را دریافت میکند تا عنوان آنرا استخراج کند و همچنین بررسی میکند که آیا targetUri، در آن درج شدهاست یا خیر (آیا spam است یا خیر)؟
تا اینجا اگر این مراحل را کنار هم قرار دهیم به کلاس Pingback ذیل خواهیم رسید:
Pingback.cs
نحوهی استفاده از کلاس Pingback تهیه شده
کار ارسال Pingback عموما به این نحو است: هر زمانیکه مطلبی یا یکی از نظرات آن، ثبت یا ویرایش میشوند، نیاز است Pingbackهای آن ارسال شوند. بنابراین تنها کاری که باید انجام شود، استخراج لینکهای خارجی یک صفحه و سپس فراخوانی متد Send کلاس فوق است.
یافتن لینکهای یک محتوا را نیز میتوان مانند متد extractPingbackServiceUriFormPage فوق، توسط یک Regex انجام داد و یا حتی با استفاده از کتابخانهی معروف HTML Agility Pack:
var doc = new HtmlWeb().Load(url); var linkTags = doc.DocumentNode.Descendants("link"); var linkedPages = doc.DocumentNode.Descendants("a") .Select(a => a.GetAttributeValue("href", null)) .Where(u => !String.IsNullOrEmpty(u));
WAS یا Windows Process Activation Service
پیکربندی مدیریتی در WAS
- Global configuration information
- Protocol configuration information for both HTTP and non-HTTP protocols
- Application pool configuration, such as the process account information
- Site configuration, such as bindings and applications
- Application configuration, such as the enabled protocols and the application pools to which the applications belong
مدیریت پروسهها Process Managment
نکته: از آنجایی که WAS هم پروسههای http و هم non-http را مدیریت میکند، پس میتوانید از یک applicatio pool برای چندین protocol استفاده کنید. به عنوان مثال شما یکی سرویس XML دارید که میتوانید از آن برای سرویس دهی به پروتوکلهای Http و net.tcp بهره بگیرید.
ماژولها در IIS
ماژولهای کد ماشین یا native
جدول ماژولهای HTTP
نام ماژول | توضیحات | نام فایل منبع |
CustomErrorModule | موقعی که هنگام response، کد خطایی تولید میگردد، پیام خطا را پیکربندی و سپس ارسال میکند. | Inetsrv\Custerr.dll |
HttpRedirectionModule | تنظمیات redirection برای درخواستهای http را در دسترس قرار میدهد. | Inetsrv\Redirect.dll |
ProtocolSupportModule | انجام عملیات مربوط به پروتوکلها بر عهده این ماژول است؛ مثل تنظیم کردن قسمت هدر برای response. | Inetsrv\Protsup.dll |
RequestFilteringModule | این ماژول از IIS 7.5 به بعد اضافه شد. درخواستها را فیلتر میکند تا پروتوکل و رفتار محتوا را کنترل کند. | Inetsrv\modrqflt.dll |
WebDAVModule | این ماژول از IIS 7.5 به بعد اضافه شد. امنیت بیشتر در هنگام انتشار محتوا روی HTTP SSL | Inetsrv\WebDAV.dll |
ماژولهای امنیتی
نام ماژول | توضیحات | نام فایل منبع |
AnonymousAuthenticationModule | موقعی که هیچ کدام از عملیات authentication با موفقیت روبرو نشود، عملیات Anonymous authentication انجام میشود. | Inetsrv\Authanon.dll |
BasicAuthenticationModule | عمل ساده و اساسی authentication را انجام میدهد. | Inetsrv\Authbas.dll |
CertificateMappingAuthenticationModule | انجام عمل Certificate Mapping authentication در Active Directory | Inetsrv\Authcert.dll |
DigestAuthenticationModule | Digest authentication | Inetsrv\Authmd5.dll |
IISCertificateMappingAuthenticationModule | همان Certificate Mapping authentication ولی اینبار با IIS Certificate . | Inetsrv\Authmap.dll |
RequestFilteringModule | عملیات اسکن URL از قبیل نام صفحات و دایرکتوریها ، توع verb و یا کاراکترهای مشکوک و خطرآفرین | Inetsrv\Modrqflt.dll |
UrlAuthorizationModule | عمل URL authorization | Inetsrv\Urlauthz.dll |
WindowsAuthenticationModule | عمل NTLM integrated authentication | Inetsrv\Authsspi.dll |
IpRestrictionModule | محدود کردن IPهای نسخه 4 لیست شده در IP Security در قسمت پیکربندی | Inetsrv\iprestr.dll |
ماژولهای محتوا
نام ماژول | توضیحات | نام فایل منبع |
CgiModule | ایجاد پردازشهای (Common Gateway Interface (CGI به منظور ایجاد خروجی response | Inetsrv\Cgi.dll |
DefaultDocumentModule | تلاش برای ساخت یک سند پیش فرض برای درخواست هایی که دایرکتوری والد ارسال میشود | Inetsrv\Defdoc.dll |
DirectoryListingModule | لیست کردن محتوای یک دایرکتوری | Inetsrv\dirlist.dll |
IsapiModule | میزبانی فایل های ISAPI | Inetsrv\Isapi.dll |
IsapiFilterModule | پشتیبانی از فیلتر های ISAPI | Inetsrv\Filter.dll |
ServerSideIncludeModule | پردازش کدهای include شده سمت سرور | Inetsrv\Iis_ssi.dll |
StaticFileModule | ارائه فایلهای ایستا | Inetsrv\Static.dll |
FastCgiModule | پشتبانی از CGI | Inetsrv\iisfcgi.dll |
ماژولهای فشرده سازی
DynamicCompressionModule | فشرده سازی پاسخ response با gzip | Inetsrv\Compdyn.dll |
StaticCompressionModule | فشرده سازی محتوای ایستا | Inetsrv\Compstat.dll |
ماژولهای کش کردن
FileCacheModule | تهیه کش در مد کاربری برای فایلها. | Inetsrv\Cachfile.dll |
HTTPCacheModule | تهیه کش مد کاربری و مد کرنل برای http.sys | Inetsrv\Cachhttp.dll |
TokenCacheModule | تهیه کش مد کاربری بر اساس جفت نام کاربری و یک token که توسط Windows user principals تولید شده است. | Inetsrv\Cachtokn.dll |
UriCacheModule | تهیه یک کش مد کاربری از اطلاعات URL | Inetsrv\Cachuri.dll |
ماژولهای عیب یابی و لاگ کردن
CustomLoggingModule | بارگزاری ماژولهای خصوصی سازی شده جهت لاگ کردن | Inetsrv\Logcust.dll |
FailedRequestsTracingModule | برای ردیابی درخواستهای ناموفق | Inetsrv\Iisfreb.dll |
HttpLoggingModule | دریافت اطلاعات و پردازش وضعیت http.sys برای لاگ کردن | Inetsrv\Loghttp.dll |
RequestMonitorModule | ردیابی درخواست هایی که در حال حاضر در پروسههای کارگر در حال اجرا هستند و گزارش اطلاعاتی در مورد وضعیت اجرا و کنترل رابط برنامه نویسی کاربردی. | Inetsrv\Iisreqs.dll |
TracingModule | گزارش رخدادهای Microsoft Event Tracing for Windows یا به اختصار ETW | Inetsrv\Iisetw.dll |
ماژولهای مدیریتی و نظارتی بر کل ماژولها
ManagedEngine | مدیرتی بر ماژولهای غیر native که در پایین قرار دارند. | Microsoft.NET\Framework\v2.0.50727\webengine.dll |
ConfigurationValidationModule | اعتبارسنجی خطاها، مثل موقعی که برنامه در حالت integrated اجرا شده و ماژولها یا هندلرها در system.web تعریف شدهاند. | Inetsrv\validcfg.dll |
ماژول | توضیحات | منبع |
AnonymousIdentification | مدیریت منابع تعیین هویت برای کاربران ناشناس مانند asp.net profile | System.Web.Security.AnonymousIdentificationModule |
DefaultAuthentication | اطمینان از وجود شی Authentication در context مربوطه | System.Web.Security.DefaultAuthenticationModule |
FileAuthorization | تایید هویت کاربر برای دسترسی به فایل درخواست | System.Web.Security.FileAuthorizationModule |
FormsAuthentication | با این قسمت که باید کاملا آشنا باشید؛ برای تایید هویت کاربر | System.Web.Security.FormsAuthenticationModule |
OutputCache | مدیریت کش | System.Web.Caching.OutputCacheModule |
Profile | مدیریت پروفایل کاربران که تنظیماتش را در یک منبع دادهای چون دیتابیس ذخیره و بازیابی میکند. | System.Web.Profile.ProfileModule |
RoleManager | مدیریت نقش و سمت کاربران | System.Web.Security.RoleManagerModule |
Session | مدیریت session ها | System.Web.SessionState.SessionStateModule |
UrlAuthorization | آیا کاربر جاری حق دسترسی به URL درخواست را دارد؟ | System.Web.Security.UrlAuthorizationModule |
UrlMappingsModule | تبدیل یک Url واقعی به یک Url کاربرپسند | System.Web.UrlMappingsModule |
WindowsAuthentication | شناسایی و تایید و هویت یک کاربر بر اساس لاگین او به ویندوز | System.Web.Security.WindowsAuthenticationModule |
مرتب سازی پراکسیها در سرویسهای WCF
فرض کنید یک پراکسی دینامیک (dynamic proxy) از یک کوئری دریافت کرده اید. حال میخواهید این پراکسی را در قالب یک آبجکت CLR سریال کنید. هنگامی که آبجکتهای موجودیت را بصورت POCO-based پیاده سازی میکنید، EF بصورت خودکار یک آبجکت دینامیک مشتق شده را در زمان اجرا تولید میکند که dynamix proxy نام دارد. این آبجکت برای هر موجودیت POCO تولید میشود. این آبجکت پراکسی بسیاری از خواص مجازی (virtual) را بازنویسی میکند تا عملیاتی مانند ردیابی تغییرات و بارگذاری تنبل را انجام دهد.
فرض کنید مدلی مانند تصویر زیر دارید.
ما از کلاس ProxyDataContractResolver برای deserialize کردن یک آبجکت پراکسی در سمت سرور و تبدیل آن به یک آبجکت POCO روی کلاینت WCF استفاده میکنیم. مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع WCF Service Application بسازید. یک Entity Data Model به پروژه اضافه کنید و جدول Clients را مطابق مدل مذکور ایجاد کنید.
- کلاس POCO تولید شده توسط EF را باز کنید و کلمه کلیدی virtual را به تمام خواص اضافه کنید. این کار باعث میشود EF کلاسهای پراکسی دینامیک تولید کند. کد کامل این کلاس در لیست زیر قابل مشاهده است.
public class Client { public virtual int ClientId { get; set; } public virtual string Name { get; set; } public virtual string Email { get; set; } }
نکته: بیاد داشته باشید که هرگاه مدل EDMX را تغییر میدهید، EF بصورت خودکار کلاسهای موجودیتها را مجددا ساخته و تغییرات مرحله قبلی را بازنویسی میکند. بنابراین یا باید این مراحل را هر بار تکرار کنید، یا قالب T4 مربوطه را ویرایش کنید. اگر هم از مدل Code-First استفاده میکنید که نیازی به این کارها نخواهد بود.
- ما نیاز داریم که DataContractSerializer از یک کلاس ProxyDataContractResolver استفاده کند تا پراکسی کلاینت را به موجودیت کلاینت برای کلاینت سرویس WCF تبدیل کند. یعنی client proxy به client entity تبدیل شود، که نهایتا در اپلیکیشن کلاینت سرویس استفاده خواهد شد. بدین منظور یک ویژگی operation behavior میسازیم و آن را به متد ()GetClient در سرویس الحاق میکنیم. برای ساختن ویژگی جدید از کدی که در لیست زیر آمده استفاده کنید. بیاد داشته باشید که کلاس ProxyDataContractResolver در فضای نام Entity Framework قرار دارد.
using System.ServiceModel.Description; using System.ServiceModel.Channels; using System.ServiceModel.Dispatcher; using System.Data.Objects; namespace Recipe8 { public class ApplyProxyDataContractResolverAttribute : Attribute, IOperationBehavior { public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters) { } public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy) { DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors .Find<DataContractSerializerOperationBehavior>(); dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver(); } public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch) { DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors .Find<DataContractSerializerOperationBehavior>(); dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver(); } public void Validate(OperationDescription description) { } } }
- فایل IService1.cs را باز کنید و کد آن را مطابق لیست زیر تکمیل نمایید.
[ServiceContract] public interface IService1 { [OperationContract] void InsertTestRecord(); [OperationContract] Client GetClient(); [OperationContract] void Update(Client client); }
- فایل IService1.svc.cs را باز کنید و پیاده سازی سرویس را مطابق لیست زیر تکمیل کنید.
public class Client { [ApplyProxyDataContractResolver] public Client GetClient() { using (var context = new EFRecipesEntities()) { context.Cofiguration.LazyLoadingEnabled = false; return context.Clients.Single(); } } public void Update(Client client) { using (var context = new EFRecipesEntities()) { context.Entry(client).State = EntityState.Modified; context.SaveChanges(); } } public void InsertTestRecord() { using (var context = new EFRecipesEntities()) { // delete previous test data context.ExecuteSqlCommand("delete from [clients]"); // insert new test data context.ExecuteStoreCommand(@"insert into [clients](Name, Email) values ('Armin Zia','armin.zia@gmail.com')"); } } }
- حال پروژه جدیدی از نوع Console Application به راه حل جاری اضافه کنید. این اپلیکیشن، کلاینت تست ما خواهد بود. پروژه سرویس را ارجاع کنید و کد کلاینت را مطابق لیست زیر تکمیل نمایید.
using Recipe8Client.ServiceReference1; namespace Recipe8Client { class Program { static void Main(string[] args) { using (var serviceClient = new Service1Client()) { serviceClient.InsertTestRecord(); var client = serviceClient.GetClient(); Console.WriteLine("Client is: {0} at {1}", client.Name, client.Email); client.Name = "Armin Zia"; client.Email = "arminzia@live.com"; serviceClient.Update(client); client = serviceClient.GetClient(); Console.WriteLine("Client changed to: {0} at {1}", client.Name, client.Email); } } } }
Client changed to: Armin Zia at arminzia@live.com
شرح مثال جاری
مایکروسافت استفاده از آبجکتهای POCO با WCF را توصیه میکند چرا که مرتب سازی (serialization) آبجکت موجودیت را سادهتر میکند. اما در صورتی که از آبجکتهای POCO ای استفاده میکنید که changed-based notification دارند (یعنی خواص موجودیت را با virtual علامت گذاری کرده اید و کلکسیونهای خواص پیمایشی از نوع ICollection هستند)، آنگاه EF برای موجودیتهای بازگشتی کوئریها پراکسیهای دینامیک تولید خواهد کرد.
استفاده از پراکسیهای دینامیک با WCF دو مشکل دارد. مشکل اول مربوط به سریال کردن پراکسی است. کلاس DataContractSerializer تنها قادر به serialize/deserialize کردن انواع شناخته شده (known types) است. در مثال جاری این یعنی موجودیت Client. اما از آنجا که EF برای موجودیتها پراکسی میسازد، حالا باید آبجکت پراکسی را سریال کنیم، نه خود کلاس Client را. اینجا است که از DataContractResolver استفاده میکنیم. این کلاس میتواند حین سریال کردن آبجکت ها، نوعی را به نوع دیگر تبدیل کند. برای استفاده از این کلاس ما یک ویژگی سفارشی ساختیم، که پراکسیها را به کلاسهای POCO تبدیل میکند. سپس این ویژگی را به متد ()GetClient اضافه کردیم. این کار باعث میشود که پراکسی دینامیکی که توسط متد ()GetClient برای موجودیت Client تولید میشود، به درستی سریال شود.
مشکل دوم استفاده از پراکسیها با WCF مربوط به بارگذاری تبل یا Lazy Loading میشود. هنگامی که DataContractSerializer موجودیتها را سریال میکند، تمام خواص موجودیت را دستیابی خواهد کرد که منجر به اجرای lazy-loading روی خواص پیمایشی میشود. مسلما این رفتار را نمیخواهیم. برای رفع این مشکل، مشخصا قابلیت بارگذاری تنبل را خاموش یا غیرفعال کرده ایم.
سولوشنی حاوی دو پروژه تشکیل داده شده است. یکی از پروژه ها، یک سرویس و دومین پروژه یک وب فرم است که قرار است با آن سرویس ارتباط برقرار کند. همچنین در قلب پروژهی وب فرم یک سرویس دیگر قرار داده ایم و دو موضوع را بررسی کرده ایم:
چگونه یک وب فرم با سرویس موجود درون پروژهی خودش تعامل کند؟
چگونه یک وب فرم با یک سرویس خارج از پروژهی خودش تعامل کند؟
- سرویسها از نوع Restful تعریف شده اند.
- دادهها از طریق Json ارسال و دریافت شده اند.
- روش POST و GET در هر دو پروژه بررسی شده است.
جلوگیری از ارسال Spam در ASP.NET MVC
MVC vs 3-Tier Pattern
اصولا باید تقسیم بندی لایهها به این صورت باشه که:
1-لایه زیرساخت که متشکل از DataLayer,ServiceLayer,domainclasses هست
2-لایه وب سرویس که میتواند یک سرویس دهنده باشد مثل WebApi,Wcf
3-لایه ui که هر نوع appی میتواند باشد .مانند Asp.Net Webforms,Asp.Net Mvc,Andriod ,Ios,Winphone و یا حتی php
در واقع لایه وب سرویس هست که لایه ui رو تغذیه میکند.
لایه ui با ارسال درخواست به لایه وب سرویس داده مورد نظر خود را دریافت میکند و مهم نیست که این لایه از چه نوع تکنولوژی استفاده میکند.
اینگونه است که میتوان وابستگی لایهها را تفکیک کرد و به یک معماری مستقل رسید.
پروژه ای از نوع Wcf Service App میسازیم و یک سرویس با یک متد که خروجی آن نیز void است خواهیم داشت. به صورت زیر:
[ServiceContract] public interface ISampleService { [OperationContract] void Wait(); }
public class SampleService : ISampleService { public void Wait() { Thread.Sleep( new TimeSpan( 0, 1, 0 ) ); } }
class Program { static void Main( string[] args ) { SampleService.SampleServiceClient client = new SampleService.SampleServiceClient(); Console.WriteLine( DateTime.Now ); client.Wait(); Console.WriteLine( DateTime.Now ); Console.ReadKey(); } }
دلیل اینکه Timeout Exception پرتاب شد این است که به صورت پیش فرض مقدار خاصیت sendTimeout برابر 59 ثانیه است، در نتیجه قبل از اینکه پاسخی از سمت سرور به کلاینت برگشت داه شود این Exception رخ میدهد. برای حل این مشکل کافیست در فایل app.config کلاینت در قسمت تنظیمات Binding ، تغییر زیر را اعمال کنیم:
<basicHttpBinding> <binding name="BasicHttpBinding_ISampleService" sendTimeout="0:2:0"/> </basicHttpBinding>
مشخص است که تا زمانی که عملیات سمت سرور به پایان نرسد،(یا توجه به اینکه خروجی متد سمت سرور void است) اجرای برنامه در کلاینت نیز متوقف خواهد بود(اختلاف زمانهای بالا کمی بیش از یک دقیقه است).
در این مواقع زمانی که باید متدی سمت سرور فراخوانی شود و قرار نیست که خروجی نیز در اختیار کلاینت قرار دهد بهتر است که از متدهای یک طرفه استفاده نماییم. متدهای یک طرفه یا یه اصطلاح OneWay، هیچ پاسخی را به کلاینت برگشت نمیدهند و بلافاصله بعد از فراخوانی، کنترل اجرای برنامه را در اختیار کلاینت قرار خواهند داد. برای تعریف یک متد به صورت یک طرفه کافیست به صورت زیر عمل نماییم(مقدار خاصیت IsOneWay را در OperationContractAttribute برابر true خواهیم کرد):
[ServiceContract] public interface ISampleService { [OperationContract( IsOneWay = true )] void Wait(); }
از آن میتوان جهت ثبت استثناهای مدیریت نشده در انواع و اقسام برنامههای وب دات نتی استفاده کرد. از برنامههای ASP.NET Webforms ، ASP.NET MVC تا سرویسهای WCF ، WCF RIA و غیره. سپس این Http module ویژه امکان مرور خطاهای ثبت شده را از راه دور میسر میسازد، به همراه فید RSS از خطاها، امکان مشاهدهی مقادیر فیلدها در لحظهی بروز خطا، ارسال ایمیل خطاها و امکان ذخیره سازی آنها در فایلهای XML تا انواع بانکهای اطلاعاتی.
برای نمونه، سایت Stackoverflow از یک نمونهی سفارشی شدهی آن استفاده میکند (+).
همانطور که در این معرفی آمده است : ثبت استثناهای "مدیریت نشده". اما آیا امکان ثبت استثناهای مدیریت شده هم توسط آن وجود دارد؟
پاسخ: بله. به صورت زیر:
... } catch (Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex); ...
در یک سری از متدهای فایل global.asax عموما Context ای وجود ندارد (دقیقا مانند برنامههای دسکتاپ). در این حالت از روش زیر
Elmah.ErrorLog.GetDefault(null).Log(new Error(ex))
using System; using System.Text; using Elmah; namespace Common.WebToolkit { public static class ElmahLogEx { public static void LogException(this string ex) { if (string.IsNullOrWhiteSpace(ex)) return; LogException(new Exception(ex)); } public static void LogException(this Exception ex) { if (ex == null) return; try { ErrorSignal.FromCurrentContext().Raise(ex); } catch { ErrorLog.GetDefault(null).Log(new Error(ex)); } } } }
هدف از مطلب فوق اجرا نمودن عملیات Insert، Update و غیرو...
بوسیله چندین Connection در یک Transaction در زمان اجرای سرویسهای WCF میباشد. برای پیاده سازی و شرح Transaction ، سه پروژه ایجاد مینماییم. دو پروژه WCF سرویس و یک پروژهClient ، هر سه پروژه را در یک Solution به نام WCFTransaction اضافه مینماییم. در هر دو پروژه WCF بطور جداگانه Connection رویDatabase ایجاد مینماییم. سپس سعی میکنیم بوسیله Transaction عملیات Insert هر دو Service را کنترل نماییم. بطوریکه اگر یکی از Service ها در زمان عملیات Insert دچار مشکل شود. دیگری نیز Commit نگردد. به عبارتی در قدیم نمیتوانستیم بیش از یک Connection در یک Transaction ایجاد نماییم. اما بوسیله Transactionscope ، انجام عملیات Insert، Update و غیرو... بوسیله چندین Connection به یکDatabase بطور همزمان در یک Transaction فراهم شده است. برای نمایش دادن عملیات Rollback نیز،به عمد خطایی ایجاد میکنیم،تا نحوه Rollback شدن در Transaction را مشاهده نماییم.
سعی شده است پیاده سازی و استفاده از Transaction در شش مرحله انجام شود.
مرحله اول: ایجاد دو پروژه WCFService و یک پروژه Client جهت فراخوانی (Call) کردن سرویسها
در این مرحله همانطور که از قیل نیز
توضیح داده شده است، دو پروژه WCF به نامهای WCFService1 و WCFService2 ایجاد شده است و یک پروژه Client به نام WCFTransactions نیز ایجاد میکنیم.
مرحله دوم : افزودن Attribute ی به نام TransactionFlow به Interface سرویسها.
در این مرحله در Interface هریک از سرویسها متد جدیدی به نام UpdateData اضافه مینماییم. که عملیات Insert into درون Database را انجام میدهد. حال بالای متد UpdateData از صفت TransactionFlow استفاده مینماییم. تا قابلیت Transaction برای متد فوق فعال گردد و متد فوق اجازه مییابد از Transaction استفاده نماید.
<ServiceContract()> _ Public Interface IService1 <OperationContract()> _ Function GetData(ByVal value As Integer) As String <OperationContract()> _ Function GetDataUsingDataContract(ByVal composite As CompositeType) As CompositeType <OperationContract()> _ <TransactionFlow(TransactionFlowOption.Allowed)> _ Sub UpdateData() End Interface
مرحله سوم:
در این مرحله متد UpdateData را پیاده سازی مینماییم. بطوریکه یک Insert Into ساده در Database انجام میدهیم.و بالای متد فوق نیز کد زیر را میافزاییم.
<OperationBehavior(TransactionScopeRequired:=True)>
کد متد UpdateData
<OperationBehavior(TransactionScopeRequired:=True)> _ Public Sub UpdateData() Implements IService1.UpdateData Dim objConnection As SqlConnection = New SqlConnection(strConnection) objConnection.Open() Dim objCommand As SqlCommand = New SqlCommand("insert into T(ID,Age) values(10,10)", objConnection) objCommand.ExecuteNonQuery() objConnection.Close() End Sub
مرحله دوم و سوم را برای Service دوم نیز تکرار مینماییم.
مرحله چهارم:
در این مرحله TransactionFlow را در Web.Config دو سرویس فعال مینماییم. تا قابلیت استفاده از TransactionFlow برای سرویسها نیز فعال گردد. نحوه فعال نمودن بصورت زیر میباشد:
برای WCFService1خواهیم داشت:
<bindings> <wsHttpBinding> <binding name="TransactionalBind" transactionFlow="true"/> </wsHttpBinding> </bindings>
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="TransactionalBind" contract="WcfService1.IService1">
برای WCFService2نیز خواهیم داشت:
<bindings> <wsHttpBinding> <binding name="TransactionalBind" transactionFlow="true"/> </wsHttpBinding> </bindings>
و در ادامه داریم:
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="TransactionalBind" contract="WcfService2.IService1">
مرحله پنجم:
در این مرحله دو سرویس فوق را به پروژه WCFTransactions اضافه نموده و قطعه کد زیر را درون فرم Load مینویسیم.
Private Sub frmmain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Using ts As New TransactionScope(TransactionScopeOption.Required) Try Dim obj As ServiceReference1.Service1Client = New ServiceReference1.Service1Client() obj.UpdateData() Dim obj1 As ServiceReference2.Service1Client = New ServiceReference2.Service1Client() obj1.UpdateData() ts.Complete() Catch ex As Exception ts.Dispose() End Try End Using End Sub
پس از اجرای برنامه دو رکورد در جدول درج خواهد شد.
مرحله ششم:
حال برای RollBack کردن کل عملیات و مشاهده آنها کافیست در یکی از متدهای UpdateData یک Throw Exception ایجاد نماییم.
سعی میکنیم با کمی تغییر در متد UpdateData در WCFService2 ، خطایی ایجاد شود، تا نحوه RollBack را مشاهده نماییم.
Public Sub UpdateData() Implements IService1.UpdateData
Throw New Exception()
Dim objConnection As SqlConnection = New SqlConnection(strConnection)
objConnection.Open()
Dim objCommand As SqlCommand = New SqlCommand("insert into T(ID,Age) values(101,101)", objConnection)
objCommand.ExecuteNonQuery()
objConnection.Close()
End Sub
فقط کد زیر به متد UpdateData اضافه شده است:
Throw New Exception()
و در رویداد Load فرم نیز پیاده سازی آن بشکل زیر خواهد بود:
Using ts As New TransactionScope(TransactionScopeOption.Required) Try Dim obj As ServiceReference1.Service1Client = New ServiceReference1.Service1Client() obj.UpdateData() Throw New Exception("There was Error") Dim obj1 As ServiceReference2.Service1Client = New ServiceReference2.Service1Client() obj1.UpdateData() ts.Complete() Catch ex As Exception ts.Dispose() End Try End Using
وقتی برنامه را اجرا نمایید، مشاهده میکنید که هیچ رکوردی دورن دیتابیس درج نشده است.
بسبار مهم: برای اینکه بتوانید بصورت Distibuted عملیات Transaction را انجام دهید میبایست تنظیماتی را روی سرور که دیتایس و سرویسها و کامپیوتر کلاینت انجام دهید که بصورت زیر میباشد:
نحوه تنظیم:
1- سرویسDistribute Transaction Coordinator را روی هر دو Serverهای WCFService ، Database و کامپیوتر کلاینت، Start مینماییم.
البته در شرایطی که Serviceهای WCF و برنامه Client و Database روی یک سیستم باشد، تنظیمات فوق فقط روی همان سیستم انجام میشود.
برای دسترسی به قسمت Service های Windows ابتدا Administrative
Tools و سپس Service را باز نمایید و روی Start کلیک کنید.
2- در ادامه روی MY Computer کلیک راست نموده و تب MSDTC را انتخاب نمایید:
در ادامه روی Security
Configuration کلیک نمایید. تا فرم زیر نمایش داده شود.
مطمئن شوید که آیتمهای زیر انتخاب شده باشند:
· Network DTC Access
· Allow Remote Clients
· Allow Inbound
· Allow Outbound
·
Enable
Transaction Internet Protocol(TIP) Transactions
در ضمن اگر از SQL Server 2000 استفاده مینمایید. لازم است تنظیم زیر را انجام دهید.
روی SQL Server Service Manager کلیک نموده و کامبوی Service را Dropdown نمایید و Distribute Transaction Coordinator را انتخاب کنید. اما برای ورژنهای بالاتر از SQL Server 2000 نیاز به انتخاب Distribute Transaction Coordinator نمیباشد.
امیدوارم مطلب فوق مفید واقع شود، چنانچه کم و کاستی مشاهده نمودید، اینجانب را از نظرات خود بهره مند سازید.