Вопрос о ASP.NET

Добрый день, господа завсегдатаи форума.

Столкнулся с проблемой при использовании динамического добавления User control.

Для иницилизации контролла испльзовался конструктор, в котором задавались поля контроллов входящих в состав объекта. Во время выполнения получаю исключение Object reference not set to an instance of an object.

Те же самые действия приводят к исключению если привязать заполнение полей к событию PageLoad, однако в случае жесткого задания контролла в файле.aspx все проходит удачно.



Default.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Blog._Default" %>
<%@ Register src="Node.ascx" tagname="Node" tagprefix="sq" %>
www.w3.org/…<wbr></wbr>D/xhtml1-transitional.dtd">

www.w3.org/1999/xhtml" >

    <title>Untitled Page</title>


    <form id="form1">
    <div>

</div>
    </form>



Node.ascx:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Node.ascx.cs" Inherits="Blog.Node" %>
<div class="Node">
    <asp:label id="Head" runat="server"></asp:label>
    <asp:label id="Body" runat="server"></asp:label>
    <asp:label id="Date" runat="server"></asp:label>
</div>

Default.aspx.cs:

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

namespace Blog
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Node post = new Node("Head", "BODY", DateTime.Now);
            Page.Controls.Add(post);
        }

}
}

Node.ascx.cs:

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

namespace Blog
{
    public partial class Node : System.Web.UI.UserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {

}
        public Node(string head, string body, DateTime date)
        {
            Head.Text = head;
            Body.Text = body;
            Date.Text = date.ToLongDateString();
        }
        public Node()
        {
            Head.Text = "head";
            Body.Text = "body";
            Date.Text = DateTime.Now.ToLongDateString();
        }

}
}

Собсетвенно вопрос, о котором упоминалось в названии темы: В чем ошибка?:)


Полдня курения мануалов результата не дало, посему выкладываю сюда.

Насколько я понял дело в моем неправильном понимании событийной модели либо последовательности иницилизации компонентов.


Заранее извиняюсь если посты подобного рода нежелательны на этом форуме, отпишитесь если это так.

👍НравитсяПонравилось0
В избранноеВ избранном0
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Небольшой status-report:
Книга оказалась действительно стоящей. Единственное что не понравилось — перед изложением деталей не достаточно четко рассмотрены основные концепции. Информация есть, но акцент на нее не сделан. Ну, а мой подход с объектами оказался в корне не правильным:)

Так что спасибо Жеке за наводку.

Ок, невопрос. Без обид

Саша, думаю пора бы почитать какую-нибудь литературу, т.к. на инфе из форумов далеко не уедешь. Категорически советую почитать Шпушта-Макдональд ASP.NET 3.5 и Silverlight 2 — там хорошо написано про биндинги, выражения на странице и тому подобные вещи. Без обид...

Итак, правильно сформулируем вопрос: Как правильно использовать привязку данных внутри user control?

В дополнение к предъидущему: При вызове Page.DateBind выражение привязки на самой странице сработало, в контролле нет.

Ммм, спасибо, честно говоря, думал что Data binding это один из этапов жизненного цикла =)
Пробуем =)
UPD: Продолжаем разбор моих тупняков. Прописал в обработчике Page_Load контролла this.DataBind (); И... ничего не произошло Поля так и остались пустыми. И дело уже не в лейблах, написал < %# %> выражение прямо в ascx файле, место так и осталось пустым.

Прочитал главу о привязке данных у Шпушты. Противоречий со своим кодом не нашел)

Еще нюанс:

не нужно забывать энкодить текст — сырой текст выталкивать на страницу небезопасно. Протестить можно следующим образом:

public Node(string head, string body, DateTime date)
    {
        Head = HttpUtility.HtmlEncode(head);
        Body = body;
        Date = date.ToLongDateString();
    }
    public Node()
        : this("<script>alert('hello Head');</script>", "<script>alert('hello Body');</script>", DateTime.Now)
    { }

2 Александр:
забыл самое главное...

Для того, чтобы декларативный биндинг работал, т.е. для того, чтобы можно было писать вот так:

<asp:Label ID="headLabel" runat="server" Text="<%# Head %>"></asp:Label>

нужно на странице, на которой расположен твой юзер контрол, вызвать DataBind() в методе Page_Load на нужном контроле или на всей странице сразу. Другими словами в Default.aspx нужно выполнить:


    protected void Page_Load(object sender, EventArgs e)
    {
        node.DataBind();
    }

или


    protected void Page_Load(object sender, EventArgs e)
    {
        DataBind();
    }

Намек понял, буду смотреть отражения, но всеж с предъидущими вариантами код получается намного проще. Понять что в них не так не смог.

Node.ascx:


<div class = "Node">
    <asp:Label ID="HeadLabel" runat="server" />
    <asp:Label ID="BodyLabel" runat="server" />
    <asp:Label ID="DateLabel" runat="server" />
</div>

Node.ascx.cs


using System;

namespace WebApplication1
{
    public partial class Node : System.Web.UI.UserControl
    {
        public Node(string head, string body, DateTime date)
        {
            HeadLabel.Text = head;
            BodyLabel.Text = body;
            DateLabel.Text = date.ToLongDateString();
        }

public Node() {}
    }
}

Default.aspx


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "www.w3.org/…ransitional.dtd">

<html xmlns="www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:PlaceHolder ID="pHolder" runat="server" />
    </div>
    </form>
</body>
</html>

Default.aspx.cs


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web.UI;

namespace WebApplication1
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Control toAdd = LoadControl("Node.ascx", "aaaa","bbbbb", DateTime.Now);

pHolder.Controls.Add(toAdd);
        }

/// <summary>
        /// Load user control and call appropriate constructor using reflection
        /// </summary>
        /// <param name="controlPath"></param>
        /// <param name="constructorParams"></param>
        /// <returns></returns>
        private UserControl LoadControl(string controlPath, params object[] constructorParams)
        {
            List<Type> paramTypes = new List<Type>();

foreach (object param in constructorParams)
                paramTypes.Add(param.GetType());

// Default constructor of user control is called here
            UserControl ctl = Page.LoadControl(controlPath) as UserControl;

// Now find the constructor with parameters
            ConstructorInfo constructor = ctl.GetType().BaseType.GetConstructor(paramTypes.ToArray());

// Call it
            if (constructor == null)
                throw new MemberAccessException("The requested constructor was not found on : " + ctl.GetType().BaseType.ToString());
            else
                constructor.Invoke(ctl, constructorParams);

return ctl;
        }
    }
}

Итак, мои ручки снова дошли до изучения ASP.NET и... снова неудача. Не то кучерявость ручек не позволила, но результат, как говорится, налицо. Не знаю почему, но вариант с привязкой данных, который посоветовал Жека, не сработал. Место отведенное под искомый контрол так и осталось пустым. Для пущей убедительности даю исходник, буду благодарен за копание в нем.



Control:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Node.ascx.cs" Inherits="Blog.Node" %>
<div class = "Node">
    <asp:Label ID="HeadLabel" runat="server" Text ="<%#Head%>"></asp:Label>
    <asp:Label ID="BodyLabel" runat="server" Text ="<%#Body%>"></asp:Label>
    <asp:Label ID="DateLabel" runat="server" Text ="<%#Date%>"></asp:Label>
</div>

public partial class Node : System.Web.UI.UserControl
    {
        protected string Head, Body, Date;
        protected void Page_Load(object sender, EventArgs e)
        {
        }
        public Node(string head, string body, DateTime date)
        {
            Head = head;
            Body = body;
            Date = date.ToLongDateString();
        }
        public Node():this("HEAD","BODY",DateTime.Now)
        { }

}
}

Default.aspx.cs:

protected void Page_PreInit(object sender, EventArgs e)
        {
            Node post = new Node("aaaa","bbbbb", DateTime.Now);
            StripeHolder.Controls.Add(post);

}

Как ни странно, со вторым вариантом тоже облом:) Великий и Ужастный нуллреференсэксепшн поглотил мой ничтожный код при создании контролла в PreInit и ручном переписывании полей в Load. Вот.

Прошу малость прояснить ситуацию.

UserControl добавляется на страницу с помощью метода LoadControl:

Control FeaturedProductUserControl = LoadControl("FeaturedProduct.ascx");
Controls.Add(FeaturedProductUserControl);

или в Placeholder:

PlaceHolderLeftMenu.Controls.Add(FeaturedProductUserControl);

Спасибо, камрады, даж не знаю что такого сказать хорошего:)

2 Александр Олейников:
Существует несколько способов вызова конструкторов:
public YourClassName (): this ( «parameter1» ) — вызывается другой конструктор данного класса с параметром
public YourClassName (): base ( «parameter1» ) — вызывается конструктор базового класса с параметром
public YourClassName () — вызывается конструктор базового класса без параметра, синтаксис аналогичный следующему public YourClassName (): base ()

Где это применимо — да везде применимо, это синтаксис языка, так то можешь применять его где угодно.

а может в событии PreInit это делать?
Хотя так не принято, но все же можно.
У меня была подобная проблема, МакДональд и Шпушта как-то писали об этом в своей книге.

Я делал добавение label-ов в pageLoad:

        protected void Page_Load(object sender, EventArgs e)
        {
            ControlsHelper.UpdateBrandsFromBD(this);
            ControlsHelper.ReloadLabelsOnPageLoad(this, holder);
    }

public static void ReloadLabelsOnPageLoad(Page page, PlaceHolder holder)
        {
            Object obj = page.Session[LABEL_LIST];
            if (obj != null)
            {
                holder.Controls.Clear();
                foreach (object o in (ArrayList)obj)
                {
                    Label label = new Label();
                    label.Text = (string)o + "";
                    holder.Controls.Add(label);
                }
            }
        }

У меня так работало. Может пробовать ДОБАВЛЯТЬ контролы?

PS лучше контролы добавлять в холдер.

Спасибо всем за оперативность:)

Подметил для себя интересный синтаксис наследования конструктора. Где еще это применимо?

Проблема в том, что обращаться к контролам можно обращаться не ранее метода OnInit (). В конструкторе контролов Label еще нет — поэтому и происходит null reference exception.
Выходов из ситуации несколько:
1) Создать 3 protected филда, забивать значения в них в конструкторе и биднить ими контролы:
public partial class Node: UserControl
{
protected string Head;
protected string Body;
protected string Date;
public Node (string head, string body, DateTime date)
{
Head = head;
Body = body;
Date = date.ToLongDateString ();
}
public Node (): this (“head text”, “body text”, DateTime.Now)
{} }
< div class = “Node” >
< asp: Label ID= “headLabel” runat= “server” Text="< %# Head %> “></asp: Label>
< asp: Label ID= “bodyLabel” runat= “server” Text="< %# Body %> “></asp: Label>
< asp: Label ID= “dateLabel” runat= “server” Text="< %# Date %> “></asp: Label>
</div>
2) Создать 3 приватных филда, забивать значения в них в конструкторе и популировать лэйблы в Page_Load:
public partial class Node: UserControl
{
private string Head;
private string Body;
private string Date;
protected void Page_Load (object sender, EventArgs e)
{
headLabel.Text = Head;
bodyLabel.Text = Body;
dateLabel.Text = Date;
}
public Node (string head, string body, DateTime date)
{
Head = head;
Body = body;
Date = date.ToLongDateString ();
}
public Node (): this (“head text”, “body text”, DateTime.Now)
{} }
< div class = “Node” >
< asp: Label ID= “headLabel” runat= “server” ></asp: Label>
< asp: Label ID= “bodyLabel” runat= “server” ></asp: Label>
< asp: Label ID= “dateLabel” runat= “server” ></asp: Label>
</div>

Первый вариант лично для меня кажется предпочтительнее, т.к. для реализации нужно меньше кода.

Подписаться на комментарии