Son birkaç yılımı popüler bir Java temelli Ajax kullanıcı arayüzü kütüphanesi olan ZK(www.zkoss.org) kullanarak geçirdim. Bu kütüphanede temel bakış açısı sıfır kod(ZK buradan geliyor, alman bir ekip geliştirdiği için ZC değil ZK denmiş olabilir.). Son altı aydır her ne kadar .NET konforuna kavuşmuş olsam da ZK’dan kalma birkaç alışkanlığımın .NET’te karşılığını bulamamak bazen beni zorlamıyor değil. İşte bunlardan biri de Custom Control kullanımı. Olayın gerisini makaleden takip edebilirsiniz.
Problem
Konunun üç beş ay önce ASP.NET forumlarındaki tartışmalarını http://forums.asp.net/t/1252896.aspx adresinden takip edebilirsiniz. Problem şu ki, bir Tree nesnesinde sadece metin ve değerden ibaret iki sütun değil de, bunların dışında başka bilgileri de bulunan bir object(Entity veya Java kodlayanlar için POJO-Plain Old Java Object) tutup, veritabanı etkileşimini azaltmak istiyorum.
Bu amaca yönelik en basit örnek olarak halihazırdaki ASP.NET TreeNode bileşenini şu şekilde genişletiyorum:
public class myTreeNode : TreeNode
{
public int nodeType;
public myTreeNode(string text, string value, int nodeType)
: base(text, value) {
this.nodeType=nodeType;
}
}
Oldukça masum bu yeni TreeNode, orijinal kontrolen farklı olarak int türden bir field ve bu field’e bir atamadan ibaret.
Ve arkasından şöyle bir ASP.NET sayfasında kullanmak istiyorum:
Default.aspx
===================================================================
< %@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_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>Untitled Page
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TreeView ID="TreeView1" runat="server">
</asp:TreeView>
</div>
<asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />
</form>
</body>
</html>
Default.aspx.cs
===================================================================
using System;
using System.Web;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
TreeView1.Nodes.Add(new myTreeNode("node 1", "A",99));
Button1.Text = (TreeView1.Nodes[0] as myTreeNode).nodeType.ToString();
// Buton üstünde düğümün tipini görelim.
}
}
protected void Button1_Click(object sender, EventArgs e)
{
Button1.Text = (TreeView1.Nodes[0] as myTreeNode ).nodeType.ToString() ;
}
}
Değiştirilmiş TreeNode’dan yeni bir tane nüsha oluşturup değer atarken sorun çıkmamasına rağmen, postback işleminden sonra, atadığım değeri geri okuyamadığı için şu şekilde bir hata fırlatıyordu:
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the
current web request. Please review the stack trace for more
information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 14: protected void Button1_Click(object sender, EventArgs e)
Line 15: {
Line 16: Button1.Text = (TreeView1.Nodes[0] as myTreeNode).nodeType.ToString();
Line 17: }
Line 18: }
Source File: c:\Inetpub\wwwroot\test\Default.aspx.cs Line: 16
2.ViewState Hakkında Kısa Bilgi
ViewState, her bir page nesnesi ile birlikte, kontrollerin spesifik değerlerini page nesnesinin postback işlemlerinden sonra da hatırlamasını sağlayan bir veri saklama yöntemi ortamı olup, her bir sayfa üretiminde istemciye kodlanmış olarak gönderilir ve her postback işleminde geri açılarak kontrollerin eski değerleri elde edilir.
Konu hakkında ayrıntılı Türkçe bilgi http://www.csharpnedir.com/makalegoster.asp?MId=776 adresinde mevcut.
3.Çözüm
İlk bakışta göze problemsiz gibi gelen bu değiştirilmiş TreeNode bileşeninde göz ardı edilen nokta, kontrol durumunun viewstate’e kaydedilmemesi… Orijinal bir TreeNode kontrolünde kontrol tarafından taşınan bilgilerin ViewState’e kaydedilmesi ve geri okunması şu şekilde kodlanmış:
public class TreeNode
{
private string NodeText = string.Empty;
private string NodeURL = string.Empty;
protected override object SaveViewState()
{
object[] arrState = new object[4];
arrState[0] = base.SaveViewState();
arrState[1] = this.NodeText;
arrState[2] = this.NodeURL;
return arrState;
}
protected override void LoadViewState(object savedState)
{
if (savedState != null)
{
object[] arrState = savedState as object[];
this.NodeText = (string)arrState[1];
this.NodeURL = (string)arrState[2];
base.LoadViewState(arrState[0]);
}
}
Benzer şekilde kendi kontrolümüze nodeType için eklemeleri yaparsak
public class myTreeNode : TreeNode
{
public int nodeType { get; set; }
public myTreeNode(): base() { nodeType = ""; }
protected override object SaveViewState()
{
object[] arrState = new object[2];
//eklediğimiz fazladan property kadar dizi tanımlıyoruz.
arrState[0] = base.SaveViewState();
// sıfırıncı elemana orijinal kontrolün property dizisini kaydediyoruz.
arrState[1] = (int)this.nodeType;
// geriye kalan elemana da ek olarak tanımladığımız property’i kaydediyoruz.
return arrState;
}
protected override void LoadViewState(object state)
{
if (state != null)
{
object[] arrState = (object[])state;
this.nodeType = int.Parse(arrState[1].ToString());
base.LoadViewState(arrState[0]);
}
}
}
Yeni kontrol düzgün bir şekilde kullanılabilecektir.
Sonuç
ASP.NET Viewstate modeli kullandığı için kontrolleri değiştirirken, eklenen özelliklerin postback işleminden sonra da hatırlanması gerekiyorsa ViewState yöntemi kullanarak bunu yazacak ve okuyacak yöntemleri belirtmek gerekir. göz ardı edilmemesi gereken başka bir nokta; çok fazla özellikleri viewstate’e eklenmiş bir kontrol performans açısından sorun çıkartabilir.