C#: Recursively get Dictonary value with deep string expression - c#

I want to get an object from a Dictionary<string, object> using a string expression as key.
For example taking the following data structure:
var data = new Dictionary<string, object>()
{
{ "Products", new object[]
{
new Dictionary<string, object>()
{
{ "Name", "Tin of biscuits" }
}
}
}
}
I want to return the product name with the expression: "Products[0].Name".
This is just an example, the data could be of any depth and the string expression could be something like "BookStore.Books[3].Author.ContactDetails.Address" for eg.
So far I have been experimenting with Recursive methods and have also tried string.Split('.') with Aggregate Linq method, but I am struggling and I think this is a perfect little puzzle for SO.

Assuming you'll always have the Dictionary<string, object> and object[] in your data structure, this approach should work:
private static object? GetSelectedValueOrDefault(this Dictionary<string, object> data, string selector)
{
string[] selectorSegments = selector.Split('.');
if (selectorSegments.Length == 0)
{
return null;
}
object? currentNode = data.GetValueOrDefault(selectorSegments[0]);
if (currentNode == null)
{
return null;
}
for (int index = 1; index < selectorSegments.Length; index++)
{
string segment = selectorSegments[index];
if (currentNode is not Dictionary<string, object> dictionary)
{
return null;
}
var selectorWithIndex = GetSelectorAndElementIndexOrDefault(segment);
if (selectorWithIndex is not null &&
currentNode is Dictionary<string, object> dict)
{
currentNode = dict.GetValueOrDefault(selectorWithIndex.Value.ItemSelector);
currentNode = GetElementOrDefault(currentNode, selectorWithIndex.Value.Index);
continue;
}
currentNode = dictionary.GetValueOrDefault(segment);
if (index == selectorSegments.Length - 1)
{
return currentNode;
}
}
return null;
}
private static object? GetElementOrDefault(object? currentNode, int index)
{
if (currentNode is not object[] array)
{
return null;
}
if (index >= array.Length)
{
return null;
}
return array[index];
}
private static (string ItemSelector, int Index)? GetSelectorAndElementIndexOrDefault(string segment)
{
if (!segment.Contains('['))
{
return null;
}
string[] result = segment.Split('[', ']');
return (result[0], int.Parse(result[1]));
}
Example
var data = new Dictionary<string, object>()
{
{
"BookStore",
new Dictionary<string, object>()
{
{
"Name",
"The Book Store"
},
{
"Books",
new object[]
{
new Dictionary<string, object>(),
new Dictionary<string, object>(),
new Dictionary<string, object>(),
new Dictionary<string, object>()
{
{
"Author",
new Dictionary<string, object>()
{
{
"Name",
"Luke T O'Brien"
},
{
"ContactDetails",
new Dictionary<string, object>()
{
{
"Address",
"Some address"
}
}
}
}
}
}
}
}
}
}
};
Console.WriteLine(data.GetSelectedValueOrDefault("BookStore.Name"));
Console.WriteLine(data.GetSelectedValueOrDefault("BookStore.Books[3].Author.Name"));
Console.WriteLine(data.GetSelectedValueOrDefault("BookStore.Books[3].Author.ContactDetails.Address"));
Output
The Book Store
Luke T O'Brien
Some address

Related

Program.FindRoutes(string[][])' is inaccessible due to its protection level

I am having a problem with
C# Version: 8.0 (.NET Core 3.1)
I got this error
tests/Fixture.cs(11,62): error CS0122: 'Program.FindRoutes(string[][])' is inaccessible due to its protection level
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
string[][] routes = new string[][] {
new string[] { "USA", "BRA" },
new string[] { "JPN", "PHL" },
new string[] { "BRA", "UAE" },
new string[] { "UAE", "JPN" }
};
}
static string FindRoutes(string[][] routes)
{
Dictionary<string, List<string>> graph = new Dictionary<string, List<string>>();
Dictionary<string, int> indegree = new Dictionary<string, int>();
foreach (var route in routes)
{
if (!graph.ContainsKey(route[0]))
{
graph[route[0]] = new List<string>();
indegree[route[0]] = 0;
}
if (!graph.ContainsKey(route[1]))
{
graph[route[1]] = new List<string>();
indegree[route[1]] = 0;
}
graph[route[0]].Add(route[1]);
indegree[route[1]]++;
}
Queue<string> queue = new Queue<string>();
foreach (var node in indegree)
{
if (node.Value == 0)
{
queue.Enqueue(node.Key);
}
}
string result = "";
while (queue.Count > 0)
{
string node = queue.Dequeue();
result += node + ", ";
foreach (var neighbor in graph[node])
{
indegree[neighbor]--;
if (indegree[neighbor] == 0)
{
queue.Enqueue(neighbor);
}
}
}
return result.TrimEnd(new char[] { ',', ' ' });
}
}

How N level object convert to N level Dictionary<string,object> this object can be Enumerable<Dictionary<string,object>> or object (without Json.Net)

How N level object convert to N level Dictionary<string,object> this object can be Enumerable<Dictionary<string,object>> or object and without Json.Net
input :
var department = new
{
ID = "S001",
Name = "HR",
Users = new[]{
new {ID="E001",Name="Jack"},
new {ID="E002",Name="Terry"},
new {ID="E003",Name="Jassie"},
},
ChildDepartments = new[] {"D004","D005","D006"}
};
convert to data like
var expectedResult = new Dictionary<string, object>
{
["ID"] = "S001",
["Name"] = "HR",
["Users"] = new List<Dictionary<string, object>>{
new Dictionary<string,object>{["ID"]="E001",["Name"]="Jack"},
new Dictionary<string,object>{["ID"]="E002",["Name"]="Terry"},
new Dictionary<string,object>{["ID"]="E003",["Name"]="Jassie"},
},
["ChildDepartments"] = new[] {"D004","D005","D006"}
};
Below is what I tried, only can support 2 level, it can't support than 2 level
internal static Dictionary<string, object> ToDictionary(object value)
{
if (value == null)
return new Dictionary<string, object>();
else if (value is Dictionary<string, object> dicStr)
return dicStr;
else if (value is ExpandoObject)
return new Dictionary<string, object>(value as ExpandoObject);
if (IsStrongTypeEnumerable(value))
throw new Exception("The parameter cannot be a collection type");
else
{
Dictionary<string, object> reuslt = new Dictionary<string, object>();
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(value);
foreach (PropertyDescriptor prop in props)
{
object val1 = prop.GetValue(value);
if (IsStrongTypeEnumerable(val1))
{
List<Dictionary<string, object>> sx = new List<Dictionary<string, object>>();
foreach (object val1item in (IEnumerable)val1)
{
if (val1 == null)
{
sx.Add(new Dictionary<string, object>());
continue;
}
else if (val1 is Dictionary<string, object> dicStr)
{
sx.Add(dicStr);
continue;
}
else if (val1 is ExpandoObject)
{
sx.Add(new Dictionary<string, object>(value as ExpandoObject));
continue;
}
PropertyDescriptorCollection props2 = TypeDescriptor.GetProperties(val1item);
Dictionary<string, object> reuslt2 = new Dictionary<string, object>();
foreach (PropertyDescriptor prop2 in props2)
{
object val2 = prop2.GetValue(val1item);
reuslt2.Add(prop2.Name, val2);
}
sx.Add(reuslt2);
}
reuslt.Add(prop.Name, sx);
}
else
{
reuslt.Add(prop.Name, val1);
}
}
return reuslt;
}
}
internal static bool IsStrongTypeEnumerable(object obj)
{
return obj is IEnumerable && !(obj is string) && !(obj is char[]) && !(obj is string[]);
}

DocuSign API returning errorCode REQUEST_ENTITY_TOO_LARGE

When trying to send two 19MB documents in the envelope I am getting this error code REQUEST_ENTITY_TOO_LARGE.
I developed in C# with binary transfer as suggested by DocuSign.
SendEnvelope main console code:
DocuSignLib.SendEnvelope sendEnvelope = new DocuSignLib.SendEnvelope();
// 19MB file 1
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "19MB.pdf");
sendEnvelope.AddDocument(filePath);
// 19MB file 2
filePath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "19MB_1.pdf");
sendEnvelope.AddDocument(filePath);
sendEnvelope.AddSigner(ConfigurationManager.AppSettings["SignerName"],
ConfigurationManager.AppSettings["SignerEmail"], "1", "*s1*", "*r1*", null, null, 20, 10);
var envSummary = sendEnvelope.Send(config.UserId,
config.IntegrationKey,
config.RSAPrivateKey,
config.IsDeveloperSandbox,
ConfigurationManager.AppSettings["EmailSubject"],
ConfigurationManager.AppSettings["EmailBody"],
600);
Console.WriteLine("\tEnvelope Id: " + envSummary.EnvelopeId);
Console.WriteLine("\tEnvelope Status: " + envSummary.Status);
Console.WriteLine("\tEnvelope Uri: " + envSummary.Uri);
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
SendEnvelope class:
using DocuSign.eSign.Model;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;
public class SendEnvelope
{
private List<Dictionary<string, dynamic>> signers = new List<Dictionary<string, dynamic>>();
private List<Dictionary<string, dynamic>> carbonCopies = new List<Dictionary<string, dynamic>>();
List<dynamic> docs = new List<dynamic>();
private List<Dictionary<string, dynamic>> witnesses = new List<Dictionary<string, dynamic>>();
private DocuSignConfig Config;
public void AddSigner(string Name,
string Email,
string RecipientId,
string SignHereAnchor,
string InitialHereAnchor,
string EmailSubject,
string EmailBody,
int AnchorXOffset,
int AnchorYOffset)
{
if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email) && !string.IsNullOrEmpty(RecipientId))
{
if (Util.IsValidEmail(Email))
{
Dictionary<string, dynamic> signer = new Dictionary<string, dynamic>()
{
{ "email", Email },
{ "name", Name },
{ "recipientId", RecipientId }
};
Dictionary<string, dynamic> signHere = null;
Dictionary<string, dynamic> initialHere = null;
if (!string.IsNullOrEmpty(SignHereAnchor))
{
signHere = new Dictionary<string, dynamic>()
{
{ "anchorString", SignHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(InitialHereAnchor))
{
initialHere = new Dictionary<string, dynamic>()
{
{ "anchorString", InitialHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
signer.Add("emailNotification", emailNotification);
}
if (initialHere != null || signHere != null)
{
Dictionary<string, dynamic> Tabs = new Dictionary<string, dynamic>()
{
{ "initialHereTabs", new dynamic[] { initialHere } },
{ "signHereTabs", new dynamic[] { signHere } }
};
signer.Add("tabs", Tabs);
}
signers.Add(signer);
}
else
throw new Exception("The e-mail " + Email + " is ivalid!");
}
}
public void AddCarbonCopy(string Name, string Email, string EmailSubject, string EmailBody)
{
if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email))
{
if (Util.IsValidEmail(Email))
{
Dictionary<string, dynamic> cc = new Dictionary<string, dynamic>()
{
{ "email", Email },
{ "name", Name }
};
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
cc.Add("emailNotification", emailNotification);
}
carbonCopies.Add(cc);
}
else
throw new Exception("The e-mail " + Email + " is ivalid!");
}
}
public void AddWitness(string Name,
string Email,
string RecipientId,
string WitnessFor, // Signer RecipentId
string SignHereAnchor,
string InitialHereAnchor,
string EmailSubject,
string EmailBody,
int AnchorXOffset,
int AnchorYOffset)
{
if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email) &&
!string.IsNullOrEmpty(RecipientId) && !string.IsNullOrEmpty(WitnessFor))
{
if (Util.IsValidEmail(Email))
{
Dictionary<string, dynamic> wit = new Dictionary<string, dynamic>()
{
{ "email", Email },
{ "name", Name },
{ "recipientId", RecipientId },
{ "witnessFor", WitnessFor }
};
Dictionary<string, dynamic> signHere = null;
Dictionary<string, dynamic> initialHere = null;
if (!string.IsNullOrEmpty(SignHereAnchor))
{
signHere = new Dictionary<string, dynamic>()
{
{ "anchorString", SignHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(InitialHereAnchor))
{
initialHere = new Dictionary<string, dynamic>()
{
{ "anchorString", InitialHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
wit.Add("emailNotification", emailNotification);
}
if (initialHere != null || signHere != null)
{
Dictionary<string, dynamic> Tabs = new Dictionary<string, dynamic>()
{
{ "initialHereTabs", new dynamic[] { initialHere } },
{ "signHereTabs", new dynamic[] { signHere } }
};
wit.Add("tabs", Tabs);
}
witnesses.Add(wit);
}
else
throw new Exception("The e-mail " + Email + " is ivalid!");
}
}
public void AddWitnessGroup(string GroupName,
string GroupId,
string RecipientId,
string WitnessFor, // Signer RecipentId
string SignHereAnchor,
string InitialHereAnchor,
string EmailSubject,
string EmailBody,
int AnchorXOffset,
int AnchorYOffset)
{
if (!string.IsNullOrEmpty(GroupName) && !string.IsNullOrEmpty(GroupId) &&
!string.IsNullOrEmpty(RecipientId) && !string.IsNullOrEmpty(WitnessFor))
{
Dictionary<string, dynamic> wit = new Dictionary<string, dynamic>()
{
{ "signingGroupId", GroupId },
{ "signingGroupName", GroupName },
{ "recipientId", RecipientId },
{ "witnessFor", WitnessFor }
};
Dictionary<string, dynamic> signHere = null;
Dictionary<string, dynamic> initialHere = null;
if (!string.IsNullOrEmpty(SignHereAnchor))
{
signHere = new Dictionary<string, dynamic>()
{
{ "anchorString", SignHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(InitialHereAnchor))
{
initialHere = new Dictionary<string, dynamic>()
{
{ "anchorString", InitialHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
wit.Add("emailNotification", emailNotification);
}
if (initialHere != null || signHere != null)
{
Dictionary<string, dynamic> Tabs = new Dictionary<string, dynamic>()
{
{ "initialHereTabs", new dynamic[] { initialHere } },
{ "signHereTabs", new dynamic[] { signHere } }
};
wit.Add("tabs", Tabs);
}
witnesses.Add(wit);
}
}
private void ValidateFileExtension(string FileExtension)
{
if (!Util.IsValidFileExtension(FileExtension))
{
throw new Exception("Invalid file extension '" + FileExtension + "'! DocuSign eSignature supports the following file types: " + Util.GetValidFileExtensions());
}
}
public void AddDocument(string FileName, byte[] FileContent)
{
ValidateFileExtension(Path.GetExtension(FileName));
docs.Add(
new
{
mime = MimeMapping.GetMimeMapping(FileName),
name = FileName,
documentId = (docs.Count + 1).ToString(),
bytes = FileContent
});
}
public void AddDocument(string FilePath)
{
ValidateFileExtension(Path.GetExtension(FilePath));
byte[] buff = File.ReadAllBytes(FilePath);
if (buff != null)
{
docs.Add(
new
{
mime = MimeMapping.GetMimeMapping(FilePath),
name = Path.GetFileNameWithoutExtension(FilePath),
documentId = (docs.Count + 1).ToString(),
bytes = buff
});
}
}
private Dictionary<string, dynamic> CreateEnvelope(string EmailSubject, string EmailBody)
{
return new Dictionary<string, dynamic>()
{
{ "emailSubject", !string.IsNullOrEmpty(EmailSubject) ? EmailSubject : null},
{ "emailBlurb", !string.IsNullOrEmpty(EmailBody) ? EmailBody : null},
{ "documents", docs.ToArray() },
{ "recipients", CreateRecipients() },
{ "status", "sent" }
};
}
private Dictionary<string, dynamic> CreateRecipients()
{
// Order the recipients
int order = 1;
for (int i = 0; i < signers.Count; i++)
{
signers[i]["routingOrder"] = order.ToString();
order++;
}
for (int i = 0; i < witnesses.Count; i++)
{
witnesses[i]["routingOrder"] = order.ToString();
order++;
}
for (int i = 0; i < carbonCopies.Count; i++)
{
carbonCopies[i]["routingOrder"] = order.ToString();
order++;
}
return new Dictionary<string, dynamic>()
{
{ "signers", signers },
{ "carbonCopies", carbonCopies },
{ "witnesses", witnesses }
};
}
public EnvelopeSummaryCopy Send(string UserId, string IntegrationKey, string RSAPrivateKey, bool IsDeveloperSandbox, string EmailSubject, string EmailBody, int ApiTimeout)
{
if (docs.Count == 0 || signers.Count == 0 || string.IsNullOrEmpty(EmailSubject))
throw new Exception(string.Format("No {0} found!",
docs.Count == 0 ? "document" : (signers.Count == 0 ? "signer" : "e-mail subject")));
else
{
EnvelopeSummaryCopy esc;
try
{
esc = SendNow(UserId, IntegrationKey, RSAPrivateKey, IsDeveloperSandbox, EmailSubject, EmailBody, ref ApiTimeout);
}
catch (Exception ex)
{
// Token expired, try again
if (ex.Message.Contains("USER_AUTHENTICATION_FAILED"))
{
JWTAuth.ClearToken();
esc = SendNow(UserId, IntegrationKey, RSAPrivateKey, IsDeveloperSandbox, EmailSubject, EmailBody, ref ApiTimeout);
}
else
throw ex;
}
return esc;
}
}
private EnvelopeSummaryCopy SendNow(string UserId, string IntegrationKey, string RSAPrivateKey, bool IsDeveloperSandbox, string EmailSubject, string EmailBody, ref int ApiTimeout)
{
ApiTimeout *= 1000;
Config = new DocuSignConfig(UserId, IntegrationKey, RSAPrivateKey, IsDeveloperSandbox);
APICallInfo apiCallInfo = JWTAuth.GetToken(Config);
if (string.IsNullOrEmpty(apiCallInfo.ErrorMessage))
{
dynamic envelope = CreateEnvelope(EmailSubject, EmailBody);
byte[] CRLF = Encoding.ASCII.GetBytes("\r\n");
byte[] boundary = Encoding.ASCII.GetBytes("multipartboundary_multipartboundary");
byte[] hyphens = Encoding.ASCII.GetBytes("--");
string uri = apiCallInfo.BaseUri
+ "/v2.1/accounts/" + apiCallInfo.AccountId + "/envelopes";
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "multipart/form-data; boundary=" + Encoding.ASCII.GetString(boundary);
request.Headers.Add("Authorization", "Bearer " + apiCallInfo.AccessToken);
request.Timeout = ApiTimeout < 100000 ? 100000 : ApiTimeout;
using (var buffer = new BinaryWriter(request.GetRequestStream(), Encoding.ASCII))
{
buffer.Write(hyphens);
buffer.Write(boundary);
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Type: application/json"));
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Disposition: form-data"));
buffer.Write(CRLF);
buffer.Write(CRLF);
var json = JsonConvert.SerializeObject(envelope, Formatting.Indented);
buffer.Write(Encoding.ASCII.GetBytes(json));
// Loop to add the documents.
// See section Multipart Form Requests on page https://developers.docusign.com/esign-rest-api/guides/requests-and-responses
foreach (var d in docs)
{
buffer.Write(CRLF);
buffer.Write(hyphens);
buffer.Write(boundary);
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Type:" + d.mime));
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Disposition: file; filename=\"" + d.name + ";documentid=" + d.documentId));
buffer.Write(CRLF);
buffer.Write(CRLF);
buffer.Write(d.bytes);
}
// Add closing boundary
buffer.Write(CRLF);
buffer.Write(hyphens);
buffer.Write(boundary);
buffer.Write(hyphens);
buffer.Write(CRLF);
buffer.Flush();
}
WebResponse response;
try
{
response = request.GetResponse();
}
catch (WebException ex)
{
throw ex;
}
var res = "";
using (var stream = response.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
res = reader.ReadToEnd();
}
}
HttpStatusCode code = ((HttpWebResponse)response).StatusCode;
dynamic obj = JsonConvert.DeserializeObject(res);
bool statusOk = code >= HttpStatusCode.OK && code < HttpStatusCode.MultipleChoices;
if (statusOk)
{
return new EnvelopeSummaryCopy()
{
EnvelopeId = obj.envelopeId,
Status = obj.status,
StatusDateTime = obj.statusDateTime,
Uri = obj.uri
};
}
else
{
throw new Exception(obj.errorCode + " : " + obj.message);
}
}
else throw new Exception(apiCallInfo.ErrorMessage);
}
}
Error from DocuSign API:
{
"errorCode": "REQUEST_ENTITY_TOO_LARGE",
"message": "The request size of 92875839 bytes exceeded the maximum size of 35651584 bytes."
}
The limit for a single API call is 25mb. As IvanD says, you're trying to upload too much in one API call.
The total document size limit for a single Envelope is larger and should accommodate your two documents. But you'll need to upload them separately.
Or you could create a template that includes the documents and then use the template in your Create Envelope call.
The limit for an envelope is 25MB max (all docs combined), you are trying to upload 2x19MB
Try with only one and see what the result will be

Pretty printing a collection recursively

I've seen lots of questions about this, but I haven't seen any about doing it recursively. I've created some extension methods that do a pretty good job of pretty printing at a depth of 1. It looks like this:
public static string PrettyToString<K, V>(this Dictionary<K, V> dictionary)
{
string result = "{";
foreach (var kvp in dictionary)
{
result += $"({kvp.Key}, {kvp.Value}) ";
}
result += "}";
return result;
}
public static string PrettyToString<T>(this List<T> list)
{
string result = "{";
foreach (var element in list)
{
result += $"{element}, ";
}
result += "}";
return result;
}
public static string PrettyToString<T>(this T [] list)
{
string result = "{";
foreach (var element in list)
{
result += $"{element}, ";
}
result += "}";
return result;
}
But, what if K, V, or T is another collection like a List or a Dictionary? I want to recursively pretty print, but I'm not sure how to do that. As a result, my output looks like this:
{(foo, System.Collections.Generic.Dictionary`2[System.String,System.Boolean])...
I want it to look like this instead:
{(foo, {(bar, true)})...
I'm looking for methods that print recursively regardless of the nested types:
var x = new List<Dictionary<string, string>>();
var y = new Dictionary<Dictionary<string, string>, string>>();
var z = new Dictionary<Dictionary<string, string>, List<string>>>();
...
x.PrettyToString();
y.PrettyToString();
z.PrettyToString();
Should all recursively print out the contents. How do I achieve that?
I changed the signature of your methods and made them non-generic.
The trick is to determine the type before converting.
Look at the sample code below. I hope it helps.
Please look at the Ext class at the bottom of the source code.
Try it online
using System;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
var Dic = new Dictionary<int, string> { { 1, "Ali" }, { 2, "B" } };
Console.WriteLine(Dic.PrettyToString());
var Dic1 = new Dictionary<string, float> { { "Ali", 12.5f }, { "B", 99.9f } };
Console.WriteLine(Dic1.PrettyToString());
var Dic2 = new Dictionary<List<int>, string>
{
{ new List<int> { 1, 2, 3 }, "A" },
{ new List<int> { 4, 5, 6 }, "B" }
};
Console.WriteLine(Dic2.PrettyToString());
var Dic3 = new Dictionary<Dictionary<string, string>, string>
{
{ new Dictionary<string, string> { { "a", "A" }, { "b", "B" } }, "Capital" },
{ new Dictionary<string, string> { { "1", "1" }, { "2", "4" }, { "4", "16" } }, "Power" }
};
Console.WriteLine(Dic3.PrettyToString());
var Dic4 = new Dictionary<Dictionary<string, string>, List<string>>
{
{ new Dictionary<string, string> { { "a", "A" }, { "b", "B" } }, new List<string> { "A", "B" } },
{ new Dictionary<string, string> { { "1", "1" }, { "2", "4" }, { "4", "16" } }, new List<string> { "1", "2", "4" } }
};
Console.WriteLine(Dic4.PrettyToString());
var L = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5, 6 }
};
Console.WriteLine(L.PrettyToString());
Console.ReadKey();
}
}
static class Ext
{
public static string PrettyToString(this IDictionary dictionary)
{
string result = "{";
foreach (var Key in dictionary.Keys)
{
result += string.Format("({0}, {1}) ", PrettyToString(Key), PrettyToString(dictionary[Key]));
}
result += "}";
return result;
}
public static string PrettyToString(this IEnumerable list)
{
string result = "{";
foreach (var element in list)
{
result += string.Format("{0}, ", PrettyToString(element));
}
result += "}";
return result;
}
private static string PrettyToString(object O)
{
var S = O as string;
if (S != null) return S;
var D = O as IDictionary;
if (D != null) return D.PrettyToString();
var L = O as IEnumerable;
if (L != null) return L.PrettyToString();
return O.ToString();
}
}
}

Is it possible to bind a List to a ListView in WinForms?

I'd like to bind a ListView to a List<string>. I'm using this code:
somelistview.DataBindings.Add ("Items", someclass, "SomeList");
I'm getting this exception: Cannot bind to property 'Items' because it is read-only.
I don't know how I should bind if the Items property is readonly?
The ListView class does not support design time binding. An alternative is presented in this project.
I use the following technique to bind data to a ListView.
It supports proper (not text-based) sorting. In the case above, by string, DateTime and integer.
The ListView above was generated with this code:
var columnMapping = new List<(string ColumnName, Func<Person, object> ValueLookup, Func<Person, string> DisplayStringLookup)>()
{
("Name", person => person.Name, person => person.Name),
("Date of birth", person => person.DateOfBirth, person => $"{person.DateOfBirth:dd MMM yyyy}"),
("Height", person => person.HeightInCentimetres, person => Converter.CentimetresToFeetInchesString(person.HeightInCentimetres))
};
var personListview = new ListViewEx<Person>(columnMapping)
{
FullRowSelect = true,
View = View.Details,
Left = 20,
Top = 20,
Width = 500,
Height = 300,
};
var people = new[]
{
new Person("Cathy Smith", DateTime.Parse("1980-05-15"), 165),
new Person("Bill Wentley", DateTime.Parse("1970-10-30"), 180),
new Person("Alan Bridges", DateTime.Parse("1990-03-22"), 190),
};
personListview.AddRange(people);
Controls.Add(personListview);
In the column mapping, you'll notice that you have to specify how to get the item's value (for sorting) as well as a string (for displaying).
Full source:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace GUI
{
public class ListViewEx<T> : ListView
{
public ListViewEx(IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> columnInfo) : base()
{
ColumnInfo = columnInfo;
DoubleBuffered = true;
//Create the columns
columnInfo
.Select(ci => ci.ColumnName)
.ToList()
.ForEach(columnName =>
{
var col = Columns.Add(columnName);
col.Width = -2;
});
//Add the sorter
lvwColumnSorter = new ListViewColumnSorter<T>(columnInfo);
ListViewItemSorter = lvwColumnSorter;
ColumnClick += ListViewEx_ColumnClick;
}
IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> ColumnInfo { get; }
private readonly ListViewColumnSorter<T> lvwColumnSorter;
public void Add(T item)
{
var lvi = Items.Add("");
lvi.Tag = item;
RefreshContent();
}
public void AddRange(IList<T> items)
{
foreach (var item in items)
{
Add(item);
}
}
public void Remove(T item)
{
if (item == null) return;
var listviewItem = Items
.Cast<ListViewItem>()
.Select(lvi => new
{
ListViewItem = lvi,
Obj = (T)lvi.Tag
})
.FirstOrDefault(lvi => item.Equals(lvi.Obj))
.ListViewItem;
Items.Remove(listviewItem);
RefreshContent();
}
public List<T> GetSelectedItems()
{
var result = SelectedItems
.OfType<ListViewItem>()
.Select(lvi => (T)lvi.Tag)
.ToList();
return result;
}
public void RefreshContent()
{
var columnsChanged = new List<int>();
Items
.Cast<ListViewItem>()
.Select(lvi => new
{
ListViewItem = lvi,
Obj = (T)lvi.Tag
})
.ToList()
.ForEach(lvi =>
{
//Update the contents of this ListViewItem
ColumnInfo
.Select((column, index) => new
{
Column = column,
Index = index
})
.ToList()
.ForEach(col =>
{
var newDisplayValue = col.Column.DisplayStringLookup(lvi.Obj);
if (lvi.ListViewItem.SubItems.Count <= col.Index)
{
lvi.ListViewItem.SubItems.Add("");
}
var subitem = lvi.ListViewItem.SubItems[col.Index];
var oldDisplayValue = subitem.Text ?? "";
if (!oldDisplayValue.Equals(newDisplayValue))
{
subitem.Text = newDisplayValue;
columnsChanged.Add(col.Index);
}
});
});
columnsChanged.ForEach(col => { Columns[col].Width = -2; });
//AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
//AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
}
private void ListViewEx_ColumnClick(object sender, ColumnClickEventArgs e)
{
if (e.Column == lvwColumnSorter.ColumnToSort)
{
if (lvwColumnSorter.SortOrder == SortOrder.Ascending)
{
lvwColumnSorter.SortOrder = SortOrder.Descending;
}
else
{
lvwColumnSorter.SortOrder = SortOrder.Ascending;
}
}
else
{
lvwColumnSorter.ColumnToSort = e.Column;
lvwColumnSorter.SortOrder = SortOrder.Ascending;
}
Sort();
}
}
public class ListViewColumnSorter<T> : IComparer
{
public ListViewColumnSorter(IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> columnInfo)
{
ColumnInfo = columnInfo;
}
public int Compare(object x, object y)
{
if (x == null || y == null) return 0;
int compareResult;
var listviewX = (ListViewItem)x;
var listviewY = (ListViewItem)y;
var objX = (T)listviewX.Tag;
var objY = (T)listviewY.Tag;
if (objX == null || objY == null) return 0;
var valueX = ColumnInfo[ColumnToSort].ValueLookup(objX);
var valueY = ColumnInfo[ColumnToSort].ValueLookup(objY);
compareResult = Comparer.Default.Compare(valueX, valueY);
if (SortOrder == SortOrder.Ascending)
{
return compareResult;
}
else if (SortOrder == SortOrder.Descending)
{
return -compareResult;
}
else
{
return 0;
}
}
public int ColumnToSort { get; set; } = 0;
public SortOrder SortOrder { get; set; } = SortOrder.Ascending;
public IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> ColumnInfo { get; }
}
}
Nice binding implementation for ListView
http://www.interact-sw.co.uk/utilities/bindablelistview/source/
Alternatively, you can use DataGridView if you want data binding. Using BindingList and BindingSource will update your DataGrid when new item is added to your list.
var barcodeContract = new BarcodeContract { Barcode = barcodeTxt.Text, Currency = currencyTxt.Text, Price = priceTxt.Text };
list.Add(barcodeContract);
var bindingList = new BindingList<BarcodeContract>(list);
var source = new BindingSource(bindingList, null);
dataGrid.DataSource = source;
And data model class
public class BarcodeContract
{
public string Barcode { get; set; }
public string Price { get; set; }
public string Currency { get; set; }
}
Adding to the answer by #Fidel
If you just want quick auto-mapped columns, add this code to the ListViewEx class:
using System.Reflection;
public ListViewEx() : this(AutoMapColumns()) { }
private static List<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> AutoMapColumns()
{
var mapping = new List<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)>();
var props = typeof(T).GetTypeInfo().GetProperties();
foreach (var prop in props)
{
mapping.Add((
prop.Name,
(T t) => prop.GetValue(t),
(T t) => prop.GetValue(t)?.ToString()
));
}
return mapping;
}
Alternative to Reflection
After some testing, I discovered that using Reflection like in the code above is much slower than direct property access.
In my test, I did 100,000,000 iterations of each. Reflection took 8.967 seconds, direct access took 0.465 seconds.
So I wrote this method to generate the code for the ListViewEx ColumnMapping.
// Given an object, generate columnMapping suitable for passing to the constructor of a ListViewEx control
// Usage: AutoMapColumns_CodeGen(new Person());
private static string AutoMapColumns_CodeGen<T>(T source)
{
var info = typeof(T).GetTypeInfo();
var props = info.GetProperties();
var columns = new List<string>();
foreach (var prop in props)
columns.Add($"\t(\"{prop.Name}\", o => o.{prop.Name}, o=> o.{prop.Name}?.ToString())");
string code = string.Join("\n",
$"var columnMapping = new List<(string ColumnName, Func<{info.Name}, object> ValueLookup, Func<{info.Name}, string> DisplayStringLookup)>() {{",
string.Join(",\n",columns),
"};"
);
return code;
}
Output
var columnMapping = new List<(string ColumnName, Func<Person, object> ValueLookup, Func<Person, string> DisplayStringLookup)>() {
("Name", o => o.Name, o=> o.Name?.ToString()),
("DateOfBirth", o => o.DateOfBirth, o=> o.DateOfBirth?.ToString()),
("HeightInCentimetres", o => o.HeightInCentimetres, o=> o.HeightInCentimetres?.ToString())
};

Categories

Resources