diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/Todo.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/Todo.cs index d3579a4..360ab0e 100644 --- a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/Todo.cs +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/Todo.cs @@ -8,14 +8,23 @@ namespace TodoApp.DataAccess.DataModels public class Todo { public long Id { get; set; } + [Required] public string Name { get; set; } + public string? Description { get; set; } + public bool IsCompleted { get; set; } + [Required] public DateTime CreatedTime { get; set; } + public DateTime? CompletedTime { get; set; } + [Required] + [ForeignKey("User")] public long CreatedUser { get; set; } + + public virtual User? User { get; set; } } } diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/TodoContext.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/TodoContext.cs index c171e01..41d6f7b 100644 --- a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/TodoContext.cs +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/TodoContext.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; +using System.Reflection.Metadata; namespace TodoApp.DataAccess.DataModels { @@ -13,5 +14,12 @@ public TodoContext(DbContextOptions options) : base(options) { } public string Name => nameof(TodoContext); public string Provider => nameof(Microsoft.EntityFrameworkCore); + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasData( + new User { Id = 1, FirstName = "Admin", LastName = "Admin", Email = "indunilw@99x.io" }); + } + } } diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/User.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/User.cs index 3890e54..8649f94 100644 --- a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/User.cs +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/DataModels/User.cs @@ -8,13 +8,18 @@ namespace TodoApp.DataAccess.DataModels public class User { public long Id { get; set; } + [Required] public string Email { get; set; } + [Required] public string FirstName { get; set; } + [Required] public string LastName { get; set; } + public ICollection ToDos { get; set; } + } } \ No newline at end of file diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/20221021062445_InitialMigration.Designer.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/20221021062445_InitialMigration.Designer.cs new file mode 100644 index 0000000..8bc2b67 --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/20221021062445_InitialMigration.Designer.cs @@ -0,0 +1,113 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoApp.DataAccess.DataModels; + +#nullable disable + +namespace TodoApp.DataAccess.Migrations +{ + [DbContext(typeof(TodoContext))] + [Migration("20221021062445_InitialMigration")] + partial class InitialMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("TodoApp.DataAccess.DataModels.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CompletedTime") + .HasColumnType("datetime2"); + + b.Property("CreatedTime") + .HasColumnType("datetime2"); + + b.Property("CreatedUser") + .HasColumnType("bigint"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedUser"); + + b.ToTable("Todo"); + }); + + modelBuilder.Entity("TodoApp.DataAccess.DataModels.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("User"); + + b.HasData( + new + { + Id = 1L, + Email = "indunilw@99x.io", + FirstName = "Admin", + LastName = "Admin" + }); + }); + + modelBuilder.Entity("TodoApp.DataAccess.DataModels.Todo", b => + { + b.HasOne("TodoApp.DataAccess.DataModels.User", "User") + .WithMany("ToDos") + .HasForeignKey("CreatedUser") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TodoApp.DataAccess.DataModels.User", b => + { + b.Navigation("ToDos"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/20221021062445_InitialMigration.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/20221021062445_InitialMigration.cs new file mode 100644 index 0000000..1e5bb72 --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/20221021062445_InitialMigration.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TodoApp.DataAccess.Migrations +{ + public partial class InitialMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "User", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Email = table.Column(type: "nvarchar(max)", nullable: false), + FirstName = table.Column(type: "nvarchar(max)", nullable: false), + LastName = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_User", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Todo", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + IsCompleted = table.Column(type: "bit", nullable: false), + CreatedTime = table.Column(type: "datetime2", nullable: false), + CompletedTime = table.Column(type: "datetime2", nullable: true), + CreatedUser = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Todo", x => x.Id); + table.ForeignKey( + name: "FK_Todo_User_CreatedUser", + column: x => x.CreatedUser, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "User", + columns: new[] { "Id", "Email", "FirstName", "LastName" }, + values: new object[] { 1L, "indunilw@99x.io", "Admin", "Admin" }); + + migrationBuilder.CreateIndex( + name: "IX_Todo_CreatedUser", + table: "Todo", + column: "CreatedUser"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Todo"); + + migrationBuilder.DropTable( + name: "User"); + } + } +} diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/TodoContextModelSnapshot.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/TodoContextModelSnapshot.cs index 72aac2c..c081f3f 100644 --- a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/TodoContextModelSnapshot.cs +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.DataAccess/Migrations/TodoContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.9") + .HasAnnotation("ProductVersion", "6.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); @@ -51,6 +51,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("CreatedUser"); + b.ToTable("Todo"); }); @@ -77,6 +79,31 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); b.ToTable("User"); + + b.HasData( + new + { + Id = 1L, + Email = "indunilw@99x.io", + FirstName = "Admin", + LastName = "Admin" + }); + }); + + modelBuilder.Entity("TodoApp.DataAccess.DataModels.Todo", b => + { + b.HasOne("TodoApp.DataAccess.DataModels.User", "User") + .WithMany("ToDos") + .HasForeignKey("CreatedUser") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TodoApp.DataAccess.DataModels.User", b => + { + b.Navigation("ToDos"); }); #pragma warning restore 612, 618 } diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Controllers/TodoController.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Controllers/TodoController.cs new file mode 100644 index 0000000..3c3219e --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Controllers/TodoController.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.EntityFrameworkCore; +using TodoApp.DataAccess.DataModels; + +namespace TodoApp.Portal.Controllers +{ + public class TodoController : Controller + { + private readonly TodoContext _context; + + public TodoController(TodoContext context) + { + _context = context; + } + + // GET: Todo + public async Task Index() + { + return View(await _context.Todos.ToListAsync()); + } + + // GET: Todo/Details/5 + public async Task Details(long? id) + { + if (id == null || _context.Todos == null) + { + return NotFound(); + } + + var todo = await _context.Todos + .FirstOrDefaultAsync(m => m.Id == id); + if (todo == null) + { + return NotFound(); + } + + return View(todo); + } + + // GET: Todo/Create + public IActionResult Create() + { + return View(); + } + + // POST: Todo/Create + // To protect from overposting attacks, enable the specific properties you want to bind to. + // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598. + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create([Bind("Id,Name,Description,IsCompleted,CompletedTime")] Todo todo) + { + ModelState.Remove("User"); + if (ModelState.IsValid) + { + todo.CreatedUser = todo.CreatedUser == 0 ? 1 : todo.CreatedUser; + todo.CreatedTime = DateTime.Now; + _context.Add(todo); + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + return View(todo); + } + + // GET: Todo/Edit/5 + public async Task Edit(long? id) + { + if (id == null || _context.Todos == null) + { + return NotFound(); + } + + var todo = await _context.Todos.FindAsync(id); + if (todo == null) + { + return NotFound(); + } + return View(todo); + } + + // POST: Todo/Edit/5 + // To protect from overposting attacks, enable the specific properties you want to bind to. + // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598. + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(long id, [Bind("Id,Name,Description,IsCompleted,CompletedTime,CreatedUser")] Todo todo) + { + if (id != todo.Id) + { + return NotFound(); + } + + if (ModelState.IsValid) + { + try + { + _context.Update(todo); + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!TodoExists(todo.Id)) + { + return NotFound(); + } + else + { + throw; + } + } + return RedirectToAction(nameof(Index)); + } + return View(todo); + } + + // GET: Todo/Delete/5 + public async Task Delete(long? id) + { + if (id == null || _context.Todos == null) + { + return NotFound(); + } + + var todo = await _context.Todos + .FirstOrDefaultAsync(m => m.Id == id); + if (todo == null) + { + return NotFound(); + } + + return View(todo); + } + + // POST: Todo/Delete/5 + [HttpPost, ActionName("Delete")] + [ValidateAntiForgeryToken] + public async Task DeleteConfirmed(long id) + { + if (_context.Todos == null) + { + return Problem("Entity set 'TodoContext.Todos' is null."); + } + var todo = await _context.Todos.FindAsync(id); + if (todo != null) + { + _context.Todos.Remove(todo); + } + + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + + private bool TodoExists(long id) + { + return _context.Todos.Any(e => e.Id == id); + } + } +} diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Program.cs b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Program.cs index 252decc..d5059ec 100644 --- a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Program.cs +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Program.cs @@ -36,6 +36,6 @@ app.MapControllerRoute( name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); + pattern: "{controller=Todo}/{action=Index}/{id?}"); app.Run(); \ No newline at end of file diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/TodoApp.Portal.csproj b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/TodoApp.Portal.csproj index f629d04..647474e 100644 --- a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/TodoApp.Portal.csproj +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/TodoApp.Portal.csproj @@ -7,10 +7,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Shared/_Layout.cshtml b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Shared/_Layout.cshtml index 7081751..f842028 100644 --- a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Shared/_Layout.cshtml +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Shared/_Layout.cshtml @@ -6,27 +6,13 @@ @ViewData["Title"] - TodoApp.Portal - +
@@ -36,11 +22,6 @@ -
-
- © 2022 - TodoApp.Portal - Privacy -
-
diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Create.cshtml b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Create.cshtml new file mode 100644 index 0000000..7ed5258 --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Create.cshtml @@ -0,0 +1,49 @@ +@model TodoApp.DataAccess.DataModels.Todo + +@{ + ViewData["Title"] = "Create"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

Create

+ +

Todo

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+ +
+
+ + + +
+
+ +
+
+
+
+ + + +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Delete.cshtml b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Delete.cshtml new file mode 100644 index 0000000..17ee957 --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Delete.cshtml @@ -0,0 +1,58 @@ +@model TodoApp.DataAccess.DataModels.Todo + +@{ + ViewData["Title"] = "Delete"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

Delete

+ +

Are you sure you want to delete this?

+
+

Todo

+
+
+
+ @Html.DisplayNameFor(model => model.Name) +
+
+ @Html.DisplayFor(model => model.Name) +
+
+ @Html.DisplayNameFor(model => model.Description) +
+
+ @Html.DisplayFor(model => model.Description) +
+
+ @Html.DisplayNameFor(model => model.IsCompleted) +
+
+ @Html.DisplayFor(model => model.IsCompleted) +
+
+ @Html.DisplayNameFor(model => model.CreatedTime) +
+
+ @Html.DisplayFor(model => model.CreatedTime) +
+
+ @Html.DisplayNameFor(model => model.CompletedTime) +
+
+ @Html.DisplayFor(model => model.CompletedTime) +
+
+ @Html.DisplayNameFor(model => model.CreatedUser) +
+
+ @Html.DisplayFor(model => model.CreatedUser) +
+
+ +
+ + | + Back to List +
+
diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Details.cshtml b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Details.cshtml new file mode 100644 index 0000000..2a56263 --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Details.cshtml @@ -0,0 +1,55 @@ +@model TodoApp.DataAccess.DataModels.Todo + +@{ + ViewData["Title"] = "Details"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

Details

+ +
+

Todo

+
+
+
+ @Html.DisplayNameFor(model => model.Name) +
+
+ @Html.DisplayFor(model => model.Name) +
+
+ @Html.DisplayNameFor(model => model.Description) +
+
+ @Html.DisplayFor(model => model.Description) +
+
+ @Html.DisplayNameFor(model => model.IsCompleted) +
+
+ @Html.DisplayFor(model => model.IsCompleted) +
+
+ @Html.DisplayNameFor(model => model.CreatedTime) +
+
+ @Html.DisplayFor(model => model.CreatedTime) +
+
+ @Html.DisplayNameFor(model => model.CompletedTime) +
+
+ @Html.DisplayFor(model => model.CompletedTime) +
+
+ @Html.DisplayNameFor(model => model.CreatedUser) +
+
+ @Html.DisplayFor(model => model.CreatedUser) +
+
+
+ diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Edit.cshtml b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Edit.cshtml new file mode 100644 index 0000000..9152d67 --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Edit.cshtml @@ -0,0 +1,55 @@ +@model TodoApp.DataAccess.DataModels.Todo + +@{ + ViewData["Title"] = "Edit"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

Edit

+ +

Todo

+
+
+
+
+
+ +
+ + + +
+
+ + + +
+
+ +
+
+ + + +
+
+ + + +
+
+ +
+
+
+
+ + + +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Index.cshtml b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Index.cshtml new file mode 100644 index 0000000..07efcac --- /dev/null +++ b/azure-app-service-vnet-template/Source/TodoApp/TodoApp.Portal/Views/Todo/Index.cshtml @@ -0,0 +1,66 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Task To Do"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +
+

+ Create New +

+ + + + + + + + + + + + + + @foreach (var item in Model) { + + + + + + + + + + } + +
+ @Html.DisplayNameFor(model => model.Name) + + @Html.DisplayNameFor(model => model.Description) + + @Html.DisplayNameFor(model => model.IsCompleted) + + @Html.DisplayNameFor(model => model.CreatedTime) + + @Html.DisplayNameFor(model => model.CompletedTime) + + @Html.DisplayNameFor(model => model.CreatedUser) +
+ @Html.DisplayFor(modelItem => item.Name) + + @Html.DisplayFor(modelItem => item.Description) + + @Html.DisplayFor(modelItem => item.IsCompleted) + + @Html.DisplayFor(modelItem => item.CreatedTime) + + @Html.DisplayFor(modelItem => item.CompletedTime) + + @Html.DisplayFor(modelItem => item.User.Email) + + Edit | + Details | + Delete +
+