2016-01-04 13 views
5

Próbuję utworzyć proste narzędzie do raportowania, w którym użytkownik może wybierać z zestawu wskaźników KPI, wykresów, funkcji zagregowanych i innych parametrów, kliknąć przycisk, po czym usługa wcf jest wywoływana, a następnie zwraca niestandardowy model ze wszystkimi danymi. Następnie można go wyświetlić w aplikacji MVC/WPF (może to być jedno i drugie).Ogólne interfejsy dla raportu półdokładowego

Ponieważ użytkownicy mogą pochodzić z wielu krajów, chcę użyć adnotacji danych, aby wyodrębnić wszystkie numery i nagłówki w sposób dostosowany do formatu językowego i liczbowego, do którego przyzwyczaił się bieżący użytkownik.

Ładowanie danych i wszystko to działa dobrze, bez problemu. Używam też adnotacji do danych, więc wszystkie ustawienia dotyczące języka/kultury są zadbane. Problemy pojawiają się, gdy próbuję umieścić wszystkie dane w modelu, który chcę wyświetlić użytkownikowi.

Co próbuję zrobić, to klasa Report, która zawiera kolekcję kolumn. Każda kolumna może być listą wartości int/double/... Teraz, od kiedy mam do czynienia z WCF i powyższe wyjaśnienie implikuje (o ile rozumiem) użycie generycznych, zakładam, że mogę użyć [KnownType] lub [ServiceKnownType] dla klas/wcf operacji, podczas gdy faktycznie używając typ bazowy lub interfejs jako wartość zwracana. Nigdy tak naprawdę nie próbowałem tego, ale znalazłem kilka dobrych wyjaśnień, które wydają mi się logiczne, więc zakładam, że nie będę miał dużych problemów w tej części (przynajmniej mam taką nadzieję).

Teraz moje interfejsy są jako takie (uproszczony skupić się na rzeczywisty problem mam):

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

Ponieważ wartość w każdej kolumnie może być int/double/... Zakładam muszę mieć rzeczywistą klasę tylko dla wartości (nie sądzę, można użyć atrybutu adnotacji dane na temat rodzaju zbiórki), takie jak:

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

oczywiście, że wygląda dziwnie, bo może po prostu sprawiają, że jest to klasa ogólna Wartość, która implementuje IValue i zostanie z nią wykonana, ale jeśli zrobię to głupie i utwórz klasę dla każdego możliwego typu (teraz, kiedy ją rozpisuję, to brzmi naprawdę źle, wiem), mogę użyć atrybutu DisplayFormat i nie muszę się martwić o sposób, w jaki będzie on prezentowany użytkownikowi, to " Zawsze będę odpowiedni.

Teraz dla klas, które implementują iColumn i iReport, że jest prosta:

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

Z listy interfejsów i klas, od razu widać, że to sprawia, że ​​niemożliwe, aby mieć raport, gdzie niektóre kolumny mieć inne typy. Więc nie jest możliwe utworzenie raportu, w którym niektóre kolumny są int, niektóre są podwójne, ... Ponieważ ogólne ograniczenie w IReport pozwala określić typ, utkniesz z tym dla wszystkich kolumn, ponieważ propaguje się do wartości każda kolumna ... I tego właśnie chcę naprawdę.

Czuję, że nigdzie się nie dostaję, a prawdopodobnie brakuje mi czegoś naprawdę prostego, więc doceniłbym szturchnięcie we właściwym kierunku.

TL; DR: Jak uzyskać ogólną kolekcję w typie nietypowym?

+0

Nie możesz utworzyć raportu jako 'nowy IReport '? – Baldrick

+0

O ile widzę teraz, to nie pozwala mi dodać int lub podwójnej kolumny, ponieważ oczekuje, że będą one IConvertible, a nie JAKIEKOLWIEK klasę wykonawczą IConvertible. –

Odpowiedz

2

W porządku, zaczerpnąłem inspirację z sugerowanych rozwiązań i wprowadziłem odmianę, jak poniżej. Rozumiem, że nie chcę zbytnio używać generycznych, ale nadal mnie to irytuje. W końcu chcę kolumn (lub wartości) kilku typów. Po to są generics. Ponadto chciałem udostępnić wbudowany mechanizm zapewniający formatowanie pól.

Zostawiłem interfejs IReport i IColumn całkiem prosto, ale nie odwołuję się do interfejsu IValue w interfejsie IColumn. Zamiast tego używam wartości klasy abstrakcyjnej, w której definiuję niektóre podstawowe ramy do formatowania i pobierania danych (w formacie łańcuchowym).

Pomiędzy rzeczywistą wartością IntValue/DoubleValue a wartością podstawową Value dodałem ogólną klasę Value, która implementuje ogólny interfejs IValue, który nie zawiera nic innego niż podanie pola Data, więc nie muszę tego robić w klasie IntValue/Klasy DoubleValue i implementuj metodę AsFormattedString, która używa normalnej metody ToString z użyciem formatera, który utworzę w konstruktorze klasy baseclass.

Rzeczywista implementacja tego formatyzatora jest dostępna w klasach IntValue/DoubleValue i daje możliwość użycia standardowego formatu, który już został zakodowany lub niestandardowego dostarczonego przez użytkownika klasy.

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

Jest używany jako takie:

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

Jeśli jest coś do dodania/skorygowany o tym, byłbym zadowolony, aby usłyszeć. Sprawdzę wzorzec Odwiedzających, który został wskazany w powyższym przykładzie przez Binary Worrier, i przetestuję całą konfigurację. Daj mi znać, jeśli zrobię głupie lub kiepskie wybory projektowe! Prawdopodobnie będę musiał zmienić to trochę w lewo i prawo, aby zapewnić jednolity format dla całej kolumny bez konieczności podawania jej do każdej wartości, ale podstawowa struktura jest tam, jak sądzę.

1

Myślę, że używanie leków generycznych dla tych typów doprowadzi was do szaleństwa. Nie spędziłem zbyt wiele czasu na ocenienie, co jest złego w używaniu leków generycznych. . . ponieważ nie wierzę, że w ogóle potrzebujesz leków generycznych.

Raport prostu potrzebuje listę kolumn, nie dbają o typach kolumn

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

Kolumna musi tylko listę wartości, a tak naprawdę, gdy wartości mogą dbać o siebie , nie dba o typy wartości.

interface IColumn 
{ 
    IEnumerable IValue Values {get;} 
} 

Wartość prostu musi być w stanie uczynić swój własny (ewentualnie tylko jako ciąg, ewentualnie do „rysować” jego własny w danym prostokącie etc)

interface IValue 
{ 
    string AsString(); 
} 

można mieć wpisane Implementacja wartości dla różnych typów (IntValue, DoubleValue itp.), a po wdrożeniu interfejsu IValue się śmiejesz.

Czy to ma sens?

+0

Tak, dziękuję. Spróbuję to naprawdę szybko. Zakładam, że część z adnotacją danych przechodzi do rzeczywistej implementacji IValue. Co jednak, jeśli chcę wymusić na użytkownikach klasy faktyczne podanie typów wartości? W tej chwili to tylko zestaw strun, który z pewnością działa. Ale co jeśli chcę zacząć używać tych danych w obliczeniach, ...? –

+0

W dół linii spojrzałbym na [wzór użytkownika] (https: // en.wikipedia.org/wiki/Visitor_pattern) do implementacji różnych agregacji i obliczeń na danych. Po wprowadzeniu wartości znanego interfejsu użytkownik może zrobić to, co chce, z wynikami wywołań interfejsu. na przykład masz gościa "DoubleSum", który przechodzi przez IValue i próbuje Zsumować ich wartości. Jeśli nie może przymuszać żadnej wartości do podwójnej wartości, może wyrzucić lub traktować tę komórkę jako zero, lub co tylko zechcesz. –