Serializacja w c# - dobre praktyki

0

Witam
Jak powszechnie wiadomo, za pisanie klas z publicznymi polami na wszelkich forach programistycznych obowiązuje kara śmierci ;)
Tu pojawia się moje pytanie o poprawny sposób implementacji serializacji takiej klasy przy użyciu np. XMLSerializer :
Czy najlepszą metodą jest stworzenie nowej klasy jedynie do serializacji o takich samych polach, tyle że publicznych i skopiowanie wartości do niej?

I problem 2:
XmlSerializer umożliwia mi łatwą serializację listy obiektów, gdzie przy pomocy atrybutów typu [XmlAtribute], [XmlElement], [XmlRoot] mogę sobie dowolnie dostosować wygląd dokumentu xml. Jednak żeby użyć tych atrybutów potrzebuję nowej klasy z polem typu lista obiektów do serializacji.
I tu powstaje pytanie: czy jest możliwość zastosowania tych atrybutów bez tworzenia dodatkowej klasy z listą czyli np. zastosowanie ich do "List<SerializableObject> list" tworzonej jako zwykła lista a nie klasa z polem typu lista?

Jakbym miał strzelać, to raczej nie ma takiej możliwości, bo bez odpowiedniej klasy z listą nie da się przeprowadzić deserializacji gdy atrybuty w xml są inaczej ponazywane - ale wolę się zapytać.

0

Jak powszechnie wiadomo, za pisanie klas z publicznymi polami na wszelkich forach programistycznych obowiązuje kara śmierci.

Bez przesady. Wtedy przeca nie działałaby serializacja.

Czy najlepszą metodą jest stworzenie nowej klasy jedynie do serializacji o takich samych polach, tyle że publicznych i skopiowanie wartości do niej?

To bez sensu. Po prostu tworzy się obiekty, które będą przechowywać rzeczy podlegające serializacji. Żadnego przepisywania klas się nie robi tylko wrzuca się od razu rzeczy podlegające serializacji do takiej publicznej klasy. Klasą z publicznymi danymi może być np. klasa stanu sesji pomiędzy serwerem, a klientem. Taką klasę serializuje się i wysyła do sieci po czym druga strona sobie to deserializuje i nie przejmuje się tym jak wyciągnąć dane z takiego obiektu, bo to się robi niejako samoczynnie. W jednym ze swoich projektów miałem np. taką klasę do szyfrowania danych między końcówkami w sieci:

        public class Cryptogram {
            public byte[] Signature { get; set; }
            public byte[] EncryptedMessage { get; set; }
            public byte[] EncryptedAESKey { get; set; }
            public string ServerPublicKey { get; set; }
            public string ClientPublicKey { get; set; }
        }

Po uzupełnieniu danych całą taką klasę wrzucałem zserializowaną w strumień, a druga strona sobie odczytywała to do identycznego obiektu. Chodzi o wygodę, bo nie trzeba martwić się o format i ułożenie danych w obiekcie. Wszystko wygląda tak jak zostało zdefiniowane w klasie.

czy jest możliwość zastosowania tych atrybutów bez tworzenia dodatkowej klasy z listą czyli np. zastosowanie ich do "List<serializableobject> list"...

Jeżeli chcesz serializować kolekcję to przecież zawsze możesz podać do serializacji obiekt typeof(List<CustomClass>); Jeżeli CustomClass jest klasą publiczną to jej publiczne pola zostaną zapisane do XML'a z całej kolekcji.

0

Dzięki za odpowiedź.

Zastanawia mnie dostępność tych publicznych pól poza chwilami gdy zachodzi serializacja. Jeśli zawierają ważne informacje, które nie powinny być modyfikowane poza wyjątkowymi sytuacjami, to lepiej byłoby je pozostawić jako prywatne. Może konkretny przykład:
Mam klase Account

public class Account
{
private string _name;
private string _login;
private string _password;
// + jakieś właściwości i metody
}
 

Obiekty tej klasy powinny być modyfikowane tylko przy wczytaniu z pliku np. xml i przy pomocy edytora kont. Następnie są używane w trakcie działania programu gdzie średni czas życia takiego obiektu to ok 40 min - sporo czasu żeby publiczne pola mogły zostać zmodyfikowane, czy to przez przypadek, nieuwagę programisty, cokolwiek. Natomiast serializacja/deserializacja listy takich obiektów następuje sporadycznie. Nie lepiej jest wtedy mieć osobną klasę o takich samych polach tyle że publicznych przeznaczoną tylko do serializacji/deserializacji do pliku xml raz/dwa razy w ciągu działania programu? W tym wypadku zależy mi głównie na bezpieczeństwie i niezmienności danych w obiekcie. Oczywiście można by się uprzeć i zaimplementować ręcznie serializację nie przejmując się czy pole jest prywatne czy nie, ale gdy dostępne są gotowe narzędzia to po co się męczyć?

A co do serializacji list to mogę tak zrobić, ale wtedy nazwy elementów w pliku xml będą przyjmowały nazwę klasy obiektów serializowanych. Nie mogę wtedy też zmienić nazwy [XmlRoot] bo lista jest tworzona tuż przed serializacją i nie mam jak zdefiniować nazw elementów innych niż te występujące w CustomClass. Kawałek kodu:

 
public class CustomClass
{
   [XmlAttribute]
   public string Name { get; set; }
   [XmlAttribute]
   public string Login { get; set; }
   [XmlAttribute]
   public string Password { get; set; }
}

[XmlRoot("Root")]
public class ListSerializable
{
   [XmlArray ("AccountList")]
   [XmlArrayItem("Account")]
   public List<CustomClass> AccountList { get; set; }
}

// i serializacja
XmlSerializer serializer = new XmlSerializer(typeof(ListSerializable));
using (StreamWriter writer = new StreamWriter(filePath))
{
   serializer.Serialize(writer, listSerializable);
}

i plik wynikowy wygląda tak:

 
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <AccountList>
    <Account Name="nazwa" Login="login1" Password="haslo" />
  </AccountList>
</Root>

Pytanie teraz takie: jak osiągnąć identyczny wynik bez korzystania z klasy ListSerializable a tylko z List<CustomClass> czli jak użyć atrybutów [XmlArray ("AccountList")] i [XmlArrayItem("Account")] poza klasą na samej liście.

0

Czyli chcesz zrobić obiekt edytowalny i nie edytowalny zewnątrz jednocześnie? :) Po co dane takie jak login/hasło itp. walają się po systemie przez 40 minut? A jak ktoś uzna że zrobi sobie zmianę na na haśle w kodzie to gratuluje. :D

0
nexusone napisał(a):

Obiekty tej klasy powinny być modyfikowane tylko przy wczytaniu z pliku np. xml i przy pomocy edytora kont. Następnie są używane w trakcie działania programu gdzie średni czas życia takiego obiektu to ok 40 min - sporo czasu żeby publiczne pola mogły zostać zmodyfikowane, czy to przez przypadek, nieuwagę programisty, cokolwiek. Natomiast serializacja/deserializacja listy takich obiektów następuje sporadycznie. Nie lepiej jest wtedy mieć osobną klasę o takich samych polach tyle że publicznych przeznaczoną tylko do serializacji/deserializacji do pliku xml raz/dwa razy w ciągu działania programu? W tym wypadku zależy mi głównie na bezpieczeństwie i niezmienności danych w obiekcie.

Masz rację, Twoje podejście jest bezpieczniejsze, a ponadto zgodne z dobrymi praktykami (np. SRP) - bo reprezentowanie obiektu w trakcie działania aplikacji, a przechowywanie jego stanu na dysku, to dwie różne odpowiedzialności. Jest to dodatkowy wysiłek, ale moim zdaniem warto, bo prawdopodobnie zmniejszy czas przeznaczony na szukanie błędów w kodzie.

Przy czym, nie wiem, czy to jest dobre w tym konkretnym przypadku, klasa trzymająca loginy i hasła w pliku XML jest sama w sobie niebezpieczna.

0

Klasa tymczasowo jest serializowana do xml bo w czasie pisania aplikacji łatwiej jest się bawić z plikiem xml niż binarnym - szczególnie komuś niedoświadczonemu jak ja. Z czasem pewnie zmienię to na plik binarny. Tu akurat wyszedłem z założenia, że zasady dotyczące serializacji w XmlSerializer i w BinaryFormatter będą podobne.

DibbyDum napisał(a):

Czyli chcesz zrobić obiekt edytowalny i nie edytowalny zewnątrz jednocześnie? :) Po co dane takie jak login/hasło itp. walają się po systemie przez 40 minut? A jak ktoś uzna że zrobi sobie zmianę na na haśle w kodzie to gratuluje. :D

Aco do przetrzymywania obiektu z loginem i hasłem przez długi czas to przechowuje on dane wykorzystywane przez zewnętrzny program, który ponadto nie udostępnia żadnego interfejsu do komunikacji. Muszę się do niego dostać przez manipulację jego procesem i WinApi i właściwie nigdy nie wiem kiedy to hasło i login będzie potrzebne, a operacje związane z obiektem trwają właśnie około tych 40 min. Obie wartości przesyłane są przez wywołanie eventów klawiatury w WinApi.
Bezpieczniejsze rozwiązanie to pewnie przechowywanie tego obiektu identyfikując go po id a doczytanie hasła i loginu z pliku dopiero gdy jest potrzebne. Jednak program ma działać lokalnie i wątpię żeby istnienie tych danych w obiekcie przez cały czas działania aplikacji było dużym zagrożeniem. Równie prawdopodobne jak przechwycenie ich z pamięci procesu jest przechwycenie przez jakiegoś keyloggera podczas edycji.
A co do bezpieczeństwa danych jako prywatnych a nie publicznych to chodzi mi o zabezpieczenie ich przed nierozważnym programistą (czyli mną ;) ewentualnie kimś kto mógłby projekt rozwijać w przyszłości). Gdyby były to pola publiczne gdzieś w kodzie przez przypadek mógłbym im przypisać nową wartość i potem szukać błędu. Z tego samego powodu ich wartości są ustawiane przez konstruktor lub odpowiednią metodę a nie przez setter powiązanej właściwości. Doświadczony programista nie potrzebuje tego typu zabezpieczeń, ale wolę tu dmuchać na zimne.

1 użytkowników online, w tym zalogowanych: 0, gości: 1