From ab6d950f50188b3e28e6547197d55eb43f5cbf72 Mon Sep 17 00:00:00 2001 From: Jim Vacca <33520581+MSFTJim@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:19:12 +0000 Subject: [PATCH 1/7] initial setup for local dev --- cocktails/appsettings.Development.json | 12 +++++++++++- frontend/appsettings.Development.json | 12 ++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cocktails/appsettings.Development.json b/cocktails/appsettings.Development.json index 9f95f02..fa17b8e 100644 --- a/cocktails/appsettings.Development.json +++ b/cocktails/appsettings.Development.json @@ -6,7 +6,17 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "AzureServicesAuthConnectionString": "RunAs=Developer;DeveloperTool=AzureCli;", + "ManagedIdentity-AzureServicesAuthConnectionString": "RunAs=App", + "LocalDev-AzureServicesAuthConnectionString": "RunAs=Developer;DeveloperTool=AzureCli;", + "ConnectionStrings": { + "defaultConnection": "Server=tcp:jfv-sql.database.windows.net,1433;Initial Catalog=dbItems;Persist Security Info=False;User ID=dbadmin;Password=;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30", + "ProductionConnection-ManagedIdentity": "Server=tcp:jfv-sql.database.windows.net;Authentication=Active Directory Default; Database=dbItems;", + "DevelopmentConnection-SQLAuth": "Server=tcp:jfv-sql.database.windows.net,1433;Initial Catalog=dbItems;Persist Security Info=False;User ID=dbadmin;Password=;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30", + "MCAPSOld-defaultConnection": "Server=tcp:sql-jfv.database.windows.net;Authentication=Active Directory Default; Database=SocialEvents;", + "MCAPSOld-defaultSQLAuthConnection": "Server=tcp:sql-jfv.database.windows.net,1433;Initial Catalog=SocialEvents;Persist Security Info=False;User ID=dbadmin;Password=;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30" + } diff --git a/frontend/appsettings.Development.json b/frontend/appsettings.Development.json index c0960db..c71ad81 100644 --- a/frontend/appsettings.Development.json +++ b/frontend/appsettings.Development.json @@ -2,9 +2,13 @@ "DetailedErrors": true, "Logging": { "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" } - } + }, + "AllowedHosts": "*", + "APIProductionUrl": "http://127.0.0.1:5000/cocktails", + "FY25-APIProductionUrl": "https://cocktail-api.azurewebsites.net/cocktails", + "Dev-APIUrl": "http://127.0.0.1:5000/cocktails" } From 0def3c48f6e8c625dbcb9b54d613b4efa6d385ee Mon Sep 17 00:00:00 2001 From: Jim Vacca <33520581+MSFTJim@users.noreply.github.com> Date: Tue, 6 Aug 2024 19:01:42 +0000 Subject: [PATCH 2/7] changed settings to run locally for dev --- .vscode/launch.json | 2 +- cocktails/DB/SqlDb.cs | 13 ++++++++++--- cocktails/Properties/launchSettings.json | 2 +- frontend/Properties/launchSettings.json | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 95f59f1..2df3fa4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,7 +31,7 @@ "request": "launch", "preLaunchTask": "build API", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/cocktails/bin/Debug/net7.0/cocktails.dll", + "program": "${workspaceFolder}/cocktails/bin/Debug/net8.0/cocktails.dll", "args": [], "cwd": "${workspaceFolder}/cocktails", "stopAtEntry": false, diff --git a/cocktails/DB/SqlDb.cs b/cocktails/DB/SqlDb.cs index ec44ebc..90ae1fe 100644 --- a/cocktails/DB/SqlDb.cs +++ b/cocktails/DB/SqlDb.cs @@ -30,12 +30,19 @@ public SqlDb(IConfiguration configuration) public SqlConnection GetSQLCn() { + var env = _configuration["ASPNETCORE_ENVIRONMENT"]; + bool isDevelopment = env == "Development"; + var builder = new SqlConnectionStringBuilder( _configuration["ConnectionStrings:defaultConnection"]); + + if (isDevelopment) + { + // The below 2 lines are used during development only. SMI is used in Production + var keyVaultSecretLookup = _configuration["AzureKeyVaultSecret:defaultSecret"]; + builder.Password = _configuration.GetValue(keyVaultSecretLookup); + } - // The below 2 lines are used during development only. SMI is used in Production - var keyVaultSecretLookup = _configuration["AzureKeyVaultSecret:defaultSecret"]; - // builder.Password = _configuration.GetValue(keyVaultSecretLookup); SqlConnection sqlDBCn = new SqlConnection(builder.ConnectionString); diff --git a/cocktails/Properties/launchSettings.json b/cocktails/Properties/launchSettings.json index 8cafd60..791527d 100644 --- a/cocktails/Properties/launchSettings.json +++ b/cocktails/Properties/launchSettings.json @@ -19,7 +19,7 @@ }, "cocktails": { "commandName": "Project", - "dotnetRunMessages": "true", + "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "https://localhost:5001;http://localhost:5000", diff --git a/frontend/Properties/launchSettings.json b/frontend/Properties/launchSettings.json index f2d8719..0ae70de 100644 --- a/frontend/Properties/launchSettings.json +++ b/frontend/Properties/launchSettings.json @@ -17,9 +17,9 @@ }, "frontend": { "commandName": "Project", - "dotnetRunMessages": "true", + "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "applicationUrl": "https://localhost:3001;http://localhost:3000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } From f564255d599a22458a557b4213b7381c5fd93adf Mon Sep 17 00:00:00 2001 From: Jim Vacca <33520581+MSFTJim@users.noreply.github.com> Date: Wed, 7 Aug 2024 21:25:38 +0000 Subject: [PATCH 3/7] updated delete logic to use entire object and not just id --- cocktails/Controllers/CocktailController.cs | 2 ++ frontend/Models/APICocktailRepository.cs | 9 +++++++-- frontend/Models/ICocktailRepository.cs | 2 +- frontend/Pages/DeleteCocktail.cshtml.cs | 5 +++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cocktails/Controllers/CocktailController.cs b/cocktails/Controllers/CocktailController.cs index fbf92c5..c1f0589 100644 --- a/cocktails/Controllers/CocktailController.cs +++ b/cocktails/Controllers/CocktailController.cs @@ -169,6 +169,8 @@ public async Task> UpdateCocktail(Item item) [HttpDelete("id/{id:int}")] //[HttpDelete] public async Task> DeleteCocktail(int id) + //public async Task> DeleteCocktail(Item item) + { int rowsAffected; diff --git a/frontend/Models/APICocktailRepository.cs b/frontend/Models/APICocktailRepository.cs index 901cbeb..004d5e6 100644 --- a/frontend/Models/APICocktailRepository.cs +++ b/frontend/Models/APICocktailRepository.cs @@ -80,12 +80,17 @@ public async Task> AddItemAsync(Item cocktailToAdd) return cocktailList; } - public async Task> DeleteItemAsync(int cocktailIdToDelete) + //public async Task> DeleteItemAsync(int cocktailIdToDelete) + public async Task> DeleteItemAsync(Item cocktailIdToDelete) { - var deleteUrl = apiUrl + "/id/" + cocktailIdToDelete; + var deleteUrl = apiUrl + "/id/" + cocktailIdToDelete.Id; var response = await APIclient.DeleteAsync(deleteUrl); + // var jsonItem = JsonSerializer.Serialize(cocktailIdToDelete); + // var httpContent = new StringContent(jsonItem, Encoding.UTF8, "application/json"); + // var response = await APIclient.DeleteAsync(apiUrl, httpContent) + cocktailList = await APIclient.GetFromJsonAsync>(apiUrl); diff --git a/frontend/Models/ICocktailRepository.cs b/frontend/Models/ICocktailRepository.cs index d8b092c..e4c5656 100644 --- a/frontend/Models/ICocktailRepository.cs +++ b/frontend/Models/ICocktailRepository.cs @@ -15,7 +15,7 @@ public interface ICocktailRepository Task> UpdateItemAsync(Item updatedItem); Task> AddItemAsync(Item cocktailToAdd); - Task> DeleteItemAsync(int cocktailIdToDelete); + Task> DeleteItemAsync(Item cocktailIdToDelete); } diff --git a/frontend/Pages/DeleteCocktail.cshtml.cs b/frontend/Pages/DeleteCocktail.cshtml.cs index 85a33d1..47f2c32 100644 --- a/frontend/Pages/DeleteCocktail.cshtml.cs +++ b/frontend/Pages/DeleteCocktail.cshtml.cs @@ -32,8 +32,9 @@ public IActionResult OnGet(int id) public async Task OnPost(Item cocktailToDelete) { // call the add method - await cocktailRepository.DeleteItemAsync(cocktailToDelete.Id); - + //await cocktailRepository.DeleteItemAsync(cocktailToDelete.Id); + await cocktailRepository.DeleteItemAsync(cocktailToDelete); + // redirect to summary page return Redirect("/Cocktails"); From 270e76c142fc2a68e3e6025eb9395cba9611f8bf Mon Sep 17 00:00:00 2001 From: Jim Vacca <33520581+MSFTJim@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:47:01 +0000 Subject: [PATCH 4/7] changed back to delete by id --- cocktails/Controllers/CocktailController.cs | 6 ++++-- frontend/Models/APICocktailRepository.cs | 4 ++-- frontend/Models/ICocktailRepository.cs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cocktails/Controllers/CocktailController.cs b/cocktails/Controllers/CocktailController.cs index c1f0589..d4f0d7c 100644 --- a/cocktails/Controllers/CocktailController.cs +++ b/cocktails/Controllers/CocktailController.cs @@ -166,11 +166,13 @@ public async Task> UpdateCocktail(Item item) } // cocktail/Post -- Delete Item - [HttpDelete("id/{id:int}")] + //[HttpDelete] - public async Task> DeleteCocktail(int id) //public async Task> DeleteCocktail(Item item) + [HttpDelete("id/{id:int}")] + public async Task> DeleteCocktail(int id) + { int rowsAffected; diff --git a/frontend/Models/APICocktailRepository.cs b/frontend/Models/APICocktailRepository.cs index 004d5e6..a2f8bd9 100644 --- a/frontend/Models/APICocktailRepository.cs +++ b/frontend/Models/APICocktailRepository.cs @@ -81,10 +81,10 @@ public async Task> AddItemAsync(Item cocktailToAdd) } //public async Task> DeleteItemAsync(int cocktailIdToDelete) - public async Task> DeleteItemAsync(Item cocktailIdToDelete) + public async Task> DeleteItemAsync(Item cocktailToDelete) { - var deleteUrl = apiUrl + "/id/" + cocktailIdToDelete.Id; + var deleteUrl = apiUrl + "/id/" + cocktailToDelete.Id; var response = await APIclient.DeleteAsync(deleteUrl); // var jsonItem = JsonSerializer.Serialize(cocktailIdToDelete); diff --git a/frontend/Models/ICocktailRepository.cs b/frontend/Models/ICocktailRepository.cs index e4c5656..264df6f 100644 --- a/frontend/Models/ICocktailRepository.cs +++ b/frontend/Models/ICocktailRepository.cs @@ -15,7 +15,7 @@ public interface ICocktailRepository Task> UpdateItemAsync(Item updatedItem); Task> AddItemAsync(Item cocktailToAdd); - Task> DeleteItemAsync(Item cocktailIdToDelete); + Task> DeleteItemAsync(Item cocktailToDelete); } From f545fadccb9d6164e00c54e7142539578b7108a1 Mon Sep 17 00:00:00 2001 From: Jim Vacca <33520581+MSFTJim@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:56:21 +0000 Subject: [PATCH 5/7] chore: Refactor delete logic to use entire object instead of just id --- cocktails/Controllers/CocktailController.cs | 2 +- cocktails/DB/SqlDb.cs | 58 ++++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/cocktails/Controllers/CocktailController.cs b/cocktails/Controllers/CocktailController.cs index d4f0d7c..a13f4e2 100644 --- a/cocktails/Controllers/CocktailController.cs +++ b/cocktails/Controllers/CocktailController.cs @@ -186,7 +186,7 @@ public async Task> DeleteCocktail(int id) return NotFound(); - _logger.LogInformation("Received request to delete by this item id: {@int}", id); + _logger.LogInformation("Received request to delete this item: {@int}", id); return $"Row(s) deleted were: {rowsAffected}"; diff --git a/cocktails/DB/SqlDb.cs b/cocktails/DB/SqlDb.cs index 90ae1fe..14776b7 100644 --- a/cocktails/DB/SqlDb.cs +++ b/cocktails/DB/SqlDb.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using cocktails.models; using Microsoft.Extensions.Configuration; +using System; namespace cocktails.DB { @@ -168,7 +169,50 @@ private int CRUD(string sqlStatetment) return rowsAffected; } - private async Task CRUDAsync(string sqlStatetment) + private async Task CRUDAsync(string sqlStatetment, Item item) + { + int rowsAffected = 0; + + using SqlConnection SQLCn = GetSQLCn(); + + using SqlCommand crudCommand = new SqlCommand(sqlStatetment, SQLCn); + crudCommand.CommandType = CommandType.Text; + + bool IgnoreCase = true; + if (sqlStatetment.StartsWith("D", IgnoreCase, null) | sqlStatetment.StartsWith("U", IgnoreCase, null)) + crudCommand.Parameters.Add("@ItemId", SqlDbType.Int).Value = item.Id; + + if (sqlStatetment.StartsWith("I", IgnoreCase, null) | sqlStatetment.StartsWith("U", IgnoreCase, null)) + { + crudCommand.Parameters.Add("@ItemName", SqlDbType.VarChar, 20).Value = item.Name; + var paramPrice = crudCommand.Parameters.Add("@ItemPrice", SqlDbType.Decimal); + paramPrice.Value = item.Price; + paramPrice.Precision = 10; + paramPrice.Scale = 2; + var paramRating = crudCommand.Parameters.Add("@ItemRating", SqlDbType.Decimal); + paramRating.Value = item.Rating; + paramRating.Precision = 10; + paramRating.Scale = 2; + crudCommand.Parameters.Add("@ItemImagePath", SqlDbType.VarChar, 255).Value = item.ImagePath; + } + + try + { + await SQLCn.OpenAsync(); + rowsAffected = await crudCommand.ExecuteNonQueryAsync(); + } + catch (Exception Ex) + { + string methodReturnValue = Ex.Message; + rowsAffected = -1; + // throw; + } + + return rowsAffected; + + } + + private async Task oldCRUDAsync(string sqlStatetment) { SqlCommand command; int rowsAffected; @@ -189,9 +233,11 @@ private async Task CRUDAsync(string sqlStatetment) public async Task DeleteItembyId(int id) { int crudResult; - string sql = $"Delete from {tblName} where Id = {id}"; + Item itemToDelete = new Item { Id = id }; + + string sql = $"Delete from {tblName} where Id = @ItemId"; - crudResult = await CRUDAsync(sql); + crudResult = await CRUDAsync(sql, itemToDelete); return crudResult; } @@ -202,16 +248,16 @@ public async Task UpdateItembyId(Item item) string sql = $"Update t Set t.name = '{item.Name}', t.price = {item.Price}, t.rating = {item.Rating}, t.ImagePath = '{item.ImagePath}'" + $" From {tblName} t where t.id = {item.Id}"; - crudResult = await CRUDAsync(sql); + crudResult = await CRUDAsync(sql,item); return crudResult; } public async Task InsertItem(Item item) { int crudResult; - string sql = $"Insert into {tblName} (Name, Price ,Rating) values ('{item.Name}', {item.Price}, {item.Rating})"; + string sql = $"Insert into {tblName} (Name, Price ,Rating, ImagePath) values ('{item.Name}', {item.Price}, {item.Rating}, {item.ImagePath})"; - crudResult = await CRUDAsync(sql); + crudResult = await CRUDAsync(sql, item); return crudResult; } From e4e1edebe9fc5d08a63d6e4070c2e04cbf431011 Mon Sep 17 00:00:00 2001 From: Jim Vacca <33520581+MSFTJim@users.noreply.github.com> Date: Thu, 8 Aug 2024 20:54:16 +0000 Subject: [PATCH 6/7] chore: Update & Insert SQL queries to use parameterized values for item properties --- cocktails/DB/SqlDb.cs | 10 +++++----- .../Data/Create and Populate Item for Cocktail.sql | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cocktails/DB/SqlDb.cs b/cocktails/DB/SqlDb.cs index 14776b7..b31abd2 100644 --- a/cocktails/DB/SqlDb.cs +++ b/cocktails/DB/SqlDb.cs @@ -184,7 +184,7 @@ private async Task CRUDAsync(string sqlStatetment, Item item) if (sqlStatetment.StartsWith("I", IgnoreCase, null) | sqlStatetment.StartsWith("U", IgnoreCase, null)) { - crudCommand.Parameters.Add("@ItemName", SqlDbType.VarChar, 20).Value = item.Name; + crudCommand.Parameters.Add("@ItemName", SqlDbType.VarChar, 50).Value = item.Name; var paramPrice = crudCommand.Parameters.Add("@ItemPrice", SqlDbType.Decimal); paramPrice.Value = item.Price; paramPrice.Precision = 10; @@ -245,8 +245,8 @@ public async Task DeleteItembyId(int id) public async Task UpdateItembyId(Item item) { int crudResult; - string sql = $"Update t Set t.name = '{item.Name}', t.price = {item.Price}, t.rating = {item.Rating}, t.ImagePath = '{item.ImagePath}'" - + $" From {tblName} t where t.id = {item.Id}"; + string sql = $"Update t Set t.name = @ItemName, t.price = @ItemPrice, t.rating = @ItemRating, t.ImagePath = @ItemImagePath" + + $" From {tblName} t where t.id = @ItemId"; crudResult = await CRUDAsync(sql,item); @@ -255,8 +255,8 @@ public async Task UpdateItembyId(Item item) public async Task InsertItem(Item item) { int crudResult; - string sql = $"Insert into {tblName} (Name, Price ,Rating, ImagePath) values ('{item.Name}', {item.Price}, {item.Rating}, {item.ImagePath})"; - + string sql = $"Insert into {tblName} (Name, Price ,Rating, ImagePath) values (@ItemName, @ItemPrice, @ItemRating, @ItemImagePath)"; + item.ImagePath = item.ImagePath ?? "NoImageSelected.png"; crudResult = await CRUDAsync(sql, item); return crudResult; diff --git a/cocktails/Data/Create and Populate Item for Cocktail.sql b/cocktails/Data/Create and Populate Item for Cocktail.sql index 1d71bfb..1e7fa64 100644 --- a/cocktails/Data/Create and Populate Item for Cocktail.sql +++ b/cocktails/Data/Create and Populate Item for Cocktail.sql @@ -5,10 +5,10 @@ GO -- Create the table in the specified schema CREATE TABLE [dbo].[Items] ( [Id] INT IDENTITY (1001, 1) NOT NULL, - [Name] VARCHAR (20) NULL, + [Name] VARCHAR (50) NULL, [Price] NUMERIC (10, 2) NULL, [Rating] NUMERIC (10, 2) NULL, - [ImagePath] VARCHAR (255) CONSTRAINT [DEFAULT_Items_ImagePath] DEFAULT ('NoImage.jpg') NULL, + [ImagePath] VARCHAR (255) CONSTRAINT [DEFAULT_Items_ImagePath] DEFAULT ('NoImageSelected.png') NULL, PRIMARY KEY CLUSTERED ([Id] ASC) ); GO From 7c3c22c243fd4ca85483119e84083e18dd00a46f Mon Sep 17 00:00:00 2001 From: Jim Vacca <33520581+MSFTJim@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:17:43 +0000 Subject: [PATCH 7/7] chore: Update Index.cshtml to include environment information --- frontend/Pages/Index.cshtml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/Pages/Index.cshtml b/frontend/Pages/Index.cshtml index ca0b36f..5858671 100644 --- a/frontend/Pages/Index.cshtml +++ b/frontend/Pages/Index.cshtml @@ -10,3 +10,10 @@

July 2024

Hit the API directly!

+ + +
Environment is Development
+
+ +
Environment is NOT Development
+