Skip to content

Commit e16cfba

Browse files
authored
.NET 문서 수정 (microsoft#10)
* .NET용 커스텀 인스트럭션 위치 조정 * .NET 샘플 앱 추가 * .NET 앱 업데이트 * Java Dockerfile 생성 * 컨테이너 오케스트레이션 추가 * 컨테이너 오케스트레이션 조정 * 커스텀 인스트럭션 조정
1 parent 916234e commit e16cfba

File tree

93 files changed

+61482
-92
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+61482
-92
lines changed

.github/.gitkeep

Whitespace-only changes.

.github/copilot-instructions.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

complete/Dockerfile.dotnet

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Stage 1: Build the application
2+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
3+
WORKDIR /src
4+
5+
# Copy the project files
6+
COPY ["dotnet/ContosoSnsWebApp/ContosoSnsWebApp.csproj", "ContosoSnsWebApp/"]
7+
RUN dotnet restore "ContosoSnsWebApp/ContosoSnsWebApp.csproj"
8+
9+
# Copy the rest of the application code
10+
COPY ["dotnet/ContosoSnsWebApp/", "ContosoSnsWebApp/"]
11+
12+
# Build the application
13+
WORKDIR "/src/ContosoSnsWebApp"
14+
RUN dotnet build "ContosoSnsWebApp.csproj" -c Release -o /app/build
15+
16+
# Stage 2: Publish the application
17+
FROM build AS publish
18+
RUN dotnet publish "ContosoSnsWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
19+
20+
# Stage 3: Final stage with the runtime image
21+
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
22+
WORKDIR /app
23+
EXPOSE 8080
24+
25+
# Set the environment variables
26+
ENV ASPNETCORE_URLS=http://+:8080
27+
ENV DOTNET_RUNNING_IN_CONTAINER=true
28+
29+
# Copy the published files from the build stage
30+
COPY --from=publish /app/publish .
31+
32+
# Set the entry point for the container
33+
ENTRYPOINT ["dotnet", "ContosoSnsWebApp.dll"]

complete/Dockerfile.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Stage 1: Build the application
2+
FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu AS build
3+
4+
WORKDIR /app
5+
6+
# Copy gradle files for dependency resolution
7+
COPY java/demo/gradle/ ./gradle/
8+
COPY java/demo/gradlew java/demo/build.gradle java/demo/settings.gradle ./
9+
10+
# Give executable permissions to gradlew
11+
RUN chmod +x ./gradlew
12+
13+
# Download dependencies to cache this layer
14+
RUN ./gradlew dependencies --no-daemon
15+
16+
# Copy source code
17+
COPY java/demo/src ./src
18+
19+
# Build the application
20+
RUN ./gradlew bootJar --no-daemon
21+
22+
# Stage 2: Extract JRE from JDK
23+
FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu AS jre-build
24+
25+
# Create a custom JRE using jlink that only includes modules needed for the application
26+
RUN jlink \
27+
--add-modules java.base,java.compiler,java.desktop,java.instrument,java.management,java.naming,java.prefs,java.rmi,java.security.jgss,java.security.sasl,java.sql,jdk.crypto.ec,jdk.unsupported,jdk.zipfs,jdk.management \
28+
--strip-debug \
29+
--no-man-pages \
30+
--no-header-files \
31+
--compress=2 \
32+
--output /jre-minimal
33+
34+
# Stage 3: Create final image
35+
FROM ubuntu:22.04
36+
37+
# Set environment variables
38+
ENV JAVA_HOME=/opt/jre-minimal
39+
ENV PATH="${JAVA_HOME}/bin:${PATH}"
40+
41+
# Copy the extracted JRE from the jre-build stage
42+
COPY --from=jre-build /jre-minimal $JAVA_HOME
43+
44+
# Copy the built application from the build stage
45+
WORKDIR /app
46+
COPY --from=build /app/build/libs/*.jar app.jar
47+
48+
# Create a directory for persistent data
49+
RUN mkdir -p /app/data
50+
51+
# Expose the application port
52+
EXPOSE 8080
53+
54+
# Set the entrypoint command to run the application
55+
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

complete/docker-compose.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
services:
2+
contoso-java:
3+
build:
4+
context: .
5+
dockerfile: Dockerfile.java
6+
container_name: contoso-java
7+
ports:
8+
- "5050:8080"
9+
volumes:
10+
- ./java/demo/sns.db:/app/sns.db
11+
networks:
12+
- contoso
13+
14+
contoso-dotnet:
15+
build:
16+
context: .
17+
dockerfile: Dockerfile.dotnet
18+
container_name: contoso-dotnet
19+
ports:
20+
- "3000:8080"
21+
depends_on:
22+
- contoso-java
23+
environment:
24+
- ApiBaseUrl=http://contoso-java:8080
25+
networks:
26+
- contoso
27+
28+
networks:
29+
contoso:
30+
name: contoso
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContosoSnsWebApp", "ContosoSnsWebApp\ContosoSnsWebApp.csproj", "{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Debug|x64 = Debug|x64
12+
Debug|x86 = Debug|x86
13+
Release|Any CPU = Release|Any CPU
14+
Release|x64 = Release|x64
15+
Release|x86 = Release|x86
16+
EndGlobalSection
17+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
18+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Debug|Any CPU.Build.0 = Debug|Any CPU
20+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Debug|x64.ActiveCfg = Debug|Any CPU
21+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Debug|x64.Build.0 = Debug|Any CPU
22+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Debug|x86.ActiveCfg = Debug|Any CPU
23+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Debug|x86.Build.0 = Debug|Any CPU
24+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Release|Any CPU.ActiveCfg = Release|Any CPU
25+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Release|Any CPU.Build.0 = Release|Any CPU
26+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Release|x64.ActiveCfg = Release|Any CPU
27+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Release|x64.Build.0 = Release|Any CPU
28+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Release|x86.ActiveCfg = Release|Any CPU
29+
{D7288AEF-CF49-4790-9EE7-0E5587B7C06E}.Release|x86.Build.0 = Release|Any CPU
30+
EndGlobalSection
31+
GlobalSection(SolutionProperties) = preSolution
32+
HideSolutionNode = FALSE
33+
EndGlobalSection
34+
EndGlobal
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<base href="/" />
8+
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
9+
<link rel="stylesheet" href="@Assets["app.css"]" />
10+
<link rel="stylesheet" href="@Assets["ContosoSnsWebApp.styles.css"]" />
11+
<ImportMap />
12+
<link rel="icon" type="image/png" href="favicon.png" />
13+
<HeadOutlet />
14+
</head>
15+
16+
<body>
17+
<Routes />
18+
<script src="_framework/blazor.web.js"></script>
19+
</body>
20+
21+
</html>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
@namespace ContosoSnsWebApp.Components
3+
4+
<button class="btn @ButtonClass @SizeClass @(Disabled ? "disabled" : "") @CssClass"
5+
type="@Type"
6+
disabled="@Disabled"
7+
@onclick="OnClick">
8+
@ChildContent
9+
</button>
10+
11+
@code {
12+
[Parameter] public RenderFragment? ChildContent { get; set; }
13+
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
14+
[Parameter] public string Variant { get; set; } = "primary"; // primary, secondary, outline, danger, etc.
15+
[Parameter] public bool Small { get; set; }
16+
[Parameter] public bool Disabled { get; set; }
17+
[Parameter] public string Type { get; set; } = "button"; // button, submit, reset
18+
[Parameter] public string? CssClass { get; set; } // Allow additional custom classes
19+
20+
private string ButtonClass => Variant switch
21+
{
22+
"primary" => "btn-primary",
23+
"secondary" => "btn-secondary",
24+
"outline" => "btn-outline-primary", // Example mapping for outline
25+
"danger" => "btn-danger",
26+
_ => "btn-primary" // Default
27+
};
28+
29+
private string SizeClass => Small ? "btn-sm" : "";
30+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
2+
@using ContosoSnsWebApp.Models
3+
@using ContosoSnsWebApp.Services
4+
@inject ApiService ApiService
5+
6+
<div class="mt-4">
7+
<h4 class="mb-3">댓글 (@(comments?.Count ?? 0))</h4>
8+
9+
@if (isLoading)
10+
{
11+
<p>댓글 로딩 중...</p>
12+
}
13+
else if (!string.IsNullOrEmpty(loadError))
14+
{
15+
<div class="alert alert-warning">@loadError</div>
16+
}
17+
else if (comments == null || comments.Count == 0)
18+
{
19+
<p class="text-muted">아직 댓글이 없습니다.</p>
20+
}
21+
else
22+
{
23+
<div class="list-group list-group-flush mb-3">
24+
@foreach (var comment in comments.OrderByDescending(c => c.CreatedAt))
25+
{
26+
<div class="list-group-item px-0">
27+
<div class="d-flex w-100 justify-content-between">
28+
<h6 class="mb-1">@comment.UserName</h6>
29+
<small class="text-muted">@FormatDate(comment.CreatedAt)</small>
30+
</div>
31+
<p class="mb-1">@comment.Content</p>
32+
</div>
33+
}
34+
</div>
35+
}
36+
37+
<EditForm Model="newCommentRequest" OnValidSubmit="HandleCommentSubmitAsync" FormName="newCommentForm">
38+
<DataAnnotationsValidator />
39+
<div class="mb-2">
40+
<InputTextArea @bind-Value="newCommentRequest.Content" class="form-control" rows="2" placeholder="댓글을 입력하세요..." required />
41+
<ValidationMessage For="@(() => newCommentRequest.Content)" />
42+
</div>
43+
@if (!string.IsNullOrEmpty(submitError))
44+
{
45+
<div class="alert alert-danger mt-2 py-1">@submitError</div>
46+
}
47+
<Button Type="submit" Variant="secondary" Small="true" Disabled="isSubmitting">
48+
@(isSubmitting ? "등록 중..." : "댓글 등록")
49+
</Button>
50+
</EditForm>
51+
</div>
52+
53+
@code {
54+
[Parameter, EditorRequired] public int PostId { get; set; }
55+
[Parameter] public string? UserName { get; set; } // Current user's name
56+
57+
private List<Comment>? comments;
58+
private NewCommentRequest newCommentRequest = new("", "");
59+
private bool isLoading = true;
60+
private bool isSubmitting = false;
61+
private string? loadError = null;
62+
private string? submitError = null;
63+
64+
protected override async Task OnInitializedAsync()
65+
{
66+
await LoadCommentsAsync();
67+
if (!string.IsNullOrEmpty(UserName))
68+
{
69+
// newCommentRequest = newCommentRequest with { UserName = UserName };
70+
newCommentRequest.UserName = UserName; // Ensure username is set
71+
}
72+
}
73+
74+
protected override async Task OnParametersSetAsync()
75+
{
76+
// Reload comments if PostId changes
77+
// This might happen if the modal is reused without full disposal
78+
if (PostId > 0 && (comments == null || comments.All(c => c.PostId != PostId)))
79+
{
80+
await LoadCommentsAsync();
81+
}
82+
83+
// Update username for new comment if it changes
84+
if (!string.IsNullOrEmpty(UserName) && newCommentRequest.UserName != UserName)
85+
{
86+
// newCommentRequest = newCommentRequest with { UserName = UserName };
87+
newCommentRequest.UserName = UserName; // Ensure username is set
88+
}
89+
}
90+
91+
private async Task LoadCommentsAsync()
92+
{
93+
if (PostId <= 0) return;
94+
95+
isLoading = true;
96+
loadError = null;
97+
StateHasChanged();
98+
try
99+
{
100+
comments = await ApiService.GetCommentsAsync(PostId);
101+
}
102+
catch (Exception ex)
103+
{
104+
Console.Error.WriteLine($"Error loading comments: {ex.Message}");
105+
loadError = "댓글을 불러오는 데 실패했습니다.";
106+
}
107+
finally
108+
{
109+
isLoading = false;
110+
StateHasChanged();
111+
}
112+
}
113+
114+
private async Task HandleCommentSubmitAsync()
115+
{
116+
if (isSubmitting || PostId <= 0 || string.IsNullOrWhiteSpace(UserName))
117+
{
118+
submitError = "사용자 이름이 필요합니다."; // Should ideally not happen if UserName is passed correctly
119+
return;
120+
}
121+
122+
isSubmitting = true;
123+
submitError = null;
124+
StateHasChanged();
125+
126+
// newCommentRequest = newCommentRequest with { UserName = UserName }; // Ensure username is set
127+
newCommentRequest.UserName = UserName; // Ensure username is set
128+
129+
try
130+
{
131+
var createdComment = await ApiService.CreateCommentAsync(PostId, newCommentRequest);
132+
if (createdComment != null)
133+
{
134+
comments ??= [];
135+
comments.Add(createdComment);
136+
newCommentRequest = new(UserName, ""); // Reset content, keep username
137+
}
138+
else
139+
{
140+
submitError = "댓글 등록에 실패했습니다.";
141+
}
142+
}
143+
catch (Exception ex)
144+
{
145+
Console.Error.WriteLine($"Error submitting comment: {ex.Message}");
146+
submitError = "댓글 등록 중 오류가 발생했습니다.";
147+
}
148+
finally
149+
{
150+
isSubmitting = false;
151+
StateHasChanged();
152+
}
153+
}
154+
155+
private string FormatDate(DateTime? date)
156+
{
157+
// More detailed format for comments might be nice
158+
return date?.ToString("yyyy-MM-dd HH:mm") ?? string.Empty;
159+
}
160+
}

0 commit comments

Comments
 (0)