۱۳۸۸/۰۴/۱۵

بارگذاري يك يوزركنترل با استفاده از جي‌كوئري


مزيت استفاده از يوزر كنترل‌ها، ماژولار كردن برنامه است. براي مثال اگر صفحه جاري شما قرار است از چهار قسمت اخبار، منوي پويا ، سخن روز و آمار كاربران تشكيل شود، مي‌توان هر كدام را توسط يك يوزر كنترل پياده سازي كرده و سپس صفحه اصلي را از كنار هم قرار دادن اين يوزر كنترل‌ها تهيه نمود.
با اين توضيحات اكنون مي‌خواهيم يك يوزكنترل ASP.Net را توسط jQuery Ajax بارگذاري كرده و نمايش دهيم. حداقل دو مورد كاربرد را مي‌توان براي آن متصور شد:
الف) در اولين باري كه يك صفحه در حال بارگذاري است، قسمت‌هاي مختلف آن‌را بتوان از يوزر كنترل‌هاي مختلف خواند و تا زمان بارگذاري كامل هر كدام، يك عبارت لطفا منتظر بمانيد را نمايش داد. نمونه‌ي آن‌را شايد در بعضي از CMS هاي جديد ديده باشيد. صفحه به سرعت بارگذاري مي‌شود. در حاليكه مشغول مرور صفحه جاري هستيد، قسمت‌هاي مختلف صفحه پديدار مي‌شوند.
ب) بارگذاري يك قسمت دلخواه صفحه بر اساس درخواست كاربر. مثلا كليك بر روي يك دكمه و امثال آن.

روش كلي كار:
1) تهيه يك متد وب سرويس كه يوزر كنترل را بر روي سرور اجرا كرده و حاصل را تبديل به يك رشته كند.
2) استفاده از متد Ajax جي‌كوئري براي فراخواني اين متد وب سرويس و افزودن رشته دريافت شده به صفحه.
بديهي است زمانيكه متد Ajax فراخواني مي‌شود مي‌توان عبارت يا تصوير منتظر بمانيد را نمايش داد و پس از پايان كار اين متد، عبارت (يا تصوير) را مخفي نمود.

پياده سازي:
قسمت تبديل يك يوزر كنترل به رشته را قبلا در مقاله "تهيه قالب براي ايميل‌هاي ارسالي يك برنامه ASP.Net" مشاهده كرده‌ايد. در اين‌جا براي استفاده از اين متد در يك وب سرويس نياز به كمي تغيير وجود داشت (KeyValuePair ها درست سريالايز نمي‌شوند) كه نتيجه نهايي به صورت زير است. يك فايل Ajax.asmx را به برنامه اضافه كرده و سپس در صفحه Ajax.asmx.cs كد آن به صورت زير مي‌تواند باشد:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.UI;
using System.Web.UI.HtmlControls;

namespace AjaxTest
{
public class KeyVal
{
public string Key { set; get; }
public object Value { set; get; }
}

/// <summary>
/// Summary description for Ajax
/// </summary>
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Ajax : WebService
{
/// <summary>
/// Removes Form tags using Regular Expression
/// </summary>
private static string cleanHtml(string html)
{
return Regex.Replace(html, @"<[/]?(form)[^>]*?>", string.Empty, RegexOptions.IgnoreCase);
}

/// <summary>
/// تبديل يك يوزر كنترل به معادل اچ تي ام ال آن
/// </summary>
/// <param name="path">مسير يوزر كنترل</param>
/// <param name="properties">ليست خواص به همراه مقادير مورد نظر</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"><c>NotImplementedException</c>.</exception>
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string RenderUserControl(string path,
List<KeyVal> properties)
{
Page pageHolder = new Page();

UserControl viewControl =
(UserControl)pageHolder.LoadControl(path);

viewControl.EnableViewState = false;

Type viewControlType = viewControl.GetType();

if (properties != null)
foreach (var pair in properties)
{
if (pair.Key != null)
{
PropertyInfo property =
viewControlType.GetProperty(pair.Key);

if (property != null)
{
if (pair.Value != null) property.SetValue(viewControl, pair.Value, null);
}
else
{
throw new NotImplementedException(string.Format(
"UserControl: {0} does not have a public {1} property.",
path, pair.Key));
}
}
}

//Form control is mandatory on page control to process User Controls
HtmlForm form = new HtmlForm();

//Add user control to the form
form.Controls.Add(viewControl);

//Add form to the page
pageHolder.Controls.Add(form);

//Write the control Html to text writer
StringWriter textWriter = new StringWriter();

//execute page on server
HttpContext.Current.Server.Execute(pageHolder, textWriter, false);

// Clean up code and return html
return cleanHtml(textWriter.ToString());
}
}
}
تا اين‌جا متد وب سرويسي را داريم كه مي‌تواند مسير يك يوزر كنترل را به همراه خواص عمومي آن‌را دريافت كرده و سپس يوزر كنترل را رندر نموده و حاصل را به صورت HTML به شما تحويل دهد. با استفاده از reflection خواص عمومي يوزر كنترل يافت شده و مقادير لازم به آن‌ها پاس مي‌شوند.

چند نكته:
الف) وب كانفيگ برنامه ASP.Net شما اگر با VS 2008 ايجاد شده باشد مداخل لازم را براي استفاده از اين وب سرويس توسط jQuery Ajax دارد در غير اينصورت موفق به استفاده از آن نخواهيد شد.
ب) هنگام بازگرداندن اين اطلاعات با فرمت json = ResponseFormat.Json جهت استفاده در jQuery Ajax ، گاهي از اوقات بسته به حجم بازگردانده شده ممكن است خطايي حاصل شده و عمليات متوقف شد. اين طول پيش فرض را (maxJsonLength) در وب كانفيگ به صورت زير تنظيم كنيد تا مشكل حل شود:

<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="10000000"></jsonSerialization>
</webServices>
</scripting>
</system.web.extensions>

براي پياده سازي قسمت Ajax آن براي اينكه كار كمي تميزتر و با قابليت استفاده مجدد شود يك پلاگين تهيه شده (فايلي با نام jquery.advloaduc.js) كه سورس آن به صورت زير است:

$.fn.advloaduc = function(options) {
var defaults = {
webServiceName: 'Ajax.asmx', //نام فايل وب سرويس ما
renderUCMethod: 'RenderUserControl', //متد وب سرويس
ucMethodJsonParams: '{path:\'\'}',//پارامترهايي كه قرار است پاس شوند
completeHandler: null //پس از پايان كار وب سرويس اين متد جاوا اسكريپتي فراخواني مي‌شود
};
var options = $.extend(defaults, options);

return this.each(function() {
var obj = $(this);
obj.prepend("<div align='center'> لطفا اندكي تامل بفرمائيد... <img src=\"images/loading.gif\"/></div>");

$.ajax({
type: "POST",
url: options.webServiceName + "/" + options.renderUCMethod,
data: options.ucMethodJsonParams,
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
obj.html(msg.d);

// if specified make callback and pass element
if (options.completeHandler)
options.completeHandler(this);
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
obj.html("امكان اتصال به سرور در اين لحظه مقدور نيست. لطفا مجددا سعي كنيد.");
}
});
});
};
براي اينكه با كليات اين روش آشنا شويد مي‌توان به مقاله "بررسي وجود نام كاربر با استفاده از jQuery Ajax در ASP.Net" مراجعه نمود كه از ذكر مجدد آن‌ها خودداري مي‌شود. همچنين در مورد نوشتن يك پلاگين جي‌كوئري در مقاله "افزونه جملات قصار jQuery" توضيحاتي داده شده است.
عمده كاري كه در اين پلاگين صورت مي‌گيرد فراخواني متد Ajax جي‌كوئري است. سپس به متد وب سرويس ما (كه در اينجا نام آن به صورت پارامتر نيز قابل دريافت است)، پارامترهاي لازم پاس شده و سپس نتيجه حاصل به يك شيء در صفحه اضافه مي‌شود.
completeHandler آن اختياري است و پس از پايان كار متد اجكس فراخواني مي‌شود. در صورتيكه به آن نيازي نداشتيد يا مقدار آن را null قرار دهيد يا اصلا آن‌را ذكر نكنيد.

مثالي در مورد استفاده از اين وب سرويس و همچنين پلاگين جي‌كوئري نوشته شده:

الف) يوزر كنترل ساده زير را به پروژه اضافه كنيد:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="part1.ascx.cs" Inherits="TestJQueryAjax.part1" %>
<asp:Label runat="server" ID="lblData" ></asp:Label>
بديهي است يك يوزر كنترل مي‌تواند به اندازه يك صفحه كامل پيچيده باشد به همراه انواع و اقسام ارتباطات با ديتابيس و غيره.

سپس كد آن‌را به صورت زير تغيير دهيد:

using System;
using System.Threading;

namespace TestJQueryAjax
{
public partial class part1 : System.Web.UI.UserControl
{
public string Text1 { set; get; }
public string Text2 { set; get; }

protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep(3000);
if (!string.IsNullOrEmpty(Text1) && !string.IsNullOrEmpty(Text2))
lblData.Text = Text1 + "<br/>" + Text2;
}
}
}
اين يوزر كنترل دو خاصيت عمومي دارد كه توسط وب سرويس مقدار دهي خواهد شد و نهايتا حاصل نهايي را در يك ليبل در دو سطر نمايش مي‌دهد.
عمدا يك sleep سه ثانيه‌اي در اينجا در نظر گرفته شده تا اثر آن‌را بهتر بتوان مشاهده كرد.

ب) اكنون كد مربوط به صفحه‌اي كه قرار است اين يوزر كنترل را به صورت غيرهمزمان بارگذاري كند به صورت زير خواهد بود (مهم‌ترين قسمت آن نحوه تشكيل پارامترها و مقدار دهي خواص يوزر كنترل است):

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestJQueryAjax._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jquery.advloaduc.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script>

<script type="text/javascript">
function showAlert() {
alert('finished!');
}

//تشكيل پارامترهاي متد وب سرويس جهت ارسال به آن
var fileName = 'part1.ascx';
var props = [{ 'Key': 'Text1', 'Value': 'سطر يك' }, { 'Key': 'Text2', 'Value': 'سطر 2'}];
var jsonText = JSON.stringify({ path: fileName, properties: props });

$(document).ready(function() {
$("#loadMyUc").advloaduc({
webServiceName: 'Ajax.asmx',
renderUCMethod: 'RenderUserControl',
ucMethodJsonParams: jsonText,
completeHandler: showAlert
});
});

</script>

</head>
<body>
<form id="form1" runat="server">
<div id="loadMyUc">
</div>
</form>
</body>

</html>
نكته:
براي ارسال صحيح و امن اطلاعات json به سرور، از اسكريپت استاندارد json2.js استفاده شد.

ابزار ديباگ:
بهترين ابزار براي ديباگ اين نوع اسكريپت‌ها استفاده از افزونه فايرباگ فايرفاكس است. براي مثال مطابق تصوير زير، يوزر كنترلي فراخواني شده است كه در سرور وجود ندارد:


دريافت مثال فوق