Używanie platformy ASP.NET Core SignalR z Blazor
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy (Zasady obsługi platformy .NET Core). Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ten samouczek zawiera podstawowe środowisko robocze do tworzenia aplikacji w czasie rzeczywistym przy użyciu programu SignalR Blazor. Ten artykuł jest przydatny dla deweloperów, którzy są już zaznajomieni i SignalR chcą zrozumieć, jak używać SignalR w Blazor aplikacji. Aby uzyskać szczegółowe wskazówki dotyczące platform SignalR i Blazor , zobacz następujące zestawy dokumentacji referencyjnej i dokumentację interfejsu API:
- Omówienie platformy ASP.NET Core SignalR
- ASP.NET Core Blazor
- Przeglądarka interfejsów API platformy .NET
Instrukcje:
- Tworzenie Blazor aplikacji
- Dodawanie biblioteki SignalR klienta
- SignalR Dodawanie koncentratora
- Dodawanie SignalR usług i punktu końcowego SignalR dla centrum
- Razor Dodawanie kodu składnika na potrzeby czatu
Na końcu tego samouczka będziesz mieć działającą aplikację do czatu.
Wymagania wstępne
Program Visual Studio (najnowsza wersja) z pakietem roboczym tworzenia aplikacji internetowych i ASP.NET
Przykładowa aplikacja
Pobranie przykładowej aplikacji do czatu z samouczka nie jest wymagane w tym samouczku. Przykładowa aplikacja to ostateczna, działająca aplikacja utworzona przez wykonanie kroków tego samouczka. Po otwarciu repozytorium przykładów otwórz folder wersji, który ma być docelowy, i znajdź przykład o nazwie BlazorSignalRApp
.
Pobranie przykładowej aplikacji do czatu z samouczka nie jest wymagane w tym samouczku. Przykładowa aplikacja to ostateczna, działająca aplikacja utworzona przez wykonanie kroków tego samouczka. Po otwarciu repozytorium przykładów otwórz folder wersji, który ma być docelowy, i znajdź przykład o nazwie BlazorWebAssemblySignalRApp
.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Tworzenie Blazor aplikacji internetowej
Postępuj zgodnie ze wskazówkami dotyczącymi wyboru narzędzi:
Uwaga
Wymagany jest program Visual Studio 2022 lub nowszy oraz zestaw .NET Core SDK 8.0.0 lub nowszy.
W programie Visual Studio:
- Wybierz pozycję Utwórz nowy projekt w oknie startowym lub wybierz pozycję Plik>nowy>projekt na pasku menu.
- W oknie dialogowym Tworzenie nowego projektu wybierz pozycję Blazor Aplikacja internetowa z listy szablonów projektów. Kliknij przycisk Next (Dalej).
- W oknie dialogowym Konfigurowanie nowego projektu nadaj projektowi nazwę projektu
BlazorSignalRApp
w polu Nazwa projektu, w tym dopasowanie liter. Użycie tej dokładnej nazwy projektu jest ważne, aby upewnić się, że przestrzenie nazw pasują do kodu skopiowanego z samouczka do aplikacji, którą tworzysz. - Upewnij się, że lokalizacja aplikacji jest odpowiednia. Pozostaw zaznaczone pole wyboru Umieść rozwiązanie i projekt w tym samym katalogu. Kliknij przycisk Next (Dalej).
- W oknie dialogowym Dodatkowe informacje użyj następujących ustawień:
- Struktura: upewnij się, że wybrano najnowszą strukturę . Jeśli lista rozwijana programu Visual Studio Framework nie zawiera najnowszej dostępnej platformy .NET Framework , zaktualizuj program Visual Studio i uruchom ponownie samouczek.
- Typ uwierzytelniania: Brak
- Konfigurowanie dla protokołu HTTPS: wybrane
- Tryb renderowania interakcyjnego: Zestaw WebAssembly
- Lokalizacja interakcyjności: na stronę/składnik
- Uwzględnij przykładowe strony: wybrane
- Nie używaj instrukcji najwyższego poziomu: nie wybrano
- Wybierz pozycję Utwórz.
Wskazówki zawarte w tym artykule używają składnika WebAssembly dla SignalR klienta, ponieważ nie ma sensu używać SignalR do nawiązywania połączenia z koncentratorem ze składnika Interactive Server w tej samej aplikacji, co może prowadzić do wyczerpania portów serwera.
Dodawanie biblioteki SignalR klienta
W Eksplorator rozwiązań kliknij prawym przyciskiem myszy BlazorSignalRApp.Client
projekt i wybierz polecenie Zarządzaj pakietami NuGet.
W oknie dialogowym Zarządzanie pakietami NuGet upewnij się, że źródło pakietu ma wartość nuget.org
.
Po wybraniu pozycji Przeglądaj wpisz Microsoft.AspNetCore.SignalR.Client
w polu wyszukiwania.
W wynikach wyszukiwania wybierz najnowszą wersję Microsoft.AspNetCore.SignalR.Client
pakietu. Wybierz Zainstaluj.
Jeśli zostanie wyświetlone okno dialogowe Podgląd zmian, wybierz przycisk OK.
Jeśli zostanie wyświetlone okno dialogowe Akceptacja licencji, wybierz pozycję Akceptuję, jeśli zgadzasz się z postanowieniami licencyjnymi.
SignalR Dodawanie koncentratora
W projekcie serwera BlazorSignalRApp
utwórz folder (mnoga) i dodaj następującą ChatHub
klasę Hubs
(Hubs/ChatHub.cs
):
using Microsoft.AspNetCore.SignalR;
namespace BlazorSignalRApp.Hubs;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
Dodawanie usług i punktu końcowego SignalR dla centrum
Program
Otwórz plik projektu serweraBlazorSignalRApp
.
Dodaj przestrzenie nazw i Microsoft.AspNetCore.ResponseCompression klasę ChatHub
na początku pliku:
using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;
Dodaj SignalR i usługi oprogramowania pośredniczącego kompresji odpowiedzi:
builder.Services.AddSignalR();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
["application/octet-stream"]);
});
Użyj oprogramowania pośredniczącego kompresji odpowiedzi w górnej części konfiguracji potoku przetwarzania. Umieść następujący wiersz kodu bezpośrednio po wierszu, który kompiluje aplikację (var app = builder.Build();
):
app.UseResponseCompression();
Dodaj punkt końcowy centrum bezpośrednio przed wierszem, który uruchamia aplikację (app.Run();
):
app.MapHub<ChatHub>("/chathub");
Dodawanie Razor kodu składnika na potrzeby czatu
Dodaj następujący Pages/Chat.razor
plik do BlazorSignalRApp.Client
projektu:
@page "/chat"
@rendermode InteractiveWebAssembly
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Chat</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
Dodaj wpis do składnika, NavMenu
aby uzyskać dostęp do strony czatu. Natychmiast Components/Layout/NavMenu.razor
po <div>
bloku składnika Weather
dodaj następujący <div>
blok:
<div class="nav-item px-3">
<NavLink class="nav-link" href="chat">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Chat
</NavLink>
</div>
Uwaga
Wyłącz oprogramowanie pośredniczące kompresji odpowiedzi w Development
środowisku podczas korzystania z Przeładowywanie na gorąco. Aby uzyskać więcej informacji, zobacz wskazówki dotyczące platformy ASP.NET CoreBlazorSignalR.
Uruchom aplikację
Postępuj zgodnie ze wskazówkami dotyczącymi narzędzi:
Po wybraniu projektu serwera BlazorSignalRApp
w Eksplorator rozwiązań naciśnij F5, aby uruchomić aplikację z debugowaniem lub Ctrl+F5 (Windows)/⌘+F5 (macOS), aby uruchomić aplikację bez debugowania.
Skopiuj adres URL z paska adresu, otwórz inne wystąpienie przeglądarki lub kartę i wklej adres URL na pasku adresu.
Wybierz jedną z przeglądarek, wprowadź nazwę i komunikat, a następnie wybierz przycisk, aby wysłać wiadomość. Nazwa i komunikat są wyświetlane na obu stronach natychmiast:
Cytaty: Star Trek VI: Nieodkryty kraj ©1991 Paramount
Środowisko hostowane Blazor WebAssembly
Tworzenie aplikacji
Postępuj zgodnie ze wskazówkami dotyczącymi wyboru narzędzi, aby utworzyć hostowaną Blazor WebAssembly aplikację:
Uwaga
Wymagany jest program Visual Studio 2022 lub nowszy oraz zestaw .NET Core SDK 6.0.0 lub nowszy.
Tworzenie nowego projektu.
Blazor WebAssembly Wybierz szablon Aplikacja. Wybierz Dalej.
Wpisz BlazorWebAssemblySignalRApp
w polu Nazwa projektu. Upewnij się, że wpis Lokalizacja jest poprawny lub podaj lokalizację projektu. Wybierz Dalej.
W oknie dialogowym Dodatkowe informacje zaznacz pole wyboru ASP.NET Core Hosted.
Wybierz pozycję Utwórz.
Upewnij się, że utworzono hostowaną Blazor WebAssembly aplikację: w Eksplorator rozwiązań potwierdź obecność Client projektu i Server projektu. Jeśli dwa projekty nie są obecne, przed wybraniem pozycji Utwórz rozpocznij od nowa i potwierdź zaznaczenie pola wyboru ASP.NET Core Hosted.
Dodawanie biblioteki SignalR klienta
W Eksplorator rozwiązań kliknij prawym przyciskiem myszy BlazorWebAssemblySignalRApp.Client
projekt i wybierz polecenie Zarządzaj pakietami NuGet.
W oknie dialogowym Zarządzanie pakietami NuGet upewnij się, że źródło pakietu ma wartość nuget.org
.
Po wybraniu pozycji Przeglądaj wpisz Microsoft.AspNetCore.SignalR.Client
w polu wyszukiwania.
W wynikach wyszukiwania wybierz Microsoft.AspNetCore.SignalR.Client
pakiet. Ustaw wersję tak, aby odpowiadała współużytkowanej strukturze aplikacji. Wybierz Zainstaluj.
Jeśli zostanie wyświetlone okno dialogowe Podgląd zmian, wybierz przycisk OK.
Jeśli zostanie wyświetlone okno dialogowe Akceptacja licencji, wybierz pozycję Akceptuję, jeśli zgadzasz się z postanowieniami licencyjnymi.
SignalR Dodawanie koncentratora
W projekcie BlazorWebAssemblySignalRApp.Server
utwórz folder (mnoga) i dodaj następującą ChatHub
klasę Hubs
(Hubs/ChatHub.cs
):
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
Dodawanie usług i punktu końcowego SignalR dla centrum
W projekcie BlazorWebAssemblySignalRApp.Server
otwórz Program.cs
plik.
Dodaj przestrzeń nazw klasy ChatHub
na początku pliku:
using BlazorWebAssemblySignalRApp.Server.Hubs;
Dodaj SignalR i usługi oprogramowania pośredniczącego kompresji odpowiedzi:
builder.Services.AddSignalR();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
Użyj oprogramowania pośredniczącego kompresji odpowiedzi w górnej części konfiguracji potoku przetwarzania natychmiast po wierszu, który kompiluje aplikację:
app.UseResponseCompression();
Między punktami końcowymi kontrolerów i rezerwowym po stronie klienta dodaj punkt końcowy dla centrum. Natychmiast po wierszu app.MapControllers();
dodaj następujący wiersz:
app.MapHub<ChatHub>("/chathub");
W projekcie BlazorWebAssemblySignalRApp.Server
otwórz Startup.cs
plik.
Dodaj przestrzeń nazw klasy ChatHub
na początku pliku:
using BlazorWebAssemblySignalRApp.Server.Hubs;
Dodaj SignalR i usługi oprogramowania pośredniczącego kompresji odpowiedzi:
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
Użyj oprogramowania pośredniczącego kompresji odpowiedzi w górnej części konfiguracji potoku przetwarzania:
app.UseResponseCompression();
Między punktami końcowymi kontrolerów i rezerwowym po stronie klienta dodaj punkt końcowy centrum bezpośrednio po wierszu endpoints.MapControllers();
:
endpoints.MapHub<ChatHub>("/chathub");
Dodawanie Razor kodu składnika na potrzeby czatu
W projekcie BlazorWebAssemblySignalRApp.Client
otwórz Pages/Index.razor
plik.
Zastąp znacznik następującym kodem:
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Index</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Index</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IAsyncDisposable
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection hubConnection;
private List<string> messages = new List<string>();
private string userInput;
private string messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
async Task Send() =>
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IDisposable
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection hubConnection;
private List<string> messages = new List<string>();
private string userInput;
private string messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
async Task Send() =>
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public void Dispose()
{
_ = hubConnection?.DisposeAsync();
}
}
Uwaga
Wyłącz oprogramowanie pośredniczące kompresji odpowiedzi w Development
środowisku podczas korzystania z Przeładowywanie na gorąco. Aby uzyskać więcej informacji, zobacz wskazówki dotyczące platformy ASP.NET CoreBlazorSignalR.
Uruchom aplikację
Postępuj zgodnie ze wskazówkami dotyczącymi narzędzi:
W Eksplorator rozwiązań wybierz BlazorWebAssemblySignalRApp.Server
projekt. Naciśnij F5, aby uruchomić aplikację przy użyciu debugowania lub Ctrl+F5 (Windows)/⌘+F5 (macOS), aby uruchomić aplikację bez debugowania.
Ważne
Podczas wykonywania hostowanej Blazor WebAssembly aplikacji uruchom aplikację z projektu rozwiązaniaServer.
Przeglądarka Google Chrome lub Microsoft Edge musi być wybraną przeglądarką na potrzeby sesji debugowania.
Jeśli uruchomienie aplikacji nie powiedzie się w przeglądarce:
- W konsoli platformy .NET upewnij się, że rozwiązanie jest uruchomione z projektu "Server".
- Odśwież przeglądarkę przy użyciu przycisku załaduj ponownie przeglądarkę.
Skopiuj adres URL z paska adresu, otwórz inne wystąpienie przeglądarki lub kartę i wklej adres URL na pasku adresu.
Wybierz jedną z przeglądarek, wprowadź nazwę i komunikat, a następnie wybierz przycisk, aby wysłać wiadomość. Nazwa i komunikat są wyświetlane na obu stronach natychmiast:
Cytaty: Star Trek VI: Nieodkryty kraj ©1991 Paramount
Następne kroki
W tym samouczku zawarto informacje na temat wykonywania następujących czynności:
- Tworzenie Blazor aplikacji
- Dodawanie biblioteki SignalR klienta
- SignalR Dodawanie koncentratora
- Dodawanie SignalR usług i punktu końcowego SignalR dla centrum
- Razor Dodawanie kodu składnika na potrzeby czatu
Aby uzyskać szczegółowe wskazówki dotyczące platform SignalR i Blazor , zobacz następujące zestawy dokumentacji referencyjnej:
Dodatkowe zasoby
- Uwierzytelnianie tokenu elementu nośnego za pomocą zdarzeń Identity Server, WebSocket i Server-Sent
- Zabezpieczanie SignalR centrum w Blazor WebAssembly aplikacjach
- SignalR Negocjacje między źródłami na potrzeby uwierzytelniania
- SignalR konfiguracja
- Debugowanie aplikacji ASP.NET Core Blazor
- Blazorprzykładowe repozytorium GitHub () (
dotnet/blazor-samples
jak pobrać)
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla