نحوه ساخت Custom Control در ASP.Net

یکشنبه 16 خرداد 1395

این مقاله برای برنامه نویسانی نوشته شده است که تجربه کار زیادی با ASP.NET custom control (کنترل های سفارشی شده ASP.Net) ندارند. در این مقاله، مفهوم Control های سفارشی شده را بررسی می کنیم و مطالب مفیدی راجع به آن ها می آموزیم.

نحوه ساخت Custom Control در ASP.Net

چگونگی کد نویسی

همه ی کد ها در یک project library قرار دارند. بنابراین شما می توانید آن را کامپایل کنید و در مواقع بعدی از آن استفاده کنید. این وب سایت یک نمونه ی ساده است که جهت تست ایجاد شده است و به صورت کامل برای هر بخش توضیحاتی در مقاله گنجانده شده است. این پروژه با استفاده از برنامه ی visual Studio  و C# نوشته شده است.

مفهوم

-user control چیست؟ و یک custom control به چه معناست؟

-دلایل برتری custom control ها بر user control ها چیست؟

- قابل استفاده برای ثبت Toolbox ، icon و tag

-مدیریت ViewState

-مقایسه ی custom control های ساده و custom control های ترکیبی

-ذخیره سازی مجدد داده های ارسال شده

-مدیریت رخدادهای ساده

-گسترش custom control ها

- custom control ها و Web Part

user control چیست؟ و یک custom control به چه معناست؟

User control مانند یک محفظه (ظرف نگهدارنده ) برای markup ها (شامل HTML و Server control ها) به شمار می رود که شما می توانید از ان ها در هر بخشی از یک وب سایت استفاده کنید. شما می توانید Property ها، رویدادها و متدهای مورد نظرتان را اضافه کنید و به شکل دلخواه خودتان، آن ها را سفارشی سازی کنید. ایجاد یک User control ، بسیار ساده تر از ایجاد یک custom control است  و معمولا به صورت ساده و با drag and drop کردن کنترل ها از Toolbox انجام می گیرد. Custom control یک کلاس است که از توسعه ی کلاس های Control و WebControl ایجاد شده است. برای این که بتوانید از Control استفاده کنید، باید ابتدا آن را ثبت کنید.

دلایل برتری custom control ها بر user control ها چیست؟

مطمئنا ما مجبور نیستیم در همه ی پروژه ها از custom control استفاده کنیم ولی دلایل زیر می توانند شما را قانع کنند که custom control ها بسیار کاربردی و مفید هستند.

اگر شما می خواهید control هایتان را در سطح وسیعی از برنامه ها، وب سایت ها و حتی برنامه های خارجی مبتنی بر .NET به اشتراک بگذارید، custom control ها بهترین انتخاب برای شما خواهند بود، زیرا می توانند کامپایل شوند و به این ترتیب به راحتی در پروژه های دیگر مورد استفاده قرار بگیرند.

اگر حوزه ی فعالیت تان امنیتی است، یعنی در حال توسعه ی Control ای هستید که قرار است برای سازمان ها و یا بخش های دیگر استفاده شود، و یا  Contorl مورد نظرتان دارای اطلاعات مهمی است و عملیات حساسی انجام می دهد، بهتر است از custom control ها استفاده کنید. زیرا می توانید به راحتی آن ها را کامپایل کنید و به صورت یک assembly دربیاورید که در این حالت امنیت آن در سطح بالایی است.

اگر به دنبال کارایی و مقیاس پذیری هستید، همیشه از custom control ها استفاده کنید. زیرا شما می توانید به آسانی روند پردازش ها را تغییر بدهید و محتویات را نیز اضافه و یا کم کنید. در این صورت کارایی شما به میزان قابل توجهی افزایش پیدا خواهد کرد.

پشتیبانی گسترده و قوی جهت امورطراحی : اگر به دنبال ابزار طراحی گسترده و قدرتمند برای استفاده در Control هستید ، به سراغ   custom control ها بروید. در آن ها طیف وسیعی از Property های تعبیه شده و رویدادهای آماده وجود دارد و شما می توانید به راحتی با آن ها کار کنید. از سوی دیگر ، یک user Control پشتیبانی بسیار کمی در حوزه ی طراحی دارد و Property ها و رویداد های آن بسیار محدود است .

اگر می خواهید که Control شما قادر باشد به راحتی با برنامه های دیگر ارتباط برقرار کند و تعامل داشته باشد، بهترین انتخاب،  custom control خواهد بود . custom control ها حتی می توانند در درون Control  های دیگر نیز به کار گرفته شوند. اما این قابلیت ها در User Control در دسترس نیستند.

custom control ها ، قابل جابجایی از یک سیستم به سیستم دیگر هستند. (portable) این ویژگی یکی از بهترین ویژگی های آن ها به شمار می رود.

ثبت Toolbox, iconو   tag

وقتی شما به وسیله ی قالب Visual Studio 8 ASP.NET Server Control  یک پروژه ایجاد می کنید، کدهای زیر به صورت اتوماتیک برای شما ایجاد خواهند شد:

[DefaultProperty("Text")]
[ToolboxData("<{0}:WebCustomControl1 runat="server">")]
public class WebCustomControl1 : WebControl {
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("")]
    [Localizable(true)]
    public string Text {
        get {
            String s = (String)ViewState["Text"];
            return ((s == null) ? String.Empty : s);
        }

        set {
            ViewState["Text"] = value;
        }
    }

    protected override void RenderContents(HtmlTextWriter output) {
        output.Write(Text);
    }
}

این همه ی کاری است که برای ساخت یک custom controlساده نیاز دارید. custom control برای شما ویژگی ToolboxData را تعریف می کند که تگ ثبت نام (registration tag) را در صفحه ی ASPX به عنوان یک server markup ایجاد و کنترل می کند. همچنین WebControl را برای شما توسعه می دهد که  یکی از پیچیده ترین کنترل های بصری است . این ویژگی ، یک Property با چند صفت برای ما ایجاد می کند . شما می توانید ببینید که همه ی فیلد های  Property  با یک شی از ViewState جایگزین می شوند و در نهایت ، کنترل به عنوان یک متن ساده به شما تحویل داده می شود. ما می توانیم این کد تولید شده را با مطابق با نیازهایمان ، کمی تغییر بدهیم:

[ToolboxData("<{0}:CustomEditBox runat="\""server\" Label=\"[Label]\" Text=\"[Text]\">")]

 

در اینجا  tagتولید شده را با یک المان سفارشی شده جایگزین کردیم. توجه کنید که می توانیم مطابق زیر ، تعداد بیشتری صفت برای تولید شدن در صفحه ی container ASPX اضافه کنیم. برای آیکون مورد استفاده برای اجزا، تنها کاری که لازم است انجام بدهید این است که یک تصویر BMP با نام مشابه custom Control  ایجاد کنید. ایجاد یک تصویر برای آیکون ها جزو امور اختیاری محسوب می شود و شما در صورت نیاز می توانید این کار را انجام بدهید.

ولی در نهایت می بینیم که آیکونی که ایجاد کردیم در قسمت tool box در تب ثبت نام خودکار نمایش داده نخواهد شد. برای نمایش آن ، باید ثبت نام را از طریق toolbox انجام دهید. ابتدا Item context menu را انتخاب کنید،آدرس assembly مورد نظر را وارد کنید و سپس جزء مورد نظر را ثبت کنید. حالا می توانید آیکون مورد نظر را در toolbox ببینید.

حالا همه چیز بجز تکه کد زیر آماده است:

[ToolboxData("<{0}:CustomEditBox runat="\""server\" Label=\"[Label]\" Text=\"[Text]\">")]

چطور {0} ، مقدار مورد نظر را می گیردد و از کجا این کار را انجام می دهد؟

در مورد این موضوع نگران نباشید.شما می توانید پیشوند های مورد نیاز برای tag  هایتان(TagPrefix) را به وسیله ی تکه کد زیر ثبت کنید:

[assembly: TagPrefix("FirstCustomControl", "FC")] 

شما باید این خط را در AssemblyInfo.cs در پروژه ی class library ایجاد کنید و بعد از این کار می توانید ببینید که TagPrefix در درون فضای نام ثبت خواهد شد(نه در درون کلاس ) بنابراین همه ی کلاس ها در درون فضای نام، پیشوند مشابه را خواهند گرفت. بنابراین هر بار که Control را از نوار ابزار به درون صفحه وب می کشید، المان زیر برای شما تولید خواهد شد:

<FC:CustomEmailLink ID="CustomEmailLink1" runat="server" />

مدیریت ViewState

این عنوان بسیار مهم است. در حقیقت، مهم ترین عنوان در Custom Control ها همین است. با این که مطالب زیادی در مورد ViewState وجود دارد، بیشتر برنامه نویسان اشتباهات و خطاهایی در این زمینه دارند. وقتی آن ها می خواهند Control های فرزند (کنترل های داخلی) را در ViewState ذخیره کنند ، با این خطا مواجه می شوند: “this [object] is not serializable” ابتدا اجازه بدهید کمی با مفهوم ViewState آشنا شویم. ViewState یک شی داخلی ASP.NET server است که حالت صفحه و کنترل های داخلی را در خودش نگهداری می کند ، بنابراین مفهوم آن با شی Session که شی های مربوط به یک بخش خاص را در خودش نگهداری می کند، تفاوت دارد.متغیر های session در همه ی صفحات در دسترس هستند ولی ViewState فقط برای یک صفحه است و به صورت فیلدهای مخفی به کاربر نمایش داده می شود، به همین دلیل شی Session قابل سریال سازی نیست. ViewState یک property از HttpContext نیست، زیرا یک شی واحد برای یک صفحه است . حالا بیایید نگاهی سریع به این کد بیندازیم .

[DefaultProperty("Text")]
[ToolboxData("<{0}:CustomEmailLink runat="server">")]
public class CustomEmailLink : WebControl {
//.... other code
//.... other code

[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]
public string Email {
    get {
        String s = (String)ViewState["Email"];
        return ((s == null) ? "[Email]" : s);
    }

    set {
        ViewState["Email"] = value;
    }
}

 Property ای که به نام Email است، حالت داخلی را در درون یک فیلد ذخیره نمی کند، زیرا custom control هر بار که صفحه Post back  شود، دوباره ایجاد می شود، بنابراین شی دوباره از ابتدا در server ایجاد می شود و از طریق صفحه ای که پست شده است، دوباره ذخیره سازی می شود.(در صفحه، یک ViewState data در قالب یک فایل مخفی وجود دارد) تکه کد زیر بیانگر توضیحات بالا است:

[DefaultProperty("Text")]
[ToolboxData("<{0}:CustomEditBoxPrimitive runat="\""server\" 
             Label=\"[Label]\" Text=\"[Text]\">")]

public class CustomEditBoxPrimitive : WebControl {

    public string Text {
    get {
        String s = ViewState["Text"] as string;
        return ((s == null) ? "[Text]" : s);
    }

    set {
        ViewState["Text"] = value;
    }
}

حالا در مورد CustomEditBoxPrimitive صحبت خواهیم کرد. من آن را primitive نامیده ام زیرا امکاناتی را برای ما فراهم می کند که ما از ابتدای پروژه و کارمان به آن ها نیاز داریم. این بخش از کنترل TextBox داخلی استفاده می کند تا به این وسیله بتواند TextBox را به عنوان بخشی از CustomEditBoxPrimitive نمایش بدهد. اگرچه ما هرگز خود TextBox را در درون ViewState ذخیره نمی کنیم. ما فقط Propertyهای مهم را در درون ViewState ذخیره می کنیم ، مانند متن درون TextBox  و یا مقدار انتخاب شده از drop down list. به هیچ وجه نیازی نیست تا شی های پیچیده را در درون ViewState ذخیره کنیم. مسئله دیگری که در مورد مکانیزم سریال سازی وجود دارد این است که یک فرآیند پرهزینه است، و بر روی کارایی و مقیاس پذیری تاثیر خواهد گذاشت. بنابراین ، در زمان استفاده از ViewState باید مراقب باشیم تا فقط داده های حساس را ذخیره کنیم.

مقایسه ی custom control های ساده و custom control های ترکیبی

این ایده بدی نیست که همه ی کنترل ها را به صورت Custom control های پیچیده و ترکیبی ایجاد کنیم . ولی فایده ی ایجاد یک Custom control ساده ، در کارایی آن است . ساخت درخت کنترل های داخلی (child control) در یک متغیر StringBuilder و نمایش آن ، بسیار سریع تر از ایجاد یک نمونه از هر child control است .

کنترل ساده در حال حاضر یک Property به نام “Controls”دارد که شما می توانید کنترل های بیشتری را به عنوان کنترل های فرزند به آن اضافه کنید. و همچنین یک متد به نام CreateChildControls در این جا وجود دارد که در ساخت درخت مربوط به گره های فرزند به ما کمک می کند.

public class CustomEditBoxPrimitive : WebControl {
//...... code

//..... code

[Bindable(true)]
protected override void CreateChildControls() {
    BuildControl();
    base.CreateChildControls();
}

private void RenderDesign(HtmlTextWriter output) {
    output.Write("Text:{0} Label:{1}", this.Text, this.Label);
}
private void InitControls() {
    _innerTextControl.EnableViewState = true;
    _innerTextControl.ID = "Inner_TextBox";
    _innerLabel.EnableViewState = true;
    _innerLabel.ID = "Inner_Label";
    LoadDataState();
}

همان طور که می بینید، کنترل از WebControl ارث بری کرده است(که پیچیده ترین کنترل موجود است). در همین زمان، این قابلیت را هم دارد که از ایجاد درخت برای کنترل های فرزند پشتیبانی کند.

ذخیره سازی مجدد داده های ارسال شده

این موضوع جذاب ترین بخش در میان مسائل مربوط به Custom Control ها است. (Custom control هایی که دارای یک نوع داده ورودی هستند). در تکه کدی که در زیر می بینید، داده هایی که توسط کنترل ارسال (Post) می شوند، گرفته خواهند شد.

public class CustomEditBoxPrimitive : WebControl {
//.... code
//.... code
private void LoadDataState() {
    if (this.Context != null) {
        foreach (string key in this.Page.Request.Form.AllKeys) {
            if (key.ToLower().Contains(this._innerTextControl.UniqueID.ToLower())) {
                this.Text = this.Page.Request.Form[key];
            }
        }
    }
}

راه دیگری که برای گرفتن داده ها وجود دارد استفاده از custom control API است که توسط Framework ها نیز پشتیبانی می شود به عنوان مثال به کارگیری اینترفیس IPostBackDataHandler . در آن ، یک متد LoadPostData وجود دارد که همه ی اطلاعات را در یک بسته ی key value آماده می کند و بلافاصله آن را در اختیار کاربر قرار می دهد.

[DefaultProperty("Text")]
[ToolboxData("<{0}:CustomEditBox runat="\""server\" Label=\"[Label]\" Text=\"[Text]\">")]
public class CustomEditBox : CompositeControl,IPostBackDataHandler {
//... code
// .. code
public bool LoadPostData(string postDataKey, 
  System.Collections.Specialized.NameValueCollection postCollection) {

    if (postDataKey == this.UniqueID) {
        foreach (string key in postCollection.Keys) {
            if (key.ToLower().Contains(this._innerTextControl.UniqueID.ToLower())) {
                this.Text = postCollection[key];
            }
        }
    }
    return false;
}

مدیریت رخدادهای ساده

مدیریت رخدادهای ساده(Managing simple events) بسیار ساده و راحت است و شما می توانید یک رویداد (Event) سفارشی شده برای خودتان ایجاد کنید و آن را گسترش بدهید. شما برای این کار، دو راه دارید:

می توانید رویداد مورد نظر را با توجه به custom action گسترش بدهید ، مثل توسعه ی رویداد OnChanged که بعد از عمل مقایسه در تکه کد زیر انجام می گیرد. (این مقایسه قبل و بعد از پس شدن داده ها استفاده می شود.)

[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text {
    get {
        String s = ViewState["Text"] as string;
        return ((s == null) ? "[Text]" : s);
    }

    set {
        if (ViewState["Text"] != null && (ViewState["Text"].ToString() != value))
            if (this.Text != this._innerTextControl.Text) 
                OnTextChanged(this, EventArgs.Empty);
        ViewState["Text"] = value;
    }
}

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

گسترش custom control ها

یکی از ویژگی های اصلی custom control ها، توانایی توسعه پذیری آن ها است . شما برای این کار می توانید کتابخانه (Library) مخصوص به خودتان را ایجاد کنید و در پروژه اتان از آن استفاده کنید. با استفاده از این قابلیت شما می توانید یک control را که قبلا ایجاد کرده اید را با Property های دلخواه خودتان گسترش بدهید و یا Control هایی مطابق با نیازهایتان طراحی کنید. همچنین یک Control می تواند با ارث بری از یک server control نیز توسعه پیدا کند.

بعد از نمایش به صورت زیر خواهد بود :

برای داشتن قابلیت های داینامیک بیشتر می توانید از Decorator pattern استفاده کنید که به این معنی است که شما از کلاس WebControl ارث بری می کنید و reference را به کنترل Target از WebControl می دهید.(طریقه ی این کار در شکل زیر نشان داده شده است)

 

گسترش و توسعه ی کنترل با استفاده از الگوی Decorator ،برای درک، نیاز به فعالیت عملی دارد(در برنامه ضمیمه از آن استفاده شده است) ولی شما می توانید یک نوع خاصی از Control ها را برای افزودن قابلیت های بیشتر ایجاد کنید. در نمونه زیر با استفاده از یک کلاس جادویی، همه ی visual control ها را توسعه خواهیم داد.

public class LinkedEmailDecoratorExtender:WebControl {
WebControl _baseControl = null;
public LinkedEmailDecoratorExtender(WebControl baseControl) {
    _baseControl = baseControl;
}
protected override void Render(System.Web.UI.HtmlTextWriter writer) {
    writer.Write(" <div {0}="{1}">", "style", 
                 "'border: medium dotted #FF00FF; padding: 5px; margin: 5px;" + 
                 " width: auto; height: auto; font-family: Arial; font-weight: bold;'");
    _baseControl.RenderControl(writer);
    writer.Write("</div>");
}

custom control ها و Web Part ها

Web Part ها ویژگی های فوق العاده ای هستند که در ASP.NET 2.0 معرفی شدند و زیبایی آن ها قابلیت سازگاری با SharePoint است . اگر custom control شما از یک WebPart ارث بری کند، می توانید از آن در صفحات ASP.NET که از پشتیبانی کامل WebPart zone برخوردار هستند، استفاده کنید. علاوه بر این، شما می توانید از آن در SharePoint دوباره استفاده کنید . برای این کار کافی است از WebPart ارث بری کند. البته این بحث، نیازمند وقت و زمان بیشتری است ولی به طور کلی یکی از دلایلی که custom control ها را بسیار محبوب و متداول کرده است ، ویژگی گسترش پذیری WebPart ها است.

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

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

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید