I have a web API function that successfully upload files to Dropbox (using their new .NET SDK) and then gets shared links to the uploaded files (each document a time).
private async Task<string> Upload(DropboxClient dbx, string localPath, string remotePath)
{
const int ChunkSize = 4096 * 1024;
using (var fileStream = File.Open(localPath, FileMode.Open))
{
if (fileStream.Length <= ChunkSize)
{
WriteMode mode = new WriteMode();
FileMetadata fileMetadata = await dbx.Files.UploadAsync(remotePath, body: fileStream, mode: mode.AsAdd, autorename: true);
//set the expiry date
var settings = new SharedLinkSettings(expires: DateTime.Today.AddDays(7));
SharedLinkMetadata sharedLinkMetadata = await dbx.Sharing.CreateSharedLinkWithSettingsAsync(fileMetadata.PathLower, settings);
return sharedLinkMetadata.Url;
}
else
{
await this.ChunkUpload(dbx, remotePath, fileStream, ChunkSize);
}
return "error";
}
}
That usually works fine but when I upload the exact same document (name and content) twice, nothing happens and I do need to have both files stored in my Dropbox account.
It can be as much same documents as needed (not only two), my best scenario would be to have the second document (and third etc..) automatically renamed and uploaded to Dropbox.
Any idea on how to accomplish that?
I post the answer, maybe it will help someone.. I spent long time till I figure it out.
This is the code that checks if a file already exists in Dropbox.
If the file exists it checks if a link was shared for this file and based on the result it generates/retrieves a/the shared link.
private async Task<string> Upload(DropboxClient dbx, string localPath, string remotePath)
{
const int ChunkSize = 4096 * 1024;
using (var fileStream = File.Open(localPath, FileMode.Open))
{
if (fileStream.Length <= ChunkSize)
{
WriteMode mode = new WriteMode();
FileMetadata fileMetadata = await dbx.Files.UploadAsync(remotePath, body: fileStream, mode: mode.AsAdd, autorename: true);
//set the expiry date
var existingDoc = await dbx.Files.GetMetadataAsync(remotePath);
if (existingDoc.IsFile)
{
var sharedLink = await dbx.Sharing.ListSharedLinksAsync(remotePath);
var settings = new ListSharedLinksArg(remotePath);
ListSharedLinksResult listSharedLinksResult = await dbx.Sharing.ListSharedLinksAsync(remotePath);
if (listSharedLinksResult.Links.Count > 0)
{
return listSharedLinksResult.Links[0].Url;
}
else
{
var sharedLinkSettings = new SharedLinkSettings(expires: DateTime.Today.AddDays(7));
SharedLinkMetadata sharedLinkMetadata = await dbx.Sharing.CreateSharedLinkWithSettingsAsync(remotePath, sharedLinkSettings);
return sharedLinkMetadata.Url;
}
}
else
{
var settings = new SharedLinkSettings(expires: DateTime.Today.AddDays(7));
SharedLinkMetadata sharedLinkMetadata = await dbx.Sharing.CreateSharedLinkWithSettingsAsync(fileMetadata.PathLower, settings);
return sharedLinkMetadata.Url;
}
}
else
{
var sharedLink = await this.ChunkUpload(dbx, remotePath, fileStream, ChunkSize);
return sharedLink;
}
}
When you upload the same exact content to the same path again, the Dropbox API won't produce a conflict or another copy of the file, as nothing changed. (Edit: you can force a conflict even for identical contents by using strictConflict=true, e.g., on UploadAsync).
If you want another copy of the same data in your account, you can specify the different desired path when calling UploadAsync the second time.
Or, more efficiently, you can use CopyAsync to make a copy of the file already on Dropbox.
Related
I have put together the following function to upload a file from the web server to OneDrive with MS Graph. However, I am no longer saving files to the server and would rather just take the IFormFile from my users form submit and then upload that straight to OneDrive. However, my function uses a path on the web server. How do I convert this to use IformFile directly without saving it to a physical server path?
public async Task<String> UploadFileAsync(string DriveID, string SourceFile, string DestinationPath)
{
try
{
using var fileStream = System.IO.File.OpenRead(SourceFile);
// Use properties to specify the conflict behavior
// in this case, replace
var uploadProps = new DriveItemUploadableProperties
{
AdditionalData = new Dictionary<string, object>
{
{ "#microsoft.graph.conflictBehavior", "fail" }
}
};
// Create the upload session
// itemPath does not need to be a path to an existing item
var uploadSession = await _graphServiceClient.Me.Drives[DriveID].Root
.ItemWithPath(DestinationPath)
.CreateUploadSession(uploadProps)
.Request()
.PostAsync();
// Max slice size must be a multiple of 320 KiB
int maxSliceSize = 320 * 1024;
var fileUploadTask =
new LargeFileUploadTask<DriveItem>(uploadSession, fileStream, maxSliceSize);
var totalLength = fileStream.Length;
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(prog => {
Console.WriteLine($"Uploaded {prog} bytes of {totalLength} bytes");
});
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
Console.WriteLine(uploadResult.UploadSucceeded ?
$"Upload complete, item ID: {uploadResult.ItemResponse.Id}" :
"Upload failed");
return uploadResult.ItemResponse.Id;
}
catch (ServiceException ex)
{
Console.WriteLine($"Error uploading: {ex.ToString()}");
return "Failed: " + ex.ToString();
}
}
This creates the file no problem and returns the
I'm trying out a few things with Blazor and I'm still new to it. I'm trying to get a file stream to download to the browser. What's the best way to download a file from Blazor to browser?
I've tried using a method in my razor view that returns a stream but that didn't work.
//In my Blazor view
#code{
private FileStream Download()
{
//get path + file name
var file = #"c:\path\to\my\file\test.txt";
var stream = new FileStream(test, FileMode.OpenOrCreate);
return stream;
}
}
The code above doesn't give me anything, not even an error
Another solution is to add a simple api controller endpoint using endpoints.MapControllerRoute. This will work only with server side blazor though.
Ex:
endpoints.MapBlazorHub();
endpoints.MapControllerRoute("mvc", "{controller}/{action}");
endpoints.MapFallbackToPage("/_Host");
Then add a controller. For example:
public class InvoiceController : Controller
{
[HttpGet("~/invoice/{sessionId}")]
public async Task<IActionResult> Invoice(string sessionId, CancellationToken cancel)
{
return File(...);
}
}
Usage in a .razor file:
async Task GetInvoice()
{
...
Navigation.NavigateTo($"/invoice/{orderSessionId}", true);
}
Although the above answer is technically correct, if you need to pass in a model -POST-, then NavigationManager won't work. In which case you, must likely end up using HttpClient component. If so wrap the response.Content -your stream- in a DotNetStreamReference instance - new DotNetStreamReference(response.Content). This will create a ReadableStream. Then create the blob with the content. Keep in mind DotNetStreamReference was recently introduced with .NET 6 RC1. As of now the most efficient way. Otherwise, you can use fetch API and create a blob from the response.
I wound up doing it a different way, not needing NavigationManager. It was partially taken from the Microsoft Docs here. In my case I needed to render an Excel file (using EPPlus) but that is irrelevant. I just needed to return a Stream to get my result.
On my Blazor page or component when a button is clicked:
public async Task GenerateFile()
{
var fileStream = ExcelExportService.GetExcelStream(exportModel);
using var streamRef = new DotNetStreamReference(stream: fileStream);
await jsRuntime.InvokeVoidAsync("downloadFileFromStream", "Actual File Name.xlsx", streamRef);
}
The GetExcelStream is the following:
public static Stream GetExcelStream(ExportModel 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.SomeUsefulName);
var rowIndex = 1;
foreach (var dataRow in exportModel.Rows)
{
...
// Add rows and cells to the worksheet
...
}
sheet.Cells.AutoFitColumns();
package.SaveAs(result);
}
result.Position = 0; // This is required or no data is in result
return result;
}
This JavaScript is in the link above, but adding it here as the only other thing I needed.
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 need to maintain a historical record of certain documents, my initial solution was to copy them to a shared folder from .NET, but that doesn't seem so safe to me. Can I make the upload of those files to One Drive by using .NET with C#? If so, I would like documentation about it, I've already done a free search and I haven't found anything that could satisfy my needs. I apologize if the question is way too vague. Thank you.
Maybe this can help you out:
Using the code:
public string OneDriveApiRoot { get; set; } = "https://api.onedrive.com/v1.0/";
Upload file to OneDrive
//this is main method of upload file to OneDrive
public async Task<string> UploadFileAsync(string filePath, string oneDrivePath)
{
//get the upload session,we can use this session to upload file resume from break point
string uploadUri = await GetUploadSession(oneDrivePath);
//when file upload is not finish, the result is upload progress,
//When file upload is finish, the result is the file information.
string result = string.Empty;
using (FileStream stream = File.OpenRead(filePath))
{
long position = 0;
long totalLength = stream.Length;
int length = 10 * 1024 * 1024;
//In one time, we just upload a part of file
//When all file part is uploaded, break out in this loop
while (true)
{
//Read a file part
byte[] bytes = await ReadFileFragmentAsync(stream, position, length);
//check if arrive file end, when yes, break out with this loop
if (position >= totalLength)
{
break;
}
//Upload the file part
result = await UploadFileFragmentAsync(bytes, uploadUri, position, totalLength);
position += bytes.Length;
}
}
return result;
}
private async Task<string> GetUploadSession(string oneDriveFilePath)
{
var uploadSession = await AuthRequestToStringAsync(
uri: $"{OneDriveApiRoot}drive/root:/{oneDriveFilePath}:/upload.createSession",
httpMethod: HTTPMethod.Post,
contentType: "application/x-www-form-urlencoded");
JObject jo = JObject.Parse(uploadSession);
return jo.SelectToken("uploadUrl").Value<string>();
}
private async Task<string> UploadFileFragmentAsync(byte[] datas, string uploadUri, long position, long totalLength)
{
var request = await InitAuthRequest(uploadUri, HTTPMethod.Put, datas, null);
request.Request.Headers.Add("Content-Range", $"bytes {position}-{position + datas.Length - 1}/{totalLength}");
return await request.GetResponseStringAsync();
}
Get Share Link: ( Javascript )
//This method use to get ShareLink, you can use the link in web or client terminal
public async Task<string> GetShareLinkAsync(string fileID, OneDriveShareLinkType type, OneDrevShareScopeType scope)
{
string param = "{type:'" + type + "',scope:'" + scope + "'}";
string result = await AuthRequestToStringAsync(
uri: $"{OneDriveApiRoot}drive/items/{fileID}/action.createLink",
httpMethod: HTTPMethod.Post,
data: Encoding.UTF8.GetBytes(param),
contentType: "application/json");
return JObject.Parse(result).SelectToken("link.webUrl").Value<string>();
}
From: https://code.msdn.microsoft.com/office/How-to-upload-file-to-21125137
An easy solution for beginners:
Install OneDrive app on your computer then login to your account and set a folder to it.
Then you just need to copy your files in the folder you set in the last step. It would be synced automatically by OneDrive application.
File.Copy(sourceFileFullPath,OneDriveFileFullPath);
public static async Task Store(ObservableCollection<Product> list)
{
Uri path = new Uri("ms-appx:///ListCollection.json");
var store = await StorageFile.GetFileFromApplicationUriAsync(path);
var stream = File.OpenWrite(store.Path);
var serialize = new DataContractJsonSerializer(typeof(ObservableCollection<Product>));
serialize.WriteObject(stream, list);
}
Ok this is the piece of code that I used to serialize a collection , works very well , no problem with it , but what I want and tried and no success. I created a JSON file in my project. I want to store and stream data to that file. I tried some methods but no success , how do I open a stream to a file that is currently in my project?
EDITED : Commented the code that was working and wrote what I intend to do. Thanks for support.
When I get to this line
var stream = File.OpenWrite(store.Path); it says that is inaccesible.
What I intend to do is serialize some data to a file called ListCollection.json that is emtpy , that file is project file. It might be the stream or it might be the file that gives me that error. No idea.
My guess is that your project file is located in the installation directory of your application and as far as I know you can't just write to that directory.
You would have to put a deployment action in your solution that writes the desired project file to the application data directory. There you should be able to write it.
I looked through some of the documentation and came accross this:
MSDN
The app's install directory is a read-only location.
I found a Link which makes use of a little hack or so it seems.
I am not sure if this will work if the application is deployed etc.
but you can try this to write the file.
I am not sure if you need a stream or not but feel free to comment:
private void Button_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<string> list = new ObservableCollection<string>();
list.Add("Hallo");
list.Add("Welt");
Task t = Store(list);
}
public static async Task Store(ObservableCollection<string> list)
{
StorageFile file = await GetStorageFileFromApplicationUriAsync();
if (file == null)
{
file = await GetStorageFileFromFileAsync();
}
if (file != null)
{
await file.DeleteAsync();
await CreateFileInInstallationLocation(list);
}
}
private static async Task<StorageFile> GetStorageFileFromFileAsync()
{
StorageFile file = null;
if (file == null)
{
try
{
StorageFolder folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
file = await folder.GetFileAsync("ListCollection.json");
}
catch
{ }
}
return file;
}
private static async Task<StorageFile> GetStorageFileFromApplicationUriAsync()
{
StorageFile file = null;
try
{
Uri path = new Uri("ms-appx:///ListCollection.json");
file = await StorageFile.GetFileFromApplicationUriAsync(path);
}
catch
{ }
return file;
}
private static async Task CreateFileInInstallationLocation(ObservableCollection<string> list)
{
var pkg = Windows.ApplicationModel.Package.Current;
var installedLocationFolder = pkg.InstalledLocation;
try
{
var file = await installedLocationFolder.CreateFileAsync("ListCollection.json", Windows.Storage.CreationCollisionOption.GenerateUniqueName);
var filePath = file.Path;
DataContractJsonSerializer serialize = new DataContractJsonSerializer(typeof(ObservableCollection<String>));
using (Stream stream = await file.OpenStreamForWriteAsync())
{
serialize.WriteObject(stream, list);
stream.Flush();
}
}
catch (Exception ex)
{
var msg = ex.Message;
}
}
What this basically does is:
Find the file
Delete the file
Create a new file
Write your JSON to the file
I am really not an expert on this matter and it even to me seems pretty hacky but it apparently does the job.
If you can avoid writing to the install directory do it and use the method Frank J proposed
Basically, the app displays images, and I want the user to be able to select an image for download and store it locally.
I have the URL, but I don't know how to use that url in conjunction with the filepicker.
You can use the following method to download the file from a given Uri to a file selected with a file picker:
private async Task<StorageFile> SaveUriToFile(string uri)
{
var picker = new FileSavePicker();
// set appropriate file types
picker.FileTypeChoices.Add(".jpg Image", new List<string> { ".jpg" });
picker.DefaultFileExtension = ".jpg";
var file = await picker.PickSaveFileAsync();
using (var fileStream = await file.OpenStreamForWriteAsync())
{
var client = new HttpClient();
var httpStream = await client.GetStreamAsync(uri);
await httpStream.CopyToAsync(fileStream);
fileStream.Dispose();
}
return file;
}
I think you can always read the file as a stream and save it bit by bit on the local machine. But I need to say that I've done this many times in JAVA, I never needed to check this in C# :)
SaveFileDialog myFilePicker = new SaveFileDialog();
//put options here like filters or whatever
if (myFilePicker.ShowDialog() == DialogResult.OK)
{
WebClient webClient = new WebClient();
webClient.DownloadFile("http://example.com/picture.jpg", myFilePicker.SelectedFile);
}