The basic idea is I have a UWP app pulling user data from a json file saved locally, and at various times it may pull that full list of objects from the file, but it always checks that the user has set the location for the data, and if not, prompts via a FolderPicker for the user to set the location. In this case, I have combobox that helps filter the objects after selecting a criteria and entering text.
Here's the call stack:
UWPMiniatures.exe!UWPMiniatures.Data.MiniDAL.SetSaveFolder() Line 98 C# Symbols loaded.
UWPMiniatures.exe!UWPMiniatures.Data.MiniDAL.LoadAllAsync() Line 71 C# Symbols loaded.
UWPMiniatures.exe!UWPMiniatures.Data.MiniDataService.Load() Line 36 C# Symbols loaded.
UWPMiniatures.exe!UWPMiniatures.MainPage.FilterGridView(string submission) Line 156 C# Symbols loaded.
UWPMiniatures.exe!UWPMiniatures.MainPage.SearchIcon_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) Line 95 C# Symbols loaded.
So, working backwords, the FolderPicker is being called here:
private async Task SetSaveFolder()
{
if(!StorageApplicationPermissions.FutureAccessList.ContainsItem("PickedFolderToken"))
{
FolderPicker folderPicker = new FolderPicker();
folderPicker.SuggestedStartLocation = PickerLocationId.Desktop;
folderPicker.FileTypeFilter.Add("*");
folderPicker.CommitButtonText = "Pick A Folder To Save Your Data";
StorageFolder folder = await folderPicker.PickSingleFolderAsync();
if (folder != null)
{
// Application now has read/write access to all contents in the picked folder (including other sub-folder contents)
StorageApplicationPermissions.FutureAccessList.AddOrReplace("PickedFolderToken", folder);
var userFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("PickedFolderToken");
var file = await userFolder.CreateFileAsync("AllMinisList.json",CreationCollisionOption.OpenIfExists);
var imagefolder = await userFolder.CreateFolderAsync("Images");
}
}
}
The folder picker dialog opens, with a blinking cursor next to Folder:, but nothing happens when I click anywhere, nor can i type in Folder: textbox. Now, putting this identical code in a new project and calling it in response to a click event works fine: Dialog opens, I make a new folder or pick an existing one, it gets added to future access list. Not sure how to else to troubleshoot this but the problem seems to lie out side the actual code calling the FolderPicker.
here is the code for the other calling function
private void SearchIcon_Click(object sender, RoutedEventArgs e)
{
FilterGridView(SearchTextBox.Text);
SearchTextBox.Text = "";
}
private async void FilterGridView(string submission)
{
var selected = FilterComboBox.SelectedValue;
miniDS = new MiniDataService();
if(selected.ToString()=="All")
{
MiniList.Clear();
List<Miniature> fullList = await miniDS.Load();
fullList.ForEach(m => MiniList.Add(m));
}
else if (selected.ToString() == "Quantity")
{
List<Miniature> fullList = await miniDS.Load();
var templist = fullList.AsQueryable()
.Where($"{selected} = #0", submission); ;
MiniList.Clear();
templist.ToList<Miniature>()
.ForEach(m => MiniList.Add(m));
}
else
{
List<Miniature> fullList = await miniDS.Load();
var templist = fullList.AsQueryable()
.Where($"{selected}.StartsWith(#0)", submission);
MiniList.Clear();
templist.ToList<Miniature>()
.ForEach(m => MiniList.Add(m));
}
}
MiniDataService and MiniDal don't do much here other than pass the call along.
Any ideas where I can look to troubleshoot this?
UPDATE: Some additional info, I copied the code from SetSaveFolder() directly into a new event handler for a button, clicked it, I get FolderPicker, functions perfectly. But thats not at all the functionality needed. I need it to be called, directly or indirectly from my Data Service. So here's where its created:
public sealed partial class MainPage : Page
{
/// <summary>
/// MiniList is the list of minis currently being displayed
/// </summary>
private ObservableCollection<Miniature> MiniList;
private MiniDataService miniDS;
private List<string> FilterComboList;
private Miniature NewMini;
public MainPage()
{
this.InitializeComponent();
miniDS = new MiniDataService();
MiniList = new ObservableCollection<Miniature>();
FilterComboList = PopulateFilterCombo();
NewMini = new Miniature();
MyFrame.Navigate(typeof(MiniListPage), MiniList);
}
...
So the problem seems to have something to do with the fact that FolderPicker is being called from this "static" object. Is this a thread issue? I thought in UWP I am always on the UI threadm and since at the top level an event handler is calling folderPicker I can't understand why the UI seems locked.
So i think I figured it out, though I have no idea why this happened. If anyone can clue me in, id appreciate it.
So from the call List<Miniature> fullList = await miniDS.Load();
Here's that method:
public async Task<List<Miniature>> Load()
{
return await minidal.LoadAllAsync();
}
public async Task<List<Miniature>> LoadAllAsync()
{
List<Miniature> MiniCollection = new List<Miniature>();
if (StorageApplicationPermissions.FutureAccessList.ContainsItem("PickedFolderToken"))
{
try
{
var userFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("PickedFolderToken");
var file = await userFolder.GetFileAsync("AllMinisList.json");
var data = await file.OpenReadAsync();
using (StreamReader stream = new StreamReader(data.AsStream()))
{
string text = stream.ReadToEnd();
MiniCollection = JsonConvert.DeserializeObject<List<Miniature>>(text);
}
}
catch(Exception e)
{
throw e;
}
}
else
{
SetSaveFolder().Wait();
return MiniCollection;
}
return MiniCollection;
}
So the problem was right here:
SetSaveFolder().Wait();
When I replace that with
await SetSaveFolder();
it works fine. I can click in the folderPicker, and it does what it's supposed to. I guess I though .Wait() was used when you aren't return anything other than but it seems there is more too it than that!
Related
I'm wondering whether there is a clean way to create a new file using Octokit in case the file specified doesn't exist, or otherwise create it? I'm currently attempting to do this via a try-catch block. However, there needs to be a more straightforward way to do this except a try-catch block?
private IRepositoryContentsClient Content => _client.Repository.Content;
public void TransmitLog(string logFilePath)
{
var logContent = LogFileToString(logFilePath);
var githubPath = GenerateGithubPath(logFilePath);
try
{
UpdateLog(githubPath, logContent);
}
catch (NotFoundException)
{
CreateLog(githubPath, logContent);
}
catch (AggregateException)
{
CreateLog(githubPath, logContent);
}
}
private void UpdateLog(string githubPath, string logContent)
{
// MY APP FAILS HERE
var existingFile = Content.GetAllContentsByRef(
_owner, _repo, githubPath, _branch).Result;
// update the file
var relevantSha = existingFile.First().Sha;
var updateRequest = new UpdateFileRequest("Log update" + DateTime.UtcNow, logContent, relevantSha, _branch);
var updateChangeSet = Content.UpdateFile(_owner, _repo, githubPath, updateRequest);
}
private void CreateLog(string githubPath, string logFileContent)
{
// if file is not found, create it
var createRequest =
new CreateFileRequest("Log Creation" + DateTime.UtcNow, logFileContent, _branch);
var createChangeSet = Content.CreateFile(_owner, _repo, githubPath, createRequest);
}
EDIT: I initially had both CreateLog and UpdateLog declared as async void, which led to a whole set of other problems. I edited that out since what I'm really interested in is how to avoid this try/catch structure.
This may not be 100% an EPPlus issue, but since it is Blazor WASM it appears I cannot get the file path to a static image in the wwwroot/images folder. I can get the url and paste it into a browser and that works, even adding that same path to the src attribute of an img works, neither of those helps me.
FYI "background" in this context means a watermark.
It appears that the EPPlus dev team only wants a drive path the file (ex. C:\SomeFolder\SomeFile.png), and I am not seeing how to get that within Blazor WASM. I can get the bytes of the file in c# and even a stream, but no direct path.
My code is the following:
using (var package = new ExcelPackage(fileName))
{
var sheet = package.Workbook.Worksheets.Add(exportModel.OSCode);
sheet.BackgroundImage.SetFromFile("https://localhost:44303/images/Draft.png");
...
}
This returns an exception:
Unhandled exception rendering component: Can't find file /https:/localhost:44303/images/Draft.png
Noticing that leading / I even tried:
sheet.BackgroundImage.SetFromFile("images/Draft.png");
Which returned the same error:
Unhandled exception rendering component: Can't find file /images/Draft.png
So, I am perhaps needing 1 of 2 possible answers:
A way to get a local drive path to the file so the .SetFromFile method is not going to error.
To have a way to set that BackgroundImage property with a byte array or stream of the image. There is this property BackgroundImage.Image but it is readonly.
Thanks to a slap in the face from #Panagiotis-Kanavos I wound up taking the processing out of the client and moving it to the server. With that, I was able to use Static Files to add the watermark with relatively little pain.
In case anyone may need the full solution (which I always find helpful) here it is:
Here is the code within the button click on the Blazor component or page:
private async Task GenerateFile(bool isFinal)
{
...
var fileStream = await excelExportService.ProgramMap(exportModel);
var fileName = "SomeFileName.xlsx";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await jsRuntime.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
That calls a client-side service that really just passes control over to the server:
public class ExcelExportService : IExcelExportService
{
private const string baseUri = "api/excel-export";
private readonly IHttpService httpService;
public ExcelExportService(IHttpService httpService)
{
this.httpService = httpService;
}
public async Task<Stream> ProgramMap(ProgramMapExportModel exportModel)
{
return await httpService.PostAsJsonForStreamAsync<ProgramMapExportModel>($"{baseUri}/program-map", exportModel);
}
}
Here is the server-side controller that catches the call from the client:
[Route("api/excel-export")]
[ApiController]
public class ExcelExportController : ControllerBase
{
private readonly ExcelExportService excelExportService;
public ExcelExportController(ExcelExportService excelExportService)
{
this.excelExportService = excelExportService;
}
[HttpPost]
[Route("program-map")]
public async Task<Stream> ProgramMap([FromBody] ProgramMapExportModel exportModel)
{
return await excelExportService.ProgramMap(exportModel);
}
}
And that in-turn calls the server-side service where the magic happens:
public async Task<Stream> ProgramMap(ProgramMapExportModel exportModel)
{
var result = new MemoryStream();
ExcelPackage.LicenseContext = LicenseContext.Commercial;
var fileName = #$"Gets Overwritten";
using (var package = new ExcelPackage(fileName))
{
var sheet = package.Workbook.Worksheets.Add(exportModel.OSCode);
if (!exportModel.IsFinal)
{
var pathToDraftImage = #$"{Directory.GetCurrentDirectory()}\StaticFiles\Images\Draft.png";
sheet.BackgroundImage.SetFromFile(pathToDraftImage);
}
...
sheet.Cells.AutoFitColumns();
package.SaveAs(result);
}
result.Position = 0; // Without this, data does not get written
return result;
}
For some reason, this next method was not needed when doing this on the client-side but now that it is back here, I had to add a method that returned a stream specifically and used the ReadAsStreamAsync instead of ReadAsJsonAsync:
public async Task<Stream> PostAsJsonForStreamAsync<TValue>(string requestUri, TValue value, CancellationToken cancellationToken = default)
{
Stream result = default;
var responseMessage = await httpClient.PostAsJsonAsync(requestUri, value, cancellationToken);
try
{
result = await responseMessage.Content.ReadAsStreamAsync(cancellationToken: cancellationToken);
}
catch (HttpRequestException e)
{
...
}
return result;
}
Lastly, in order for it to give the end-user a download link, this was used (taken from the Microsoft Docs):
window.downloadFileFromStream = async (fileName, contentStreamReference) => {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement("a");
anchorElement.href = url;
anchorElement.download = fileName ?? "";
anchorElement.click();
anchorElement.remove();
URL.revokeObjectURL(url);
}
I am brand new to Xamarin and still learning the ropes so bear with me. I am using the PCLStorage library (https://github.com/dsplaisted/pclstorage) to read and write to a json file.
I have set up static classes in my PCL so that I can call them from the native code (not sure if that's the best way of doing it, see below). The rootFolder being passed in is FileSystem.Current.LocalStorage.
public static async Task<string> ReadFileContent(string fileName, IFolder rootFolder)
{
IFile file = null;
string text = null;
var files = await rootFolder.GetFilesAsync();
file = files.FirstOrDefault(f => f.Name == fileName);
if (file != null)
{
try
{
text = await file.ReadAllTextAsync();
}
catch (Exception exception)
{
var x = exception.Message;
}
}
return text;
}
public static async Task WriteFileContent(string fileName, IFolder rootFolder, string content)
{
ExistenceCheckResult exist = await rootFolder.CheckExistsAsync(fileName);
if (exist == ExistenceCheckResult.FileExists)
{
IFile file = await rootFolder.GetFileAsync(fileName);
await file.WriteAllTextAsync(content);
}
else
{
IFile file = await rootFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
await file.WriteAllTextAsync(content);
}
}
This works perfectly when I first run the Android app. It reads the files multiple times and every thing works. However later on I fire off an intent which takes the user to a webpage. When the user is directed back to the app to a different Activity it stops working. The "await rootFolder.GetFilesAsync();" throws an unhandled exception with no more information than that.
Is there something i'm missing when resuming the app?
Thanks in advance.
I'm beginner with C# and XAML.
In my app I read lines of text to list like this:
string path = "ms-appx:///" + _index + ".txt";
StorageFile sampleFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(path));
_stopsList = await FileIO.ReadLinesAsync(sampleFile, Windows.Storage.Streams.UnicodeEncoding.Utf8);
And I put this to combobox2:
comboBox2.ItemsSource = routesList[comboBox.SelectedIndex]._stopsList;
One time I run my app in debug mode, combobox2 is correctly filled with the lines from file (like this 1, but for example next time when I run my app, combobox2 is empty (2) and next to _stopsList appears Count: 0. Content in combobox2 doesn't appear every time.
BusRoute class:
class BusRoute
{
public BusRoute(string name, int index)
{
Name = name;
_index = index;
GetStopsList();
}
public async void GetStopsList()
{
string path = "ms-appx:///" + _index + ".txt";
StorageFile sampleFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(path));
_stopsList = await FileIO.ReadLinesAsync(sampleFile, Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
public string Name
{
get { return _routeName; }
set
{
if (value != null)
{
_routeName = value;
}
}
}
public IList<string> _stopsList = new List<string>();
private string _routeName;
private int _index;
}
MainPage:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.DataContext = this;
this.InitializeComponent();
routesList.Add(new BusRoute("Laszki – Wysocko - Jarosław", 1));
routesList.Add(new BusRoute("Tuchla - Bobrówka - Jarosław", 2));
this.comboBox.ItemsSource = routesList;
this.comboBox.DisplayMemberPath = "Name";
this.comboBox.SelectedIndex = 0;
this.comboBox2.ItemsSource = routesList[comboBox.SelectedIndex]._stopsList;
}
List<BusRoute> routesList = new List<BusRoute>();
}
So the problem here is that GetStopsList() is marked to run async. When you call GetStopsList in the BusRoute constructor, the code continues immediately, and eventually reaches this.comboBox2.ItemsSource = routesList[comboBox.SelectedIndex]._stopsList; At that point the ReadLinesAsync hasn't completed yet (execution in the constructor wasn't paused), so an empty list of data is bound to the comboBox2.
The reason this works when you are debugging is that when you add a break point and inspect the code you are causing an artificial delay which allows enough time for ReadLinesAsync to complete.
Try changing public async void GetStopsList() to public async Task GetStopsList() this will allow the caller to await the function. You then need to call await GetStopsList(); before binding the data list.
You can't await inside the constructor, so you will need to call the intialisation function from somewhere else. This posses an interesting challenge as all your code is inside a constructor. Perhaps there is a Page event you can do this in, e.g. on Load or on Init.
I have the following code in my windows 8 desktop app. This gets data from a web service and populates it in a List SampleDataGroup.
protected override async void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://localhost:12345/api/items");
var info = new List<SampleDataGroup>();
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var item = JsonConvert.DeserializeObject<dynamic>(content);
foreach (var data in item)
{
var infoSect = new SampleDataGroup
(
(string)data.Id.ToString(),
(string)data.Name,
(string)"",
(string)data.PhotoUrl,
(string)data.Description
);
info.Add(infoSect);
//data=infoSect;
}
}
else
{
MessageDialog dlg = new MessageDialog("Error");
await dlg.ShowAsync();
}
}
This works fine. But I want to use the data in the above function in another function. I want to use. The properties and values I get from the web service, I would like to use in another function.
private void Item_Click(object sender, RoutedEventArgs e)
{
//Use (string)data.Name
}
In the click function I want to use the Name, as well as all other data values from the first function.
I tried setting SampleDataGroup data=null; in the code and then using data=infoSect seen commented out above and then call data.Name in the click function but it returns an null exception.
How do i pass data from one function to another?
Well currently your async method returns void, and you're not doing anything with info after populating it. It seems to me that you probably want to either return that list from the method (making it return Task<List<SampleDataGroup>> for example) or make the method set the state in an instance variable, so that it becomes part of the state of the object the method is called on.
Without more information about the class you're designing, it's hard to know whether the information is logically part of the state of the object or whether it should be returned from the method - but those are your basic choices. (There are more complicated alternatives, but those are the first two to consider.)
You should also consider what you want to happen in terms of state if there's an error - you're showing a dialog box, but what do you want to happen after that?
Simplest would be to declare a global object as below
object item;
and then change it like this
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
item = JsonConvert.DeserializeObject<dynamic>(content);
now this should be available in the click event with the latest values