Reputation: 11
I am developing Blazor Server application using DevExpress controls for Blazor (v 21.1). Target Framework is .NET 5.0, Blazor Server is hosted on IIS.
The goal is to work with some image files which reside in Network Share folder (lets say \server\share\folder) . The network folder is in different path than wwwroot, obviously.
I need to download one ore more files (images) into my blazor app and then display them in a popup window (wizard). Along with images comes data from DB, working with DB data is no problem (EF queries). For simplifying the question: lets say we have a set of [ID, path, description] in SQL DB. For every entry in DB there is a file in network share.
The goal of this part of the application (wizard) is to load existing files along with DB data, display them, do some basic operations like add some new files to file set (by selecting in browser and loading into MemoryStream, this part I have already accomplished) and remove existing files (mark as deleted). At the end the new file set is to be synchronized with the DB and network share, so some delete and upload operations for each of the changed file in file set.
What I try to accomplish is downloading the files from network share into List of MemoryStream (so controller should serve them as Streams).
Please can you point me in the right direction how can it be accomplished?
I have read about api controllers and actions, and it looks like this is what I need (download file action, delete action, upload action).
I implemented some code for upload action (it is working well, gets called, upload files to network share)
[HttpPost("[action]")]
public ActionResult UploadFile(IFormFile myAttachmentFileName)
{
try
{
if (DBCF != null)
{
if (Context == null)
{
Context = DBCF.CreateDbContext();
}
long orderID = 0;
if (long.TryParse(Request.Headers["OID"], out orderID))
{
// some database searching for right orderID
//
if (dbO != null) // order exists in DB
{
var path = GetOrCreateUploadFolder(orderID); //this is working well
}
}
else
{
Response.StatusCode = 400;
}
}
}
catch(Exception )
{
Response.StatusCode = 400;
}
return new EmptyResult();
}
the upload action is called in my Blazor Component like:
<DxUpload Name="myAttachmentFileName" UploadUrl="@GetUploadUrl("api/Upload/UploadFile/")">
<!-- some additional settings for DxUpload, irrelevant -->
</DxUpload>
Now, the part where it gets trickier for me is the download part. I have this in UploadController class:
[HttpGet("[action]")]
public IActionResult DownloadBDPicture(string fileName)
{
try
{
if (System.IO.File.Exists(fileName))
{
var stream = new FileStream(fileName, FileMode.Open);
var result = new FileStreamResult(stream, "image");
//result.FileDownloadName = fileName;
return result;
}
} catch(Exception ex)
{
return null;
}
return null;
}
this is based on the answer:
Blazor Server Side: How to set <img> source as picture from File Share Server
but I struggle to call this api action from code block of my blazor page.
I do it in following manner: query DB for files based on orderID, return the files list and then load every file in foreach loop.
I have tried it with the HttpClient, no success (bad Uri forming, I don't know how the api action should be called from HttpClient).
I thought about NavigationManager, but I don't want to change the page where the wizard is displayed, nor open any new windows for downloading the files (images). I just simply want to load them in form of Stream into List of MemoryStream, for displaying, maybe add some more or mark some of preloaded files as "to delete" and then synchronize them with DB, network share and dispose of MemoryStream List when the app wizard is closed.
Is this the right direction with api controller and HttpClient (getStreamAsync) or maybe the task described earlier can be solved in a different, simpler way?
SUMMARY:
Tried to implement api controller action for downloading file from network share (like \server\share\images) and then tried to call it. I don't know how to call the api action from blazor code block. Should it be something like HttpClient, or NavigationManager, or there is another way?
Upvotes: 1
Views: 1274
Reputation: 11
Problem solved, at least in a very basic way (still working on it)
The solution for the problem with controller calling is implementing separate controller with DownloadFile action, in a completely new class:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
namespace WebMRM.Controllers.AspNetCoreHost
{
[Microsoft.AspNetCore.Mvc.Route("api/[controller]")]
[ApiController]
public partial class DownloadBDFileController : ControllerBase
{
protected string ContentRootPath { get; set; }
public DownloadBDFileController(IWebHostEnvironment hostingEnvironment, IConfiguration appConfig)
{
ContentRootPath = appConfig.GetSection("AWPaths").GetValue<string>("targetPath"); // here we set the path where the files are placed (network share)
}
[HttpGet("[action]")]
public IActionResult DownloadBDFile(string fileName)
{
try
{
var path = Path.Combine(ContentRootPath, fileName);
if (System.IO.File.Exists(path))
{
const string DefaultContentType = "application/octet-stream";
var provider = new FileExtensionContentTypeProvider();
if (!provider.TryGetContentType(path, out string contentType))
{
contentType = DefaultContentType;
}
var stream = new FileStream(path, FileMode.Open);
var result = new FileStreamResult(stream, contentType);
//result.FileDownloadName = fileName;
return result;
}
} catch(Exception ex)
{
}
return new BadRequestResult();
}
}
}
And then the DownloadBDFileController action DownladBDFile is called in wizard app code block like this:
private async Task LoadBDPictureFromDB(tbl_WIZ_IMAGES pictFile)
{
MemoryStream ms = null;
if (pictFile == null) return;
try
{
// TODO - do some check, maybe duplicate files and is a file an image?
string fileExt = pictFile.PICTURE_SOURCE.Substring(pictFile.PICTURE_SOURCE.LastIndexOf("."));
if (IsGraphicFile(fileExt))
{
var httpClient = HttpClientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://localhost:443");
string filePath = pictFile.PICTURE_SOURCE;
string url = $"https://localhost:443/api/DownloadBDFile/DownloadBDFile?fileName={filePath}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var stream = response.Content.ReadAsStreamAsync();
ms = new MemoryStream();
await stream.Result.CopyToAsync(ms);
WizDataDTO.listWizardPictureData.Add(ms);
}
else
{
}
}
else
{
// TODO return false and maybe set some kind of error loading image
}
}
catch (Exception ex)
{
fileReadingException = ex;
}
if (ms != null) ms.Dispose();
ms = null;
GC.Collect();
}
Somehow when this DownloadBDFile action was placed in UploadController class aside another HttpPost action, it hasn't been called properly from the HttpClient (error 404 satus in response). After moving Download action to separate controller all is working well.
Probably I was missing something obvious or the code in UploadController wasn't right.
Upvotes: 0