diff --git a/SynchronousIO/ReadMe.md b/SynchronousIO/ReadMe.md index 9f9a789..43a5dbd 100644 --- a/SynchronousIO/ReadMe.md +++ b/SynchronousIO/ReadMe.md @@ -1,22 +1,17 @@ # SynchronousIO Sample Code -The SynchronousIO sample code illustrates techniques for retrieving information from a web service and returning it to a client. The sample comprises the following items: - -* SynchronousIO solution file - -* AzureCloudService - -* CreateFileToUpload project +It is a sample implementation to ilustrate [Synchronous I/O antipattern](https://learn.microsoft.com/azure/architecture/antipatterns/synchronous-io/) -* WebRole WebAPI project +The SynchronousIO sample code illustrates techniques for retrieving information from a web service and returning it to a client. The sample comprises the following items: -* [Detailed Documentation][docs] +- SynchronousIO Web Api Application +- -The sample simulates fetching information from a data store. The data returned is a -`UserProfile` object (defined in the Models folder in the WebRole project): +The sample simulates fetching information from a data store. The data returned is a `UserProfile` object (defined in the Models folder in the project): **C#** -``` C# + +```C# public class UserProfile { public string FirstName { get; set; } @@ -24,10 +19,10 @@ public class UserProfile } ``` -The code that actually retrieves the data is located in the `FakeUserProfileService` -class, located in the WebRole project. This class exposes the following three methods: -***C#*** -``` C# +The code that actually retrieves the data is located in the `FakeUserProfileService` class, located in the project. This class exposes the following three methods: +**_C#_** + +```C# public class FakeUserProfileService : IUserProfileService { public UserProfile GetUserProfile() @@ -46,54 +41,99 @@ public class FakeUserProfileService : IUserProfileService } } ``` -These methods demonstrate the synchronous, task-based asynchronous, and wrapped async -techniques for fetching data. The methods return hard-coded values, but simulate the -delay expected when retrieving information from a remote data store. -WebRole is a Web API project. It contains five controllers: +These methods demonstrate the synchronous, task-based asynchronous, and wrapped async techniques for fetching data. The methods return hard-coded values, but simulate the delay expected when retrieving information from a remote data store. + +The Web API project contains five controllers: + +- `AsyncController` + +- `AsyncUploadController` + +- `SyncController` + +- `SyncUploadController` + +- `WrappedSyncController` -* `AsyncController` +The `AsyncController`, `SyncController`, and `WrappedSyncController` controllers call the corresponding methods of the `FakeUserProfileService` class. -* `AsyncUploadController` +The `AsyncUploadController` and `SyncUploadController` controllers call corresponding methods in the Azure Blob storage sdk to upload the "FileToUpload.txt" file to Blob storage. -* `SyncController` +The CreateFile class is used to generate a file content that is 10 MB in size. -* `SyncUploadController` +## Prerequisites -* `WrappedSyncController` +- Permission to create a new resource group and resources in an [Azure subscription](https://azure.com/free) +- Unix-like shell. Also available in: + - [Azure Cloud Shell](https://shell.azure.com/) + - [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/windows/wsl/install) +- [Git](https://git-scm.com/downloads) +- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) +- Optionally, an IDE, like [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Visual Studio Code](https://code.visualstudio.com/). -The `AsyncController`, `SyncController`, and `WrappedSyncController` WebAPI -controllers call the corresponding methods of the `FakeUserProfileService` class. +## Steps -The `AsyncUploadController` and `SyncUploadController` WebAPI controllers call -corresponding methods in the Azure Blob storage sdk to upload the "FileToUpload.txt" -file to Blob storage. +1. Clone this repository to your workstation and navigate to the working directory. -The CreateFileToUpload project is a console app that can be used to generate a file -named "FileToUpload.txt" that is 10 MB in size. + ```bash + git clone https://github.com/mspnp/performance-optimization + cd SynchronousIO + ``` -## Configuring the project +1. Log into Azure and create an empty resource group. -The `SyncUploadController` and `AsyncUploadController` use Azure Storage to save blob data. Use the Azure Management Portal to create an Azure Storage Account and add the connection string for this account to the AzureCloudService ServiceConfiguration files. + ```bash + az login + az account set -s -## Deploying the project to Azure + export USER_OBJECTID= + + LOCATION=eastus + RESOURCEGROUP=rg-synchronous-IO-${LOCATION} -In Visual Studio Solution Explorer, right-click the AzureCloudService project and then -click *Publish* to deploy the project to Azure. + az group create --name ${RESOURCEGROUP} --location ${LOCATION} + ``` -## Load testing +1. Deploy the supporting Azure resources. + It will create a storage account that only allows Managed Identity access. The User Object Id will have the Role to upload Blobs. + + ```bash + az deployment group create --resource-group ${RESOURCEGROUP} \ + -f ./bicep/main.bicep \ + -p userObjectId=${USER_OBJECTID} + ``` + +1. Configure database connection string + + On appsettings.json you need to complete with your azure account name. + + ```bash + "https://.blob.core.windows.net/", + ``` + +1. Authenticate with a Microsoft Entra identity + + As far the implementation is using manage identity, you need to assign the role to your [developer identity](https://learn.microsoft.com/azure/azure-functions/functions-reference?tabs=blob&pivots=programming-language-csharp#local-development-with-identity-based-connections). + +1. Run proyect locally + + Execute the API and then you will be able to call both endpoints + +## :broom: Clean up resources + +Most of the Azure resources deployed in the prior steps will incur ongoing charges unless removed. + +```bash +az group delete -n ${RESOURCEGROUP} -y +``` -You can use [Visual Studio Online](http://www.visualstudio.com/en-us/get-started/load-test-your-app-vs.aspx) to -load test your application. -For details of the load testing strategy for this sample, see [Load Testing][Load Testing]. +## Contributions -For more realistic results, configure the user load to simulate bursts of traffic with -periods of low usage between bursts of high usage. In order to raise and lower the -user load within a load test, you will need to create a [custom load test plugin](https://msdn.microsoft.com/en-us/library/ms243153.aspx). +Please see our [Contributor guide](./CONTRIBUTING.md). -## Dependencies -This project requires Azure SDK 2.5 +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact with any additional questions or comments. -[docs]: docs/SynchronousIO.md -[Load Testing]: docs/LoadTesting.md +With :heart: from Azure Patterns & Practices, [Azure Architecture Center](https://azure.com/architecture). diff --git a/SynchronousIO/SynchronousIO/.vscode/launch.json b/SynchronousIO/SynchronousIO/.vscode/launch.json deleted file mode 100644 index 4b75fcf..0000000 --- a/SynchronousIO/SynchronousIO/.vscode/launch.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/bin/Debug/netcoreapp2.1/SynchronousIO.dll", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart", - "launchBrowser": { - "enabled": true, - "args": "${auto-detect-url}", - "windows": { - "command": "cmd.exe", - "args": "/C start ${auto-detect-url}" - }, - "osx": { - "command": "open" - }, - "linux": { - "command": "xdg-open" - } - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ,] -} \ No newline at end of file diff --git a/SynchronousIO/SynchronousIO/.vscode/settings.json b/SynchronousIO/SynchronousIO/.vscode/settings.json deleted file mode 100644 index bf2558c..0000000 --- a/SynchronousIO/SynchronousIO/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "appService.deploySubpath": "", - "appService.defaultWebAppToDeploy": "None" -} \ No newline at end of file diff --git a/SynchronousIO/SynchronousIO/.vscode/tasks.json b/SynchronousIO/SynchronousIO/.vscode/tasks.json deleted file mode 100644 index 5f3a59a..0000000 --- a/SynchronousIO/SynchronousIO/.vscode/tasks.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet build", - "type": "shell", - "group": { - "isDefault": true, - "kind": "build" - }, - "presentation": { - "reveal": "silent" - }, - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/SynchronousIO/SynchronousIO/Controllers/AsyncController.cs b/SynchronousIO/SynchronousIO/Controllers/AsyncController.cs deleted file mode 100644 index d21969e..0000000 --- a/SynchronousIO/SynchronousIO/Controllers/AsyncController.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.AspNetCore.Mvc; -using SynchronousIO.WebRole.Models; - -namespace SynchronousIO.WebRole.Controllers -{ - public class AsyncController : ControllerBase - { - private readonly IUserProfileService _userProfileService; - - public AsyncController() - { - _userProfileService = new FakeUserProfileService(); - } - - /// - /// This is an asynchronous method that calls the Task based GetUserProfileAsync method. - /// - public async Task GetUserProfileAsync() - { - return await _userProfileService.GetUserProfileAsync(); - } - } -} diff --git a/SynchronousIO/SynchronousIO/Controllers/AsyncUploadController.cs b/SynchronousIO/SynchronousIO/Controllers/AsyncUploadController.cs deleted file mode 100644 index 7ace7d7..0000000 --- a/SynchronousIO/SynchronousIO/Controllers/AsyncUploadController.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.IO; -using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Hosting; -using Microsoft.WindowsAzure.Storage; -using Microsoft.Extensions.Configuration; -using Microsoft.WindowsAzure.Storage.Blob; - -namespace SynchronousIO.WebRole.Controllers -{ - [ApiController] - [Route("api/[controller]")] - public class AsyncUploadController : ControllerBase - { - private readonly IHostingEnvironment environment; - private readonly IConfiguration configuration; - - public AsyncUploadController(IHostingEnvironment environment, IConfiguration configuration) - { - this.environment = environment; - this.configuration = configuration; - } - - [HttpGet] - public async Task UploadFileAsync() - { - var storageAccount = CloudStorageAccount.Parse(configuration.GetConnectionString("storage")); - var blobClient = storageAccount.CreateCloudBlobClient(); - var container = blobClient.GetContainerReference("uploadedfiles"); - - await container.CreateIfNotExistsAsync(); - - var blockBlob = container.GetBlockBlobReference("myblob"); - - // Create or overwrite the "myblob" blob with contents from a local file. - using (var stream = CreateFile.Get()) - { - await blockBlob.UploadFromStreamAsync(stream); - } - } - } -} diff --git a/SynchronousIO/SynchronousIO/Controllers/SyncController.cs b/SynchronousIO/SynchronousIO/Controllers/SyncController.cs deleted file mode 100644 index 3d16f9d..0000000 --- a/SynchronousIO/SynchronousIO/Controllers/SyncController.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Web.Http; -using Microsoft.AspNetCore.Mvc; -using SynchronousIO.WebRole.Models; - -namespace SynchronousIO.WebRole.Controllers -{ - public class SyncController : ControllerBase - { - private readonly IUserProfileService _userProfileService; - - public SyncController() - { - _userProfileService = new FakeUserProfileService(); - } - - /// - /// This is a synchronous method that calls the synchronous GetUserProfile method. - /// - public UserProfile GetUserProfile() - { - return _userProfileService.GetUserProfile(); - } - } -} diff --git a/SynchronousIO/SynchronousIO/Controllers/SyncUploadController.cs b/SynchronousIO/SynchronousIO/Controllers/SyncUploadController.cs deleted file mode 100644 index 2dbe1bd..0000000 --- a/SynchronousIO/SynchronousIO/Controllers/SyncUploadController.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.IO; -using System.Web.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Blob; - -namespace SynchronousIO.WebRole.Controllers -{ - [ApiController] - [Route("api/[controller]")] - public class SyncUploadController : ControllerBase - { - private readonly IHostingEnvironment environment; - private readonly IConfiguration configuration; - - public SyncUploadController(IHostingEnvironment environment, IConfiguration configuration) - { - this.environment = environment; - this.configuration = configuration; - } - - [HttpGet] - public void UploadFile() - { - var storageAccount = CloudStorageAccount.Parse(configuration.GetConnectionString("storage")); - var blobClient = storageAccount.CreateCloudBlobClient(); - var container = blobClient.GetContainerReference("uploadedfiles"); - - container.CreateIfNotExists(); - - var blockBlob = container.GetBlockBlobReference("myblob"); - - // Create or overwrite the "myblob" blob with contents from a local file. - using (var stream = CreateFile.Get()) - { - blockBlob.UploadFromStream(stream); - } - } - } -} diff --git a/SynchronousIO/SynchronousIO/FileToUpload.txt b/SynchronousIO/SynchronousIO/FileToUpload.txt deleted file mode 100644 index b480832..0000000 --- a/SynchronousIO/SynchronousIO/FileToUpload.txt +++ /dev/null @@ -1 +0,0 @@ -Use the `CreateFileToUpload` utility to produce a larger file. \ No newline at end of file diff --git a/SynchronousIO/SynchronousIO/IUserProfileService.cs b/SynchronousIO/SynchronousIO/IUserProfileService.cs deleted file mode 100644 index bfa3a38..0000000 --- a/SynchronousIO/SynchronousIO/IUserProfileService.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Threading.Tasks; -using SynchronousIO.WebRole.Models; - -namespace SynchronousIO.WebRole -{ - public interface IUserProfileService - { - UserProfile GetUserProfile(); - Task GetUserProfileAsync(); - Task GetUserProfileWrappedAsync(); - } -} \ No newline at end of file diff --git a/SynchronousIO/SynchronousIO/Models/UserProfile.cs b/SynchronousIO/SynchronousIO/Models/UserProfile.cs deleted file mode 100644 index 9dfd9aa..0000000 --- a/SynchronousIO/SynchronousIO/Models/UserProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace SynchronousIO.WebRole.Models -{ - public class UserProfile - { - public string FirstName { get; set; } - public string LastName { get; set; } - } -} \ No newline at end of file diff --git a/SynchronousIO/SynchronousIO/Program.cs b/SynchronousIO/SynchronousIO/Program.cs deleted file mode 100644 index f492ed4..0000000 --- a/SynchronousIO/SynchronousIO/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace SynchronousIO -{ - public class Program - { - public static void Main(string[] args) - { - CreateWebHostBuilder(args).Build().Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup(); - } -} diff --git a/SynchronousIO/SynchronousIO/Properties/launchSettings.json b/SynchronousIO/SynchronousIO/Properties/launchSettings.json deleted file mode 100644 index e525967..0000000 --- a/SynchronousIO/SynchronousIO/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:16613", - "sslPort": 44319 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "SynchronousIO": { - "commandName": "Project", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/SynchronousIO/SynchronousIO/Startup.cs b/SynchronousIO/SynchronousIO/Startup.cs deleted file mode 100644 index 89e3ced..0000000 --- a/SynchronousIO/SynchronousIO/Startup.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace SynchronousIO -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseMvc(); - } - } -} diff --git a/SynchronousIO/SynchronousIO/SynchronousIO.csproj b/SynchronousIO/SynchronousIO/SynchronousIO.csproj deleted file mode 100644 index 497f511..0000000 --- a/SynchronousIO/SynchronousIO/SynchronousIO.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - netcoreapp2.1 - 20dcce1c-9edf-4e66-8769-cd5cc0497ee0 - - - - - - - - - - - - PreserveNewest - - - - diff --git a/SynchronousIO/SynchronousIO/SynchronousIO.sln b/SynchronousIO/SynchronousIO/SynchronousIO.sln index f105d28..35ae40e 100644 --- a/SynchronousIO/SynchronousIO/SynchronousIO.sln +++ b/SynchronousIO/SynchronousIO/SynchronousIO.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.329 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SynchronousIO", "SynchronousIO.csproj", "{DA6E8DF0-1E76-4926-B242-128AA4AE980F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SynchronousIO", "SynchronousIO\SynchronousIO.csproj", "{0540063C-4B64-4033-8F23-A12D84D0D536}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,15 +11,15 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DA6E8DF0-1E76-4926-B242-128AA4AE980F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA6E8DF0-1E76-4926-B242-128AA4AE980F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA6E8DF0-1E76-4926-B242-128AA4AE980F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA6E8DF0-1E76-4926-B242-128AA4AE980F}.Release|Any CPU.Build.0 = Release|Any CPU + {0540063C-4B64-4033-8F23-A12D84D0D536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0540063C-4B64-4033-8F23-A12D84D0D536}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0540063C-4B64-4033-8F23-A12D84D0D536}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0540063C-4B64-4033-8F23-A12D84D0D536}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BD4038AF-59B0-47DF-9C06-235EEB4E2B07} + SolutionGuid = {8B834A00-E543-436D-AF8C-505FF4521B0F} EndGlobalSection EndGlobal diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/AsyncController.cs b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/AsyncController.cs new file mode 100644 index 0000000..3922c24 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/AsyncController.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; + +namespace SynchronousIO.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AsyncController(IUserProfileService _userProfileService) : ControllerBase + { + /// + /// This is an asynchronous method that calls the Task based GetUserProfileAsync method. + /// + [HttpGet()] + public async Task GetUserProfileAsync() + { + return Ok(await _userProfileService.GetUserProfileAsync()); + } + } +} diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/AsyncUploadController.cs b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/AsyncUploadController.cs new file mode 100644 index 0000000..0d84528 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/AsyncUploadController.cs @@ -0,0 +1,27 @@ +using Azure.Storage.Blobs; +using Microsoft.AspNetCore.Mvc; +namespace SynchronousIO.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AsyncUploadController(BlobServiceClient _blobServiceClient) : ControllerBase + { + /// + /// This is an asynchronous method that calls the Task based GetUserProfileAsync method. + /// + [HttpGet()] + public async Task UploadFileAsync() + { + var container = _blobServiceClient.GetBlobContainerClient("data"); + + var blobClient = container.GetBlobClient("myblob.txt"); + + // Create or overwrite the "myblob" blob with contents from a local file. + using (var stream = CreateFile.Get()) + { + await blobClient.UploadAsync(stream, true); + } + return Ok(); + } + } +} diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/SyncController.cs b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/SyncController.cs new file mode 100644 index 0000000..9a3f928 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/SyncController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; +using SynchronousIO.Models; + +namespace SynchronousIO.Controllers +{ + [ApiController] + [Route("[controller]")] + public class SyncController(IUserProfileService _userProfileService) : ControllerBase + { + /// + /// This is a synchronous method that calls the synchronous GetUserProfile method. + /// + [HttpGet()] + public UserProfile GetUserProfile() + { + return _userProfileService.GetUserProfile(); + } + } +} diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/SyncUploadController.cs b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/SyncUploadController.cs new file mode 100644 index 0000000..fce90f5 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/SyncUploadController.cs @@ -0,0 +1,28 @@ +using Azure.Storage.Blobs; +using Microsoft.AspNetCore.Mvc; + +namespace SynchronousIO.Controllers +{ + [ApiController] + [Route("[controller]")] + public class SyncUploadController(BlobServiceClient _blobServiceClient) : ControllerBase + { + /// + /// This is an asynchronous method that calls the Task based GetUserProfileAsync method. + /// + [HttpGet()] + public IActionResult UploadFile() + { + var container = _blobServiceClient.GetBlobContainerClient("data"); + + var blobClient = container.GetBlobClient("myblob.txt"); + + // Create or overwrite the "myblob" blob with contents from a local file. + using (var stream = CreateFile.Get()) + { + blobClient.Upload(stream, true); + } + return Ok(); + } + } +} diff --git a/SynchronousIO/SynchronousIO/Controllers/WrappedSyncController.cs b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/WrappedSyncController.cs similarity index 50% rename from SynchronousIO/SynchronousIO/Controllers/WrappedSyncController.cs rename to SynchronousIO/SynchronousIO/SynchronousIO/Controllers/WrappedSyncController.cs index 47ebe2a..0834474 100644 --- a/SynchronousIO/SynchronousIO/Controllers/WrappedSyncController.cs +++ b/SynchronousIO/SynchronousIO/SynchronousIO/Controllers/WrappedSyncController.cs @@ -1,28 +1,19 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.AspNetCore.Mvc; +using SynchronousIO.Models; -using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.AspNetCore.Mvc; -using SynchronousIO.WebRole.Models; - -namespace SynchronousIO.WebRole.Controllers +namespace SynchronousIO.Controllers { - public class WrappedSyncController : ControllerBase + [ApiController] + [Route("[controller]")] + public class WrappedSyncController(IUserProfileService _userProfileService) : ControllerBase { - private readonly IUserProfileService _userProfileService; - - public WrappedSyncController() - { - _userProfileService = new FakeUserProfileService(); - } - /// /// This is an asynchronous method that calls the Task based GetUserProfileWrappedAsync method. /// Even though this method is async, the result is similar to the SyncController in that threads /// are tied up by the synchronous GetUserProfile method in the Task.Run. Under significant load /// new threads will need to be created. /// + [HttpGet()] public async Task GetUserProfileAsync() { return await _userProfileService.GetUserProfileWrappedAsync(); diff --git a/SynchronousIO/SynchronousIO/CreateFile.cs b/SynchronousIO/SynchronousIO/SynchronousIO/CreateFile.cs similarity index 93% rename from SynchronousIO/SynchronousIO/CreateFile.cs rename to SynchronousIO/SynchronousIO/SynchronousIO/CreateFile.cs index ef21f23..7f1a52a 100644 --- a/SynchronousIO/SynchronousIO/CreateFile.cs +++ b/SynchronousIO/SynchronousIO/SynchronousIO/CreateFile.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Text; namespace SynchronousIO { diff --git a/SynchronousIO/SynchronousIO/FakeUserProfileService.cs b/SynchronousIO/SynchronousIO/SynchronousIO/FakeUserProfileService.cs similarity index 86% rename from SynchronousIO/SynchronousIO/FakeUserProfileService.cs rename to SynchronousIO/SynchronousIO/SynchronousIO/FakeUserProfileService.cs index 7bfbfc5..d40ddfa 100644 --- a/SynchronousIO/SynchronousIO/FakeUserProfileService.cs +++ b/SynchronousIO/SynchronousIO/SynchronousIO/FakeUserProfileService.cs @@ -1,11 +1,6 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using SynchronousIO.Models; -using System.Threading; -using System.Threading.Tasks; -using SynchronousIO.WebRole.Models; - -namespace SynchronousIO.WebRole +namespace SynchronousIO { public class FakeUserProfileService : IUserProfileService { diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/IUserProfileService.cs b/SynchronousIO/SynchronousIO/SynchronousIO/IUserProfileService.cs new file mode 100644 index 0000000..a1fe089 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/IUserProfileService.cs @@ -0,0 +1,11 @@ +using SynchronousIO.Models; + +namespace SynchronousIO +{ + public interface IUserProfileService + { + UserProfile GetUserProfile(); + Task GetUserProfileAsync(); + Task GetUserProfileWrappedAsync(); + } +} diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/Models/UserProfile.cs b/SynchronousIO/SynchronousIO/SynchronousIO/Models/UserProfile.cs new file mode 100644 index 0000000..f00f007 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/Models/UserProfile.cs @@ -0,0 +1,8 @@ +namespace SynchronousIO.Models +{ + public class UserProfile + { + public string FirstName { get; set; } + public string LastName { get; set; } + } +} diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/Program.cs b/SynchronousIO/SynchronousIO/SynchronousIO/Program.cs new file mode 100644 index 0000000..5719384 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/Program.cs @@ -0,0 +1,63 @@ +using Azure.Identity; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.Extensions.Azure; +using SynchronousIO; +using SynchronousIO.Models; +using System.Net; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddScoped(); + +builder.Services.AddAzureClients(clientBuilder => +{ + clientBuilder.AddBlobServiceClient(new Uri(builder.Configuration["storage_url"])) + .WithCredential(new DefaultAzureCredential()); ; +}); + +var loggerFactory = LoggerFactory.Create(builder => +{ + builder.AddConsole(); +}); + +var _logger = loggerFactory.CreateLogger(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseExceptionHandler(appError => +{ + appError.Run(async context => + { + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + context.Response.ContentType = "application/json"; + + var contextFeature = context.Features.Get(); + if (contextFeature != null) + { + _logger.LogError($"Something went wrong: {contextFeature.Error}"); + + await context.Response.WriteAsync("Internal Server Error."); + } + }); +}); + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/SynchronousIO.csproj b/SynchronousIO/SynchronousIO/SynchronousIO/SynchronousIO.csproj new file mode 100644 index 0000000..9d43ddc --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/SynchronousIO.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + + + + + + + + + + diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/appsettings.Development.json b/SynchronousIO/SynchronousIO/SynchronousIO/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/SynchronousIO/SynchronousIO/SynchronousIO/appsettings.json b/SynchronousIO/SynchronousIO/SynchronousIO/appsettings.json new file mode 100644 index 0000000..bf3fd68 --- /dev/null +++ b/SynchronousIO/SynchronousIO/SynchronousIO/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "storage_url": "https://.blob.core.windows.net/" +} diff --git a/SynchronousIO/SynchronousIO/appsettings.Cloud.json b/SynchronousIO/SynchronousIO/appsettings.Cloud.json deleted file mode 100644 index 1ef3f01..0000000 --- a/SynchronousIO/SynchronousIO/appsettings.Cloud.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ConnectionStrings": { - "storage": "" - }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/SynchronousIO/SynchronousIO/appsettings.Development.json b/SynchronousIO/SynchronousIO/appsettings.Development.json deleted file mode 100644 index 1ef3f01..0000000 --- a/SynchronousIO/SynchronousIO/appsettings.Development.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ConnectionStrings": { - "storage": "" - }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/SynchronousIO/SynchronousIO/appsettings.json b/SynchronousIO/SynchronousIO/appsettings.json deleted file mode 100644 index ed9272e..0000000 --- a/SynchronousIO/SynchronousIO/appsettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "ConnectionStrings": { - "storage": "" - }, - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/SynchronousIO/bicep/main.bicep b/SynchronousIO/bicep/main.bicep new file mode 100644 index 0000000..b72f6b0 --- /dev/null +++ b/SynchronousIO/bicep/main.bicep @@ -0,0 +1,39 @@ +param location string = resourceGroup().location +param dataStorageAccountName string = 'stor${uniqueString(resourceGroup().id)}' +param userObjectId string //your user object id + +var storageBlobDataContributorRole = subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' +) // Azure Storage Blob Data Contributor + +resource dataStorageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { + name: dataStorageAccountName + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + allowSharedKeyAccess: false + supportsHttpsTrafficOnly: true + minimumTlsVersion: 'TLS1_2' + } +} + +resource dataStorageAccountNameContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2022-09-01' = { + name: '${dataStorageAccountName}/default/data' + dependsOn: [ + dataStorageAccount + ] +} + +resource AzureStorageBlobDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(dataStorageAccount.id, userObjectId, storageBlobDataContributorRole) + scope: dataStorageAccount + properties: { + roleDefinitionId: storageBlobDataContributorRole + principalId: userObjectId + principalType: 'User' + } +}