‏نمایش پست‌ها با برچسب WCF RIA Services. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب WCF RIA Services. نمایش همه پست‌ها

۱۳۸۹/۰۵/۲۹

نحوه‌ي مشاهده‌ي خروجي SQL توليد شده توسط WCF RIA Services


اين روزها با وجود ORMs ، كوئري SQL‌ نوشتن شبيه به دوراني شده كه با وجود زبان‌هاي سطح بالا، عده‌اي علاقمند هستند با استفاده از زبان اسمبلي برنامه نويسي كنند! WCF RIA Services به صورت پيش فرض از entity framework استفاده مي‌كند (هر چند مي‌توان از ساير ORMs هم استفاده كرد)، بنابراين عنوان صحيح‌تر بحث اين خواهد بود: چگونه خروجي SQL توليد شده توسط Entity framework را بررسي كنيم؟

الف) استفاده از SQL Server profiler
اولين برنامه‌اي كه از سال‌ها قبل، حتي پيش از ظهور ORMs وجود داشته، برنامه‌ي SQL server profiler است، كه عموما در مسير ذيل قابل دستيابي است:
Start Menu->Programs->Microsoft SQL Server 2008->Performance Tools->SQL Server profiler



نكته مهم:
حين كار با SQL Server profiler ، ممكن است انبوهي از كوئري‌هاي ديگر مثلا مرتبط با SQL Server agent يا reporting services و غيره نيز لاگ شوند. اما الان ما تنها به كوئري‌هاي برنامه‌ي خود نياز داريم. براي اين منظور به كانكشن استرينگ خود، گزينه‌ي Application Name=My Application Name را نيز اضافه كنيد:

<connectionStrings>
<add name="dmEntities" connectionString="metadata=res://*/Models.dmDataModel.csdl|res://*/Models.dmDataModel.ssdl|res://*/Models.dmDataModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=(local);Initial Catalog=dm;Integrated Security=True;Application Name=My Application Name;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
</connectionStrings>

اكنون اگر برنامه را با پروفايلر مورد بررسي قرار دهيد خروجي به صورت زير خواهد بود:



براي فيلتر كردن Application Name مورد نظر، در ابتداي كار كه يك سشن جديد را آغاز مي‌كنيد به برگه‌ي events selection مراجعه كرده و بر روي دكمه‌ي column filter كليك كنيد. گزينه‌ي application name را در صفحه‌ي باز شده انتخاب نموده و در قسمت Like آن مطابق تصوير زير ، نام برنامه‌ي خود را وارد نمائيد:




ب) استفاده از IntelliTrace در VS.NET 2010
برنامه را در حالت ديباگ در VS.NET 2010 اجرا كنيد. در هر لحظه‌اي مي‌توان روي گزينه‌ي Break all كليك كرد و خروجي SQL توليد شده را نيز علاوه بر اطلاعات ديگر مشاهده نمود:




ج) استفاده از برنامه‌ي حرفه‌اي entity framework profiler
اين برنامه از هر دو مورد قبل كاملتر بوده و اساسا براي لاگ كردن كوئري‌ها، مدت زمان اجرا، گزارشگيري از وضعيت برنامه، كداميك از كوئري‌ها سنگين‌تر هستند، حتي از طريق كدام متد فراخواني شده‌اند، ارائه‌ي گزارشات و راهنمايي‌هايي در مورد چگونگي بهبود كارآيي برنامه‌ي تهيه شده و امثال آن كاربرد دارد.



استفاده از آن هم بسيار ساده است. ابتدا ارجاعي را به اسمبلي HibernatingRhinos.Profiler.Appender.v4.0 به پروژه‌ي ASP.NET خود اضافه كنيد (همان پروژه‌ي هوست مربوط به WCF RIA Service ما). سپس به فايل Global.asax.cs برنامه مراجعه كرده و يك سطر ذيل را اضافه كنيد:

protected void Application_Start(object sender, EventArgs e)
{
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
}

از اين پس تنها كافي است برنامه‌ي پروفايلر در حال اجرا بوده و برنامه شما نيز اجرا شود. كليه‌ي تبادلات با ديتابيس لاگ خواهند شد.

۱۳۸۹/۰۵/۲۰

يكپارچه كردن ELMAH با WCF RIA Services


پيشتر در مورد 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، نوبت به اعمال آن مي‌رسد. به فايل DomainService خود مراجعه كرده و يك سطر زير را به آن اضافه نمائيد:
    [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>
اكنون براي مثال به يكي از متدهاي DomainService خود سطر زير را اضافه كرده و برنامه را آزمايش كنيد:
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)

۱۳۸۹/۰۵/۱۸

مديريت بهتر كدهاي توليد شده توسط WCF RIA Services


در حين كار با WCF RIA Services اگر تمام موجوديت‌هاي تعريف شده انتخاب شوند يك فايل طويل DomainService حاصل خواهد شد. كار كردن با اين فايل نه زيبا است و نه ساده. بعد از يك مدت شايد بگوئيم، خوب! من به ازاي هر جدول يك DomainService جدا توليد مي‌كنم با نامي مختص به آن و اين اطلاعات را در فايلي جداگانه نيز ذخيره خواهم كرد. پس از انجام اينكار با خطاي زير مواجه خواهيم شد:

The entity type ... is exposed by multiple DomainService types. Entity types cannot be shared across DomainServices

به صورت خلاصه: مهم نيست سيستم شما از چند جدول تشكيل شده است؛ مهم اين است كه تنها يك فايل DomainService را بايد توليد كنيد و البته اين يك محدوديت نيست؛ يك هدف محسوب مي‌شود؛ از اين ديدگاه كه موجوديت‌هاي مرتبط بايد در يك Domain قرار گيرند و تنها در يك دومين هستند كه روابط بين آن‌ها معنا پيدا مي‌كند.
- روش توصيه شده براي مديريت اين كلاس DomainService طويل، استفاده از واژه‌ي كليدي partial است (براي مثال public partial class MyDomainService). به اين صورت هر موجوديت را مي‌توان در يك فايل جداگانه قرار داد و به اين ترتيب مديريت ساده‌تري را بر روي اطلاعات توليد شده داشت و همچنين تمامي اين فايل‌ها در نهايت يك كلاس واحد را تشكيل مي‌دهند و اصل وجود يك DomainService واحد در برنامه زير سؤال نخواهد رفت.
- كدهايي را كه خودتان نيز به اين مجموعه اضافه خواهيد كرد، در لابلاي كدهاي توليد شده قرار ندهيد. در صورت تغييري در جداول نياز است تا اين فايل‌ها مجددا توليد شوند و اينجا است كه تمام تغييرات خود را از دست خواهيد داد. براي اين منظور باز هم يك كلاس partial ديگر را تعريف كنيد تا كدهاي سفارشي خود را بتوان به صورت مجزايي از كدهاي توليد شده به صورت خودكار، در آن قرار داد (براي مثال به نام MyServiceNameDomainService.extensions.cs).
- جايي كه قرار است يك سري از فايل‌ها مجددا توليد شوند استفاده از برنامه‌هاي سورس كنترل را فراموش نكنيد؛ تا هر زماني بتوان كدهاي جديد را با كدهاي نگارش‌هاي قبل به سادگي مقايسه كرد؛ يا حتي به نگارش‌هاي قبلي بازگشت نمود.
- اين نكته را به خاطر داشته باشيد كه اگر اطلاعاتي به صورت خودكار از پيش تعريف شده و موجود است (كلاس دومين يا متاديتاي آن)،‌ اين اطلاعات حين استفاده از امكانات توليد كد خودكار، مجددا توليد نخواهند شد. به همين جهت بايد ابتدا آن‌ها را به صورت comment در آورد (يا از پروژه خارج نمود؛ استفاده از امكانات include و exclude پوشه‌ها يا فايل‌ها در ويژوال استوديو) تا همواره كدهاي آخرين اطلاعات موجود توليد گردند.