Common Interfaces for Semi-Special Report

I am trying to create a simple reporting tool in which the user can select from a set of KPI parameters, diagrams, aggregate functions and other parameters, click a button, and then the wcf service is called, which then returns the user model with all the data. This can then be displayed in an MVC / WPF application (there may be both).

Since users can be from several countries, I want to use data annotations to portray all numbers and headers according to the language formats and the numbers that the current user is using.

Downloading data and all this stuff is working fine, no problem. In addition, I use data annotations, so all language / culture settings are taken into account. Problems arise when I try to put all the data in the model that I want to display to the user.

What I'm trying to do is report class containing a set of columns. Each column can be a list of int / double / values ​​.... Now, since I am dealing with WCF and the above explanation follows (as I understand it) the use of generics, I assume that I can use [KnownType] or [ServiceKnownType] for class / FOS operations, while actually using the base type or interface as the return value. I have never tried this, but I found some useful explanations that seem to me pretty logical, so I assume that I will not have any big problems for this part (at least I hope not).

Currently, my interfaces as such (simplified to focus on the actual problem that I have):

public interface IReport<T> where T: IConvertible { ICollection<IColumn<T>> Columns { get; set; } } public interface IColumn<T> where T: IConvertible { ICollection<IValue<T>> Values { get; set; } } public interface IValue<T> where T: IConvertible { T Value { get; set; } } 

Since the value in each column can be int / double / ..., I assume that I should have an actual class only for the value (I don't think I can use the data annotation attribute for the collection type) as such:

 public class IntValue: IValue<int> { [DisplayFormat(DataFormatString = "{0:#,##0;-#,##0;'---'}", ApplyFormatInEditMode = true)] public int Value { get; set; } } 

Of course, this looks strange, because you can just make it a common Value class that implements IValue and execute with it, but if I do a stupid thing and create a class for every possible type (now when I type it it sounds really bad, I I know), I can use the DisplayFormat attribute and do not have to worry about how it will be presented to the user, it will always be suitable.

Now for classes implementing IColumn and IReport, this is simple:

 public class Report<T>: IReport<T> where T: IConvertible { public ICollection<IColumn<T>> Columns { get; set; } public Report() { Columns=new List<IColumn<T>>(); } } public class Column<T>: IColumn<T> where T: IConvertible { public ICollection<IValue<T>> Values { get; set; } public Column() { Values = new List<IValue<T>>(); } } 

From the list of interfaces and classes, you will immediately see that this makes it impossible to have a report in which some columns have other types. Thus, it is impossible to create a report in which some columns are int, some of them double ... Since the general restriction in IReport forces you to specify the type, you are stuck with this for all columns, since it extends to the value of each column ... And this is exactly what i want really.

I feel like I'm not going anywhere, and I probably missed something really simple, so I could be pushed in the right direction.

TL DR: How to get a general collection in a non-generic type?

+5
source share
2 answers

Well, I took into account the proposed solutions and implemented the option as shown below. I understand that I don’t want to use generics too much, but it still annoyed me. In the end, I need columns (or values) of several types. This is what generics have. In addition, I wanted to provide a built-in mechanism for formatting fields.

I left the IReport and IColumn interfaces pretty simple, but I do not belong to the IValue interface in the IColumn interface. Instead, I use the abstract class Value, in which I define the basic structure for formatting and retrieving data (in string format).

Between the actual IntValue / DoubleValue base class and the Value base class, I added a common Value class that implements a common IValue interface that does nothing but provide a Data field, so I don't need to do this in the IntValue / DoubleValue classes and implement the AsFormattedString method, which uses the usual ToString method using Formatter, which I create in the constructor of the base class Value.

The actual implementation of this formatting is provided in IntValue / DoubleValue classes and makes it possible to use the standard format, which I have already hard-coded, or the custom one provided by the user of the class.

 public interface IReport { ICollection<IColumn> Columns { get; set; } } public interface IColumn { ICollection<Value> Values { get; set; } } public interface IValue<T> where T: IConvertible { T Data { get; set; } } public abstract class Value { #region Formatting protected IFormatProvider Formatter { get; set; } protected abstract IFormatProvider GetFormatter(); protected abstract string AsFormattedString(); public override string ToString() { return AsFormattedString(); } #endregion public Value() { Formatter = GetFormatter(); } } public abstract class Value<T>: Value, IValue<T> where T: IConvertible { #region IValue members public T Data { get; set; } #endregion #region Formatting protected override string AsFormattedString() { return Data.ToString(Formatter); } #endregion } public class IntValue: Value<int> { public IntValue() { } public IntValue(string formatstring, int data) { Formatter = new IntFormatter(formatstring); Data = data; } #region Formatting protected override IFormatProvider GetFormatter() { return new IntFormatter(); } internal class IntFormatter: CustomFormatter { public IntFormatter() : this("{0:#,##0;-#,##0;'---'}") { } public IntFormatter(string formatstring) : base(formatstring) { } } #endregion } public class DoubleValue: Value<double> { public DoubleValue() { } public DoubleValue(string formatstring, double data) { Formatter = new DoubleFormatter(formatstring); Data = data; } #region Formatting protected override IFormatProvider GetFormatter() { return new DoubleFormatter(); } internal class DoubleFormatter: CustomFormatter { public DoubleFormatter() : this("{0:0.#0;-0.#0;'---'}") { } public DoubleFormatter(string formatstring) : base(formatstring) { } } #endregion } public class ReportView: IReport { public ICollection<IColumn> Columns { get; set; } public ReportView() { Columns = new List<IColumn>(); } } public class ReportColumn: IColumn { public ICollection<Value> Values { get; set; } public ReportColumn() { Values = new List<Value>(); } } 

It is used as such:

  // Creating a report IReport report = new ReportView(); // Adding columns IColumn mycolumn = new ReportColumn(); mycolumn.Values.Add(new IntValue() { Data = 1 }); mycolumn.Values.Add(new DoubleValue() { Data = 2.7 }); mycolumn.Values.Add(new IntValue("{0:#,##0;-#,##0;'---'}", 15)); mycolumn.Values.Add(new DoubleValue("{0:0.#0;-0.#0;'---'}", 2.9)); report.Columns.Add(mycolumn); // Looping through each column, and get each value in the formatted form foreach(var column in report.Columns) { foreach(var value in column.Values) { value.ToString(); } } 

If something is added / fixed about this, I would be happy to hear. I will check the visitor pattern that was outlined above the Binary Worrier and check the whole setup. Let me know if I make a silly or bad choice pls design! I will probably have to change it left and right a bit to provide one single format for the entire column without having to provide it with each value, but the basic structure is there, I think.

+2
source

I think that using generics for types will lead you crazy. I have not spent much time figuring out exactly what is wrong with using generics. Because I don’t think you need generics at all.

The report just needs a list of columns; it doesn't care about column types

 interface IReport { IEnumerable<IColumn> Columns {get;} } 

A column just needs a list of values, and indeed, as soon as the values ​​can take care of themselves, they don't care about the types of values.

 interface IColumn { IEnumerable IValue Values {get;} } 

The value just needs to be able to display it itself (perhaps just as a string or, perhaps, "draw" itself in a given rectangle, etc.)

 interface IValue { string AsString(); } 

You may have an introduced implementation of Value for different types ( IntValue , DoubleValue , etc.), and as soon as they implement the IValue interface, you laugh.

It makes sense?

+1
source

Source: https://habr.com/ru/post/1239795/


All Articles