Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2769039

Browse files
committed
Merge branch 'v5.0' into v6.0
2 parents 63f3d49 + d9edd57 commit 2769039

17 files changed

+493
-307
lines changed

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ In this branch the backend REST API service and the database are modified so tha
7676

7777
### V2.0 Notes
7878

79-
In this branch the backend REST API service is modified so that the to-do list can be saved an manged using an Azure SQL database. Communication with the database is done using JSON too, as Azure SQL support [JSON natively](https://docs.microsoft.com/sql/relational-databases/json/json-data-sql-server).
79+
In this branch the backend REST API service is modified so that the to-do list can be saved an managed using an Azure SQL database. Communication with the database is done using JSON too, as Azure SQL support [JSON natively](https://docs.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver15). As an alternative, the same API using code [EF Core](https://learn.microsoft.com/en-us/ef/core/) is also provided.
80+
A initial option to (imperatively) deploy the database is also used, either using manually applied database script, or via the EF Core migrations.
8081

8182
### V1.0 Notes
8283

‎api/Startup.cs‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.EntityFrameworkCore;
5+
using Todo.Backend.EFCore;
6+
7+
[assembly: FunctionsStartup(typeof(Todo.Backend.Startup))]
8+
9+
namespace Todo.Backend
10+
{
11+
public class Startup : FunctionsStartup
12+
{
13+
public override void Configure(IFunctionsHostBuilder builder)
14+
{
15+
string connectionString = Environment.GetEnvironmentVariable("AzureSQL");
16+
builder.Services.AddDbContext<TodoContext>(
17+
options => options.UseSqlServer(connectionString)
18+
);
19+
}
20+
}
21+
}

‎api/ToDoHandler.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
using Polly;
1919
using System.ComponentModel;
2020

21-
namespace api
21+
namespace Todo.Backend
2222
{
2323
public static class ToDoHandler
2424
{

‎api/ToDoHandlerEFCore.cs‎

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Azure.WebJobs;
6+
using Microsoft.Azure.WebJobs.Extensions.Http;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.Logging;
9+
using Newtonsoft.Json;
10+
using Newtonsoft.Json.Linq;
11+
using System.Collections.Generic;
12+
using System.Linq;
13+
using System.Data;
14+
using Microsoft.Data.SqlClient;
15+
using Microsoft.EntityFrameworkCore;
16+
using Microsoft.EntityFrameworkCore.Design;
17+
using System.ComponentModel.DataAnnotations.Schema;
18+
using System.Security.Claims;
19+
using System.Text;
20+
21+
namespace Todo.Backend.EFCore
22+
{
23+
[Table("todos")]
24+
public class Todo {
25+
26+
[JsonProperty("id")]
27+
[Column("id")]
28+
public int Id { get; set; }
29+
30+
[JsonProperty("title")]
31+
[Column("todo", TypeName = "nvarchar(100)")]
32+
public string Title { get; set; }
33+
34+
[JsonProperty("completed")]
35+
[Column("completed", TypeName = "tinyint")]
36+
public bool Completed { get; set; }
37+
38+
[Column("owner_id", TypeName = "nvarchar(128)")]
39+
public string Owner { get; set; }
40+
41+
public bool ShouldSerializeOwner() => false;
42+
43+
}
44+
45+
public class TodoContext : DbContext
46+
{
47+
public TodoContext(DbContextOptions<TodoContext> options)
48+
: base(options)
49+
{ }
50+
51+
protected override void OnModelCreating(ModelBuilder modelBuilder)
52+
{
53+
modelBuilder.HasSequence<int>("global_sequence");
54+
55+
modelBuilder.Entity<Todo>()
56+
.Property(o => o.Id)
57+
.HasDefaultValueSql("NEXT VALUE FOR global_sequence");
58+
59+
modelBuilder.Entity<Todo>()
60+
.Property(o => o.Owner)
61+
.HasDefaultValue("anonymous");
62+
}
63+
64+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
65+
=> optionsBuilder
66+
.LogTo(Console.WriteLine, LogLevel.Information)
67+
.EnableSensitiveDataLogging()
68+
.EnableDetailedErrors();
69+
70+
public DbSet<Todo> Todos { get; set; }
71+
}
72+
73+
public class TodoContextFactory: IDesignTimeDbContextFactory<TodoContext>
74+
{
75+
public TodoContext CreateDbContext(string[] args)
76+
{
77+
var optionsBuilder = new DbContextOptionsBuilder<TodoContext>();
78+
optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("AzureSQL"));
79+
80+
return new TodoContext(optionsBuilder.Options);
81+
}
82+
}
83+
84+
public class ClientPrincipal
85+
{
86+
public string IdentityProvider { get; set; }
87+
public string UserId { get; set; }
88+
public string UserDetails { get; set; }
89+
public IEnumerable<string> UserRoles { get; set; }
90+
}
91+
92+
public static class Utils
93+
{
94+
public static ClientPrincipal ParsePrincipal(this HttpRequest req)
95+
{
96+
var principal = new ClientPrincipal();
97+
98+
if (req.Headers.TryGetValue("x-ms-client-principal", out var header))
99+
{
100+
var data = header[0];
101+
var decoded = Convert.FromBase64String(data);
102+
var json = Encoding.UTF8.GetString(decoded);
103+
principal = JsonConvert.DeserializeObject<ClientPrincipal>(json);
104+
}
105+
106+
principal.UserRoles = principal.UserRoles?.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase);
107+
principal.UserId = principal.UserId ?? "anonymous";
108+
109+
return principal;
110+
}
111+
}
112+
113+
public class ToDoHandler
114+
{
115+
private TodoContext _todoContext;
116+
117+
public ToDoHandler(TodoContext todoContext)
118+
{
119+
this._todoContext = todoContext;
120+
}
121+
122+
[FunctionName("GetEF")]
123+
public async Task<IActionResult> Get(
124+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ef/todo/{id:int?}")] HttpRequest req,
125+
ILogger log,
126+
int? id)
127+
{
128+
var cp = req.ParsePrincipal();
129+
130+
IQueryable<Todo> todos = this._todoContext.Todos.Where(t => t.Owner == cp.UserId);
131+
132+
if (id.HasValue) {
133+
todos = this._todoContext.Todos.Where(t => t.Id == id);
134+
}
135+
136+
return new OkObjectResult(await todos.ToListAsync());
137+
}
138+
139+
[FunctionName("PostEF")]
140+
public async Task<IActionResult> Post(
141+
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "ef/todo")] HttpRequest req,
142+
ILogger log)
143+
{
144+
var cp = req.ParsePrincipal();
145+
146+
string body = await new StreamReader(req.Body).ReadToEndAsync();
147+
var todo = JsonConvert.DeserializeObject<Todo>(body);
148+
todo.Owner = cp.UserId;
149+
150+
await this._todoContext.AddAsync(todo);
151+
await this._todoContext.SaveChangesAsync();
152+
153+
var result = new List<Todo>() { todo };
154+
return new OkObjectResult(result);
155+
}
156+
157+
[FunctionName("PatchEF")]
158+
public async Task<IActionResult> Patch(
159+
[HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "ef/todo/{id}")] HttpRequest req,
160+
ILogger log,
161+
int id)
162+
{
163+
var cp = req.ParsePrincipal();
164+
165+
string body = await new StreamReader(req.Body).ReadToEndAsync();
166+
var newTodo = JsonConvert.DeserializeObject<Todo>(body);
167+
168+
var targetTodo = this._todoContext.Todos.Where(t => t.Owner == cp.UserId).FirstOrDefault(t => t.Id == id);
169+
if (targetTodo == null)
170+
return new NotFoundResult();
171+
172+
//targetTodo.Id = newTodo.Id;
173+
targetTodo.Title = newTodo.Title;
174+
targetTodo.Completed = newTodo.Completed;
175+
176+
await this._todoContext.SaveChangesAsync();
177+
178+
return new OkObjectResult(targetTodo);
179+
}
180+
181+
[FunctionName("DeleteEF")]
182+
public async Task<IActionResult> Delete(
183+
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "ef/todo/{id}")] HttpRequest req,
184+
ILogger log,
185+
int id)
186+
{
187+
var cp = req.ParsePrincipal();
188+
189+
var todo = this._todoContext.Todos.Where(t => t.Owner == cp.UserId).FirstOrDefault(t => t.Id == id);
190+
191+
if (todo == null)
192+
return new NotFoundResult();
193+
194+
this._todoContext.Todos.Remove(todo);
195+
await this._todoContext.SaveChangesAsync();
196+
197+
return new OkObjectResult(todo);
198+
}
199+
}
200+
}

‎api/api.csproj‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageReference Include="Dapper" Version="2.0.123" />
8+
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
89
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.1.0" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.10">
11+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
12+
<PrivateAssets>all</PrivateAssets>
13+
</PackageReference>
14+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.10" />
15+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
916
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
1017
<PackageReference Include="Polly" Version="7.2.3" />
1118
</ItemGroup>

‎api/migrations/20221111194425_InitialCreate.Designer.cs‎

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Microsoft.EntityFrameworkCore.Migrations;
2+
3+
#nullable disable
4+
5+
namespace api.migrations
6+
{
7+
public partial class InitialCreate : Migration
8+
{
9+
protected override void Up(MigrationBuilder migrationBuilder)
10+
{
11+
migrationBuilder.CreateSequence<int>(
12+
name: "global_sequence");
13+
14+
migrationBuilder.CreateTable(
15+
name: "todos",
16+
columns: table => new
17+
{
18+
id = table.Column<int>(type: "int", nullable: false, defaultValueSql: "NEXT VALUE FOR global_sequence"),
19+
todo = table.Column<string>(type: "nvarchar(100)", nullable: true),
20+
completed = table.Column<byte>(type: "tinyint", nullable: false)
21+
},
22+
constraints: table =>
23+
{
24+
table.PrimaryKey("PK_todos", x => x.id);
25+
});
26+
27+
// Custom Code
28+
29+
migrationBuilder.Sql(@"
30+
insert into dbo.[todos]
31+
(todo, completed)
32+
values
33+
('slides', 0),
34+
('demos', 0)
35+
");
36+
}
37+
38+
protected override void Down(MigrationBuilder migrationBuilder)
39+
{
40+
migrationBuilder.DropTable(
41+
name: "todos");
42+
43+
migrationBuilder.DropSequence(
44+
name: "global_sequence");
45+
}
46+
}
47+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /