Automate class generation from an interface using field/property naming conventions - c#

How would I automate the creation of a default implementation of a class from an interface using conventions. In other words, if I have an interface:
public interface ISample
{
int SampleID {get; set;}
string SampleName {get; set;}
}
Is there a snippet, T4 template, or some other means of automatically generating the class below from the interface above? As you can see, I want to put the underscore before the name of the field and then make the field the same name as the property, but lower-case the first letter:
public class Sample
{
private int _sampleID;
public int SampleID
{
get { return _sampleID;}
set { _sampleID = value; }
}
private string _sampleName;
public string SampleName
{
get { return _sampleName;}
set { _sampleName = value; }
}
}

I am not sure if T4 would be the easiest solution here in terms of readability but you can also use another code generation tool at your disposal: the CodeDom provider.
The concept is very straightforward: code consists of building blocks that you put together.
When the time is ripe, these building blocks are then parsed into the language of choice . What you end up with is a string that contains the source code of your newly created program. Afterwards you can write this to a textfile to allow for further use.
As you have noticed: there is no compile-time result, everything is runtime. If you really want compiletime then you should use T4 instead.
The code:
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Text;
namespace TTTTTest
{
internal class Program
{
private static void Main(string[] args)
{
new Program();
}
public Program()
{
// Create namespace
var myNs = new CodeNamespace("MyNamespace");
myNs.Imports.AddRange(new[]
{
new CodeNamespaceImport("System"),
new CodeNamespaceImport("System.Text")
});
// Create class
var myClass = new CodeTypeDeclaration("MyClass")
{
TypeAttributes = TypeAttributes.Public
};
// Add properties to class
var interfaceToUse = typeof (ISample);
foreach (var prop in interfaceToUse.GetProperties())
{
ImplementProperties(ref myClass, prop);
}
// Add class to namespace
myNs.Types.Add(myClass);
Console.WriteLine(GenerateCode(myNs));
Console.ReadKey();
}
private string GenerateCode(CodeNamespace ns)
{
var options = new CodeGeneratorOptions
{
BracingStyle = "C",
IndentString = " ",
BlankLinesBetweenMembers = false
};
var sb = new StringBuilder();
using (var writer = new StringWriter(sb))
{
CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(ns, writer, options);
}
return sb.ToString();
}
private void ImplementProperties(ref CodeTypeDeclaration myClass, PropertyInfo property)
{
// Add private backing field
var backingField = new CodeMemberField(property.PropertyType, GetBackingFieldName(property.Name))
{
Attributes = MemberAttributes.Private
};
// Add new property
var newProperty = new CodeMemberProperty
{
Attributes = MemberAttributes.Public | MemberAttributes.Final,
Type = new CodeTypeReference(property.PropertyType),
Name = property.Name
};
// Get reference to backing field
var backingRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), backingField.Name);
// Add statement to getter
newProperty.GetStatements.Add(new CodeMethodReturnStatement(backingRef));
// Add statement to setter
newProperty.SetStatements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), backingField.Name),
new CodePropertySetValueReferenceExpression()));
// Add members to class
myClass.Members.Add(backingField);
myClass.Members.Add(newProperty);
}
private string GetBackingFieldName(string name)
{
return "_" + name.Substring(0, 1).ToLower() + name.Substring(1);
}
}
internal interface ISample
{
int SampleID { get; set; }
string SampleName { get; set; }
}
}
This produces:
Magnificent, isn't it?
Sidenote: a property is given Attributes = MemberAttributes.Public | MemberAttributes.Final because omitting the MemberAttributes.Final would make it become virtual.
And last but not least: the inspiration of this awesomeness. Metaprogramming in .NET by Kevin Hazzard and Jason Bock, Manning Publications.

Related

Using specific function to create new instance of object when deserializing CSV using CSVHelper

I am using JoshClose's CsvHelper library in order to serialize and deserialize data into CSV.
I am facing the following problem : When deserializing my data, I want to use a specific function to create a new instance of my class being deserialized.
I saw I can use a specific constructor as indicated here, but in my case, I would like to use a function which is not a constructor (but still returns a new instance of my class). Is this even possible?
Another solution would be to allow CSVHelper to "override" the values of an existing instance of my class. Is this possible?
Here is my current code so far:
private MyClass[] DeserializeFromCSV( string csv )
{
MyClass[] output = null;
using ( System.IO.TextReader textReader = new System.IO.StringReader( csv ) )
using ( CsvHelper.CsvReader csvReader = new CsvHelper.CsvReader( textReader ) )
{
// Here, I would like to provide the function to call
// in order to instantiate a new instance of the class
// The function I want to call does not return a `ConstructorInfo`
// as needed by the property here
//csvReader.Configuration.GetConstructor = type =>
//{
// return null;
//};
// CSVClassMap is a getter to get a ClassMap
csvReader.Configuration.RegisterClassMap( CSVClassMap );
csvReader.Read();
output = csvReader.GetRecords<T>().ToArray();
}
return output;
}
You can use CsvHelper.ObjectResolver to do this.
void Main()
{
var s = new StringBuilder();
s.Append("Id,Name\r\n");
s.Append("1,one\r\n");
s.Append("2,two\r\n");
using (var reader = new StringReader(s.ToString()))
using (var csv = new CsvReader(reader))
{
CsvHelper.ObjectResolver.Current = new ObjectResolver(CanResolve, Resolve);
csv.Configuration.RegisterClassMap<TestMap>();
csv.GetRecords<Test>().ToList().Dump();
}
}
public bool CanResolve(Type type)
{
return type == typeof(Test);
}
public object Resolve(Type type, object[] constructorArgs)
{
// Get a dependency from somewhere.
var someDependency = new object();
return new Test(someDependency);
}
public class Test
{
public int Id { get; set; }
public string Name { get; set; }
public Test(object someDependency) { }
}
public class TestMap : ClassMap<Test>
{
public TestMap()
{
Map(m => m.Id);
Map(m => m.Name);
}
}

Serialization: Dynamic class names

I have already tried various possibilities but maybe I am just too tired of seeing the solution -.-
I have an xml structure like this:
<diagnosisList>
<diagnosis>
<surgery1>
<date>1957-08-13</date>
<description>a</description>
<ops301>0-000</ops301>
</surgery1>
<surgery2>
<date>1957-08-13</date>
<description>a</description>
<ops301>0-000</ops301>
</surgery2>
<surgery...>
</surgery...>
</diagnosis>
</diagnosisList>
As you see there is a variable number of surgeries. I have a class "surgery" containing the XML elements.
class Surgery
{
[XmlElement("date")]
public string date { get; set; }
[XmlElement("description")]
public string description { get; set; }
[XmlElement("ops301")]
public string ops301 { get; set; }
public Surgery()
{
}
}
and a class diagnosis creating the structure by adding the surgery class to the constructor.
diagnosis.cs
class Diagnosis
{
[XmlElement("surgery")]
public Surgery surgery
{
get;
set;
}
public Diagnosis(Surgery Surgery)
{
surgery = Surgery;
}
}
I need to be able to serialize the class name of the surgery dynamically by adding a number before serialization happens.
does anybody know a way to achieve that?
any help is really appreciated :)
Kind regards
Sandro
-- EDIT
I create the whole structure starting from my root class "Import". this class then will be passed to the serializer. So I cannot use XMLWriter in the middle of creation of the structure. I Need to create the whole structure first and finally it will be serialized:
private static void XmlFileSerialization(Import import)
{
string filename = #"c:\dump\trauma.xml";
// default file serialization
XmlSerializer<Import>.SerializeToFile(import, filename);
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = Encoding.UTF8;
settings.Indent = true;
settings.IndentChars = "\t";
XmlSerializer<Import>.SerializeToFile(import, filename, namespaces, settings);
}
and then in the Method "SerializeToFile"
public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces, XmlWriterSettings settings)
{
if (source == null)
throw new ArgumentNullException("source", "Object to serialize cannot be null");
XmlSerializer serializer = new XmlSerializer(source.GetType());
using (XmlWriter xmlWriter = XmlWriter.Create(filename, settings))
{
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T));
x.Serialize(xmlWriter, source, namespaces);
}
}
}
What I Need is to be able to instantiate a variable number of classes based on the main class "Surgery". The class must have a variable Name, i.e.
surgery1, surgery2, surgery3, etc.
This cannot be changed because this is given by the Institution defining the XML structure.
the class must be accessible by its dynamic Name because the property in the class must be set.
so:
surgery1.Property = "blabla";
surgery2. Property = "babla";
etc.
I am even thinking about using T4 methods to create this part of code, but there must be another way to achieve dynamic class names.
I also thought of creating instances with variable names of the class by using reflection:
System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(string className)
But this doesn't work actually -.-
Does anybody have a hint and could put me in the right direction?
I think, you could try implement methods from IXmlSerializable in object contains diagnosisList.
Try to use custom xml writer and reader.
public class SurgeryWriter : XmlTextWriter
{
public SurgeryWriter(string url) : base(url, Encoding.UTF8) { }
private int counter = 1;
public override void WriteStartElement(string prefix, string localName, string ns)
{
if (localName == "surgery")
{
base.WriteStartElement(prefix, "surgery" + counter, ns);
counter++;
}
else
base.WriteStartElement(prefix, localName, ns);
}
}
public class SurgeryReader : XmlTextReader
{
public SurgeryReader(string url) : base(url) { }
public override string LocalName
{
get
{
if (base.LocalName.StartsWith("surgery"))
return "surgery";
return base.LocalName;
}
}
}
Classes:
[XmlRoot("diagnosisList")]
public class DiagnosisList
{
[XmlArray("diagnosis")]
[XmlArrayItem("surgery")]
public Surgery[] Diagnosis { get; set; }
}
[XmlRoot("surgery")]
public class Surgery
{
[XmlElement("date", DataType = "date")]
public DateTime Date { get; set; }
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("ops301")]
public string Ops301 { get; set; }
}
Use:
var xs = new XmlSerializer(typeof(DiagnosisList));
DiagnosisList diagnosisList;
using (var reader = new SurgeryReader("test.xml"))
diagnosisList = (DiagnosisList)xs.Deserialize(reader);
using (var writer = new SurgeryWriter("test2.xml"))
xs.Serialize(writer, diagnosisList);
Don't mix XML and C#.
You don't need dynamic names in the C# code!
If you need an arbitrary number of instances of a class, create them in a loop and place it in any collection.
var surgeries = new List<Surgery>();
for (int i = 0; i < 10; i++)
{
var surgery = new Surgery();
surgeries.Add(surgery);
}
Later you can access them by index or by enumerating.
surgeries[5]
foreach (var surgery in surgeries)
{
// use surgery
}
As you can see no need dynamic names!
Alternatively, use the dictionary with arbitrary names as keys.
var surgeryDict = new Dictionary<string, Surgery>();
for (int i = 0; i < 10; i++)
{
var surgery = new Surgery();
surgeryDict["surgery" + i] = surgery;
}
Access by name:
surgeryDict["surgery5"]

Generate mock XML file from C# class hierachy

In order to generate UBL-order documents in XML, I have created 44 classes in C# using Xml.Serialization. The class consist of a root class "OrderType" which contains a lot of properties (classes), which again contains more properties.
In order to test that the classes always will build a XML document that will pass a validation. I want a XML file containing all the possible nodes (at least once) the hierarchy of Classes/Properties can build.
A very reduced code example:
[XmlRootAttribute]
[XmlTypeAttribute]
public class OrderType
{
public DeliveryType Delivery { get; set; }
//+ 50 more properties
public OrderType(){}
}
[XmlTypeAttribute]
public class DeliveryType
{
public QuantityType Quantity { get; set; }
//+ 10 more properties
public DeliveryType (){}
}
I have already tried to initialise some properties in some of the constructors and it works fine, but this method would take a whole week to finish.
So! Is there a smart an quick method to generate a Mock XML document with all properties initialized?
It's ok that the outer nodes just are defined e.g.:
< Code />
Well, sometimes you have to do it on your own:
using System;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
namespace extanfjTest
{
class Program
{
static void Main(string[] args)
{
OrderType ouo = new OrderType(DateTime.Now);
SetProperty(ouo);
XmlSerializer ser = new XmlSerializer(typeof(OrderType));
FileStream fs = new FileStream("C:/Projects/group.xml", FileMode.Create);
ser.Serialize(fs, ouo);
fs.Close();
}
public static void SetProperty(object _object)
{
if (_object == null)
{ return; }
foreach (PropertyInfo prop in _object.GetType().GetProperties())
{
if ("SemlerServices.OIOUBL.dll" != prop.PropertyType.Module.Name)
{ continue; }
if (prop.PropertyType.IsArray)
{
var instance = Activator.CreateInstance(prop.PropertyType.GetElementType());
Array _array = Array.CreateInstance(prop.PropertyType.GetElementType(), 1);
_array.SetValue(instance, 0);
prop.SetValue(_object, _array, null);
SetProperty(instance);
}
else
{
var instance = Activator.CreateInstance(prop.PropertyType);
prop.SetValue(_object, instance, null);
SetProperty(instance);
}
}
}
}
}

Writing enumerable to csv file

I'm sure its very straightforward but I am struggling to figure out how to write an array to file using CSVHelper.
I have a class for example
public class Test
{
public Test()
{
data = new float[]{0,1,2,3,4};
}
public float[] data{get;set;}
}
i would like the data to be written with each array value in a separate cell. I have a custom converter below which is instead providing one cell with all the values in it.
What am I doing wrong?
public class DataArrayConverter<T> : ITypeConverter
{
public string ConvertToString(TypeConverterOptions options, object value)
{
var data = (T[])value;
var s = string.Join(",", data);
}
public object ConvertFromString(TypeConverterOptions options, string text)
{
throw new NotImplementedException();
}
public bool CanConvertFrom(Type type)
{
return type == typeof(string);
}
public bool CanConvertTo(Type type)
{
return type == typeof(string);
}
}
To further detail the answer from Josh Close, here what you need to do to write any IEnumerable (including arrays and generic lists) in a recent version (anything above 3.0) of CsvHelper!
Here the class under test:
public class Test
{
public int[] Data { get; set; }
public Test()
{
Data = new int[] { 0, 1, 2, 3, 4 };
}
}
And a method to show how this can be saved:
static void Main()
{
using (var writer = new StreamWriter("db.csv"))
using (var csv = new CsvWriter(writer))
{
var list = new List<Test>
{
new Test()
};
csv.Configuration.HasHeaderRecord = false;
csv.WriteRecords(list);
writer.Flush();
}
}
The important configuration here is csv.Configuration.HasHeaderRecord = false;. Only with this configuration you will be able to see the data in the csv file.
Further details can be found in the related unit test cases from CsvHelper.
In case you are looking for a solution to store properties of type IEnumerable with different amounts of elements, the following example might be of any help:
using CsvHelper;
using CsvHelper.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CsvHelperSpike
{
class Program
{
static void Main(string[] args)
{
using (var writer = new StreamWriter("db.csv"))
using (var csv = new CsvWriter(writer))
{
csv.Configuration.Delimiter = ";";
var list = new List<AnotherTest>
{
new AnotherTest("Before String") { Tags = new List<string> { "One", "Two", "Three" }, After="After String" },
new AnotherTest("This is still before") {After="after again", Tags=new List<string>{ "Six", "seven","eight", "nine"} }
};
csv.Configuration.RegisterClassMap<TestIndexMap>();
csv.WriteRecords(list);
writer.Flush();
}
using(var reader = new StreamReader("db.csv"))
using(var csv = new CsvReader(reader))
{
csv.Configuration.IncludePrivateMembers = true;
csv.Configuration.RegisterClassMap<TestIndexMap>();
var result = csv.GetRecords<AnotherTest>().ToList();
}
}
private class AnotherTest
{
public string Before { get; private set; }
public string After { get; set; }
public List<string> Tags { get; set; }
public AnotherTest() { }
public AnotherTest(string before)
{
this.Before = before;
}
}
private sealed class TestIndexMap : ClassMap<AnotherTest>
{
public TestIndexMap()
{
Map(m => m.Before).Index(0);
Map(m => m.After).Index(1);
Map(m => m.Tags).Index(2);
}
}
}
}
By using the ClassMap it is possible to enable HasHeaderRecord (the default) again. It is important to note here, that this solution will only work, if the collection with different amounts of elements is the last property. Otherwise the collection needs to have a fixed amount of elements and the ClassMap needs to be adapted accordingly.
This example also shows how to handle properties with a private set. For this to work it is important to use the csv.Configuration.IncludePrivateMembers = true; configuration and have a default constructor on your class.
Unfortunately, it doesn't work like that. Since you are returning , in the converter, it will quote the field, as that is a part of a single field.
Currently the only way to accomplish what you want is to write manually, which isn't too horrible.
foreach( var test in list )
{
foreach( var item in test.Data )
{
csvWriter.WriteField( item );
}
csvWriter.NextRecord();
}
Update
Version 3 has support for reading and writing IEnumerable properties.

How can I use a string argument to case a namespace or type?

I need to get some JSON output in a .NET 2.0 C# script. The goal is to use one method to output all the JSON feeds I need. All the models have the same id and name properties so I have about 15 namespaces that have the same parts here. In short: since I'm use castle I can call the function like:
/public/get_place_tags.castle
/public/get_place_types.castle
/public/get_place_whichEver.castle
Which in castle is calling each method, ie: the get_place_tags(){} but I want to not have to work where I can call one method to get output from each type like this:
/public/get_json.castle?wantedtype=place_types
Does anyone know how to fix this?
namespace campusMap.Controllers
{
[Layout("home")]
public class PublicController : BaseController
{
/* works and returns */
public void get_pace_type()
{
CancelView();
CancelLayout();
place_types[] types = ActiveRecordBase<place_types>.FindAll();
List<JsonAutoComplete> type_list = new List<JsonAutoComplete>();
foreach (place_types place_type in types)
{
JsonAutoComplete obj = new JsonAutoComplete();
obj.id = place_type.place_type_id;
obj.label = place_type.name;
obj.value = place_type.name;
type_list.Add(obj);
}
string json = JsonConvert.SerializeObject(type_list);
RenderText(json);
}
/* can;t ever find the namespace */
public void get_json(string wantedtype)
{
CancelView();
CancelLayout();
Type t = Type.GetType(wantedtype);
t[] all_tag = ActiveRecordBase<t>.FindAll();
List<JsonAutoComplete> tag_list = new List<JsonAutoComplete>();
foreach (t tag in all_tag)
{
JsonAutoComplete obj = new JsonAutoComplete();
obj.id = tag.id;
obj.label = tag.name;
obj.value = tag.name;
tag_list.Add(obj);
}
string json = JsonConvert.SerializeObject(tag_list);
RenderText(json);
}
}
}
[EDIT]-- (Newest Idea) Runtime type creation.. This I think is the cleanest idea on a way to get it to work...-----
So the goal is really to have at runtime a type used.. right.. so I thought this would work.
http://www.java2s.com/Code/CSharp/Development-Class/Illustratesruntimetypecreation.htm
and based of that here is the method so far. I'm still having issues getting t to get past the error
"The type or namespace name 't' could not be found (are you missing a using directive or an assembly reference?)" .. not sure where I'm going wrong here. Can't seem to get any of it to work lol..
public void get_json(String TYPE)
{
CancelView();
CancelLayout();
if (String.IsNullOrEmpty(TYPE))
{
TYPE = "place_types";
}
// get the current appdomain
AppDomain ad = AppDomain.CurrentDomain;
// create a new dynamic assembly
AssemblyName an = new AssemblyName();
an.Name = "DynamicRandomAssembly";
AssemblyBuilder ab = ad.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
// create a new module to hold code in the assembly
ModuleBuilder mb = ab.DefineDynamicModule("RandomModule");
// create a type in the module
TypeBuilder tb = mb.DefineType(TYPE, TypeAttributes.Public);
// finish creating the type and make it available
Type t = tb.CreateType();
t[] all_tag = ActiveRecordBase<t>.FindAll();
List<JsonAutoComplete> tag_list = new List<JsonAutoComplete>();
foreach (t tag in all_tag)
{
JsonAutoComplete obj = new JsonAutoComplete();
obj.id = tag.id;
obj.label = tag.name;
obj.value = tag.name;
tag_list.Add(obj);
}
RenderText(JsonConvert.SerializeObject(tag_list));
}
[EDIT]-- (Older idea) Eval code-----
So this attempt to make this happen is to use reflection and stuff to do a eval of sorts based on this http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=11939
namespace EvalCSCode
{
/// <summary>
/// Interface that can be run over the remote AppDomain boundary.
/// </summary>
public interface IRemoteInterface
{
object Invoke(string lcMethod, object[] Parameters);
}
/// <summary>
/// Factory class to create objects exposing IRemoteInterface
/// </summary>
public class RemoteLoaderFactory : MarshalByRefObject
{
private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance;
public RemoteLoaderFactory() { }
/// <summary> Factory method to create an instance of the type whose name is specified,
/// using the named assembly file and the constructor that best matches the specified parameters. </summary>
/// <param name="assemblyFile"> The name of a file that contains an assembly where the type named typeName is sought. </param>
/// <param name="typeName"> The name of the preferred type. </param>
/// <param name="constructArgs"> An array of arguments that match in number, order, and type the parameters of the constructor to invoke, or null for default constructor. </param>
/// <returns> The return value is the created object represented as ILiveInterface. </returns>
public IRemoteInterface Create(string assemblyFile, string typeName, object[] constructArgs)
{
return (IRemoteInterface)Activator.CreateInstanceFrom(
assemblyFile, typeName, false, bfi, null, constructArgs,
null, null, null).Unwrap();
}
}
}
#endregion
namespace campusMap.Controllers
{
public class JsonAutoComplete
{
private int Id;
[JsonProperty]
public int id
{
get { return Id; }
set { Id = value; }
}
private string Label;
[JsonProperty]
public string label
{
get { return Label; }
set { Label = value; }
}
private string Value;
[JsonProperty]
public string value
{
get { return Value; }
set { Value = value; }
}
}
[Layout("home")]
public class publicController : BaseController
{
#region JSON OUTPUT
/* works and returns */
public void get_pace_type()
{
CancelView();
CancelLayout();
place_types[] types = ActiveRecordBase<place_types>.FindAll();
List<JsonAutoComplete> type_list = new List<JsonAutoComplete>();
foreach (place_types place_type in types)
{
JsonAutoComplete obj = new JsonAutoComplete();
obj.id = place_type.id;
obj.label = place_type.name;
obj.value = place_type.name;
type_list.Add(obj);
}
string json = JsonConvert.SerializeObject(type_list);
RenderText(json);
}
/* how I think it'll work to have a dynmaic type */
public void get_json(string type)
{
CancelView();
CancelLayout();
/*t[] all_tag = ActiveRecordBase<t>.FindAll();
List<JsonAutoComplete> tag_list = new List<JsonAutoComplete>();
foreach (t tag in all_tag)
{
JsonAutoComplete obj = new JsonAutoComplete();
obj.id = tag.id;
obj.label = tag.name;
obj.value = tag.name;
tag_list.Add(obj);
}*/
StringBuilder jsonobj = new StringBuilder("");
jsonobj.Append(""+type+"[] all_tag = ActiveRecordBase<"+type+">.FindAll();\n");
jsonobj.Append("List<JsonAutoComplete> tag_list = new List<JsonAutoComplete>();{\n");
jsonobj.Append("foreach ("+type+" tag in all_tag){\n");
jsonobj.Append("JsonAutoComplete obj = new JsonAutoComplete();\n");
jsonobj.Append("obj.id = tag.id;\n");
jsonobj.Append("obj.label = tag.name;\n");
jsonobj.Append("obj.value = tag.name;\n");
jsonobj.Append("tag_list.Add(obj);\n");
jsonobj.Append("}\n");
CSharpCodeProvider c = new CSharpCodeProvider();
ICodeCompiler icc = c.CreateCompiler();
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("system.dll");
cp.ReferencedAssemblies.Add("Newtonsoft.Json.Net20.dll");
cp.ReferencedAssemblies.Add("Castle.ActiveRecord.dll");
cp.CompilerOptions = "/t:library";
cp.GenerateInMemory = true;
StringBuilder sb = new StringBuilder("");
sb.Append("namespace CSCodeEvaler{ \n");
sb.Append("public class CSCodeEvaler{ \n");
sb.Append("public object EvalCode(){\n");
sb.Append("return " + jsonobj + "; \n");
sb.Append("} \n");
sb.Append("} \n");
sb.Append("}\n");
CompilerResults cr = icc.CompileAssemblyFromSource(cp, sb.ToString());
System.Reflection.Assembly a = cr.CompiledAssembly;
object o = a.CreateInstance("CSCodeEvaler.CSCodeEvaler");
Type t = o.GetType();
MethodInfo mi = t.GetMethod("EvalCode");
object s = mi.Invoke(o, null);
string json = JsonConvert.SerializeObject(s);
RenderText(json);
}/**/
#endregion
}
I know it was suggested that the using is not needed.. I know I don't know them off the top and maybe that is level showing.. But here they are for what I think will work just above.
using System;
using System.Collections;
using System.Collections.Generic;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Queries;
using Castle.MonoRail.Framework;
using Castle.MonoRail.ActiveRecordSupport;
using campusMap.Models;
using MonoRailHelper;
using System.IO;
using System.Net;
using System.Web;
using NHibernate.Expression;
using System.Xml;
using System.Xml.XPath;
using System.Text.RegularExpressions;
using System.Text;
using System.Net.Sockets;
using System.Web.Mail;
using campusMap.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Utilities;
using Newtonsoft.Json.Linq;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.IO;
using System.Threading;
using System.Reflection;
OK, this is just a suggestion, and I know nothing about castle but it seems to me that you are wanting a custom route.
This is untested and of course you will have to tweak it but have a look at your Global.asax RegisterRoutes method and add this above you default route
routes.MapRoute(
"TypeRoute", // Route name
"Public/{wantedtype}", // URL with parameters
new { controller = "Public", action = "get_json", wantedtype = UrlParameter.Optional } // Parameter defaults
);
something like this might get what you are after.
you might need to fiddle with the action so it matches your castle framework
EDIT
I had some more time to look at this. Using the route I described above and the following method on the controller I got a good result
public void get_json(string wantedtype)
{
Type t = Type.GetType(wantedtype);
// t.Name = "Int32" when "System.Int32 is used as the wanted type
}
http://.../Public/System.Int32 works,
this url doesn't work http://.../Public/Int32
Now if you wanted to use the short version you could use something like MEF or AutoFAC or Castle to do a lookup in your container for the type you are after.
So my answer is use a good route to get the key for the wanted type then build a factory to manage the type mappings and produce an instance of what you are after.
I just had a look at the ActiveRecord API Doco Here
There is a method FindAll(Type targetType) : Array
have you tried replacing
Type t = Type.GetType(wantedtype);
t[] all_tag = ActiveRecordBase<t>.FindAll();
// blah blah blah
with
Type t = Type.GetType(wantedtype);
dynamic all_tag = ActiveRecordBase.FindAll(t);
RenderText(JsonConvert.SerializeObject(all_tag ));
completely untested but see what you think
I tired and tried and tired to so one of these ways. Me and an co-worker came up this with solution. I made 2 files.
Ijson_autocomplete.cs
using System;
namespace campusMap.Models
{
public interface Ijson_autocomplete
{
int id { get; set; }
string name { get; set; }
String get_json_data();
}
}
json_autocomplete.cs
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Castle.ActiveRecord;
using System.Collections.Generic;
using System.Data.SqlTypes;
using Newtonsoft.Json;
using Newtonsoft.Json.Utilities;
using Newtonsoft.Json.Linq;
namespace campusMap.Models
{
public class JsonAutoComplete
{
private int Id;
[JsonProperty]
public int id
{
get { return Id; }
set { Id = value; }
}
private string Label;
[JsonProperty]
public string label
{
get { return Label; }
set { Label = value; }
}
private string Value;
[JsonProperty]
public string value
{
get { return Value; }
set { Value = value; }
}
}
public class json_autocomplete<t> where t : campusMap.Models.Ijson_autocomplete
{
virtual public String get_json_data()
{
t[] all_tag = ActiveRecordBase<t>.FindAll();
List<JsonAutoComplete> tag_list = new List<JsonAutoComplete>();
foreach (t tag in all_tag)
{
JsonAutoComplete obj = new JsonAutoComplete();
obj.id = tag.id;
obj.label = tag.name;
obj.value = tag.name;
tag_list.Add(obj);
}
return JsonConvert.SerializeObject(tag_list);
}
}
}
then when I called the function from the url this is what it ended up as
public void get_json(String TYPE)
{
CancelView();
CancelLayout();
Type t = Type.GetType("campusMap.Models."+TYPE);
Ijson_autocomplete theclass = (Ijson_autocomplete)Activator.CreateInstance(t);
RenderText(theclass.get_json_data());
}
and one off the models
namespace campusMap.Models
{
[ActiveRecord(Lazy=true)]
public class place_types : json_autocomplete<place_types>, campusMap.Models.Ijson_autocomplete
{
private int place_type_id;
[PrimaryKey("place_type_id")]
virtual public int id
{
get { return place_type_id; }
set { place_type_id = value; }
}
private string Name;
[Property]
virtual public string name
{
get { return Name; }
set { Name = value; }
}
private string Attr;
[Property]
virtual public string attr
{
get { return Attr; }
set { Attr = value; }
}
private IList<place> places;
[HasAndBelongsToMany(typeof(place), Lazy = true, Table = "place_to_place_models", ColumnKey = "place_model_id", ColumnRef = "place_id", Inverse = true, NotFoundBehaviour = NotFoundBehaviour.Ignore)]
virtual public IList<place> Places
{
get { return places; }
set { places = value; }
}
}
}
now when you call
/public/get_json.castle?wantedtype=place_types
or
/public/get_json.castle?wantedtype=place_tags
You will get the json output of each model. Tada! :D Thank you everyone for helping.

Categories