Get property names as strings from a input string - c#

I am currently creating a module which will create merge fields in word using Gembox.Document from C#. First of all, I just want to say that this is a task that I have been given, so wether this is a bad way to do it or not, this is the way they want it.
I have a Windows forms application where there is a textbox and a button. In the textbox, they want the possibility to paste a dto/model, for example:
"public class Example
{
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
}"
I already have the logic to add mergefields to word with a method where I pass in a string[] which contains all the merge field names.
PROBLEM IS: I need to be able to somehow substring this big string above which contains a dto/model written as a string in the textbox, to get the property names and add them to the string[], since they will be the merge field names.
I hope I could explain myself well enough. This is my first question here and I am not used to explain my problems in English.
EDIT:
To specify the problem: I need to get the property names out of this string and put them into an string[]:
string s = #"public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }"

I think that maybe you should parse this text (using parser not own solution) and than search syntax tree to find properties names. I think about something similar to this:
Using NRefactory for analyzing C# code
This code returns complete tree or error (I use NRefactory but you can use Roslyn):
var parser = new CSharpParser();
var syntaxTree = parser.Parse(programCode);
than search syntaxTree field for properties.
Example code:
const string code = #"public class Example {
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
}";
var syntaxTree = new CSharpParser().Parse(code, "program.cs");
var listOfPropertiesNames = syntaxTree.Children
.SelectMany(astParentNode => astParentNode.Children)
.OfType<PropertyDeclaration>()
.Select(astPropertyNode => astPropertyNode.Name)
.ToList();
This snippet extract properties names.

You can use the CSharpCodeProvider class to compile your code to an assembly, and then use reflection to find the types in the compiled assembly.
var sourcePart = #"public class Example
{
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
}";
var sourceTemplate = #"using System;
#code
";
var code = sourceTemplate.Replace("#code", sourcePart);
CSharpCodeProvider c = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
CompilerResults cr = c.CompileAssemblyFromSource(cp, code);
if (cr.Errors.Count > 0)
{
MessageBox.Show("ERROR: " + cr.Errors[0].ErrorText,
"Error evaluating cs code", MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
var a = cr.CompiledAssembly;
var type = a.GetTypes().Single();
string[] propertyNames = type.GetProperties().Select(p => p.Name).ToArray();
UPDATE:
Remeber however, that a type loaded in an app domain can't be unloaded, and will keep consuming memory until the application exit.
So if the user works with this function a lot, memory would be consumed incrementally.
If this becomes a problem, you can workaround this by creating a separate app domain or spawn another process to serve this function, but it's another question.

You can create custom static method to parse your text. What it does it jumps across string from one index of '{' to next index goes backward and checks if there is '(' or ')' char (which indicates that it is a method and not a property and it should skip it) and goes backwards to find the beginning of the property. After that it extracts value, then jumps to next index of '{' char and so on :
static string[] GetProperties(string dirty)
{
List<string> properties = new List<string>();
int i = dirty.IndexOf("{ ");
StringBuilder sb = new StringBuilder();
int propEndIndex = -1; int i2 = -1;
for (; i != -1; i = dirty.IndexOf("{ ", i + 1))
{
i2 = i - 1;
for (; dirty[i2] == ' '; i2--) { }
if (dirty[i2] == '(' || dirty[i2] == ')') continue;
propEndIndex = i2 + 1;
for (; dirty[i2] != ' '; i2--) { }
for (i2++; i2 < propEndIndex; i2++)
sb.Append(dirty[i2]);
properties.Add(sb.ToString());
sb.Clear();
}
return properties.ToArray();
}
Example of usage :
Stopwatch sw = new Stopwatch();
var s = #"public class Example
{
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
public void MyMethod() { }
}";
sw.Start();
string[] props = GetProperties(s);
sw.Stop();
foreach (var item in props)
Console.WriteLine(item);
Console.WriteLine("\n\nMethod is executed in " + sw.ElapsedMilliseconds + " ms");
Console.ReadKey();
Output:
Name
Surname
CellPhone
Address
CompanyName
CurrentDate
Method is executed in 1 ms

Related

List overwriting previous added Objects when a new Object is added

I'm looping through some logic for a program I'm making that reads text through a .txt file and every time I get to the place where the algorithm adds a Class Object I created it works but then the next time it hits it the previous object gets its data changed to the object currently being added and so on.
Here is a Snippet of code for preface this is inside While loop and nested in 3 if statements.
Question: Why is it overwriting all the other entries?
My logic is 100% working I ran tests on it for over 10 hours with many breakpoints also please go easy on me I'm semi proficient at C#
if (Att == a1)
{
Student s1 = new Student();
s1.Eid = Eid;
s1.Name = Name;
s1.Attempt1 = att1;
AllStudents.Add(s1);
//AllStudents.Add(new Student(Eid,Name, att1));
Eid = line;
Att = "";
qnum = 1;
counter = 1;
}
Here is my Student class
public class Student
{
public string Eid { get; set; }
public string Name { get; set; }
public string[] Attempt1 { get; set; }
public string[] Attempt2 { get; set; }
public string[] Attempt3 { get; set; }
public string[] Att1filler = { "n/a", "n/a", "n/a", "n/a", "n/a", "n/a" };
public string[] Att2filler = {"n/a","n/a","n/a","n/a","n/a","n/a"};
public string[] Att3filler = {"n/a","n/a","n/a","n/a","n/a","n/a"};
public int FinalGrade { get; set; }
public int Grade1 { get; set; }
public int Grade2 { get; set; }
public int Grade3 { get; set; }
public int Grade4 { get; set; }
public Student()
{
FinalGrade = 0;
Attempt1 = Att1filler;
Attempt2 = Att2filler;
Attempt3 = Att3filler;
}
public Student(string Eagid, string name, string[] Att1)
{
Eid = Eagid;
Name = name;
Attempt1 = Att1;
Attempt2 = Att2filler;
Attempt3 = Att3filler;
FinalGrade = 0;
}
public Student(string Eagid, string name, string[] Att1, string[] Att2)
{
Eid = Eagid;
Name = name;
Attempt1 = Att1;
Attempt2 = Att2;
Attempt3 = Att3filler;
FinalGrade = 0;
}
public Student(string Eagid, string name, string[] Att1, string[] Att2, string[] Att3)
{
Eid = Eagid;
Name = name;
Attempt1 = Att1;
Attempt2 = Att2;
Attempt3 = Att3;
FinalGrade = 0;
}
}
And finally this is how I declared my List
public List<Student> AllStudents = new List<Student>();
also the AllStudents.add(new Student(Eid,Name, att1)); is from another solution i found that still did not work for me.
I figured it out. Learned my lesson of passing by references vs passing by value. make sure if your algorithm is looping that anything that is initialized by new and is used inside the loop is re-initialized inside the loop so you don't just pass the same reference for each object.(sorry if this answer isn't 100% I'm running on 2 hours of sleep trying to get this project done!)

Allowing a user to select column headers to import

I'm using LINQtoCSV within a program that allows the user to import an order from a CSV file. I have all the code working however, if the CSV file doesn't have the exact column headers then it doesn't work.
Below is my class that LINQtoCSV reads into -
public class orderProduct
{
public orderProduct() { }
public string product { get; set; }
public string price { get; set; }
public string orderQty { get; set; }
public string value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(price) * Convert.ToDouble(orderQty)).ToString();
}
}
If the CSV file doesn't have the exact headers it won't work. The data I actually only need is the first 4 strings.
Below is my function that actually reads in the data.
private void csvParse()
{
// order.Clear();
string fileName = txt_filePath.Text.ToString().Trim();
try
{
CsvContext cc = new CsvContext();
CsvFileDescription inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
IEnumerable<orderProduct> fromCSV = cc.Read<orderProduct>(fileName, inputFileDescription);
foreach (var d in fromCSV)
{
MessageBox.Show($#"Product:{d.product},Quantity:""{d.orderQty}"",Price:""{d.price}""");
orderReturn.Add(d);
}
this.DialogResult = DialogResult.Yes;
this.Close();
}
catch (Exception ex)
{
if (ex.ToString().Contains("being used by another process"))
{
MessageBox.Show("Error: Please close the file in Excel and try again");
}
else
{
MessageBox.Show(ex.ToString());
}
}
}
I want the user to be able to just pass in a file and then select the relevant columns which relate to the corresponding values and then read in the data ignoring any columns that haven't been selected.
Hope this all makes sense, is something like this possible within LINQtoCSV
You have to add IgnoreUnknownColumns = true to your CsvFileDescription
CSV:
product,price,someColumn,orderQty,value,otherColumn
my product,$123,xx,2,$246,aa
my other product,$10,yy,3,$30,bb
Working code (I modified your code a little bit, to run it in a console)
using System;
using System.Collections.Generic;
using LINQtoCSV;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
csvParse();
Console.ReadLine();
}
private static void csvParse()
{
string fileName = "../../../test.csv"; // provide a valid path to the file
CsvContext cc = new CsvContext();
CsvFileDescription inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true,
IgnoreUnknownColumns = true // add this line
};
IEnumerable<orderProduct> fromCSV = cc.Read<orderProduct>(fileName, inputFileDescription);
foreach (var d in fromCSV)
{
Console.WriteLine($#"Product:{d.product},Quantity:""{d.orderQty}"",Price:""{d.price}""");
}
}
}
public class orderProduct
{
public orderProduct() { }
public string product { get; set; }
public string price { get; set; }
public string orderQty { get; set; }
public string value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(price) * Convert.ToDouble(orderQty)).ToString();
}
}
}
Output:
Product:my product,Quantity:"2",Price:"$123"
Product:my other product,Quantity:"3",Price:"$10"
If your properties have different names than CSV columns, you should use CsvColumn attribute:
public class OrderProduct
{
[CsvColumn(Name = "product")]
public string Product { get; set; }
[CsvColumn(Name = "price")]
public string Price { get; set; }
[CsvColumn(Name = "orderQty")]
public string OrderQuantity { get; set; }
public string Value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(Price) * Convert.ToDouble(OrderQuantity)).ToString();
}
}
Or if you prefer mapping columns by their indices:
public class OrderProduct
{
[CsvColumn(FieldIndex = 0)]
public string Product { get; set; }
[CsvColumn(FieldIndex = 1)]
public string Price { get; set; }
[CsvColumn(FieldIndex = 2)]
public string OrderQuantity { get; set; }
public string Value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(Price) * Convert.ToDouble(OrderQuantity)).ToString();
}
}
If you have to specify the columns on the fly, the only way seems to be to read raw data and process it yourself (the solution is based on this article):
internal class DataRow : List<DataRowItem>, IDataRow
{
}
...
int productColumnIndex = 0; // your users will provide it
var fromCSV = cc.Read<DataRow>(fileName);
foreach (var row in fromCSV)
{
var orderProduct = new OrderProduct
{
Product = row[productColumnIndex].Value,
};
Console.WriteLine(orderProduct.Product);
}

Overload error when adding objects to list

I have written a web service and constructor that adds objects to my list. I am getting an error that makes no sense to me because I am passing in the 3 parameters I should be passing in.
The error is:
There is no argument given that corresponds to the required formal
parameter 'myArticleID' of
'MainPage.GetTileDetails.GetTileDetails(string, string, int)'
Here is my code:
Web Service:
[OperationContract]
List<ViewDetails> ViewDetails();
[DataContract]
public class ViewDetails
{
[DataMember]
public string TitleView { get; set; }
[DataMember]
public string BodyView { get; set; }
[DataMember]
public int ArticleID { get; set; }
public ViewDetails() { }
public ViewDetails(string myTitleView, string myBodyView, int myArticleID)
{
this.TitleView = myTitleView;
this.BodyView = myBodyView;
this.ArticleID = myArticleID;
}
}
Project where i am using web service
public async void ViewData()
{
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
List<GetTileDetails> tileList = new List<GetTileDetails>();
var res = await client.ViewDetailsAsync();
for (int i = 0; i < res.Count; i++)
{
tileList.Add(new GetTileDetails(res[i].TitleView, res[i].BodyView.Substring(0, 170) + " ..."), res[i].ArticleID);
}
tileGridView.ItemsSource = tileList;
}
public class GetTileDetails
{
public string TitleView { get; set; }
public string BodyView { get; set; }
public int ArticleID { get; set; }
public GetTileDetails() { }
public GetTileDetails(string myTitleView, string myBodyView, int myArticleID)
{
this.TitleView = myTitleView;
this.BodyView = myBodyView;
this.ArticleID = myArticleID;
}
}
Can anyone tell me why I am getting that error? I am passing in (string, string, int)....
Replace this line:
tileList.Add(new GetTileDetails(res[i].TitleView, res[i].BodyView.Substring(0, 170) + " ..."), res[i].ArticleID);
with this one:
tileList.Add(new GetTileDetails(res[i].TitleView, res[i].BodyView.Substring(0, 170) + " ...", res[i].ArticleID));
Note you have a misplaced ) right after " ...").

how to get Json nested properties to primary one

I have below scenario:
This is my class structure :
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public System.Collections.ObjectModel.Collection<Likes> Likes { get; set; }
}
public class Likes
{
public string Sport { get; set; }
public string Music { get; set; }
public string Food { get; set; }
public string Place { get; set; }
}
When I serialize object of User class then it will generate the below json string :
{"FirstName":"Naresh",
"LastName":"Parmar",
"Likes": [{"Sport":"Cricket",
"Music":"Classic",
"Food":"Gujarati",
"Place":"India"}]
}
I want to generate above json string like below:
{"FirstName":"Naresh",
"LastName":"Parmar",
"Sport":"Cricket",
"Music":"Classic",
"Food":"Gujarati",
"Place":"India"
}
I want the nested properties as primary one.
Any help would be appreciated.
Thanks in advance..
EDIT:
{"FirstName":"Naresh",
"LastName":"Parmar",
"Sport":"Cricket,Chess,Football",
"Music":"Classic",
"Food":"Gujarati",
"Place":"India"
}
It's really bad practice, since the code i'll post bellow doesn't have great maintainability, however if that's what you looking for, you can use this. Another class that have the format that you'd like, and have a method that adds a list of likes to the format you've required. That the class you should serialize to JSON:
class NestedUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Sport { get; set; }
public string Music { get; set; }
public string Food { get; set; }
public string Place { get; set; }
public void AddLikes(System.Collections.ObjectModel.Collection<Likes> likes)
{
foreach (Likes like in likes)
{
Sport += like.Sport + ",";
Music += like.Music + ",";
Food += like.Food + ",";
Place += like.Place + ",";
}
if (Sport != string.Empty)
{
Sport = Sport.Substring(0, Sport.Length - 1);
}
if (Music != string.Empty)
{
Music = Music.Substring(0, Music.Length - 1);
}
if (Food != string.Empty)
{
Food = Food.Substring(0, Food.Length - 1);
}
if (Place != string.Empty)
{
Place = Place.Substring(0, Place.Length - 1);
}
}
}
Since it's not only limited to Likes objects I'd suggest using dynamic objects. So the User class I propose is as follows:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public dynamic Details { get; set; }
public User()
{
Details = new ExpandoObject();
}
public void AddSingleDetail(string key, string value)
{
var dict = this.Details as IDictionary<string, Object>;
if (dict.ContainsKey(key))
{
dict[key] += "," + value;
}
else
{
dict[key] = value;
}
}
public void AddDetails(object detailsObject)
{
var type = detailsObject.GetType();
foreach (var prop in type.GetProperties())
{
AddSingleDetail(prop.Name, prop.GetValue(detailsObject).ToString());
}
}
}
You can use it for adding single proerpties or adding an object as a whole. I used reflection to get all the property name and values and add them to the user details.
Sample usage:
static void Main(string[] args)
{
var user1 = new User() { FirstName = "Homer", LastName = "Simpson" };
user1.AddSingleDetail("Sport", "Bowling");
user1.AddSingleDetail("Sport", "Sleeping");
user1.AddSingleDetail("Food", "Donut");
user1.AddSingleDetail("Music", "Rock");
string flattenedHomer1 = ConvertUserToFlattenedJson(user1);
var user2 = new User() { FirstName = "Homer", LastName = "Simpson" };
var likes1 = new Likes() { Food = "Donut", Music = "Rock", Place = "Springfield", Sport = "Bowling" };
var likes2 = new Likes() { Food = "Steaks", Music = "Metal", Place = "Evergreen Terrace", Sport = "Sleeping" };
var proStuff = new ProfessionalStuff() { Title = "Boss" };
user2.AddDetails(likes1);
user2.AddDetails(likes2);
user2.AddDetails(proStuff);
string flattenedHomer2 = ConvertUserToFlattenedJson(user2);
}
And the method performing the JSON conversion is:
public static string ConvertUserToFlattenedJson(User u)
{
dynamic flatUser = new ExpandoObject();
flatUser.FirstName = u.FirstName;
flatUser.LastName = u.LastName;
var dict = u.Details as IDictionary<string, Object>;
foreach (var like in dict)
{
((IDictionary<string, Object>)flatUser)[like.Key] = like.Value;
}
string json = Newtonsoft.Json.JsonConvert.SerializeObject(flatUser);
return json;
}
In my sample above user2 is converted to the following JSON string which I believe is what you are looking for:
{
"FirstName": "Homer",
"LastName": "Simpson",
"Sport": "Bowling,Sleeping",
"Music": "Rock,Metal",
"Food": "Donut,Steaks",
"Place": "Springfield,Evergreen Terrace",
"Title": "Boss"
}
While concatenating strings you can check for null or duplicate values. I didn't handle that part.
For the sake of completeness, here's the ProfessionalStuff class I made up:
public class ProfessionalStuff
{
public string Title { get; set; }
}
Hope this helps.

Entity Framework is deleting an Entry

I'm fetching information from a webpage in two pages:
First page:
- Content c1 is created and a Translation c1.t1 is created;
- Content c2 is created and Translation c2.t1 is created;
Second page:
- The system detects that c1 already exists and just adds c1.t2 to the proper table;
- The system detects that c2 already exists and just adds c2.t2 to the proper table;
Somehow, on the second page, the system is overritting c1.t1 with c1.t2 and only the second translation is available on the database. When debbugging, found that it is deletting c1.t1 at some point but I couldn't figure out why.
This is my actual stuff:
EF 4.1
Code-First Aproach
DbContext
I have this POCO Entities (minimized):
RegionalContent: - It's like a tranlation and regional info about a content:
public class XBLRegionalContent
{
[Key, Column(Order = 0)]
public string ContentId { get; set; }
[ForeignKey("ContentId")]
public virtual XBLContent Content { get; set; }
[Key, Column(Order = 1)]
public string RegionId { get; set; }
[ForeignKey("RegionId")]
public virtual XBLRegion Region { get; set; }
public string Name { get; set; }
}
Content: - Unique content per GUID:
public class XBLContent
{
#region [ Properties ]
/// <summary>
/// The GUID
/// </summary>
[Key]
[StringLength(36, ErrorMessage="Must have 36 characters")]
[Required(ErrorMessage="Must have a unique GUID")]
public string GUID { get; set; }
public string Type { get; set; }
public virtual ICollection<XBLRegionalContent> RegionalInfo { get; set; }
}
Region - Pretty straight forward:
public class XBLRegion
{
[Key]
[StringLength(5, ErrorMessage="ID must have 5 characters")]
[Required]
[RegularExpression(#"[a-z]{2}-[A-Z]{2}", ErrorMessage = "ID must be in ISO 639 standard")]
public string ID { get; set; }
public string Country { get; set; }
public string Language { get; set; }
}
DbContext class has nothing different, just DbSets.
One content has many translations. One translation has one content related. The translation primary key is compound of content guid and region id.
I have a class in Model that populates the database and creates a local list that the View uses to display information. That way, I only access the Database one time to save, and don't need to retrieve information when it is saved.
Here is only the important information about this class:
public class XBLChart : IDisposable
{
XBLContentContext db = new XBLContentContext();
private string baseurl = "http://foo.bar/";
public string Locale { get; private set; }
public string HTML { get; private set; }
public string URL { get; set; }
public ContentType Type { get; private set; }
public List<XBLContent> Contents { get; set; }
public XBLChart(ContentType type, string sort, string locale)
{
Type = type;
if (sort == null)
sort = Enum.GetName(typeof(SortBy), SortBy.OfferStartDate);
if (locale != null && locale.Length == 5)
Locale = locale;
else
Locale = "en-US";
URL = baseurl + Locale + "/" + sort;
HTML = FeedUtils.RequestHTML(URL);
Contents = new List<XBLContent>();
PopulateList();
}
private void PopulateList()
{
MatchCollection itens = Regexes.ChartItems().Matches(HTML);
MatchCollection titulos = Regexes.ChartTitles().Matches(HTML);
int type = (int)Type;
int start = type * 12;
this.Title = HttpUtility.HtmlDecode(titulos[type].Groups["title"].Value);
if (titulos.Count < 8 && start > 1)
{
start = (type - 1) * 12;
type--;
}
XBLRegion region;
if (!db.XBLRegions.Any(x => x.ID == Locale))
{
region = new XBLRegion { ID = Locale };
db.XBLRegions.Add(region);
db.SaveChanges();
}
else
region = db.XBLRegions.SingleOrDefault(x => x.ID == Locale);
for (int i = start; i < (start + 2); i++)
{
string guid = itens[i].Groups["guid"].Value;
XBLContent c = new XBLContent(guid);
if (!db.XBLContents.Any(x => x.GUID == guid))
{
c.Type = Type.ToString();
c.PopularInfo(Locale);
db.XBLContents.Add(c);
}
else
c = db.XBLContents.Single(x => x.GUID == c.GUID);
XBLRegionalContent regionalcontent = new XBLRegionalContent(guid, Locale);
if (!db.XBLRegionalInfos.Any(x => x.ContentId == guid && x.RegionId == Locale))
{
if (c.HTML == null)
c.PopularInfo(Locale);
regionalcontent.Populate(c.HTML);
regionalcontent.Name = HttpUtility.HtmlDecode(itens[i].Groups["name"].Value);
db.XBLRegionalInfos.Add(regionalcontent);
}
else
regionalcontent = db.XBLRegionalInfos.Single(x => x.ContentId == guid && x.RegionId == Locale);
db.SaveChanges();
c.RegionalInfo.Clear();
regionalcontent.Region = region;
c.RegionalInfo.Add(regionalcontent);
Contents.Add(c);
}
}
}
you are missing a db.SaveChanges() after
db.SaveChanges();
c.RegionalInfo.Clear();
regionalcontent.Region = region;
c.RegionalInfo.Add(regionalcontent);

Categories