Files

1063 lines
47 KiB
TeX

% Options for packages loaded elsewhere
\PassOptionsToPackage{unicode}{hyperref}
\PassOptionsToPackage{hyphens}{url}
\documentclass[
]{article}
\usepackage{xcolor}
\usepackage{amsmath,amssymb}
\setcounter{secnumdepth}{-\maxdimen} % remove section numbering
\usepackage{iftex}
\ifPDFTeX
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{textcomp} % provide euro and other symbols
\else % if luatex or xetex
\usepackage{unicode-math} % this also loads fontspec
\defaultfontfeatures{Scale=MatchLowercase}
\defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
\fi
\usepackage{lmodern}
\ifPDFTeX\else
% xetex/luatex font selection
\fi
% Use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
\IfFileExists{microtype.sty}{% use microtype if available
\usepackage[]{microtype}
\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
}{}
\makeatletter
\@ifundefined{KOMAClassName}{% if non-KOMA class
\IfFileExists{parskip.sty}{%
\usepackage{parskip}
}{% else
\setlength{\parindent}{0pt}
\setlength{\parskip}{6pt plus 2pt minus 1pt}}
}{% if KOMA class
\KOMAoptions{parskip=half}}
\makeatother
\usepackage{color}
\usepackage{fancyvrb}
\newcommand{\VerbBar}{|}
\newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
% Add ',fontsize=\small' for more characters per line
\newenvironment{Shaded}{}{}
\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}}
\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\BuiltInTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{#1}}
\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{#1}}}
\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}}
\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}}
\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{#1}}}
\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
\newcommand{\ExtensionTok}[1]{#1}
\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{#1}}
\newcommand{\ImportTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{\textbf{#1}}}
\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
\newcommand{\NormalTok}[1]{#1}
\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}}
\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}}
\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{#1}}
\newcommand{\RegionMarkerTok}[1]{#1}
\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}}
\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}}
\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\usepackage{longtable,booktabs,array}
\newcounter{none} % for unnumbered tables
\usepackage{calc} % for calculating minipage widths
% Correct order of tables after \paragraph or \subparagraph
\usepackage{etoolbox}
\makeatletter
\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
\makeatother
% Allow footnotes in longtable head/foot
\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
\makesavenoteenv{longtable}
\setlength{\emergencystretch}{3em} % prevent overfull lines
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\usepackage{bookmark}
\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
\urlstyle{same}
\hypersetup{
pdftitle={manuscript},
hidelinks,
pdfcreator={LaTeX via pandoc}}
\title{manuscript}
\author{}
\date{}
\begin{document}
\maketitle
{
\setcounter{tocdepth}{3}
\tableofcontents
}
\section{Architektura ASP.NET Core i Ekosystem
Blazor}\label{architektura-asp.net-core-i-ekosystem-blazor}
Programiści aplikacji WinForms i WPF od lat budują interfejsy w oparciu
o stanowe kontrolki użytkownika i wzorzec MVVM. Nowoczesny ekosystem
platformy .NET pozwala ujednolicić i zmodernizować ten proces za pomocą
frameworka Blazor, działającego na fundamentach ASP.NET Core. Blazor
opiera się na języku C\# zamiast na JavaScripcie, oferując spójny,
oparty na komponentach i zdarzeniach model programowania, który z
perspektywy dewelopera aplikacji desktopowych będzie wysoce znajomy.
\paragraph{Mapowanie pojęć: Desktop vs
Web}\label{mapowanie-pojux119ux107-desktop-vs-web}
Migracja do nowej platformy wymaga redefinicji klasycznych wzorców.
Tabela poniżej przedstawia bezpośrednie zestawienie paradygmatów:
{\def\LTcaptype{none} % do not increment counter
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Koncepcja Desktop (WPF/WinForms) & Odpowiednik w ASP.NET Core / Blazor &
Opis zmiany \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\textbf{UserControl} & \textbf{Komponent Razor} & Logika i widok są
hermetyzowane w reużywalnych plikach \texttt{.razor}. Renderowanie
interfejsu opiera się na wydajnym mechanizmie różnicowym
(diff-based). \\
\textbf{\texttt{app.config} / \texttt{web.config}} &
\textbf{\texttt{appsettings.json}} & Przejście na lekką, hierarchiczną
konfigurację w formacie JSON, która wspiera nadpisywanie zmiennych dla
różnych środowisk (Development, Production). \\
\textbf{Wstrzykiwanie zależności (DI)} & \textbf{Wbudowany kontener DI}
& Standaryzacja DI jako natywnego mechanizmu frameworka ASP.NET Core,
który w .NET 4.8 często wymagał zewnętrznych bibliotek. \\
\textbf{Cykl życia okna} & \textbf{Cykl życia żądania / Potok
Middleware} & W aplikacjach webowych żądania HTTP i stan logiki są
przetwarzane przez konfigurowalną sekwencję komponentów (Middleware). \\
\end{longtable}
}
\paragraph{Architektura systemowa: Zarządzanie stanem i piaskownica
środowiska}\label{architektura-systemowa-zarzux105dzanie-stanem-i-piaskownica-ux15brodowiska}
Główną różnicą między architekturą „Heavy Client'' (WPF) a nowoczesnymi
rozwiązaniami webowymi jest sposób zarządzania stanem, który w
środowisku internetowym bywa rozproszony i asynchroniczny. W zależności
od wymagań aplikacji, framework Blazor udostępnia różne modele hostingu:
\begin{itemize}
\tightlist
\item
\textbf{Blazor Server:} Komponenty wykonywane są na serwerze w ramach
aplikacji ASP.NET Core. Aktualizacje UI, obsługa zdarzeń i wywołania
JavaScriptu przesyłane są asynchronicznie za pomocą protokołu
WebSockets i połączenia SignalR. Stan skojarzony z każdym połączonym
klientem (tzw. obwód, ang. \emph{circuit}) znajduje się w pamięci
serwera. Gwarantuje to pełną kompatybilność z API .NET i bezpośredni
dostęp do zasobów serwera.
\item
\textbf{Blazor WebAssembly:} Środowisko uruchomieniowe .NET i
komponenty pobierane są do przeglądarki klienta, gdzie działają
bezpośrednio w oparciu o technologię WebAssembly. Znacząco różni się
to od rozwiązań desktopowych -- z uwagi na ograniczenia bezpieczeństwa
(piaskownica przeglądarki), komponenty nie mają bezpośredniego dostępu
do lokalnego systemu plików czy bazy danych. Wymagana jest wtedy
pośrednia komunikacja asynchroniczna z serwerowymi API (np. REST).
\end{itemize}
\paragraph{Strategia Migracji: Podejście Blazor
Hybrid}\label{strategia-migracji-podejux15bcie-blazor-hybrid}
Dla doświadczonych zespołów rozwijających systemy desktopowe
przepisywanie całych aplikacji od zera (tzw. Big Bang rewrite) narzuca
ogromne ryzyko i koszty operacyjne. Naturalną ścieżką ewolucji,
realizującą strategię \emph{In-place upgrade}, jest wykorzystanie
architektury \textbf{Blazor Hybrid}.
W modelu hybrydowym komponenty Razor nie działają poprzez WebAssembly.
Zamiast tego są uruchamiane natywnie wewnątrz istniejącej aplikacji WPF
lub WinForms, współpracując bezpośrednio z osadzonym oknem przeglądarki
za pośrednictwem lokalnej kontrolki \texttt{BlazorWebView}.
\textbf{Wnioski implementacyjne z modelu hybrydowego:}
\begin{itemize}
\tightlist
\item
\textbf{Pełny dostęp do zasobów:} W przeciwieństwie do ograniczeń
przeglądarki (sandbox), aplikacje Blazor Hybrid działają z natywnymi
prawami systemu operacyjnego. Zapewniają pełny dostęp do sprzętu i
zasobów systemu plików za pośrednictwem klasycznych API .NET.
\item
\textbf{Sukcesywne przenoszenie UI:} Programiści mogą krok po kroku
przepisywać ekrany z XAML na komponenty Razor. Utworzone w ten sposób
widoki mogą być później łatwo współdzielone w innych środowiskach, na
przykład w czystych projektach webowych na Blazor WebAssembly.
\end{itemize}
\paragraph{Laboratorium kodu: Inicjalizacja usług i
potoku}\label{laboratorium-kodu-inicjalizacja-usux142ug-i-potoku}
W platformie ASP.NET Core tradycyjne pliki \texttt{Global.asax} zostają
zastąpione zwięzłym API w pliku \texttt{Program.cs}. Obiekt
\texttt{WebApplicationBuilder} przejmuje pełną odpowiedzialność za
inicjalizację kontenera zależności (DI), logowanie i konfigurację hosta.
Poniżej przedstawiono standardowy proces rejestracji komponentów
webowych:
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{var builder = WebApplication.CreateBuilder(args);}
\NormalTok{// 1. Inicjalizacja kontenera DI i zasilenie go konfiguracją z appsettings.json}
\NormalTok{builder.Services.AddRazorComponents()}
\NormalTok{ .AddInteractiveServerComponents();}
\NormalTok{var app = builder.Build();}
\NormalTok{// 2. Potok Middleware {-} definicja zachowań w zależności od środowiska}
\NormalTok{if (!app.Environment.IsDevelopment())}
\NormalTok{\{}
\NormalTok{ app.UseExceptionHandler("/Error", createScopeForErrors: true);}
\NormalTok{ app.UseHsts(); // Wymuszenie protokołu HTTPS}
\NormalTok{\}}
\NormalTok{app.UseHttpsRedirection();}
\NormalTok{app.UseStaticFiles(); // Middleware serwujący pliki statyczne}
\NormalTok{app.UseAntiforgery();}
\NormalTok{// 3. Mapowanie ruterów dla stworzonych komponentów}
\NormalTok{app.MapRazorComponents\textless{}App\textgreater{}()}
\NormalTok{ .AddInteractiveServerRenderMode();}
\NormalTok{app.Run();}
\end{Highlighting}
\end{Shaded}
W nowych środowiskach doświadczenie nabyte w pracy z MVVM bezpośrednio
procentuje. Reaktywność interfejsu (znana z interfejsu
\texttt{INotifyPropertyChanged}) jest w Blazor ustandaryzowana przez
wywoływanie zdarzeń cyklu życia, takich jak metoda
\texttt{StateHasChanged}, podczas gdy dostęp do serwisów biznesowych
realizowany jest po prostu przez wbudowaną dyrektywę \texttt{@inject}.
Umożliwia to zachowanie rygorystycznej separacji pomiędzy warstwą logiki
biznesowej a nową, wieloplatformową warstwą prezentacji.
\section{Fundamenty Architektury -- Od Desktopu do ASP.NET Core i
Blazor}\label{fundamenty-architektury-od-desktopu-do-asp.net-core-i-blazor}
Przejście z klasycznych aplikacji desktopowych (WinForms / WPF) w
środowisku .NET Framework 4.8 do nowoczesnego ekosystemu webowego i
hybrydowego w oparciu o ASP.NET Core oraz framework Blazor wymaga
przyswojenia nowych fundamentów architektonicznych. W architekturze
"Heavy Client" punktem wyjścia była pętla zdarzeń systemu Windows oraz
scentralizowany stan w pamięci aplikacji. Nowoczesne fundamenty .NET
opierają się z kolei na asynchronicznym potoku przetwarzania żądań,
wbudowanym mechanizmie wstrzykiwania zależności oraz modułowym hoście.
\paragraph{1. Mapowanie pojęć: Desktop vs ASP.NET
Core}\label{mapowanie-pojux119ux107-desktop-vs-asp.net-core}
Migracja architektury wymaga zrozumienia, jak klasyczne mechanizmy z
aplikacji okienkowych przekładają się na usługi ASP.NET Core:
{\def\LTcaptype{none} % do not increment counter
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Koncepcja Desktop (WPF/WinForms) & Odpowiednik w ASP.NET Core / Blazor &
Opis zmiany architektonicznej \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\textbf{\texttt{App.config} / Settings} &
\textbf{\texttt{appsettings.json}} & Przejście z XML na lekką,
hierarchiczną konfigurację w formacie JSON, która natywnie obsługuje
różne środowiska (np. \texttt{Development}, \texttt{Production}) i
zmienne środowiskowe. \\
\textbf{Brak natywnego DI / Zewnętrzne kontenery} & \textbf{Wbudowany
kontener DI} & Standaryzacja mechanizmu Dependency Injection (DI) na
poziomie frameworka. Usługi rejestruje się globalnie, co całkowicie
zastępuje wzorce takie jak Service Locator z czasów .NET 4.8. \\
\textbf{\texttt{App.xaml.cs} (Cykl życia)} & \textbf{\texttt{Program.cs}
(Minimal Host)} & Kod startowy aplikacji został uproszczony do
konfiguracji obiektu \texttt{WebApplicationBuilder}, który inicjalizuje
usługi i potok przetwarzania. \\
\textbf{Zdarzenia okna / Global.asax} & \textbf{Potok Middleware} &
Przetwarzanie żądań jest realizowane przez elastyczny łańcuch
komponentów (Middleware), z których każdy może zmodyfikować żądanie lub
skrócić potok. \\
\end{longtable}
}
\paragraph{2. Architektura systemowa: Potok Middleware, Host i
Zarządzanie
Stanem}\label{architektura-systemowa-potok-middleware-host-i-zarzux105dzanie-stanem}
Aplikacje oparte na ASP.NET Core są zbudowane wokół koncepcji
\textbf{Hosta}. Host ten enkapsuluje wszystkie zasoby systemu, takie jak
serwer HTTP (np. wieloplatformowy serwer Kestrel), mechanizmy logowania,
kontener usług (DI) oraz konfigurację. W przeciwieństwie do
monolitycznej natury aplikacji WPF, platforma webowa wymusza
rozproszenie stanu.
W rozwiązaniach opartych na \textbf{Blazor Server}, komponenty i logika
biznesowa wykonują się po stronie serwera wewnątrz aplikacji ASP.NET
Core. Stan każdego użytkownika jest przechowywany w pamięci jako tzw.
obwód (ang. \emph{circuit}), a aktualizacje interfejsu (DOM) przesyłane
są w czasie rzeczywistym za pomocą połączenia SignalR.
Z kolei w modelu \textbf{Blazor WebAssembly}, środowisko uruchomieniowe
.NET jest pobierane bezpośrednio do przeglądarki. Fundamentalną zmianą
dla deweloperów desktopowych jest tutaj bezpieczeństwo: kod działa w
zamkniętej piaskownicy przeglądarki (sandbox), co odcina bezpośredni
dostęp do systemu plików czy rejestru Windows. Komunikacja ze światem
zewnętrznym odbywa się w tym modelu wyłącznie asynchronicznie poprzez
zabezpieczone serwerowe interfejsy API.
Pojęcie pętli zdarzeń znane z WinForms zostaje zastąpione przez
\textbf{potok Middleware}. Każde żądanie przechodzi przez kolejne
oprogramowanie pośredniczące, które wykonuje wybrane operacje (np.
autoryzacja, routing, logowanie) i decyduje, czy przekazać kontekst
(\texttt{HttpContext}) do kolejnego elementu.
\paragraph{3. Laboratorium kodu: Inicjalizacja Hosta i Konfiguracja
Potoku}\label{laboratorium-kodu-inicjalizacja-hosta-i-konfiguracja-potoku}
W nowoczesnym środowisku .NET inicjalizacja aplikacji odbywa się z
poziomu jednego pliku \texttt{Program.cs}. Znane z aplikacji WPF ręczne
instancjonowanie okien i wstrzykiwanie widoków zastępuje rejestracja
komponentów systemowych.
Analiza przypadku -- Rejestracja usług i middleware dla aplikacji
Blazor:
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{var builder = WebApplication.CreateBuilder(args);}
\NormalTok{// Wstrzykiwanie Zależności (DI) {-} nowoczesny odpowiednik wzorca z WPF}
\NormalTok{// Rejestracja własnego kontekstu bazy danych oraz usług komponentów Blazor}
\NormalTok{builder.Services.AddDbContextFactory\textless{}AppDbContext\textgreater{}(options =\textgreater{} }
\NormalTok{ options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));}
\NormalTok{builder.Services.AddRazorComponents()}
\NormalTok{ .AddInteractiveServerComponents(); // Wskazanie modelu Blazor Server}
\NormalTok{// Zbudowanie Hosta (Host enkapsuluje serwer Kestrel, logowanie i konfigurację)}
\NormalTok{var app = builder.Build();}
\NormalTok{// Architektura Middleware {-} definiowanie potoku}
\NormalTok{// Zabezpieczenie środowiskowe: szczegółowe logi błędu tylko w Development}
\NormalTok{if (!app.Environment.IsDevelopment()) }
\NormalTok{\{}
\NormalTok{ app.UseExceptionHandler("/Error", createScopeForErrors: true);}
\NormalTok{ app.UseHsts(); }
\NormalTok{\}}
\NormalTok{app.UseHttpsRedirection();}
\NormalTok{app.UseStaticFiles(); // Middleware udostępniający zasoby (CSS, pliki JS)}
\NormalTok{app.UseAntiforgery();}
\NormalTok{// Mapowanie komponentów {-} serwer zaczyna reagować na ruting UI}
\NormalTok{app.MapRazorComponents\textless{}App\textgreater{}()}
\NormalTok{ .AddInteractiveServerRenderMode();}
\NormalTok{app.Run(); // Uruchomienie pętli nasłuchującej}
\end{Highlighting}
\end{Shaded}
W powyższym fragmencie wykorzystano również natywny mechanizm
wstrzykiwania zależności (\texttt{@inject} w widokach), który
standaryzuje to, co w aplikacjach WPF osiągano za pomocą rozwiązań
zewnętrznych przy architekturze MVVM.
\paragraph{4. Strategia Migracji: Podejście Blazor
Hybrid}\label{strategia-migracji-podejux15bcie-blazor-hybrid-1}
Ze względu na drastyczne różnice w dostępie do zasobów maszyny
(piaskownica webowa vs natywne uprawnienia Windows) oraz zmianę
paradygmatu zarządzania stanem, natychmiastowe przepisanie dużej
aplikacji WPF do czystego środowiska Web bywa kosztowne i ryzykowne. Z
pomocą przychodzi tu \textbf{Blazor Hybrid}.
W modelu hybrydowym, komponenty interfejsu (Razor) nie są uruchamiane na
serwerze ani za pośrednictwem WebAssembly. Zamiast tego wykonują się one
natywnie wewnątrz istniejącej aplikacji desktopowej (WPF, WinForms lub
.NET MAUI) wykorzystując wbudowaną kontrolkę \texttt{BlazorWebView}.
\textbf{Wnioski implementacyjne dla Blazor Hybrid:}
\begin{itemize}
\tightlist
\item
\textbf{Bezpieczeństwo i Dostęp:} Aplikacje hybrydowe omijają
restrykcje piaskownicy przeglądarki. Interfejs jest renderowany w
technologiach webowych (HTML/CSS), ale kod logiki biznesowej C\#
współdzieli natywny proces aplikacji WPF, oferując bezpośredni i pełny
dostęp do systemu plików, lokalnych baz danych i sprzętu za
pośrednictwem zwykłego API platformy .NET.
\item
\textbf{Współdzielenie komponentów:} Przyjęcie tego modelu pozwala
zespołom programistycznym stopniowo "odcinać" stare widoki XAML i
zastępować je komponentami Razor w obrębie tej samej instancji
programu. Nowo powstałe kontrolki można docelowo przenieść bez zmian
do aplikacji Blazor WebAssembly, co realizuje założenia ewolucyjnej
modernizacji systemów klasy Enterprise.
\end{itemize}
\section{Fundamenty Architektury -- Koncepcja Generic
Host}\label{fundamenty-architektury-koncepcja-generic-host}
W klasycznych aplikacjach desktopowych (WPF, WinForms) cykl życia
programu był nierozerwalnie związany z głównym oknem interfejsu
użytkownika oraz pętlą zdarzeń (np. plik \texttt{App.xaml.cs} i metoda
\texttt{Application.Run()}). Modernizacja architektury i przejście do
nowoczesnego ekosystemu .NET wymaga odłączenia logiki uruchomieniowej od
warstwy prezentacji. Służy temu mechanizm \textbf{Hosta} (ang.
\emph{Host}), który stanowi ewolucyjny fundament zarządzania zasobami i
cyklem życia nowoczesnych aplikacji.
Na etapie uruchamiania, nowoczesna aplikacja ASP.NET Core buduje Hosta,
który w jednym miejscu enkapsuluje wszystkie kluczowe zasoby systemu,
takie jak serwer HTTP, oprogramowanie pośredniczące (Middleware),
mechanizmy logowania, usługi wstrzykiwania zależności (DI) oraz
konfigurację środowiska.
\paragraph{1. Mapowanie pojęć: Od Desktopu do
Hosta}\label{mapowanie-pojux119ux107-od-desktopu-do-hosta}
Zrozumienie koncepcji Hosta wymaga zmapowania mechanizmów znanych z .NET
Framework 4.8 na ich nowoczesne odpowiedniki bazujące na wzorcu
\textbf{Generic Host}:
{\def\LTcaptype{none} % do not increment counter
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Koncepcja Desktop (WPF/WinForms) & Odpowiednik w architekturze .NET
(Host) & Opis zmiany architektonicznej \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\textbf{\texttt{Application.Startup} / \texttt{App.xaml.cs}} &
\textbf{\texttt{Program.cs} / \texttt{WebApplicationBuilder}} &
Inicjalizacja całej aplikacji przeniesiona jest do zwięzłego interfejsu
budowniczego (Builder), który ujednolica konfigurację. \\
\textbf{Pętla komunikatów UI (\texttt{Dispatcher})} &
\textbf{\texttt{app.Run()} / Serwer HTTP (Kestrel)} & Zamiast
nasłuchiwać zdarzeń myszy i klawiatury, Host uruchamia serwer Kestrel
nasłuchujący asynchronicznych żądań HTTP. \\
\textbf{Klasy statyczne / Service Locator} & \textbf{Wbudowany kontener
DI} & Host automatycznie zarządza cyklem życia rejestrowanych usług
(Singleton, Scoped, Transient), zastępując statyczne menedżery czy
zewnętrzne kontenery IoC. \\
\textbf{Wątki w tle (BackgroundWorker)} &
\textbf{\texttt{IHostedService} (Scenariusze Non-Web)} & Długotrwałe
procesy w tle są zarządzane i bezpiecznie zamykane w ramach cyklu życia
Hosta jako tzw. Hosted Services. \\
\end{longtable}
}
\paragraph{2. Architektura systemowa: Rodzaje Hostów i Generic
Host}\label{architektura-systemowa-rodzaje-hostuxf3w-i-generic-host}
W historii platformy ASP.NET Core istniało kilka wariantów hostowania, w
tym przestarzały już \texttt{WebHost} zachowany jedynie dla
kompatybilności wstecznej. Obecnym standardem systemowym jest
\textbf{.NET Generic Host} (często wykorzystywany poprzez klasę
\texttt{WebApplication}, zwaną \emph{Minimal Host}).
Klasa \texttt{WebApplication} zachowuje się analogicznie do klasycznego
\emph{Generic Host}, eksponując te same interfejsy, ale drastycznie
redukuje ilość kodu wymaganego do jej konfiguracji.
Obiekt \texttt{WebApplicationBuilder}, podczas swojego tworzenia
(\texttt{Build()}), nakłada na aplikację zestaw domyślnych,
zoptymalizowanych ustawień:
\begin{itemize}
\tightlist
\item
Integruje i uruchamia wieloplatformowy serwer Kestrel.
\item
Ładuje hierarchiczną konfigurację bazującą na plikach
\texttt{appsettings.json}, zmiennych środowiskowych i argumentach
wiersza poleceń.
\item
Konfiguruje dostawców logowania (kierując logi domyślnie m.in. do
konsoli i okna Debug).
\end{itemize}
\paragraph{3. Laboratorium kodu: Inicjalizacja Minimal
Host}\label{laboratorium-kodu-inicjalizacja-minimal-host}
W nowoczesnych rozwiązaniach C\# punktem wejścia do aplikacji jest
wysoce zoptymalizowany plik \texttt{Program.cs}. Dla programistów
przenoszących kod z WPF, to miejsce stanowi odpowiednik jednoczesnego
konfigurowania App.config, kontenera IoC i okna głównego.
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{// 1. Inicjalizacja wzorca Builder dla Hosta}
\NormalTok{var builder = WebApplication.CreateBuilder(args);}
\NormalTok{// 2. Rejestracja usług w kontenerze Dependency Injection Hosta}
\NormalTok{// Odpowiednik rejestrowania ViewModeli i serwisów w zewnętrznych bibliotekach (np. Autofac) w WPF}
\NormalTok{builder.Services.AddControllersWithViews(); }
\NormalTok{builder.Services.AddRazorPages();}
\NormalTok{// 3. Utworzenie instancji Hosta ze zdefiniowanymi usługami}
\NormalTok{var app = builder.Build();}
\NormalTok{// 4. Konfiguracja potoku żądań (Middleware) w zbudowanym Hoście}
\NormalTok{if (!app.Environment.IsDevelopment())}
\NormalTok{\{}
\NormalTok{ app.UseExceptionHandler("/Error"); // Centralne zarządzanie błędami}
\NormalTok{ app.UseHsts();}
\NormalTok{\}}
\NormalTok{app.UseHttpsRedirection();}
\NormalTok{app.UseStaticFiles();}
\NormalTok{app.UseRouting();}
\NormalTok{app.UseAuthorization();}
\NormalTok{// 5. Uruchomienie pętli głównej Hosta (serwer rozpoczyna nasłuchiwanie)}
\NormalTok{app.Run();}
\end{Highlighting}
\end{Shaded}
\emph{Analiza przypadku:} Wywołanie metody \texttt{Run()} usypia główny
wątek, pozwalając serwerowi HTTP (lub usługom w tle) przejąć kontrolę
nad procesem. Stanowi to bezpośredni odpowiednik uruchomienia głównej
pętli zdarzeń w natywnej aplikacji Windows.
\paragraph{4. Strategia Migracji: Separacja Logiki i Scenariusze Non-Web
(Background
Tasks)}\label{strategia-migracji-separacja-logiki-i-scenariusze-non-web-background-tasks}
Jednym z najważniejszych wniosków implementacyjnych dla zespołów
migrujących systemy z Windows Forms i WPF jest fakt, że nowoczesny
\textbf{Generic Host pozwala na tworzenie aplikacji, które nie są
aplikacjami webowymi}.
Architektura Generic Host ułatwia innym typom aplikacji (np. konsolowym
czy procesom backendowym) korzystanie z wieloprzekrojowych rozszerzeń
frameworka (takich jak logowanie, wstrzykiwanie zależności,
konfiguracja, czy zarządzanie cyklem życia).
Zamiast utrzymywać skomplikowaną logikę biznesową polegającą na
odpytywaniu bazy danych (polling) w ukrytym oknie WPF czy za pomocą
klasycznego obiektu \texttt{Timer} uwięzionego w kodzie interfejsu
(Code-Behind), strategia migracji zakłada ekstrakcję tego kodu. Logikę
przenosi się do klas implementujących interfejs \texttt{IHostedService}
uruchamianych wewnątrz lekkiego procesu opartego na
\texttt{.NET\ Generic\ Host}. Zapewnia to architekturę gotową do pracy
jako rozproszony mikrokonsument lub usługa systemu Windows, gwarantując
profesjonalne zarządzanie błędami i pełną separację od warstwy UI.
\section{Zarządzanie cyklem życia w architekturze Generic
Host}\label{zarzux105dzanie-cyklem-ux17cycia-w-architekturze-generic-host}
W klasycznych aplikacjach Windows (WinForms, WPF) cykl życia programu
był ściśle sprzężony z głównym oknem interfejsu użytkownika oraz pętlą
zdarzeń systemu operacyjnego (np. \texttt{Application.Run()}).
Nowoczesny ekosystem platformy .NET rozdziela warstwę prezentacji od
logiki uruchomieniowej aplikacji poprzez wykorzystanie wzorca
\textbf{Generic Host}. W tej architekturze to obiekt Hosta przejmuje
pełną odpowiedzialność za zarządzanie całkowitym czasem życia aplikacji,
inicjalizację procesów w tle oraz kontrolę pamięci.
\paragraph{Mapowanie pojęć: Desktop vs ASP.NET
Core}\label{mapowanie-pojux119ux107-desktop-vs-asp.net-core-1}
Zrozumienie zmiany paradygmatu wymaga zmapowania mechanizmów
desktopowych na scentralizowany system oparty o Generic Host i wbudowane
kontenery odwrócenia kontroli (IoC):
{\def\LTcaptype{none} % do not increment counter
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Koncepcja Desktop (WPF/WinForms) & Odpowiednik w architekturze Generic
Host & Opis zmiany architektonicznej \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\textbf{Zdarzenia \texttt{App.Startup} / \texttt{App.Exit}} &
\textbf{Cykl życia Hosta (App Lifetime)} & Przeniesienie
odpowiedzialności za start i zamknięcie programu z klasy Application na
wbudowany mechanizm zarządzania czasem życia aplikacji w obiekcie
Host. \\
\textbf{Pętla komunikatów UI} & \textbf{Serwer HTTP / Żądania
asynchroniczne} & Działanie programu nie zależy od nasłuchiwania
myszy/klawiatury, lecz od Hosta utrzymującego proces nasłuchujący,
takiego jak serwer Kestrel. \\
\textbf{Ręczne wywoływanie \texttt{Dispose()}} & \textbf{Kontener
Wstrzykiwania Zależności (DI)} & Host zarządza inicjalizacją i
zwalnianiem obiektów. Na przykład kontrolery Web API są aktywowane i
poddawane utylizacji dla każdego pojedynczego żądania (per request
basis). \\
\textbf{\texttt{BackgroundWorker} / \texttt{DispatcherTimer}} &
\textbf{Usługi \texttt{IHostedService}} & Generic Host integruje zadania
w tle jako odizolowane procesy współdzielące wspólny cykl życia Hosta,
niezależne od warstwy sieciowej. \\
\end{longtable}
}
\paragraph{Architektura systemowa: Centralizacja zasobów i potok
żądań}\label{architektura-systemowa-centralizacja-zasobuxf3w-i-potok-ux17cux105daux144}
Na etapie rozruchu, Host enkapsuluje wszystkie zasoby systemowe,
włączając w to kontener wstrzykiwania zależności (DI), konfigurację
środowiskową, logowanie i docelowo potok oprogramowania pośredniczącego
(Middleware).
Fundamentalną różnicą względem aplikacji desktopowych jest rozproszenie
i kaskadowość cyklu życia. Poza globalnym cyklem życia samego Hosta
(start i bezpieczne wyłączenie procesu), platforma wprowadza bardzo
rygorystyczny cykl życia pojedynczych żądań sieciowych oraz obiektów
wewnątrz nich:
\begin{itemize}
\tightlist
\item
\textbf{Komponenty oparte na konwencjach (Convention-based
Middleware):} Instancjonowane tylko raz przy uruchomieniu aplikacji i
funkcjonujące jako Singleton przez cały cykl życia Hosta.
\item
\textbf{Komponenty implementujące interfejs \texttt{IMiddleware}:}
Fabryka instancjonuje je oddzielnie dla każdego nadchodzącego żądania
HTTP (tzw. cykl \emph{Scoped}), co pozwala bezpiecznie wstrzykiwać do
nich lokalne usługi i kontekst bazy danych.
\item
\textbf{Scenariusze non-web (Usługi w tle):} Host dostarcza
uniwersalny interfejs umożliwiający korzystanie z globalnego
zarządzania cyklem życia oraz logowania w aplikacjach, które wcale nie
odbierają żądań HTTP (np. mikrousługi wykonujące asynchroniczną
analizę danych).
\end{itemize}
\paragraph{Laboratorium kodu: Inicjalizacja cyklu życia usług i
żądań}\label{laboratorium-kodu-inicjalizacja-cyklu-ux17cycia-usux142ug-i-ux17cux105daux144}
Poniżej przedstawiono proces uruchamiania minimalnego Hosta
(\texttt{WebApplication}) wraz z definicją cyklu życia jego usług:
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{var builder = WebApplication.CreateBuilder(args);}
\NormalTok{// 1. Zarządzanie cyklem życia przez kontener DI}
\NormalTok{// Rejestracja kontrolerów, które zostaną aktywowane dla każdego żądania }
\NormalTok{builder.Services.AddControllers();}
\NormalTok{// Rejestracja usług biznesowych}
\NormalTok{// W WPF wymagałoby to ręcznego cyklu życia lub np. zewnętrznego Autofac}
\NormalTok{builder.Services.AddScoped\textless{}IMyBusinessLogic, MyBusinessLogic\textgreater{}(); // Czas życia ograniczony do jednego żądania}
\NormalTok{var app = builder.Build();}
\NormalTok{// 2. Cykl życia potoku przetwarzania (Middleware)}
\NormalTok{// Rejestracja inline middleware, który przekaże kontrolę dalej}
\NormalTok{app.Use(async (context, next) =\textgreater{} \{}
\NormalTok{ // Rozpoczęcie przetwarzania}
\NormalTok{ await next();}
\NormalTok{ // Powrót w potoku po obsłużeniu cyklu życia danego żądania}
\NormalTok{\});}
\NormalTok{// Zmapowanie kontrolerów }
\NormalTok{app.MapControllers();}
\NormalTok{// 3. Uruchomienie głównej pętli Hosta}
\NormalTok{// Metoda Run() podtrzymuje proces i zarządza sygnałami zakończenia działania}
\NormalTok{app.Run();}
\end{Highlighting}
\end{Shaded}
\paragraph{Strategia Migracji: Zabezpieczenie zadań w tle (Background
Tasks)}\label{strategia-migracji-zabezpieczenie-zadaux144-w-tle-background-tasks}
W projektach bazujących na WinForms lub WPF, programiści bardzo często
wykorzystują niewidzialne okna (tzw. okna narzędziowe) lub komponenty
typu \texttt{Timer} powiązane z warstwą prezentacji (tzw. pętlą UI) w
celu cyklicznego odpytywania bazy danych, synchronizacji plików lub
wykonywania innej logiki biznesowej. Podejście to uniemożliwia proste
przejście na architekturę backendową lub chmurową.
Wzorzec \textbf{Generic Host} stanowi naturalną strategię modernizacji
dla takich mechanizmów, ponieważ z założenia wspomaga tworzenie procesów
tła dla scenariuszy nie-webowych (non-web scenarios). Zamiast polegać na
warstwie prezentacji, programiści powinni dokonać ekstrakcji cyklicznych
procedur do odizolowanych klas implementujących interfejs
\texttt{IHostedService}. W ten sposób zarządzanie całkowitym cyklem
uruchomienia, podtrzymania i, co niezwykle istotne, przewidywalnego i
prawidłowego zamknięcia programu zostaje w pełni zestandaryzowane i
zintegrowane w rdzeniu nowoczesnej platformy .NET. Taki kod można
następnie użyć zarówno jako niezależnej usługi w środowisku Blazor
Server, jak i mikroserwisu pracującego bez żadnego interfejsu
graficznego.
\section{Wbudowane wstrzykiwanie zależności (DI) w architekturze Generic
Host}\label{wbudowane-wstrzykiwanie-zaleux17cnoux15bci-di-w-architekturze-generic-host}
W klasycznych systemach tworzonych w .NET Framework 4.8 wstrzykiwanie
zależności (Dependency Injection) często wymagało integracji
zewnętrznych bibliotek (takich jak Autofac, Ninject czy Unity) i
ręcznego zarządzania cyklem życia komponentów. W nowoczesnym ekosystemie
.NET mechanizm ten został zestandaryzowany i głęboko zintegrowany z
architekturą opartą na wzorcu \textbf{Generic Host}. Platforma ASP.NET
Core zawiera wbudowany framework DI, który udostępnia skonfigurowane
usługi we wszystkich warstwach aplikacji. Zrozumienie, jak Generic Host
enkapsuluje i zarządza zależnościami, stanowi fundament projektowania
skalowalnych rozwiązań wieloplatformowych.
\paragraph{1. Mapowanie pojęć: Desktop vs ASP.NET Core
DI}\label{mapowanie-pojux119ux107-desktop-vs-asp.net-core-di}
Implementacja wbudowanego mechanizmu DI wymusza zmianę podejścia do
tworzenia i dostarczania usług biznesowych:
{\def\LTcaptype{none} % do not increment counter
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Koncepcja Desktop (WPF/WinForms) & Odpowiednik w .NET (Generic Host) &
Opis zmiany architektonicznej \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\textbf{Zewnętrzny kontener IoC} & \textbf{Wbudowany kontener DI} &
Standaryzacja mechanizmu Inversion of Control na poziomie całej
platformy. Zewnętrzne kontenery można wciąż integrować, lecz wbudowany
mechanizm zazwyczaj pokrywa pełne zapotrzebowanie aplikacji. \\
\textbf{Service Locator / Inicjalizacja ręczna} & \textbf{Wstrzykiwanie
przez konstruktor (Constructor Injection)} & Klasy definiują swoje
zależności w konstruktorze, a framework DI automatycznie dostarcza
odpowiednie instancje w czasie działania programu. \\
\textbf{Statyczne instancje w pamięci aplikacji} & \textbf{Cykl życia
zarządzany przez Hosta (Singleton, Scoped, Transient)} & Host kontroluje
czas życia obiektów, zapewniając automatyczną inicjalizację oraz
bezpieczne uwalnianie zasobów po zakończeniu żądania lub pracy
aplikacji. \\
\end{longtable}
}
\paragraph{2. Architektura systemowa: Host jako zarządca
usług}\label{architektura-systemowa-host-jako-zarzux105dca-usux142ug}
W nowoczesnych rozwiązaniach systemowych punktem centralnym cyklu życia
programu jest tzw. Host. Na etapie uruchamiania aplikacji, Host ładuje
konfigurację i inicjalizuje scentralizowany kontener wstrzykiwania
zależności. Oznacza to, że wszystkie zasoby -- od logowania po konteksty
bazy danych -- są rejestrowane i przechowywane w jednym, zarządzanym
przez Hosta obiekcie \texttt{IServiceCollection}.
Kluczową zaletą tego paradygmatu dla twórców oprogramowania desktopowego
jest uniwersalność: wzorzec Generic Host umożliwia używanie mechanizmu
DI, zarządzania cyklem życia i logowania w aplikacjach, które nie
posiadają interfejsu webowego (scenariusze non-web). Pozwala to na
wydzielenie logiki, która w aplikacjach WPF często znajdowała się w
zdarzeniach UI, do niezależnych procesów w tle (tzw.
\texttt{IHostedService}). Jeśli jednak wbudowany kontener Inversion of
Control (IoC) nie spełnia wszystkich specyficznych wymagań zaawansowanej
architektury przedsiębiorstwa, platforma wciąż pozwala na jego
zastąpienie kontenerem firmy trzeciej.
\paragraph{3. Laboratorium kodu: Rejestracja i wstrzykiwanie
usług}\label{laboratorium-kodu-rejestracja-i-wstrzykiwanie-usux142ug}
Proces konfigurowania aplikacji został drastycznie uproszczony dzięki
obiektowi \texttt{WebApplicationBuilder} (który zachowuje się
analogicznie do .NET Generic Host, wymagając mniejszej liczby wywołań
zwrotnych). Kod konfiguracyjny (dawniej dodawany m.in. w pliku
\texttt{Startup.cs} lub \texttt{App.xaml.cs}) rejestruje usługi przy
użyciu właściwości \texttt{builder.Services}.
\textbf{Analiza przypadku -- Rejestracja w kontenerze Hosta:}
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{var builder = WebApplication.CreateBuilder(args);}
\NormalTok{// Rejestracja klasycznych usług ASP.NET Core}
\NormalTok{builder.Services.AddRazorPages();}
\NormalTok{builder.Services.AddControllersWithViews();}
\NormalTok{// Rejestracja kontekstu bazy danych (EF Core)}
\NormalTok{builder.Services.AddDbContextFactory\textless{}BlazorWebAppMoviesContext\textgreater{}(options =\textgreater{}}
\NormalTok{ options.UseSqlServer(builder.Configuration.GetConnectionString("MoviesContext")));}
\NormalTok{// Rejestracja usług interfejsu (Blazor)}
\NormalTok{builder.Services.AddRazorComponents()}
\NormalTok{ .AddInteractiveServerComponents();}
\NormalTok{// Host buduje kontener i staje się gotowy do pracy}
\NormalTok{var app = builder.Build();}
\end{Highlighting}
\end{Shaded}
\emph{Powyższy fragment ukazuje, jak metoda \texttt{CreateBuilder}
dostarcza usługi takie jak konfiguracja czy logowanie, do których
następnie programista dopina własne abstrakcje, np. kontekst bazy
danych.}
\textbf{Wstrzykiwanie w warstwie prezentacji (Blazor):}
W tradycyjnym wzorcu MVVM w środowisku WPF powiązanie ViewModelu z
widokiem (DataContext) wymagało nierzadko tworzenia fabryk lub
statycznego rozwiązywania zależności. W systemie komponentowym Blazor,
usługi wyciągane są z kontenera DI natywnie w fazie renderowania za
pomocą prostej dyrektywy \texttt{@inject}.
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{@page "/movies"}
\NormalTok{@rendermode InteractiveServer}
\NormalTok{@using Microsoft.EntityFrameworkCore}
\NormalTok{@inject IDbContextFactory\textless{}BlazorWebAppMoviesContext\textgreater{} DbFactory}
\NormalTok{\textless{}h1\textgreater{}Index\textless{}/h1\textgreater{}}
\NormalTok{@code \{}
\NormalTok{ private BlazorWebAppMoviesContext context = default!;}
\NormalTok{ // Instancja bazy danych zostaje dostarczona do komponentu przez Hosta}
\NormalTok{ protected override void OnInitialized()}
\NormalTok{ \{}
\NormalTok{ context = DbFactory.CreateDbContext();}
\NormalTok{ \}}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}
W kodzie niezwiązanym z interfejsem graficznym, takim jak kontrolery czy
strony Razor Pages, standardowo wykorzystuje się metodę wstrzykiwania
przez konstruktor. Framework samodzielnie dopasowuje parametry
konstruktora do zarejestrowanych usług.
\paragraph{4. Strategia Migracji: DI w środowisku Blazor
Hybrid}\label{strategia-migracji-di-w-ux15brodowisku-blazor-hybrid}
Przenosząc skomplikowaną logikę biznesową (tzw. "Heavy Client") z WPF i
WinForms, zintegrowane DI staje się fundamentem procesu modernizacji.
Strategia ewolucyjna wykorzystująca technologię \textbf{Blazor Hybrid}
pozwala na wdrożenie wzorca Generic Host we wnętrzu istniejącego procesu
aplikacji desktopowej.
\textbf{Wnioski implementacyjne z wdrożenia wstrzykiwania zależności w
modelu hybrydowym:}\\
Zamiast przepisywać mechanizmy na nowo, programiści C\# mogą zainicjować
instancję \texttt{IServiceCollection} w punkcie wejścia (np. przy
uruchamianiu \texttt{App.xaml}). Usługi specyficzne dla sprzętu czy
systemu operacyjnego (np. czytnik kodów kreskowych, zapis na dysku C:)
mogą zostać zarejestrowane w kontenerze jako Singletony, a nowo
stworzone webowe komponenty Razor (osadzone w kontrolce
\texttt{BlazorWebView}) zażądają ich przez zwykłe \texttt{@inject}.
Dzięki wbudowanemu mechanizmowi Dependency Injection w modelu hybrydowym
współdzielenie instancji obiektów między kodem natywnym WPF a
nowoczesnym interfejsem staje się bezproblemowe, a ewentualne późniejsze
oderwanie logiki do zewnętrznego mikroserwisu odbywa się naturalnie.
\section{Zarządzanie Konfiguracją (appsettings.json) w Architekturze
Generic
Host}\label{zarzux105dzanie-konfiguracjux105-appsettings.json-w-architekturze-generic-host}
W klasycznych aplikacjach desktopowych (WPF, WinForms) opartych na .NET
Framework 4.8, zarządzanie konfiguracją opierało się niemal wyłącznie na
plikach XML, takich jak \texttt{App.config} czy \texttt{Web.config}, do
których dostęp uzyskiwano statycznie poprzez klasę
\texttt{ConfigurationManager}. W nowoczesnym ekosystemie .NET,
fundamentem zarządzania cyklem życia i zasobami jest \textbf{Generic
Host}, który centralizuje i unowocześnia proces ładowania ustawień.
Zamiast sztywnego formatu XML, standardem stał się format JSON, w
szczególności plik \texttt{appsettings.json}, który we współpracy z
wbudowanym mechanizmem wstrzykiwania zależności (DI) diametralnie
zmienia architekturę dostępu do danych konfiguracyjnych.
\paragraph{1. Mapowanie pojęć: Desktop vs Generic
Host}\label{mapowanie-pojux119ux107-desktop-vs-generic-host}
Przejście na architekturę Generic Host wymaga od programistów
desktopowych zmiany paradygmatu w sposobie definiowania i odczytywania
parametrów startowych:
{\def\LTcaptype{none} % do not increment counter
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Koncepcja Desktop (WPF/WinForms) & Odpowiednik w .NET (Generic Host) &
Opis zmiany architektonicznej \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\textbf{\texttt{App.config} / \texttt{Web.config} (XML)} &
\textbf{\texttt{appsettings.json}} & Format XML został zastąpiony
czystą, hierarchiczną strukturą JSON, która ułatwia organizację
złożonych ustawień i sekcji. \\
\textbf{Transformacje XML (Slow/Complex)} & \textbf{Konfiguracja
środowiskowa} & Host automatycznie wczytuje nadpisujące pliki zależne od
środowiska, np. \texttt{appsettings.Development.json} lub
\texttt{appsettings.Production.json}. \\
\textbf{\texttt{ConfigurationManager.AppSettings}} & \textbf{Interfejs
\texttt{IConfiguration}} & Odczyt statyczny ustępuje miejsca
wstrzykiwaniu zależności. Host ładuje ustawienia do scentralizowanego
obiektu konfiguracyjnego. \\
\textbf{Pobieranie pojedynczych kluczy} & \textbf{Wzorzec Opcji (Options
Pattern)} & Platforma preferuje silnie typowane wiązanie hierarchii JSON
do klas języka C\#, co redukuje błędy i upraszcza testowanie. \\
\end{longtable}
}
\paragraph{2. Architektura systemowa: Host jako konfigurator
zasobów}\label{architektura-systemowa-host-jako-konfigurator-zasobuxf3w}
W momencie uruchamiania aplikacji, Generic Host (lub jego nowsza
inkarnacja \texttt{WebApplicationBuilder}) odpowiada za hermetyzację
wszystkich zasobów systemowych, w tym logowania, kontenera DI oraz
konfiguracji. Zamiast polegać na jednym pliku, Host buduje konfigurację
poprzez agregację danych z uporządkowanego zestawu dostawców.
Domyślna konfiguracja tworzona przez metodę \texttt{Build()} wczytuje
ustawienia w następującej kolejności:
\begin{enumerate}
\tightlist
\item
Plik \texttt{appsettings.json}.
\item
Pliki środowiskowe (np. \texttt{appsettings.Development.json}).
\item
Zmienne środowiskowe, które automatycznie nadpisują wartości pobrane z
plików JSON.
\item
Argumenty wiersza poleceń.
\end{enumerate}
Kluczowym wnioskiem architektonicznym dla systemów klasy Enterprise jest
fakt, że pliki \texttt{appsettings.json} służą do przechowywania
struktury, ale nie powinny zawierać danych poufnych w postaci jawnego
tekstu (takich jak hasła) w środowiskach produkcyjnych. W architekturze
zorientowanej na chmurę zaleca się wykorzystanie narzędzia Secret
Manager (w fazie deweloperskiej) oraz usług takich jak Azure Key Vault
do bezpiecznego wstrzykiwania sekretów.
\paragraph{3. Laboratorium kodu: Wzorzec Opcji i wstrzykiwanie
konfiguracji}\label{laboratorium-kodu-wzorzec-opcji-i-wstrzykiwanie-konfiguracji}
Najlepszą praktyką odczytu konfiguracji w nowoczesnym .NET jest
zastosowanie \textbf{wzorca opcji} (Options Pattern), który wiąże
struktury JSON z silnie typowanymi klasami. Eliminuje to konieczność
używania "magicznych ciągów znaków" (ang. \emph{magic strings})
rozrzuconych po logice biznesowej.
\textbf{Analiza przypadku -- Struktura pliku \texttt{appsettings.json}:}
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{\{}
\NormalTok{ "Logging": \{}
\NormalTok{ "LogLevel": \{}
\NormalTok{ "Default": "Information"}
\NormalTok{ \}}
\NormalTok{ \},}
\NormalTok{ "AppSettings": \{}
\NormalTok{ "FeatureToggle": true,}
\NormalTok{ "ApplicationName": "ModulBiznesowyWPF",}
\NormalTok{ "MaxUsers": 1000}
\NormalTok{ \}}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}
Powyższy plik stanowi odpowiednik dawnych sekcji w \texttt{App.config}.
Zamiast statycznego \texttt{ConfigurationManager}, definiujemy klasę
odzwierciedlającą tę strukturę:
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{public class AppSettings}
\NormalTok{\{}
\NormalTok{ public bool FeatureToggle \{ get; set; \}}
\NormalTok{ public string ApplicationName \{ get; set; \}}
\NormalTok{ public int MaxUsers \{ get; set; \}}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}
\textbf{Rejestracja w kontenerze Hosta (\texttt{Program.cs}):}
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{var builder = WebApplication.CreateBuilder(args);}
\NormalTok{// Rejestracja konfiguracji w kontenerze DI za pomocą wzorca Opcji}
\NormalTok{builder.Services.Configure\textless{}AppSettings\textgreater{}(builder.Configuration.GetSection("AppSettings"));}
\NormalTok{var app = builder.Build();}
\NormalTok{// Uruchomienie aplikacji...}
\end{Highlighting}
\end{Shaded}
\textbf{Wstrzykiwanie do komponentu Blazor lub usługi:}\\
Kontrolery lub usługi biznesowe nie odpytują pliku samodzielnie --
otrzymują gotowy, rzutowany obiekt za pośrednictwem konstruktora.
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{public class MyBusinessService}
\NormalTok{\{}
\NormalTok{ private readonly AppSettings \_appSettings;}
\NormalTok{ // Interfejs IOptions pozwala wstrzyknąć silnie typowaną konfigurację}
\NormalTok{ public MyBusinessService(IOptions\textless{}AppSettings\textgreater{} appSettings)}
\NormalTok{ \{}
\NormalTok{ \_appSettings = appSettings.Value;}
\NormalTok{ \}}
\NormalTok{ public void ProcessData()}
\NormalTok{ \{}
\NormalTok{ Console.WriteLine($"Aplikacja: \{\_appSettings.ApplicationName\}");}
\NormalTok{ \}}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}
\paragraph{4. Strategia Migracji: Scenariusze Non-Web i Blazor
Hybrid}\label{strategia-migracji-scenariusze-non-web-i-blazor-hybrid}
Dla deweloperów planujących migrację złożonych aplikacji WPF, pełne
przepisanie logiki odczytu \texttt{App.config} wydaje się ryzykownym
procesem. Jednakże architektura Generic Host wspiera również
\textbf{scenariusze nie-webowe (non-web scenarios)}.
\textbf{Wnioski implementacyjne dla strategii hybrydowej:}\\
Programiści C\# mogą zainicjować instancję Generic Host w klasie
\texttt{App.xaml.cs} swojej istniejącej aplikacji WPF. Pozwala to
natychmiastowo zrezygnować z przestarzałego
\texttt{ConfigurationManager} na rzecz nowoczesnego
\texttt{appsettings.json} oraz silnie typowanego wzorca opcji. Tak
przygotowany Host zarządza cyklem życia nie tylko starych usług
natywnych, ale jest też perfekcyjnie zintegrowany z kontrolką
\texttt{BlazorWebView}, używaną w podejściu \textbf{Blazor Hybrid}.
Komponenty Razor osadzone w okienkach WPF zyskują w ten sposób dokładnie
taki sam i ustandaryzowany dostęp do ustawień
(\texttt{@inject\ IOptions\textless{}T\textgreater{}}), jak miałyby,
gdyby aplikacja działała jako czysty wariant Blazor WebAssembly czy
Blazor Server w chmurze.
\end{document}