Reputation: 530
I have a HTML row (<tr>
) generated on client side, I want to convert the string which contains the row-cells information in a HtmlTableRow
control. That is what I have done so far using the example on Convert string to WebControls - asp.net. Thanks
string row = "<tr><td>item</td><td><input name=\"radio0\" type=\"radio\"/></td></tr>";
Dictionary<string, HtmlContainerControl> controlConstructor = new Dictionary<string, HtmlContainerControl>
{
{"tr", new HtmlTableRow()},
{"td", new HtmlTableCell()}
};
var htmlDoc = XElement.Parse(row);
Func<XElement, HtmlControl> constructHtmlStructure = null;
constructHtmlStructure = (o =>
{
var control = controlConstructor[o.Name.ToString()];
if (o.HasElements)
{
control.Controls.Add(constructHtmlStructure(o.Elements().Single())); //Exception: Sequence contains more than one element (When is a input item)
}
else
{
control.InnerText = o.Value;
}
return control;
});
HtmlTableRow structure = (HtmlTableRow)constructHtmlStructure(htmlDoc);
Upvotes: 2
Views: 2438
Reputation: 7135
Linq's Single
method returns the first and only element of a sequence - if there's more than one element it throws the exception you're seeing. As your table row contains two table cells, its o.Elements()
method returns an enumerable with two members, and using Single
makes it fall over.
Instead of using:
control.Controls.Add(constructHtmlStructure(o.Elements().Single()));
...use:
foreach(var element in o.Elements())
{
control.Controls.Add(constructHtmlStructure(element));
}
That way you iterate over each of the table row's cells, and add each one to the control.
Edit
There's another change you need to make. Your dictionary contains exactly one HtmlTableRow
, and exactly one HtmlTableRow
, both already created by the time you get into constructHtmlStructure
. Instead of having pre-existing objects (and therefore only one instance of each available) you should have your dictionary create a new instance of each type on demand.
So instead of this (adjusted for spacing):
var controlConstructor = new Dictionary<string, HtmlContainerControl>
{
{ "tr", new HtmlTableRow() },
{ "td", new HtmlTableCell() }
};
...
var control = controlConstructor[o.Name.ToString()];
...try this:
var controlConstructor = new Dictionary<string, Func<XElement, HtmlControl>>
{
{ "tr", o => new HtmlTableRow() },
{ "td", o => new HtmlTableCell() }
};
...
var control = controlConstructor[o.Name.ToString()].Invoke(o);
You'll also need a function to create an input from an XElement
, something like this:
private static HtmlControl CreateInputFromElement(XElement element)
{
// Create an appropriate HtmlInputControl...
}
...which you can add to your dictionary like this:
var controlConstructor = new Dictionary<string, Func<XElement, HtmlControl>>
{
{ "tr", o => new HtmlTableRow() },
{ "td", o => new HtmlTableCell() },
{ "input", CreateInputFromElement }
};
Upvotes: 0
Reputation: 12375
why don't you use much simpler ways to parse your string to HtmlTableRow.
your Dictionary<string, HtmlContainerControl> controlConstructor
considers only tr
and td
, what about input control nested inside them?
so even if you get away through that "sequence contains more than one element
", using a foreach loop, you will get an error "key doesn't exist
".
and even if you manage overcome that(by adding input key in your dictionary), you cannot parse it to a HtmlContainerControl
.
and even if you do, by updating your Dictionary<string, HtmlContainerControl>
to Dictionary<string, HtmlControl>
, you will have to think of ways to handle that input control because, for that you cannot do control.InnerText = o.value;
hence a much simpler way:
string row = "<tr><td>item</td><td><input name=\"radio0\" type=\"radio\"/></td></tr>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(row);
HtmlTableRow tblRow = new HtmlTableRow();
foreach (XmlNode node in doc.SelectSingleNode("tr").ChildNodes)
{
HtmlTableCell cell = new HtmlTableCell();
cell.InnerText = node.InnerXml;
foreach (XmlNode childNode in node.ChildNodes)
{
if (childNode.Name == "input")
{
if (childNode.Attributes["type"] != null)
{
switch (childNode.Attributes["type"].Value.ToString())
{
case "radio":
HtmlInputRadioButton rad = new HtmlInputRadioButton();
rad.Name = childNode.Attributes["name"].ToString();
cell.Controls.Add(rad);
break;
///other types of input controls
default:
break;
}
}
else
{
HtmlInputButton button = new HtmlInputButton("button");
cell.Controls.Add(button);
}
}
}
tblRow.Cells.Add(cell);
}
as you can see, it is a very rough and strict logic: what best you can do is to come up with a recursive function, to construct your HtmlTableRow
Upvotes: 1