- ABP Framework version: v5.1.3
- UI type: Blazor Server
- DB provider: EF Core
- Tiered (MVC) or Identity Server Separated (Angular): yes
Hi , i am using Syncfusion Blazor Componentsto to handle upload and dowload like in this ApiController Code:
[RemoteService]
[Area("app")]
[ControllerName("FileManager")]
[Route("api/app/filemanager")]
[IgnoreAntiforgeryToken]
public class FileManagerController : AbpController, IFileManagerAppService
{
{
// Processing the Download operation
[Route("Download")]
public IActionResult Download(string downloadInput)
{
//Invoking download operation with the required paramaters
// path - Current path where the file is downloaded; Names - Files to be downloaded;
FileManagerDirectoryContent args = JsonConvert.DeserializeObject<FileManagerDirectoryContent>(downloadInput);
return operation.Download(args.Path, args.Names);
}
// Processing the Upload operation
[Route("Upload")]
public IActionResult Upload(string path, IList<IFormFile> uploadFiles, string action)
{
//Invoking upload operation with the required paramaters
// path - Current path where the file is to uploaded; uploadFiles - Files to be uploaded; action - name of the operation(upload)
FileManagerResponse uploadResponse;
uploadResponse = operation.Upload(path, uploadFiles, action, null);
if (uploadResponse.Error != null)
{
Response.Clear();
Response.ContentType = "application/json; charset=utf-8";
Response.StatusCode = Convert.ToInt32(uploadResponse.Error.Code);
Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = uploadResponse.Error.Message;
}
return Content("");
}
}
Here all the work is done in the ApiController.
I want to be able to integrate that ApiCalls in an ApplicationService, so i can easly access it, from other Applicationservices and UI. Normally an ApiController just call the same method in Application Service, but this time it should be otherwise.
I tried this so far:
public interface IFileManagerAppService : IApplicationService
{
Task<IActionResult> Download(string downloadInput);
Task<IActionResult> Upload(string path, IList<IFormFile> uploadFiles, string action);
}
public class FileManagerAppService : ApplicationService , IFileManagerAppService
{
public FileManagerAppService()
{
}
How should i implement them here, if the work is done in apicontroller ?
// Task<IActionResult> Download(string downloadInput)
//Task<IActionResult> Upload(string path, IList<IFormFile> uploadFiles, string action)
}
I doesn't seem to be a good way to implement all logic in appication layer, because its an lowlevel api, which also uses a physical Layer etc . A lot more is done in that layer. look here : https://blazor.syncfusion.com/documentation/file-manager/getting-started
So how can i solve this ? I would like to be to call these Method after injection at Blazor Client-side .
11 Answer(s)
-
0
hi
See https://docs.abp.io/en/abp/5.1/Application-Services#working-with-streams
-
0
Hi Maliming, do you imean i should use it like that ?:
public Task<IRemoteStreamContent> Download(Guid id) { var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.OpenOrCreate); return Task.FromResult( (IRemoteStreamContent) new RemoteStreamContent(fs) { ContentType = "application/octet-stream" } ); } public async Task Upload(Guid id, IRemoteStreamContent streamContent) { using (var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.Create)) { await streamContent.GetStream().CopyToAsync(fs); await fs.FlushAsync(); } }
Can you give me an example ?, because the method header wont match with above methodes, So should i then create an on applicationservice with above to be able to use it ?
-
0
because the method header wont match with above methodes,
What do you mean?
Here is full example https://docs.abp.io/en/abp/5.1/Application-Services#working-with-streams
-
0
What i mean is, that its possible to use IRemoteStreamContent like in the link if you call the download /upload from an other Application Service. The logic in the link is the same as always. Thelogic and operations are done at application level, not apicontroller level. ( also the download method return a mvc FileStreamResult not a io stream - Casting would cause additional memory use. )
But here the logic and operations for the download/upload are defined in the apiController. So before i am able to use these method, i first need to generate an ApplicationService. Before this is not done, i can't access these methods from other applicationservice.
So please explain, how you would write the ApplicationService/IApplicationService for the above code ?
-
0
-
0
The code above again calls from an apicontroller the applicationservice. Also IRemoteStreamContent is System.IO.Stream and i get FileResultStream. And casting and bufferings in same app from different controller doesn't seam right.
My Controller works fine when used in swagger, but isn't accessible in application service.
I found a possible workaround:
public virtual async Task<IActionResult> Download(string downloadInput) { // only dummy return new ContentResult(); } public virtual async Task<IActionResult> Upload(string path, IList<IFormFile> uploadFiles, string action) { // only dummy return new ContentResult(); }
I created the functions as dummy at applicationlevel. In fact they will never be called, because at apicontroller level, we wont call them.. But for other apllicationservices this should work, because of same method pattern
-
0
You can use
IRemoteStreamContent
as the controller return type.https://github.com/abpframework/abp/blob/e3e1779de6df5d26f01cdc8e99ac9cbcb3d24d3c/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs#L15 https://github.com/abpframework/abp/blob/e3e1779de6df5d26f01cdc8e99ac9cbcb3d24d3c/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs#L14
-
0
I did test my workaround.. If i call from swagger and hit directly the webapi for download it works. But if i try to access it via Applicationservice from other Applicationservice it doesn't work, because the apicontroller isn't called (whats the correct behaviour). So my workaround doesn't work.
I would like to specify more detailed, where i see the problem here:
The ApiController performes some actions, and also manipulate HttpRequest and HttpResponse for example if i upload a file and add custom data from clientside to the request header, i would get it in the controller like :
[HttpPost] [Route("save")] public async Task Save(IList<IFormFile> chunkFile, IList<IFormFile> UploadFiles) { var data = Request.Form["path"]; <<----- here var path = data.ToString().Replace("/", "\\"); .... }
I did check your links. Yes i could implement the needed return type in the controller method. But that still doesn't solve my problem. I can't move the complete logic to the application layer (because it depends on api request,response etc.) and i also can't generate an application service for it to make it accessable for other applicationservices ? I still get not the advantage of RemoteStreamContent.
I would like to make that working api call accessable through application service. To be more concret, how can i achieve this, when some logic is always neccesary at apiController level.
Please let me know how you would really solve this, pleae do not only post a link like. I need a concret pattern.
-
0
hi
You cannot call controller methods from application services in a monolithic project.
Like I said above and shared URL, you can control
IRemoteStreamContent
in the controller and app services.If your app services dynamically generate the APIs, I recommend you exclude this method and implement and call the app service in a new controller.
-
0
Hi Maliming,
i did rethink my problem and found a good solution:
Other Applicationservices only need the Download Method, so i setup an ApplicationService as you suggested with IRemoteStreamContent . From Blazor-Client Side its not a problem, because they directly use the ApiController..
So i moved only the Download Part from ApiController to ApplicationService like this:
public class FileManagerAppService : ApplicationService , IFileManagerAppService { public PhysicalFileProvider operation; public string basePath; public string fullPath; public string uploadPath; public string workPath; public string installPath; public string root = "FileManagement"; public FileManagerAppService() { basePath = AppDomain.CurrentDomain.BaseDirectory; fullPath = Path.Combine(basePath, root); uploadPath = Path.Combine(fullPath, "Uploads"); workPath = Path.Combine(fullPath, "Work"); installPath = Path.Combine(fullPath, "Install"); operation = new PhysicalFileProvider(); operation.RootFolder(fullPath); // Data\Files denotes in which files and folders are available. } public async Task<IRemoteStreamContent> Download(string downloadInput) { //Invoking download operation with the required paramaters // path - Current path where the file is downloaded; Names - Files to be downloaded; FileManagerDirectoryContent args = JsonSerializer.Deserialize<FileManagerDirectoryContent>(downloadInput); if (args.Path.IsNullOrEmpty()) { throw new UserFriendlyException("Error during Download. Parameter downloadInput doesn't contain a valid Path."); } var downStream = operation.Download(args.Path, args.Names); // wait max 60s (for zipping files) WaitforStream(downStream, 60); // set download name to id, if more then one file is downloaded if (downStream.FileDownloadName == "files.zip") downStream.FileDownloadName = args.Path.Split("/")[2] + ".zip"; return new RemoteStreamContent(downStream.FileStream) { ContentType = "application/octet-stream", FileName = downStream.FileDownloadName }; } public async Task CreateFileAsync(CreateFileInput input) { using (var fs = new FileStream("C:\\Temp\\" + input.Content.FileName, FileMode.Create)) { await input.Content.GetStream().CopyToAsync(fs); await fs.FlushAsync(); } } private static bool WaitforStream(FileStreamResult fs, int timeout)//* in sec { var time = Stopwatch.StartNew(); while (time.ElapsedMilliseconds < timeout * 1000) { try { if (fs?.FileStream?.Length > 0) return true; } catch (IOException) { return false; } } throw new UserFriendlyException("Failed perform action: download within allowed time."); } }
In my other ApplicationSertvice where i perform other requires operation, i can then call it like that:
public virtual async Task Download(Guid id) { .... perform other action ... // Generate downloadInput for Syncfusion var downloadInput = JsonSerializer.Serialize( new DownloadInfo() { Path = ... , Names = ....Select(x => x.Name).ToArray() }); await _fileManagerAppService.CreateFileAsync(new CreateFileInput() { Content = await _fileManagerAppService.Download(downloadInput) }); }
Maliming, i thank you for your hints and good scrrenshots.. The were very helpful. !!
-
0
Good news!