diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md new file mode 100644 index 0000000000..6207c1df07 --- /dev/null +++ b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md @@ -0,0 +1,462 @@ +# Real Time Messaging In A Distributed Architecture Using Abp Framework, SingalR & RabbitMQ + +In this article, we will build a basic real time messaging application in a distributed architecture. We will use [Abp Framework](https://abp.io) for infrastructure and tiered startup template, [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) for real time server-client communication and [RabbitMQ](https://www.rabbitmq.com/) as the distributed event bus. + +When Web & API tiers are separated, it is impossible to directly send a server-to-client message from the HTTP API. This is also true for a microservice architected application. We suggest to use the distributed event bus to deliver the message from API application to the web application, then to the client. + + +![data flow](dataflow-diagram.png) + +Above, you can see the data-flow that we will implement in this article. This diagram represents how data will flow in our application when **Client 1** sends a message to **Client 2**. It is explained in 5 steps: + +1. **Client 1** sends a message data to **Web Application** via REST call. +2. **Web Application** redirects the message data to **Http Api**. +3. The message data is processed in **Http Api** and **Http Api** publishes an event that holds the data that will be sent to **Client 2**. +4. **Web application**, that is subscribed to that event, receives it. +5. **Web Application** sends the message to **Client 2**. + +For this example flow, we could send message from **Client 1** to **Client 2** directly on the **SignalR Hub**. However, what we are trying here to demonstrate is sending a real-time message from the **Http Api** to a specific user who is connected to the web application. + +## Implementation + +### Startup template and initial run + +[Abp Framework](www.abp.io) offers startup templates to get into the business faster. We can download a new tiered startup template using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI): + +`abp new SignalRTieredDemo --tiered` + +After download is finished, we run ***.DbMigrator** project to create the database and seed initial data (admin user, role etc). Then we run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** to see our application working. + +### Creating Application Layer + +We create an [application service](https://docs.abp.io/en/abp/latest/Application-Services) that publishes the message as event. + +In ***.Application.Contracts** project: + +````csharp +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace SignalRTieredDemo +{ + public interface IChatAppService : IApplicationService + { + Task SendMessageAsync(SendMessageInput input); + } +} +```` +Input DTO for SendMessageAsync method: + +````csharp +namespace SignalRTieredDemo +{ + public class SendMessageInput + { + public string TargetUserName { get; set; } + + public string Message { get; set; } + } +} +```` +Event transfer object (ETO) for communication on event bus: + +````csharp +using System; + +namespace SignalRTieredDemo +{ + public class ReceivedMessageEto + { + public string ReceivedText { get; set; } + + public Guid TargetUserId { get; set; } + + public string SenderUserName { get; set; } + + public ReceivedMessageEto( + Guid targetUserId, string senderUserName, string receivedText) + { + ReceivedText = receivedText; + TargetUserId = targetUserId; + SenderUserName = senderUserName; + } + } +} + +```` + +In ***.Application** project: + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Identity; + +namespace SignalRTieredDemo +{ + public class ChatAppService: SignalRTieredDemoAppService, IChatAppService + { + private readonly IIdentityUserRepository _identityUserRepository; + private readonly ILookupNormalizer _lookupNormalizer; + private readonly IDistributedEventBus _distributedEventBus; + + public ChatAppService(IIdentityUserRepository identityUserRepository, ILookupNormalizer lookupNormalizer, IDistributedEventBus distributedEventBus) + { + _identityUserRepository = identityUserRepository; + _lookupNormalizer = lookupNormalizer; + _distributedEventBus = distributedEventBus; + } + + public async Task SendMessageAsync(SendMessageInput input) + { + var targetId = (await _identityUserRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(input.TargetUserName))).Id; + + await _distributedEventBus.PublishAsync(new ReceivedMessageEto(targetId, CurrentUser.UserName, input.Message)); + } + } +} +```` + +### Creating API Layer + +We create an endpoint for sending message that redirects the process to application layer: + +In **controllers** folder of ***.HttpApi** project: + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace SignalRTieredDemo.Controllers +{ + [Route("api/app/chat")] + public class ChatController : AbpController, IChatAppService + { + private readonly IChatAppService _chatAppService; + + public ChatController(IChatAppService chatAppService) + { + _chatAppService = chatAppService; + } + + [HttpPost] + [Route("send-message")] + public async Task SendMessageAsync(SendMessageInput input) + { + await _chatAppService.SendMessageAsync(input); + } + } +} +```` +### Adding SignalR + +To add SignalR to our solution, we add `Volo.Abp.AspNetCore.SignalR` nuget package to ***.Web** project. + +And then add `AbpAspNetCoreSignalRModule` dependency: + +````csharp + +namespace SignalRTieredDemo.Web +{ + [DependsOn( + ... + typeof(AbpAspNetCoreSignalRModule) // <--- + )] + public class SignalRTieredDemoWebModule : AbpModule + { +```` + +Also, we need to add [@abp/signalr](https://www.npmjs.com/package/@abp/signalr) npm package to package.json in ***.Web** project, then run **yarn** and **gulp** commands. + +`````json +{ + . + . + "dependencies": { + . + . + "@abp/signalr": "^2.9.0" + } +} +````` + +*Remember to add the latest package version.* + +You can find more information for Abp SignalR Integration on [the related document](https://docs.abp.io/en/abp/latest/SignalR-Integration). + +### Creating A Hub + +We need a hub for SignalR connection. We can inherit it from `AbpHup` base class. + +In ***.Web** project: + +````csharp +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.AspNetCore.SignalR; + +namespace SignalRTieredDemo.Web +{ + [Authorize] + public class ChatHub : AbpHub + { + } +} + +```` + +While you could inherit from the standard `Hub` class, `AbpHub` has some common services pre-injected as base properties, which is useful on your development. + +### Adding & Configuring RabbitMQ + +To add RabbitMQ to our solution, we add `Volo.Abp.EventBus.RabbitMQ` nuget package to ***.HttpApi.Host** and ***.Web** projects. + +Launch a **command line**, navigate to directory where ***.HttpApi.Host.csproj** file exist, and run the command below using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI): + +````bash + abp add-package Volo.Abp.EventBus.RabbitMQ +```` + +Then do the same for ***.Web** project. + +After we add the package, we configure RabbitMQ by adding configuration in **appsettings.json** files of those projects. + +For ***.HttpApi.Host** project: + +````json +{ + ... + "RabbitMQ": { + "Connections": { + "Default": { + "HostName": "localhost" + } + }, + "EventBus": { + "ClientName": "SignalRTieredDemo_HttpApi", + "ExchangeName": "SignalRTieredDemoTest" + } + }, + ... +} +```` +For ***.Web** project: +````json +{ + ... + "RabbitMQ": { + "Connections": { + "Default": { + "HostName": "localhost" + } + }, + "EventBus": { + "ClientName": "SignalRTieredDemo_Web", + "ExchangeName": "SignalRTieredDemoTest" + } + }, + ... +} + +```` + +### Handling New Message Event + +Once we publish a new message event from `Http Api`, we must to handle it in `Web Application`. Therefore we need an event handler in ***.Web** Project: + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace SignalRTieredDemo.Web +{ + public class ReceivedMessageEventHandler : + IDistributedEventHandler, + ITransientDependency + { + private readonly IHubContext _hubContext; + + public ReceivedMessageEventHandler(IHubContext hubContext) + { + _hubContext = hubContext; + } + + public async Task HandleEventAsync(ReceivedMessageEto eto) + { + var message = $"{eto.SenderUserName}: {eto.ReceivedText}"; + + await _hubContext.Clients + .User(eto.TargetUserId.ToString()) + .SendAsync("ReceiveMessage", message); + } + } +} +```` + +### Creating Chat Page + +We create the files below in **Pages** folder of ***.Web** Project. + + **Chat.cshtml**: + +````html +@page +@using Volo.Abp.AspNetCore.Mvc.UI.Packages.SignalR +@model SignalRTieredDemo.Web.Pages.ChatModel +@section styles { + +} +@section scripts { + + +} +

Chat

+ +
+ + +
All Messages:
+
    +
+
+ +
+ + + + + + + + + + + + + + + + + +
+
+
+
+```` + + **Chat.cshtml.cs**: + +````csharp +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace SignalRTieredDemo.Web.Pages +{ + public class ChatModel : PageModel + { + public void OnGet() + { + } + } +} +```` + +**Chat.css**: + +````css +#MessageList { + border: 1px solid gray; + height: 400px; + overflow: auto; + list-style: none; + padding-left: 0; + padding: 10px; +} + +#TargetUser { + width: 100%; +} + +#Message { + width: 100%; +} +```` + +**Chat.js**: + +````javascript +$(function () { + var connection = new signalR.HubConnectionBuilder().withUrl("/signalr-hubs/chat").build(); + + connection.on("ReceiveMessage", function (message) { + console.log(message); + $('#MessageList').append('
  • ' + message + '
  • '); + }); + + connection.start().then(function () { + + }).catch(function (err) { + return console.error(err.toString()); + }); + + $('#SendMessageButton').click(function (e) { + e.preventDefault(); + + var targetUserName = $('#TargetUser').val(); + var message = $('#Message').val(); + $('#Message').val(''); + + + signalRTieredDemo.controllers.chat.sendMessage({ + targetUserName: targetUserName, + message: message + }).then(function() { + $('#MessageList') + .append('
  • ' + abp.currentUser.userName + ': ' + message + '
  • '); + }); + + }); +}); +```` + +Then we can add this new page to menu on ***MenuContributor.cs** in **Menus** folder: + +````csharp + ... + public class SignalRTieredDemoMenuContributor : IMenuContributor + { + ... + private Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + ... + context.Menu.Items.Add(new ApplicationMenuItem("SignalRDemo.Chat", "Chat", "/Chat")); // <-- We add this line + + return Task.CompletedTask; + } + ... + } +```` + +## Running & Testing + +We run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** in order. After ***.Web** project is ran, firstly login with `admin` username and `1q2w3E*` password. + +![click on login](login1.png) + +![login with `admin` username and `1q2w3E*` password.](login2.png) + +After we login, go to `/Identity/Users` page and create a new user. So that we can chat with them. + +![create a new user](new-user.png) + +Then we open the application in another browser and login with the user we created above. Now we can go to chat page and start messaging: + +![messaging](chat.png) + +We can test with more user. All sent and incoming messages are displayed in the left box. + +### Source code + +Source code of the final application can be found on the [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo). \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png new file mode 100644 index 0000000000..b29daa9492 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png new file mode 100644 index 0000000000..bb81ddf37a Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png new file mode 100644 index 0000000000..6e5a46b780 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png new file mode 100644 index 0000000000..4e371e2ee9 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png new file mode 100644 index 0000000000..68c5a3259d Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png new file mode 100644 index 0000000000..2ffdf99d45 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md new file mode 100644 index 0000000000..560e15e288 --- /dev/null +++ b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md @@ -0,0 +1,396 @@ +# File Upload/Download with BLOB Storage System in ASP.NET Core & ABP Framework + +## Introduction + +This step-by-step article describes how to upload a file to a Web server and also download by client with using ASP.NET Core & ABP Framework. By following this article, you will create a web project and its related code to upload and download files. + +Before the creating application, we need to know some fundamentals. + +## BLOB Storing + +It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. [BLOB](https://en.wikipedia.org/wiki/Binary_large_object)s, into a **storage**. For example, you may want to save user profile pictures. + +A BLOB is a typically **byte array**. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be options. + +The ABP Framework provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits; + +- You can **easily integrate** to your favorite BLOB storage provides with a few lines of configuration. +- You can then **easily change** your BLOB storage without changing your application code. +- If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored. + +ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](https://docs.abp.io/en/abp/latest/Multi-Tenancy). + +To get more information about ABP BLOB Storing system, please check this [documentation](https://docs.abp.io/en/abp/latest/Blob-Storing). + +## Preparing the Project + +### Startup template and the initial run + +Abp Framework offers startup templates to get into the business faster. We can download a new startup template using Abp CLI: + +`abp new FileActionsDemo -m none` + +After the download is finished, we run `FileActionsDemo.DbMigrator` project to create the database and seed initial data (admin user, role, etc). Then we run `FileActionsDemo.Web` to see our application working. + +> _Default admin username is **admin** and password is **1q2w3E\***_ + +![initial-project](initial-project.png) + +### Adding Blob Storing Module + +For this article, we use [Blob Storing Database Provider](https://docs.abp.io/en/abp/latest/Blob-Storing-Database). + +You can use [Azure](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure) or [File System](https://docs.abp.io/en/abp/latest/Blob-Storing-File-System) providers also. + +Open a command prompt (terminal) in the folder containing your solution (.sln) file and run the following command: + +`abp add-module Volo.Abp.BlobStoring.Database` + +This action will add the module depencies and also module migration. After this action, run `FileActionsDemo.DbMigrator` to update the database. + +### Setting up Blob Storaging + +BLOB Strorage system works with `Containers`. Before the using blob storage, we need to create our blob container. + +Create a class that name `MyFileContainer` at the `FileActionsDemo.Domain` project. + +```csharp +using Volo.Abp.BlobStoring; + +namespace FileActionsDemo +{ + [BlobContainerName("my-file-container")] + public class MyFileContainer + { + + } +} +``` + +That's all, we can start to use BLOB storing in our application. + +## Creating Application Layer + +Before the creating Application Service, we need to create some [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)s that used by Application Service. + +Create following DTOs in `FileActionsDemo.Application.Contracts` project. + +- `BlobDto.cs` + + ```csharp + namespace FileActionsDemo + { + public class BlobDto + { + public byte[] Content { get; set; } + + public string Name { get; set; } + } + } + ``` + +- `GetBlobRequestDto.cs` + + ```csharp + using System.ComponentModel.DataAnnotations; + + namespace FileActionsDemo + { + public class GetBlobRequestDto + { + [Required] + public string Name { get; set; } + } + } + ``` + +- `SaveBlobInputDto.cs` + + ```csharp + using System.ComponentModel.DataAnnotations; + + namespace FileActionsDemo + { + public class SaveBlobInputDto + { + public byte[] Content { get; set; } + + [Required] + public string Name { get; set; } + } + } + ``` + +Create `IFileAppService.cs` interface at the same place with DTOs. + +- `IFileAppService` + + ```csharp + using System.Threading.Tasks; + using Volo.Abp.Application.Services; + + namespace FileActionsDemo + { + public interface IFileAppService : IApplicationService + { + Task SaveBlobAsync(SaveBlobInputDto input); + + Task GetBlobAsync(GetBlobRequestDto input); + } + } + ``` + +After creating DTOs and interface, `FileActionsDemo.Application.Contracts` project should be like as following image. + +![application-contracts-project](application-contracts-project.png) + +Then we can create our Application Service. + +Create `FileAppService.cs` in `FileActionsDemo.Application` project. + +```csharp +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.BlobStoring; + +namespace FileActionsDemo +{ + public class FileAppService : ApplicationService, IFileAppService + { + private readonly IBlobContainer _fileContainer; + + public FileAppService(IBlobContainer fileContainer) + { + _fileContainer = fileContainer; + } + + public async Task SaveBlobAsync(SaveBlobInputDto input) + { + await _fileContainer.SaveAsync(input.Name, input.Content, true); + } + + public async Task GetBlobAsync(GetBlobRequestDto input) + { + var blob = await _fileContainer.GetAllBytesAsync(input.Name); + + return new BlobDto + { + Name = input.Name, + Content = blob + }; + } + } +} +``` + +As you see in previous code block, we inject `IBlobContainer` to our app service. It will handle all blob actions for us. + +- `SaveBlobAsync` method uses `SaveAsync` of `IBlobContainer` to save the given blob to storage, this is a simple example so we don't check is there any file exist with same name. We sent blob name, blob content and `true` for `overrideExisting` parameter. + +- `GetBlobAsync` method is uses `GetAllBytesAsync` of `IBlobContainer` to get blob content by name. + +We finished the application layer for this project. After that we will create a `Controller` for API and `Razor Page` for UI. + +## Creating Controller + +Create `FileController.cs` in your `FileActionsDemo.HttpApi` project. + +```csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace FileActionsDemo +{ + public class FileController : AbpController + { + private readonly IFileAppService _fileAppService; + + public FileController(IFileAppService fileAppService) + { + _fileAppService = fileAppService; + } + + [HttpGet] + [Route("download/{fileName}")] + public async Task DownloadAsync(string fileName) + { + var fileDto = await _fileAppService.GetBlobAsync(new GetBlobRequestDto{ Name = fileName }); + + return File(fileDto.Content, "application/octet-stream", fileDto.Name); + } + } +} +``` + +As you see, `FileController` injects `IFileAppService` that we defined before. This controller has only one endpoint. + +`DownloadAsync` is using to send file from server to client. + +This endpoint is requires only a `string` parameter, then we use that parameter to get stored blob. If blob is exist, we return a `File` result so download process can start. + +## Creating User Interface + +We will create only one page to prove download and upload actions are working. + +Create folder that name `Files` in your `Pages` folder at `FileActionsDemo.Web` project. + +Create a Razor page that name `Index` with its model. + +- `Index.cshtml.cs` + +```csharp +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace FileActionsDemo.Web.Pages.Files +{ + public class Index : AbpPageModel + { + [BindProperty] + public UploadFileDto UploadFileDto { get; set; } + + private readonly IFileAppService _fileAppService; + + public bool Uploaded { get; set; } = false; + + public Index(IFileAppService fileAppService) + { + _fileAppService = fileAppService; + } + + public void OnGet() + { + + } + + public async Task OnPostAsync() + { + using (var memoryStream = new MemoryStream()) + { + await UploadFileDto.File.CopyToAsync(memoryStream); + + await _fileAppService.SaveBlobAsync( + new SaveBlobInputDto + { + Name = UploadFileDto.Name, + Content = memoryStream.ToArray() + } + ); + } + + return Page(); + } + } + + public class UploadFileDto + { + [Required] + [Display(Name = "File")] + public IFormFile File { get; set; } + + [Required] + [Display(Name = "Filename")] + public string Name { get; set; } + } +} +``` + +As you see, we use `UploadFileDto` as a `BindProperty` and we inject `IFileAppService` to upload files. + +The `UploadFileDto` is requires a `string` parameter for using as a blob name and a `IFormFile` that sent by user. + +At the post action (`OnPostAsync`), if everything is well, we use `MemoryStream` to get all bytes from file content. + +Then we save file with `SaveBlobAsync` method of `IFileAppService`. + +- `Index.cshtml` + +```csharp +@page +@model FileActionsDemo.Web.Pages.Files.Index + +@section scripts{ + +} + + + +

    File Upload and Download

    +
    + + + +

    Upload File

    +
    +
    + + + + + +
    +
    + + +

    Download File

    +
    +
    +
    + * + +
    + + +
    +
    +
    +
    +
    +``` + +We divided the page vertically, left side will be using for upload and right side will be using for download. We use [ABP Tag Helpers](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Tag-Helpers/Index) to create page. + +- `index.js` + +```javascript +$(function () { + var DOWNLOAD_ENDPOINT = "/download"; + + var downloadForm = $("form#DownloadFile"); + + downloadForm.submit(function (event) { + event.preventDefault(); + + var fileName = $("#fileName").val().trim(); + + var downloadWindow = window.open( + DOWNLOAD_ENDPOINT + "/" + fileName, + "_blank" + ); + downloadWindow.focus(); + }); + + $("#UploadFileDto_File").change(function () { + var fileName = $(this)[0].files[0].name; + + $("#UploadFileDto_Name").val(fileName); + }); +}); +``` + +This jQuery codes are using for download. Also we wrote a simple code to autofill `Filename` input when user selects a file. + +After creating razor page and js file, `FileActionsDemo.Web` project should be like as following image. + +![web-project](web-project.png) + +## Result + +After completing code tutorial, run `FileActionsDemo.Web` project and go `/Files`. You can upload any file with any name and also download those uploaded files. + +![file-up](file-upload-result.gif) diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png new file mode 100644 index 0000000000..924adab6f6 Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif new file mode 100644 index 0000000000..e59ee890c6 Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png new file mode 100644 index 0000000000..0e611deebe Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png new file mode 100644 index 0000000000..f22d2dbdcb Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png differ