Generic Object to '|'-separated string serialization - reflection

I have written this class-methods for .net 2.0 to create objects from '|'-separated strings and vise-versa.
But the problem is, they are not giving right results in case of Inherted types, i.e. inherited properties are coming last and the sequence of the data supplied in the form of a '|'-separated string is not working.
For example:
class A
{
int ID;
}
class B : A
{
string Name;
}
the string is "1|John". the methods are reading as the name==1 and ID=="John".
Please tell me how to do it.
public class ObjectConverter<T>
{
public static T TextToObject(string text)
{
T obj = Activator.CreateInstance<T>();
string[] data = text.Split('|');
PropertyInfo[] props = typeof(T).GetProperties();
int objectPropertiesLength = props.Length;
int i = 0;
if (data.Length == objectPropertiesLength)
{
for (i = 0; i < objectPropertiesLength; i++)
{
props[i].SetValue(obj, data[i], null);
}
}
return obj;
}
public static string ObjectToText(T obj)
{
StringBuilder sb = new StringBuilder();
Type t = typeof(T);
PropertyInfo[] props = t.GetProperties();
int i = 0;
foreach (PropertyInfo pi in props)
{
object obj2 = props[i++].GetValue(obj, null);
sb.Append(obj2.ToString() + "|");
}
sb = sb.Remove(sb.Length - 1, 1);
return sb.ToString();
}
}

I don't think the runtime assures you that when you call getproperties the property info objects will always be in the same order. You will need to do something like get the list of property names sort them and use the same sorting for serialization and deserialization .
There are at least 3 ways to serialize object built into .net is there a reason why you are not using one of those

Related

Xunit CSV streamReader.ReadToEnd returns System.ArgumentOutOfRangeException

I would like to evaluate a CSV data series with Xunit.
For this I need to read in a string consisting of int, bool, double and others.
With the following code, the transfer basically works for one row.
But since I want to test for predecessor values, I need a whole CSV file for evaluation.
My [Theory] works with InlineData without errors.
But when I read in a CSV file, the CSVDataHandler gives a System.ArgumentOutOfRangeException!
I can't find a solution for the error and ask for support.
Thanks a lot!
[Theory, CSVDataHandler(false, "C:\\MyTestData.txt", Skip = "")]
public void TestData(int[] newLine, int[] GetInt, bool[] GetBool)
{
for (int i = 0; i < newLine.Length; i++)
{
output.WriteLine("newLine {0}", newLine[i]);
output.WriteLine("GetInt {0}", GetInt[i]);
output.WriteLine("GetBool {0}", GetBool[i]);
}
}
[DataDiscoverer("Xunit.Sdk.DataDiscoverer", "xunit.core")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class DataArribute : Attribute
{
public abstract IEnumerable<object> GetData(MethodInfo methodInfo);
public virtual string? Skip { get; set; }
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class CSVDataHandler : DataAttribute
{
public CSVDataHandler(bool hasHeaders, string pathCSV)
{
this.hasHeaders = hasHeaders;
this.pathCSV = pathCSV;
}
public override IEnumerable<object[]> GetData(MethodInfo methodInfo)
{
var methodParameters = methodInfo.GetParameters();
var paramterTypes = methodParameters.Select(p => p.ParameterType).ToArray();
using (var streamReader = new StreamReader(pathCSV))
{
if (hasHeaders) { streamReader.ReadLine(); }
string csvLine = string.Empty;
// ReadLine ++
//while ((csvLine = streamReader.ReadLine()) != null)
//{
// var csvRow = csvLine.Split(',');
// yield return ConvertCsv((object[])csvRow, paramterTypes);
//}
// ReadToEnd ??
while ((csvLine = streamReader.ReadToEnd()) != null)
{
if (Environment.NewLine != null)
{
var csvRow = csvLine.Split(',');
yield return ConvertCsv((object[])csvRow, paramterTypes); // System.ArgumentOutOfRangeException
}
}
}
}
private static object[] ConvertCsv(IReadOnlyList<object> cswRow, IReadOnlyList<Type> parameterTypes)
{
var convertedObject = new object[parameterTypes.Count];
for (int i = 0; i < parameterTypes.Count; i++)
{
convertedObject[i] = (parameterTypes[i] == typeof(int)) ? Convert.ToInt32(cswRow[i]) : cswRow[i]; // System.ArgumentOutOfRangeException
convertedObject[i] = (parameterTypes[i] == typeof(double)) ? Convert.ToDouble(cswRow[i]) : cswRow[i];
convertedObject[i] = (parameterTypes[i] == typeof(bool)) ? Convert.ToBoolean(cswRow[i]) : cswRow[i];
}
return convertedObject;
}
}
MyTestData.txt
1,2,true,
2,3,false,
3,10,true,
The first call to streamReader.ReadToEnd() will return the entire contents of the file in a string, not just one line. When you call csvLine.Split(',') you will get an array of 12 elements.
The second call to streamReader.ReadToEnd() will not return null as your while statement appears to expect, but an empty string. See the docu at
https://learn.microsoft.com/en-us/dotnet/api/system.io.streamreader.readtoend?view=net-7.0
If the current position is at the end of the stream, returns an empty
string ("").
With the empty string, the call to call csvLine.Split(',') will return an array of length 0, which causes your exception when its first element (index 0) is accessed.
All of this could have been easily discovered by simply starting the test in a debugger.
It looks like you have some other issues here as well.
I don't understand what your if (Environment.NewLine != null) is intended to do, the NewLine property will never be null but should have one of the values "\r\n" or "\n" so the if will always be taken.
The parameters of your test method are arrays int[] and bool[], but you are checking against the types int, double and bool in your ConvertCsv method, so the alternative cswRow[i] will always be returned. You'll wind up passing strings to your method expecting int[] and bool[] and will at latest get an error there.
This method reads a data series from several rows and columns and returns it as an array for testing purposes.
The conversion of the columns can be adjusted according to existing pattern.
Thanks to Christopher!
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class CSVDataHandler : Xunit.Sdk.DataAttribute
{
public CSVDataHandler(string pathCSV)
{
this.pathCSV = pathCSV;
}
public override IEnumerable<object[]> GetData(MethodInfo methodInfo)
{
List<int> newLine = new();
List<int> GetInt = new();
List<bool> GetBool = new();
var reader = new StreamReader(pathCSV);
string readData = string.Empty;
while ((readData = reader.ReadLine()) != null)
{
string[] split = readData.Split(new char[] { ',' });
newLine.Add(int.Parse(split[0]));
GetInt.Add(int.Parse(split[1]));
GetBool.Add(bool.Parse(split[2]));
// Add more objects ...
}
yield return new object[] { newLine.ToArray(), GetInt.ToArray(), GetBool.ToArray() };
}
}

How to use GUID as Primary Key with System.Data.Sqlite

I'm not saying it's advisable, but sometimes you inherit something which just needs to work. In this case it's Guids for Primary Keys...
Out of the box, you'll get an error about System.Guid not working with byte[] (or String if you're using BinaryGUID=False).
To fix this, you need to intercept byte[] arrays (or strings) and instead return Guid type. This is possible with the System.Data.Sqlite provider.
First
using System.Data.SQLite;
Then, put this in the constructor of your code-first db context:
var con = (SQLiteConnection)base.Database.Connection;
var bind = System.Data.SQLite.SQLiteTypeCallbacks.Create(
null,
new SQLiteReadValueCallback(GuidInterceptor), null, null);
con.SetTypeCallbacks("uniqueidentifier", bind);
con.SetTypeCallbacks("", bind); //Sometimes, the system just doesn't know
con.Flags |= SQLiteConnectionFlags.UseConnectionReadValueCallbacks;
And then this is the magic function:
private void GuidInterceptor(SQLiteConvert convert, SQLiteDataReader reader, SQLiteConnectionFlags flags, SQLiteReadEventArgs args, string typename, int index, object userdata, out bool complete)
{
complete = false;
if (typename == "uniqueidentifier")
{
var e = (SQLiteReadValueEventArgs)args;
var o = reader.GetGuid(index);
e.Value.Value = o;
e.Value.GuidValue = o;
complete = true;
}
else
{
var o = reader.GetValue(index);
if (o is byte[])
{
var b = (byte[])o;
if (b.Length == 16)
{
var e = (SQLiteReadValueEventArgs)args;
var g = new Guid(b);
e.Value.Value = g;
e.Value.GuidValue = g;
complete = true;
}
}
else if (o is string)
{
var s = (string)o;
if (s.Length == 36)
{
var e = (SQLiteReadValueEventArgs)args;
var goGuid = (e.MethodName == "GetGuid");
if (!goGuid)
goGuid = (s[8] == '-' && s[13] == '-' && s[18] == '-' && s[23] == '-');
Guid g;
if (goGuid && Guid.TryParse(s, out g))
{
e.Value.Value = g;
e.Value.GuidValue = g;
complete = true;
}
else
{
}
}
}
}
}
That only fixes reading in of data. If you try to .Where() on a Guid member, and you're using BinaryGUID=True no rows will be returned (when there should be). For now, you'll need BinaryGUID=False, which does take up more space, but it's a simple solution.
If you can help it, try and define your Guid properties instead as:
[Key]
[MaxLength(16)]
[MinLength(16)]
public byte[] AccountUserId { get; set; }
You'll be forced to call guidValue.ToByteArray() in your application layer. You might be able to create some helpers to you're not having to call that function. Having your own custom function proxy for creating and parsing Guids may help, so you can easily change the implementation.
In my case, I don't have the luxury of time to convert all the Guid properties and usages to byte[]

How to setCellValueFactory return type for ArrayList

I have following error in this code: Cannot infer type arguments for ReadOnlyListWrapper<>
How should my return type look like? I need to save arraylist for each node in all columns. But I can not return it.
for (Entry<String, String> ent : dc.getSortedOrgAll().entrySet()) {
TreeTableColumn<String, ArrayList<String>> col = new TreeTableColumn<>(
ent.getValue());
col.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<String, ArrayList<String>>, ObservableValue<ArrayList<String>>>() {
#Override
public ObservableValue<ArrayList<String>> call(
CellDataFeatures<String, ArrayList<String>> param) {
TreeMap<String, List<String>> temp = (TreeMap<String, List<String>>) dc
.getFuncTypeOrg().clone();
ArrayList<String> result = new ArrayList<>();
for (int i = 0; i < temp.size(); i++) {
List<String> list = temp.firstEntry().getValue();
String key = temp.firstEntry().getKey();
// root.getChildren();
if (list.get(1).equals("Papier")) {
System.out.println(list.get(1));
}
if (list.get(1).equals(param.getValue().getValue())
&& list.get(5).equals(col.getText())) {
result.add(list.get(2));
if(list.size()==9)
result.add(list.get(list.size()-1));
else result.add("White");
} else {
temp.remove(key);
// result = null;
}
}
return new ReadOnlyListWrapper<>(result);
}
});
ReadOnlyListWrapper<T> implements ObservableValue<ObservableList<T>>, which isn't what you need, as you declared the callback to return an ObservableValue<ArrayList<T>> (T is just String here).
So I think you just need
return new ReadOnlyObjectWrapper<ArrayList<String>>(result);
and you can probably omit the generic type:
return new ReadOnlyObjectWrapper<>(result);
Just a comment: I think you could make your life much easier by defining some actual data model classes, instead of trying to force your data into various collections implementations.

asp.net MVC3 custom helper not working

I have created the following code to render a table from below array
var fruit = new string[] { "apple", "pear", "tomato" };
public static MvcHtmlString CustomGrid(this HtmlHelper htmlHelper, String Id, IList Items, IDictionary<string, string> Attributes)
{
if (Items == null || Items.Count == 0 || string.IsNullOrEmpty(Id))
return MvcHtmlString.Empty;
return BuildGrid(Items, Id, Attributes);
}
public static MvcHtmlString BuildGrid(IList Items, string Id, IDictionary<string, string> attributes)
{
StringBuilder sb = new StringBuilder();
BuildHeader(sb, Items[0].GetType());
foreach (var item in Items)
{
BuildTableRow(sb, item);
}
TagBuilder builder = new TagBuilder("table");
builder.MergeAttributes(attributes);
builder.MergeAttribute("name", Id);
builder.InnerHtml = sb.ToString();
var Tag = builder.ToString(TagRenderMode.Normal);
return MvcHtmlString.Create(Tag);
}
public static void BuildTableRow(StringBuilder sb, object obj)
{
Type objType = obj.GetType();
sb.AppendLine("\t<tr>");
foreach (var property in objType.GetProperties())
{
sb.AppendFormat("\t\t<td>{0}</td>\n", property.GetValue(obj, null));
// sb.AppendFormat("\t\t<td>{0}</td>\n", obj);
}
sb.AppendLine("\t</tr>");
}
public static void BuildHeader(StringBuilder row, Type p)
{
row.AppendLine("\t<tr>");
foreach (var property in p.GetProperties())
{
row.AppendFormat("\t\t<th>{0}</th>\n", p.Name);
}
row.AppendLine("\t</tr>");
}
but it doesn't render any thing. I am using it like this:
Html.CustomGrid("myTable", (System.Collections.IList)fruit, null);
Please suggest solution to it.
First put in some debugging statements to make sure anything is coming from the output. If its not, you are likely not using is like #Html.CustomGrid and instead using it in a code block where the output is not rendered to the response stream.
You're missing the # from
Html.CustomGrid("myTable", (System.Collections.IList)fruit, null);

How can I get parameters values from a lamba expression for my nifty cache extension?

First of all it might be worth looking at this question:
How can I cache objects in ASP.NET MVC?
There some pseudo code that almost does what i want:
public class CacheExtensions
{
public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator)
{
var result = cache[key];
if(result == null)
{
result = generator();
cache[key] = result;
}
return (T)result;
}
}
However, what I'd really like to do, is auto-generate the "key" from the generator. I figure i need to change the method signature to:
public static T GetOrStore<T>(this Cache cache,
System.Linq.Expressions.Expression<Func<T>> generator)
I want to use the method name, but also any parameters and their values to generate the key. I can get the method body from the expression, and the paramter names (sort of), but I have no idea how to get the paramter values...?
Or am I going about this the wrong way? Any ideas much appreciated.
Here's how I did it:
public static class ICacheExtensions
{
public static T GetOrAdd<T>(this ICache cache, Expression<Func<T>> getterExp)
{
var key = BuildCacheKey<T>(getterExp);
return cache.GetOrAdd(key, () => getterExp.Compile().Invoke());
}
private static string BuildCacheKey<T>(Expression<Func<T>> getterExp)
{
var body = getterExp.Body;
var methodCall = body as MethodCallExpression;
if (methodCall == null)
{
throw new NotSupportedException("The getterExp must be a MethodCallExpression");
}
var typeName = methodCall.Method.DeclaringType.FullName;
var methodName = methodCall.Method.Name;
var arguments = methodCall.Arguments
.Select(a => ExpressionHelper.Evaluate(a))
.ToArray();
return String.Format("{0}_{1}_{2}",
typeName,
methodName,
String.Join("|", arguments));
}
}
with this helper to evaluate nodes of an expression tree:
internal static class ExpressionHelper
{
public static object Evaluate(Expression e)
{
Type type = e.Type;
if (e.NodeType == ExpressionType.Convert)
{
var u = (UnaryExpression)e;
if (TypeHelper.GetNonNullableType(u.Operand.Type) == TypeHelper.GetNonNullableType(type))
{
e = ((UnaryExpression)e).Operand;
}
}
if (e.NodeType == ExpressionType.Constant)
{
if (e.Type == type)
{
return ((ConstantExpression)e).Value;
}
else if (TypeHelper.GetNonNullableType(e.Type) == TypeHelper.GetNonNullableType(type))
{
return ((ConstantExpression)e).Value;
}
}
var me = e as MemberExpression;
if (me != null)
{
var ce = me.Expression as ConstantExpression;
if (ce != null)
{
return me.Member.GetValue(ce.Value);
}
}
if (type.IsValueType)
{
e = Expression.Convert(e, typeof(object));
}
Expression<Func<object>> lambda = Expression.Lambda<Func<object>>(e);
Func<object> fn = lambda.Compile();
return fn();
}
}
When calling a function that produces a collection i want to cache i pass all my function's parameters and function name to the cache function which creates a key from it.
All my classes implement an interface that has and ID field so i can use it in my cache keys.
I'm sure there's a nicer way but somehow i gotta sleep at times too.
I also pass 1 or more keywords that i can use to invalidate related collections.

Resources