0

I'm currently developing an app for a visitor kiosk for a gravesite where one of the functions pulls data from a csv file and uses said data to populate a listview. The following code is what takes the data and returns them in a list type grave.

    public  class Grave 
    {
        public string plots { get; set; }
        public string DOBS { get; set; }
        public string lastNames { get; set; }
        public string firstNames { get; set; }
        public string companys { get; set; }
        public string regts { get; set; }
        public string unitTypes { get; set; }
        public string states { get; set; }
        public string ranks { get; set; }
        public string sections { get; set; }
        public string image { get; set; }
        public string text { get; set; }
        public string notenums { get; set; }
     }



public class GraveManager
{



    public  static  List<Grave> GetGrave() 
    {   //points to desired folder
        StorageFolder folder = ApplicationData.Current.LocalFolder;

        string csvPath = folder.Path + @"\PGexcel.csv";
        //loads .csv file from folder
        Csv csv = new Csv();

        //property that tells csv parser to not treat the first row as data
        csv.HasColumnNames = true;

        bool success1;
        success1 = csv.LoadFile(csvPath);

        //name of columns
        string plot = "Plot", DOB = "Date_of_Death", lastName = "Last_Name", firstName = "First_Name", company = "Company", regt = "Regt", state = "State", unitType = "Unit_Type", Rank = "Rank", Section = "Section", Notables= "Notables";


        //initialize string of arrays for each column
        string[] OCplots = new string[csv.NumRows];
        string[] OCDOBS = new string[csv.NumRows];
        string[] OClastNames = new string[csv.NumRows];
        string[] OCfirstNames = new string[csv.NumRows];
        string[] OCcompanys = new string[csv.NumRows];
        string[] OCregts = new string[csv.NumRows];
        string[] OCunitTypes = new string[csv.NumRows];
        string[] OCstates = new string[csv.NumRows];
        string[] OCranks = new string[csv.NumRows];
        string[] OCsections = new string[csv.NumRows];
        string[] OCimage = new string[csv.NumRows];
        string[] OCtext = new string[csv.NumRows];
        string[] OCnotenums = new string[csv.NumRows];


        //populates the arrays with values from .csv file
        for (int i = 0; i < csv.NumRows; i++)
        {

            OCplots[i] = csv.GetCellByName(i, plot);
            OCDOBS[i] = csv.GetCellByName(i, DOB);
            OClastNames[i] = csv.GetCellByName(i, lastName);
            OCfirstNames[i] = csv.GetCellByName(i, firstName);
            OCcompanys[i] = csv.GetCellByName(i, company);
            OCregts[i] = csv.GetCellByName(i, regt);
            OCunitTypes[i] = csv.GetCellByName(i, unitType);
            OCranks[i] = csv.GetCellByName(i, Rank);
            OCsections[i] = csv.GetCellByName(i, Section);
            OCstates[i] = csv.GetCellByName(i, state);
            OCnotenums[i] = csv.GetCellByName(i, Notables); 
        }



        //concantenate arrays with .jpg .txt to call for corresponding files
        for (int i = 0; i < csv.NumRows; i++)
        {
            //OCimage[i] = "C:/Users/POGR_ADMIN/AppData/Local/Packages/6b3614f6-6a5f-48fc-9687-80291e70b64d_phwtyg9y34v1t/LocalState/" + OCplots[i] + ".jpg";
            OCimage[i] = folder.Path + @"\" + OCplots[i] + ".jpg";
            OCtext[i] = folder.Path + @"\"+ OCplots[i] + ".txt";  
        }

        var graves = new List<Grave>();



        //attempt to populate List using the for loop
        for (int i = 0; i < csv.NumRows; i++)
        {
            graves.Add(new Grave { plots= OCplots[i], DOBS = OCDOBS[i], lastNames = OClastNames[i], firstNames = OCfirstNames[i], companys = OCcompanys[i], regts = OCregts[i], states = OCstates[i], unitTypes = OCunitTypes[i],ranks = OCranks[i], sections = OCsections[i], image = OCimage[i], text = OCtext[i], notenums = OCnotenums[i] });

        }




        return graves;             

    }
}

I then initialize a list type grave on another page and fill it by calling the method when the constructor is called. Which is shown here:

   public List<Grave> Graves;

   public FindaGrave()
    {
        this.InitializeComponent();
        GraveInitializer();

    }

     public void GraveInitializer()
    {
        Graves = GraveManager.GetGrave();
    }

This code works perfectly and fine and populates the listview exactly how I envisioned. However, it has been brought to my attention that there are many errors in the csv file that they have found and plan on finding due to human error. With that being said, they want to be able to make a change the csv file and the changes will show up after they reload the app(without any help from me). Due to the local state folder being packaged the only way I saw fit to do so would be to change StorageFolder folder = ApplicationData.Current.LocalFolder; into StorageFolder folder = KnownFolders.DocumentsLibrary;

I felt that if I made sure the file was in the Document Library, added the capability in the app package manifest like so <uap:Capability Name="documentsLibrary" /> and add the appropriate filetype association that it should work the same. However, when i run it, the list no longer shows up, and after I debugged a little bit there is no longer a value for folder.Path and the string csvPath just becomes equal to `"\PGexcel.csv" . I am at a complete loss because what i thought was gonna be a quick fix ended up being something very difficult, furthermore I'm new to UWP so that doesn't help. I've looked for days and can not find the solution and any assistance on this matter will be greatly appreciated. You would be a life saver!!

Calvin Carter
  • 59
  • 1
  • 7
  • before we going to dive in your problem. Are you aware that the documentslibary capability is restricted and if want to submit to the store you better dont use it? source: https://msdn.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations – Dave Smits Dec 30 '16 at 23:20
  • I understand that it's better not to use it but this app is going to be sideloaded and won't have to go through the store so it really doesn't matter. @DaveSmits – Calvin Carter Dec 31 '16 at 00:15

2 Answers2

0

oke i checked your code and problem is that UWP apps are kind of sandboxed and only have full access to the ApplicationData. Even if you manage to get a full path outside your application data you are not able to just open it. You need to open them via the KnownFolders class or save them in StorageApplicationPermissions class

Best way would use the Storiage Api's to open the filestream and pass that to your csv parser, but i am not sure if your csv parsers accepts a stream

Dave Smits
  • 1,871
  • 14
  • 22
  • DocumentsLibrary is apart of the Known Folders Class. And the csv parser accepts a string as the path. Would you be able to show an example of using the storage API? @DaveSmits – Calvin Carter Dec 31 '16 at 00:32
  • Also since it is in Known Folder class is there no way of using it? If that was the case I find it pretty silly that they would even have it as an option regardless of the restrictions placed, especially if it's a sideloaded app @DaveSmits – Calvin Carter Dec 31 '16 at 00:35
0

Because UWP apps need to be safe, they are not allowed to access non-application data file system locations directly by using paths.

You can still use StorageFile APIs though, and it should be sufficient in your case if your CSV library supports input from string, instead of file.

public static async Task<List<Grave>> GetGraveAsync() 
{   
    //points to desired folder
    StorageFolder folder = ApplicationData.Current.LocalFolder;
    //find the file
    StorageFile csvFile = await folder.GetFileAsync( "PGexcel.csv" );


    //loads .csv file from folder
    Csv csv = new Csv();

    //property that tells csv parser to not treat the first row as data
    csv.HasColumnNames = true;

    bool success1;

    //load the CSV data from string
    success1 = csv.LoadFromString( await FileIO.ReadAllText(csvFile) );

    //... rest of your code

    return graves;             
}

StorageApplicationPermissions.FutureAccessList

The simplest solution to your issue would be to let the users choose the file on the first launch of the app using the FileOpenPicker and then store it using the StorageApplicationPermissions.FutureAccessList API. This API allows you to save the access permission to 1000 storage items, so that you can access the file the user selected. even over multiple launches of the app.

Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
  • Functionality wise. What's the difference between this code and my original? They still wouldn't be able to make updates or replace the csv file if they needed to because they don't have access to the local state folder. That's the only reason I've been looking for ways to make it go to the documents folder because thats the only folder that will allow it – Calvin Carter Dec 31 '16 at 16:31
  • 1
    The difference between your version and this one is that you should actually be able to access the file this way if you declare the documents library capability - I am using the `DocumentLibrary` there. Also as an alternative you can also use the `FileOpenPicker` in conjunction with `StorageApplicationPermission.FutureAccessList` to let the users choose the source file once (during the first use of the app) and store it over the next launches of the app. By saving the file into future access list, you can access it again anytime you need. – Martin Zikmund Dec 31 '16 at 16:35
  • I have updated my answer with info about `FutureAccessList` – Martin Zikmund Dec 31 '16 at 16:41