2

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!

d51
  • 316
  • 1
  • 6
  • 23
  • 1) That isn't a text file. 2) I'm not sure that you can without additional information, as it won't be possible to tell where one record ends and the next one starts. – ProgrammingLlama Jun 04 '20 at 02:30
  • @John Do you have any suggestions on what I could do? – d51 Jun 04 '20 at 02:33
  • A simple approach might be to write the record to a `MemoryStream` first, and then prepend each record with its length. – ProgrammingLlama Jun 04 '20 at 02:34
  • You need to provide more information. What do you want to loop through? Changing string `SignUp.txt` in your loop? – Louis Go Jun 04 '20 at 05:25
  • @John I have made a ```MemoryStream```, but I don't know how to prepend it with its length – d51 Jun 04 '20 at 22:50
  • @LouisGo I want to loop through all the usernames and passwords that are in SignUp.txt to check it against the username and password the user inputted while logging in to see if it is an existing account. At the moment it only checks with the first username and password created to see if there is a match, not all – d51 Jun 04 '20 at 22:53

3 Answers3

6

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

d51
  • 316
  • 1
  • 6
  • 23
  • 1
    I like this, however I would change your `TryGet` method name to `Get` as it doesn't really follow the `Try...` pattern. Similarly with `TryAdd`. – the.Doc Jun 04 '20 at 09:39
  • @the.Doc Edit to apply your comment please. I agree & will accept the edit. :) –  Jun 04 '20 at 09:41
  • @JQSOFT this doesn't work for me, it doesn't save the username or password to my file. Am I doing something wrong? – d51 Jun 06 '20 at 04:58
  • @danyal51 The `main` should be like this. See the edit. –  Jun 06 '20 at 12:39
  • @JQSOFT for some reason it still doesn't work, I am using your exact code but it just doesn't save to my file – d51 Jun 07 '20 at 00:08
  • 1
    @JQSOFT Turns out I was looking at the wrong file with the same name! Thanks for all the help much appreciated! – d51 Jun 07 '20 at 04:52
3

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.

Louis Go
  • 2,213
  • 2
  • 16
  • 29
  • Thanks for trying to help! But I only have one file I want to loop through. Thanks anyway! – d51 Jun 04 '20 at 22:59
  • @danyal51, JQSOFT wrote an comprehensive answer. My suggestion is making your post clearer for Input and Output. For example, what you may want to save in a file. Because you might want to achieve something not knowing in the beginning, explaining your intent with detail and your code would help future questions get answered quickly. – Louis Go Jun 05 '20 at 02:31
1

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)
    
MBB
  • 1,635
  • 3
  • 9
  • 19
  • 1
    it doesn't work, I keep getting a `Binary stream "0" does not contain a valid BinaryHeader` error when I try to run the code – d51 Jun 06 '20 at 04:40
  • I have tested this and working fine .. Looks like some other issue plz check here https://stackoverflow.com/questions/15907894/binary-stream-0-does-not-contain-a-valid-binaryheader-error-on-deserialization – MBB Jun 06 '20 at 05:50