1063 lines
47 KiB
TeX
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}
|