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.
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.
Hello I try to download file from server.
Here is my controller
public ActionResult Downloads()
{
var dir = new System.IO.DirectoryInfo(Server.MapPath("~/Content/AnnFiles/"));
System.IO.FileInfo[] fileNames = dir.GetFiles("*.*"); List<string> items = new List<string>();
foreach (var file in fileNames)
{
items.Add(file.Name);
}
return View(items);
}
public FileResult Download(string file)
{
var FileVirtualPath = "~/Content/AnnFiles/" + file;
return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
and in my view
#Html.ActionLink("Download", "Download", "announcement", new { id = Model.file})
It doesn't work. It returns the error
Could not find a part of the path 'C:\Myprojects\MyprojectName\MyprojectName\Content\AnnFiles\'
Any idea?
thank you
You need to use the following overload;
#Html.ActionLink("Download", "Download", "announcement", new { file = Model.file}, null);
Adding the null parameter at the end uses the following:
LinkExtensions.ActionLink Method (HtmlHelper, String, String, String, RouteValueDictionary, IDictionary)
Fiddle
I have a container called systemdesignwhich have several subfolders for example dfd usecase and other UML design tool names. I want to show the images of a particular "folder" for example dfd, rather than all the images found inside this container.
Below are some screenshots, and partial code that revolves around this. Please do not mind the nature of the images those are just test data.
http://i.imgur.com/fVs1SZk.png [Shows everything rather than a container]
EDIT: For example in folder dfd there should be just one picture, 2nd folder should be another 3.
http://i.imgur.com/kMyBLca.png [How my "directories" are sectioned]
The Code that affects the above:
SystemDesignController
// GET: SystemDesign
public ActionResult Index()
{
StorageCredentials credentials = new StorageCredentials(storagename, accountkey);
CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, true);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer storageContainer = blobClient.GetContainerReference("systemdesign");
Models.SystemDesignModel blobsList = new Models.SystemDesignModel(storageContainer.ListBlobs(useFlatBlobListing: true));
return View(blobsList);
}
SystemDesignModel.cs // This class holds the model and ultimately is used in the View to show the images
public class SystemDesignModel
{
public SystemDesignModel() : this(null)
{
Files = new List<SystemDesign>();
}
public SystemDesignModel(IEnumerable<IListBlobItem> list)
{
Files = new List<SystemDesign>();
if (list != null && list.Count<IListBlobItem>() > 0)
{
foreach (var item in list)
{
SystemDesign info = SystemDesign.CreateImageFromIListBlob(item);
if (info != null)
{
Files.Add(info);
}
}
}
else
{
}
}
public List<SystemDesign> Files { get; set; }
}
index.cshtml Partial Code For the View that affects this
#foreach (var item in Model.Files)
{
<img src="#item.URL" height="128" width="128"/>
}
What I have tried to do by now:
1) I tried changing CloudBlobContainer storageContainer = blobClient.GetContainerReference("systemdesign"); from systemdesign to dfdbut it issued me a 404 and a storageException in SystemDesignModel.cs if condition
2) tried useFlatBlobListing as false but it output nothing.
Any idea how I can output just one folder according to the section I want?
Thanks
When listing blobs in a container, you can pass a blob prefix which is essentially the name of the folder (dfd, usecase etc. in your example). Then you will only see the blobs in that folder. Here's the link to the documentation: https://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storage.blob.cloudblobcontainer.listblobs.aspx.