2016-01-04 16 views
5

Sto provando a creare un semplice strumento di reporting, in cui un utente può selezionare da un insieme di KPI, grafici, funzioni di aggregazione e altri parametri, fare clic su un pulsante, dopo di che un servizio wcf viene chiamato, che restituisce un modello personalizzato con tutti i dati. Questo potrebbe quindi essere visualizzato in un'applicazione MVC/WPF (potrebbe essere entrambi).Interfacce generiche per report semi-ad hoc

Poiché gli utenti potrebbero provenire da diversi paesi, desidero utilizzare le annotazioni di dati per ritrarre tutti i numeri e le intestazioni in un modo adatto alla lingua e ai formati numerici a cui è abituato l'utente corrente.

Il caricamento dei dati e tutto il resto funziona perfettamente, nessun problema. Inoltre, io uso le annotazioni dei dati, quindi tutte le impostazioni specifiche per lingua e cultura sono prese in considerazione. I problemi iniziano quando provo a inserire tutti i dati nel modello che voglio mostrare all'utente.

Quello che sto cercando di fare è avere una classe Report, che contenga una collezione di colonne. Ogni colonna potrebbe essere un elenco di valori int/double/.... Ora, dato che ho a che fare con WCF e la spiegazione di cui sopra implica (per quanto ho capito) l'uso dei generici, presumo di poter usare il [KnownType] o [ServiceKnownType] per le operazioni classes/wcf, mentre effettivamente uso un tipo di base o interfaccia come valore di ritorno. Non ho mai provato questo, ma ho trovato alcune buone spiegazioni che mi sembrano abbastanza logiche, quindi presumo che non avrò grandi problemi per questa parte (almeno spero di no).

In questo momento, i miei interfacce sono in quanto tali (semplificato per concentrarsi sul problema reale che ho):

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; } } 

Poiché il valore in ogni colonna potrebbe essere un int/doppia/..., presumo devo avere una classe reale solo per il valore (non credo che posso utilizzare un attributo di dati di annotazione su un tipo di raccolta), in quanto tale:

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

Naturalmente, che sembra strano, dal momento che si può solo rendilo un valore generico di classe che implementa IValue e sii fatto con esso, ma se faccio la cosa sciocca e fai una lezione per ogni tipo possibile (ora che la digito, suona davvero male, lo so), posso usare l'attributo DisplayFormat e non devo preoccuparmi del modo in cui si presenterà all'utente, ' Sarò sempre appropriato.

Ora, per le classi che implementano iColumn e IReport, che è semplice:

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>>(); } 
} 

Dall'elenco di interfacce e classi, vi renderete subito conto che questo rende impossibile avere un rapporto in cui alcune colonne avere altri tipi Quindi non è possibile creare un report in cui alcune colonne sono int, alcune sono doppie, ... Poiché il vincolo generico in IReport ti fa specificare un tipo, sei bloccato con quello per tutte le colonne, poiché si propaga al valore di ogni colonna ... E questo è esattamente ciò che voglio davvero.

Mi sento come se non dovessi arrivare da nessuna parte, e probabilmente mi manca qualcosa di veramente semplice, quindi una spinta nella giusta direzione sarebbe apprezzata.

TL; DR: Come si ottiene una raccolta generica in un tipo non generico?

+0

Non riesci a creare il tuo rapporto come 'nuovo IReport '? – Baldrick

+0

Per quanto posso ora vedere, questo non mi consente di aggiungere una colonna int o doppia, poiché si aspetta che siano IConvertibili, e non QUALSIASI classe di implementazione IConvertible. –

risposta

2

Bene, ho preso ispirazione dalle soluzioni suggerite e implementato una variante come di seguito. Capisco di non voler usare troppo i farmaci generici, ma mi ha comunque infastidito. Dopotutto, voglio colonne (o valori) di diversi tipi. È quello per cui i generali ci sono. Inoltre, volevo fornire un meccanismo integrato per fornire la formattazione dei campi.

Ho lasciato le interfacce IReport e IColumn piuttosto semplici, ma non mi riferisco a un'interfaccia IValue nell'interfaccia IColumn. Invece, io uso una classe astratta Value in cui definisco parte del framework di base per la formattazione e il recupero dei dati (in formato stringa che è).

Tra l'IntValue/DoubleValue effettivo e la classe di base Value, ho aggiunto una classe Value generica che implementa l'interfaccia IValue generica che non fa altro che fornire il campo Data, quindi non devo farlo in IntValue/Classi DoubleValue e implementa il metodo AsFormattedString, che utilizza il normale metodo ToString utilizzando il programma di formattazione che creo nel costruttore Value baseclass.

L'implementazione effettiva di tale formattatore viene fornita nelle classi IntValue/DoubleValue e offre la possibilità di utilizzare un formato standard già codificato in modo rigido o personalizzato fornito dall'utente della classe.

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>(); } 
} 

E 'usato come tale:

// 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(); } 
    } 

Se c'è qualcosa da aggiungere/correggere su questo, sarei felice di sentire. Verificherò il pattern Visitor che è stato suggerito da Binary Worrier e testerò l'intera configurazione. Fammi sapere se faccio scelte di design stupide o scadenti! Probabilmente dovrò modificarlo a destra ea sinistra per fornire un unico formato per l'intera colonna senza doverlo fornire a ciascun valore, ma credo che il framework di base sia lì.

1

Penso che usare i generici per i tipi ti farà impazzire. Non ho passato molto tempo a valutare esattamente cosa c'è di sbagliato nel tuo uso dei farmaci generici. . . perché non credo che tu abbia bisogno di generici.

Il rapporto ha solo bisogno di un elenco di colonne, che non si preoccupa dei tipi di colonne

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

La colonna solo bisogno di un elenco di valori, e in realtà, una volta che i valori possono badare a se stessi , non si preoccupa dei tipi dei valori.

interface IColumn 
{ 
    IEnumerable IValue Values {get;} 
} 

Il valore deve solo essere in grado di rendere la sua auto (forse solo come una stringa, o, eventualmente, per "disegnare" la sua auto in un rettangolo dato etc)

interface IValue 
{ 
    string AsString(); 
} 

Si può avere una Implementazione del valore tipizzata per diversi tipi (IntValue, DoubleValue ecc.) e una volta implementata l'interfaccia IValue stai ridendo.

Ha senso?

+0

Lo fa, lo fa, grazie. Ci proverò molto velocemente. Presumo che la parte relativa all'annotazione dei dati passi quindi alla reale implementazione di IValue. Tuttavia, cosa succede se voglio forzare l'utente delle classi a fornire effettivamente tipi di valore? In questo momento è solo un insieme di stringhe, che funziona sicuramente. Ma cosa succede se voglio iniziare a usare questi dati nei calcoli, ...? –

+0

In fondo alla riga guarderei il [modello visitatore] (https: // en.wikipedia.org/wiki/Visitor_pattern) per implementare diverse aggregazioni e calcoli sui dati. Una volta che i valori implementano un'interfaccia conosciuta, il visitatore può fare ciò che gli piace con i risultati delle chiamate sull'interfaccia. per esempio. hai un visitatore "DoubleSum", che passa attraverso IValues ​​e prova a Sum i loro valori. Se non può forzare alcun valore a un doppio, può lanciare o trattare quella cella come zero, o qualunque cosa ti piaccia. –

Problemi correlati