Mapping Error In Linq to Lucene.Net - c#

I have the following Document Specs for Use in Linq TO Lucene
public class Doc1
{
public Doc1()
{
Docs = new List<Doc2>();
}
private IList<Doc2> Docs{get;set;}
}
public Class Doc2
{
//whatever
}
The Problem is when I try to Use it like this
var provider = new LuceneDataProvider(directory, Version.LUCENE_30);
using (var session = provider.OpenSession<Doc1>())
{
var m = new Doc1();
session.Add(m);
}
The Problem is when I execute this code, I get the following error
Property Event of type System.Collections.Generic.IList`1[Doc1] cannot be converted from System.String
Please is there another way of doing this
Thanks in advance
Here goes the Full Stack Trace
[NotSupportedException: Property Event of type System.Collections.Generic.IList`1[Doc2] cannot be converted from System.String]
Lucene.Net.Linq.Mapping.FieldMappingInfoBuilder.GetConverter(PropertyInfo p, Type type, FieldAttribute metadata) +487
Lucene.Net.Linq.Mapping.FieldMappingInfoBuilder.BuildPrimitive(PropertyInfo p, Type type, FieldAttribute metadata, Version version, Analyzer externalAnalyzer) +63
Lucene.Net.Linq.Mapping.FieldMappingInfoBuilder.Build(PropertyInfo p, Version version, Analyzer externalAnalyzer) +364
Lucene.Net.Linq.Mapping.ReflectionDocumentMapper`1.BuildFieldMap(IEnumerable`1 props) +184
Lucene.Net.Linq.Mapping.ReflectionDocumentMapper`1..ctor(Version version, Analyzer externalAnalyzer) +77
Lucene.Net.Linq.LuceneDataProvider.OpenSession(ObjectLookup`1 lookup) +67
Lucene.Net.Linq.LuceneDataProvider.OpenSession(ObjectFactory`1 factory) +142
Lucene.Net.Linq.LuceneDataProvider.OpenSession() +114

Related

Inject method in C# assembly with Mono.Cecil

I'm developing some .Net application and I need to inject in any assembly new method with my own code. I'm using Mono.Cecil to get body of assembly and I found some samples, but they're old enough. Unfortunately, there's no info in migraton section on github wiki.
So, I have this code:
using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace CustomFieldsInjection
{
public partial class Injector
{
public static void MethodInjection(string assemblyFilename, string typeName, string methodName)
{
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(assemblyFilename);
TypeReference returnTypeReference = assembly.MainModule.Import(typeof(void));
MethodDefinition methodDefinition = new MethodDefinition(methodName, MethodAttributes.Public | MethodAttributes.Static, returnTypeReference);
Instruction instruction1 = methodDefinition.Body.CilWorker.Create(OpCodes.Nop);
Instruction instruction2 = methodDefinition.Body.CilWorker.Create(OpCodes.Ldstr, methodName);
MethodReference writeline = assembly.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
methodDefinition.Body.CilWorker.Append(instruction1);
methodDefinition.Body.CilWorker.Append(instruction2);
methodDefinition.Body.CilWorker.InsertAfter(instruction2, methodDefinition.Body.CilWorker.Create (OpCodes.Call, writeline));
methodDefinition.Body.CilWorker.Append (methodDefinition.Body.CilWorker.Create(OpCodes.Ret))
assembly.MainModule.Inject(methodDefinition, assembly.MainModule.Types[typeName]);
MethodReference methodReference = null;
foreach (MethodDefinition method in assembly.MainModule.Types[typeName].Methods)
{
if (method.Name == methodName)
{
methodReference = assembly.MainModule.Import(method);
break;
}
}
Instruction callTest = methodDefinition.Body.CilWorker.Create(OpCodes.Call, methodReference);
if (assembly.EntryPoint != null)
{
assembly.EntryPoint.Body.CilWorker.InsertBefore(assembly.EntryPoint.Body.Instructions[0], callTest);
}
AssemblyFactory.SaveAssembly(assembly, assemblyFilename);
}
}
}
It's old sample. Most features are up to date. I'm interesting in this construction:
assembly.MainModule.Inject(methodDefinition, assembly.MainModule.Types[typeName]);
I could not find a new analogues of this design. Someone can tell me what it can be replaced?
I'm not familiar with the construct you are referring to, but adding a MethodDefinition to an existing type is quite easy
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly("assemblyPath")) {
var module = AssemblyDefinition.MainModule;
//Select the type you need to open for addition
var typeDef = module.Types.First(td => td.Name == "footer");
//Add your MethodDefinition
typeDef.Methods.Add(your_method_definition);
//Write the assembly back
assemblyDefinition.Write();
}
NOTE: If you don't use yet cecil 0.10.0.0 you'll use slightly different ReadAssembly() and Write() variants (without the using, and passing the assemblyPath to Write, mainly...)

SOAP exception in WPF Application while accessing Web Service

I am learning C#, and in one of the assignment i have to display the data from an Access file into a DataGrid upon button click using a WPF Application, and a web reference.
There is a SOAP Exception which i am not able to resolve, any help is appreciated, thanks in advance -
Web reference asmx file contains the below method, that is called upon button click -
[WebMethod]
public ArrayList queryDB()
{
string selectStmt = "select * from Students";
OleDbConnection conn_obj = new DBConnect().checkDBStatus(databaseLocation);
conn_obj.Open();
OleDbCommand query = new OleDbCommand(selectStmt, conn_obj);
OleDbDataReader dbReader = query.ExecuteReader();
ArrayList dbRecordList = new ArrayList();
foreach (DbDataRecord dbRecord in dbReader)
{
dbRecordList.Add(dbRecord);
}
conn_obj.Close();
return dbRecordList;
}
WPF file code is as below - "Button click method"
private void GetData_Click(object sender, RoutedEventArgs e)
{
ArrayList datagridList = (ArrayList) websrvc.queryDB();
dataGrid.ItemsSource = datagridList;
}
The above code is creating an error - Cannot implicitly convert type 'object[]' to 'System.Collections.ArrayList'
Hence based on a suggestion over web, changed the code as below -
private void GetData_Click(object sender, RoutedEventArgs e)
{
object obj = websrvc.queryDB();
ArrayList datagridList = (ArrayList) obj;
dataGrid.ItemsSource = datagridList;
}
Now, there seems to be no errors, but an exception at the below line of code, which i am not able to solve -
object obj = websrvc.queryDB();
{"System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException: System.Data.Common.DataRecordInternal cannot be serialized because it does not have a parameterless constructor.\n at System.Xml.Serialization.TypeDesc.CheckSupported()\n at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError)\n at System.Xml.Serialization.XmlSerializationWriter.CreateUnknownTypeException(Type type)\n at System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(String name, String ns, Object o, Boolean xsiType)\n at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write1_Object(String n, String ns, Object o, Boolean isNullable, Boolean needType)\n at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write2_queryDBResponse(Object[] p)\n at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer1.Serialize(Object objectToSerialize, XmlSerializationWriter writer)\n at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)\n --- End of inner exception stack trace ---\n at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)\n at System.Web.Services.Protocols.SoapServerProtocol.WriteReturns(Object[] returnValues, Stream outputStream)\n at System.Web.Services.Protocols.WebServiceHandler.WriteReturns(Object[] returnValues)\n at System.Web.Services.Protocols.WebServiceHandler.Invoke()\n --- End of inner exception stack trace ---"}
Create a class on web service side named Student with all the properties that you want to send to client.
Mark this class as serializable.
Map the DB object properties one by one to this Student class.
From the service, instead of returning ArrayList, return ArrayList();
Let me know if this works.

How to pass parameters for a managed code dll in installshield?

I have a dotnet script which is for encryption and decryption. I have to pass the parameters for the setvalues function in installshield. How can I achieve this? Dotnet code is as follows. I have the assembly (.dll) file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Security;
using System.Xml;
using System.Collections.Specialized;
namespace EncryptionDecryption
{
public class EncrptionHelper
{
#region Member variables
static byte[] entropy = { 0, 8, 2, 3, 5 };
#endregion
#region Public Methods
public static void SetValue(string configFilePathName, string appSettingKey, string appSettingValue)
{
appSettingValue = EncryptString(ToSecureString(appSettingValue));
SetSetting(appSettingKey, appSettingValue, configFilePathName);
}
public static string GetValue(string configFilePathName, string appSettingKey)
{
string value = GetSetting(appSettingKey, configFilePathName);
value = ToInsecureString( DecryptString(value));
return value;
}
#endregion
#region Private Methods
private static bool SetSetting(string Key, string Value, string configFilePath)
{
bool result = false;
try
{
// System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(configFilePath);
// config.AppSettings.File = configFilePath;
// config.AppSettings.Settings[Key].Value = Value;
// config.Save(ConfigurationSaveMode.Modified);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(configFilePath);
xmlDoc.SelectSingleNode("//appSettings/add[#key='" + Key +"']").Attributes["value"].Value = Value;
xmlDoc.Save(configFilePath);
ConfigurationManager.RefreshSection("appSettings");
result = true;
}
finally
{ }
return result;
}
private static string GetSetting(string Key, string configFilePath)
{
string result = null;
try
{
XmlDocument appSettingsDoc = new XmlDocument();
appSettingsDoc.Load(configFilePath);
XmlNode node = appSettingsDoc.SelectSingleNode("//appSettings");
XmlElement value = (XmlElement)node.SelectSingleNode(string.Format("//add[#key='" + Key + "']"));
result = (value.GetAttribute("value").ToString());
}
finally
{ }
return result;
}
private static SecureString ToSecureString(string input)
{
SecureString secure = new SecureString();
foreach (char c in input)
{
secure.AppendChar(c);
}
secure.MakeReadOnly();
return secure;
}
private static string ToInsecureString(SecureString input)
{
string returnValue = string.Empty;
IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
try
{
returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
}
finally
{
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
}
return returnValue;
}
private static string EncryptString(System.Security.SecureString input)
{
byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(
System.Text.Encoding.Unicode.GetBytes(ToInsecureString(input)), entropy, System.Security.Cryptography.DataProtectionScope.CurrentUser);
return Convert.ToBase64String(encryptedData);
}
private static SecureString DecryptString(string encryptedData)
{
try
{
byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect(
Convert.FromBase64String(encryptedData),
entropy,
System.Security.Cryptography.DataProtectionScope.CurrentUser);
return ToSecureString(System.Text.Encoding.Unicode.GetString(decryptedData));
}
catch
{
return new SecureString();
}
}
#endregion
}
}
Update: Action start 14:31:36: Encryption.
MSI (c) (84:40) [14:31:36:525]: Invoking remote custom action. DLL: C:\Users\<username>\AppData\Local\Temp\MSIE259.tmp, Entrypoint: m1
InstallShield: Attempting to load through CLR 4 APIs...
InstallShield: Getting meta host...
InstallShield: Enumerating available runtimes...
InstallShield: Highest available runtime: v4.0.30319
InstallShield: Trying to use highest runtime...
InstallShield: Using highest version runtime...
InstallShield: Loading assembly Security.Encryption from resource 4097
InstallShield: Calling method with parameters [(System.String)C:\Program Files (x86)\<Installdir>\<configfilename>.config, (System.String)VWFPassword, (System.String)]
InstallShield: Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\Program Files (x86)\<Installdir>\<configfilename>.config'.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
at System.Xml.XmlDownloadManager.GetStream(Uri uri, ICredentials credentials, IWebProxy proxy, RequestCachePolicy cachePolicy)
at System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn)
at System.Xml.XmlTextReaderImpl.OpenUrlDelegate(Object xmlResolver)
at System.Threading.CompressedStack.runTryCode(Object userData)
at
System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.CompressedStack.Run(CompressedStack compressedStack, ContextCallback callback, Object state)
at System.Xml.XmlTextReaderImpl.OpenUrl()
at System.Xml.XmlTextReaderImpl.Read()
at System.Xml.XmlLoader.Load(XmlDocument doc, XmlReader reader, Boolean preserveWhitespace)
at System.Xml.XmlDocument.Load(XmlReader reader)
at System.Xml.XmlDocument.Load(String filename)
at Security.Encryption.EncrptionHelper.SetSetting(String appSettingKey, String appsettingValue, String configFilePathName)
at Security.Encryption.EncrptionHelper.SetValue(String configFilePathName, String appSettingKey, String appSettingValue)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at InstallShield.ClrHelper.CustomActionHelper.CallMethod(EntryPointInfo info)
at InstallShield.ClrHelper.CustomActionHelper.RunAction(UInt32 installHandle, Int32 entryNumber, Int64 instanceHandle)
InstallShield: Managed code threw an unhandled exception.
This is the error I receive after doing all that is mentioned in the screenshots below and doing some R&D. The directory mentioned "C:\Program Files (x86)\\.config" exists when the encryption custiom action is being called but it throws an exception.
Yes, it can be done with following steps:
1- write your required functionality in c# installer class (make sure your are using installer class)
2- Compile and add your dll into installshield (recomended create a separate component for this dll)
3- Select component view -> select above component and go to .Net settings section, set the ".Net Installer class" to true. Set the ".net installer class parameters"
Parameters are passed as key/value pair e.g
/targetDirectory="[INSTALLDIR]\"
All steps are same, just added screenshots.
Create a dll with an installer class and your encrypt/decrypt class.
Add dll and config file to component(above mentioned), if config file is already added to some other component then its fine. no need to add again.
I have added and retrieved INSTALLDIR variable as argument which is predefined. if you want to receive some input from user (from some custom textboxes) then you will need to define your own variables to store and pass values as arguments.
Creating dll with installer class and your requred logic for other task
Creating component and adding files
Mark the dll as installer class and pass arguments
Here goes installer class:
using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Configuration.Install;
using System.Collections.Generic;
namespace EncryptionDecryption
{
[RunInstaller(true)]
public class InstallerClassDemo : Installer
{
private string installationDirectory=string.Empty;
private string testString=string.Empty ;
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
try
{
//For testing purpose only.. work only in debug mode when pdb files are deployed as well.
//Debugger.Break();
installationDirectory = Context.Parameters["INSTALLDIR"];
//I believe, the config file must be located in the installation directory if so, then use the following way to compute path
string configFilePath = Path.Combine(installationDirectory, "myConfigFile.config");
EncrptionHelper.SetValue(configFilePath, "testKey", "testValue");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
protected override void OnCommitted(System.Collections.IDictionary savedState)
{
base.OnCommitted(savedState);
}
public override void Uninstall(System.Collections.IDictionary savedState)
{
base.Uninstall(savedState);
}
}
}
I think installshield cannot (or need extra work) for read your assemblies, but you can run simple console application which consume your assemblies from installscript and passing parameter from installshield.
Create simple console application
Include console application on Support Files
Copy to installation folder or assemblies location, so console application can access your assemblies
Launch from installscript using launchappandwait
If consoleapp.exe not use anymore, just delete it.

.NET assemblies: understanding type visibility

I am trying to reproduce something that System.Xml.Serialization already does, but for a different source of data.
For now task is limited to deserialization only.
I.e. given defined source of data that I know how to read. Write a library that takes a random type, learns about it fields/properties via reflection, then generates and compiles "reader" class that can take data source and an instance of that random type and writes from data source into the object's fields/properties.
here is a simplified extract from my ReflectionHelper class
public class ReflectionHelper
{
public abstract class FieldReader<T>
{
public abstract void Fill(T entity, XDataReader reader);
}
public static FieldReader<T> GetFieldReader<T>()
{
Type t = typeof(T);
string className = GetCSharpName(t);
string readerClassName = Regex.Replace(className, #"\W+", "_") + "_FieldReader";
string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);
CompilerParameters prms = new CompilerParameters();
prms.GenerateInMemory = true;
prms.ReferencedAssemblies.Add("System.Data.dll");
prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] {source});
if (compiled.Errors.Count > 0)
{
StringWriter w = new StringWriter();
w.WriteLine("Error(s) compiling {0}:", readerClassName);
foreach (CompilerError e in compiled.Errors)
w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
w.WriteLine();
w.WriteLine("Generated code:");
w.WriteLine(source);
throw new Exception(w.GetStringBuilder().ToString());
}
return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
}
private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<EntityField> fields)
{
StringWriter w = new StringWriter();
// write out field setters here
return #"
using System;
using System.Data;
namespace " + ns + #".Generated
{
public class " + readerClassName + #" : ReflectionHelper.FieldReader<" + className + #">
{
public void Fill(" + className + #" e, XDataReader reader)
{
" + w.GetStringBuilder().ToString() + #"
}
}
}
";
}
}
and the calling code:
class Program
{
static void Main(string[] args)
{
ReflectionHelper.GetFieldReader<Foo>();
Console.ReadKey(true);
}
private class Foo
{
public string Field1 = null;
public int? Field2 = null;
}
}
The dynamic compilation of course fails because Foo class is not visible outside of Program class. But! The .NET XML deserializer somehow works around that - and the question is: How?
After an hour of digging System.Xml.Serialization via Reflector I came to accept that I lack some kind of basic knowledge here and not really sure what am I looking for...
Also it is entirely possible that I am reinventing a wheel and/or digging in a wrong direction, in which case please do speak up!
You don’t need to create a dynamic assembly and dynamically compile code in order to deserialise an object. XmlSerializer does not do that either — it uses the Reflection API, in particular it uses the following simple concepts:
Retrieving the set of fields from any type
Reflection provides the GetFields() method for this purpose:
foreach (var field in myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
// ...
I’m including the BindingFlags parameter here to ensure that it will include non-public fields, because otherwise it will return only public ones by default.
Setting the value of a field in any type
Reflection provides the function SetValue() for this purpose. You call this on a FieldInfo instance (which is returned from GetFields() above) and give it the instance in which you want to change the value of that field, and the value to set it to:
field.SetValue(myObject, myValue);
This is basically equivalent to myObject.Field = myValue;, except of course that the field is identified at runtime instead of compile-time.
Putting it all together
Here is a simple example. Notice you need to extend this further to work with more complex types such as arrays, for example.
public static T Deserialize<T>(XDataReader dataReader) where T : new()
{
return (T) deserialize(typeof(T), dataReader);
}
private static object deserialize(Type t, XDataReader dataReader)
{
// Handle the basic, built-in types
if (t == typeof(string))
return dataReader.ReadString();
// etc. for int and all the basic types
// Looks like the type t is not built-in, so assume it’s a class.
// Create an instance of the class
object result = Activator.CreateInstance(t);
// Iterate through the fields and recursively deserialize each
foreach (var field in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
field.SetValue(result, deserialize(field.FieldType, dataReader));
return result;
}
Notice I had to make some assumptions about XDataReader, most notably that it can just read a string like that. I’m sure you’ll be able to change it so that it works with your particular reader class.
Once you’ve extended this to support all the types you need (including int? in your example class), you can deserialize an object by calling:
Foo myFoo = Deserialize<Foo>(myDataReader);
and you can do this even when Foo is a private type as it is in your example.
If I try to use sgen.exe (the standalone XML serialization assembly compiler), I get the following error message:
Warning: Ignoring 'TestApp.Program'.
- TestApp.Program is inaccessible due to its protection level. Only public types can be processed.
Warning: Ignoring 'TestApp.Program+Foo'.
- TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.
Assembly 'c:\...\TestApp\bin\debug\TestApp.exe' does not contain any types that can be serialized using XmlSerializer.
Calling new XmlSerializer(typeof(Foo)) in your example code results in:
System.InvalidOperationException: TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.
So what gave you the idea that XmlSerializer can handle this?
However, remember that at runtime, there are no such restrictions. Trusted code using reflection is free to ignore access modifiers. This is what .NET binary serialization is doing.
For example, if you generate IL code at runtime using DynamicMethod, then you can pass skipVisibility = true to avoid any checks for visibility of fields/classes.
I've been working a bit on this. I'm not sure if it will help but, anyway I think it could be the way. Recently I worked with Serialization and DeSerealization of a class I had to send over the network. As there were two different programs (the client and the server), at first I implemented the class in both sources and then used serialization. It failed as the .Net told me it had not the same ID (I'm not sure but it was some sort of assembly id).
Well, after googling a bit I found that it was because the serialized class was on different assemblies, so the solution was to put that class in a independent library and then compile both client and server with that library. I've used the same idea with your code, so I put both Foo class and FieldReader class in a independent library, let's say:
namespace FooLibrary
{
public class Foo
{
public string Field1 = null;
public int? Field2 = null;
}
public abstract class FieldReader<T>
{
public abstract void Fill(T entity, IDataReader reader);
}
}
compile it and add it to the other source (using FooLibrary;)
this is the code I've used. It's not exactly the same as yours, as I don't have the code for GetCSharpName (I used t.Name instead) and XDataReader, so I used IDataReader (just for the compiler to accept the code and compile it) and also change EntityField for object
public class ReflectionHelper
{
public static FieldReader<T> GetFieldReader<T>()
{
Type t = typeof(T);
string className = t.Name;
string readerClassName = Regex.Replace(className, #"\W+", "_") + "_FieldReader";
object[] fields = new object[10];
string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);
CompilerParameters prms = new CompilerParameters();
prms.GenerateInMemory = true;
prms.ReferencedAssemblies.Add("System.Data.dll");
prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
prms.ReferencedAssemblies.Add("FooLibrary1.dll");
CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] { source });
if (compiled.Errors.Count > 0)
{
StringWriter w = new StringWriter();
w.WriteLine("Error(s) compiling {0}:", readerClassName);
foreach (CompilerError e in compiled.Errors)
w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
w.WriteLine();
w.WriteLine("Generated code:");
w.WriteLine(source);
throw new Exception(w.GetStringBuilder().ToString());
}
return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
}
private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<object> fields)
{
StringWriter w = new StringWriter();
// write out field setters here
return #"
using System;
using System.Data;
namespace " + ns + ".Generated
{
public class " + readerClassName + #" : FieldReader<" + className + #">
{
public override void Fill(" + className + #" e, IDataReader reader)
" + w.GetStringBuilder().ToString() +
}
}";
}
}
by the way, I found a tiny mistake, you should use new or override with the Fill method, as it is abstract.
Well, I must admit that GetFieldReader returns null, but at least the compiler compiles it.
Hope that this will help you or at least it guides you to the good answer
regards

Serialization problem

We save and read files by (de) serializing a class named "DocumentClass".
All was working well, untill we added 2 more fields to the documentclass. (We think that's the problem)
When we now try to open files that were serialized by the previous edition, we get an error.
System.ArgumentException: Object of type 'System.Int32' cannot be converted to type 'System.String'.
at SoftwareProject.Componenten.Bestand.DocumentClass.d(String A_0)
at de..ctor(String A_0)
at g.a(String A_0)
The method generating the error is the method "Read". (DocumentClass.d() is the obfuscated name )
But things get weirder: when we open the file in VS debug mode, no error is generated, but all fields in the documentclass are 0 or null ???
We are lost here ... please help ...
We've added the [OptionalField] attribute to the new fields, but that doesn't help ..
Why are all values null in debug mode ??
And where is the runtime error coming from ? How can we debug it ?
Thanks in advance!!
public static DocumentClass Read(string fullFilePath)
{
DocumentClass c = new DocumentClass();
Stream s = File.OpenRead(fullFilePath);
BinaryFormatter b = new BinaryFormatter();
//b.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
b.Binder = new MyCustomBinder();
try
{
c = (DocumentClass)b.Deserialize(s);
}
catch( Exception exc )
{
s.Close();
throw exc;
}
finally
{
s.Close();
}
return c;
}
public class MyCustomBinder : SerializationBinder {
public override Type BindToType(string assemblyName, string typeName) {
Type tyType = null;
string sShortAssemblyName = assemblyName.Split(',')[0];
Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
if (sShortAssemblyName.ToLower() == "debugAssemblyName")
{
sShortAssemblyName = "AppAssemblyName";
}
foreach (Assembly ayAssembly in ayAssemblies) {
if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0]) {
tyType = ayAssembly.GetType(typeName);
break;
}
}
return tyType;
}
}
.Net has something called "Version Tolerant Serialization" which most likely solves this issue ;)
You should check out this easy to understand example on object serialization:
http://programming.flashadventures.com/c-sharp/writing-objects-to-files-serialization/
I assume you are using BinaryFormatter? This serializer is notoriously brittle, since it (by default) includes the field-name in the stream; this impacts obfuscation particularly badly. Presumably the obfuscator is now choosing new named for the fields (perhaps at random, perhaps due to the new fields), and so it can't deserialize correctly.
A few options:
don't obfuscate the DTO
implement ISerializable so the field names don't matter
use a serializer that doesn't care about field names
I'd personally opt for the latter, but I'm a bit biased ;-p I know of people using protobuf-net with obfuscated classes; the data only includes numeric markers, so the meaning isn't really exposed (except of course, by inspection of the data - post codes etc, but that is the job of encryption).
I think you need to use Custom Serialization

Categories