From 13ba5c064904555eb748632795361812705323bb Mon Sep 17 00:00:00 2001 From: DavidMakarov Date: Fri, 4 Oct 2024 12:20:51 +0400 Subject: [PATCH 1/2] 1 of 3 comp done --- Components/SaveToPdf.cs | 2 ++ TestingForm/FormMain.Designer.cs | 19 +++++++++++++++- TestingForm/FormMain.cs | 38 ++++++++++++++++++++++++++++++++ TestingForm/FormMain.resx | 3 +++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Components/SaveToPdf.cs b/Components/SaveToPdf.cs index 7ac3f44..7663bf4 100644 --- a/Components/SaveToPdf.cs +++ b/Components/SaveToPdf.cs @@ -2,6 +2,7 @@ using MigraDoc.DocumentObjectModel.Tables; using MigraDoc.Rendering; using Components.SaveToPdfHelpers; +using System.Text; namespace Components { @@ -13,6 +14,7 @@ namespace Components public void CreateDoc(string title, string path, List tables) { _document = new Document(); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); DefineStyles(_document); _section = _document.AddSection(); diff --git a/TestingForm/FormMain.Designer.cs b/TestingForm/FormMain.Designer.cs index 05ec2ff..c7d5542 100644 --- a/TestingForm/FormMain.Designer.cs +++ b/TestingForm/FormMain.Designer.cs @@ -28,18 +28,35 @@ /// private void InitializeComponent() { + components = new System.ComponentModel.Container(); + userControlTableDocument1 = new Components.UserControlTableDocument(components); + button1 = new Button(); SuspendLayout(); // + // button1 + // + button1.Location = new Point(12, 12); + button1.Name = "button1"; + button1.Size = new Size(188, 65); + button1.TabIndex = 0; + button1.Text = "Сохранить первый компонент"; + button1.UseVisualStyleBackColor = true; + button1.Click += button1_Click; + // // FormMain // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(800, 450); + Controls.Add(button1); Name = "FormMain"; - Text = "cB"; + Text = "Testing form"; ResumeLayout(false); } #endregion + + private Components.UserControlTableDocument userControlTableDocument1; + private Button button1; } } diff --git a/TestingForm/FormMain.cs b/TestingForm/FormMain.cs index 80839c4..6310531 100644 --- a/TestingForm/FormMain.cs +++ b/TestingForm/FormMain.cs @@ -6,5 +6,43 @@ namespace TestingForm { InitializeComponent(); } + + private void button1_Click(object sender, EventArgs e) + { + using var dialog = new SaveFileDialog + { + Filter = "pdf|*.pdf", + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + + List tables = new List(); + string[][] table1 = new string[2][]; + table1[0] = new string[] { "test", "test 2" }; + table1[1] = new string[] { "test next", "test next 2" }; + + string[][] table2 = new string[3][]; + table2[0] = new string[] { "ttt", "ttt 2", "ttt 3" }; + table2[1] = new string[] { "ttt next", "ttt next 2" }; + table2[2] = new string[] { "next" }; + + tables.Add(table1); + tables.Add(table2); + + try + { + userControlTableDocument1.SaveToDocument( + " ", + dialog.FileName, + tables + ); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + MessageBox.Show(""); + } } } diff --git a/TestingForm/FormMain.resx b/TestingForm/FormMain.resx index af32865..c5b82f3 100644 --- a/TestingForm/FormMain.resx +++ b/TestingForm/FormMain.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file From d803f6226f748b78a9adaab681678b030edd294d Mon Sep 17 00:00:00 2001 From: DavidMakarov Date: Wed, 16 Oct 2024 01:47:44 +0400 Subject: [PATCH 2/2] 3 of 3 comps done --- Components/Components.csproj | 1 - Components/Nonvisual/LegendAlignment.cs | 16 ++ ...ntrolConfigurableTableDocument.Designer.cs | 36 ++++ .../UserControlConfigurableTableDocument.cs | 41 +++++ .../Nonvisual/UserControlHist.Designer.cs | 36 ++++ Components/Nonvisual/UserControlHist.cs | 42 +++++ .../Nonvisual/UserControlTableDocument.cs | 2 +- Components/SaveToPdf.cs | 171 +++++++++++++++++- .../SaveToPdfHelpers/PdfChartParameters.cs | 18 ++ .../SaveToPdfHelpers/PdfRowParameters.cs | 4 + TestingForm/FormMain.Designer.cs | 30 +++ TestingForm/FormMain.cs | 68 +++++++ TestingForm/FormMain.resx | 6 + TestingForm/TestUser.cs | 24 +++ 14 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 Components/Nonvisual/LegendAlignment.cs create mode 100644 Components/Nonvisual/UserControlConfigurableTableDocument.Designer.cs create mode 100644 Components/Nonvisual/UserControlConfigurableTableDocument.cs create mode 100644 Components/Nonvisual/UserControlHist.Designer.cs create mode 100644 Components/Nonvisual/UserControlHist.cs create mode 100644 Components/SaveToPdfHelpers/PdfChartParameters.cs create mode 100644 TestingForm/TestUser.cs diff --git a/Components/Components.csproj b/Components/Components.csproj index 3b33f9a..d501559 100644 --- a/Components/Components.csproj +++ b/Components/Components.csproj @@ -8,7 +8,6 @@ - diff --git a/Components/Nonvisual/LegendAlignment.cs b/Components/Nonvisual/LegendAlignment.cs new file mode 100644 index 0000000..c7971ba --- /dev/null +++ b/Components/Nonvisual/LegendAlignment.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Components.Nonvisual +{ + public enum LegendAlignment + { + Top, + Bottom, + Left, + Right + } +} diff --git a/Components/Nonvisual/UserControlConfigurableTableDocument.Designer.cs b/Components/Nonvisual/UserControlConfigurableTableDocument.Designer.cs new file mode 100644 index 0000000..375d59a --- /dev/null +++ b/Components/Nonvisual/UserControlConfigurableTableDocument.Designer.cs @@ -0,0 +1,36 @@ +namespace Components.Nonvisual +{ + partial class UserControlConfigurableTableDocument + { + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/Components/Nonvisual/UserControlConfigurableTableDocument.cs b/Components/Nonvisual/UserControlConfigurableTableDocument.cs new file mode 100644 index 0000000..d4c169d --- /dev/null +++ b/Components/Nonvisual/UserControlConfigurableTableDocument.cs @@ -0,0 +1,41 @@ +using MigraDoc.DocumentObjectModel.Tables; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Components.Nonvisual +{ + public partial class UserControlConfigurableTableDocument : Component + { + public UserControlConfigurableTableDocument() + { + InitializeComponent(); + } + + public UserControlConfigurableTableDocument(IContainer container) + { + container.Add(this); + + InitializeComponent(); + } + + public void SaveToDocument(string filePath, string title, + List<(double width, string header, string propName)> columns, + double headerRowHeight, double rowHeight, List data) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("Empty string instead of path to file"); + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException("Title string is empty"); + if (data.Count == 0) throw new ArgumentNullException("Data list is empty"); + if (columns.Count == 0) throw new ArgumentNullException("Column info list is empty"); + if (rowHeight == 0D || headerRowHeight == 0D) throw new ArgumentNullException("Row height is empty"); + + SaveToPdf saver = new SaveToPdf(); + + saver.CreateConfigurableTableDocument(filePath, title, columns, headerRowHeight, rowHeight, data); + } + } +} diff --git a/Components/Nonvisual/UserControlHist.Designer.cs b/Components/Nonvisual/UserControlHist.Designer.cs new file mode 100644 index 0000000..de82aa1 --- /dev/null +++ b/Components/Nonvisual/UserControlHist.Designer.cs @@ -0,0 +1,36 @@ +namespace Components.Nonvisual +{ + partial class UserControlHist + { + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/Components/Nonvisual/UserControlHist.cs b/Components/Nonvisual/UserControlHist.cs new file mode 100644 index 0000000..1bf0a63 --- /dev/null +++ b/Components/Nonvisual/UserControlHist.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Components.Nonvisual +{ + public partial class UserControlHist : Component + { + public UserControlHist() + { + InitializeComponent(); + } + + public UserControlHist(IContainer container) + { + container.Add(this); + + InitializeComponent(); + } + + public void CreateHist(string filePath, string title, string histTitle, + LegendAlignment alignment, Dictionary data) + { + if (string.IsNullOrEmpty(filePath)) + throw new ArgumentException("Filename cannot be empty"); + if (string.IsNullOrEmpty(title)) + throw new ArgumentException("Title cannot be empty"); + if (string.IsNullOrEmpty(histTitle)) + throw new ArgumentException("Hist title cannot be empty"); + if (data.Count == 0) + throw new ArgumentException("Data cannot be empty"); + + SaveToPdf saver = new SaveToPdf(); + saver.CreateHist(filePath, title, histTitle, alignment, data); + } + } +} diff --git a/Components/Nonvisual/UserControlTableDocument.cs b/Components/Nonvisual/UserControlTableDocument.cs index a1ce594..c05bb07 100644 --- a/Components/Nonvisual/UserControlTableDocument.cs +++ b/Components/Nonvisual/UserControlTableDocument.cs @@ -30,7 +30,7 @@ namespace Components SaveToPdf saver = new SaveToPdf(); - saver.CreateDoc(filePath, header, tables); + saver.CreateTableDocumentWithContext(filePath, header, tables); } } } diff --git a/Components/SaveToPdf.cs b/Components/SaveToPdf.cs index 7663bf4..2f9dd31 100644 --- a/Components/SaveToPdf.cs +++ b/Components/SaveToPdf.cs @@ -3,6 +3,10 @@ using MigraDoc.DocumentObjectModel.Tables; using MigraDoc.Rendering; using Components.SaveToPdfHelpers; using System.Text; +using System.Security.Cryptography; +using MigraDoc.DocumentObjectModel.Shapes.Charts; +using Components.Nonvisual; +using System.IO; namespace Components { @@ -11,7 +15,7 @@ namespace Components private Document? _document; private Section? _section; private Table? _table; - public void CreateDoc(string title, string path, List tables) + public void CreateTableDocumentWithContext(string title, string path, List tables) { _document = new Document(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); @@ -51,6 +55,102 @@ namespace Components renderer.RenderDocument(); renderer.PdfDocument.Save(path); } + + public void CreateConfigurableTableDocument(string path, string title, + List<(double width, string header, string propName)> columns, double headerRowHeight, + double rowHeight, List data) + { + _document = new Document(); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + DefineStyles(_document); + _section = _document.AddSection(); + + CreateParagraph(new PdfParagraph + { + Text = title, + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + + CreateTable(columns.Select(c => c.width).ToList()); + + CreateRow(new PdfRowParameters + { + Texts = columns.Select(c => c.header).ToList(), + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Center, + RowHeight = headerRowHeight, + }); + + foreach (var obj in data) + { + List cells = new List(); + + for (int i = 0; i < columns.Count; i++) + { + var propName = columns[i].propName; + var prop = typeof(T).GetProperty(propName); + if (prop is null) + { + throw new ArgumentException($"Failed to find property {prop} in provided objects class"); + } + var propVal = prop.GetValue(obj)?.ToString(); + if (propVal is not null) + { + cells.Add(propVal); + } + } + + CreateRow(new PdfRowParameters + { + Texts = cells, + Style = "Normal", + ParagraphAlignment = PdfParagraphAlignmentType.Left, + FirstCellStyle = "NormalTitle", + FirstCellParagraphAlignment = PdfParagraphAlignmentType.Center, + RowHeight = rowHeight, + }); + } + + CreateParagraph(new PdfParagraph()); + + var renderer = new PdfDocumentRenderer(true) + { + Document = _document + }; + renderer.RenderDocument(); + renderer.PdfDocument.Save(path); + } + + public void CreateHist(string filePath, string title, string histTitle, + LegendAlignment alignment, Dictionary data) + { + _document = new Document(); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + DefineStyles(_document); + _section = _document.AddSection(); + + CreateParagraph(new PdfParagraph + { + Text = title, + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + + CreateChart(new PdfChartParameters + { + Data = data, + Title = histTitle, + LegendAlignment = alignment, + }); + + var renderer = new PdfDocumentRenderer(true) + { + Document = _document + }; + renderer.RenderDocument(); + renderer.PdfDocument.Save(filePath); + } private static ParagraphAlignment GetParagraphAlignment(PdfParagraphAlignmentType type) { return type switch @@ -92,6 +192,19 @@ namespace Components _table.AddColumn(elem); } } + + protected void CreateTable(List columnWidths) + { + if (_document == null) + { + return; + } + _table = _document.LastSection.AddTable(); + foreach (var elem in columnWidths) + { + _table.AddColumn(Unit.FromCentimeter(elem)); + } + } protected void CreateRow(PdfRowParameters rowParameters) { if (_table == null) @@ -102,18 +215,70 @@ namespace Components for (int i = 0; i < rowParameters.Texts.Count; ++i) { row.Cells[i].AddParagraph(rowParameters.Texts[i]); - if (!string.IsNullOrEmpty(rowParameters.Style)) + if (i == 0 && !string.IsNullOrEmpty(rowParameters.FirstCellStyle)) + { + row.Cells[i].Style = rowParameters.FirstCellStyle; + } + else if (!string.IsNullOrEmpty(rowParameters.Style)) { row.Cells[i].Style = rowParameters.Style; } + if (i == 0 && rowParameters.FirstCellParagraphAlignment != null) + { + row.Cells[i].Format.Alignment = GetParagraphAlignment((PdfParagraphAlignmentType)rowParameters.FirstCellParagraphAlignment); + } + else + { + row.Cells[i].Format.Alignment = GetParagraphAlignment(rowParameters.ParagraphAlignment); + } + if (rowParameters.RowHeight is not null) + { + row.Height = Unit.FromCentimeter((double)rowParameters.RowHeight); + } Unit borderWidth = 0.5; row.Cells[i].Borders.Left.Width = borderWidth; row.Cells[i].Borders.Right.Width = borderWidth; row.Cells[i].Borders.Top.Width = borderWidth; row.Cells[i].Borders.Bottom.Width = borderWidth; - row.Cells[i].Format.Alignment = GetParagraphAlignment(rowParameters.ParagraphAlignment); row.Cells[i].VerticalAlignment = VerticalAlignment.Center; } } + + protected void CreateChart(PdfChartParameters chartParams) + { + if (_section == null) + return; + + Chart chart = new Chart(ChartType.Bar2D); + chart.Width = "15cm"; + chart.Height = "8cm"; + + foreach (var dataSeries in chartParams.Data) + { + Series series = chart.SeriesCollection.AddSeries(); + series.Name = dataSeries.Key; + series.Add(dataSeries.Value.Select(x => (double)x).ToArray()); + } + + chart.TopArea.AddParagraph(chartParams.Title); + + chart.XAxis.MajorTickMark = TickMarkType.Outside; + chart.YAxis.MajorTickMark = TickMarkType.Outside; + chart.YAxis.HasMajorGridlines = true; + + _ = chartParams.LegendAlignment switch + { + LegendAlignment.Top => chart.TopArea.AddLegend(), + LegendAlignment.Right => chart.RightArea.AddLegend(), + LegendAlignment.Bottom => chart.BottomArea.AddLegend(), + LegendAlignment.Left => chart.LeftArea.AddLegend(), + _ => chart.BottomArea.AddLegend(), + }; + + chart.PlotArea.LineFormat.Width = 1; + chart.PlotArea.LineFormat.Visible = true; + + _section.Add(chart); + } } } diff --git a/Components/SaveToPdfHelpers/PdfChartParameters.cs b/Components/SaveToPdfHelpers/PdfChartParameters.cs new file mode 100644 index 0000000..4a3d033 --- /dev/null +++ b/Components/SaveToPdfHelpers/PdfChartParameters.cs @@ -0,0 +1,18 @@ +using Components.Nonvisual; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Components.SaveToPdfHelpers +{ + internal class PdfChartParameters + { + public Dictionary Data = new(); + + public string Title = string.Empty; + + public LegendAlignment LegendAlignment = LegendAlignment.Bottom; + } +} diff --git a/Components/SaveToPdfHelpers/PdfRowParameters.cs b/Components/SaveToPdfHelpers/PdfRowParameters.cs index 76435a6..a540d6d 100644 --- a/Components/SaveToPdfHelpers/PdfRowParameters.cs +++ b/Components/SaveToPdfHelpers/PdfRowParameters.cs @@ -5,6 +5,10 @@ namespace Components.SaveToPdfHelpers { public List Texts { get; set; } = new(); public string Style { get; set; } = string.Empty; + + public double? RowHeight { get; set; } public PdfParagraphAlignmentType ParagraphAlignment { get; set; } + public string FirstCellStyle { get; set; } = string.Empty; + public PdfParagraphAlignmentType? FirstCellParagraphAlignment { get; set; } } } diff --git a/TestingForm/FormMain.Designer.cs b/TestingForm/FormMain.Designer.cs index c7d5542..790b31a 100644 --- a/TestingForm/FormMain.Designer.cs +++ b/TestingForm/FormMain.Designer.cs @@ -31,6 +31,10 @@ components = new System.ComponentModel.Container(); userControlTableDocument1 = new Components.UserControlTableDocument(components); button1 = new Button(); + button2 = new Button(); + userControlConfigurableTableDocument1 = new Components.Nonvisual.UserControlConfigurableTableDocument(components); + button3 = new Button(); + userControlHist1 = new Components.Nonvisual.UserControlHist(components); SuspendLayout(); // // button1 @@ -43,11 +47,33 @@ button1.UseVisualStyleBackColor = true; button1.Click += button1_Click; // + // button2 + // + button2.Location = new Point(218, 12); + button2.Name = "button2"; + button2.Size = new Size(188, 65); + button2.TabIndex = 1; + button2.Text = "Сохранить второй компонент"; + button2.UseVisualStyleBackColor = true; + button2.Click += button2_Click; + // + // button3 + // + button3.Location = new Point(433, 12); + button3.Name = "button3"; + button3.Size = new Size(188, 65); + button3.TabIndex = 2; + button3.Text = "Сохранить третий компонент"; + button3.UseVisualStyleBackColor = true; + button3.Click += button3_Click; + // // FormMain // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(800, 450); + Controls.Add(button3); + Controls.Add(button2); Controls.Add(button1); Name = "FormMain"; Text = "Testing form"; @@ -58,5 +84,9 @@ private Components.UserControlTableDocument userControlTableDocument1; private Button button1; + private Button button2; + private Components.Nonvisual.UserControlConfigurableTableDocument userControlConfigurableTableDocument1; + private Button button3; + private Components.Nonvisual.UserControlHist userControlHist1; } } diff --git a/TestingForm/FormMain.cs b/TestingForm/FormMain.cs index 6310531..9d6d3ad 100644 --- a/TestingForm/FormMain.cs +++ b/TestingForm/FormMain.cs @@ -1,3 +1,5 @@ +using Components.Nonvisual; + namespace TestingForm { public partial class FormMain : Form @@ -44,5 +46,71 @@ namespace TestingForm } MessageBox.Show(""); } + + private void button2_Click(object sender, EventArgs e) + { + using var dialog = new SaveFileDialog + { + Filter = "pdf|*.pdf", + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + List users = new List + { + new TestUser(1, "user 1", 20, 50000), + new TestUser(2, "user 2", 19, 30000), + new TestUser(3, "user 3", 26, 70000) + }; + + try + { + userControlConfigurableTableDocument1.SaveToDocument( + dialog.FileName, + " ", + new List<(double width, string header, string propName)> + { + (2.0, "ID", "Id"), + (6.0, "", "Name"), + (2.0, "", "Age"), + (6.0, "", "Salary"), + }, + 7.0, + 2.0, + users + ); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + MessageBox.Show(""); + } + + private void button3_Click(object sender, EventArgs e) + { + using var dialog = new SaveFileDialog + { + Filter = "pdf|*.pdf", + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + Dictionary data = new Dictionary + { + { "test 1", new int[] {1, 2, 3, 6, 7, 9} }, + { "test 2", new int[] {6, 8, 11, 16, 4, 2} }, + }; + + try + { + userControlHist1.CreateHist(dialog.FileName, "", "Test", LegendAlignment.Bottom, data); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + MessageBox.Show(""); + } } } diff --git a/TestingForm/FormMain.resx b/TestingForm/FormMain.resx index c5b82f3..df6383c 100644 --- a/TestingForm/FormMain.resx +++ b/TestingForm/FormMain.resx @@ -120,4 +120,10 @@ 17, 17 + + 222, 17 + + + 496, 17 + \ No newline at end of file diff --git a/TestingForm/TestUser.cs b/TestingForm/TestUser.cs new file mode 100644 index 0000000..7cc5612 --- /dev/null +++ b/TestingForm/TestUser.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestingForm +{ + public class TestUser + { + public int Id { get; private set; } + public string Name { get; private set; } + public int Age { get; private set; } + public int Salary { get; private set; } + + public TestUser(int id, string name, int age, int salary) + { + Id = id; + Name = name; + Age = age; + Salary = salary; + } + } +}