بررسی وجود پیاده سازی محتویات MasterPage

25 10 2009

شاید در استفاده از MasterPage ها زمانی پیش آمده باشد که لازم شده وجود پیاده سازی یک ContentPlaceHolder را در صفحه خاص رو بررسی کنید. مثلا زمانی رو در نظر بگیرید که در صفحه خاصی نباید بلوک خلاصه محتویات صفحه یا لینکها نمایش داده شود و این کادر نیز در MasterPage پیاده سازی شده است.

پس روشی باید به کار گیرید که آن کادر در آن صفحه به خصوص نمایش داده نشود. متاسفانه در دات نت روشی برای این کار در نظر گرفته نشده است؛ لااقل هیچ متد عمومی برای این کار وجود ندارد.

خوشبختانه ویژگی داخلی و مخفی در کلاس MasterPage وجود دارد به نام ContentTemplates که لیستی از ContentPlaceHolder هست که توسط صفحه جاری پیاده سازی شده است. کاری که لازم است استفاده از قدرت Reflection دات نت هست تا به آن ویژگی دسترسی پیدا کنیم.

همراه با این باید ContentPlaceHolder برای وجود کنترل در داخلی خودش هم بررسی بشه. تابع زیر برای همین کار هست:

public static bool HasNonEmptyControls(ContentPlaceHolder cph)
{
    if (cph.Controls.Count == 0)
    {
        return false;
    }
    else if (cph.Controls.Count == 1)
    {
        LiteralControl c = cph.Controls[0] as LiteralControl;

        if (string.IsNullOrEmpty(c.Text) || IsWhiteSpace(c.Text))
            return false;
    }

    return true;
}

static bool IsWhiteSpace(string s)
{
    for (int i = 0; i < s.Length; i++)
        if (!char.IsWhiteSpace(s[i]))
            return false;

    return true;
}

در ادامه تابع اصلی منظور ما که وجود پیاده سازی از یک ContentPlaceHolder به خصوص رو بررسی خواهد کرد:

static readonly Type _masterType = typeof(MasterPage);
static readonly PropertyInfo _contentTemplatesProp = _masterType.GetProperty("ContentTemplates", BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);

public static bool HasContentPageContent(ContentPlaceHolder cph)
{
    IDictionary templates = null;
    MasterPage master = cph.Page.Master;

    while (templates == null && master != null)
    {
        templates = (IDictionary)_contentTemplatesProp.GetValue(master, null);
        master = master.Master;
    }

    if (templates == null)
        return false;

    bool isSpecified = false;

    foreach (string key in templates.Keys)
    {
        if (key == cph.ID)
        {
            isSpecified = true;

            break;
        }
    }

    return isSpecified;
}

همانطور که از کد مشخصه با بدست آوردن ویژگی ContentTemplates که یک لسیت دیکشنری است وجود ContentPlaceHolder مورد نظر رو بررسی می کنیم.

و سر انجام ترکیبی از این دو تابع جهت بدست آوردن نتیجه مطلوب:

public static bool HasContentOrControls(ContentPlaceHolder cph)
{
    return HasNonEmptyControls(cph) || HasContentPageContent(cph);
}

و تمام. به راحتی می توانید از این تابع استفاده کنید. مانند نمونه زیر:

<%if (HasContentOrControls(plhOptions)){ %>
<div id="options">
 <div>
 <h2>
 <asp:ContentPlaceHolder ID="plhOptionsTitle" runat="server" />
 </h2>
 <asp:ContentPlaceHolder ID="plhOptions" runat="server" />
 </div>
</div>
<%}%>

همانطور که در این مثال مشاهده می کنید، فقط در صورتی که plhOptions در صفحه پیاده سازی شده باشد کادر مربوط به آن همراه با محتویات بخش plhOptionsTitle نمایش داده خواهند شد.

منبع

خوش باشید.





رفع باگ موجود در CookieContainer

16 10 2009

قبلا نوشته بودم که ماکروسافت قراره باگی رو که در کلاس CookieContainer هست رو رفع کنه، که اون باگ باعث 7 ماه سرگردانی من شده است. خبر خوبی بود، اما اگر به سایت ماکروسافت که لینکش رو گذاشته بودم مراجعه کنید می بینید که هیچ وصله ای قرار نیست برای دات نت 2 منتشر بشه. و این یعنی اینکه همچنان با مشکل باید دست و پنجه نرم کنیم!

به هر حال در ادامه می خوام علت بروز مشکل و راه حل اون رو با زحمت بسیار و همکاری یه نفر دیگه بدست اومده رو بررسی کنم.

  • مقدمه ای در مورد Domain در کوکی ها

تمام مشکل حول محور نحوه مدیریت domain در کلاس Cookiecontainer بر میگرده. پس میخوام مختصر توضیحی در مورد دامین در کوکی ها بدم.

در مبحث کار با کوکی ها با تنظیم مقدار domain می توانید تعیین کنید که کوکی برای کدام دامنه یا زیر دامنه ها قابل دسترسی باشد.

یک مثال ساده مانند زیر هست؛ در این مثال  کوکی با نام Hello رو برای دامین site.org ثبت می کنیم:

Hello=cookieValue value; domain=site.org; path=/

توجه کنید که این کوکی فقط به دامین site.org ثبت شده و فقط برای آن قابل مشاهده خواهد بود. هیچ زیر دامنه ای مانند http://www.site.org نمی تواند این کوکی رو مشاهده کند. برای اینکه کوکی به زیر دامنه های سایت site.org هم قابل دسترسی باشه باید یک نقطه (.) به اول دامنه اضافه کنیم. مانند این نمونه:

Hello=cookieValue value; domain=.site.org; path=/

با این کار این کوکی به تمامی زیر دامنه ها قابل مشاهده خواهد بود.

همچنین توجه کنید که این قانون به کوکی های خود زیر دامنه ها هم اعمال میشه. برای مثال کوکی زیر فقط برای sub.site.org قابل مشاهده خواهد بود:

Hello=cookieValue value; domain=sub.site.org; path=/

و کوکی زیر، هم برای دامنه اصلی و هم برای تمامی زیر دامنه های آن قابل مشاهده خواهد بود:

Hello=cookieValue value; domain=.sub.site.org; path=/

  • بررسی مشکل

قبل از اینکه به مشکل بپردازم لازمه بدونید که کلاس CookieContainer برای مدیریت و نگهداری کوکی های برای ارسال درخواست های وب توسط HttpWebRequest مورد استفاده قرار میگیره. و به علت مشکلی که داره بعضا باعث اشکالات عجیب و نامشخص می شه.

احتمالا تا حالا متوجه شدید که مشکل کلاس CookieContainer در نحوه مدیریت نقطه (.) هایی هست که در ابتدای دامین قرار می گیره و به شیوه اعمال اونها تاثیر میذاره.

برای نمونه مشکل زمانی اتفاق می افته که یک کوکی رو به آدرس site.org ثبت کنید و درخواست خودتون رو آدرس http://www.site.org ارسال کنید.
طبق مقدمه انتظار میره که هیچ کوکی ارسال نشه ولی برخلاف انتطار کوکی مزبور ارسال خواهد شد. متن کوکی این مثال:

Hello=CookieValue value; domain=site.org; path=/

همچینین مشکل دیگر و البته بدتر زمانی هست که کوکی رو به آدرس http://www.site.org ثبت کنید و درخواست خودتون رو به آدرس http://www.site.org ارسال کنید. انتظار می ره که کوکی به سلامتی ارسال بشه ولی در کمال ناباوری هیچ کوکی ارسال نخواهد شد. متن کوکی این مثال:

Hello=CookieValue value; domain=www.site.org; path=/

علت بروز مشکل به یک سری پیچیدگیهای اضافی که ماکروسافت در پیاده سازی این کلاس اعمال کرده بر می گرده.

  • نحوه رفع باگ

مشکل اصلی به نحوه پیاده سازی یکی از متد ها داخلی و پنهان کلاس CookieContainer بر می گرده. در نتییجه توابعی که از اون تابع استفاده می کنند این مشکل زو خواهند داشت. متاسفانه همه سه تابعی که برای افزودن کوکی قابل دسترسی هستند از مشکل رنج می برند! این سه تابع و توضیح مختصر در مورد اونها:

  • Add(Uri,Cookie) این تابع کوکی را اضافه کرده و به مسیر تعیین شده ارتباط میدهد
  • Add(Cookie) این تابع کوکی رو اضافه کرده و دامینهای قابل اعمال رو از دامین کوکی تعیین می کنه
  • SetCookies(CookieHeader) این تابع کوکی که به صورت هدر کوکی ارسال شده رو تولید کرده و اضافه می کنه.

راه حل قطعی که تا کنون با همکاری دوستم تونستیم پیدا کنیم تغییر کلید های دسترسی به کوکی و اضافه کردن کلید های مفقود شده به صورت دستی با استفاده از امکانات Reflection دات نت.

به صورت خلاصه تابع زیر برای رفع این نقیصه نوشته شده. این تابع رو همراه با یک سری تمهیدات باید به کار ببرد که در ادامه توضیح خواهد داد:

private static Type _cookieContainerType = Type.GetType("System.Net.CookieContainer, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
private static Type _pathListType = Type.GetType("System.Net.PathList, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
///<summary>
/// This method is aimed to fix a goddamn CookieContainer issue,
/// It adds missed path for cookies which are not started with dot.
/// This is a dirty hack
///</summary>
///<remarks>
/// This method is only for .NET 2.0 which is used by .NET 3.0 and 3.5 too.
/// The issue will be fixed in .NET 4, I hope!
///</remarks>
/// Many thanks to CallMeLaNN "dot-net-expertise.blogspot.com" to complete this method
private void BugFix_AddDotCookieDomain(CookieContainer cookieContainer)
{
	Hashtable table = (Hashtable)_cookieContainerType.InvokeMember("m_domainTable",
									 System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance,
									 null,
									 cookieContainer,
									 new object[] { });

	ArrayList keys = new ArrayList(table.Keys);

	object pathList1;
	object pathList2;

	SortedList sortedList1;
	SortedList sortedList2;
	ArrayList pathKeys;

	CookieCollection cookieColl1;
	CookieCollection cookieColl2;

	foreach (string key in keys)
	{
		if (key[0] == '.')
		{
			string nonDotKey = key.Remove(0, 1);
			// Dont simply code like this:
			// table[nonDotKey] = table[key];
			// instead code like below:

			// This codes will copy all cookies in dot domain key into nondot domain key.

			pathList1 = table[key];
			pathList2 = table[nonDotKey];
			if (pathList2 == null)
			{
				pathList2 = Activator.CreateInstance(_pathListType); // Same as PathList pathList = new PathList();
				lock (cookieContainer)
				{
					table[nonDotKey] = pathList2;
				}
			}

			// merge the PathList, take cookies from table[keyObj] copy into table[nonDotKey]
			sortedList1 = (SortedList)_pathListType.InvokeMember("m_list", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, pathList1, new object[] { });
			sortedList2 = (SortedList)_pathListType.InvokeMember("m_list", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, pathList2, new object[] { });

			pathKeys = new ArrayList(sortedList1.Keys);

			foreach (string pathKey in pathKeys)
			{

				cookieColl1 = (CookieCollection)sortedList1[pathKey];
				cookieColl2 = (CookieCollection)sortedList2[pathKey];
				if (cookieColl2 == null)
				{
					cookieColl2 = new CookieCollection();
					sortedList2[pathKey] = cookieColl2;
				}

				foreach (Cookie c in cookieColl1)
				{
					lock (cookieColl2)
					{
						cookieColl2.Add(c);
					}
				}
			}
		}
	}
}

جزئیات نحوه کار این متد زیاد مهم نخواهد بود پس از ذکر آنها خودداری می کنم.

شیوه به کار گیری:

نکته خیلی خیلی مهم این هست که از هیچ کدام از توابع Add(Uri,Cookie)  و SetCookies(CookieHeader) نباید استفاده کنید. مشکلاتی این دو تابع ایجاد می کنند غیر قابل اصلاح است.

تنها تابع مجاز برای استفاده Add(Cookie) جها اضافه کردن کوکی های به کلاس CookieContainer است.

و سر انجام اینکه تابع BugFix_AddDotCookieDomain رو زمانی باید فراخوانی کنید که کارتان با کلاس CookieContainer به پایان رسیده و آماده ارسال درخواست هستید.

این تابع تمام مشکلات ذکر شده در بالا را رفع خواهد کرد.

نکته پایانی

تنها موردی که باقی ماند که نیاز اساسی به یه راه حل داره تابع SetCookies هست. چون این تابع با هدر ها کار می کنه ممکنه که به اون نیاز پیدا کنید. متاسفانه همونطور که گفتم مشکل مربوط به SetCookies هنوز رفع نشده و من و دوستم به دنبال راه حلی برای این مورد هستیم.

  • لینکهای مرتبط

بحث طولانی من و دوستم که منجر به رسیدن به این راه حل شد

وصله ماکروسافت برای رفع مشکل 7 ماهه من

پاسخ ماکروسافت به این مشکل





ایجاد FavIcon متحرک و تغییر آن با جاوا اسکریپت

9 05 2009

تاکنون سایتهایی را دیده اید که favicon متحرک دارند. اعمال این انیمشین به سادگی امکان پذیر است.

برای اطلاع از اینکه favicon چیست به اینجا مراجعه کنید.

متحرک بودن آیکون سایت فقط در مرورگر فایرفاکس پشتیبانی میشود و در بقیه متحرک نخواهد بود. مرور IE نیز که به طور کل آن را پشتیبانی نمی کند.

favicon متحرک

برای اعمال آیکون متحرک ابتدا فایل انیمشن gif مورد خودتون رو ایجاد کنید و در مسیر سایت قرار بدید.

کد زیر رو به قسمت head فایل html مورد نظر اضافه کنید:
<link rel="shortcut icon" href="favicon.gif" type="image/gif">

نکته ای که در اینجا مهم هست استفاده همزمان از فایل gif و icon هست  تا برای مرورگرهایی که از این ویژگی پشتیانی نمی کنند مشکلی پیش نیاید و همچنان آیکون سایت نمایش داده بشود.
بس برای این منظور فایل favicon.ico را هم در مسیر قرار و لینک آن را نیز به قسمت head اضافه کنید. توجه کنید لینک icon باید قبل از gif باشد تا فایل gif در اولویت قرار گیرد.

نتیجه نهایی قسمت head صفحه به این صورت خواهد بود:

<head>
  <title>SalarBlog.wordpress.com Animated Favicons</title>
  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
  <link rel="shortcut icon" href="favicon.gif" type="image/gif">
</head>

نحوه تغییر favicon با جاوا اسکریپت

در صورتی که بخواهید آیکون سایت در شرایط مختلف تغییر کنه می توانید از روشی که در ادامه توضیح می دهم استفاده کنید.
کاربرد این تغییر می تونه برای وضعیت های مختلف صفحه باشه. برای مثال آیکون سایت برای روز متفاوت از آیکون برای شب باشه و از این مدل تغییرات.

این روش فقط در مرورگرهای فایرفاکس و اپرا کار خواهد کرد و در سایر مرورگرها بی اثر هست.
تابع زیر در جاوا اسکریپت با دریافت آدرس آیکون آن را به صفحه اعمال می کند:

function ChangeFavicon(iconUrl){
  var docHead=document.getElementsByTagName('head');
  if(docHead!=null && docHead.length>0)
    docHead=docHead[0];
else return;
  var link = document.createElement("link");
  link.type="image/x-icon";
  link.href=iconUrl;
  link.rel="shortcut icon";
  docHead.appendChild(link);
}

در این تابع با ایجاد یک شی Link و مقدار دهی آن و سپس اضافه کردن آن به بخش head سایت مرورگر رو وادار به شناسایی آیکون جدید کرده و آن را نمایش می دهیم.

استفاده از این تابع آسان است و به صورت زیر خواهد بود.

ChangeFavicon('favicon-frame1.ico');
ChangeFavicon('favicon-frame2.ico');
ChangeFavicon('favicon-frame3.ico');

پ.ن: برای ایجاد favicon به صورت آنلاین از سایت favicon.cc استفاده کنید.

موفق باشید





دو مقاله دو ابزار

11 03 2009

چند روز پیش کار بر روی دو تا مقاله  توصیفی برای ابزارهایی که قبلا نوشته بودم روبه پایان رسوندم که لینک آنها رو اینجا میذارم.

ابزار اولی مربوط به کنترل خطاها در Asp.Net و بعدی در مورد کار با thread ها در دلفی است.

در این مقاله به بررسی ابزار CustomErrorHandler می پردازیم که به ما این امکان را می دهد که شیوه نمایش خطاها را در ASP.NET تغییر دهیم. این خطاها از لحاظ ساختار بسیار استاندارد تر و برای گوگل قابل تشخیص تر هستند.

با استفاده ابزار ThreadWorker که برای دلفی نوشته شده است می توانید، وظایف و یا کارهای طولانی خود را بدون وقفه در کار برنامه انجام دهید.





محاسبه تعداد کاربران آنلاین در ASP.NET

3 01 2009

محاسبه کاربران آنلاین در سایت یکی از مسائل مهم در آمار گیری آن هست که برای انجام آن راه های مختلفی وجود دارد. برخی سایت ها وجود دارند که چنین سرویس هایی را برای سایتها عرضه می کنند و برخی سیستم های آمار گیری وجود دارند که می توانید آن ها را خریداری کرده و یا به طور رایگان استفاده کنید.

در ASP.NET نیز چندین روش وجود دارد که یکی از آنها استفاده از رویدادهای Session است. در استفاده از شیئ Session در حالت عادی چندین مشکل مانند TimeOut وجود دارد که باعث عدم کارایی این روش می شود.

ابزار OnlineActiveUsers را به عنوان راه حلی ساده برای این مشکلات نوشتم تا به راحتی امکان آمار گیری از کاربران آنلاین سایت وجود داشته باشد.

  • توضیحی مختصر درباره OnlineActiveUsers

این ابزار از رویدادهای شیئ Session به نحوی متفاوت استفاده می کند، به گونه ای که مشکلات استفاده از آن مرتفع شده و روشی مطمئن برخوردار خواهید بود.

در صورتی که نیاز دارید تفکیکی بر کاربران عضو در سایت و کاربران میهمان داشته باشید، این ابزار این کار را برای شما می تواند انجام دهد و تنها کافی است تا فراخوانی های مورد نیاز انجام شود.

این ابزار را کاملا تست شده نوشته ام و هم اکنون بر روی سایت خودم نصب هست.

اگر نظرات و پیشنهادی در مورد توسعه این ابزار دارید خوشحال می شوم

* مطالب مرتبط

بررسی فعال بودن Javascript در ASP.NET

راهنمای کامل توابع JQuery و MooTools به صورت CHM





بررسی فعال بودن Javascript در ASP.NET

28 12 2008

برای اینکه به فعال بودن جاوا اسکریپت در مرورگر کاربر مطمئن بشین چند روش می تونید استفاده کنید که با توجه به نیازتون یکی از اونا رو انتخاب کنید و یا ترکیبی از آنها استفاده کنید.

  • استفاده از تگ noscript

البته این روش ربطی به ASP.NET نداره ولی با این حال مفید هست. با استفاده از کد زیر می تونید کاربر رو زمانی که جاوا اسکریپت در صفحه فعال نیست به صفحه خاصی هدایت کنید.

<meta http-equiv="refresh" content="0;url=EnableScripts.htm">

همانطور که از کد مشخصه این کار باعث میشه تا کاربر به صفحه EnableScripts.htm هدایت بشه. معمولا این روش زیاد کاربر پسند نیست ولی برای صفحاتی که بدون وجود جاوا اسکریپت به هم خواهند خورد و فعال بودن آن حیاطی هست می تونید از این روش استفاده کنید. دقت کنید که کد بالا باید در بخش Head سایت قرار بگیره، مانند زیر:<head>
<title>Welcome</title>
<noscript>
<meta http-equiv="refresh" content="0;url=http://www.google.com">
</noscript>
</head>

همچنین به جای کد بالا می تونید پیغامی رو در صفحه نمایش بدید:

<noscript>
کاربر گرامی، برای مشاهده این صفحه باید جاوا اسکریپت را در مرورگر خود فعال نمایید.
</noscript>

که این کد رو می تونید در هرجای صفحه قرار بدین.

  • استفاده از فیلد مخفی یا Hidden در ASP.NET

گاهی اوقات لازم است تا در صفحه هایی مانند فرم های ویزارد که چندین مرحله دارند، از فعال بودن جاوا اسکریپت اطمینان حاصل کنید. بدین منظور می توانید از دو روش متفاوت استفاده کنید که من در اینجا یکیش رو توضیح می دم.

برای بررسی فعال بودن جاوا اسکریپت در رویداد های PostBack ابتدا باید یک فیلد hidden رو در فرم قرار بدین و مقدار این فیلد رو برابر 0 قرار بدین. چیزی شبیه این:
<asp:HiddenField ID="txtCheckJS" runat="server" Value="0" />

سپس باید کد زیر رو در زیر همین کنترل قرار بدین. این کد فقط زمانی اجرا خواهد شد که جاوا اسکریپت در صفحه فعال باشد.

<script type="text/javascript">
document.getElementById('<%=txtCheckJS.ClientID %>').value='1';
</script>

کاری که این کد جاوا اسکریپت انجام میده تغییر مقدار فیلد به 1 هست، که یعنی جاوا اسکریپت فعال است. تنها کاری که باقی مونده بررسی این فیلد در رویداد کلیک هست. کافی هست که کدی مانند زیر رو استفاده کنید:
if (Page.IsPostBack)
{
if (Convert.ToInt32(txtCheckJS.Value) == 1)
{
// Javascript is enabled
}
else
{
// Oh No!
// Javascript is not enabled
}
}

روش دیگری از بررسی که با استفاده از Validator ها پیاده سازی شده رو می تونید تو این پست از وبلاگ وحید نصیری مطالعه کنید.