We have bunch of Domain Entities which should be rendered to an html format, which shows their detail in a pop up window.
I would be glad to do something like this:
Product product = new Product(...);
product.ToHtml(); // or: HtmlRenderer.Render(Product);
but my main problem is how to do this stuff from behind.
I have 3 different answers:
1. Render By Code:
I can simply write my code for rendering the Html inside the ToHtml Method (C#) - the problem it is that it is too static. if you would like to move a little bit the header to the middle you should change code.
moreover, it is very hard to read Html indentation in C#.
2. Using XSL:
XSL Files can easily manage the Html template and using XSLT I can transform XML file to the right place of the documents.
the parser already written by someone else (just need to learn the syntax)
** for this we will need that each object could serialize to Xml. and if the object changed -> the Xml will be changed --> the xslt need to be changed too
** this will also give me the option to indent the html easily for example: adding css capabilities and\or change the html design
3. using other template engine:
Write my own C# -> Html template Engine so it will read the template from file (*.template) and will insert the right property in the right place of the template using reflection.
** in this solution we have many issues that we can think of, for example: how the syntax should be like?
is this thing ok?
%Name% %Description%
and how we can handle arrays?
** maybe we can use an existing engine (Brail or T4-Templating)?
What do you prefer?
do you know a good engine?
for now I prefer the second solution, but it gonna be very slow.
thanks
I once had a need to render a collection of any type to an Html Table. I created an extension method on IEnumerable<T> that had overloads for css and the like. You can see my blog post about it here:
http://crazorsharp.blogspot.com/2009/03/cool-ienumberable-extension-method_25.html
It uses reflection to get all the properties of the object, and renders a nice little table. See if that would work for you.
[System.Runtime.CompilerServices.Extension()]
public string ToHtmlTable<T>(IEnumerable<T> list, string propertiesToIncludeAsColumns = "")
{
return ToHtmlTable(list, string.Empty, string.Empty, string.Empty, string.Empty, propertiesToIncludeAsColumns);
}
[System.Runtime.CompilerServices.Extension()]
public string ToHtmlTable<T>(IEnumerable<T> list, string tableSyle, string headerStyle, string rowStyle, string alternateRowStyle, string propertiesToIncludeAsColumns = "")
{
dynamic result = new StringBuilder();
if (String.IsNullOrEmpty(tableSyle)) {
result.Append("<table id=\"" + typeof(T).Name + "Table\">");
} else {
result.Append((Convert.ToString("<table id=\"" + typeof(T).Name + "Table\" class=\"") + tableSyle) + "\">");
}
dynamic propertyArray = typeof(T).GetProperties();
foreach (object prop in propertyArray) {
if (string.IsNullOrEmpty(propertiesToIncludeAsColumns) || propertiesToIncludeAsColumns.Contains(prop.Name + ",")) {
if (String.IsNullOrEmpty(headerStyle)) {
result.AppendFormat("<th>{0}</th>", prop.Name);
} else {
result.AppendFormat("<th class=\"{0}\">{1}</th>", headerStyle, prop.Name);
}
}
}
for (int i = 0; i <= list.Count() - 1; i++) {
if (!String.IsNullOrEmpty(rowStyle) && !String.IsNullOrEmpty(alternateRowStyle)) {
result.AppendFormat("<tr class=\"{0}\">", i % 2 == 0 ? rowStyle : alternateRowStyle);
} else {
result.AppendFormat("<tr>");
}
foreach (object prop in propertyArray) {
if (string.IsNullOrEmpty(propertiesToIncludeAsColumns) || propertiesToIncludeAsColumns.Contains(prop.Name + ",")) {
object value = prop.GetValue(list.ElementAt(i), null);
result.AppendFormat("<td>{0}</td>", value ?? String.Empty);
}
}
result.AppendLine("</tr>");
}
result.Append("</table>");
return result.ToString();
}
I can't agree more with John Feminella. Integrating Html rendering directly into your domain entities is a horrid pollution of your domain with a completely arbitrary and external concern. Like John said, you will make your entities very brittle by doing that, and break both of those critical rules: Separation of Concerns and Single Responsibility.
From the choices you gave, #3 is the closest to an appropriate way to do it. You need not write your own template engine. There are plenty free and ready-made template engines on the net that will do the job more than adequately (NVelocity, StringTemplate, Brail, etc. etc.)
Keep rendering where it belongs...in your presentation/view, and don't pollute your domain with higher level concerns.
This is the job of your view, not your controllers or models. As you suspect, using method (1) will make your changes very brittle. Instead, write a view fragment for each way in which you'd like to render a particular domain entity. Then simply pull in the appropriate views to build your final page.
I sincerely prefer method A. The HTML is already a standard so you shouldn't beat yourself up too much with intermediary formats such as XML (via XSL - method 2) or a custom format (method 3).
If writing in/for ASP.NET MVC you could however build an .ASCX (user control) that would accept an object of your type and render the appropiate HTML. And you don't get the nasty C# indentation and no highlighting nor autocomplete.
You could also write multiple User views to accomodate different scenarios for one object type.
Is there a reason you do not want to create a custom control .ascx file that takes domain entities object as an argument and then you can make the gui any way you want. This way you could dynamically add the controls to the page and just set a property to populate them.
Edit 1:
Just render the control to an HTMLTextWriter instead of placing it on the page.
StringBuilder outString = new StringBuilder();
StringWriter outWriter = new StringWriter(outString);
HtmlTextWriter htmlText = new HtmlTextWriter(outWriter);
System.Web.UI.WebControls.Label lbl1 = new System.Web.UI.WebControls.Label();
lbl1.Text = "This is just a test!";
lbl1.ToolTip = "Really";
lbl1.RenderControl(htmlText);
ViewData["Output"] = outString.ToString();
Output
<span title="Really">This is just a test!</span>
Of course use your custom control instead of a built in control but this was a quick and easy demo.
After bit of research i made my own function to convert Any Object toHtmlString.
VB .Net Code:
Public Function ToHtmlString(ByVal fObj As Object) As String
Dim pType = fObj.GetType()
Dim props As IList(Of PropertyInfo) = pType.GetProperties()
Dim value As Object = Nothing
Dim htmlString As String = "<html><body><form>"
For Each p In props
value = p.GetValue(fObj, Nothing)
If value <> Nothing Then
htmlString += String.Format("<input type=""hidden"" name=""{0}"" id=""{0}"" value=""{1}""/>", p.Name, value)
End If
Next
htmlString += "</form></body></html>"
Return htmlString.ToString()
it will get the property name and value from a particular object. you can manipulate html based on your
Personally, I'd combine method two and three: Just create a generic XML document using reflection, say:
<object type="xxxx">
<property name="ttt" value="vvv"/>
...
</object>
and use an XSTL stylesheet to create the actual HTML from this.
Related
I am trying to loop through a Dataset, creating a page per item using Aspose.Words Mail-Merge functionality. The below code is looping through a Dataset - and passing some values to the Mail-Merge Execute function.
var blankDocument = new Document();
var pageDocument = new Document(sFilename);
...
foreach (DataRow row in ds.Tables[0].Rows){
var sBarCode = row["BarCode"].ToString();
var imageFilePath = HttpContext.Current.Server.MapPath("\\_temp\\") + sBarCode + ".png";
var tempDoc = (Document)pageDocument.Clone(true);
var fieldNames = new string[] { "Test", "Barcode" };
var fieldData = new object[] { imageFilePath, imageFilePath };
tempDoc.MailMerge.Execute(fieldNames, fieldData);
blankDocument.AppendDocument(tempDoc, ImportFormatMode.KeepSourceFormatting);
}
var stream = new MemoryStream();
blankDocument.Save(stream, SaveFormat.Docx);
// I then output this stream using headers,
// to cause the browser to download the document.
The mail merge item { MERGEFIELD Test } gets the correct data from the Dataset. However the actual image displays page 1's image on all pages using:
{ INCLUDEPICTURE "{MERGEFIELD Barcode }" \* MERGEFORMAT \d }
Say this is my data for the "Barcode" field:
c:\img1.png
c:\img2.png
c:\img3.png
Page one of this document, displays c:\img1.png in text for the "Test" field. And the image that is show, is img1.png.
However Page 2 shows c:\img2.png as the text, but displays img1.png as the actual image.
Does anyone have any insight on this?
Edit: It seems as this is more of a Word issue. When I toggle between Alt+F9 modes inside Word, the image actually displays c:\img1.png as the source. So that would be why it is being displayed on every page.
I've simplified it to:
{ INCLUDEPICTURE "{MERGEFIELD Barcode }" \d }
Also, added test data for this field inside Word's Mailings Recipient List. When I preview, it doesn't pull in the data, changing the image. So, this is the root problem.
I know this is old question. But still I would like to answer it.
Using Aspose.Words it is very easy to insert images upon executing mail merge. To achieve this you should simply use mergefield with a special name, like Image:MyImageFieldName.
https://docs.aspose.com/words/net/insert-checkboxes-html-or-images-during-mail-merge/#how-to-insert-images-from-a-database
Also, it is not required to loop through rows in your dataset and execute mail merge for each row. Simply pass whole data into MailMerge.Execute method and Aspose.Words will duplicate template for each record in the data.
Here is a simple example of such template
After executing mail merge using the following code:
// Create dummy data.
DataTable dt = new DataTable();
dt.Columns.Add("FirstName");
dt.Columns.Add("LastName");
dt.Columns.Add("MyImage");
dt.Rows.Add("John", "Smith", #"C:\Temp\1.png");
dt.Rows.Add("Jane", "Smith", #"C:\Temp\2.png");
// Open template, execute mail merge and save the result.
Document doc = new Document(#"C:\Temp\in.docx");
doc.MailMerge.Execute(dt);
doc.Save(#"C:\Temp\out.docx");
The result will look like the following:
Disclosure: I work at Aspose.Words team.
If this was Word doing the output, (not sure about Aspose), there would be two possible problems here.
INCLUDEPICTURE expects backslashes to be doubled up, e.g. "c\\img2.png", or (somewhat less reliable) to use forward slashes, or Mac ":" separators on that platform. It may be OK if the data comes in via a field result as you are doing here, though.
INCLUDEPICTURE results have not updated automatically "by design" since Microsoft modified a bunch of field behaviors for security reasons about 10 years ago. If you are merging to an output document, you can probably work around that by using the following nested fields:
{ INCLUDEPICTURE { IF TRUE "{ MERGEFIELD Barcode }" } }
or to remove the fields in the result document,
{ IF { INCLUDEPICTURE { IF TRUE "{ MERGEFIELD Barcode }" } } {
INCLUDEPICTURE { IF TRUE "{ MERGEFIELD Barcode }" } } }
All the { } need to be inserted with Ctrl+F9 in the usual way.
(Don't ask me where this use of "TRUE" is documented - as far as I know, it is not.)
suppose i need to send mail to customer with customer detail and his order detail.
i have template html data in a html file.customer data is there and as well as order detail is also there in same html template file. my html look like
<html>
<body>
Hi {FirstName} {LastName},
Here are your orders:
{foreach Orders}
Order ID {OrderID} Quantity : {Qty} <strong>{Price}</strong>.
{end}
</body>
</html>
now i want to fill up all sample keyword surrounded with {} with actual value and also iterate and fill up orders.
i search google and found that microsoft provide a class called MailDefinition
by which we can generate mail body dynamically. i got a sample code also like
MailDefinition md = new MailDefinition();
md.From = "test#domain.com";
md.IsBodyHtml = true;
md.Subject = "Test of MailDefinition";
ListDictionary replacements = new ListDictionary();
replacements.Add("<%Name%>", "Martin");
replacements.Add("<%Country%>", "Denmark");
string body = "
Hello <%Name%> You're from <%Country%>.";
MailMessage msg = md.CreateMailMessage("you#anywhere.com", replacements, body, new System.Web.UI.Control());
by the above code we can replace pseudo value with actual value but i don't know how iterate in Orders detail and populate orders data.
so if it is possible using MailDefinition class then please guide me with code that how can i iterate in loop and generate body for orders detail.
As an alternative to MailDefinition, have a look at RazorEngine https://github.com/Antaris/RazorEngine.
RazorEngine is a simplified templating framework built around
Microsoft's new Razor parsing engine, used in both ASP.NET MVC3 and
Web Pages. RazorEngine provides a wrapper and additional services
built around the parsing engine to allow the parsing technology to
be used in other project types.
It lets you use razor templates outside of ASP.NET MVC and then write something like this (not tested):
string template =
#"<html>
<body>
Hi #Model.FirstName #Model.LastName,
Here are your orders:
#foreach(var order in Model.Orders) {
Order ID #order.Id Quantity : #order.Qty <strong>#order.Price</strong>.
}
</body>
</html>";
var model = new OrderModel {
FirstName = "Martin",
LastName = "Whatever",
Orders = new [] {
new Order { Id = 1, Qty = 5, Price = 29.99 },
new Order { Id = 2, Qty = 1, Price = 9.99 }
}
};
string mailBody = Razor.Parse(template, model);
You can't do such "complicated" logic with the default replacement stuff (the placeholder handling is made to be used for simple variables only, e.g. names or values).
You'll have to do the parsing yourself. Depending on the complexity (e.g. loops withing loops), this can get a bit tricky.
If you don't want or need such things, it's more trivial. E.g. use the regular expression \{foreach (.*?)\}(.*?)\{end\} to find such loops, then parse the contents/matched groups the way you need. Once that part is done, you could replace other values or use the default replacement feature.
Only downside with this approach is the fact that you'll have to recreate the mail for each recipient (i.e. you can't mass mail using MailDefinition).
First off, I'm not terribly experienced in XML. I know the very basics of reading in and writing it, but for the most part, things like schemas start to make my eyes cross really quickly. If it looks like I'm making incorrect assumptions about how XML works, there's a good chance that I am.
That disclaimer aside, this is a problem I've run into several times without finding an agreeable solution. I have an XML which defines data, including nested entries (to give an example, a file might have a "Power" element which has a child node of "AlternatePowers" which in turn contains "Power" elements). Ideally, I would like to be able to generate a quick set of classes from this XML file to store the data I'm reading in. The general solution I've seen is to use Microsoft's XSD.exe tool to generate an XSD file from the XML file and then use the same tool to convert the schema into classes. The catch is, the tool chokes if there are nested elements. Example:
- A column named 'Power' already belongs to this DataTable: cannot set
a nested table name to the same name.
Is there a nice simple way to do this? I did a couple of searches for similar questions here, but the only questions I found dealing with generating schemas with nested elements with the same name were unanswered.
Alternately, it's also possible that I am completely misunderstanding how XML and XSD work and it's not possible to have such nesting...
Update
As an example, one of the things I'd like to parse is the XML output of a particular character builder program. Fair warning, this is a bit wordy despite me removing anything but the powers section.
<?xml version="1.0" encoding="ISO-8859-1"?>
<document>
<product name="Hero Lab" url="http://www.wolflair.com" versionmajor="3" versionminor="7" versionpatch=" " versionbuild="256">Hero Lab® and the Hero Lab logo are Registered Trademarks of LWD Technology, Inc. Free download at http://www.wolflair.com
Mutants & Masterminds, Second Edition is ©2005-2011 Green Ronin Publishing, LLC. All rights reserved.</product>
<hero active="yes" name="Pretty Deadly" playername="">
<size name="Medium"/>
<powers>
<power name="Enhanced Trait 16" info="" ranks="16" cost="16" range="" displaylevel="0" summary="Traits: Constitution +6 (18, +4), Dexterity +8 (20, +5), Charisma +2 (12, +1)" active="yes">
<powerdesc>You have an enhancement to a non-effect trait, such as an ability (including saving throws) or skill (including attack or defense bonus). Since Toughness save cannot be increased on its own,use the Protection effect instead of Enhanced Toughness (see Protection later in this chapter).</powerdesc>
<descriptors/>
<elements/>
<options/>
<traitmods>
<traitmod name="Constitution" bonus="+6"/>
<traitmod name="Dexterity" bonus="+8"/>
<traitmod name="Charisma" bonus="+2"/>
</traitmods>
<flaws/>
<powerfeats/>
<powerdrawbacks/>
<usernotes/>
<alternatepowers/>
<chainedpowers/>
<otherpowers/>
</power>
<power name="Sailor Suit (Device 2)" info="" ranks="2" cost="8" range="" displaylevel="0" summary="Hard to lose" active="yes">
<powerdesc>A device that has one or more powers and can be equipped and un-equipped.</powerdesc>
<descriptors/>
<elements/>
<options/>
<traitmods/>
<flaws/>
<powerfeats/>
<powerdrawbacks/>
<usernotes/>
<alternatepowers/>
<chainedpowers/>
<otherpowers>
<power name="Protection 6" info="+6 Toughness" ranks="6" cost="10" range="" displaylevel="1" summary="+6 Toughness; Impervious [4 ranks only]" active="yes">
<powerdesc>You're particularly resistant to harm. You gain a bonus on your Toughness saving throws equal to your Protection rank.</powerdesc>
<descriptors/>
<elements/>
<options/>
<traitmods/>
<extras>
<extra name="Impervious" info="" partialranks="2">Your Protection stops some damage completely. If an attack has a damage bonus less than your Protection rank, it inflicts no damage (you automatically succeed on your Toughness saving throw). Penetrating damage (see page 112) ignores this modifier; you must save against it normally.</extra>
</extras>
<flaws/>
<powerfeats/>
<powerdrawbacks/>
<usernotes/>
<alternatepowers/>
<chainedpowers/>
<otherpowers/>
</power>
</otherpowers>
</power>
</powers>
</hero>
</document>
Yes, there are a number of unnecessary tags in there, but it's an example of the kind of XML that I'd like to be able to plug in and get something reasonable. This XML, when sent into XSD, generates the following error:
- A column named 'traitmods' already belongs to this DataTable: cannot set
a nested table name to the same name.
I just finished helping someone with that. Try reading this thread here: https://stackoverflow.com/a/8840309/353147
Taking from your example and my link, you'd have classes like this.
public class Power
{
XElement self;
public Power(XElement power) { self = power; }
public AlternatePowers AlternatePowers
{ get { return new AlternatePowers(self.Element("AlternatePowers")); } }
}
public class AlternatePowers
{
XElement self;
public AlternatePowers(XElement power) { self = power; }
public Power2[] Powers
{
get
{
return self.Elements("Power").Select(e => new Power2(e)).ToArray();
}
}
}
public class Power2
{
XElement self;
public Power2(XElement power) { self = power; }
}
Without knowing the rest of your xml, I cannot make the properties that make up each class/node level, but you should get the gist from here and from the link.
You'd then reference it like this:
Power power = new Power(XElement.Load("file"));
foreach(Power2 power2 in power.AlternatePowers.Powers)
{
...
}
Your error message implies that you are trying to generate a DataSet from the schema (/d switch), as opposed to a set of arbitrary classes decorated with XML Serializer attributes (/c switch).
I've not tried generating a DataSet like that myself, but I can see how it might fail. A DataSet is a collection of DataTables, which in turn contain a collection of DataRows. That's a fixed 3-level hierarchy. If your XML schema is more or less than 3 levels deep, then it won't fit into the required structure. Try creating a test DataSet in the designer and examine the generated .xsd file; that will show you what kind of schema structure will fit.
I can assure you from personal experience, if you convert the schema to a set of arbitrary classes instead, then it will handle pretty much any schema structure that you care to throw at it.
So, it's not pretty, but the following is what I wound up with as a solution. I run processElement on the base node and then I go through extantElements and export the class code.
namespace XMLToClasses
{
public class Element
{
public string Name;
public HashSet<string> attributes;
public HashSet<string> children;
public bool hasText;
public Element()
{
Name = "";
attributes = new HashSet<string>();
children = new HashSet<string>();
hasText = false;
}
public string getSource()
{
StringBuilder sourceSB = new StringBuilder();
sourceSB.AppendLine("[Serializable()]");
sourceSB.AppendLine("public class cls_" + Name);
sourceSB.AppendLine("{");
sourceSB.AppendLine("\t// Attributes" );
if (hasText)
{
sourceSB.AppendLine("\tstring InnerText;");
}
foreach(string attribute in attributes)
{
sourceSB.AppendLine("\tpublic string atr_" + attribute + ";");
}
sourceSB.AppendLine("");
sourceSB.AppendLine("\t// Children");
foreach (string child in children)
{
sourceSB.AppendLine("\tpublic List<cls_" + child + "> list" + child + ";");
}
sourceSB.AppendLine("");
sourceSB.AppendLine("\t// Constructor");
sourceSB.AppendLine("\tpublic cls_" + Name + "()");
sourceSB.AppendLine("\t{");
foreach (string child in children)
{
sourceSB.AppendLine("\t\tlist" + child + " = new List<cls_" + child + ">()" + ";");
}
sourceSB.AppendLine("\t}");
sourceSB.AppendLine("");
sourceSB.AppendLine("\tpublic cls_" + Name + "(XmlNode xmlNode) : this ()");
sourceSB.AppendLine("\t{");
if (hasText)
{
sourceSB.AppendLine("\t\t\tInnerText = xmlNode.InnerText;");
sourceSB.AppendLine("");
}
foreach (string attribute in attributes)
{
sourceSB.AppendLine("\t\tif (xmlNode.Attributes[\"" + attribute + "\"] != null)");
sourceSB.AppendLine("\t\t{");
sourceSB.AppendLine("\t\t\tatr_" + attribute + " = xmlNode.Attributes[\"" + attribute + "\"].Value;");
sourceSB.AppendLine("\t\t}");
}
sourceSB.AppendLine("");
foreach (string child in children)
{
sourceSB.AppendLine("\t\tforeach (XmlNode childNode in xmlNode.SelectNodes(\"./" + child + "\"))");
sourceSB.AppendLine("\t\t{");
sourceSB.AppendLine("\t\t\tlist" + child + ".Add(new cls_" + child + "(childNode));");
sourceSB.AppendLine("\t\t}");
}
sourceSB.AppendLine("\t}");
sourceSB.Append("}");
return sourceSB.ToString();
}
}
public class XMLToClasses
{
public Hashtable extantElements;
public XMLToClasses()
{
extantElements = new Hashtable();
}
public Element processElement(XmlNode xmlNode)
{
Element element;
if (extantElements.Contains(xmlNode.Name))
{
element = (Element)extantElements[xmlNode.Name];
}
else
{
element = new Element();
element.Name = xmlNode.Name;
extantElements.Add(element.Name, element);
}
if (xmlNode.Attributes != null)
{
foreach (XmlAttribute attribute in xmlNode.Attributes)
{
if (!element.attributes.Contains(attribute.Name))
{
element.attributes.Add(attribute.Name);
}
}
}
if (xmlNode.ChildNodes != null)
{
foreach (XmlNode node in xmlNode.ChildNodes)
{
if (node.Name == "#text")
{
element.hasText = true;
}
else
{
Element childNode = processElement(node);
if (!element.children.Contains(childNode.Name))
{
element.children.Add(childNode.Name);
}
}
}
}
return element;
}
}
}
I'm sure there's ways to make this look more pretty or work better, but it's sufficient for me.
Edit: And ugly but functional deserialization code added to take an XMLNode containing the object and decode it.
Later Thoughts: Two years later, I had an opportunity to re-use this code. Not only have I not kept it up to date here (I'd made changes to better normalize the names of the items), but I think that the commenters saying that I was going about this the wrong way were right. I still think this could be a handy way of generating template classes for an XML file where a given type of element could show up at different depths, but it's inflexible (you have to rerun the code and re-extract the classes every time) and doesn't nicely handle changes in versioning (between when I first created this code to allow me to quickly create a character file converter and now, the format changed, so I had people complaining that it stopped working. In retrospect, it would have made more sense to search for the correct elements using XPaths and then pull the data from there).
Still, it was a valuable experience, and I suspect I'm probably going to come back to this code from time to time for quickly roughing out XML data, at least until I find something better.
I am trying to create a dynamic table that is being built based on the value of a search text box. The Table will have probably 6 columns of 20(max) rows.
My problem is I can't figure out how to return the data from the web-service to the JS function in a way that will allow me to extract the data that I need.
Here is the web-service method I have currently:
[WebMethod]
public v_EMU_AIR_AIR[] GetCompletionList(string prefixText)
{
NeoDataContext dataContext = new NeoDataContext();
var results = from a in dataContext.GetTable<v_EMU_AIR_AIR>()
where a.Report_Date_ID.StartsWith(prefixText) ||
a.Item_Description.Contains(prefixText) ||
a.Drw_Nbr.StartsWith(prefixText)
select a;
return results.ToArray<v_EMU_AIR_AIR>();
}
This will return an Array my objects, but in my javascript:
populateTable:function(returnList) {
var length = returnList.childNodes[0].childNodes.length
for(var i=0; i<length; i++) {
var textValue = returnList.childNodes[0].childNodes[i].textContent
$("<tr><td>" + textValue + "</td></tr>").insertAfter(this.tblResults[0].childNodes[1]);
}
}
All I can produce is a dump of the entire Object and can't pull out the specific fields that I need.
I would serialize your object using Javascript Object Notation (JSON).
Unfortunately, this feature isn't built in to the .NET framework, so you'll need to write the code yourself.
Fortunately, most of it is already done for you. Check out this link or this link for more information.
In your javascript, you can retreive the JSON object with jquery (documentation) and access the values of your properties like you would in C#
$(document).ready(function() {
$.getJSON("http://url.to.webservice.com/", function(jsonObj) {
alert(jsonObj.property);
});
});
Recently, my question here was answered. Now that I've got my XML all parsed and looking pretty, I've got another question about my application I've been banging my head against a wall over the past few day(s).
The XML is used to automatically add Artist names to a listbox. What I want to do is provide links to Amazon searches from these artists. In the following function, the XML is parsed and the artist name is then added to the list. I need to somehow put a hyperlink on this artist name. Does anybody know how this would be possible?
EDIT: I am missing the connection between steps 2 and 3 in the answer that has been provided. Also, I do not understand how number 3 works at all. I must admit I'm a neophyte at Silverlight programming. From my understanding, you do the binding in the XAML page. How can this be done for listbox items that have not even been created yet?
Additionally, I realized something that the Amazon URLs use + signs where spaces are in artist names. I edited the code to reflect that. Please understand that having the hyperlink as text under each artist name is not what I'm going after. ;)
public void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null || e.Error.Message.IndexOf("NotFound") == -1)
{
ArtistsList.Items.Clear();
uname.Text = "Try Another One!";
XDocument doc = XDocument.Parse(e.Result);
var topArtists = from results in doc.Descendants("artist")
select results.Element("name").Value.ToString();
foreach (string artist in topArtists)
{
ArtistsList.Items.Add(artist);
string amazonPlus = artist.Replace(" ", "+");
string amazonURL = "http://www.amazon.ca/s/ref=nb_ss_gw?url=search-alias%3Daps&field-keywords=" + amazonPlus + "&x=0&y=0";
ArtistsList.Items.Add(amazonURL);
}
}
}
EDIT 2 Is there anybody who can clarify the answer provided?
1) Create an Artist object with a Name and Amazon Url Property
2) When you parse the XML, create a collection of items using LINQ.
var topArtists = from result in doc.Descendants("artists")
select new Artist
{
Name = result.Element("name").Value,
Amazon = new Uri(string.format("http://amazon.com/artist={0}", result.Element("name").Value), UriKind.Absolute),
};
ArtistList.ItemsSource = topArtists;
3) I would then use a data template to bind the Name to a TextBlock
Text or HyperlinkButton Content and the Amazon property to the
HyperlinkButton.NavigateUrl.