How to loop through a file to get data?

987 Views Asked by At

I am trying to make a basic login and sign up c# console application, however, I need to loop through my file to see if the username and password the user inputted has a match when logging in. If the user types in a username and password, I want my code to go through my file to check if it is an existing username and password

Here is my code:

[Serializable]
public class Users
{
    public string UserName;
    public string Password;

    public Users(string userName, string password)
    {
        UserName = userName;
        Password = password;
    }
}

public class SaveToFile
{
    public static void SerializeSignUpDetails(string userName, string password)
    {
        Users obj = new Users(userName, password);
        IFormatter formatter = new BinaryFormatter();
        Stream stream = new FileStream("SignUp.txt", FileMode.Append, FileAccess.Write);
        formatter.Serialize(stream, obj);
        stream.Close();
    }
    public static Users DeserializeSignUpDetails()
    {
        Stream stream = new FileStream("SignUp.txt", FileMode.Open, FileAccess.Read);
        IFormatter formatter = new BinaryFormatter();
        Users objnew = (Users)formatter.Deserialize(stream);
        stream.Close();
        return objnew;
    }
}

public static void Main(string[] args)
{
    Console.WriteLine("To Login Type 1, To Create a new account Type 2");
    int LogInOrSignUp;
    do
    {
        int.TryParse(Console.ReadLine(), out LogInOrSignUp);
    } while (LogInOrSignUp != 1 && LogInOrSignUp != 2);

    string userName = "";
    string password = "";
    bool successfull = false;
    Users userDetails = SaveToFile.DeserializeSignUpDetails();
    while (!successfull)
    {
        if (LogInOrSignUp == 1)
        {
            Console.WriteLine("Write your username:");
            userName = Console.ReadLine();
            Console.WriteLine("Enter your password:");
            password = Console.ReadLine();
            if (userName == userDetails.UserName && password == userDetails.Password)
            {
                Console.WriteLine("You have logged in successfully!");
                successfull = true;
                break;
            }
            if (!successfull)
            {
                Console.WriteLine("Your username or password is incorect, try again!");
            }
        }

        else if (LogInOrSignUp == 2)
        {
            Console.WriteLine("Enter a username:");
            userName = Console.ReadLine();

            Console.WriteLine("Enter a password:");
            password = Console.ReadLine();

            successfull = true;
            SaveToFile.SerializeSignUpDetails(userName, password);
        }
    }
}

I want to use foreach to loop through my file, but I am not sure how.

Any help appreciated!

3

There are 3 best solutions below

6
AudioBubble On BEST ANSWER

To keep a login record for multiple entries using serialization, you need to serialize a list of objects. In your case, you could create a couple of serializable classes, User class that encapsulates the data of a single entry, and Users class that contains a List<User> objects plus the data manipulation methods.

✔ Note: Name as you prefer.

Namespaces to import

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

The User class

[Serializable]
public class User
{
    public string UserName { get; set; }
    public string Password { get; set; }
    //More details...

    public User(string userName, string password)
    {
        UserName = userName;
        Password = password;
    }

    public override string ToString() => $"{UserName}, {Password}";
}

The Users class

[Serializable]
public class Users
{
    public readonly List<User> Accounts;

    public Users() => Accounts = new List<User>();

    public void Save(string filePath)
    {
        if (string.IsNullOrEmpty(filePath)) return;

        var bf = new BinaryFormatter();
        using (var fs = new FileStream(filePath, FileMode.Create))
            bf.Serialize(fs, this);
    }

    public static Users Load(string filePath)
    {
        if (!File.Exists(filePath)) return null;

        var bf = new BinaryFormatter();
        using (var sr = new FileStream(filePath, FileMode.Open))
            return bf.Deserialize(sr) as Users;
    }

    public bool ContainsUserName(string userName) => 
        Accounts.Any(x => x.UserName == userName);

    public bool ContainsAccount(string userName, string pass) =>
        Accounts.Any(x => x.UserName == userName && x.Password == pass);

    public User Get(string userName, string pass) =>
        Accounts.FirstOrDefault(x => x.UserName == userName && x.Password == pass);

    public bool Add(string userName, string pass)
    {
        if (ContainsUserName(userName)) return false;

        Accounts.Add(new User(userName, pass));
        return true;
    }
}

In your implementation, to create, load, and save your data:

//Load...
users = Users.Load(dataFilePath);

//Or create new object...
if (users is null)
    users = new Users();

//and when it comes to save...
users.Save(dataFilePath);

Use the ContainsUserName method to find out whether a given username is already exist so you can avoid the duplicates. The Add method will do the same plus it will add the valid new entries into the list. The Get method searches the list for the given username and password and returns a User object if any otherwise null, and the ContainsAccount method will do the same if you don't need to return a User object.

var user = users.Get("user", "pass");
if (user is null)
    Console.WriteLine("Incorrect username and/or password...");

//or
if (!users.ContainsAccount("user", "pass"))
    Console.WriteLine("Incorrect username and/or password...");

Applying that in your main:

public static void Main(string[] args)
{
    Console.WriteLine("To Login Type 1, To Create a new account Type 2");
    int LogInOrSignUp;
    do
    {
        int.TryParse(Console.ReadLine(), out LogInOrSignUp);
    } while (LogInOrSignUp != 1 && LogInOrSignUp != 2);

    var filePath = Path.Combine(AppContext.BaseDirectory, "SignUp.dat");
    var userName = "";
    var password = "";
    var successfull = false;
    var userDetails = Users.Load(filePath);

    if (userDetails is null)
        userDetails = new Users();

    while (!successfull)
    {
        if (LogInOrSignUp == 1)
        {
            Console.WriteLine("Write your username:");
            userName = Console.ReadLine();
            Console.WriteLine("Enter your password:");
            password = Console.ReadLine();
            if (userDetails.ContainsAccount(userName, password))
            {
                Console.WriteLine("You have logged in successfully!");
                successfull = true;
                break;
            }
            else
                Console.WriteLine("Your username or password is incorect, try again!");
        }

        else //if (LogInOrSignUp == 2)
        {
            Console.WriteLine("Enter a username:");
            userName = Console.ReadLine();

            if (userDetails.ContainsUserName(userName))
                Console.WriteLine("The username is taken. Try another one.");
            else
            {
                Console.WriteLine("Enter a password:");
                password = Console.ReadLine();

                successfull = true;
                userDetails.Add(userName, password);
                userDetails.Save(filePath);
                Console.WriteLine($"A new account for {userName} has been created.");
            }
        }
    }
    Console.ReadLine();
}

✔ Note: Better to use the switch statement to select LogInOrSignUp instead of the if statements

SOQ62185878

2
Louis Go On

Since OP provided detail about multiple credentials in one file. Most common way is serialization. Binary or xml are okay.

Related topics: Saving Data Structures in C#

However version compatible might be your next question.

Version tolerant serialization might solve your problem.

====== Assuming you have a bunches of txt in a folder.

Two things must be done.

  1. A function accept a file path argument.
  2. A function helping you to iterate through all files.

Rewrite DeserializeSignUpDetails to take filepath.

        public static Users DeserializeSignUpDetails( string szPath)
        {
            Stream stream = new FileStream( szPath, FileMode.Open, FileAccess.Read);
            IFormatter formatter = new BinaryFormatter();
            Users objnew = (Users)formatter.Deserialize(stream);
            stream.Close();
            return objnew;
        } 

Loop through files and get all login credential. It could be placed in your mainprogram.

        List<Users> GetAllLoginCredential()
        {
            List<Users> list = new List<Users>();
            string[] files = Paths.GetFileName( "D:\\YourDirectory" );
            foreach( var path in files ){
                Users user = SaveToFile.DeserializeSignUpDetails( path );
                list.Add( user );
            }
        }

Then you may check each users. Also you might want to cache it to prevent repetitively opening files.

By the way, Users has only one user info, you might want it to be User.

2
MBB On

Couple points you may have to look -

  1. Change the class name from Users to User as this is a single representation of user.
  2. You can convert all the users that are stored in the file to List<User> as shown below:

       public static List<Users> DeserializeSignUpDetails()
        {
            List<Users> users = new List<Users>();
    
            using (Stream stream = new FileStream(@"SignUp.txt", FileMode.Open, FileAccess.Read))
            {
                if (stream.Length != 0)
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    while (stream.Position != stream.Length)
                    {
                        users.Add((Users)formatter.Deserialize(stream));
                    }
                    return users;
                }
    
            }
    
            return users;
    
        }                    
    
  3. You can then use this in your Main class like below:

      List<Users> userDetails = SaveToFile.DeserializeSignUpDetails();
    

AND during login validation like below:

if (userDetails != null)
    {
       foreach (Users user in userDetails)
         {
             if (userName == user.UserName && password == user.Password)
                 {
                    Console.WriteLine("You have logged in successfully!");
                    successfull = true;
                    break;
                  }
              if (!successfull)
                 {
                   Console.WriteLine("Your username or password is incorect, try again!");
                 }
        }
    }

Other notices:

  1. Use the using while reading streams to dispose correctly.

  2. Check Null or empty for streams before serialize/de-serialize to encourage defensive programming something like -

    if (stream.Length != 0)