Open Closed

Howto define ApplicationService to use Method from ApiController or howto integrate it best ? #2623


User avatar
0
hakan.uskaner created
  • 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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    See https://docs.abp.io/en/abp/5.1/Application-Services#working-with-streams

  • User Avatar
    0
    hakan.uskaner created

    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 ?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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

  • User Avatar
    0
    hakan.uskaner created

    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 ?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Here is file-management module code:

  • User Avatar
    0
    hakan.uskaner created

    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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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

  • User Avatar
    0
    hakan.uskaner created

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0
    hakan.uskaner created

    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. !!

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Good news!

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11