Search code examples
c#xamarin.formsstreamreader

Cannot find txt file StreamReader


'Unhandled Exception:

System.IO.FileNotFoundException: Could not find file "/Logins.txt" occurred'

Hi, newbie making a very basic login/signup system in xamarin c# that uses StreamReader from Systsem.IO to read a text file with stored usernames and passwords. The txt file is in the same directory as the .cs file itself and is visible in the solution explorer. I have tried putting in the full path with no success and ran as administrator just in case it was to do with permissions. Anything wrong?

public partial class LoginPage : ContentPage
{
    public LoginPage()
    {
        InitializeComponent();
    }
    List<string> user = new List<string>();
    List<string> pass = new List<string>();

    public void btnLogin_Clicked(object sender, EventArgs e)
    {
        //Read the txt
        StreamReader sr = new StreamReader("Logins.txt");
        string line = "";

        //Read every line until there is nothing in the next line
        while ((line = sr.ReadLine()) != null)
        {
            //Grab items within new lines separated by a space and chuck them into their array
            string[] components = line.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            user.Add(components[0]);
            pass.Add(components[0]);
        }
        //Check entries are on the same line and match
        if (user.Contains(txtUsername.Text) && pass.Contains(txtPassword.Text) && Array.IndexOf(user.ToArray(), txtUsername.Text) == Array.IndexOf(pass.ToArray(), txtPassword.Text))
        {
            Navigation.PushModalAsync(new HomeScreen());
        }
        else
            DisplayAlert("Error", "The username or password you have entered is incorrect", "Ok");
    }
    private void Cancel_Clicked(object sender, EventArgs e)
    {
        Navigation.PushModalAsync(new MainPage());
    }
}

Solution

  • If you change the Logins.txt file to be an embedded resource, you can then load that resource from the assembly at runtime like this:

    var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoginPage)).Assembly;
    Stream stream = assembly.GetManifestResourceStream("YourAssembly.Logins.txt");
    string text = "";
    using (var reader = new System.IO.StreamReader (stream)) 
    {
        text = reader.ReadToEnd();
    }
    

    YouAssembly refers to the name of the assembly (dll) that will be generated when you build your project. If you don't know what this is, or haven't changed it, it'll likely be the same name as the Project.

    The Xamarin docs have a good example of how this is done: File Handling in Xamarin.Forms

    Edit:

    A fuller example of doing this inside the constructor, and processing the contents so you can do your authentication processing.

    But first, let's define a POCO to hold the username / password details:

    public class UserCredentials
    {
        public string Username {get;set;}
        public string Password {get;set;}
    }
    

    Add a property to hold this to the class:

    List<UserCredentials> CredentialList {get;set;}
    

    Now, in the constructor:

    public LoginPage()
    {
        InitializeComponent();
    
        // Read in the content of the embedded resource, but let's read 
        // each line to make processing a little easier
        var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoginPage)).Assembly;
        Stream stream = assembly.GetManifestResourceStream("YourAssembly.Logins.txt");
        var textLines = new List<string>();
        string line = null;
        using (var reader = new System.IO.StreamReader (stream)) 
        {
            while ((line = sr.ReadLine()) != null) 
            {
                textLines.Add(line);
            }
        }
    
        // Init our cred list
        this.CredentialList = new List<UserCredentials>();
    
        // Read out the contents of the username/password combinations
        foreach (var line in textLines)
        {
            // Grab items within new lines separated by a space and chuck them into their array
            string[] components = line.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
    
            // Add the combination to our list
            this.CredentialList.Add(new UserCredential 
            {
                Username = user.Add(components[0]),
                Password = pass.Add(components[0])
            });
        }
    }
    

    Now our login becomes much easier. Previously we were checking first that the username / password both existed in our know dataset, and then were checking that they were at the same index in both lists.

    public void btnLogin_Clicked(object sender, EventArgs e)
    {
        // Now can use linq to validate the user
        // NOTE: this is case sensitive
        if (this.CredentialList.Any(a => a.Username == txtUsername.Text && a.Password == txtPassword.Text))
        {
            // NOTE: you've only validated the user here, but aren't passing
            // or storing any detail of who the currently logged in user is
            Navigation.PushModalAsync(new HomeScreen());
        }
        else
        {
            DisplayAlert("Error", "The username or password you have entered is incorrect", "Ok");
        }
    }