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


مزيت استفاده از يوزر كنترل‌ها، ماژولار كردن برنامه است. براي مثال اگر صفحه جاري شما قرار است از چهار قسمت اخبار، منوي پويا ، سخن روز و آمار كاربران تشكيل شود، مي‌توان هر كدام را توسط يك يوزر كنترل پياده سازي كرده و سپس صفحه اصلي را از كنار هم قرار دادن اين يوزر كنترل‌ها تهيه نمود.
با اين توضيحات اكنون مي‌خواهيم يك يوزكنترل 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 استفاده شد.

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


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


7 نظرات:

  نیما

سلام استاد نصیری
بسیار مقاله مفید و Trick جالبی بود.به تازگی افزونه ای برای فایر باگ عرضه شده به اسم Fire Query که مواردی را برای کار و دیباگ jQuery فراهم میکنه.
یه سوال از حضورتون داشتم چرا خروجی Execute در وب سرویس شامل تگ های html و body نیست؟آیا این همیشه ثابته؟ و اینکه مواردی مثل r\ و n\ رو هم از خروجی حذف کنیم بهتر نیست؟
باسپاس از شما استاد عزیز

  ناشناس

سلام آقای نصیری
امکانش هست در مورد متد stringify و عملی که در اینجا انجام میده کمی توضیح بدهید؟ من اون لینک صفحه JSON رو خوندم ولی متوجه نشدم چرا شما اینجا از این متد استفاده کردید.
ممنون

  وحيد نصيري

@نيما
بله. يك trim را هم مي‌توانيد اضافه كنيد بعلاوه نكته حذف فاصله خالي بين تگ‌ها كه در كاهش حجم نهايي مؤثر است.

@ناشناس
مي‌شد اين آرگومان‌هاي مورد نظر رو به شكل يك رشته يك سطري هم در آورد (فرقي نمي‌كرد). اما با استفاده از اين تابع شما مي‌تونيد يك شيء جاوا اسكريپتي را تبديل به json كنيد كه از اين لحاظ خوب يك قدم پيشرفت است و در خطايابي كدها كمك بزرگي است (رشته قابل خطايابي نيست).

  مهدی

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

  وحيد نصيري

اگر خطاي viewstate is corrupted را دريافت مي‌كنيد به اين علت است كه الان در صفحه دو فيلد مخفي viewstate داريم. براي حذف اين مورد دوم سطر زير را به تابع cleanHtml‌اضافه كنيد:
html = Regex.Replace(html, "<input.*name=\"__VIEWSTATE\"[^>]*>", string.Empty, RegexOptions.IgnoreCase);

+ در اين حالت چون اطلاعات كنترل‌ها به ViewState ‌اضافه نمي‌شوند،‌ ASP.Net Postback هم كار نمي‌كنه.
در اين حالت براي ارسال ديتا و غيره امكان استفاده از وب سرويس و پاس كردن مقدار به آن هست (با استفاده از جي‌كوئري اجكس).
كمي بيشتر كار ميبره نسبت به MS Ajax.

  sirasad

آقاي نصيري عزيز من خواستم اين كد رو در .net 2.0 تست بكنم , مقادير اون تكست ها منتقل نمي شود ولي showalert رو فايرآپ ميكنه .
يه مسئله ديگه اينكه اين ajax شما بدون پارامتر ورودي كار ميكنه ؟

  وحيد نصيري

بله. اگر پارامتر ورودي نداشت يك '{}' قرار دهيد كفايت مي‌كنه.
براي دات نت 2 كمي كار شما بيشتر مي‌شود چون در VS2008 خيلي از موارد را خودكار اضافه مي‌كند.
در اين حالت يا بايد ASP.NET AJAX Extensions روي سرور نصب باشد يا dll هاي آن‌را به صورت دستي به دايركتوري bin برنامه اضافه كنيد و يك ارجاع از آن‌ها را نيز به برنامه اضافه كنيد.
همچنين وب كانفيگ را نيز بايد ويرايش كنيد تا اين تغيير در آن لحاظ شود. (در غير اينصورت كار نمي‌كند)
يك نكته ديگر هم اينكه بجاي msg.d شما فقط msg را در asp.net 2‌ داريد و اين d از 3.5 به بعد اضافه شده (در قسمت success و هنگام دريافت پاسخ از سرور توسط jQuery).

ارسال يک نظر