I have an Azure function QueueTrigger1 that executes a function executeTemplateProcess to upload a tsv file on Google Drive and update a Jira ticket.
I need to create a Message Queue to generate a tsv file on datalake, run a python code, and then retrieve the tsv file (location) from the datalake and add it to the queue.
I have a basic foundation for the queue today, but i am not sure how to generate the file on the datalake and retrieve its location. We need to pass the file into the python code as input, thats why I am thinking we would need the file location on the datalake to be enqueued, but I am not sure how to perform this.
This is the namespaces for both the QueueTrigger1 and the executeTemplateProcess()
namespace DI
{
public class DIProcess
{
public static void executeTemplateProcess(string jiraKey, string jiraIssueType, string jiraSummary, Component component, string jiraDescription)
{
if (rowCount > 0)
{ //python code would run somewhere here following queue process before jira code executes below
string dfileId = CopyTemplate(component.FileId, sheetName);
// stop process if copy template not sucessfull
if (string.IsNullOrEmpty(dfileId))
return;
jira.AddComment("Google File copied.");
// Update JIRA with the web link
webLink = $"https://docs.google.com/spreadsheets/d/{dfileId}";
jira.AddWebLink(webLink, sheetName);
jira.AddComment("Jira weblink added.");
}
else
{
jira.UpdateStatus("Abandoned");
jira.AddComment("Jira status updated to Abandoned.");
}
}
}
}
namespace companyxyzjira.QueueTrigger1
{
public static class JiraQueueTrigger
{
[FunctionName("QueueTrigger1")]
public static void Run([QueueTrigger("companyxyz-jira-dev-am", Connection = "storageaccountcompanyxyzji42f6_STORAGE")]string myQueueItem
, ILogger log, ExecutionContext context)
{
dynamic jira;
string jiraKey;
string jiraIssueType;
string jiraSummary;
string jiraDescription;
string[] jiraComponentNames;
Component jiraComponent;
log.LogInformation("Queue trigger function processing");
jira = JsonConvert.DeserializeObject(myQueueItem);
jiraKey = jira.issue.key;
jiraIssueType = jira.issue.fields.issuetype.name;
jiraSummary = jira.issue.fields.summary;
jiraDescription = jira.issue.fields.description;
try
{
DIProcess.executeTemplateProcess(jiraKey, jiraIssueType, jiraSummary, jiraComponent, jiraDescription);
}
catch (System.Exception e)
{
log.LogError(e.ToString());
log.LogError(e.Message);
log.LogError(e.StackTrace);
}
}
}
}
I suppose this is my line of thinking but I am not sure how to communicate with the datalake...
[FunctionName("HttpTriggerCSharp")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req, [Queue("companyxyz-jira-dev-pm-mapping-done")] ICollector<string> QueueItem, ILogger log)
{
log.LogInformation("HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = String.Empty;
using (StreamReader streamReader = new StreamReader(req.Body))
{
requestBody = await streamReader.ReadToEndAsync();
QueueItem.Add(requestBody); //i think?
}
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"{name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
datalake snapshot with input/output files (uploaded manually but thats what we want to automate from now on so we need to generate/retrieve these artifacts from/to message queue as described above)
from azure.storage.filedatalake import DataLakeServiceClient
import pandas as pd
connect_str = os.getenv('AZURE_STORAGE_CONNECTION_STRING')
service_client = DataLakeServiceClient.from_connection_string(connect_str)
#Put above code out of the body of function.
file_system_client = service_client.get_file_system_client(file_system="test")
directory_client = file_system_client.get_directory_client("test")
file_client = directory_client.create_file("uploaded-file0316.txt")
#Upload to datalake
head = ["col1" , "col2" , "col3"]
l = [[1 , 2 , 3],[4,5,6] , [8 , 7 , 9]]
df = pd.DataFrame (l , columns = head)
data = df.to_csv(index_label="idx", encoding = "utf-8")
output = data.replace(',', '\t')
print(output)
file_client.append_data(data=output, offset=0, length=len(output))
file_client.flush_data(len(output))
#download from datalake
download = file_client.download_file()
content = download.readall()
print(content)
Related
I have image upload feature in my Xamarin.iOS application. I have stored this uploaded image(s) in firebase storage. My files gets uploaded to firebase storage successfully, but the issue is:
When I am trying to get all images using listAll() method of firebase
storage it not return all images until the folder have images >= 2.
Code to upload image on firebase storage:
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{
if (e.Info[UIImagePickerController.MediaType].ToString() == "public.image")
{
NSData imgData = new NSData();
imgData = e.OriginalImage.AsJPEG();
Guid uniqId = Guid.NewGuid(); // use - uniqId.ToString()
FirebaseClient.Instance.UploadAdventurePhoto(imgData, this.Adventure.Id, uniqId.ToString());
//(Path of folder - gs://myapp.appspot.com/adventures/00ac45a3-7c92-4335-a4b8-b9b705c4dd72)
StorageReference photsUrl = Storage.DefaultInstance.GetReferenceFromUrl($"gs://myapp.appspot.com/adventures/{this.Adventure.Id}");
photsUrl.ListAll(this.Handler);
}
this.imagePicker.DismissViewController(true, null);
}
// Add image to Firestore collection's Document.
private async void Handler(StorageListResult result, NSError arg2)
{
foreach (var image in result.Items)
{
// Logic to append image to Firestore document.
}
}
/// <param name="imgData">Selected image that needs to be stored on storage.</param>
/// <param name="adventureId">Name of the folder.</param>
/// <param name="imageName">By this name image will get stored in folder.</param>
public void UploadAdventurePhoto(NSData imgData, string adventureId, string imageName)
{
StorageReference adventurePictureRef = Storage.DefaultInstance.GetReferenceFromPath($"adventures/{adventureId}/{imageName}");
StorageMetadata metaData = new StorageMetadata();
metaData.ContentType = "image/jpg";
adventurePictureRef.PutData(imgData, metaData);
}
After first image get uploaded, image get uploaded successfully but when handler gets called it gives this response:
But after this when I upload 2nd image that time it give Firebase.Storage.StorageReference1 in response:
Means if there are two images then only it returns url reference. How to fix this issue?
I have already added rules_version = '2'; in storage rules.
Haven't got the solution for listAll() but I have got the work around for this problem.
What I was trying is to get all the images from firebase storage using listAll(), but in that I am getting this issue. So now instead of listAll() I am using PutData() method's completion handler.
The completion handler will provide you the Metadata of an uploaded image. From this meta data we can get image directly like this:
metadata.Name
Here is how I have fix the problem by adding completion handler in UploadAdventurePhoto() method:
private void UploadAdventurePhoto(NSData imgData, string folderName, string imageName)
{
StorageReference adventurePictureRef = Storage.DefaultInstance.GetReferenceFromPath($"adventures/{folderName}/{imageName}");
StorageMetadata metaData = new StorageMetadata();
metaData.ContentType = "image/jpeg";
adventurePictureRef.PutData(imgData, metaData, this.HandleStorageGetPutUpdateCompletion);
}
private async void HandleStorageGetPutUpdateCompletion(StorageMetadata metadata, NSError error)
{
if (error != null)
{
// Uh-oh, an error occurred!
return;
}
var url = metadata.Name;
var downloadUrl = metadata.Path;
Debug.WriteLine($"Image url - {url}\n Path-{downloadUrl}");
CollectionReference collectionRef = Firestore.SharedInstance.GetCollection(FirebaseClient.AdventuresCollection);
var docRef = collectionRef.GetDocument(this.Adventure.Id);
var keys = new NSString[]
{
new NSString($"{AdventureBase.PhotoPropName}"),
};
var value = new NSObject[]
{
new NSString(url),
};
var objects = new NSObject[]
{
FieldValue.FromArrayUnion(value),
};
var dict = new NSDictionary<NSString, NSObject>(keys, objects);
await docRef.SetDataAsync(dict, true);
docRef.AddSnapshotListener(this.UpdateDataHandler);
}
private async void UpdateDataHandler(DocumentSnapshot snapshot, NSError error)
{
if (error != null)
{
// something went wrong
Debug.WriteLine($"Error - {error.Description}");
return;
}
Toast.MakeToast("Image uploaded successfully").Show();
}
I found the BlazorInputFile library, but there are still-open PRs from October of 2019, and I am not sure whether this library is still maintained. Also, I found a couple articles in blogs about how we can upload files with JS in Blazor. While I don't want to use JS if possible, I do need to upload files using Blazor... is it possible to do so without using JavaScript?
I was dabbling with installing SteveSandersonMS' repo and then realised that, as of February 2021, there is actually a native InputFile component in ASP.NET Core 5.0.
It supports uploading of single and multiple files in Blazor and is easy to use (and you don't need to add your own JS files etc.).
I used it for single file uploads - all you need to do is add the InputFile component in the Razor page:
<InputFile OnChange="#SingleUpload" />
and then in my case I needed the file in a byte array:
#code {
private async Task SingleUpload(InputFileChangeEventArgs e)
{
MemoryStream ms = new MemoryStream();
await e.File.OpenReadStream().CopyToAsync(ms);
var bytes = ms.ToArray();
//do something with bytes
}
}
InputFileChangeEventArgs gives you an IReadOnlyList of IBrowserFile which you can use to get the Name, LastModified, Size and ContentType, as well as an OpenReadStream method for getting a Stream.
There is good documentation and code on how to get multiple files in the ASP.NET docs.
Your will also need to add the System.IO namespace:
#using System.IO
As of June 2020, The best method (WA), assuming you are using a form is to use a Tewr's FileReader. Let start with the API, the post controller would be :
public async Task<IActionResult> PostMedia(
[FromForm] IFormFile Picture,
[FromForm] string Focus,
[FromForm] string ID,
[FromForm] string Title,
[FromForm] string FormType,
[FromForm] string AnimalType,
[FromForm] string Mode,
[FromForm] string AnimalID
)
{
Debug.WriteLine($"-------------------------------------{Focus}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{ID}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{Title}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{FormType}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{AnimalType}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{Mode}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{AnimalID}-----------------------------------------------");
//check if file was fully uploaded
if (Picture.Length == 0 || Picture == null)
return BadRequest("Upload a new File");
else
return Ok ("do something with this data....")
}
Then the post method on the client side would be:
public async Task PostFile()
{
//create content headers
var content = new MultipartFormDataContent();
content.Headers.ContentDisposition = new
System.Net.Http.Headers.ContentDispositionHeaderValue("form-data");
//create content
content.Add(new StreamContent(Pic.Stream, (int)Pic.Stream.Length), "Picture", Pic.FileName);
content.Add(new StringContent(Pic.Title), "Title");
content.Add(new StringContent(Pic.Focus), "Focus");
content.Add(new StringContent(Pic.ID), "ID");
content.Add(new StringContent(Pic.FormType), "FormType");
content.Add(new StringContent(Pic.AnimalType), "AnimalType");
content.Add(new StringContent(Pic.Mode), "Mode");
content.Add(new StringContent(Pic.AnimalID), "AnimalID");
//call to the server
var upload = await Http.PostAsync("Media",content);
//get server response
Pic.Message = await upload.Content.ReadAsStringAsync();
}
Tewr File reader helps you read the file into a stream which in my case is passed to the Pic object. The reading function which is binded to the onchange of your input element in the form would be :
public async Task ReadFile()
{
var file = (await fileReaderService.CreateReference(Xelement).EnumerateFilesAsync()).FirstOrDefault();
if (file == null) return;
var fileInfo = await file.ReadFileInfoAsync();
Pic.FileName = fileInfo.Name;
// Read into RAM
using (var memoryStream = await file.CreateMemoryStreamAsync((int)fileInfo.Size))
{
// Copy store image into pic object
Pic.Stream = new MemoryStream(memoryStream.ToArray());
}
}
Note that Xelement is ElementReference, and it used as ref on the input element in the form.
At the current state of affairs (as 2 April 2020), you will require JS, it is inevitable.
There are two main approaches you can take:
get the file data in the onchange event of the input, and call C# methods by passing the byte[] to them - that's basically the file selector approach you linked where you get the file data in the Blazor app to do whatever you want with it.
get the file data in the onchange event of the input, and use JS to call a remote endpoint that will receive the file and do something with it (like save it on your NAS or put it in your DB). This one is an actual file upload, as opposed to a file selector.
Both approaches are similar from coding perspective - you need JS. Perhaps in a future version of Blazor we will get an <InputFile> that will do the selection so you can to uploads with C# HTTP requests.
The File Selector approach is relatively easy to implement (literally a few lines), but it does not give you a file on the server, you have to work for it a little. The File Upload approach is harder to get right. I would personally use someone else's package for either. For file uploads things like Telerik UI for Blazor can be a commercial fit, and for the simpler selectors there is already another answer that links examples. By the way, Telerik's demos also have one such example as a component implemented for some of the demos.
I do this by using a component and some javascript (looks like a button). Once the component and js are incorporated, you never have to worry about it again...
Here's the Upload Component (Upload.Razor):
#inject IJSRuntime JSRuntime
#if (AllowMulitple)
{
<input id="Xinputfile00" type="file" accept="#Filter" #onchange="UploadFile" multiple hidden />
}
else
{
<input id="Xinputfile00" type="file" accept="#Filter" #onchange="UploadFile" hidden />
}
<button class="btn btn-default" #onclick="ClickUpload">#Title</button>
#code {
[Parameter]
public FileData[] Files { get; set; }
[Parameter]
public string Filter { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public bool AllowMulitple { get; set; }
[Parameter]
public Action Uploaded { get; set; }
async Task UploadFile()
{
string[] result = await JSRuntime.InvokeAsync<string[]>("blazorExtensions.GetFileData", "Xinputfile00");
List<FileData> results = new List<FileData>();
foreach (string file in result)
{
results.Add(new FileData(file));
}
this.Files = results.ToArray();
if (Uploaded != null)
{
Uploaded();
}
}
async Task ClickUpload()
{
await JSRuntime.InvokeVoidAsync("blazorExtensions.InvokeClick", "Xinputfile00");
}
public class FileData
{
public string Base64 { get; set; }
public string MIMEType { get; set; }
public byte[] Bytes
{
get
{
return Convert.FromBase64String(this.Base64);
}
}
public FileData(string data)
{
if (string.IsNullOrWhiteSpace(data) || !data.Contains(","))
{
return;
}
string[] alldata = data.Split(',');
this.MIMEType = alldata[0].Remove(0, 5).Replace(";base64", "");
this.Base64 = alldata[1];
}
}
Here's the javascript excerpt:
window.blazorExtensions = {
GetFileData: async function (id) {
var target = document.getElementById(id);
var filesArray = Array.prototype.slice.call(target.files);
return Promise.all(filesArray.map(window.blazorExtensions.fileToDataURL));
},
fileToDataURL: async function (file) {
var reader = new FileReader();
return new Promise(function (resolve, reject) {
reader.onerror = function () {
reader.abort();
reject(new DOMException('Error occurred reading file ' + file));
};
reader.onload = function (event) {
resolve(reader.result);
console.log('resolved');
};
reader.readAsDataURL(file);
console.log('returned');
})
},
InvokeClick: function (id) {
var elem = document.getElementById(id);
if (typeof elem.onclick == "function") {
elem.onclick.apply(elem);
}
elem.click();
},
}
And here's a calling markup sample:
<Upload #ref="upload" Filter=".xlsx" Title="Upload" AllowMulitple="false" Uploaded="DoMyExcelThingOrSomething" />
and the method it calls after upload:
Upload upload;
void DoMyExcelThingOrSomething()
{
if (upload.Files.Length < 1 || string.IsNullOrWhiteSpace(upload.Files[0].Base64))
{
//...nothing good here...
return;
}
//play with upload.Files here...
}
For Blazor Server, the following would upload the file to the server. There's no need to have a separate API server, or to use JS code. And it converts the stream into a file.
#using System.IO
#inject IWebHostEnvironment env
#*for ibrowser*#
#using Microsoft.AspNetCore.Components.Forms;
<h1>Blazor Server File Upload</h1>
<h3>#Message</h3>
<form #onsubmit="OnSubmit">
<InputFile OnChange="OnInputFileChange" multiple />
<br /><br />
<button type="submit">Upload Selected File(s)</button>
</form>
#code {
string Message = "No file(s) selected";
IReadOnlyList<IBrowserFile> selectedFiles;
void OnInputFileChange(InputFileChangeEventArgs e)
{
selectedFiles = e.GetMultipleFiles();
Message = $"{selectedFiles.Count} file(s) selected";
this.StateHasChanged();
}
async void OnSubmit()
{
foreach (var file in selectedFiles)
{
Stream stream = file.OpenReadStream();
var path = $"{env.WebRootPath}\\{file.Name}";
FileStream fs = File.Create(path);
await stream.CopyToAsync(fs);
stream.Close();
fs.Close();
}
Message = $"{selectedFiles.Count} file(s) uploaded on server";
this.StateHasChanged();
}
}
(Minor edit of http://www.bipinjoshi.net/articles/06473cc7-a391-409e-948d-3752ba3b4a6c.aspx)
I am trying to use the WorkItems API to extract key paramaters of a part to a text file. The work item fails with FailedMissingOutput [KeyParameters.txt] which is the file my plugin creates in the working folder. Debugging locally the file is created successfully.
Log:
Addin Code is pretty simple:
public void RunWithArguments(Document doc, NameValueMap map)
{
LogTrace("Processing " + doc.FullFileName);
LogInputData(doc, map);
try
{
var DocDir = System.IO.Path.GetDirectoryName(doc.FullFileName);
var ParametersOutputFileName = System.IO.Path.Combine(DocDir, "KeyParameters.txt");
if (doc.DocumentType == DocumentTypeEnum.kPartDocumentObject)
{
using (new HeartBeat())
{
// TODO: handle the Inventor part here
PartDocument PartDoc = (PartDocument)doc;
ExtractKeyParams(PartDoc.ComponentDefinition.Parameters, ParametersOutputFileName);
}
}
else if (doc.DocumentType == DocumentTypeEnum.kAssemblyDocumentObject) // Assembly.
{
using (new HeartBeat())
{
// TODO: handle the Inventor assembly here
AssemblyDocument AssyDoc = (AssemblyDocument)doc;
ExtractKeyParams(AssyDoc.ComponentDefinition.Parameters, ParametersOutputFileName);
}
}
}
catch (Exception e)
{
LogError("Processing failed. " + e.ToString());
}
}
public void ExtractKeyParams(Parameters Params, string OutputFileName)
{
List<string> ParamList = new List<string>();
foreach (Parameter Param in Params)
{
if (Param.IsKey)
{
ParamList.Add(Param.Name);
}
}
string[] OutputParams = ParamList.ToArray();
System.IO.File.AppendAllLines(OutputFileName, OutputParams);
}
Activity Params...
private static Dictionary<string, Parameter> GetActivityParams()
{
return new Dictionary<string, Parameter>
{
{
Constants.Parameters.InventorDoc,
new Parameter
{
Verb = Verb.Get,
Description = "File to process"
}
},
{
"OutputParams",
new Parameter
{
Verb = Verb.Put,
LocalName = "KeyParameters.txt",
Description = "Key Parameters Output",
Ondemand = false,
Required = false
}
}
};
}
.....And Work Item arguments (With token and ids removed), the signed resource is a forge bucket resource generated to expire in 60 minutes so that shouldn't be the issue,
private static Dictionary<string, IArgument> GetWorkItemArgs()
{
Dictionary<string, string> Header = new Dictionary<string, string>();
Header.Add("Authorization", "Bearer <ACCESS_TOKEN>");
Dictionary<string, string> Header2 = new Dictionary<string, string>();
Header2.Add("Authorization", "Bearer <ACCESS_TOKEN>");
Header2.Add("Content-type", "application/octet-stream");
return new Dictionary<string, IArgument>
{
{
Constants.Parameters.InventorDoc,
new XrefTreeArgument
{
Url = "https://developer.api.autodesk.com/oss/v2/buckets/<BUCKET_KEY>/objects/box.ipt",
Headers = Header
}
},
{
"OutputParams",
new XrefTreeArgument
{
Verb = Verb.Put,
Url = "https://developer.api.autodesk.com/oss/v2/signedresources/<SIGNED_RESOURCE_ID>?region=US",
Headers = Header2
}
}
};
}
I cannot work out why the KeyParameters.txt file isn't being generated by my addin, but looking at the log it seems it is and maybe the problem is uploading it to the signed resource, my token has all the needed scopes.
The KeyParameters.txt file isn't generated because your Activity calls this function Run(Document doc). It is possible to see it in your log, check this line:
InventorCoreConsole.exe Information: 0 : Run called with box.ipt
Now just try to move your code to the Run(Document doc) function.
The RunWithArguments(Document doc, NameValueMap map) function is called in case that you have any arguments in the command line in your Activity.
https://forge.autodesk.com/en/docs/design-automation/v3/developers_guide/field-guide/#command-lines
From the error message it seems like your addin is either not generating the "KeyParameters.txt" file or generating it at the wrong location.
Is it possible that your code never enter any of the if statement or it end up in the catch statement without creating the txt file?
You can download the report using the reportUrl, there might be more information in there. You might also be able to add more logging in there to help you understand what is happening.
I recently asked a question here and thanks to Gaurav Mantri I could add Metadata to blob azure .
my Code after editing in AzureBlobStorage class :
public void SaveMetaData(string fileName, string container, string key, string value)
{
var blob = GetBlobReference(fileName, container);
blob.FetchAttributes();
blob.Metadata.Add(key, value);
blob.SetMetadata();
}
and I call it from myController by this :
public JsonResult SaveMetaData(string name, string key, int id)
{
var uploadedFils = _FileStorage.GetUploadedFiles("images", id + "/");
if (!uploadedFils.Any())
_FileStorage.SaveMetaData(name, "images", key, "true");
foreach (var file in uploadedFils)
{
if (name == file.Name)
{
_FileStorage.SaveMetaData(FormatFileName(id, name), "images", key, "true");
}
else
{
_FileStorage.SaveMetaData(FormatFileName(id, file.Name), "images", key, "false");
}
}
return Json("");
}
the code to get uploaded file
public IEnumerable<Attachment> GetUploadedFiles(string container, string blobprefix)
{
if (string.IsNullOrWhiteSpace(container))
container = DefaultBlobContainer;
var storageAccount = CreateStorageAccountFromConnectionString(GetStorageConnectionString());
var blobContainer = GetBlobContainer(storageAccount, container);
var resultList = new List<Attachment>();
try
{
foreach (IListBlobItem item in blobContainer.ListBlobs(blobprefix, false))
{
var blob = (CloudBlockBlob) item;
var file = new Attachment
{
Name = blob.Name.Substring(blob.Name.LastIndexOf('/') + 1),
Size = blob.Properties.Length,
Extension = Path.GetExtension(blob.Name)
};
resultList.Add(file);
}
}
catch (Exception e)
{
}
return resultList;
}
and I call this action when I click on the desired image that I want to set as active .
for first time it works , but I don't know how to edit it for second click ,specially this is first time for me dealing with Azure ?
the logic behind this line that : when the Gallery is empty and the users upload the first image , this image will be set automatically to active:
if (!uploadedFils.Any())
_FileStorage.SaveMetaData(name, "images", key, "true");
According to your description, I checked your code, you need to modify your code as follows:
SaveMetaData method under your AzureBlobStorage class:
public void SaveMetaData(string fileName, string container, string key, string value)
{
var blob = GetBlobReference(fileName, container);
blob.FetchAttributes();
if (blob.Metadata.ContainsKey(key))
{
blob.Metadata[key] = value;
}
else
blob.Metadata.Add(key, value);
blob.SetMetadata();
}
Based on your scenario, your image files would be uploaded to images\{id}\{filename}. And before you invoke the SaveMetaData under your controller, you need to make sure the file with the specific parameters name and id exists in your blob storage. I assumed that you need to remove the following code snippet:
if (!uploadedFils.Any())
FileStorage.SaveMetaData(name, "images", key, "true");
Note: If there has no files, you could not add/update the meta data for it. Also, you just set the name for the parameter fileName without combining the id. Based on my understanding, the SaveMetaData method is used to set meta data for existing files. I recommend you move the above logic to the action for uploading the file and set the default meta data if there has no files.
I'm trying to read and write data with json file.
I created some class.
public class SimpleTask{...}
public class DayTask{...}
public class DataModel
{
...
private async Task GetSimpleTaskAsync()
{
if (_daytask.Count != 0)
return;
string fileName = "a.json";
Uri appUri = new Uri("ms-appx:///"+ fileName);
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(appUri);
string jsonText = await FileIO.ReadTextAsync(file);
JsonObject jsonObject = JsonObject.Parse(jsonText);
JsonArray jsonArray = jsonObject["DayTasks"].GetArray();
foreach (JsonValue daytaskValue in jsonArray)
{
JsonObject daytaskObject = daytaskValue.GetObject();
ObservableCollection<SimpleTask> simpletask = new ObservableCollection<SimpleTask>();
foreach (JsonValue simpletaskValue in daytaskObject["Tasks"].GetArray())
{
JsonObject simpletaskObject = simpletaskValue.GetObject();
simpletask.Add(new SimpleTask( simpletaskObject["StartTime"].GetString(),
simpletaskObject["EndTime"].GetString(),
simpletaskObject["Description"].GetString()));
}
DayTask daytask = new DayTask(daytaskObject["Day"].GetString(),simpletask);
this.DayTasks.Add(daytask);
}
}
}
As you can see, i have a method that gets data form a.json file. I created a.json file:
In the MainPage.xaml.cs, there is a method which calls GetDayTaskAysnc() method and retrieves data :
private async void ReadData1(object sender, TappedRoutedEventArgs e)
{
string test = String.Empty;
var daytask = await DataModel.GetDayTaskAsync();
foreach (var tasks in daytask)
{
test += String.Format("Day:{0}:\n", tasks.Day);
foreach (var simpletask in tasks.Tasks)
{
test += String.Format("\tStart Time: {0}\n", simpletask.StartTime);
test += String.Format("\tEnd Time: {0}\n", simpletask.EndTime);
test += String.Format("\tDescription Time: {0}\n", simpletask.Description);
}
}
TextBlock.Text = test;
}
It worked fine ! But i want to write data to the same file, so i added data in hardcore way:
private List<DayTask> creatList()
{
List<DayTask> DayTasks = new List<DayTask>();
ObservableCollection<SimpleTask> simpletask1 = new ObservableCollection<SimpleTask>();
simpletask1.Add(new SimpleTask("6AM","7AM","Breakfast"));
simpletask1.Add(new SimpleTask("8AM", "9AM", "Game"));
ObservableCollection<SimpleTask> simpletask2 = new ObservableCollection<SimpleTask>();
simpletask2.Add(new SimpleTask("6AM", "7AM", "Sleep"));
simpletask2.Add(new SimpleTask("8AM", "9AM", "School"));
DayTasks.Add(new DayTask ("3/8/2014",simpletask1));
DayTasks.Add(new DayTask("4/8/2014", simpletask2));
return DayTasks;
}
private async void WriteData(object sender, TappedRoutedEventArgs e)
{
string json = "a.json";
List<DayTask> daytasks = creatList();
var serializer = new DataContractJsonSerializer(typeof(List<DayTask>));
var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync(json, CreationCollisionOption.ReplaceExisting);
using (stream)
{
serializer.WriteObject(stream, daytasks);
}
TextBlock.Text = "Write to Json file succeeded";
}
When i ran my app with window phone emulator, Firstly, it wrote to the file. Then i clicked read data button to ensure data written correctly, the emulator showed data from a.json file without being modified by WriteData() method. I continued to creat the second read data method:
private async void ReadData2(object sender, TappedRoutedEventArgs e)
{
string test = String.Empty;
string json = "a.json";
string content = String.Empty;
List<DayTask> mytasks = new List<DayTask>();
var deserializer = new DataContractJsonSerializer(typeof(List<DayTask>));
var stream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(json);
using (stream)
{
mytasks = (List<DayTask>)deserializer.ReadObject(stream);
}
foreach (var tasks in mytasks)
{
test += String.Format("Day:{0}:\n", tasks.Day);
foreach (var simpletask in tasks.Tasks)
{
test += String.Format("\tStart Time: {0}\n", simpletask.StartTime);
test += String.Format("\tEnd Time: {0}\n", simpletask.EndTime);
test += String.Format("\tDescription Time: {0}\n", simpletask.Description);
}
}
TextBlock.Text = test;
}
I deployed my app several times, and this is my result:
ReadData2() : 'System.IO.FileNotFoundException'
WriteData() -> ReadData1(): Data from a.json was shown
WriteData() -> ReadData2(): Data from creatList() was shown
WriteData() -> ReadData1(): Data from a.json was shown -> ReadData2(): Data from creatList() was shown
So that, i have some question:
Do i have 2 json files, one i created by adding into my project and the other one i created when ran WriteData() method ? What is their paths ?
If my file is data.json in DataSource folder, how can i write data to it ? I can read data from it using uri like GetSimpleTaskAsync() but i don't know how to write to it correctly. (I tried to convert object into string to write but can't read it again, i guess i wrote it in wrong way)
Sorry for my long post and my bad english :) Thank you very much
But i want to write data to the same file, so i added data in hardcore way:
Your are making confusion between ms-appx:/// and ms-appdata:/// folders (or ApplicationData.Current.LocalFolder )
The ms-appx folder is read-only. You can't write to it. (or you could edit your app code without passing through the certification process)
The file you wrote must be into the ms-appdata folder.