diff --git a/.github/workflows/starter-no-infra_web-app-ace-project.yml b/.github/workflows/starter-no-infra_web-app-ace-project.yml new file mode 100644 index 000000000..89898939b --- /dev/null +++ b/.github/workflows/starter-no-infra_web-app-ace-project.yml @@ -0,0 +1,71 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy ASP.Net Core app to Azure Web App - web-app-ace-project + +on: + push: + branches: + - starter-no-infra + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: Install dotnet ef + run: dotnet tool install -g dotnet-ef --version 8.* + + - name: Create migrations bundle + run: dotnet ef migrations bundle --runtime linux-x64 -o ${{env.DOTNET_ROOT}}/myapp/migrationsbundle + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: ${{env.DOTNET_ROOT}}/myapp + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_0E0274DEC4A44FCE9DA768225516B525 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_83E53E57F21C46889E9D117FDB69DD7F }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_76FD69D8AE9E4688BA6D248363BB1EB0 }} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'web-app-ace-project' + slot-name: 'Production' + package: . + \ No newline at end of file diff --git a/AzureStorageService.cs b/AzureStorageService.cs new file mode 100644 index 000000000..74030456d --- /dev/null +++ b/AzureStorageService.cs @@ -0,0 +1,83 @@ +using Azure.Storage; +using Azure.Storage.Blobs; +using Azure.Storage.Sas; +using System.IO; +using System.Threading.Tasks; + +public class AzureStorageService +{ + private readonly string _connectionString; + +public AzureStorageService(IConfiguration configuration) +{ + _connectionString = configuration.GetConnectionString("AzureStorage") + ?? throw new ArgumentNullException(nameof(_connectionString), "Azure Storage connection string is not configured."); +} + + + public string GetBlobUrl(string containerName, string blobName) + { + var blobServiceClient = new BlobServiceClient(_connectionString); + var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName); + var blobClient = blobContainerClient.GetBlobClient(blobName); + + return blobClient.Uri.ToString(); + } + // Download a file from Azure Blob Storage + public async Task DownloadFileAsync(string containerName, string blobName) + { + var blobServiceClient = new BlobServiceClient(_connectionString); + var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName); + var blobClient = blobContainerClient.GetBlobClient(blobName); + + // Create a MemoryStream to store the downloaded content + var memoryStream = new MemoryStream(); + await blobClient.DownloadToAsync(memoryStream); + + // Reset the position of the MemoryStream before returning it + memoryStream.Position = 0; + + return memoryStream; + } + + // Upload a file to Azure Blob Storage + public async Task UploadFileAsync(string containerName, string blobName, Stream fileStream) + { + var blobServiceClient = new BlobServiceClient(_connectionString); + var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName); + var blobClient = blobContainerClient.GetBlobClient(blobName); + + // Upload the file stream to the blob + await blobClient.UploadAsync(fileStream, overwrite: true); + } + +public string GenerateBlobSasToken(string blobUrl, string storageAccountName, string storageAccountKey) +{ + // Create a BlobServiceClient + var blobServiceClient = new BlobServiceClient(new Uri($"https://{storageAccountName}.blob.core.windows.net"), + new StorageSharedKeyCredential(storageAccountName, storageAccountKey)); + + // Get a reference to the blob + BlobClient blobClient = blobServiceClient.GetBlobContainerClient("background-images").GetBlobClient("background.jpg"); + + // Define SAS token permissions and expiry + BlobSasBuilder sasBuilder = new BlobSasBuilder + { + BlobContainerName = "background-images", + BlobName = "background.jpg", + Resource = "b", // "b" for blob, "c" for container + StartsOn = DateTimeOffset.UtcNow.AddMinutes(-5), + ExpiresOn = DateTimeOffset.UtcNow.AddHours(1) // Token validity + }; + + sasBuilder.SetPermissions(BlobContainerSasPermissions.Read); + + // Generate the SAS token + string sasToken = sasBuilder.ToSasQueryParameters( + new StorageSharedKeyCredential(storageAccountName, storageAccountKey)) + .ToString(); + + // Combine the blob URL with the SAS token + return $"{blobClient.Uri}?{sasToken}"; +} +} \ No newline at end of file diff --git a/Controllers/FileController.cs b/Controllers/FileController.cs new file mode 100644 index 000000000..3ae870eb1 --- /dev/null +++ b/Controllers/FileController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using System.IO; + +public class FileController : Controller +{ + private readonly AzureStorageService _azureStorageService; + + // Inject the AzureStorageService into the controller through constructor dependency injection + public FileController(AzureStorageService azureStorageService) + { + _azureStorageService = azureStorageService; + } + + // An example action method to download a file from Azure Blob Storage + public async Task Download(string containerName, string blobName) + { + // Call the AzureStorageService to download the file + var fileStream = await _azureStorageService.DownloadFileAsync(containerName, blobName); + + // Return the file to the client as a downloadable stream + return File(fileStream, "application/octet-stream", blobName); + } + + // An example action method to upload a file to Azure Blob Storage + [HttpPost] + public async Task Upload(string containerName, string blobName) + { + // Get the uploaded file from the request (assuming a form post with a file) + using var fileStream = Request.Form.Files[0].OpenReadStream(); + + // Call the AzureStorageService to upload the file + await _azureStorageService.UploadFileAsync(containerName, blobName, fileStream); + + return Ok("File uploaded successfully"); + } +} diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 7c17e491f..83615af92 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -1,20 +1,30 @@ +using System.Diagnostics; using DotNetCoreSqlDb.Models; using Microsoft.AspNetCore.Mvc; -using System.Diagnostics; namespace DotNetCoreSqlDb.Controllers { public class HomeController : Controller { private readonly ILogger _logger; + private readonly AzureStorageService _azureStorageService; - public HomeController(ILogger logger) + public HomeController(ILogger logger, AzureStorageService azureStorageService) { - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _azureStorageService = azureStorageService ?? throw new ArgumentNullException(nameof(azureStorageService)); } public IActionResult Index() { + string storageAccountName = "acewwwroot"; + string storageAccountKey = "rANrk8t68qtk5ooKS4T+gRHGYHGdpFZRGUEz+iEWdVL5l3dwGgo8tAtSsxWPTGLBYqg9/Iz1g3L/+AStRxhgRw=="; // Get this securely from configuration + string blobUrl = "https://acewwwroot.blob.core.windows.net/background-images/background.jpg"; + + // Generate SAS token URL + string sasUrl = _azureStorageService.GenerateBlobSasToken(blobUrl, storageAccountName, storageAccountKey); + + ViewData["BackgroundUrl"] = sasUrl; return View(); } @@ -26,7 +36,9 @@ public IActionResult Privacy() [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + // Using Activity to get the request ID + var requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + return View(new ErrorViewModel { RequestId = requestId }); } } } diff --git a/DotNetCoreSqlDb.csproj b/DotNetCoreSqlDb.csproj index c3759ad87..e7ba626f3 100644 --- a/DotNetCoreSqlDb.csproj +++ b/DotNetCoreSqlDb.csproj @@ -10,6 +10,7 @@ + all @@ -18,6 +19,7 @@ + diff --git a/Migrations/20241124175301_AddTodoTable.Designer.cs b/Migrations/20241124175301_AddTodoTable.Designer.cs new file mode 100644 index 000000000..2ec8019cb --- /dev/null +++ b/Migrations/20241124175301_AddTodoTable.Designer.cs @@ -0,0 +1,49 @@ +// +using System; +using DotNetCoreSqlDb.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DotNetCoreSqlDb.Migrations +{ + [DbContext(typeof(MyDatabaseContext))] + [Migration("20241124175301_AddTodoTable")] + partial class AddTodoTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("DotNetCoreSqlDb.Models.Todo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("CreatedDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Todo"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20241124175301_AddTodoTable.cs b/Migrations/20241124175301_AddTodoTable.cs new file mode 100644 index 000000000..cc2257547 --- /dev/null +++ b/Migrations/20241124175301_AddTodoTable.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DotNetCoreSqlDb.Migrations +{ + /// + public partial class AddTodoTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Migrations/MyDatabaseContextModelSnapshot.cs b/Migrations/MyDatabaseContextModelSnapshot.cs index abc7278fa..17b59ef85 100644 --- a/Migrations/MyDatabaseContextModelSnapshot.cs +++ b/Migrations/MyDatabaseContextModelSnapshot.cs @@ -1,46 +1,46 @@ -// -using System; -using DotNetCoreSqlDb.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace DotNetCoreSqlDb.Migrations -{ - [DbContext(typeof(MyDatabaseContext))] - partial class MyDatabaseContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("DotNetCoreSqlDb.Models.Todo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("CreatedDate") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.HasKey("ID"); - - b.ToTable("Todo"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using DotNetCoreSqlDb.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DotNetCoreSqlDb.Migrations +{ + [DbContext(typeof(MyDatabaseContext))] + partial class MyDatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("DotNetCoreSqlDb.Models.Todo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("CreatedDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Todo"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Program.cs b/Program.cs index cd5e3cea8..1b7b5d111 100644 --- a/Program.cs +++ b/Program.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using DotNetCoreSqlDb.Data; +using Microsoft.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // Add database context and cache @@ -9,17 +10,16 @@ options.UseSqlServer(builder.Configuration.GetConnectionString("MyDbConnection"))); builder.Services.AddDistributedMemoryCache(); } -else -{ - builder.Services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("AZURE_SQL_CONNECTIONSTRING"))); - builder.Services.AddStackExchangeRedisCache(options => - { - options.Configuration = builder.Configuration["AZURE_REDIS_CONNECTIONSTRING"]; - options.InstanceName = "SampleInstance"; - }); -} - + else + { + builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("AZURE_SQL_CONNECTIONSTRING"))); + builder.Services.AddStackExchangeRedisCache(options => + { + options.Configuration = builder.Configuration["AZURE_REDIS_CONNECTIONSTRING"]; + options.InstanceName = "SampleInstance"; + }); + } // Add services to the container. builder.Services.AddControllersWithViews(); @@ -27,6 +27,28 @@ // Add App Service logging builder.Logging.AddAzureWebAppDiagnostics(); +builder.Services.AddSingleton(provider => +{ + // Retrieve IConfiguration from the DI container + var configuration = provider.GetRequiredService(); + + // Get the connection string from IConfiguration + var connectionString = configuration.GetConnectionString("AzureStorage"); + + // Check if connection string is null or empty + if (string.IsNullOrEmpty(connectionString)) + { + throw new Exception("Azure Storage connection string is not configured properly."); + } + + // Create and return the AzureStorageService with the connection string + return new AzureStorageService(configuration); // Passing IConfiguration as required by the constructor +}); + + +// Add other services as needed +builder.Services.AddControllersWithViews(); // This is for MVC controllers + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -37,6 +59,7 @@ app.UseHsts(); } + app.UseHttpsRedirection(); app.UseStaticFiles(); @@ -49,3 +72,4 @@ pattern: "{controller=Todos}/{action=Index}/{id?}"); app.Run(); + diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index f8821f259..d5f2927fc 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -1,8 +1,18 @@ -@{ - ViewData["Title"] = "Home Page"; -} + + + + @ViewData["Title"] + + + + Zdjęcie -
-

Welcome

-

Learn about building Web apps with ASP.NET Core.

-
+ + \ No newline at end of file diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 67f41da49..1e53cdb97 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -3,16 +3,16 @@ - @ViewData["Title"] - DotNetCoreSqlDb + @ViewData["Title"] - ACE-projekt - +