looping through elasticsearch queryresult using Mpdreamz/NEST - c#

Iam using Mpdreamz/NEST as .net client for searching elasticsearch.
I am getting the result count of search result using result.Hits.Total. But I am wondering how I can display the value of a field from the result.

var result = client.Search(....)
Will return a a QueryResult<dynamic> with a Documents property of type IEnumerable<dynamic> You can loop over this like so:
foreach(var d in result.Documents)
{
Console.WriteLine(d.title);
}
The casing matters here d.Title will result in a RuntimeBinderException. Unless your elasticsearch field actually exactly matches 'Title`
You can also map to POCO's
public class MyResult
{
public string Title { get; set; }
}
...
var result = client.Search<MyResult>(....)
Now Documents is a IEnumerable<MyResult> which will give you compile time guarantees the property exists.
You should not have to access .Fields["field"] unless you are dealing with user input.
result.Total is also preferred over result.Hits.Total

Looking at NEST source code, it seems that you could do something like:
foreach (var item in result.Documents.ToList())
{
string msg = "Name: " + item.Name + " | Value: " + item.Text;
alert(msg);
}

Related

Generic iteration through list

I've been building an email generator function. It started as a regex function, but quickly moved over to reflection to make it as generic as possible. The idea is to have the email generator function pull in information from a messagedata class. It started as a simple task, as the function only had to change out a few static items, but as the function got more complex and the templates being sent out needed tables of information, then the function I had built was not enough.
I've extended the function to use a foreach loop to run through the template and replace text based on a list from the messageData class. I've been trying to get the list created in messageData to implement it into the emailGenerator function.
I've got:
string value = match.Groups["value"].Value;
// Code removed to shorten length
var items = (IEnumerable) value;
But it's not gathering the information from the messageData class. I'm thinking that maybe I need to get the value into a list?
Here is the EmailGenerator function:
public class EmailGenerator : IEmailGenerator
{
private string mergeTemplate(string template, object obj)
{
var operationMatches = operationParser.Matches(template).Cast<Match>().Reverse().ToList();
foreach (var match in operationMatches)
{
string operation = match.Groups["operation"].Value;
string value = match.Groups["value"].Value;
var propertyInfo = obj.GetType().GetProperty(value);
object dataValue = propertyInfo.GetValue(obj, null);
if (operation == "endforeach")
{
string foreachToken = "$foreach " + value + "$";
var startIndex = template.LastIndexOf(foreachToken, match.Index);
var templateBlock = template.Substring(startIndex + foreachToken.Length, match.Index - startIndex - foreachToken.Length);
var items = (IEnumerable) value;
string blockResult = "";
foreach (object item in items)
{
blockResult += this.mergeTemplate(templateBlock, item);
}
template = template.Remove(startIndex, match.Index - startIndex).Insert(startIndex, blockResult);
}
}
}
And here is the messageData class. It gets the information from a DTO.
** EDIT: Removed unnecessary code.
public class messageData : IMailObject
{
public List<messageItemData> Items
{
get
{
var items = new List<messageItemData>();
foreach (var docDTO in this.recipientDTO.InfoList)
{
items.Add(new messageItemData(docDTO));
}
}
}
}
public class messageItemData
{
// Properties
}
What I'm trying to accomplish is that the emailGenerator function is made generic enough to be reusable for other email templates later down the road, gathering the replacement information from the messageData class and the list it contains.
So, I finally found the answer. The code was 99% working. It was as simple as changing var items = (IEnumerable) value; for var items = (IEnumerable) dataValue;

Two Groups with the same sAMAccountName, using FindOne() to getting the second occurance of group

I have a user that i'm currently listing "memberOf". I wanted to get some details about each group the user is a member of, such as distinguishedName, last modified, and description... The problem is, I'm using FindOne() in my code and i have a couple groups with sAMAccountName that are duplicated in various domains. Is there a way to use FindOne() and get the second occurance of the group as I have it coded below, or do I need to rewrite and use FindAll() and handling it that way.
Relevant Code Below:
foreach (object item in groups)
{
string groupProp = string.Empty;
using (DirectoryEntry dirEntry = CreateDirectoryEntry())
{
using (DirectorySearcher dirSearcher2 = new DirectorySearcher(dirEntry))
{
dirSearcher2.Filter = string.Format("(sAMAccountName=" + item + ")");
dirSearcher2.PropertiesToLoad.Add("description");
dirSearcher2.PropertiesToLoad.Add("whenChanged");
dirSearcher2.PropertiesToLoad.Add("distinguishedName");
SearchResult searchResult2 = dirSearcher2.FindOne();
if (searchResult2 != null)
{
DirectoryEntry employee = searchResult2.GetDirectoryEntry();
string desc = string.Empty;
string date = string.Empty;
string dname = string.Empty;
if (employee.Properties["description"].Value != null)
{
desc = employee.Properties["description"].Value.ToString();
}
if (employee.Properties["whenChanged"].Value != null)
{
date = employee.Properties["whenChanged"].Value.ToString();
}
if (employee.Properties["distinguishedName"].Value != null)
{
dname = employee.Properties["distinguishedName"].Value.ToString();
if (dname.Contains("DC=academic"))
{
dname = "academic";
}
}
}
}
}
Relevant New Code:
using (var results = dirSearcher2.FindAll())
{
foreach (SearchResult searchResult2 in results)
{
html.Append("<tr><td>" + item.ToString() + "</td>");
if (searchResult2.Properties.Contains("description"))
{
desc = searchResult2.Properties["description"][0].ToString();
}
if (searchResult2.Properties.Contains("whenChanged"))
{
date = searchResult2.Properties["whenChanged"][0].ToString();
}
if (searchResult2.Properties.Contains("distinguishedName"))
{
dom = searchResult2.Properties["distinguishedName"][0].ToString();
if (dom.Contains("DC=academic"))
{
dname = "academic";
}
else if (dom.Contains("DC=office"))
{
dname = "office";
}
else
{
dname = "not listed";
}
}
html.Append("<td>" + desc + "</td><td>" + dname + "</td><td>" + date + "</td></tr>");
}
Essentially, I'm getting the same results as it was getting with my first code, IE not getting the correct information on the second Group. IE: i have two groups named AppDev, both are on different domains; however, both show academic in the display. When I look in AD, i see that the distiguished name shows DC=office on one group, though the code above isn't pulling that.
FindOne() only finds one. If you need to see more, you will need to use FindAll(). Just make sure you wrap the result in a using statement, since the documentation says that you can have memory leaks if you don't:
using (var results = dirSearcher2.FindAll()) {
foreach (SearchResult searchResult2 in results) {
//do stuff
}
}
If you only want to find 2 (for example, if you only need to know if more than one exists), then you can set the SizeLimit property of your DirectorySearcher to 2:
dirSearcher2.SizeLimit = 2;
A note about efficiency: When you use .GetDirectoryEntry() and then get the properties from the DirectoryEntry object, DirectoryEntry actually goes back out to AD to get those attributes, even though you already got them during your search. You already used PropertiesToLoad to ask for those attributes, so they are already available in your SearchResult object. Just be aware that all attributes in the Properties list of SearchResult are presented as arrays, so you always need to use [0], even if they are single-valued attributes in AD.
if (searchResult2.Properties.Contains("description")) {
desc = searchResult2.Properties["description"][0];
}
If also need to make sure you are searching the Global Catalog, which will return results from all domains in your forest. You do this by creating the DirectoryEntry that you use for your SearchRoot with GC:// instead of LDAP://. This tell it to use port 3268 (the GC port) rather than the default LDAP port (389). You are creating this object in your CreateDirectoryEntry() method.

Trying to get NetSuite Country list with enumeration value linked to code and name

I am implementing a integration with NetSuite in C#. In the external system I need to populate a list of countries that will match NetSuite's country list.
The NetSuite Web Service provides an enumeration call Country
public enum Country {
_afghanistan,
_alandIslands,
_albania,
_algeria,
...
You can also get a list of country Name and Code (in an albeit not so straight forward way) from the web service. (See: http://suiteweekly.com/2015/07/netsuite-get-all-country-list/)
Which gives you access to values like this:
Afghanistan, AF
Aland Islands, AX
Albania, AL
Algeria, DZ
American Samoa, AS
...
But, as you can see, there is no way to link the two together. (I tried to match by index but that didn't work and sounds scary anyway)
NetSuite's "help" files have a list. But this is static and I really want a dynamic solution that updates as NetSuites updates because we know countries will change--even is not that often.
Screenshot of Country Enumerations from NetSuite help docs
The only solutions I have found online are people who have provided static data that maps the two sets of data. (ex. suiteweekly.com /2015/07/netsuite-complete-country-list-in-netsuite/)
I cannot (don't want to) believe that this is the only solution.
Anyone else have experience with this that has a better solution?
NetSuite, if you are reading, come on guys, give a programmer a break.
The best solution I have come up with is to leverage the apparent relationship between the country name and the enumeration key to forge a link between the two. I am sure others could improve on this solution but what I would really like to see is a solution that isn't a hack like this that relies on an apparent pattern but rather on that is based on an explicit connection. Or better yet NetSuite should just provide the data in one place all together.
For example you can see the apparent relationship here:
_alandIslands -> Aland Islands
With a little code I can try to forge a match.
I first get the Enumeration Keys into an array. And I create a list of objects of type NetSuiteCountry that will hold my results.
var countryEnumKeys = Enum.GetNames(typeof(Country));
var countries = new List<NetSuiteCountry>();
I then loop through the list of country Name and Code I got using the referenced code above (not shown here).
For each country name I then strip all non-word characters from the country name with Regex.Replace, prepend an underscore (_) and then convert the string to lowercase. Finally I try to find a match between the Enumeration Key (converted to lowercase as well) and the matcher string that was created. If a match is found I save all the data together the countries list.
UPDATE: Based on the comments I have added additional code/hacks to try to deal with the anomalies without hard-coding exceptions. Hopefully these updates will catch any future updates to the country list as well, but no promises. As of this writing it was able to handle all the known anomalies. In my case I needed to ignore Deprecated countries so those aren't included.
foreach (RecordRef baseRef in baseRefList)
{
var name = baseRef.name;
//Skip Deprecated countries
if (name.EndsWith("(Deprecated)")) continue;
//Use the name to try to find and enumkey match and only add a country if found.
var enumMatcher = $"_{Regex.Replace(name, #"\W", "").ToLower()}";
//Compares Ignoring Case and Diacritic characters
var enumMatch = CountryEnumKeys.FirstOrDefault(e => string.Compare(e, enumMatcher, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase) == 0);
//Then try by Enum starts with Name but only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => e.ToLower().StartsWith(enumMatcher));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 1 : ");
enumMatch = matches.First();
}
}
//Then try by Name starts with Enum but only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => enumMatcher.StartsWith(e.ToLower()));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 2 : ");
enumMatch = matches.First();
}
}
//Finally try by first half Enum and Name match but again only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => e.ToLower().StartsWith(enumMatcher.Substring(0, (enumMatcher.Length/2))));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 3 : ");
enumMatch = matches.First();
}
}
if (enumMatch != null)
{
var enumIndex = Array.IndexOf(CountryEnumKeys, enumMatch);
if (enumIndex >= 0)
{
var country = (Country) enumIndex;
var nsCountry = new NetSuiteCountry
{
Name = baseRef.name,
Code = baseRef.internalId,
EnumKey = country.ToString(),
Country = country
};
Debug.WriteLine($"[{nsCountry.Name}] as [{nsCountry.EnumKey}]");
countries.Add(nsCountry);
}
}
else
{
Debug.WriteLine($"Could not find Country match for: [{name}] as [{enumMatcher}]");
}
}
Here is my NetSuiteCountry class:
public class NetSuiteCountry
{
public string Name { get; set; }
public string Code { get; set; }
public string EnumKey { get; set; }
public Country Country { get; set; }
}
Let me start off with a disclaimer that I'm not a coder, and this is the first day I've tried to look at a C# program.
I need something similar for a Javascript project where I need the complete list of Netsuite company names, codes and their numeric values and when reading the help it seemed like the only way was through webservices.
I downloaded the sample application for webservices from Netsuite and a version of Visual Studio and I was able to edit the sample program provided to create a list of all of the country names and country codes (ex. Canada, CA).
I started out doing something similar to the previous poster to get the list of country names:
string[] countryList = Enum.GetNames(typeof(Country));
foreach (string s in countryList)
{
_out.writeLn(s);
}
But I later got rid of this and started a new technique. I created a class similar to the previous answer:
public class NS_Country
{
public string countryCode { get; set; }
public string countryName { get; set; }
public string countryEnum { get; set; }
public string countryNumericID { get; set; }
}
Here is the new code for getting the list of company names, codes and IDs. I realize that it's not very efficient as I mentioned before I'm not really a coder and this is my first attempt with C#, lots of Google and cutting/pasting ;D.
_out.writeLn(" Attempting to get Country list.");
// Create a list for the NS_Country objects
List<NS_Country> CountryList = new List<NS_Country>();
// Create a new GetSelectValueFieldDescription object to use in a getSelectValue search
GetSelectValueFieldDescription countryDesc = new GetSelectValueFieldDescription();
countryDesc.recordType = RecordType.customer;
countryDesc.recordTypeSpecified = true;
countryDesc.sublist = "addressbooklist";
countryDesc.field = "country";
// Create a GetSelectValueResult object to hold the results of the search
GetSelectValueResult myResult = _service.getSelectValue(countryDesc, 0);
BaseRef[] baseRef = myResult.baseRefList;
foreach (BaseRef nsCountryRef in baseRef)
{
// Didn't know how to do this more efficiently
// Get the type for the BaseRef object, get the property for "internalId",
// then finally get it's value as string and assign it to myCountryCode
string myCountryCode = nsCountryRef.GetType().GetProperty("internalId").GetValue(nsCountryRef).ToString();
// Create a new NS_Country object
NS_Country countryToAdd = new NS_Country
{
countryCode = myCountryCode,
countryName = nsCountryRef.name,
// Call to a function to get the enum value based on the name
countryEnum = getCountryEnum(nsCountryRef.name)
};
try
{
// If the country enum was verified in the Countries enum
if (!String.IsNullOrEmpty(countryToAdd.countryEnum))
{
int countryEnumIndex = (int)Enum.Parse(typeof(Country), countryToAdd.countryEnum);
Debug.WriteLine("Enum: " + countryToAdd.countryEnum + ", Enum Index: " + countryEnumIndex);
_out.writeLn("ID: " + countryToAdd.countryCode + ", Name: " + countryToAdd.countryName + ", Enum: " + countryToAdd.countryEnum);
}
}
// There was a problem locating the country enum that was not handled
catch (Exception ex)
{
Debug.WriteLine("Enum: " + countryToAdd.countryEnum + ", Enum Index Not Found");
_out.writeLn("ID: " + countryToAdd.countryCode + ", Name: " + countryToAdd.countryName + ", Enum: Not Found");
}
// Add the countryToAdd object to the CountryList
CountryList.Add(countryToAdd);
}
// Create a JSON - I need this for my javascript
var javaScriptSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
string jsonString = javaScriptSerializer.Serialize(CountryList);
Debug.WriteLine(jsonString);
In order to get the enum values, I created a function called getCountryEnum:
static string getCountryEnum(string countryName)
{
// Create a dictionary for looking up the exceptions that can't be converted
// Don't know what Netsuite was thinking with these ones ;D
Dictionary<string, string> dictExceptions = new Dictionary<string, string>()
{
{"Congo, Democratic Republic of", "_congoDemocraticPeoplesRepublic"},
{"Myanmar (Burma)", "_myanmar"},
{"Wallis and Futuna", "_wallisAndFutunaIslands"}
};
// Replace with "'s" in the Country names with "s"
string countryName2 = Regex.Replace(countryName, #"\'s", "s");
// Call a function that replaces accented characters with non-accented equivalent
countryName2 = RemoveDiacritics(countryName2);
countryName2 = Regex.Replace(countryName2, #"\W", " ");
string[] separators = {" ","'"}; // "'" required to deal with country names like "Cote d'Ivoire"
string[] words = countryName2.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < words.Length; i++)
{
string word = words[i];
if (i == 0)
{
words[i] = char.ToLower(word[0]) + word.Substring(1);
}
else
{
words[i] = char.ToUpper(word[0]) + word.Substring(1);
}
}
string countryEnum2 = "_" + String.Join("", words);
// return an empty string if the country name contains Deprecated
bool b = countryName.Contains("Deprecated");
if (b)
{
return String.Empty;
}
else
{
// test to see if the country name was one of the exceptions
string test;
bool isExceptionCountry = dictExceptions.TryGetValue(countryName, out test);
if (isExceptionCountry == true)
{
return dictExceptions[countryName];
}
else
{
return countryEnum2;
}
}
}
In the above I used a function, RemoveDiacritics I found here. I will repost the referenced function below:
static string RemoveDiacritics(string text)
{
string formD = text.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
foreach (char ch in formD)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(ch);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(ch);
}
}
return sb.ToString().Normalize(NormalizationForm.FormC);
}
Here are the tricky cases to test any solution you develop with:
// Test tricky names
Debug.WriteLine(getCountryEnum("Curaçao"));
Debug.WriteLine(getCountryEnum("Saint Barthélemy"));
Debug.WriteLine(getCountryEnum("Croatia/Hrvatska"));
Debug.WriteLine(getCountryEnum("Korea, Democratic People's Republic"));
Debug.WriteLine(getCountryEnum("US Minor Outlying Islands"));
Debug.WriteLine(getCountryEnum("Cote d'Ivoire"));
Debug.WriteLine(getCountryEnum("Heard and McDonald Islands"));
// Enums that fail
Debug.WriteLine(getCountryEnum("Congo, Democratic Republic of")); // _congoDemocraticPeoplesRepublic added to exceptions
Debug.WriteLine(getCountryEnum("Myanmar (Burma)")); // _myanmar added to exceptions
Debug.WriteLine(getCountryEnum("Netherlands Antilles (Deprecated)")); // Skip Deprecated
Debug.WriteLine(getCountryEnum("Serbia and Montenegro (Deprecated)")); // Skip Deprecated
Debug.WriteLine(getCountryEnum("Wallis and Futuna")); // _wallisAndFutunaIslands added to exceptions
For my purposes I wanted a JSON object that had all the values for Coutries (Name, Code, Enum, Value). I'll include it here in case anyone is searching for it. The numeric values are useful when you have a 3rd party HTML form that has to forward the information to a Netsuite online form.
Here is a link to the JSON object on Pastebin.
My appologies for the lack of programming knowledge (only really do a bit of javascript), hopefully this additional information will be useful for someone.

Perform a linq expression for 'contains' with searching through a list for 'like' not exact matches

Okay so I am stumped and have looked around for this and I know I am doing the implementation of something very simple more complex than it needs to be. Basically I have a POCO object that will have a member that contains a string of other members. This is labeled as 'st' and it may have strings that are comma seperated series in one string. Thus I may have two members of strings be 'images, reports' and another 'cms, crm'. I have a list of objects that I want to match for PART OF those strings but not necessarily all as a DISTINCT LIST. So a member of 'cms' would return the value of anything that contained 'cms' thus 'cms, crm' would be returned.
I want to hook this up so a generic List can be queried but I cannot get it to work and was looking at other threads but there methods do not work in my case. I keep thinking it is something simple but I am missing it completely. Please let me know if anyone has better ideas. I was looking here but could not get the logic to apply correctly:
Linq query list contains a list
I keep trying methods of 'Select', 'SelectMany', 'Contains', 'Any', 'All' at different levels of scope of the continuations to no avail. Here is a simple excerpt of where I am at with a simple Console app example:
public class Program
{
public class StringModel
{
public string name { get; set; }
public string str { get; set; }
}
static void Main(string[] args)
{
string s = "";
List<StringModel> sm = new List<StringModel>
{
new StringModel
{
name = "Set1",
str = "images, reports"
},
new StringModel
{
name = "Set2",
str = "cms, crm"
},
new StringModel
{
name = "Set3",
str = "holiday, pto, cms"
}
};
sm.ForEach(x => s += x.name + "\t" + x.str + "\n");
var selected = new List<object> {"cms", "crm"};
s += "\n\nITEMS TO SELECT: \n\n";
selected.ForEach(x => s += x + "\n");
s += "\n\nSELECTED ITEMS: \n\n";
// works on a single item just fine
var result = sm.Where(p => p.str.Contains("cms")).Select(x => new { x.name, x.str}).ToList();
// I am not using select to get POCO on other methods till I can get base logic to work.
// Does not return anything
var result2 = sm.Where(p => selected.Any(x => x == p.str)).ToList();
// Does not return anything
var result3 = sm.Where(p => selected.Any(x => selected.Contains(p.str))).ToList();
result.ForEach(y => s += y + "\n");
s += "\n\n2nd SET SELECTED: \n\n";
result2.ForEach(y => s += y + "\n");
s += "\n\n3rd SET SELECTED: \n\n";
result3.ForEach(y => s += y + "\n");
Console.WriteLine(s);
Console.ReadLine();
}
}
result2 is empty because you're comparing an object (x) with a string (StringModel.str). This will be a reference comparison. Even if you convert x to a string, you'll be comparing each value in selected ("cms", "crm") with your comma-separated string values ("images, reports", "cms, crm", "holiday, pto, cms").
result3 is empty because selected ("cms", "crm") does not contain any of the string values ("images, reports", "cms, crm", "holiday, pto, cms"), although in this case at least the comparisons are value comparisons.
I think you're looking for something like:
var result = sm.Where(p => selected.Any(x => p.str.Contains((string)x)));

Splitting a list<> that each item contains comma separated strings and formatting it out

I have a simple class like this:
class QuickReport
{
public string DeviceName { get; set; }
public string GroupName { get; set; }
public string PinName { get; set; }
public override string ToString()
{
return DeviceName + "," + GroupName + "," + PinName;
}
}
Later I make a list of items with this class:
List<QuickReport> QR = new List<QuickReport>();
Later in my program it will fill up and when I save it in a text file it will be like this example:
HBM\D1,GND,10
HBM\D1,GND,12
HBM\D1,NT_IOp,115
HBM\D1,NT_IOp,117
HBM\D2,GND,8
HBM\D2,NT_IOp,115
HBM\D2,NT_IOp,116
Now I want to make a function to save the text file in more readable manner. That is formatting it by DEVICE, GROUPS and PINS. So the above example would result in:
HBM\D1
GND: 10, 12
NT_IOp: 115, 117
HBM\D2
GND: 8
NT_IOp: 115, 116
can you please help and give some ideas?
Thanks!
var query = QR.ToLookup(i=>i.DeviceName, i => new {i.GroupName, i.PinName})
.Select(i=>
new {DeviceName = i.Key,
Groups = i.ToLookup(g=>g.GroupName, g=>g.PinName)});
var sb = new StringBuilder();
foreach ( var device in query)
{
sb.AppendLine(device.DeviceName);
foreach ( var gr in device.Groups)
{
sb.Append(gr.Key + ": ");
sb.Append(String.Join(", ", gr.ToArray()));
sb.AppendLine();
}
sb.AppendLine();
}
var stringToWrite = sb.ToString();
As i understand you have tree structure, where Device have child Groups, and Groups have child pins.
You can create custom classes like this:
class Group
{
string Name;
//pins that belong to this group
List<string> pins;
}
class Device
{
string Name;
//groups that belong to this device
List<Group> Groups;
}
And than just collect it to List<Device> and serialize it using XML Serialization.
This isn't complete, but it should give you enough to go on. You'll still need to add your newlines, and remove trailing commas, etc.
// Make your key the device name
var qrHash = new Dictionary<string, List<QuickReport>>();
// Populate your QR Dictionary here.
var output = new StringBuilder();
foreach (var keyValuePair in qrHash)
{
output.Append(keyValuePair.Key);
var gnd = new StringBuilder("GND: ");
var nt = new StringBuilder("NT_IOp: ");
foreach (var qr in keyValuePair.Value)
{
gnd.Append(qr.GroupName);
nt.Append(qr.PinName);
}
output.Append(gnd);
output.Append(nt);
}
How about using the XmlSerializer to serialize and deserialize your class? This should provide some readable output.
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx
The quickest ways I can think of to do this would either be to loop over the List<> 3 times, eachtime checking on a seperate accessor, writing it out to a StringBuilder, then returning StringBuilder.ToString() from the function.
Or, you could use 3 stringbuilders to hold each accessor type, then push all 3 from the function on return.

Categories