C# multi level object and list access - c#

I have no idea what should this question be titled nor keyword to search.
Scenario:
I have a model as below
public class Message { public List<Page> Pages { get; set; }
public class Page { public List<Line> Lines { get; set; }
public class Line { public string Text {get; set; }
When I wanted to insert a Line with Text = "Test" at Page 1, I would need to do the following.
var Message = new Message();
var line = new Line { Text = "Test" };
var page = new Page();
page.Lines.Add(line);
Message.Pages.Add(page);
Question: are there any easier way to achieve this? Eg.
Message.Pages[0].Lines[0].Text = "Test";
Thanks.
Edit: Assumed all properties are properly instantiated in constructors.

var msg = new Message {
Pages = new List<Page> {
new Page {
Lines = new List<Line> { new Line { Text = "Test" } }
}
}
};
Note: if the lists are initialized in their respective constructors, you can remove the new List<> bits:
var msg = new Message {
Pages = {
new Page {
Lines = { new Line { Text = "Test" } }
}
}
};
You could also add an implicit conversion operator from string to Line, in which case:
var msg = new Message {
Pages = {
new Page {
Lines = { "Test" }
}
}
};
Edit: fully working example, including operator and ctor initialization, and multiple pages (see comments):
using System.Collections.Generic;
public class Message {
public List<Page> Pages { get; private set; }
public Message() { Pages = new List<Page>(); }
}
public class Page {
public List<Line> Lines { get; private set; }
public Page() { Lines = new List<Line>(); }
}
public class Line {
public string Text { get; private set; }
public static implicit operator Line(string value) {
return new Line { Text = value };
}
}
static class Program {
static void Main() {
var msg = new Message {
Pages = {
new Page {
Lines = { "Test" }
},
new Page {
Lines = {
"On another page",
"With two lines"
},
}
}
};
}
}

You can create helper methods in your classes, which will provide handy API for building messages and pages. First is Message class - new Add method accepts page to add, and returns message instance. That will allow to use fluent API for adding pages (example at the bottom):
public class Message
{
public Message()
{
Pages = new List<Page>();
}
public List<Page> Pages { get; private set; }
public Message Add(Page page)
{
Pages.Add(page);
return this;
}
}
And page class. I added static creation method, which builds page with any number of lines you pass to this method:
public class Page
{
public Page()
{
Lines = new List<Line>();
}
public List<Line> Lines { get; private set; }
public static Page WithLines(params string[] texts)
{
var page = new Page();
foreach(var text in texts)
page.Lines.Add(new Line { Text = text });
return page;
}
}
Then adding page with lines will look like
message.Add(Page.WithLines("Text1", "Text2"))
.Add(Page.WithLines("Text3"));

What Marc and Sergey said. Also you can consider a simplified builder. I replaced the properties with fields to make the example smaller:
public class Message { public List<Page> Pages = new List<Page>(); }
public class Page { public List<Line> Lines = new List<Line>(); }
public class Line { public string Text; }
Then your builder would look something like this:
public class Typewriter
{
Message message = new Message();
Page currentPage;
public Typewriter NewPage()
{
currentPage = new Page();
message.Pages.Add(currentPage);
return this;
}
public Typewriter AddLine(string text)
{
currentPage.Lines.Add(new Line() { Text = text });
return this;
}
}
Then you can do:
var typewriter = new Typewriter();
typewriter.NewPage().AddLine("First line on first page")
.AddLine("Next line on first page")
.NewPage().AddLine("Next page etc");

Related

How to save/write Task with different attributes

I am trying to build a more advanced ToDoList-Console program. I am learning serialisation but what I do not seem to understand is how to:
Implement 2 kinds of tasks
saving them in a file(does not have to be json)
reading the file while taking the task type into consideration
My goal with that is having 2 Methods: task1 and task2. Task1 being the main task and task2 being a subtask of a task, which would be visualised with \t in the console.
This is my current code that just saves tasks as a string, without any kind of complexity.
public class TodoItem
{
public string Description { get; set; }
public DateTime? DueOn { get; set; }
public override string ToString()
{
return $"{this.Description}";
}
}
internal static class Program
{
static private readonly string _saveFileName = "todo.json";
static void Main()
{
{
// An example list containing 2 items
List<TodoItem> items = new List<TodoItem> {
new TodoItem { Description = "Feed the dog" },
new TodoItem { Description = "Buy groceries" /*, DueOn = new DateTime(2021, 9, 30, 16, 0, 0)*/ }
};
// Serialize it to JSON
string json = JsonSerializer.Serialize(items, new JsonSerializerOptions() { WriteIndented = true });
// Save it to a file
File.WriteAllText(_saveFileName, json);
}
// Loading list
{
string json = File.ReadAllText(_saveFileName);
List<TodoItem> items = JsonSerializer.Deserialize<List<TodoItem>>(json);
// Loading items
foreach (var todo in items)
Console.WriteLine(todo);
}
}```
You can use inheritance to implement two types of tasks.
The rest of your goals seem to work already :)
Here is an Example:
public abstract class TaskBase
{
public string Description { get; set; }
public DateTime? DueOn { get; set; }
public override string ToString()
{
return $"{Description}";
}
}
public class MainTask : TaskBase
{
public SubTask? SubTask { get; set; }
public override string ToString()
{
if (SubTask is not null)
{
return $"{Description}{Environment.NewLine}\t{SubTask}";
}
else
{
return base.ToString();
}
}
}
public class SubTask : TaskBase {}
static class Program
{
private const string _saveFileName = "todo.json";
static void Main()
{
{
// An example list containing 2 items
List<MainTask> items = new List<MainTask>
{
new MainTask { Description = "Feed the dog" },
new MainTask { Description = "Buy groceries", SubTask = new SubTask { Description = "Food" } }
};
// Serialize it to JSON
string json = JsonSerializer.Serialize(items, new JsonSerializerOptions() { WriteIndented = true });
// Save it to a file
File.WriteAllText(_saveFileName, json);
}
// Loading list
{
string json = File.ReadAllText(_saveFileName);
List<MainTask> items = JsonSerializer.Deserialize<List<MainTask>>(json);
// Loading items
foreach (var todo in items)
Console.WriteLine(todo);
}
}
}

C# Scripting with access to objects within application

Hello and still happy Ney Year
I would like to ask you for initial aid. My goal is to write a parser (e.g. source file is a bmecat-xml file and target is an Excel-file) that is dynamic and flexible enough to handle data-conversion even when sourcefile-content changes or user would require additional transformation of data.
I wrote the first part of the parser which loads data from the source-bmecat-file into corresponding classes. The class structure is exposed to the user (by reflection) and the user can map source-fields to target fields.
Where I get stuck is at the moment, when additional logic / conversion needs to be incorporated.
I think Scripting would help me to solve this. the mapping data (source field to target field) could contain an additional script that would be executed dynamically (and hence must have access to application data, especially classes which hold sourcefile and targetfile data).
It would be really great if you could point me to the right direction, to a point, where I can start from.
Thank you very much!
sample-code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace ScriptingDemoProject
{
class DataClass
{
TargetData target;
SourceData source;
MappingData map;
public DataClass()
{
target = new TargetData();
source = new SourceData();
map = new MappingData();
// generate sample data
GenerateData();
// copy source data to target data
ExecuteMapping();
}
public TargetData TargetDataInfo
{ get { return target; } }
public SourceData SourceDataInfo
{ get { return source; } }
public MappingData MappingDataInfo
{ get { return map; } }
private void GenerateData()
{
// add sourcedata
source.Header.DefaultLanguage = "deu";
source.RecipientID = "recipient...";
source.SenderID = "sender...";
SourceItem item = new SourceItem();
item.ItemID = "Item1";
item.ItemNames.AddRange( new List<SourceItemName>() {
new SourceItemName { ItemName = "Item1NameGerman", Languauge = "deu" },
new SourceItemName { ItemName = "Item1NameFrench", Languauge = "fra" }
});
source.Items.Add(item);
// add targetdata
target.AddRec(new List<TargetField>()
{
new TargetField { ColumnID=0, FieldName="ItemNo", FieldValue="Item1"},
new TargetField { ColumnID=1, FieldName="DescrGerman", FieldValue=""},
new TargetField { ColumnID=2, FieldName="DescrFrench", FieldValue=""}
});
target.AddRec(new List<TargetField>()
{
new TargetField { ColumnID=0, FieldName="ItemNo", FieldValue="Item2"},
new TargetField { ColumnID=1, FieldName="DescrGerman", FieldValue=""},
new TargetField { ColumnID=2, FieldName="DescrFrench", FieldValue=""}
});
// add mappinginstructions
map.TargetKeyFieldIndex = 0;
map.MappingFieldInfo.AddRange(new List<MappingFields>() {
new MappingFields { SourceFieldMapping="ItemName", TargetFieldMapping=1, ScriptMapping=#"... where Language=""ger""" },
new MappingFields { SourceFieldMapping="ItemName", TargetFieldMapping=2, ScriptMapping=#"... where Language=""fra""" }
});
// get properties, e.g.
var pInfo = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
}
private void ExecuteMapping()
{
// get target records
foreach (var targetRec in TargetDataInfo.TargetRecords)
{
// get key field value
string itemNo = targetRec.Where(x => x.ColumnID == map.TargetKeyFieldIndex).FirstOrDefault().FieldValue;
// get source item
SourceItem srcItem = SourceDataInfo.Items.Where(x => x.ItemID == itemNo).FirstOrDefault();
if (srcItem == null)
continue;
// get mapping instructions
foreach (var mapInstruction in map.MappingFieldInfo)
{
// i'd like to have two options
// option 1: use script
// option 2: use reflection
// option 1: script
// script will be executed at runtime and gets value from srcItem and sets value in targetRec
string script = mapInstruction.ScriptMapping;
// script would contain / execute the following statements:
TargetField field = targetRec.Where(x => x.ColumnID == mapInstruction.TargetFieldMapping).FirstOrDefault();
field.FieldValue = srcItem.ItemNames.Where(x => x.Languauge == "deu").FirstOrDefault().ItemName;
// option 2: get value by reflection
// e.g.
// though don't know how to handle List<Class>
PropertyInfo pi = SourceDataInfo.GetType().GetProperty("SenderID");
object val = pi.GetValue(SourceDataInfo, null);
// ...
}
}
}
}
public class MappingData
{
List<MappingFields> mappingFields;
public MappingData ()
{
mappingFields = new List<MappingFields>();
}
public int TargetKeyFieldIndex { get; set; }
public List<MappingFields> MappingFieldInfo
{ get { return mappingFields; } }
}
public class MappingFields
{
public string SourceFieldMapping { get; set; }
public int TargetFieldMapping { get; set; }
public string ScriptMapping { get; set; }
}
public class TargetData
{
private List<List<TargetField>> targetRecords;
public TargetData()
{
targetRecords = new List<List<TargetField>>();
}
public List<List<TargetField>> TargetRecords
{ get { return targetRecords; } }
public void AddRec(List<TargetField> TargetFields)
{
targetRecords.Add(TargetFields);
}
}
public class TargetField
{
public string FieldName
{ get; set; }
public int ColumnID
{ get; set; }
public string FieldValue
{ get; set; }
}
public class SourceData
{
private List<SourceItem> sourceItems;
private SourceHeader sourceHeader;
public SourceData()
{
sourceHeader = new SourceHeader();
sourceItems = new List<SourceItem>();
}
public SourceHeader Header
{ get { return sourceHeader; } }
public List<SourceItem> Items
{ get { return sourceItems; } }
public string SenderID
{ get; set; }
public string RecipientID
{ get; set; }
}
public class SourceHeader
{
public string DefaultLanguage
{ get; set; }
}
public class SourceItem
{
private List<SourceItemName> itemNames;
public SourceItem()
{
itemNames = new List<SourceItemName>();
}
public string ItemID
{ get; set; }
public List<SourceItemName> ItemNames
{ get { return itemNames; } }
public SourceItemName GetNameByLang(string Lang)
{
return itemNames.Where(x => x.Languauge == Lang).FirstOrDefault();
}
}
public class SourceItemName
{
public string ItemName
{ get; set; }
public string Languauge
{ get; set; }
}
}

Interface for property Action and Action<T>

I'm trying to create an interface that has a property that might be an Action or an Action<T> as actual implementation. Some of the methods passed in don't require a parameter and some do.
After researching and trying several approaches, here is where I got to:
using System;
using System.Collections.Generic;
namespace InterfaceProperty
{
class Program
{
static void Main(string[] args)
{
var ClassItems = new List<SomeClass>()
{
new SomeClass() { TitleProp = "Foo" },
new SomeClass() { TitleProp = "Bar" }
};
#region Simple
var SimpleActions = new List<SimpleAction>()
{
new SimpleAction() { Title = "Foo", Action = MethodWithoutParam },
new SimpleAction() { Title = "Bar", Action = MethodWithoutParam }
};
foreach (var item in ClassItems)
{
// In "real life" this would be triggered in an event, and the parent loop would not be required.
foreach (var simpleAction in SimpleActions)
{
if (simpleAction.Title == item.TitleProp)
{
simpleAction.Action();
}
}
}
#endregion
#region Complicated
var ComplicatedActions = new List<IGenericAction<Action>>()
{
new GenericAction<Action>() { Title = "Foo", Action = MethodWithoutParam },
new GenericAction<Action<string>>() { Title = "Bar", Action = MethodWithParam } // fails here
};
foreach (var item in ClassItems)
{
// In "real life" this would be triggered in an event, and the parent loop would not be required.
foreach (var genericAction in ComplicatedActions)
{
if (genericAction.Title == item.TitleProp)
{
genericAction.Action();
}
}
}
#endregion
Console.ReadLine();
}
private static void MethodWithoutParam()
{
Console.WriteLine("Method without a parameter");
}
private static void MethodWithParam(string param)
{
Console.WriteLine($"Method with parameter: {param}");
}
}
public class SomeClass
{
public string TitleProp { get; set; }
}
#region Simple
public class SimpleAction
{
public string Title { get; set; }
public Action Action { get; set; }
}
#endregion
#region Complicated
public interface IGenericAction<T>
{
string Title { get; set; }
T Action { get; set; }
}
public class GenericAction<T> : IGenericAction<T>
{
public string Title { get; set; }
public T Action { get; set; }
}
#endregion
}
Having to pass in the T parameter as either an Action or an Action<T> is the issue for creating a list of a generic type of the property Action.
The real implementation of this is used in the process of UI Automation. I am hooking an AutomationElement, and listening to the WindowPattern.WindowOpening event which is where the inner foreach loop would be triggered. The idea is, to have a predefined list of windows to listen for, and a method to call when that window was opened. Some of those methods require a parameter, and some don't- thus Action vs Action<T>.
I could really use a nudge in the right direction.
edit: The simple regions are included because that is the current code, and I'm providing it as a context for where I am trying to get to.
As long as you know the parameter when you construct the GenericAction, you can also write the call to MethodWithParam as:
Action action = () => MethodWithParam("my parameter");
Then you no longer need the T in your GenericAction:
string p1 = "parameter one";
var complicatedActions = new List<IGenericAction>()
{
new GenericAction() { Title = "Foo", Action = MethodWithoutParam },
new GenericAction() { Title = "Bar", Action = () => MethodWithParam(p1) }
};

C# Adding items to MainForm ListBox from a different class

I'm still fairly new to programming, and have started a project where I'm trying to seperate functionality of the program into classes, where each class handles most everything related to that specific part of the program.
I have one class, called DirectoryMonitors, that creates an object that monitors a directory with FileSystemWatcher. I'm trying to add items to a ListBox on the MainForm from an instance of this DirectoryMonitors class. However, it seems I'm unable to call the method in MainForm unless it's static - but if it's static, I can't access my ListBox.
Relevant part of my DirectoryMonitor class:
public class DirectoryMonitorData
{
public bool WatcherActive { get; set; } = true;
public string EQVersion { get; set; }
public string FolderLocation { get; set; }
}
public class DirectoryMonitor : DirectoryMonitorData
{
private void FolderWatcher_Changed(object sender, FileSystemEventArgs e)
{
FileInfo fi = new FileInfo(e.FullPath);
if (!IsFileLocked(fi))
{
int startPos = e.FullPath.LastIndexOf("\\") + 1;
int endPos = e.FullPath.IndexOf("-Inventory") - startPos;
string character = e.FullPath.Substring(startPos, endPos);
MessageBox.Show(character);
string[] delimiters = { ControlChars.Tab.ToString() };
using (TextFieldParser parser = Microsoft.VisualBasic.FileIO.FileSystem.OpenTextFieldParser(e.FullPath, delimiters))
{
// Process the file's lines.
while (!parser.EndOfData)
{
try
{
string[] fields = parser.ReadFields();
MainForm.addLogToListBox(fields[0]);
for (int i = 1; i <= 5; i++)
{
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
}
private bool IsFileLocked(FileInfo file)
{
...
}
}
Relevant part of my MainForm class:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
public void addLogToListBox(string logMessage)
{
logsListBox.Items.Insert(0, logMessage);
}
}
UPDATED CODE:
public FileSystemWatcher FolderWatcher = new FileSystemWatcher();
public DirectoryMonitor()
{
FolderWatcher.NotifyFilter = NotifyFilters.LastWrite;
FolderWatcher.Filter = "*-Inventory.txt";
FolderWatcher.Changed += FolderWatcher_Changed;
}
public void setupDirectoryMonitorList()
{
foreach (DirectoryMonitorData dmd in MainForm.directoryMonitorList)
{
DirectoryMonitor dm = new DirectoryMonitor()
{
WatcherActive = dmd.WatcherActive,
FolderLocation = dmd.FolderLocation,
EQVersion = dmd.EQVersion
};
if (dm.WatcherActive)
{
dm.FolderWatcher.Path = dm.FolderLocation;
dm.FolderWatcher.EnableRaisingEvents = true;
dm.FolderWatcher.NotifyFilter = NotifyFilters.LastWrite;
dm.FolderWatcher.Filter = "*-Inventory.txt";
dm.FolderWatcher.Changed += FolderWatcher_Changed;
}
MainForm.directoryMonitorObjects.Add(dm);
}
}
Add a property to your DirectoryMonitorData class and pass list box to it:
public class DirectoryMonitorData
{
public bool WatcherActive { get; set; } = true;
public string EQVersion { get; set; }
public string FolderLocation { get; set; }
public ListBox Logs {get; set;}
}
and then:
DirectoryMonitor monitor = new DirectoryMonitor { Logs = logsListBox };
now in your class you can simply add anything to that listbox:
Logs.Items.Add(Something);
The way I normally do that is to add a constructor to the class, that takes a 'MainForm' parameter, then save the 'MainForm' parameter in a field.
public class DirectoryMonitor : DirectoryMonitorData
{
public DirectoryMonitor(MainForm form)
{
this.mainForm = form;
}
private MainForm mainForm;
}
Now you can access all public methods an properties of MainForm by using the field mainForm.
Alternative:
Create an eventhandler in your class (with a custom EventArgs). Then from your 'MainForm', subscribe to that event. Now the class does not have to know anything about the form. You just need to Invoke the eventhandler in your class.

.net create object instance from abstract class

I have the following abstract class:
public abstract class TemplateBase
{
public abstract string TemplateName { get; }
public string RuntimeTypeName { get { return GetType().FullName; } }
public abstract List<AreaContainer> TemplateAreas { get; }
}
then these 2 inherited classes:
public class SingleColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Single column"; } }
public AreaContainer CenterColumn { get; private set; }
public SingleColumnTemplate()
{
this.CenterColumn = new AreaContainer("Middle");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.CenterColumn };
}
return this.templateAreas;
}
}
}
and
public class TwoColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Two column"; } }
public AreaContainer LeftColumn { get; private set; }
public AreaContainer RightColumn { get; private set; }
public TwoColumnTemplate()
{
LeftColumn = new AreaContainer("Left");
RightColumn = new AreaContainer("Right");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.LeftColumn, this.RightColumn };
}
return this.templateAreas;
}
}
}
I also have this class that is my model for editing:
public class ContentPage
{
public virtual int ContentPageId { get; set; }
public virtual string Title { get; set; }
public TemplateBase Template { get; set; }
}
Question:
for my ActionResults I have the following:
[HttpGet]
public ActionResult Edit()
{
var row = new ContentPage();
var template = new TwoColumnTemplate();
// Areas
HtmlArea html_left = new HtmlArea();
html_left.HtmlContent = "left area html content";
HtmlArea html_right = new HtmlArea();
html_right.HtmlContent = "right area html content";
template.LeftColumn.Areas.Add(html_left);
template.RightColumn.Areas.Add(html_right);
row.Template = template;
return View(row);
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ContentPage row)
{
// Here i could loop through List -TemplateAreas and save each template Area to Db. I guess that would work
return this.View(row);
}
Question:
For HttpGet- how would I load row Template from the database? since it could be SingleColumnClass or TwoColumnClass.
how would my ViewModel look like to solve this?
thanks
You can write your own Model Binder that is responsible for binding TemplateBase. You will still need to have a way of knowing (in the model binder) which type you will be using a runtime, but you can always delegate that to a factory or service locator of some sort. I did a quick google search and here is a blog post I found that gives you some information for making a model binder for a similar scenario:
http://weblogs.asp.net/bhaskarghosh/archive/2009/07/08/7143564.aspx
EDIT: The blog leaves out how you tell MVC about your model binder. When the application starts, you can add your model binder to System.Web.Mvc.ModelBinders.Binders
HTH
You need to know the template type in you controller, so you can pass a parameter from the view to the controller, indicating the type (SingleColumn or TwoColumn). You could do this witn a Enum:
public enum TemplateType
{
SingleColumn,
TwoColumn
}
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template;
if (templateType == TemplateType.SingleColumn)
{
template = new SingleColumnTemplate();
}
else
{
template = new TwoColumnTemplate();
}
...
return View(row);
}
When you create the action link from your view you can specify:
<%= Html.ActionLink("Edit",
"Edit",
"YouController",
new
{
// singlecolumn or twocolumn
// depending on your concrete view
TemplateType = TemplateType.xxx
},
null);
I wonder if you could do something like this?
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template = (TemplateBase)Activator.CreateInstance(templateType);
...
return View(row);
}
templateType would have to be the exact name of your inherited classes (you can ignore case)

Categories

Resources