Проблема с чтениям файла с помощью BinaryReader

Работаю над проектом и при чтении файла возникает ошибка. Метод в котором ошибка.

System.FormatException: 'Слишком много байтов в элементе, который должен быть 7-битным элементом Int32 с кодировкой'.

Я понимаю как возникает ошибка и почему она возникает, но я не могу понять как ее исправить. Помогите пожалуйста

public void ReadFile()
{
    using (BinaryReader reader = new BinaryReader(File.Open($@"C:\Users\User\source\repos\Electronic_journal\Electronic_journal\bin\Debug\Teachers.dat", FileMode.Open)))
    {          
        while (reader.PeekChar() > -1)
        {
            CurrentId = reader.ReadInt32();
            Surname = reader.ReadString();
            Name = reader.ReadString();
            Middlename = reader.ReadString();
            Year_of_Birth = reader.ReadInt32();                    
        }
    }
}

Запись в файл

public void WriteFile()
{
    using (BinaryWriter writer = new BinaryWriter(File.Open($@"C:\Users\User\source\repos\Electronic_journal\Electronic_journal\bin\Debug\Teachers.dat", FileMode.OpenOrCreate)))
    {
        writer.Write(CurrentId);
        writer.Write(Surname); 
        writer.Write(Name);
        writer.Write(Middlename);
        writer.Write(Year_of_Birth);
  
        for(int i = 0; i < Discipline.Length; i++)
        {
            writer.Write(Discipline[i]);
        }
   
        for(int i = 0; i < Groups.Length; i++)
        {
            writer.Write(Groups[i]);
        }
        writer.Write(Login);
        writer.Write(Password);    
    }
}

Данные класса

public static string Surname;//Фамилия
public static string Name;//Имя
public static string Middlename;//Очество       
public static int Year_of_Birth;//Год рождения
public static string[] Discipline;
public static string[] Groups;//Группа (можно с буквами)
public static string Login;//Логин
public static string Password;//Пароль
public static int CurrentId = -1;

Ответы (1 шт):

Автор решения: aepot

Обо всех ошибках по порядку.

  • Предположим, что учителей у вас больше одного, поэтому использование static полей недопустимо.
  • Чтобы их всех хранить, нужна коллекция или массив. Самый простой вариант с возможностью удаления и добавления элементов - список List.
  • Ошибку, которую вы указали в вопросе, я не смог воспроизвести. Но думаю, что она заключалась в том, что вы использовали FileMode.OpenOrCreate. В этом режиме, если файл существует, он будет дозаписан в конец, а не перезаписан полностью. Вам нужно было использовать режим FileMode.Create для полной перезаписи файла. А так, вы просто открывали на чтение и читали белиберду, которая была вами туда записана при первом создании файла, после которого код чтения и записи наверное менялся несколько раз.
  • Методы чтения и записи (как указал в комментариях @VladD) должны быть симметричны. То есть формат записываемых данных должен на 100% совпадать с форматом читаемых.
  • Чтобы прочитать массив с помощью BinaryReader, вам потребуется длина массива, иначе вы не сможете узнать, сколько там элементов массива лежит в файле.
  • Пароль в открытом виде хранить нелья, шифровать с ключом безопасности и хранить ключ тоже нельзя, а то будет похоже на запертую дверь с ключом, висящим рядом на крючке. Для решения этой проблемы есть алгоритмы хеширования. Хэш, это когда мы можем данные преобразовать в некий их отпечаток, а обратное преобразование вообще невозможно, только методом полного перебора всех хэшей. А это очень тяжелая для компьютера задача. Самый простой, но не самый надежный алгоритм хэширования - MD5, его я и буду использовать. Как же сравнить пароль с хэшом сохраненного пароля? Все просто, получить хэш введенного пароля и сравнить хэши. Таким образом работает любой сайт или приложение, куда вы вводите свой пароль.

Теперь к делу

Класс, хранящий данные

public class Teacher
{
    private string passwordHash;
    public int Id { get; set; } = -1;
    public string Surname { get; set; }//Фамилия
    public string Name { get; set; } //Имя
    public string Middlename { get; set; } //Очество       
    public int YearOfTheBirth { get; set; } //Год рождения
    public string[] Disciplines { get; set; }
    public string[] Groups { get; set; }//Группа (можно с буквами)
    public string Login { get; set; } //Логин
    public string Password//Пароль
    {
        get => passwordHash;
        set
        {
            MD5 md5 = MD5.Create();
            byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(value));
            passwordHash = Convert.ToBase64String(hash);
        }
    }

    public bool CheckPassword(string password)
    {
        MD5 md5 = MD5.Create();
        byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
        return passwordHash == Convert.ToBase64String(hash);
    }

    public void SetPasswordHash(string hash)
    {
        passwordHash = hash;
    }

    public override string ToString()
    {
        return $"{Id}: {Surname} {Name} {Middlename}, {YearOfTheBirth}, Дисциплины: {string.Join(" ", Disciplines)}, Группы: {string.Join(" ", Groups)}, {Login}:{Password}";
    }
}

Как видно, обеспечение безопасности при хранении пароля происходит прямо здесь. То есть открытый пароль условно даже в оперативной памяти у вас не хранится. Обратите внимание на ассиметричность свойства Password, присваивать ему надо открытый пароль, а отдавать оно вам будет уже хэш. Вы можете переделать как вам понятнее, но я решил сделать вот так.

Список учителей объявляется так. Давайте сразу наполню его тестовыми данными.

List<Teacher> teachers = new List<Teacher>();
teachers.Add(new Teacher
{
    Id = 1,
    Surname = "Базука",
    Name = "Ангелина",
    Middlename = "Петровна",
    YearOfTheBirth = 1960,
    Disciplines = new string[]
    {
        "Геометрия",
        "Алгебра"
    },
    Groups = new string[]
    {
        "Ы18",
        "Э24"
    },
    Login = "a_bazooka",
    Password = "puga4eva"
});
teachers.Add(new Teacher
{
    Id = 2,
    Surname = "Иванова",
    Name = "Наталья",
    Middlename = "Сидоровна",
    YearOfTheBirth = 1981,
    Disciplines = new string[]
    {
        "Физика",
        "Астрономия"
    },
    Groups = new string[]
    {
        "Ы18",
        "У13"
    },
    Login = "n_sidorova",
    Password = "bazookadura"
});

При этом если вы захотите посмотреть сохраненный пароль

Console.WriteLine(teachers[0].Password);

Вы получите такой вывод

0NMTx9+TQ0z4UPpsgS2vkA==

А если програмно захотите сверить введенный пароль пользователя

Console.WriteLine(teachers[0].CheckPassword("puga4eva"));

То вывод будет такой

True

Собственно, цель формирования модели данных достигнута, с данными работать получается. Безопасность хранения паролей не нарушена.

Загрузка и запись.

private static void SaveTeachers(List<Teacher> teachers)
{
    using (BinaryWriter bw = new BinaryWriter(File.Create("teachers.bin")))
    {
        foreach (Teacher teacher in teachers)
        {
            bw.Write(teacher.Id);
            bw.Write(teacher.Surname);
            bw.Write(teacher.Name);
            bw.Write(teacher.Middlename);
            bw.Write(teacher.YearOfTheBirth);
            bw.Write(teacher.Disciplines.Length);
            for (int i = 0; i < teacher.Disciplines.Length; i++)
                bw.Write(teacher.Disciplines[i]);
            bw.Write(teacher.Groups.Length);
            for (int i = 0; i < teacher.Groups.Length; i++)
                bw.Write(teacher.Groups[i]);
            bw.Write(teacher.Login);
            bw.Write(teacher.Password);
        }
    }
}

private static List<Teacher> LoadTeachers()
{
    using (BinaryReader br = new BinaryReader(File.OpenRead("teachers.bin")))
    {
        List<Teacher> teachers = new List<Teacher>();
        while (br.PeekChar() > -1)
        {
            Teacher teacher = new Teacher();
            teacher.Id = br.ReadInt32();
            teacher.Surname = br.ReadString();
            teacher.Name = br.ReadString();
            teacher.Middlename = br.ReadString();
            teacher.YearOfTheBirth = br.ReadInt32();
            teacher.Disciplines = new string[br.ReadInt32()];
            for (int i = 0; i < teacher.Disciplines.Length; i++)
                teacher.Disciplines[i] = br.ReadString();
            teacher.Groups = new string[br.ReadInt32()];
            for (int i = 0; i < teacher.Groups.Length; i++)
                teacher.Groups[i] = br.ReadString();
            teacher.Login = br.ReadString();
            teacher.SetPasswordHash(br.ReadString());
            teachers.Add(teacher);
        }
        return teachers;
    }
}

Здесь должно быть для вас все просто.

Ну и потестируем.

Сохраним данные.

SaveTeachers(teachers);

Загрузим в другой список и выведем в консоль

List<Teacher> loadedTeachers = LoadTeachers();
foreach (Teacher teacher in loadedTeachers)
    Console.WriteLine(teacher);

И получим вот такой вывод в консоль

1: Базука Ангелина Петровна, 1960, Дисциплины: Геометрия Алгебра, Группы: Ы18 Э24, a_bazooka:0NMTx9+TQ0z4UPpsgS2vkA==
2: Иванова Наталья Сидоровна, 1981, Дисциплины: Физика Астрономия, Группы: Ы18 У13, n_sidorova:VGTnUrczl1WDcXENQZo45w==

Как видно, данные успешно загружены.

Кстати, за формат вывода данных в консоль отвечает переопределенный метод ToString() в классе Teacher. Берите на вооружение.

→ Ссылка