I want to add the customfields in add order api. How to create an array like that: "an array of base64 encoded serialized array of product custom field values"
https://developers.whmcs.com/api-reference/addorder/
I have tried with
Dictionary<string, string> customfiled = new Dictionary<string, string>();
customfiled.Add("1", "Hello");
var tt2 = JsonConvert.SerializeObject(customfiled);
string str =Base64Encode(tt2);
I have created a class that will hold the list of key/values serialized as JSON.
Here is the class:
class ArrayOfBase64Array
{
// serialized JSON
private List<string> list = new List<string>();
public void Add(NameValueCollection collection)
{
foreach(var key in collection.AllKeys)
{
this.Add(key, collection[key]);
}
}
public void Add<T>(string key, T value) {
var dict = new Dictionary<string,T>();
dict.Add(key, value);
var json = JsonConvert.SerializeObject(dict);
list.Add(Base64Encode(json));
}
public string[] ToArray()
{
return list.ToArray();
}
string Base64Encode(string value)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
}
}
The AddOrder payload expects a shape that matches this C# class:
// lots of fields omitted for brevity
class AddOrder
{
public string action {get;set;}
public string[] domain {get;set;}
public string[] customfields {get;set;}
}
To create an AddOrder instance with customfields you can now do this:
// create an AddOrder instance
var addOrder = new AddOrder
{
action = "AddOrder",
domain = new [] {"one.example.com", "two.example.com"}
};
// build our customfields collection
var cfarray = new ArrayOfBase64Array();
cfarray.Add("1", "FuBar");
cfarray.Add("2", 999);
cfarray.Add("3", true);
cfarray.Add("4", new JObject(new JProperty("Foo", 4711.42)));
// or if you have a namevaluecollection
var nvc = new NameValueCollection { {"5", "test namevalue 1"}, {"6", "another value"} };
cfarray.Add(nvc);
// store the customfields as an array of strings
addOrder.customfields = cfarray.ToArray();
This is what the data looks like when POST-ed to the server:
{ "action":"AddOrder",
"domain":["one.example.com","two.example.com"],
"customfields":[
"eyIxIjoiRnVCYXIifQ==",
"eyIyIjo5OTl9",
"eyIzIjp0cnVlfQ==",
"eyI0Ijp7IkZvbyI6NDcxMS40Mn19",
"eyI1IjoidGVzdCBuYW1ldmFsdWUgMSJ9",
"eyI2IjoiYW5vdGhlciB2YWx1ZSJ9"]
}
And this is what is in each of the encoded base64 strings:
{"1":"FuBar"}
{"2":999}
{"3":true}
{"4":{"Foo":4711.42}}
{"5":"test namevalue 1"}
{"6":"another value"}
Related
I'm having a huge performance issue about mapping string property names and string property values to classes using reflection.
My issue now:
public class Person
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public string Property4 { get; set; }
// My class has around 100 properties
public string Property100 { get; set; }
}
I am mapping a key value pair collection to the class using reflection
[{"Property1": "some value"}, {"Property2": "something else"},{"Property3","Property4","value" }.....{"Property100","val"}]
It got to the point that I am now mapping around 10 000 class instances using reflection and the performance is to say it lightly bad.
Any ideas for eliminating the reflection would be greatly appreciated.
I see two options, if you need to avoid reflection for tasks like this(when code could be programatically generated).
First is Expressions I use it often, e.g. I saw some people write something like this
public class A
{
public Prop1 ...
....
public Prop100
public override ToString() => $"{nameof(Prop1)}={Prop1};...";
and so for all 100 properties, and always doing this manually.
And with Expression it can be easily automated, you just need to generate Expression for String.Concat and pass list of properties and names there.
For your example, it is not clear what are your data. How do you do lookup in the list?
Let's assume there is a dictionary<string,string>(you can transform your list of tuples to a dictionary), and all properties are strings as well.
Then we would need to generate a list assignment expressions like this
if(data.ContainsKey("Prop1")) result.Prop1 = data["Prop1"];
And the code would be complicated, anyway it would look like this
private static class CompiledDelegate<T>
{
public static Action<T, Dictionary<string, string>> initObject;
static CompiledDelegate()
{
var i = Expression.Parameter(typeof(Dictionary<string, string>), "i");
var v = Expression.Parameter(typeof(T), "v");
var propertyInfos = typeof(T).GetProperties().ToArray();
var t = new Dictionary<string, string>();
var contains = typeof(Dictionary<string, string>).GetMethod(nameof(Dictionary<string, string>.ContainsKey));
var getter = typeof(Dictionary<string, string>).GetProperties().First(x => x.GetIndexParameters().Length > 0);
var result = new List<Expression>();
foreach (var propertyInfo in propertyInfos)
{
var cst = Expression.Constant(propertyInfo.Name);
var assignExpression =
Expression.IfThen(Expression.Call(i, contains, cst),
Expression.Assign(Expression.PropertyOrField(v, propertyInfo.Name), Expression.MakeIndex(i, getter, new[] { cst })));
result.Add(assignExpression);
}
var block = Expression.Block(result);
initObject = Expression.Lambda<Action<T, Dictionary<string, string>>>(block, new ParameterExpression[] { v, i }).Compile();
}
}
It is an example, it would fail if there were non-string properties.
And it could be used like this
static void Main(string[] args)
{
var tst = new Test();
CompiledDelegate<Test>.initObject(tst, new Dictionary<string, string>
{
{ "S3", "Value3" },
{ "S2", "Value2" },
});
CompiledDelegate<Test>.initObject(tst, new Dictionary<string, string>
{
{ "S3", "Value3" },
{ "S1", "Value1" },
});
Console.ReadKey();
}
The second option is, actually, what it should be ideally imlemented like Using source generators I think such things do have to be done just in build time.
There is a lot of articles on msdn, for instance with samples. But it turned out to be not very easy to implement, even just a sample.
I can say, it didn't work for me, while I tried to do it according to samples.
In order to get it work I had to change TargetFramework to netstandard2.0, do something else...
But after all, when build was green, Visual Studio still showed an error.
Ok, it disappeared after VS restart, but still, that doesn't look very usable.
So, this is a generator, that creates a converter for every class with attribute.
It is again a sample, it doesn't check many things.
[Generator]
public class ConverterGenerator : ISourceGenerator
{
private static string mytemplate = #"using System.Collections.Generic;
using {2};
namespace GeneratedConverters
{{
public static class {0}Converter
{{
public static {0} Convert(Dictionary<string, string> data)
{{
var result = new {0}();
{1}
return result;
}}
}}
}}";
public static string GetNamespaceFrom(SyntaxNode s)
{
if (s.Parent is NamespaceDeclarationSyntax namespaceDeclarationSyntax)
{
return namespaceDeclarationSyntax.Name.ToString();
}
if (s.Parent == null)
return "";
return GetNamespaceFrom(s.Parent);
}
public void Execute(GeneratorExecutionContext context)
{
GetMenuComponents(context, context.Compilation);
}
private static void GetMenuComponents(GeneratorExecutionContext context, Compilation compilation)
{
var allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
var allClasses = allNodes.Where(d => d.IsKind(SyntaxKind.ClassDeclaration)).OfType<ClassDeclarationSyntax>();
var classes = allClasses
.Where(c => c.AttributeLists.SelectMany(a => a.Attributes).Select(a => a.Name).Any(s => s.ToString().Contains("DictionaryConverter")))
.ToImmutableArray();
foreach (var item in classes.Distinct().Take(1))
{
context.AddSource(item.Identifier.Text + "Converter", String.Format(mytemplate, item.Identifier.Text, SourceText.From(GenerateProperties(item)), GetNamespaceFrom(item)));
}
}
private static string GenerateProperties(ClassDeclarationSyntax s)
{
var properties = s.Members.OfType<PropertyDeclarationSyntax>();
return String.Join(Environment.NewLine,
properties.Select(p =>
{
var name = p.Identifier.Text;
return $"if(data.ContainsKey(\"{name}\")) result.{name} = data[\"{name}\"];";
}));
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
and
static void Main(string[] args)
{
var t1 = GeneratedConverters.TestConverter.Convert(new Dictionary<string, string>
{
{ "S3", "Value3" },
{ "S2", "Value2" },
});
}
Best performance without reflection would be manual mapping.
It seems your key/value pair collection is regular JSON. So you could use the JSONTextReader from JSON.NET and read the string. Then manually map the JSON properties to the class properties.
Like so:
JsonTextReader reader = new JsonTextReader(new StringReader(jsonString));
while (reader.Read())
{
if (reader.Value != null)
{
// check reader.Value.ToString() and assign to correct class property
}
}
More info can be found on the JSON.NET website : https://www.newtonsoft.com/json/help/html/ReadingWritingJSON.htm
I have an application where I have a class with 100 property values
For example.
Class RequestData{
String PropertName1 {get;set;}
String PropertName2 {get;set;}
String PropertName3 {get;set;}
.
.
.
String PropertName100 {get;set;}
}
I have a List> and this has all the propertyname and propertyvalues. So, ideally this has
PropertName1 = "Timothy"
PropertName2 = "rajan"
.
.
.
PropertName100 = "alex"
I need to create an instance of RequestData and assign all the property values to each of the property names
In this scenario, I need to do like this which will result in 100s of lines
RequestData requestData = new RequestData ();
requestData.PropertName1 = "Timothy" ;
requestData.PropertName2 = "Rajan" ;
requestData.PropertName3 = "Alex" ;
Is there a better way of doing this? I can loop into the List but not sure how to smartly do it something like
RequestData requestData = new RequestData ();
requestData.[key]= value ;
I hope I made it clear. Any help would be great.
You can do this with reflection
_list = new List<Tuple<string, string>>
{
new Tuple<string, string>("PropertName1", "asd"),
new Tuple<string, string>("PropertName2", "sdfgds"),
new Tuple<string, string>("PropertName3", "dfgdfg"),
new Tuple<string, string>("PropertName100", "dfgdfg")
};
var requestData = new RequestData();
foreach (var tuple in _list)
{
requestData.GetType()
.GetProperty(tuple.Item1, BindingFlags.Public| BindingFlags.NonPublic | BindingFlags.Instance )
?.SetValue(requestData, tuple.Item2);
}
Or an extension method
public void UpdateProperties<T>(this T obj, List<Tuple<string, string>> list) where T : class
{
foreach (var tuple in _list)
{
var type = obj.GetType();
var property = type.GetProperty(tuple.Item1, BindingFlags.Public| BindingFlags.NonPublic | BindingFlags.Instance );
if(property == null)
continue;
property.SetValue(obj, tuple.Item2);
}
}
Full Demo here
Additional Resources
Type.GetProperty Method (String)
Searches for the public property with the specified name.
PropertyInfo.SetValue Method (Object, Object)
Sets the property value of a specified object.
BindingFlags Enumeration
Specifies flags that control binding and the way in which the search
for members and types is conducted by reflection.
Yes, you can do it. Without more specifics though it's very difficult to give a "good" response. Based on what you've shown (and making a probably bad assumption on my part) here is one of several different possible ways to do so. I don't really recommend this personally, but perhaps it (with other answers/comments) will start you down the path that you actually want/need to go.
I should mention that there are more efficient methods for this, but wanted to show a rudimentary method that may be a bit more apparent as to what is actually happening.
static void Main()
{
var propertyList = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("PropertyName1","Value1"),
new KeyValuePair<string, string>("PropertyName2","Value2"),
new KeyValuePair<string, string>("PropertyName3","Value3"),
new KeyValuePair<string, string>("PropertyName4","Value4"),
new KeyValuePair<string, string>("PropertyName5","Value5"),
};
var requestData = new RequestData();
//reflection allows you to loop through the properties of a class
foreach (var property in requestData.GetType().GetProperties())
{
//you can loop through your list (here I made an assumption that it's a list of KeyValuePair)
foreach (var keyValuePair in propertyList)
{
//if the key matches the property name, assign the value
if (property.Name.Equals(keyValuePair.Key))
{
property.SetValue(requestData, keyValuePair.Value);
}
}
}
//to show that they are properly set
Console.WriteLine(requestData.PropertyName1);
Console.WriteLine(requestData.PropertyName2);
Console.WriteLine(requestData.PropertyName3);
Console.WriteLine(requestData.PropertyName4);
}
}
public class RequestData
{
public string PropertyName1 { get; set; }
public string PropertyName2 { get; set; }
public string PropertyName3 { get; set; }
public string PropertyName4 { get; set; }
}
Throwing another similar answer into the ring, this one using a list of strings and splitting them on the = sign, then using reflection to try to get the property name, and if that's not null, set the property value of our object:
private static void Main()
{
var propNamesAndValues = new List<string>
{
"PropertName1 = Timothy",
"PropertName2 = Rajan",
"PropertName100 = Alex",
};
var requestData = new RequestData();
foreach (var propNameValue in propNamesAndValues)
{
var parts = propNameValue.Split('=');
if (parts.Length < 2) continue;
var propName = parts[0].Trim();
var propValue = parts[1].Trim();
typeof(RequestData).GetProperty(propName)?.SetValue(requestData, propValue);
}
Console.WriteLine("{0} {1} {2}", requestData.PropertName1,
requestData.PropertName2, requestData.PropertName100);
GetKeyFromUser("\nDone! Press any key to exit...");
}
Output
I am currently creating a class library for an API. Here is my current issue:
The API has support for multi-call functionality in which I can get data for multiple submissions (i.e. a Skin, Model, Script, etc.) all at the same time in the form of an array holding an object for each of the submissions. The link I will be providing is completely for testing purposes as it returns the same submission three times and can be found here.
The goal is to grab the raw JSON data for each object and put it into a string array so that I can go through each of the objects and deserialize them individually. The reason for this is that the api could return any kind of object (as specified above) and this system must adjust to each object that goes through it. Here's the current code:
Program.cs
class Program
{
static WebClient web = new WebClient();
static void Main(string[] args)
{
APICaller ApiManager = new APICaller(web);
Model exampleObject = new Model(3962);
Model exampleObject2 = new Model(3962);
Model exampleObject3 = new Model(3962);
List<Model> SubmissionInstances = new List<Model>() { exampleObject, exampleObject2, exampleObject3 };
int[] IDs = new int[] { exampleObject.itemID, exampleObject2.itemID, exampleObject3.itemID };
string[] fields = new string[] { exampleObject.fields, exampleObject2.fields, exampleObject3.fields };
ApiManager.Data(SubmissionInstances, IDs, fields);
}
}
APICaller.cs
public List<object> Data<T>(List<T> type, int[] itemid, string[] fields)
{
//Same as the first function but supports multi-call
string itemTypes = "";
string itemIDs = "";
string itemFields = "";
string finalURL = "http://api.gamebanana.com/Core/Item/Data?";
int i = 0;
foreach (object obj in type)
{
itemTypes += "&itemtype[]=" + obj.GetType().Name;
types[i] = obj.GetType();
i++;
}
foreach (int ID in itemid)
{
itemIDs += "&itemid[]=" + ID;
}
foreach (string field in fields)
{
itemFields += "&fields[]=" + field;
}
finalURL += itemTypes + itemIDs + itemFields + "&return_object=1";
string[] JSONObjects = JsonConvert.DeserializeObject<string[]>(client.DownloadString(finalURL));
List<object> toReturn = new List<object>();
i = 0;
foreach (string str in JSONObjects)
{
toReturn.Add(DeserializeObject(type[i], str));
i++;
}
return toReturn;
}
public T DeserializeObject<T> (T obj, dynamic JSONObj)
{
return JsonConvert.DeserializeObject<T>(JSONObj);
}
I would like to deserialize JSON data which has three keys as follows : Type, Name, Data[]
My Message class which I want my JSON to be serialized into :
class Message
{
public int Type;
public string Name;
public List<KeyValuePair<string, string>> Data;
}
I am able to deserialize Type and Name with this block of code;
Message DeserializedMessage = JsonConvert.DeserializeObject<Message>(myString);
Console.WriteLine("Name: " + DeserializedMessage.Name);
Console.WriteLine("Type: " + DeserializedMessage.Type);
However, I don't have any clue to how to deserialize the key-value pairs in JSON to my List<KeyValuePair<string, string>> Data;
Example JSONs :
{
"Type":"103",
"Name":"Oguzhan",
"Data":
[
{
"InviteTo":"Bahadir"
}
]
}
{
"Type":"104",
"Name":"Oguzhan",
"Data":
[
{
"Game":"Backgammon",
"Duration":"2"
}
]
}
You should change your
public List<KeyValuePair<string, string>> data;
to
public List<Dictionary<string, string>> data;
After that, it's relatively easy:
Message message = JsonConvert.DeserializeObject<Message>(jsonString);
Or, if you would like to use dynamic:
dynamic parsed = JsonConvert.DeserializeObject(jsonString);
Message message = MapToMessage(parsed);
// ...
private Message MapToMessage(dynamic json)
{
return new Message()
{
Type = json.Type,
Name = json.Name,
Data = ((IEnumerable<dynamic>)json.Data).Select(d =>
{
var dic = new Dictionary<string, string>();
foreach (var v in d) dic.Add(v.Name, v.Value.Value);
return dic;
}).ToList()
};
}
I am trying to automap a dictionary to another without replacing existing values with null but just update them with the new ones if necessary. However i can't get it to work anyway.
Code :
class Klass
{
public Dictionary<string, string[]> Dictionary = new Dictionary<string, string[]>();
}
class Main
{
private Klass ex1 = new Klass();
public Main()
{
Mapper.CreateMap<Klass, Klass>().ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
TestRun("1");
TestRun("2");
TestRun("3");
//EVERY CALL REPLACES THE OTHER HERE IDK WHY [OTHER MAPPER VALUES I MEAN]
}
public void TestRun(string text)
{
var ex2 = new Klass();
ex2.Dictionary = new Dictionary<string, string[]>
{
{text, new []{"1", "2... etc as provided"}}
};
Mapper.Map(ex2, ex1);
}
}