Not a long time ago I've encountered an XML which was created based on XSD. This XML is actually a class with the properties set in the XSD. But I needed only a 20% of the class' properties.
The 1st approach is using the "xsd.exe" found in the .NET framework to create a dll (or a source code file) of a class based on the XSD. I assume this thing actually works in regular cases, but it didn't work for me, in the case I had the XSD had a node referring to another XSD. "xsd.exe" theoretically can resolve that, but in practice a human can't use it.
The 2nd approach is using Biztalk, it has all the mechanisms for this type of action, it produces a good cs file. And it feels like using a Nuke on a fly, though an annoying fly.
The 3rd approach, the one I used, is just get the data from the XML while ignoring the name spaces:
private static System.Xml.XmlNode GetNodeByName(ref System.Xml.XmlNode nd, string NodeName)
{
int i = 0;
while (i < nd.ChildNodes.Count)
{
if (nd.ChildNodes[i].LocalName == NodeName)
{
return nd.ChildNodes[i];
}
i++;
}
return null;
}
This is the most basic function, but what do you do if you need to locate a path? Well just wrap it a bit and the result is:
private static System.Xml.XmlNode GetPath(ref System.Xml.XmlNode nd, string NodeName)
{
string cur_name = NodeName;
string path = "";
if (cur_name.IndexOf('/') > -1)
{
path = cur_name.Substring(cur_name.IndexOf('/') + 1);
cur_name = cur_name.Substring(0, cur_name.IndexOf('/') - 1);
}
int i = 0;
while (i < nd.ChildNodes.Count)
{
if (nd.ChildNodes[i].LocalName == NodeName)
{
if (path == "")
{
return nd.ChildNodes[i];
}
else
{
System.Xml.XmlNode tmp = nd.ChildNodes[i];
System.Xml.XmlNode res = GetPath(ref tmp, path);
if (res != null)
{
return res;
}
}
}
i++;
}
return null;
}
In the case I needed it for using the "path" approach was too expensive, I had to fetch 6 values from the same node with a 3 levels path. So I decided to fetch the node once for each level, and hitting the last level 6 times in a row for 6 different nodes.
If you have to use it a lot (i.e. it's not just get the 6 values and dispose it) you might consider rebuilding the XML as a namespace-free XML, the technique is similar, but requires more coding, and I don't really see the benefit I'll be making from it.
Tuesday, July 22, 2008
Sunday, July 6, 2008
Editing a generic table using ASP .NET datagrid
The reason I'm writing this article is very simple:
"I haven't found an existing solution to this damn problem!"
What I did find are bits and pieces of code in VB and C#, referring to the events of the datagrid.
The first thing to do is add the datagrid to the aspx. Note: you'll need to hook the events and change a few properties. I did it in this way:
<asp:GridView ID="Main_grd" runat="server" AllowPaging="true" AllowSorting="true" AutoGenerateDeleteButton="true" AutoGenerateEditButton="true" Width="100%" AutoGenerateSelectButton="false" OnRowDeleting="Main_grd_Deleting" OnPageIndexChanging="Main_grd_Paging" OnRowEditing="Main_grd_Editting" OnSorting="Main_grd_Sorting" OnRowCancelingEdit="Main_grd_CancelingEdit" OnRowUpdating="Main_grd_Updating"> </asp:GridView>
Now comes the "interesting" part, which events get the whole data, and which get only partial. Unfortunately the update is the most problematic, but it will wait for a while.
The easiest to implement is "Paging", the code behind is:
string sql = "SELECT * FROM " + Request["tablename"];
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.PageIndex = e.NewPageIndex;
Main_grd.DataBind();
The sorting is supposed to almost identical. I've implemented it manually, though the reasons to do so are unclear. The implementation uses 2 hidden fields, one to indicate the direction (Ascending / Descending) and the other to identify the field
string sql;
if ((LastFilter_hid.Value == e.SortExpression) && LastDirection_hid.Value == "DESC")
{
sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + e.SortExpression + " ASC";
LastDirection_hid.Value = "ASC";
}
else
{
sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + e.SortExpression + " DESC";
LastDirection_hid.Value = "DESC";
}
Main_grd.DataSource = GetDatasetFromQuery(sql);
LastFilter_hid.Value = e.SortExpression;
Main_grd.DataBind();
Next comes the editing
MakeWhereClause(e.NewEditIndex);
string sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + LastFilter_hid.Value + " " + LastDirection_hid.Value;
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.EditIndex = e.NewEditIndex;
Main_grd.DataBind();
Note the first line. In the update event there are fields for OldValues and NewValues, but in a generic case they don't update. Leaving a nice "Null Reference" exception for the poor programmer who needs them. In order to by pass it, I did the "where clause" on the edit event, put it in a hidden field, and used it on the update. I've also used the same "where" in the delete.
private string GetValue(System.Web.UI.WebControls.WebControl ctrl)
{
string res = "";
switch (ctrl.GetType().ToString())
{
case "System.Web.UI.WebControls.TextBox":
res = "'" + ((System.Web.UI.WebControls.TextBox)ctrl).Text + "'";
break;
case "System.Web.UI.WebControls.CheckBox":
if (((System.Web.UI.WebControls.CheckBox)ctrl).Checked)
{
res = "1";
}
else if (!((System.Web.UI.WebControls.CheckBox)ctrl).Checked)
{
res = "0";
}
break;
default:
res = "";
break;
}
if ((res == "") || (res == "''"))
{
res = "NULL";
}
return res;
}
private void MakeWhereClause(int RowNum)
{
System.Text.StringBuilder update_cmd = new System.Text.StringBuilder();
for (int i = 0; i < Main_grd.Rows[RowNum].Cells.Count; i++)
{
string FieldName = ((System.Web.UI.WebControls.DataControlFieldCell)(((System.Web.UI.WebControls.TableRow)(Main_grd.Rows[RowNum])).Cells[i])).ContainingField.SortExpression;
string txt = Main_grd.Rows[RowNum].Cells[i].Text;
if ((txt != null) && (txt != "") && (txt.ToLower() != " "))
{
update_cmd.Append(" AND " + FieldName + "='" + txt + "'");
}
}
if (update_cmd.ToString() != "")
{
Where_hid.Value = update_cmd.ToString().Substring(5);
}
else
{
Where_hid.Value ="";
}
}
The delete looks like
MakeWhereClause(e.RowIndex);
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("DELETE FROM " + Request["tablename"] + " WHERE " + Where_hid.Value, GetConnection());
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
AddErrorMessage(ex.Message);
}
string sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + LastFilter_hid.Value + " " + LastDirection_hid.Value;
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.EditIndex = -1;
Main_grd.DataBind();
And the update looks like
bool HasChanges = false;
System.Text.StringBuilder update_cmd = new System.Text.StringBuilder();
update_cmd.AppendLine("UPDATE " + Request["tablename"] + " ");
for (int i = 1; i < Main_grd.Rows[e.RowIndex].Cells.Count; i++)
{
string txt = GetValue((System.Web.UI.WebControls.WebControl)(Main_grd.Rows[e.RowIndex].Cells[i].Controls[0]));
string FieldName = ((System.Web.UI.WebControls.DataControlFieldCell)(((System.Web.UI.WebControls.TableRow)(Main_grd.Rows[e.RowIndex])).Cells[i])).ContainingField.SortExpression;
if (Where_hid.Value.IndexOf(FieldName + "=" + txt) < 0)
{
if (HasChanges)
{
update_cmd.Append(", " + FieldName + "=" + txt);
}
else
{
update_cmd.Append("SET " + FieldName + "=" + txt);
HasChanges = true;
}
}
}
if (HasChanges)
{
update_cmd.AppendLine(" WHERE (");
update_cmd.AppendLine(Where_hid.Value);
update_cmd.AppendLine(")");
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(update_cmd.ToString(),GetConnection());
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
AddErrorMessage(ex.Message);
}
}
string sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + LastFilter_hid.Value + " " + LastDirection_hid.Value;
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.EditIndex = -1;
Main_grd.DataBind();
Canceling the edit mode is simply by setting the edit-index to -1. Just remember to erase the "where" or you might get an unexpected result.
The code assumes that it's already initialized in some way, that's because I did the initialization in the page_load. One can implement it in whichever way he likes, just remember to do so.
I hope there aren't too many errors, I used M$ Word to write this, and even god gave up on understanding this bloody software.
"I haven't found an existing solution to this damn problem!"
What I did find are bits and pieces of code in VB and C#, referring to the events of the datagrid.
The first thing to do is add the datagrid to the aspx. Note: you'll need to hook the events and change a few properties. I did it in this way:
<asp:GridView ID="Main_grd" runat="server" AllowPaging="true" AllowSorting="true" AutoGenerateDeleteButton="true" AutoGenerateEditButton="true" Width="100%" AutoGenerateSelectButton="false" OnRowDeleting="Main_grd_Deleting" OnPageIndexChanging="Main_grd_Paging" OnRowEditing="Main_grd_Editting" OnSorting="Main_grd_Sorting" OnRowCancelingEdit="Main_grd_CancelingEdit" OnRowUpdating="Main_grd_Updating"> </asp:GridView>
Now comes the "interesting" part, which events get the whole data, and which get only partial. Unfortunately the update is the most problematic, but it will wait for a while.
The easiest to implement is "Paging", the code behind is:
string sql = "SELECT * FROM " + Request["tablename"];
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.PageIndex = e.NewPageIndex;
Main_grd.DataBind();
The sorting is supposed to almost identical. I've implemented it manually, though the reasons to do so are unclear. The implementation uses 2 hidden fields, one to indicate the direction (Ascending / Descending) and the other to identify the field
string sql;
if ((LastFilter_hid.Value == e.SortExpression) && LastDirection_hid.Value == "DESC")
{
sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + e.SortExpression + " ASC";
LastDirection_hid.Value = "ASC";
}
else
{
sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + e.SortExpression + " DESC";
LastDirection_hid.Value = "DESC";
}
Main_grd.DataSource = GetDatasetFromQuery(sql);
LastFilter_hid.Value = e.SortExpression;
Main_grd.DataBind();
Next comes the editing
MakeWhereClause(e.NewEditIndex);
string sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + LastFilter_hid.Value + " " + LastDirection_hid.Value;
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.EditIndex = e.NewEditIndex;
Main_grd.DataBind();
Note the first line. In the update event there are fields for OldValues and NewValues, but in a generic case they don't update. Leaving a nice "Null Reference" exception for the poor programmer who needs them. In order to by pass it, I did the "where clause" on the edit event, put it in a hidden field, and used it on the update. I've also used the same "where" in the delete.
private string GetValue(System.Web.UI.WebControls.WebControl ctrl)
{
string res = "";
switch (ctrl.GetType().ToString())
{
case "System.Web.UI.WebControls.TextBox":
res = "'" + ((System.Web.UI.WebControls.TextBox)ctrl).Text + "'";
break;
case "System.Web.UI.WebControls.CheckBox":
if (((System.Web.UI.WebControls.CheckBox)ctrl).Checked)
{
res = "1";
}
else if (!((System.Web.UI.WebControls.CheckBox)ctrl).Checked)
{
res = "0";
}
break;
default:
res = "";
break;
}
if ((res == "") || (res == "''"))
{
res = "NULL";
}
return res;
}
private void MakeWhereClause(int RowNum)
{
System.Text.StringBuilder update_cmd = new System.Text.StringBuilder();
for (int i = 0; i < Main_grd.Rows[RowNum].Cells.Count; i++)
{
string FieldName = ((System.Web.UI.WebControls.DataControlFieldCell)(((System.Web.UI.WebControls.TableRow)(Main_grd.Rows[RowNum])).Cells[i])).ContainingField.SortExpression;
string txt = Main_grd.Rows[RowNum].Cells[i].Text;
if ((txt != null) && (txt != "") && (txt.ToLower() != " "))
{
update_cmd.Append(" AND " + FieldName + "='" + txt + "'");
}
}
if (update_cmd.ToString() != "")
{
Where_hid.Value = update_cmd.ToString().Substring(5);
}
else
{
Where_hid.Value ="";
}
}
The delete looks like
MakeWhereClause(e.RowIndex);
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("DELETE FROM " + Request["tablename"] + " WHERE " + Where_hid.Value, GetConnection());
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
AddErrorMessage(ex.Message);
}
string sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + LastFilter_hid.Value + " " + LastDirection_hid.Value;
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.EditIndex = -1;
Main_grd.DataBind();
And the update looks like
bool HasChanges = false;
System.Text.StringBuilder update_cmd = new System.Text.StringBuilder();
update_cmd.AppendLine("UPDATE " + Request["tablename"] + " ");
for (int i = 1; i < Main_grd.Rows[e.RowIndex].Cells.Count; i++)
{
string txt = GetValue((System.Web.UI.WebControls.WebControl)(Main_grd.Rows[e.RowIndex].Cells[i].Controls[0]));
string FieldName = ((System.Web.UI.WebControls.DataControlFieldCell)(((System.Web.UI.WebControls.TableRow)(Main_grd.Rows[e.RowIndex])).Cells[i])).ContainingField.SortExpression;
if (Where_hid.Value.IndexOf(FieldName + "=" + txt) < 0)
{
if (HasChanges)
{
update_cmd.Append(", " + FieldName + "=" + txt);
}
else
{
update_cmd.Append("SET " + FieldName + "=" + txt);
HasChanges = true;
}
}
}
if (HasChanges)
{
update_cmd.AppendLine(" WHERE (");
update_cmd.AppendLine(Where_hid.Value);
update_cmd.AppendLine(")");
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(update_cmd.ToString(),GetConnection());
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
AddErrorMessage(ex.Message);
}
}
string sql = "SELECT * FROM " + Request["tablename"] + " ORDER BY " + LastFilter_hid.Value + " " + LastDirection_hid.Value;
Main_grd.DataSource = GetDatasetFromQuery(sql);
Main_grd.EditIndex = -1;
Main_grd.DataBind();
Canceling the edit mode is simply by setting the edit-index to -1. Just remember to erase the "where" or you might get an unexpected result.
The code assumes that it's already initialized in some way, that's because I did the initialization in the page_load. One can implement it in whichever way he likes, just remember to do so.
I hope there aren't too many errors, I used M$ Word to write this, and even god gave up on understanding this bloody software.
Subscribe to:
Posts (Atom)