diff --git a/docs/cs/AspNetCore/Widgets.md b/docs/cs/AspNetCore/Widgets.md deleted file mode 100644 index f0ee218804..0000000000 --- a/docs/cs/AspNetCore/Widgets.md +++ /dev/null @@ -1,506 +0,0 @@ -# Widgety - -ABP poskytuje model a infastrukturu k vytváření **znovu použitelných widgetů**. Systém widgetů je rozšíření pro [ASP.NET Core pohledové komponenty](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgety jsou zvláště užitečné, když chcete; - -* Mít závislosti na **skriptech & stylech** ve vašem widgetu. -* Vytvářet **řídící panely** za použítí widgetů. -* Definovat widgety v znovu použitelných **[modulech](../Module-Development-Basics.md)**. -* Spolupráci widgetů s **[authorizačními](../Authorization.md)** a **[svazovacími](Bundling-Minification.md)** systémy. - -## Základní definice widgetu - -### Tvorba pohledové komponenty - -Jako první krok, vytvořte běžnou ASP.NET Core pohledovou komponentu: - -![widget-basic-files](../images/widget-basic-files.png) - -**MySimpleWidgetViewComponent.cs**: - -````csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; - -namespace DashboardDemo.Web.Pages.Components.MySimpleWidget -{ - public class MySimpleWidgetViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View(); - } - } -} -```` - -Dědění z `AbpViewComponent` není vyžadováno. Můžete dědit ze standardního ASP.NET Core `ViewComponent`. `AbpViewComponent` pouze definuje pár základních a užitečných vlastnosti. - -Můžete vložit službu a pomocí metody `Invoke` z ní získat některá data. Možná budete muset provést metodu Invoke jako asynchronní `public async Task InvokeAsync()`. Podívejte se na dokument [ASP.NET Core ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) pro všechna další použítí. - -**Default.cshtml**: - -```xml -
-

My Simple Widget

-

This is a simple widget!

-
-``` - -### Definice widgetu - -Přidejte atribut `Widget` k třídě `MySimpleWidgetViewComponent` pro označení této pohledové komponenty jako widgetu: - -````csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace DashboardDemo.Web.Pages.Components.MySimpleWidget -{ - [Widget] - public class MySimpleWidgetViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View(); - } - } -} -```` - -## Vykreslení widgetu - -Vykreslení widgetu je vcelku standardní. Použijte metodu `Component.InvokeAsync` v razor pohledu/stránce jako s kteroukoliv jinou pohledovou komponentou. Příklady: - -````xml -@await Component.InvokeAsync("MySimpleWidget") -@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent)) -```` - -První přístup používá název widgetu, zatímco druhý používá typ pohledové komponenty. - -### Widgety s argumenty - -Systém ASP.NET Core pohledových komponent umožňuje přijímat argumenty pro pohledové komponenty. Níže uvedená pohledová komponenta přijímá `startDate` a `endDate` a používá tyto argumenty k získání dat ze služby. - -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget -{ - [Widget] - public class CountersWidgetViewComponent : AbpViewComponent - { - private readonly IDashboardAppService _dashboardAppService; - - public CountersWidgetViewComponent(IDashboardAppService dashboardAppService) - { - _dashboardAppService = dashboardAppService; - } - - public async Task InvokeAsync( - DateTime startDate, DateTime endDate) - { - var result = await _dashboardAppService.GetCountersWidgetAsync( - new CountersWidgetInputDto - { - StartDate = startDate, - EndDate = endDate - } - ); - - return View(result); - } - } -} -```` - -Nyní musíte předat anonymní objekt k předání argumentů tak jak je ukázáno níže: - -````xml -@await Component.InvokeAsync("CountersWidget", new -{ - startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)), - endDate = DateTime.Now -}) -```` - -## Název widgetu - -Výchozí název pohledových komponent je vypočítán na základě názvu typu pohledové komponenty. Pokud je typ pohledové komponenty `MySimpleWidgetViewComponent` potom název widgetu bude `MySimpleWidget` (odstraní se `ViewComponent` postfix). Takto ASP.NET Core vypočítává název pohledové komponenty. - -Chcete-li přizpůsobit název widgetu, stačí použít standardní atribut `ViewComponent` z ASP.NET Core: - -```csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace DashboardDemo.Web.Pages.Components.MySimpleWidget -{ - [Widget] - [ViewComponent(Name = "MyCustomNamedWidget")] - public class MySimpleWidgetViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View("~/Pages/Components/MySimpleWidget/Default.cshtml"); - } - } -} -``` - -ABP bude respektovat přizpůsobený název při zpracování widgetu. - -> Pokud jsou názvy pohledové komponenty a složky, která pohledovou komponentu obsahuje rozdílné, pravděpodobně budete muset ručně uvést cestu pohledu tak jako je to provedeno v tomto příkladu. - -### Zobrazovaný název - -Můžete také definovat čitelný & lokalizovatelný zobrazovaný název pro widget. Tento zobrazovaný název může být využít na uživatelském rozhraní kdykoliv je to potřeba. Zobrazovaný název je nepovinný a lze ho definovat pomocí vlastností atributu `Widget`: - -````csharp -using DashboardDemo.Localization; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace DashboardDemo.Web.Pages.Components.MySimpleWidget -{ - [Widget( - DisplayName = "MySimpleWidgetDisplayName", // Lokalizační klíč - DisplayNameResource = typeof(DashboardDemoResource) // Lokalizační zdroj - )] - public class MySimpleWidgetViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View(); - } - } -} -```` - -Podívejte se na [dokument lokalizace](../Localization.md) pro více informací o lokalizačních zdrojích a klíčích. - -## Závislosti na stylech & skriptech - -Problémy když má widget soubory skriptů a stylů; - -* Každý stránka, která používá widget musí také přidat soubory **skriptů & stylů** tohoto widgetu. -* Stránka se také musí postarat o **závislé knihovny/soubory** widgetu. - -ABP tyto problémy řeší, když správně propojíme zdroje s widgetem. O závislosti widgetu se při jeho používání nestaráme. - -### Definování jednoduchých cest souborů - -Níže uvedený příklad widgetu přidá stylové a skriptové soubory: - -````csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace DashboardDemo.Web.Pages.Components.MySimpleWidget -{ - [Widget( - StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" }, - ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" } - )] - public class MySimpleWidgetViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View(); - } - } -} -```` - -ABP bere v úvahu tyto závislosti a správně je přidává do pohledu/stránky při použití widgetu. Stylové/skriptové soubory mohou být **fyzické nebo virtuální**. Plně integrováno do [virtuálního systému souborů](../Virtual-File-System.md). - -### Definování přispěvatelů balíku - -Všechny zdroje použité ve widgetech na stránce jsou přidány jako **svazek** (svázány & minifikovány v produkci pokud nenastavíte jinak). Kromě přidání jednoduchého souboru můžete využít plnou funkčnost přispěvatelů balíčků. - -Níže uvedený ukázkový kód provádí totéž co výše uvedený kód, ale definuje a používá přispěvatele balíků: - -````csharp -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace DashboardDemo.Web.Pages.Components.MySimpleWidget -{ - [Widget( - StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) }, - ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) } - )] - public class MySimpleWidgetViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View(); - } - } - - public class MySimpleWidgetStyleBundleContributor : BundleContributor - { - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files - .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css"); - } - } - - public class MySimpleWidgetScriptBundleContributor : BundleContributor - { - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files - .AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js"); - } - } -} - -```` - -Systém přispěvatelů balíků je velmi schopný. Pokud váš widget používá k vykreslení grafu JavaScript knihovnu, můžete ji deklarovat jako závislost, díky tomu se knihovna pokud nebyla dříve přidána automaticky přidá na stránku Tímto způsobem se stránka využívající váš widget nestará o závislosti. - -Podívejte se na dokumentaci [svazování & minifikace](Bundling-Minification.md) pro více informací o tomto systému. - -## RefreshUrl - -Widget může navrhnout `RefreshUrl`, který se používá vždy, když je potřeba widget aktualizovat. Je-li definován, widget se při každé aktualizaci znovu vykreslí na straně serveru (viz refresh `methoda` u `WidgetManager` níže). - -````csharp -[Widget(RefreshUrl = "Widgets/Counters")] -public class CountersWidgetViewComponent : AbpViewComponent -{ - -} -```` - -Jakmile pro svůj widget definujete `RefreshUrl`, musíte poskytnout koncový bod pro jeho vykreslení a vrátit ho: - -````csharp -[Route("Widgets")] -public class CountersWidgetController : AbpController -{ - [HttpGet] - [Route("Counters")] - public IActionResult Counters(DateTime startDate, DateTime endDate) - { - return ViewComponent("CountersWidget", new {startDate, endDate}); - } -} -```` - -Trasa `Widgets/Counters` předchozímu `RefreshUrl`. - -> Widget lze obnovit dvěma způsoby: Prvním způsobem je použití `RefreshUrl`, kdy se znovu vykreslí na serveru a nahradí HTML vrácené tím ze serveru. Druhým způsobem widget získá data (obvykle JSON objekt) ze serveru a obnoví se sám u klienta (viz refresh metoda v sekci Widget JavaScript API). - -## JavaScript API - -Možná bude potřeba vykreslit a obnovit widget na straně klienta. V takových případech můžete použít ABP `WidgetManager` a definovat API pro vaše widgety. - -### WidgetManager - -`WidgetManager` se používá k inicializaci a aktualizaci jednoho nebo více widgetů. Vytvořte nový `WidgetManager` jako je ukázáno níže: - -````js -$(function() { - var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea'); -}) -```` - -`MyDashboardWidgetsArea` může obsahovat jeden nebo více widgetů. - -> Použíti `WidgetManager` uvnitř document.ready (jako nahoře) je dobrá praktika jelikož jeho funkce používají DOM a potřebují, aby byl DOM připraven. - -#### WidgetManager.init() - -`init` jednoduše inicializuje `WidgetManager` a volá metody `init` v souvisejících widgetech pokud je obsahují (podívejte se na sekci Widget JavaScript API section níže) - -```js -myWidgetManager.init(); -``` - -#### WidgetManager.refresh() - -`refresh` metoda obnoví všechny widgety související s tímto `WidgetManager`: - -```` -myWidgetManager.refresh(); -```` - -#### WidgetManager možnosti - -WidgetManager má několik dalších možností. - -##### Filtrační formulář - -Pokud vaše widgety vyžadují parametry/filtry pak budete obvykle mít formulář pro filtrování widgetů. V takových případech můžete vytvořit formulář, který obsahuje prvky formuláře a oblast řídicího panelu s nějakými widgety uvnitř. Příklad: - -````xml -
- ...prvky formuláře -
- -
- ...widgety -
-```` - -`data-widget-filter` atribut propojuje formulář s widgety. Kdykoli je formulář odeslán, všechny widgety jsou automaticky aktualizovány pomocí polí formuláře jako filtru. - -Místo atributu `data-widget-filter`, můžete použít parametr `filterForm` v konstruktoru `WidgetManager`. Příklad: - -````js -var myWidgetManager = new abp.WidgetManager({ - wrapper: '#MyDashboardWidgetsArea', - filterForm: '#MyDashboardFilterForm' -}); -```` - -##### Zpětné volání filtru - -Možná budete chtít mít lepší kontrolu nad poskytováním filtrů při inicializaci a aktualizaci widgetů. V tomto případě můžete použít volbu `filterCallback`: - -````js -var myWidgetManager = new abp.WidgetManager({ - wrapper: '#MyDashboardWidgetsArea', - filterCallback: function() { - return $('#MyDashboardFilterForm').serializeFormToObject(); - } -}); -```` - -Tento příklad ukazuje výchozí implementaci `filterCallback`. Pomocí polí můžete vrátit jakýkoli JavaScript objekt. Příklad: - -````js -filterCallback: function() { - return { - 'startDate': $('#StartDateInput').val(), - 'endDate': $('#EndDateInput').val() - }; -} -```` - -Vrácené filtry jsou předávány všem widgetům na `init` a` refresh`. - -### Widget JavaScript API - -Widget může definovat rozhraní API jazyka JavaScript, které je v případě potřeby vyvoláno přes `WidgetManager`. Ukázku kódu níže lze použít k definování API pro widget. - -````js -(function () { - abp.widgets.NewUserStatisticWidget = function ($wrapper) { - - var getFilters = function () { - return { - ... - }; - } - - var refresh = function (filters) { - ... - }; - - var init = function (filters) { - ... - }; - - return { - getFilters: getFilters, - init: init, - refresh: refresh - }; - }; -})(); -```` - -`NewUserStatisticWidget` je tady název widgetu. Měl by odpovídat názvu widgetu definovanému na straně serveru. Všechny funkce jsou volitelné. - -#### getFilters - -Pokud má widget vlastní interní filtry, měla by tato funkce vrátit objekt filtru. Příklad: - -````js -var getFilters = function() { - return { - frequency: $wrapper.find('.frequency-filter option:selected').val() - }; -} -```` - -Tuto metodu používá `WidgetManager` při vytváření filtrů. - -#### init - -Slouží k inicializaci widgetu kdykoli je potřeba. Má argument filtru, který lze použít při získávání dat ze serveru. Metoda `init` je použita když je volána funkce `WidgetManager.init()`. Použita je i v případě že váš widget vyžaduje úplné obnovení při aktualizaci. Viz `RefreshUrl` v možnostech widgetu. - -#### refresh - -Slouží k aktualizaci widgetu kdykoli je potřeba. Má argument filtru, který lze použít při získávání dat ze serveru. Metoda `refresh` se používá kdykoliv je volána funkce `WidgetManager.refresh()`. - -## Autorizace - -Některé widgety budou pravděpodobně muset být dostupné pouze pro ověřené nebo autorizované uživatele. V tomto případě použijte následující vlastnosti atributu `Widget`: - -* `RequiresAuthentication` (`bool`): Nastavte na true, aby byl tento widget použitelný pouze pro ověřené uživatele (uživatel je přihlášen do aplikace). -* `RequiredPolicies` (`List`): Seznam názvů zásad k autorizaci uživatele. Další informace o zásadách naleznete v [dokumentu autorizace](../Authorization.md). - -Příklad: - -````csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace DashboardDemo.Web.Pages.Components.MySimpleWidget -{ - [Widget(RequiredPolicies = new[] { "MyPolicyName" })] - public class MySimpleWidgetViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View(); - } - } -} -```` - -## WidgetOptions - -Jako alternativu k atributu `Widget` můžete ke konfiguraci widgetů použít `AbpWidgetOptions`: - -```csharp -Configure(options => -{ - options.Widgets.Add(); -}); -``` - -Toto vepište do metody `ConfigureServices` vašeho [modulu](../Module-Development-Basics.md). Veškerá konfigurace udělaná přes atribut `Widget` je dostupná i za pomoci `AbpWidgetOptions`. Příklad konfigurace, která přidává styl pro widget: - -````csharp -Configure(options => -{ - options.Widgets - .Add() - .WithStyles("/Pages/Components/MySimpleWidget/Default.css"); -}); -```` - -> Tip: `AbpWidgetOptions` lze také použít k získání existujícího widgetu a ke změně jeho konfigurace. To je obzvláště užitečné, pokud chcete změnit konfiguraci widgetu uvnitř modulu používaného vaší aplikací. Použíjte `options.Widgets.Find` k získání existujícího `WidgetDefinition`. - -## Podívejte se také na - -* [Příklad projektu (zdrojový kód)](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo). - diff --git a/docs/cs/Autofac-Integration.md b/docs/cs/Autofac-Integration.md deleted file mode 100644 index 109dca6286..0000000000 --- a/docs/cs/Autofac-Integration.md +++ /dev/null @@ -1,84 +0,0 @@ -# Autofac integrace - -Autofac je jedním z nejpoužívanějších frameworků pro .Net pro vkládání závislostí (DI). Poskytuje pokročilejší funkce v porovnáním se standardní .Net Core DI knihovnou, jako dynamickou proxy a injekci vlastností. - -## Instalace Autofac integrace - -> Všechny startovací šablony a vzorky jsou s Autofac již integrovány. Takže většinou nemusíte tento balíček instalovat ručně. - -Nainstalujte do vašeho projektu balíček [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) (pro víceprojektovou aplikaci se doporučuje přidat do spustitelného/webového projektu.) - -```` -Install-Package Volo.Abp.Autofac -```` - -Poté přídejte k vašemu modulu závislost na `AbpAutofacModule`: - -```csharp -using Volo.Abp.Modularity; -using Volo.Abp.Autofac; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAutofacModule))] - public class MyModule : AbpModule - { - //... - } -} -``` - -Nakonec nastavte `AbpApplicationCreationOptions` aby nahradil výchozí služby pro vkládání závislostí na Autofac. Záleží na typu aplikace. - -### ASP.NET Core aplikace - -Volejte `UseAutofac()` v souboru **Startup.cs** jako je ukázáno níže: - -````csharp -public class Startup -{ - public IServiceProvider ConfigureServices(IServiceCollection services) - { - services.AddApplication(options => - { - //Integrace Autofac! - options.UseAutofac(); - }); - - return services.BuildServiceProviderFromFactory(); - } - - public void Configure(IApplicationBuilder app) - { - app.InitializeApplication(); - } -} -```` - -### Konzolová aplikace - -Volejte metodu `UseAutofac()` v možnostech `AbpApplicationFactory.Create` jako je ukázáno níže: - -````csharp -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create(options => - { - options.UseAutofac(); //Autofac integrace - })) - { - //... - } - } - } -} -```` - diff --git a/docs/cs/Best-Practices/images/postgresql-delete-initial-migrations.png b/docs/cs/Best-Practices/images/postgresql-delete-initial-migrations.png deleted file mode 100644 index 14788c5fb8..0000000000 Binary files a/docs/cs/Best-Practices/images/postgresql-delete-initial-migrations.png and /dev/null differ diff --git a/docs/cs/Best-Practices/images/postgresql-update-database.png b/docs/cs/Best-Practices/images/postgresql-update-database.png deleted file mode 100644 index 30a5f3abe1..0000000000 Binary files a/docs/cs/Best-Practices/images/postgresql-update-database.png and /dev/null differ diff --git a/docs/cs/CLI.md b/docs/cs/CLI.md deleted file mode 100644 index f77c3ad969..0000000000 --- a/docs/cs/CLI.md +++ /dev/null @@ -1,167 +0,0 @@ -# ABP CLI - -ABP CLI (Command Line Interface) je nástroj v příkazovém řádku k provádění některých běžných úkonů v řešeních založených na ABP. - -## Instalace - -ABP CLI je [dotnet global tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools). Nainstalujete jej pomocí okna příkazového řádku: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Aktualizace stávající instalace: - -````bash -dotnet tool update -g Volo.Abp.Cli -```` - -## Příkazy - -### new - -Vygeneruje nové řešení založené na ABP [startovací šabloně](Startup-Templates/Index.md). - -Základní použití: - -````bash -abp new [možnosti] -```` - -Příklad: - -````bash -abp new Acme.BookStore -```` - -* `Acme.BookStore` je tady název řešení. -* Běžná konvence je nazvat řešení stylem *VaseSpolecnost.VasProjekt*. Nicméně můžete použít i jiné pojmenování jako *VasProjekt* (jednostupňový jmenný prostor) nebo *VaseSpolecnost.VasProjekt.VasModul* (třístupňový jmenný prostor). - -#### Možnosti - -* `--template` nebo `-t`: Určuje název šablony. Výchozí šablona je `app`, která generuje webovou aplikaci. Dostupné šablony: - * `app` (výchozí): [Aplikační šablona](Startup-Templates/Application.md). Dodatečné možnosti: - * `--ui` nebo `-u`: Určuje UI framework. Výchozí framework je `mvc`. Dostupné frameworky: - * `mvc`: ASP.NET Core MVC. Pro tuto šablonu jsou dostupné dodatečné možnosti: - * `--tiered`: Vytvoří stupňovité řešení, kde jsou vrstvy Web a Http API fyzicky odděleny. Pokud není uvedeno, tak vytvoří vrstvené řešení, které je méně složité a vhodné pro většinu scénářů. - * `angular`: Angular. Pro tuto šablonu jsou dostupné dodatečné možnosti: - * `--separate-auth-server`: Oddělí Auth Server aplikaci od API host aplikace. Pokud není uvedeno, bude na straně serveru jediný koncový bod. - * `none`: Bez UI. Pro tuto šablonu jsou dostupné dodatečné možnosti: - * `--separate-auth-server`: Oddělí Auth Server aplikaci od API host aplikace. Pokud není uvedeno, bude na straně serveru jediný koncový bod. - * `--database-provider` nebo `-d`: Určuje poskytovatele databáze. Výchozí poskytovatel je `ef`. Dostupní poskytovatelé: - * `ef`: Entity Framework Core. - * `mongodb`: MongoDB. - * `module`: [Šablona modulu](Startup-Templates/Module.md). Dodatečné možnosti: - * `--no-ui`: Určuje nezahrnutí uživatelského rozhraní. Umožňuje vytvořit moduly pouze pro služby (a.k.a. mikroslužby - bez uživatelského rozhraní). -* `--output-folder` nebo `-o`: Určuje výstupní složku. Výchozí hodnota je aktuální adresář. -* `--version` nebo `-v`: Určuje verzi ABP & šablony. Může to být [štítek vydání](https://github.com/abpframework/abp/releases) nebo [název větve](https://github.com/abpframework/abp/branches). Pokud není uvedeno, používá nejnovější vydání. Většinou budete chtít použít nejnovější verzi. - - -### add-package - -Přidá ABP balíček do projektu, - -* Přidáním souvisejícícho nuget balíčku jako závislost do projektu. -* Přidáním `[DependsOn(...)]` atributu k modulové tříde v projektu (podívejte se na [dokument vývoje modulu](Module-Development-Basics.md)). - -> Všimněte si, že přidaný modul může vyžadovat další konfiguraci, která je obecně uvedena v dokumentaci příslušného balíčku. - -Základní použití: - -````bash -abp add-package [možnosti] -```` - -Příklad: - -```` -abp add-package Volo.Abp.MongoDB -```` - -* Tento příklad přidá do projektu balíček Volo.Abp.MongoDB. - -#### Možnosti - -* `--project` nebo `-p`: Určuje cestu k projektu (.csproj). Pokud není zadáno, CLI se pokusí najít soubor .csproj v aktuálním adresáři. - -### add-module - -Přidá [více-balíčkový aplikační modul](Modules/Index) k řešení tím, že najde všechny balíčky modulu, vyhledá související projekty v řešení a přidá každý balíček do odpovídajícího projektu v řešení. - -> Modul se obecně skládá z několika balíčků (z důvodu vrstvení, různých možností poskytovatele databáze nebo jiných důvodů). Použití příkazu `add-module` dramaticky zjednodušuje přidání modulu do řešení. Každý modul však může vyžadovat další konfiguraci, která je obecně uvedena v dokumentaci příslušného modulu. - -Základní použití: - -````bash -abp add-module [možnosti] -```` - -Příklad: - -```bash -abp add-module Volo.Blogging -``` - -* Tento příklad přidá do projektu modul Volo.Blogging. - -#### Možnosti - -* `--solution` nebo `-s`: Určuje cestu k řešení (.sln). Pokud není zadáno, CLI se pokusí najít soubor .sln v aktuálním adresáři. -* `--skip-db-migrations`: Pro poskytovatele databáze EF Core automaticky přidá nový kód první migrace (`Add-Migration`) a v případě potřeby aktualizuje databázi (`Update-Database`). Tuto možnost určete k vynechání této operace. -* `-sp` nebo `--startup-project`: Relativní cesta ke složce spouštěcího projektu. Výchozí hodnota je aktuální adresář. -* `--with-source-code`: Místo balíčků NuGet/NPM přidejte zdrojový kód modulu. - -### update - -Aktualizace všech balíčků souvisejících s ABP může být únavná, protože existuje mnoho balíčků frameworku a modulů. Tento příkaz automaticky aktualizuje na poslední verze všechny související ABP NuGet a NPM balíčky v řešení nebo projektu. - -Použití: - -````bash -abp update [možnosti] -```` - -* Pokud spouštíte v adresáři se souborem .sln, aktualizuje všechny balíčky všech projektů v řešení souvisejících s ABP na nejnovější verze. -* Pokud spouštíte v adresáři se souborem .csproj, aktualizuje všechny balíčky v projektu na nejnovější verze. - -#### Možnosti - -* `--include-previews` nebo `-p`: Zahrne náhledové, beta a rc balíčky při kontrole nových verzí. -* `--npm`: Aktualizuje pouze balíčky NPM. -* `--nuget`: Aktualizuje pouze balíčky NuGet. - -### login - -Některé funkce CLI vyžadují přihlášení k platformě abp.io. Chcete-li se přihlásit pomocí svého uživatelského jména, napište - -```bash -abp login -``` - -Všimněte si, že nové přihlášení s již aktivní relací ukončí předchozí relaci a vytvoří novou. - -### logout - -Odhlásí vás odebráním tokenu relace z počítače. - -``` -abp logout -``` - -### help - -Vypíše základní informace k používání CLI. - -Použítí: - -````bash -abp help [název-příkazu] -```` - -Příklady: - -````bash -abp help # Zobrazí obecnou nápovědu. -abp help new # Zobrazí nápovědu k příkazu "new". -```` - diff --git a/docs/cs/Contribution/Index.md b/docs/cs/Contribution/Index.md deleted file mode 100644 index ac1aaf00c1..0000000000 --- a/docs/cs/Contribution/Index.md +++ /dev/null @@ -1,64 +0,0 @@ -## Průvodce pro přispěvatele - -ABP je [open source](https://github.com/abpframework) a komunitně řízený projekt. Tento průvodce má za cíl pomoci každému kdo chce do projektu nějak přispět. - -### Příspěvek kódu - -Vždy můžete zaslat pull request do Github repositáře. - -- Naklonujte [ABP repozitář](https://github.com/abpframework/abp/) z Githubu. -- Učiňte potřebné změny. -- Zašlete pull request. - -Než budete dělat nějaké změny, diskutujte o nich prosím na [Github problémy](https://github.com/abpframework/abp/issues). Díky tomu nebude žádný jiný vývojář pracovat na stejném problému a Váš PR má lepší šanci na to být přijat. - -#### Opravy chyb a vylepšení - -Pokud chcete opravit známou chybu nebo pracovat na plánovaném vylepšení podívejte se na [seznam problémů](https://github.com/abpframework/abp/issues) na Githubu. - -#### Požadavky na funkce - -Pokud máte nápad na funkci pro framework nebo modul [vytvořte problém](https://github.com/abpframework/abp/issues/new) na Githubu nebo se připojte ke stávající diskuzi. V případě přijetí komunitou ho pak můžete implementovat. - -### Překlad dokumentů - -Pokud chcete přeložit celou [dokumentaci](https://abp.io/documents/) (včetně této stránky) do Vašeho rodného jazyka, následujte tyto kroky: - -* Naklonujte [ABP repozitář](https://github.com/abpframework/abp/) z Githubu. -* K přidání nového jazyka vytvořte novou složku v [docs](https://github.com/abpframework/abp/tree/master/docs). Název složky musí být "en", "es", "fr", "tr" atd. v závislosti na jazyku (navštivte [všechny jazykové kódy](https://msdn.microsoft.com/en-us/library/hh441729.aspx)). -* Pro referenci použijte ["en" složku](https://github.com/abpframework/abp/tree/master/docs/en) a její názvy souborů a strom složek. Při překladu této dokumentace zachovejte prosím tyto názvy stejné. -* Zašlete pull request (PR) po překladu jakéhokoliv dokumentu klidně i po jednom. Nečekejte až budete mít překlad všech dokumentů. - -Existuje několik základních dokumentů, které je třeba přeložit než bude jazyk uveřejněn na [stránkách ABP dokumentace](https://docs.abp.io) - -* Začínáme dokumenty -* Tutoriály -* CLI - -Nový jazyk je publikován jakmile jsou minimálně tyto překlady dokončeny. - -### Lokalizace zdrojů - -ABP framework má flexibilní [lokalizační systém](../Localization.md). Můžete tak vytvořit lokalizované uživatelské prostředí pro svou vlastní aplikaci. - -K tomu mají framework a vestavěné moduly již lokalizované texty. Například [lokalizační texty pro Volo.Abp.UI balík](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). - -Můžete vytvořit nový soubor ve [stejné složce](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi) k přidání překladu. - -* Naklonujte [ABP repozitář](https://github.com/abpframework/abp/) z Githubu. -* Vytvořte nový soubor pro cílový jazyk pro lokalizační text v (json) souboru (u souboru en.json). -* Zkopírujte veškerý text ze souboru en.json. -* Přeložte texty. -* Zašlete pull request na Githubu. - -K překladu lokalizovaných textů můžete také použít příkaz `abp translate` of [ABP CLI](CLI.md). - -ABP je modulářní framework, proto je zde mnoho zdrojů lokalizačních textů, jeden pro každý modul. K najití všech .json souborů, vyhledejte po naklonování repozitáře soubory "en.json". Můžete se taky podívat na [tento seznam](Localization-Text-Files.md) souborů lokalizačních textů. - -### Příspevky do blogu a návody - -Pokud se rozhodnete pro ABP vytvořit nějaké návody nebo příspěvky do blogu, dejte nám vědět (prostřednictvím [Github problémy](https://github.com/abpframework/abp/issues)), ať můžeme přidat odkaz na Váš návod/příspěvek v oficiální dokumentaci a oznámit na našem [Twitter účtu](https://twitter.com/abpframework). - -### Zpráva o chybě - -Pokud najdete chybu, [vytvořte prosím problém v Github repozitáři](https://github.com/abpframework/abp/issues/new). diff --git a/docs/cs/Dapper.md b/docs/cs/Dapper.md deleted file mode 100644 index 94e40347f0..0000000000 --- a/docs/cs/Dapper.md +++ /dev/null @@ -1,61 +0,0 @@ -# Dapper integrace - -Jelikož myšlenka Dapper je taková, že sql příkaz má přednost, tak hlavně poskytuje metody rozšíření pro `IDbConnection` rozhraní. - -Abp nezapouzdřuje přílíš mnoho funkcí pro Dapper. Abp Dapper poskytuje základní třídu `DapperRepository` založenou na Abp EntityFrameworkCore, který poskytuje vlastnosti `IDbConnection` a `IDbTransaction` vyžadované v Dapper. - -Tyto dvě vlastnosti fungují dobře s [jednotkou práce](Unit-Of-Work.md). - -## Instalace - -Nainstalujte a nakonfigurujte EF Core podle [EF Core integrační dokumentace](Entity-Framework-Core.md). - -`Volo.Abp.Dapper` je hlavní NuGet balík pro Dapper integraci. Nainstalujte jej proto do vašeho projektu (pro strukturovanou aplikaci do datové/infrastrukturní vrstvy): - -```shell -Install-Package Volo.Abp.Dapper -``` - -Poté přidejte závislost na `AbpDapperModule` modulu (atribut `DependsOn`) do Vašeho [modulu](Module-Development-Basics.md): - -````C# -using Volo.Abp.Dapper; -using Volo.Abp.Modularity; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpDapperModule))] - public class MyModule : AbpModule - { - //... - } -} -```` - -## Implementace Dapper repozitáře - -Následující kód implementuje repozitář `Person`, který vyžaduje `DbContext` z EF Core (MyAppDbContext). Můžete vložit `PersonDapperRepository` k volání jeho metod. - -`DbConnection` a `DbTransaction` jsou ze základní třídy `DapperRepository`. - -```C# -public class PersonDapperRepository : DapperRepository, ITransientDependency -{ - public PersonDapperRepository(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public virtual async Task> GetAllPersonNames() - { - return (await DbConnection.QueryAsync("select Name from People", transaction: DbTransaction)) - .ToList(); - } - - public virtual async Task UpdatePersonNames(string name) - { - return await DbConnection.ExecuteAsync("update People set Name = @NewName", new { NewName = name }, - DbTransaction); - } -} -``` diff --git a/docs/cs/Domain-Driven-Design.md b/docs/cs/Domain-Driven-Design.md deleted file mode 100644 index 161e92c8a2..0000000000 --- a/docs/cs/Domain-Driven-Design.md +++ /dev/null @@ -1,33 +0,0 @@ -# Domain Driven Design - -## Co je DDD? - -ABP framework poskytuje **infrastrukturu**, která zjednodušuje implementaci vývoje založeného na **DDD**. DDD je [definován ve Wikipedii](https://en.wikipedia.org/wiki/Domain-driven_design) takto: - -> **Domain-driven design** (**DDD**) je přístup k vývoji softwaru pro komplexní potřeby propojením implementace s vyvíjejícím se modelem. Předpoklad DDD je následující: -> -> - Primární zaměření projektu je na jádře domény a doménové logice; -> - Zakládání komplexních návrhů na modelu domény; -> - Iniciování tvůrčí spolupráce mezi technickými a doménovými odborníky s cílem iterativně zdokonalit koncepční model, který řeší konkrétní problémy v doméně. - -### Vrstvy - -ABP dodržuje principy a vzorce DDD pro dosažení vrstveného aplikačního modelu, který se skládá ze čtyř základních vrstev: - -- **Prezentační vrstva**: Poskytuje uživateli rozhraní. Používá *Aplikační vrstvu* k dosažení uživatelských interakcí. -- **Aplikační vrstva**: Prostředník mezi prezentační a doménovou vrstvou. Instrumentuje business objekty k provádění specifických úloh aplikace. Implementuje případy použití jako logiku aplikace. -- **Doménová vrstva**: Zahrnuje business objekty a jejich business pravidla. Je jádrem aplikace. -- **Vrstva infrastruktury**: Poskytuje obecné technické možnosti, které podporují vyšší vrstvy většinou pomocí knihoven třetích stran. - -## Obsah - -* **Doménová vrstva** - * [Entity & agregované kořeny](Entities.md) - * Hodnotové objekty - * [Repozitáře](Repositories.md) - * Doménové služby - * Specifikace -* **Aplikační vrstva** - * [Aplikační služby](Application-Services.md) - * [Objekty přenosu dat (DTOs)](Data-Transfer-Objects.md) - * Jednotka práce \ No newline at end of file diff --git a/docs/cs/Entity-Framework-Core-PostgreSQL.md b/docs/cs/Entity-Framework-Core-PostgreSQL.md deleted file mode 100644 index dc50c8a742..0000000000 --- a/docs/cs/Entity-Framework-Core-PostgreSQL.md +++ /dev/null @@ -1,39 +0,0 @@ -# Přepnutí na EF Core PostgreSQL providera - -Tento dokument vysvětluje, jak přepnout na poskytovatele databáze **PostgreSQL** pro **[spouštěcí šablonu aplikace](Startup-Templates/Application.md)**, která je dodávána s předem nakonfigurovaným SQL poskytovatelem. - -## Výměna balíku Volo.Abp.EntityFrameworkCore.SqlServer - -Projekt `.EntityFrameworkCore` v řešení závisí na NuGet balíku [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer). Odstraňte tento balík a přidejte stejnou verzi balíku [Volo.Abp.EntityFrameworkCore.PostgreSql](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.PostgreSql). - -## Nahrazení závislosti modulu - -Najděte třídu ***YourProjectName*EntityFrameworkCoreModule** v projektu `.EntityFrameworkCore`, odstraňte `typeof(AbpEntityFrameworkCoreSqlServerModule)` z atributu `DependsOn`, přidejte `typeof(AbpEntityFrameworkCorePostgreSqlModule)` (také nahraďte `using Volo.Abp.EntityFrameworkCore.SqlServer;` za `using Volo.Abp.EntityFrameworkCore.PostgreSql;`). - -## UseNpgsql() - -Najděte volání `UseSqlServer()` v *YourProjectName*EntityFrameworkCoreModule.cs uvnitř projektu `.EntityFrameworkCore` a nahraďte za `UseNpgsql()`. - -Najděte volání `UseSqlServer()` v *YourProjectName*MigrationsDbContextFactory.cs uvnitř projektu `.EntityFrameworkCore.DbMigrations` a nahraďte za `UseNpgsql()`. - -> V závislosti na struktuře řešení můžete najít více volání `UseSqlServer()`, které je třeba změnit. - -## Změna connection stringů - -PostgreSql connection stringy se od těch pro SQL Server liší. Je proto potřeba zkontrolovat všechny soubory `appsettings.json` v řešení a connection stringy v nich nahradit. Podívejte se na [connectionstrings.com](https://www.connectionstrings.com/postgresql/) pro více detailů o možnostech PostgreSql connection stringů. - -Typicky je potřeba změnit `appsettings.json` v projektech `.DbMigrator` a `.Web` projects, ale to záleží na vaší struktuře řešení. - -## Regenerace migrací - -Startovací šablona používá [Entity Framework Core Code First migrace](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core migrace závisí na zvoleném DBMS poskytovateli. Tudíž změna DBMS poskytovatele způsobí selhání migrace. -* Smažte složku Migrations v projektu `.EntityFrameworkCore.DbMigrations` and znovu sestavte řešení. -* Spusťte `Add-Migration "Initial"` v Package Manager Console (je nutné zvolit `.DbMigrator` (nebo `.Web`) projekt jako startovací projekt v Solution Explorer a zvolit projekt `.EntityFrameworkCore.DbMigrations` jako výchozí v Package Manager Console). - -Tímto vytvoříte migraci databáze se všemi nakonfigurovanými databázovými objekty (tabulkami). - -Spusťte projekt `.DbMigrator` k vytvoření databáze a vložení počátečních dat. - -## Spuštění aplikace - -Vše je připraveno. Stačí už jen spustit aplikaci a užívat si kódování. diff --git a/docs/cs/Getting-Started-Angular-Template.md b/docs/cs/Getting-Started-Angular-Template.md deleted file mode 100644 index 076fb45671..0000000000 --- a/docs/cs/Getting-Started-Angular-Template.md +++ /dev/null @@ -1,126 +0,0 @@ -## Začínáme s Angular aplikační šablonou - -Tento tutoriál vysvětluje, jak vytvořit novou Angular aplikaci pomocí spouštěcí šablony, jak ji nakonfigurovat a spustit. - -### Tvorba nového projektu - -Tento tutorial používá k vytvoření nového projektu **ABP CLI**. Podívejte se na stránku [začínáme](https://abp.io/get-started) pro více možností. - -Pokud jste tak dosud neučinili, nainstalujte ABP CLI pomocí okna příkazového řádku: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Použíjte příkaz `abp new` v prázdné složce k vytvoření Vašeho projektu: - -````bash -abp new Acme.BookStore -u angular -```` - -> Můžete použít různé úrovně jmenných prostorů; např. BookStore, Acme.BookStore nebo Acme.Retail.BookStore. - -`-u angular` volba specifikuje Angular jako UI framework. Výchozí poskytovatel databáze je EF Core. Podívejte se na [CLI dokumentaci](CLI.md) pro všechny dostupné možnosti. - -#### Předběžné požadavky - -Vytvořené řešení vyžaduje; - -* [Visual Studio 2019 (v16.4.0+)](https://visualstudio.microsoft.com/vs/) -* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) -* [Node v12+](https://nodejs.org) -* [Yarn v1.19+](https://classic.yarnpkg.com/) - -### Struktura řešení - -Otevřete řešení ve **Visual Studio**: - -![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-for-spa.png) - -Řešení má vrstvenou strukturu (založenou na [domain driven designu](Domain-Driven-Design.md)) a obsahuje projekty testů jednotek a integrace správně nakonfigurované pro práci s **EF Core** & **SQLite in-memory** databází. - -> Podívejte se na [dokument šablony aplikace](Startup-Templates/Application.md) k detailnímu pochopení struktury řešení. - -### Databázový connection string - -Zkontrolujte **connection string** v souboru `appsettings.json` u projektu `.HttpApi.Host`: - -````json -{ - "ConnectionStrings": { - "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" - } -} -```` - -Řešení je nakonfigurováno pro použití **Entity Framework Core** s **MS SQL Server**. EF Core podporuje [různé](https://docs.microsoft.com/en-us/ef/core/providers/) poskytovatele databáze, takže pokud chcete můžete použít jiný DBMS. V případě potřeby změňte connection string. - -### Tvorba databáze & aplikace migrací databáze - -K vytvoření databáze máte dvě možnosti. - -#### Použití aplikace DbMigrator - -Řešení obsahuje konzolovou aplikaci (v tomto příkladu nazvanou `Acme.BookStore.DbMigrator`), která dokáže vytvořit databázi, aplikovat migrace a vložit počáteční data. Ta je užitečná jak pro vývojové tak pro produkční prostředí. - -> `.DbMigrator` má vlastní `appsettings.json`. Pokud jste změnili connection string výše, měli byste změnit i tento. - -Klikněte pravým na projekt `.DbMigrator` zvolte **Set as StartUp Project**: - -![set-as-startup-project](images/set-as-startup-project.png) - -Zmáčkněte F5 (nebo Ctrl+F5) ke spuštění aplikace. Výstup by měl být podobný vyobrazení níže: - -![set-as-startup-project](images/db-migrator-app.png) - -#### Použití příkazu EF Core Update-Database - -Ef Core máš příkaz `Update-Database`, který v případě potřeby vytvoří databázi a aplikuje čekající migrace. Klikněte pravým na projekt `.HttpApi.Host` a zvolte **Set as StartUp Project**: - -![set-as-startup-project](images/set-as-startup-project.png) - -Otevřete **Package Manager Console**, zvolte `.EntityFrameworkCore.DbMigrations` jako **Default Project** a proveďte příkaz `Update-Database`: - -![pcm-update-database](images/pcm-update-database-v2.png) - -Tímto vytvoříte novou databáze podle nakonfigurovaného connection string. - -> Je doporučeno užití nástroje `.DbMigrator`, protože zároveň vloží i počáteční data ke správnému běhu webové aplikace. - -### Spuštění aplikace - -#### Spuštění API Host (na straně serveru) - -Ujistěte se že je projekt `.HttpApi.Host` nastaven jako startovací a spusťte aplikaci což otevře Swagger UI: - -![bookstore-homepage](images/bookstore-swagger-ui-host.png) - -Tady můžete vidět API aplikace a zároveň je i otestovat. Získejte [více informací](https://swagger.io/tools/swagger-ui/) o Swagger UI. - -##### Autorizace pro Swagger UI - -Vetšina API aplikace vyžaduje autentizaci & autorizaci. Pokud chcete otestovat autorizované API, manuálně přejděte na stránku `/Account/Login`, vložte `admin` jako uživatelské jméno a `1q2w3E*` jako heslo k příhlášení do aplikace. Poté budete moci provádět autorizované požadavky API. - -#### Spuštění Angular aplikace (na straně klienta) - -Přejděte do složky `angular`, otevřete terminál příkazového řádku, proveďte příkaz `yarn` (doporučujeme používat správce balíků [yarn](https://yarnpkg.com), npm install bude v mnoha případech také fungovat): - -````bash -yarn -```` - -Jakmile jsou načteny všechny node moduly, proveďte příkaz `yarn start` nebo `npm start`: - -````bash -yarn start -```` - -Otevřete Váš oblíbený prohlížeč a přejděte na adresu `localhost:4200`. Počáteční uživatelské jméno je `admin` a heslo `1q2w3E*`. - -Startovací šablona obsahuje moduly **správa identit** a **správa tenantů**. Jakmile se přihlásíte, zprístupní se administrační menu kde můžete spravovat **tenanty**, **role**, **uživatele** a jejich **oprávnění**. - -> Doporučujeme [Visual Studio Code](https://code.visualstudio.com/) jako editor pro Angular projekt, ale klidně použijte Váš oblíbený editor. - -### Co dále? - -* [Tutoriál vývoje aplikace](Tutorials/Angular/Part-I.md) diff --git a/docs/cs/Getting-Started-AspNetCore-Application.md b/docs/cs/Getting-Started-AspNetCore-Application.md deleted file mode 100644 index e269a08ba2..0000000000 --- a/docs/cs/Getting-Started-AspNetCore-Application.md +++ /dev/null @@ -1,157 +0,0 @@ -# Začínáme s ASP.NET Core MVC aplikací - -Tento tutoriál vysvětluje jak začít s ABP z ničeho s minimem závislostí. Obvykle chcete začít se **[startovací šablonou](https://abp.io/Templates)**. - -## Tvorba nového projektu - -1. Vytvořte novou AspNet Core Web aplikaci ve Visual Studio 2019 (16.4.0+): - -![](images/create-new-aspnet-core-application-v2.png) - -2. Nakonfigurujte váš nový projekt: - -![](images/select-empty-web-application-v2.png) - -3. Potvrďte kliknutím na tlačítko vytvořit - -![create-aspnet-core-application](images/create-aspnet-core-application.png) - -## Instalace Volo.Abp.AspNetCore.Mvc balíku - -Volo.Abp.AspNetCore.Mvc je AspNet Core MVC integrační balík pro ABP. Takže ho nainstalujeme do projektu: - -```` -Install-Package Volo.Abp.AspNetCore.Mvc -```` - -## Tvorba prvního ABP modulu - -ABP je modulární framework a proto vyžaduje **spouštěcí (kořenový) modul** což je třída dědící z ``AbpModule``: - -````C# -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Hosting; -using Volo.Abp; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.Modularity; - -namespace BasicAspNetCoreApplication -{ - [DependsOn(typeof(AbpAspNetCoreMvcModule))] - public class AppModule : AbpModule - { - public override void OnApplicationInitialization( - ApplicationInitializationContext context) - { - var app = context.GetApplicationBuilder(); - var env = context.GetEnvironment(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - } - - app.UseStaticFiles(); - app.UseRouting(); - app.UseConfiguredEndpoints(); - } - } -} -```` - -``AppModule`` je dobrý název pro spouštěcí modul aplikace. - -ABP balíky definují modulové třídy a modul může mít závislost na jiném. V kódu výše, ``AppModule`` má závislost na ``AbpAspNetCoreMvcModule`` (definován v balíku [Volo.Abp.AspNetCore.Mvc](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc)). Je běžné přidat ``DependsOn`` atribute po instalaci nového ABP NuGet balíku. - -Místo třídy Startup, konfigurujeme ASP.NET Core pipeline v této modulové třídě. - -## Třída Startup - -V dalším kroku upravíme Startup třídu k integraci ABP modulového systému: - -````C# -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace BasicAspNetCoreApplication -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddApplication(); - } - - public void Configure(IApplicationBuilder app) - { - app.InitializeApplication(); - } - } -} -```` - -``services.AddApplication()`` přidává všechny služby definované ve všech modulech počínaje od ``AppModule``. - -``app.InitializeApplication()`` v metodě ``Configure`` inicializuje a spustí aplikaci. - -## Spusťte aplikaci! - -To je vše! Spusťte aplikaci, bude fungovat podle očekávání. - -## Použití Autofac jako frameworku pro vkládání závislostí - -Ačkoliv je AspNet Core systém pro vkládání závíslostí (DI) dostatečný pro základní požadavky, [Autofac](https://autofac.org/) poskytuje pokročilé funkce jako injekce vlastností nebo záchyt metod, které jsou v ABP užity k provádění pokročilých funkcí frameworku. - -Nahrazení AspNet Core DI systému za Autofac a integrace s ABP je snadná. - -1. Nainstalujeme [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) balík - -```` -Install-Package Volo.Abp.Autofac -```` - -2. Přidáme ``AbpAutofacModule`` závislost - -````C# -[DependsOn(typeof(AbpAspNetCoreMvcModule))] -[DependsOn(typeof(AbpAutofacModule))] // Přidá závislost na AbpAutofacModule -public class AppModule : AbpModule -{ - ... -} -```` - -3. Upravíme `Program.cs` aby používal Autofac: - -````csharp -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - -namespace BasicAspNetCoreApplication -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseAutofac(); // Přidejte tento řádek - } -} -```` - -## Zdrojový kód - -Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication). - diff --git a/docs/cs/Getting-Started-AspNetCore-MVC-Template.md b/docs/cs/Getting-Started-AspNetCore-MVC-Template.md deleted file mode 100644 index c053e1d76a..0000000000 --- a/docs/cs/Getting-Started-AspNetCore-MVC-Template.md +++ /dev/null @@ -1,104 +0,0 @@ -## Začínáme s ASP.NET Core MVC šablonou - -Tento tutoriál vysvětluje, jak vytvořit novou ASP.NET Core MVC webovou aplikaci pomocí úvodní šablony, jak ji nakonfigurovat a spustit. - -### Tvorba nového projektu - -Tento tutoriál používá k tvorbě nového projektu **ABP CLI**. Podívejte se na stránku [Začínáme](https://abp.io/get-started) pro více možností. - -Pokud ještě nemáte ABP CLI nainstalováno, učiňte tak pomocí okna příkazového řádku: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -K tvorbě vašeho projektu použijte příkaz `abp new` v prázdné složce: - -````bash -abp new Acme.BookStore -```` - -> Můžete použít různé úrovně jmenných prostorů; např. BookStore, Acme.BookStore nebo Acme.Retail.BookStore. - -Příkaz `new` vytvoří **vrstvenou MVC aplikaci** s **Entity Framework Core** jako databázovým poskytovatelem. Jsou zde však i jiné možnosti. Podívejte se na [CLI dokumnentaci](CLI.md) pro všechny další možností. - -#### Požadavky - -Vytvořené řešení vyžaduje; - -* [Visual Studio 2019 (v16.4.0+)](https://visualstudio.microsoft.com/vs/) -* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) -* [Node v12+](https://nodejs.org) -* [Yarn v1.19+](https://classic.yarnpkg.com/) - -### Struktura řešení - -Otevřete řešení ve **Visual Studio**: - -![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png) - -Řešení má vrstvenou strukturu (založenou na [Domain Driven Design](Domain-Driven-Design.md)) a obsahuje projekty jednotkovových a integračních testů předkonfigurované pro práci s **EF Core** & **SQLite in-memory** databází. - -> Podívejte se na [dokument šablony aplikace](Startup-Templates/Application.md) k detailnímu pochopení struktury řešení. - -### Connection string databáze - -Zkontrolujte **connection string** v souboru `appsettings.json` v projektu `.Web`: - -````json -{ - "ConnectionStrings": { - "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" - } -} -```` - -Řešení je nakonfigurováno k používání **Entity Framework Core** s **MS SQL Server**. EF Core podporuje [různé](https://docs.microsoft.com/en-us/ef/core/providers/) databázové poskytovatele, takže můžete použít i jiné DBMS. V případě potřeby změňte connection string. - -### Tvorba databáze & aplikace databázových migrací - -K vytvoření databáze máte dvě možnosti. - -#### Použití DbMigrator aplikace - -Řešení obsahuje konzolovou aplikaci (v tomto příkladu nazvanou `Acme.BookStore.DbMigrator`), která může vytvářet databáze, aplikovat migrace a vkládat seed data. Je užitečná jak pro vývojové, tak pro produkční prostředí. - -> Projekt `.DbMigrator` má vlastní `appsettings.json`. Takže pokud jste změnili connection string uvedený výše, musíte změnit také tento. - -Klikněte pravým na projekt `.DbMigrator` a vyberte **Set as StartUp Project**: - -![set-as-startup-project](images/set-as-startup-project.png) - -Zmáčkněte F5 (nebo Ctrl+F5) ke spuštění aplikace. Výstup bude vypadat následovně: - -![set-as-startup-project](images/db-migrator-app.png) - -#### Použití EF Core Update-Database příkazu - -Ef Core má `Update-Database` příkaz, který v případě potřeby vytvoří databázi a aplikuje čekající migrace. Klikněte pravým na projekt `.Web` a vyberte **Set as StartUp Project**: - -![set-as-startup-project](images/set-as-startup-project.png) - -Otevřete **Package Manager Console**, vyberte projekt `.EntityFrameworkCore.DbMigrations` jako **Default Project** and spusťte příkaz `Update-Database`: - -![pcm-update-database](images/pcm-update-database-v2.png) - -Dojde k vytvoření nové databáze na základě nakonfigurovaného connection stringu. - -> Použití nástroje `.Migrator` je doporučený způsob, jelikož zároveň vloží seed data nutné k správnému běhu webové aplikace. - -### Spuštění aplikace - -Ujistěte se že je projekt `.Web` nastaven jako startovací projekt. Spusťte aplikaci což následně otevře **úvodní** stránku ve vašem prohlížeči: - -![bookstore-homepage](images/bookstore-homepage.png) - -Klikněte na tlačítko **Přihlásit**, vložte `admin` jako uživatelské jméno a `1q2w3E*` jako heslo k přihlášení do aplikace. - -Startovací šabloná obsahuje **identity management** a **tenant management** moduly. Jakmile se přihlásite, budete mít přístup do nabídky Administrace, kde můžete spravovat **tenanty**, **role**, **uživatele** a jejich **oprávnění**. Správa uživatelů vypadá takto: - -![bookstore-user-management](images/bookstore-user-management-v2.png) - -### Co dále? - -* [Tutoriál vývoje aplikace](Tutorials/AspNetCore-Mvc/Part-I.md) diff --git a/docs/cs/Getting-Started-Console-Application.md b/docs/cs/Getting-Started-Console-Application.md deleted file mode 100644 index a6ee50dbe6..0000000000 --- a/docs/cs/Getting-Started-Console-Application.md +++ /dev/null @@ -1,181 +0,0 @@ -# Začínáme s konzolovou aplikací - -Tento tutoriál vysvětluje jak začít s ABP z ničeho s minimem závislostí. Obvykle chcete začít se **[startovací šablonou](https://abp.io/Templates)**. - -## Tvorba nového projektu - -Vytvořte regulérní .NET Core konzolovou aplikaci z Visual Studio: - -![](images/create-new-net-core-console-application.png) - -## Instalace Volo.Abp balíku - -Volo.Abp.Core je základní NuGet balík k tvorbě aplikací založených na ABP. Takže ho nainstalujeme do projektu: - -```` -Install-Package Volo.Abp.Core -```` - -## Tvorba prvního ABP modulu - -ABP je modulární framework a proto vyžaduje **spouštěcí (kořenový) modul** což je třída dědící z ``AbpModule``: - -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; - -namespace AbpConsoleDemo -{ - public class AppModule : AbpModule - { - - } -} -```` - -``AppModule`` je dobrý název pro spouštěcí modul aplikace. - -## Inicializace aplikace - -Dalším krokem je bootstrap aplikace pomocí spouštěcího modulu vytvořeného výše: - -````C# -using System; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create()) - { - application.Initialize(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} - -```` - -``AbpApplicationFactory`` se používá k vytvoření aplikace a načtení všech modulů, s využitím ``AppModule`` jako spouštěcím modulem. ``Initialize()`` metoda spouští aplikaci. - -## Ahoj světe! - -Aplikace výše zatím nic nedělá. Pojďme proto vytvořit službu která už něco dělá: - -````C# -using System; -using Volo.Abp.DependencyInjection; - -namespace AbpConsoleDemo -{ - public class HelloWorldService : ITransientDependency - { - public void SayHello() - { - Console.WriteLine("Hello World!"); - } - } -} - -```` - -``ITransientDependency`` je speciální rozhraní ABP, které automaticky registruje službu jako přechodnou (více v [dokumentu vkládání závislostí](Dependency-Injection.md)). - -Nyní můžeme vyřešit ``HelloWorldService`` a vypsat naše ahoj. Změníme Program.cs podle vyobrazení níže: - -````C# -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create()) - { - application.Initialize(); - - // Vyřeší službu a použije ji - var helloWorldService = - application.ServiceProvider.GetService(); - helloWorldService.SayHello(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} -```` - -I když je to dostačující pro tento jednoduchý príklad kódu, je vždy lepší v případě přímého řešení závislostí z ``IServiceProvider`` vytvořit rámce (více v [dokumentu vkládání závislostí](Dependency-Injection.md)). - -## Využití Autofac jako frameworku pro vkládání závislostí - -Ačkoliv je AspNet Core systém pro vkládání závíslostí (DI) skvělý pro základní požadavky, Autofac poskytuje pokročilé funkce jako injekce vlastností nebo záchyt metod, které jsou v ABP užity k provádění pokročilých funkcí frameworku. - -Nahrazení AspNet Core DI systému za Autofac a integrace s ABP je snadná. - -1. Nainstalujeme [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) balík - -``` -Install-Package Volo.Abp.Autofac -``` - -1. Přidáme ``AbpAutofacModule`` závislost - -```c# -[DependsOn(typeof(AbpAutofacModule))] // Přidá závislost na AbpAutofacModule -public class AppModule : AbpModule -{ - -} -``` - -1. Změníme soubor ``Program.cs`` podle vyobrazení níže: - -```c# -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create(options => - { - options.UseAutofac(); // Autofac integrace - })) - { - application.Initialize(); - - // Vyřeší službu a použije ji - var helloWorldService = - application.ServiceProvider.GetService(); - helloWorldService.SayHello(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} -``` - -Stačí volat metodu `options.UseAutofac()` v možnostech `AbpApplicationFactory.Create`. - -## Zdrojový kód - -Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication). diff --git a/docs/cs/Index.md b/docs/cs/Index.md deleted file mode 100644 index 875f4e066e..0000000000 --- a/docs/cs/Index.md +++ /dev/null @@ -1,25 +0,0 @@ -# ABP dokumentace - -ABP je **open source aplikační framework** se zaměřením na vývoj webových aplikací založených na ASP.NET Core, zároveň ho však lze využít i k vývoji jiných typů aplikací. - -K procházení dokumentace využijte navigační nabídky vlevo. - -## Začínáme - -Nejsnazší cestou jak začít nový projekt s ABP je užití startovací šablony: - -* [ASP.NET Core MVC (Razor Pages) UI Počáteční Šablona](Getting-Started-AspNetCore-MVC-Template.md) -* [Angular UI Počáteční Šablona](Getting-Started-Angular-Template.md) - -Pokud chcete začít od nuly (s prázdným projektem) tak manuálně nainstalujte ABP Framework s pomocí následujících tutoriálů: - -* [Konzolová Aplikace](Getting-Started-Console-Application.md) -* [ASP.NET Core Web Aplikace](Getting-Started-AspNetCore-Application.md) - -## Zdrojový kód - -ABP je hostovaný na GitHub. Zobrazit [zdrojový kód](https://github.com/abpframework/abp). - -## Chcete přispět? - -ABP je komunitně řízený open source projekt. Podívejte se na [průvodce pro přispěvatele](Contribution/Index.md) pokud chcete být součástí tohoto projektu. diff --git a/docs/cs/Nightly-Builds.md b/docs/cs/Nightly-Builds.md deleted file mode 100644 index e69588f5ff..0000000000 --- a/docs/cs/Nightly-Builds.md +++ /dev/null @@ -1,26 +0,0 @@ -# Noční sestavení - -Všechny balíky frameworku a modulů jsou každý večer nasazeny na MyGet. Takže můžete používat nebo testovat nejnovější kód bez čekání na další vydání. - -## Konfigurace Visual Studia - -> Vyžaduje Visual Studio 2017+ - -1. Přejděte do `Tools > Options > NuGet Package Manager > Package Source`. -2. Klikněte na zelenou ikonku `+`. -3. Nastavte `ABP Nightly` jako *Name* a `https://www.myget.org/F/abp-nightly/api/v3/index.json` jako *Source* podle vyobrazení níže: - ![night-build-add-nuget-source](images/night-build-add-nuget-source.png) -4. Klikněte na `Update`. -5. Klikněte na `OK` k uložení změn. - -## Instalace balíku - -Nyní můžete instalovat náhledové / noční balíky do Vašeho projektu z NuGet prohlížeče nebo Package Manager Console. - -![night-build-add-nuget-package](images/night-build-add-nuget-package.png) - -1. V nuget prohlížeči, vyberte "Include prereleases". -2. Změňte zdroj balíků na "All". -3. Vyhledejte balík. Uvidíte prerelease balík formátovaný jako `(VERZE)-preview(DATUM)` (např *v0.16.0-preview20190401* jako v tomto vzorku). -4. Můžete kliknout na `Install` k přídání balíku do projektu. - diff --git a/docs/cs/docs-nav.json b/docs/cs/docs-nav.json deleted file mode 100644 index 6c05d77dcf..0000000000 --- a/docs/cs/docs-nav.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "items": [ - { - "text": "Začínáme", - "items": [ - { - "text": "Ze startovacích šablon", - "items": [ - { - "text": "Aplikace s MVC (Razor Pages) UI", - "path": "Getting-Started-AspNetCore-MVC-Template.md" - }, - { - "text": "Aplikace s Angular UI", - "path": "Getting-Started-Angular-Template.md" - } - ] - }, - { - "text": "Z prázdných projektů", - "items": [ - { - "text": "S ASP.NET Core Web aplikací", - "path": "Getting-Started-AspNetCore-Application.md" - }, - { - "text": "S konzolovou aplikací", - "path": "Getting-Started-Console-Application.md" - } - ] - } - ] - }, - { - "text": "Tutoriály", - "items": [ - { - "text": "Vývoj aplikace", - "items": [ - { - "text": "S ASP.NET Core MVC UI", - "path": "Tutorials/AspNetCore-Mvc/Part-I.md" - }, - { - "text": "S Angular UI", - "path": "Tutorials/Angular/Part-I.md" - } - ] - } - ] - }, - { - "text": "CLI", - "path": "CLI.md" - }, - { - "text": "Základy", - "items": [ - { - "text": "Konfigurace", - "path": "Configuration.md" - }, - { - "text": "Možnosti", - "path": "Options.md" - }, - { - "text": "Vkládání závislostí", - "path": "Dependency-Injection.md", - "items": [ - { - "text": "AutoFac integrace", - "path": "Autofac-Integration.md" - } - ] - }, - { - "text": "Virtuální systém souborů", - "path": "Virtual-File-System.md" - }, - { - "text": "Lokalizace", - "path": "Localization.md" - }, - { - "text": "Zpracování výjimek", - "path": "Exception-Handling.md" - }, - { - "text": "Validace", - "path": "Validation.md", - "items": [ - { - "text": "FluentValidation integrace", - "path": "FluentValidation.md" - } - ] - }, - { - "text": "Autorizace", - "path": "Authorization.md" - }, - { - "text": "Ukládání do mezipaměti", - "path": "Caching.md" - }, - { - "text": "Audit" - }, - { - "text": "Nastavení", - "path": "Settings.md" - } - ] - }, - { - "text": "Události", - "items": [ - { - "text": "Event bus (místní)" - }, - { - "text": "Distribuovaný event bus", - "items": [ - { - "text": "RabbitMQ integrace" - } - ] - } - ] - }, - { - "text": "Služby", - "items": [ - { - "text": "Současný uživatel", - "path": "CurrentUser.md" - }, - { - "text": "Mapování objekt na objekt", - "path": "Object-To-Object-Mapping.md" - }, - { - "text": "Serializace objektu" - }, - { - "text": "Serializace JSON" - }, - { - "text": "Emailování" - }, - { - "text": "GUIDy" - }, - { - "text": "Vláknování" - }, - { - "text": "Časování" - } - ] - }, - { - "text": "Multitenance", - "path": "Multi-Tenancy.md" - }, - { - "text": "Vývoj modulů", - "items": [ - { - "text": "Základy", - "path": "Module-Development-Basics.md" - }, - { - "text": "Zásuvné moduly" - }, - { - "text": "Nejlepší praktiky", - "path": "Best-Practices/Index.md" - } - ] - }, - { - "text": "Domain driven design", - "path": "Domain-Driven-Design.md", - "items": [ - { - "text": "Doménová vrstva", - "items": [ - { - "text": "Entity & agregované kořeny", - "path": "Entities.md" - }, - { - "text": "Hodnotové objekty" - }, - { - "text": "Repozitáře", - "path": "Repositories.md" - }, - { - "text": "Doménové služby" - }, - { - "text": "Specifikace" - } - ] - }, - { - "text": "Aplikační vrstva", - "items": [ - { - "text": "Aplikační služby", - "path": "Application-Services.md" - }, - { - "text": "Objekty přenosu dat" - }, - { - "text": "Jednotka práce" - } - ] - } - ] - }, - { - "text": "ASP.NET Core", - "items": [ - { - "text": "API", - "items": [ - { - "text": "Automatické API řadiče", - "path": "AspNetCore/Auto-API-Controllers.md" - }, - { - "text": "Dynamičtí C# API klienti", - "path": "AspNetCore/Dynamic-CSharp-API-Clients.md" - } - ] - }, - { - "text": "Uživatelské rozhraní", - "items": [ - { - "text": "Správa klientských balíčků", - "path": "AspNetCore/Client-Side-Package-Management.md" - }, - { - "text": "Svazování & minifikace", - "path": "AspNetCore/Bundling-Minification.md" - }, - { - "text": "Tag pomocníci", - "path": "AspNetCore/Tag-Helpers/Index.md" - }, - { - "text": "Widgety", - "path": "AspNetCore/Widgets.md" - }, - { - "text": "Motivy", - "path": "AspNetCore/Theming.md" - } - ] - } - ] - }, - { - "text": "Přístup k datům", - "path": "Data-Access.md", - "items": [ - { - "text": "Connection stringy", - "path": "Connection-Strings.md" - }, - { - "text": "Poskytovatelé databází", - "items": [ - { - "text": "Entity Framework Core", - "path": "Entity-Framework-Core.md", - "items": [ - { - "text": "Přepnutí na MySQL", - "path": "Entity-Framework-Core-MySQL.md" - }, - { - "text": "Přepnutí na PostgreSQL", - "path": "Entity-Framework-Core-PostgreSQL.md" - }, - { - "text": "Přepnutí na SQLite", - "path": "Entity-Framework-Core-SQLite.md" - }, - { - "text": "Přepnutí na jiný DBMS", - "path": "Entity-Framework-Core-Other-DBMS.md" - } - ] - }, - { - "text": "MongoDB", - "path": "MongoDB.md" - }, - { - "text": "Dapper", - "path": "Dapper.md" - } - ] - } - ] - }, - { - "text": "Pozadí", - "items": [ - { - "text": "Úkony na pozadí", - "path": "Background-Jobs.md", - "items": [ - { - "text": "Hangfire integrace", - "path": "Background-Jobs-Hangfire.md" - }, - { - "text": "RabbitMQ integrace", - "path": "Background-Jobs-RabbitMq.md" - } - ] - } - ] - }, - { - "text": "Startovací šablony", - "path": "Startup-Templates/Index.md", - "items": [ - { - "text": "Aplikace", - "path": "Startup-Templates/Application.md" - }, - { - "text": "Modul", - "path": "Startup-Templates/Module.md" - } - ] - }, - { - "text": "Vzorky", - "items": [ - { - "text": "Mikroslužby demo", - "path": "Samples/Microservice-Demo.md" - } - ] - }, - { - "text": "Moduly aplikace", - "path": "Modules/Index.md" - }, - { - "text": "Architektura mikroslužby", - "path": "Microservice-Architecture.md" - }, - { - "text": "Testování" - }, - { - "text": "Noční sestavení", - "path": "Nightly-Builds.md" - }, - { - "text": "Průvodce pro přispěvatele", - "path": "Contribution/Index.md" - } - ] -} \ No newline at end of file diff --git a/docs/cs/images/MonthlyProfitWidgetFiles.png b/docs/cs/images/MonthlyProfitWidgetFiles.png deleted file mode 100644 index c3e4d6f1ab..0000000000 Binary files a/docs/cs/images/MonthlyProfitWidgetFiles.png and /dev/null differ diff --git a/docs/cs/images/authorization-new-permission-ui-hierarcy.png b/docs/cs/images/authorization-new-permission-ui-hierarcy.png deleted file mode 100644 index 07abfc7132..0000000000 Binary files a/docs/cs/images/authorization-new-permission-ui-hierarcy.png and /dev/null differ diff --git a/docs/cs/images/authorization-new-permission-ui-localized.png b/docs/cs/images/authorization-new-permission-ui-localized.png deleted file mode 100644 index 948fd618d9..0000000000 Binary files a/docs/cs/images/authorization-new-permission-ui-localized.png and /dev/null differ diff --git a/docs/cs/images/authorization-new-permission-ui.png b/docs/cs/images/authorization-new-permission-ui.png deleted file mode 100644 index 1190f04b70..0000000000 Binary files a/docs/cs/images/authorization-new-permission-ui.png and /dev/null differ diff --git a/docs/cs/images/bookstore-apis.png b/docs/cs/images/bookstore-apis.png deleted file mode 100644 index b7928c9637..0000000000 Binary files a/docs/cs/images/bookstore-apis.png and /dev/null differ diff --git a/docs/cs/images/bookstore-create-template.png b/docs/cs/images/bookstore-create-template.png deleted file mode 100644 index bae34a3b64..0000000000 Binary files a/docs/cs/images/bookstore-create-template.png and /dev/null differ diff --git a/docs/cs/images/bookstore-homepage.png b/docs/cs/images/bookstore-homepage.png deleted file mode 100644 index dc015aa67d..0000000000 Binary files a/docs/cs/images/bookstore-homepage.png and /dev/null differ diff --git a/docs/cs/images/bookstore-swagger-ui-host.png b/docs/cs/images/bookstore-swagger-ui-host.png deleted file mode 100644 index 7ebd8d8e37..0000000000 Binary files a/docs/cs/images/bookstore-swagger-ui-host.png and /dev/null differ diff --git a/docs/cs/images/bookstore-user-management-v2.png b/docs/cs/images/bookstore-user-management-v2.png deleted file mode 100644 index cd66010e05..0000000000 Binary files a/docs/cs/images/bookstore-user-management-v2.png and /dev/null differ diff --git a/docs/cs/images/bookstore-visual-studio-solution-for-spa.png b/docs/cs/images/bookstore-visual-studio-solution-for-spa.png deleted file mode 100644 index d114ed188c..0000000000 Binary files a/docs/cs/images/bookstore-visual-studio-solution-for-spa.png and /dev/null differ diff --git a/docs/cs/images/bookstore-visual-studio-solution-tiered.png b/docs/cs/images/bookstore-visual-studio-solution-tiered.png deleted file mode 100644 index 9affe841aa..0000000000 Binary files a/docs/cs/images/bookstore-visual-studio-solution-tiered.png and /dev/null differ diff --git a/docs/cs/images/bookstore-visual-studio-solution-v3.png b/docs/cs/images/bookstore-visual-studio-solution-v3.png deleted file mode 100644 index ce821eba72..0000000000 Binary files a/docs/cs/images/bookstore-visual-studio-solution-v3.png and /dev/null differ diff --git a/docs/cs/images/build-action-embedded-resource-sample.png b/docs/cs/images/build-action-embedded-resource-sample.png deleted file mode 100644 index 700e9921f4..0000000000 Binary files a/docs/cs/images/build-action-embedded-resource-sample.png and /dev/null differ diff --git a/docs/cs/images/create-aspnet-core-application.png b/docs/cs/images/create-aspnet-core-application.png deleted file mode 100644 index 03fde7e38a..0000000000 Binary files a/docs/cs/images/create-aspnet-core-application.png and /dev/null differ diff --git a/docs/cs/images/create-new-aspnet-core-application-v2.png b/docs/cs/images/create-new-aspnet-core-application-v2.png deleted file mode 100644 index d2bce84775..0000000000 Binary files a/docs/cs/images/create-new-aspnet-core-application-v2.png and /dev/null differ diff --git a/docs/cs/images/create-new-aspnet-core-application.png b/docs/cs/images/create-new-aspnet-core-application.png deleted file mode 100644 index 2c38289810..0000000000 Binary files a/docs/cs/images/create-new-aspnet-core-application.png and /dev/null differ diff --git a/docs/cs/images/create-new-net-core-console-application.png b/docs/cs/images/create-new-net-core-console-application.png deleted file mode 100644 index 0c2b3dbcb8..0000000000 Binary files a/docs/cs/images/create-new-net-core-console-application.png and /dev/null differ diff --git a/docs/cs/images/dashboard1.png b/docs/cs/images/dashboard1.png deleted file mode 100644 index 8c542b8786..0000000000 Binary files a/docs/cs/images/dashboard1.png and /dev/null differ diff --git a/docs/cs/images/db-migrator-app.png b/docs/cs/images/db-migrator-app.png deleted file mode 100644 index d2248d4588..0000000000 Binary files a/docs/cs/images/db-migrator-app.png and /dev/null differ diff --git a/docs/cs/images/docs-create-project.jpg b/docs/cs/images/docs-create-project.jpg deleted file mode 100644 index d2baa3242a..0000000000 Binary files a/docs/cs/images/docs-create-project.jpg and /dev/null differ diff --git a/docs/cs/images/docs-module_download-new-abp-project.png b/docs/cs/images/docs-module_download-new-abp-project.png deleted file mode 100644 index bc7aaacbd6..0000000000 Binary files a/docs/cs/images/docs-module_download-new-abp-project.png and /dev/null differ diff --git a/docs/cs/images/docs-module_download-sample-navigation-menu.png b/docs/cs/images/docs-module_download-sample-navigation-menu.png deleted file mode 100644 index 8d8eb42d52..0000000000 Binary files a/docs/cs/images/docs-module_download-sample-navigation-menu.png and /dev/null differ diff --git a/docs/cs/images/docs-module_solution-explorer.png b/docs/cs/images/docs-module_solution-explorer.png deleted file mode 100644 index 97bd3fc18e..0000000000 Binary files a/docs/cs/images/docs-module_solution-explorer.png and /dev/null differ diff --git a/docs/cs/images/docs-section-ui.png b/docs/cs/images/docs-section-ui.png deleted file mode 100644 index 1c63d1ad3a..0000000000 Binary files a/docs/cs/images/docs-section-ui.png and /dev/null differ diff --git a/docs/cs/images/github-access-token-private-repo.jpg b/docs/cs/images/github-access-token-private-repo.jpg deleted file mode 100644 index cb74f1eea3..0000000000 Binary files a/docs/cs/images/github-access-token-private-repo.jpg and /dev/null differ diff --git a/docs/cs/images/github-access-token-public-repo.jpg b/docs/cs/images/github-access-token-public-repo.jpg deleted file mode 100644 index d091a6d511..0000000000 Binary files a/docs/cs/images/github-access-token-public-repo.jpg and /dev/null differ diff --git a/docs/cs/images/github-myusername.jpg b/docs/cs/images/github-myusername.jpg deleted file mode 100644 index a723c17713..0000000000 Binary files a/docs/cs/images/github-myusername.jpg and /dev/null differ diff --git a/docs/cs/images/issuemanagement-module-solution.png b/docs/cs/images/issuemanagement-module-solution.png deleted file mode 100644 index d5f64b01d2..0000000000 Binary files a/docs/cs/images/issuemanagement-module-solution.png and /dev/null differ diff --git a/docs/cs/images/layered-project-dependencies-module.png b/docs/cs/images/layered-project-dependencies-module.png deleted file mode 100644 index de3b7a412f..0000000000 Binary files a/docs/cs/images/layered-project-dependencies-module.png and /dev/null differ diff --git a/docs/cs/images/layered-project-dependencies.png b/docs/cs/images/layered-project-dependencies.png deleted file mode 100644 index ed3e03fe4d..0000000000 Binary files a/docs/cs/images/layered-project-dependencies.png and /dev/null differ diff --git a/docs/cs/images/localization-resource-json-files.png b/docs/cs/images/localization-resource-json-files.png deleted file mode 100644 index 1a1d43403c..0000000000 Binary files a/docs/cs/images/localization-resource-json-files.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-authserver-home.png b/docs/cs/images/microservice-sample-authserver-home.png deleted file mode 100644 index 684fdb1a5a..0000000000 Binary files a/docs/cs/images/microservice-sample-authserver-home.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-authserver-login.png b/docs/cs/images/microservice-sample-authserver-login.png deleted file mode 100644 index 99d898ccd2..0000000000 Binary files a/docs/cs/images/microservice-sample-authserver-login.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-backend-ui-permissions.png b/docs/cs/images/microservice-sample-backend-ui-permissions.png deleted file mode 100644 index 3e1610294f..0000000000 Binary files a/docs/cs/images/microservice-sample-backend-ui-permissions.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-backend-ui.png b/docs/cs/images/microservice-sample-backend-ui.png deleted file mode 100644 index e649c8da59..0000000000 Binary files a/docs/cs/images/microservice-sample-backend-ui.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-blogservice-permission-in-database.png b/docs/cs/images/microservice-sample-blogservice-permission-in-database.png deleted file mode 100644 index 45d7f2b115..0000000000 Binary files a/docs/cs/images/microservice-sample-blogservice-permission-in-database.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-diagram-2.png b/docs/cs/images/microservice-sample-diagram-2.png deleted file mode 100644 index 414a942aca..0000000000 Binary files a/docs/cs/images/microservice-sample-diagram-2.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-diagram.png b/docs/cs/images/microservice-sample-diagram.png deleted file mode 100644 index 47e6443852..0000000000 Binary files a/docs/cs/images/microservice-sample-diagram.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-kibana-1.png b/docs/cs/images/microservice-sample-kibana-1.png deleted file mode 100644 index 51777f6bd7..0000000000 Binary files a/docs/cs/images/microservice-sample-kibana-1.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-kibana-2.png b/docs/cs/images/microservice-sample-kibana-2.png deleted file mode 100644 index cb1d0b748c..0000000000 Binary files a/docs/cs/images/microservice-sample-kibana-2.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-product-module-in-solution.png b/docs/cs/images/microservice-sample-product-module-in-solution.png deleted file mode 100644 index 2c07ad4b99..0000000000 Binary files a/docs/cs/images/microservice-sample-product-module-in-solution.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-public-product-list.png b/docs/cs/images/microservice-sample-public-product-list.png deleted file mode 100644 index 932b0b531c..0000000000 Binary files a/docs/cs/images/microservice-sample-public-product-list.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-solution.png b/docs/cs/images/microservice-sample-solution.png deleted file mode 100644 index d1497d9be2..0000000000 Binary files a/docs/cs/images/microservice-sample-solution.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-update-database-authserver.png b/docs/cs/images/microservice-sample-update-database-authserver.png deleted file mode 100644 index 094fd20fa6..0000000000 Binary files a/docs/cs/images/microservice-sample-update-database-authserver.png and /dev/null differ diff --git a/docs/cs/images/microservice-sample-update-database-products.png b/docs/cs/images/microservice-sample-update-database-products.png deleted file mode 100644 index 32a0927c1f..0000000000 Binary files a/docs/cs/images/microservice-sample-update-database-products.png and /dev/null differ diff --git a/docs/cs/images/module-layers-and-packages.jpg b/docs/cs/images/module-layers-and-packages.jpg deleted file mode 100644 index f71a91eb8d..0000000000 Binary files a/docs/cs/images/module-layers-and-packages.jpg and /dev/null differ diff --git a/docs/cs/images/night-build-add-nuget-package.png b/docs/cs/images/night-build-add-nuget-package.png deleted file mode 100644 index f475d3aaa0..0000000000 Binary files a/docs/cs/images/night-build-add-nuget-package.png and /dev/null differ diff --git a/docs/cs/images/night-build-add-nuget-source.png b/docs/cs/images/night-build-add-nuget-source.png deleted file mode 100644 index df3176aa12..0000000000 Binary files a/docs/cs/images/night-build-add-nuget-source.png and /dev/null differ diff --git a/docs/cs/images/pcm-update-database-v2.png b/docs/cs/images/pcm-update-database-v2.png deleted file mode 100644 index 72d02e9186..0000000000 Binary files a/docs/cs/images/pcm-update-database-v2.png and /dev/null differ diff --git a/docs/cs/images/pcm-update-database.png b/docs/cs/images/pcm-update-database.png deleted file mode 100644 index a9379d2571..0000000000 Binary files a/docs/cs/images/pcm-update-database.png and /dev/null differ diff --git a/docs/cs/images/select-empty-web-application-v2.png b/docs/cs/images/select-empty-web-application-v2.png deleted file mode 100644 index 9bfd2ec6a8..0000000000 Binary files a/docs/cs/images/select-empty-web-application-v2.png and /dev/null differ diff --git a/docs/cs/images/select-empty-web-application.png b/docs/cs/images/select-empty-web-application.png deleted file mode 100644 index f4b884140d..0000000000 Binary files a/docs/cs/images/select-empty-web-application.png and /dev/null differ diff --git a/docs/cs/images/set-as-startup-project.png b/docs/cs/images/set-as-startup-project.png deleted file mode 100644 index 8a5445bf38..0000000000 Binary files a/docs/cs/images/set-as-startup-project.png and /dev/null differ diff --git a/docs/cs/images/tiered-solution-applications.png b/docs/cs/images/tiered-solution-applications.png deleted file mode 100644 index df8d2b5f4a..0000000000 Binary files a/docs/cs/images/tiered-solution-applications.png and /dev/null differ diff --git a/docs/cs/images/tiered-solution-servers.png b/docs/cs/images/tiered-solution-servers.png deleted file mode 100644 index 68e72990d7..0000000000 Binary files a/docs/cs/images/tiered-solution-servers.png and /dev/null differ diff --git a/docs/cs/images/volodocs-iis-add-website.png b/docs/cs/images/volodocs-iis-add-website.png deleted file mode 100644 index aa7da8095b..0000000000 Binary files a/docs/cs/images/volodocs-iis-add-website.png and /dev/null differ diff --git a/docs/cs/images/volodocs-iis-application-pool.png b/docs/cs/images/volodocs-iis-application-pool.png deleted file mode 100644 index 28ccfa5c42..0000000000 Binary files a/docs/cs/images/volodocs-iis-application-pool.png and /dev/null differ diff --git a/docs/cs/images/widget-basic-files.png b/docs/cs/images/widget-basic-files.png deleted file mode 100644 index c692abd9e0..0000000000 Binary files a/docs/cs/images/widget-basic-files.png and /dev/null differ diff --git a/docs/docs-langs.json b/docs/docs-langs.json deleted file mode 100644 index 3986dc8618..0000000000 --- a/docs/docs-langs.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Languages":[ - { - "DisplayName" : "English", - "Code" : "en", - "IsDefault": true - }, - { - "DisplayName" : "Português", - "Code" : "pt-BR", - "IsDefault": false - }, - { - "DisplayName" : "简体中文", - "Code" : "zh-Hans", - "IsDefault": false - } - ] -} diff --git a/docs/en/.vscode/settings.json b/docs/en/.vscode/settings.json deleted file mode 100644 index d04572b96c..0000000000 --- a/docs/en/.vscode/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "grammarly.userWords": [ - "api", - "apiName", - "cli", - "defaultProject", - "formatter", - "md", - "monorepo", - "ngsw", - "npx", - "pwa", - "rootNamespace" - ] -} \ No newline at end of file diff --git a/docs/en/API/API-Versioning.md b/docs/en/API/API-Versioning.md deleted file mode 100644 index 741eafbb75..0000000000 --- a/docs/en/API/API-Versioning.md +++ /dev/null @@ -1,344 +0,0 @@ -# API Versioning System - -ABP Framework integrates the [ASPNET-API-Versioning](https://github.com/dotnet/aspnet-api-versioning/wiki) feature and adapts to C# and JavaScript Static Client Proxies and [Auto API Controller](../API/Auto-API-Controllers.md). - - -## Enable API Versioning - -```cs -public override void ConfigureServices(ServiceConfigurationContext context) -{ - // Show neutral/versionless APIs. - context.Services.AddTransient(); - context.Services.AddAbpApiVersioning(options => - { - options.ReportApiVersions = true; - options.AssumeDefaultVersionWhenUnspecified = true; - }); - - Configure(options => - { - options.ChangeControllerModelApiExplorerGroupName = false; - }); -} -``` - -## C# and JavaScript Static Client Proxies - -This feature does not compatible with [URL Path Versioning](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-URL-Path), we suggest to use [Versioning-via-the-Query-String](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-Query-String). - -### Example - -**Application Services:** -```cs -public interface IBookAppService : IApplicationService -{ - Task GetAsync(); -} - -public interface IBookV2AppService : IApplicationService -{ - Task GetAsync(); - - Task GetAsync(string isbn); -} -``` - -**HttpApi Controllers:** -```cs -[Area(BookStoreRemoteServiceConsts.ModuleName)] -[RemoteService(Name = BookStoreRemoteServiceConsts.RemoteServiceName)] -[ApiVersion("1.0", Deprecated = true)] -[ApiController] -[ControllerName("Book")] -[Route("api/BookStore/Book")] -public class BookController : BookStoreController, IBookAppService -{ - private readonly IBookAppService _bookAppService; - - public BookController(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - [HttpGet] - public async Task GetAsync() - { - return await _bookAppService.GetAsync(); - } -} - -[Area(BookStoreRemoteServiceConsts.ModuleName)] -[RemoteService(Name = BookStoreRemoteServiceConsts.RemoteServiceName)] -[ApiVersion("2.0")] -[ApiController] -[ControllerName("Book")] -[Route("api/BookStore/Book")] -public class BookV2Controller : BookStoreController, IBookV2AppService -{ - private readonly IBookV2AppService _bookAppService; - - public BookV2Controller(IBookV2AppService bookAppService) - { - _bookAppService = bookAppService; - } - - [HttpGet] - public async Task GetAsync() - { - return await _bookAppService.GetAsync(); - } - - [HttpGet] - [Route("{isbn}")] - public async Task GetAsync(string isbn) - { - return await _bookAppService.GetAsync(isbn); - } -} -``` - -**Generated CS and JS proxies:** - -```cs -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IBookAppService), typeof(BookClientProxy))] -public partial class BookClientProxy : ClientProxyBase, IBookAppService -{ - public virtual async Task GetAsync() - { - return await RequestAsync(nameof(GetAsync)); - } -} - -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IBookV2AppService), typeof(BookV2ClientProxy))] -public partial class BookV2ClientProxy : ClientProxyBase, IBookV2AppService -{ - public virtual async Task GetAsync() - { - return await RequestAsync(nameof(GetAsync)); - } - - public virtual async Task GetAsync(string isbn) - { - return await RequestAsync(nameof(GetAsync), new ClientProxyRequestTypeValue - { - { typeof(string), isbn } - }); - } -} -``` - - -```js -// controller bookStore.books.book - -(function(){ - -abp.utils.createNamespace(window, 'bookStore.books.book'); - -bookStore.books.book.get = function(api_version, ajaxParams) { - var api_version = api_version ? api_version : '1.0'; - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/BookStore/Book' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '', - type: 'GET' - }, ajaxParams)); -}; - -})(); - -// controller bookStore.books.bookV2 - -(function(){ - -abp.utils.createNamespace(window, 'bookStore.books.bookV2'); - -bookStore.books.bookV2.get = function(api_version, ajaxParams) { - var api_version = api_version ? api_version : '2.0'; - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/BookStore/Book' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '', - type: 'GET' - }, ajaxParams)); -}; - -bookStore.books.bookV2.getAsyncByIsbn = function(isbn, api_version, ajaxParams) { - var api_version = api_version ? api_version : '2.0'; - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/BookStore/Book/' + isbn + '' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '', - type: 'GET' - }, ajaxParams)); -}; - -})(); -``` - - -## Changing version manually - -If an application service class supports multiple versions. You can inject `ICurrentApiVersionInfo` to switch versions in C#. - -```cs -var currentApiVersionInfo = _abpApplication.ServiceProvider.GetRequiredService(); -var bookV4AppService = _abpApplication.ServiceProvider.GetRequiredService(); -using (currentApiVersionInfo.Change(new ApiVersionInfo(ParameterBindingSources.Query, "4.0"))) -{ - book = await bookV4AppService.GetAsync(); - logger.LogWarning(book.Title); - logger.LogWarning(book.ISBN); -} - -using (currentApiVersionInfo.Change(new ApiVersionInfo(ParameterBindingSources.Query, "4.1"))) -{ - book = await bookV4AppService.GetAsync(); - logger.LogWarning(book.Title); - logger.LogWarning(book.ISBN); -} -``` - -We have made a default version in the JS proxy. Of course, you can also manually change the version. - -```js - -bookStore.books.bookV4.get("4.0") // Manually change the version. -//Title: Mastering ABP Framework V4.0 - -bookStore.books.bookV4.get() // The latest supported version is used by default. -//Title: Mastering ABP Framework V4.1 -``` - -## Auto API Controller - -```cs -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - //2.0 Version - options.ConventionalControllers.Create(typeof(BookStoreWebAppModule).Assembly, opts => - { - opts.TypePredicate = t => t.Namespace == typeof(BookStore.Controllers.ConventionalControllers.v2.TodoAppService).Namespace; - opts.ApiVersions.Add(new ApiVersion(2, 0)); - }); - - //1.0 Compatibility version - options.ConventionalControllers.Create(typeof(BookStoreWebAppModule).Assembly, opts => - { - opts.TypePredicate = t => t.Namespace == typeof(BookStore.Controllers.ConventionalControllers.v1.TodoAppService).Namespace; - opts.ApiVersions.Add(new ApiVersion(1, 0)); - }); - }); -} - -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var preActions = context.Services.GetPreConfigureActions(); - Configure(options => - { - preActions.Configure(options); - }); - - // Show neutral/versionless APIs. - context.Services.AddTransient(); - context.Services.AddAbpApiVersioning(options => - { - options.ReportApiVersions = true; - options.AssumeDefaultVersionWhenUnspecified = true; - - options.ConfigureAbp(preActions.Configure()); - }); - - Configure(options => - { - options.ChangeControllerModelApiExplorerGroupName = false; - }); -} -``` - -## Swagger/VersionedApiExplorer - -```cs - -public override void ConfigureServices(ServiceConfigurationContext context) -{ - // Show neutral/versionless APIs. - context.Services.AddTransient(); - context.Services.AddAbpApiVersioning(options => - { - options.ReportApiVersions = true; - options.AssumeDefaultVersionWhenUnspecified = true; - }).AddApiExplorer(options => { - // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service - // note: the specified format code will format the version as "'v'major[.minor][-status]" - options.GroupNameFormat = "'v'VVV"; - - // note: this option is only necessary when versioning by url segment. the SubstitutionFormat - // can also be used to control the format of the API version in route templates - options.SubstituteApiVersionInUrl = true; - }); - - context.Services.AddTransient, ConfigureSwaggerOptions>(); - - context.Services.AddAbpSwaggerGen( - options => - { - // add a custom operation filter which sets default values - options.OperationFilter(); - - options.CustomSchemaIds(type => type.FullName); - }); - - Configure(options => - { - options.ChangeControllerModelApiExplorerGroupName = false; - }); -} - -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - var app = context.GetApplicationBuilder(); - var env = context.GetEnvironment(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseErrorPage(); - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - app.UseRouting(); - app.UseAbpRequestLocalization(); - - app.UseSwagger(); - app.UseAbpSwaggerUI( - options => - { - var provider = app.ApplicationServices.GetRequiredService(); - // build a swagger endpoint for each discovered API version - foreach (var description in provider.ApiVersionDescriptions) - { - options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); - } - }); - - app.UseConfiguredEndpoints(); -} -``` - -## Custom multi-version API controller - -ABP Framework will not affect to your APIs, you can freely implement your APIs according to the Microsoft's documentation. - -Further information, see https://github.com/dotnet/aspnet-api-versioning/wiki - - -## Sample source code - -Follow the link below to get the sample's complete source-code - -https://github.com/abpframework/abp-samples/tree/master/Api-Versioning diff --git a/docs/en/API/Application-Configuration.md b/docs/en/API/Application-Configuration.md deleted file mode 100644 index 89463f3077..0000000000 --- a/docs/en/API/Application-Configuration.md +++ /dev/null @@ -1,70 +0,0 @@ -# Application Configuration Endpoint - -ABP Framework provides a pre-built and standard endpoint that contains some useful information about the application/service. Here, is the list of some fundamental information at this endpoint: - -* Granted [policies](../Authorization.md) (permissions) for the current user. -* [Setting](../Settings.md) values for the current user. -* Info about the [current user](../CurrentUser.md) (like id and user name). -* Info about the current [tenant](../Multi-Tenancy.md) (like id and name). -* [Time zone](../Timing.md) information for the current user and the [clock](../Timing.md) type of the application. - -> If you have started with ABP's startup solution templates and using one of the official UI options, then all these are set up for you and you don't need to know these details. However, if you are building a UI application from scratch, you may want to know this endpoint. - -## HTTP API - -If you navigate to the `/api/abp/application-configuration` URL of an ABP Framework based web application or HTTP Service, you can access the configuration as a JSON object. This endpoint is useful to create the client of your application. - -## Script - -For ASP.NET Core MVC (Razor Pages) applications, the same configuration values are also available on the JavaScript side. `/Abp/ApplicationConfigurationScript` is the URL of the script that is auto-generated based on the HTTP API above. - -See the [JavaScript API document](../UI/AspNetCore/JavaScript-API/Index.md) for the ASP.NET Core UI. - -Other UI types provide services native to the related platform. For example, see the [Angular UI settings documentation](../UI/Angular/Settings.md) to learn how to use the setting values exposes by this endpoint. - -## Extending the Endpoint - -The **application-configuration** endpoint contains some useful information about the application, such as _localization values_, _current user information_, _granted permissions_, etc. Even most of the time these provided values are sufficient to use in your application to perform common requirements such as getting the logged-in user's ID or its granted permissions. You may still want to extend this endpoint and provide additional information for your application/service. At that point, you can use the `IApplicationConfigurationContributor` endpoint. - -### IApplicationConfigurationContributor - -`IApplicationConfigurationContributor` is the interface that should be implemented to add additional information to the **application-configuration** endpoint. - -**Example: Setting the deployment version** - -```csharp -using System.Threading.Tasks; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; -using Volo.Abp.Data; - -namespace Acme.BookStore.Web -{ - public class MyApplicationConfigurationContributor : IApplicationConfigurationContributor - { - public Task ContributeAsync(ApplicationConfigurationContributorContext context) - { - //for simplicity, it's a static number, you can inject any service to this class and perform your logic... - var deploymentVersion = "v1.0.0"; - - //setting the deploymentVersion - context.ApplicationConfiguration.SetProperty("deploymentVersion", deploymentVersion); - - return Task.CompletedTask; - } - } -} -``` - -Add your contributor instance to the `AbpApplicationConfigurationOptions` - -```csharp -Configure(options => -{ - options.Contributors.AddIfNotContains(new MyApplicationConfigurationContributor()); -}); -``` - -* `IApplicationConfigurationContributor` defines the `ContributeAsync` method to extend the **application-configuration** endpoint with the specified additional data. -* You can inject services and perform any logic needed to extend the endpoint as you wish. - -> Application configuration contributors are executed as a part of the application configuration initialization process. diff --git a/docs/en/API/Application-Localization.md b/docs/en/API/Application-Localization.md deleted file mode 100644 index adf26c8fa8..0000000000 --- a/docs/en/API/Application-Localization.md +++ /dev/null @@ -1,33 +0,0 @@ -# Application Localization Endpoint - -ABP Framework provides a pre-built and standard endpoint that returns all the [localization](../Localization.md) resources and texts defined in the server. - -> If you have started with ABP's startup solution templates and using one of the official UI options, then all these are set up for you and you don't need to know these details. However, if you are building a UI application from scratch, you may want to know this endpoint. - -## HTTP API - -`/api/abp/application-localization` is the main URL of the HTTP API that returns the localization data as a JSON string. I accepts the following query string parameters: - -* `cultureName` (required): A culture code to get the localization data, like `en` or `en-US`. -* `onlyDynamics` (optional, default: `false`): Can be set to `true` to only get the dynamically defined localization resources and texts. If your client-side application shares the same localization resources with the server (like ABP's Blazor and MVC UIs), you can set `onlyDynamics` to `true`. - -**Example request:** - -```` -/api/abp/application-localization?cultureName=en -```` - -## Script - -For [ASP.NET Core MVC (Razor Pages)](../UI/AspNetCore/Overall.md) applications, the same localization data is also available on the JavaScript side. `/Abp/ApplicationLocalizationScript` is the URL of the script that is auto-generated based on the HTTP API above. - -**Example request:** - -```` -/Abp/ApplicationLocalizationScript?cultureName=en -```` - -See the [JavaScript API document](../UI/AspNetCore/JavaScript-API/Index.md) for the ASP.NET Core UI. - -Other UI types provide services native to the related platform. For example, see the [Angular UI localization documentation](../UI/Angular/Localization.md) to learn how to use the localization values exposes by this endpoint. - diff --git a/docs/en/API/Auto-API-Controllers.md b/docs/en/API/Auto-API-Controllers.md deleted file mode 100644 index ee7c068bec..0000000000 --- a/docs/en/API/Auto-API-Controllers.md +++ /dev/null @@ -1,219 +0,0 @@ -# Auto API Controllers - -Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. - -ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it. - -## Configuration - -Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: - -````csharp -[DependsOn(BookStoreApplicationModule)] -public class BookStoreWebModule : AbpModule -{ - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(options => - { - options - .ConventionalControllers - .Create(typeof(BookStoreApplicationModule).Assembly); - }); - } -} -```` - -This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). - -![bookstore-apis](../images/bookstore-apis.png) - -### Examples - -Some example method names and the corresponding routes calculated by convention: - -| Service Method Name | HTTP Method | Route | -| ----------------------------------------------------- | ----------- | -------------------------- | -| GetAsync(Guid id) | GET | /api/app/book/{id} | -| GetListAsync() | GET | /api/app/book | -| CreateAsync(CreateBookDto input) | POST | /api/app/book | -| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | -| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | -| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | -| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | - -### HTTP Method - -ABP uses a naming convention while determining the HTTP method for a service method (action): - -- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. -- **Put**: Used if the method name starts with 'Put' or 'Update'. -- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. -- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. -- **Patch**: Used if the method name starts with 'Patch'. -- Otherwise, **Post** is used **by default**. - -If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. - -### Route - -Route is calculated based on some conventions: - -* It always starts with '**/api**'. -* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: - -````csharp -Configure(options => -{ - options.ConventionalControllers - .Create(typeof(BookStoreApplicationModule).Assembly, opts => - { - opts.RootPath = "volosoft/book-store"; - }); -}); -```` - -Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. - -* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **kebab-case**. If your application service class name is 'ReadingBookAppService' then it becomes only '/reading-book'. - * If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. -* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. -* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; - * Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. - * Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. - * Converting the result to **kebab-case**. - * If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'. - * Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. -* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). - -#### Customizing the Route Calculation - -`IConventionalRouteBuilder` is used to build the route. It is implemented by the `ConventionalRouteBuilder` by default and works as explained above. You can replace/override this service to customize the route calculation strategy. - -#### Version 3.x Style Route Calculation - -The route calculation was different before the version 4.0. It was using camelCase conventions, while the ABP Framework version 4.0+ uses kebab-case. If you use the old route calculation strategy, follow one of the approaches; - -* Set `UseV3UrlStyle` to `true` in the options of the `options.ConventionalControllers.Create(...)` method. Example: - -````csharp -options.ConventionalControllers - .Create(typeof(BookStoreApplicationModule).Assembly, opts => - { - opts.UseV3UrlStyle = true; - }); -```` - -This approach effects only the controllers for the `BookStoreApplicationModule`. - -* Set `UseV3UrlStyle` to `true` for the `AbpConventionalControllerOptions` to set it globally. Example: - -```csharp -Configure(options => -{ - options.UseV3UrlStyle = true; -}); -``` - -Setting it globally effects all the modules in a modular application. - -## Service Selection - -Creating conventional HTTP API controllers are not unique to application services actually. - -### IRemoteService Interface - -If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. - -### RemoteService Attribute - -`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: - -````csharp -[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] -public class PersonAppService : ApplicationService -{ - -} -```` - -### TypePredicate Option - -You can further filter classes to become an API controller by providing the `TypePredicate` option: - -````csharp -services.Configure(options => -{ - options.ConventionalControllers - .Create(typeof(BookStoreApplicationModule).Assembly, opts => - { - opts.TypePredicate = type => { return true; }; - }); -}); -```` - -Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. - -## API Explorer - -API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. - -API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: - -````csharp -[RemoteService(IsMetadataEnabled = false)] -public class PersonAppService : ApplicationService -{ - -} -```` - -Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. - -## Replace or Remove Controllers. - -In addition to [Overriding a Controller](../Customizing-Application-Modules-Overriding-Services.md#example-overriding-a-controller), you can also use a completely independent **Controller** to replace the controller in the framework or module. - -They have the same [route](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-7.0), but can have **different** input and output parameters. - -### Replace built-in AbpApplicationConfigurationController - -The `ReplaceControllersAttribute` indicates the replaced controller type. - -````csharp -[ReplaceControllers(typeof(AbpApplicationConfigurationController))] -[Area("abp")] -[RemoteService(Name = "abp")] -public class ReplaceBuiltInController : AbpController -{ - [HttpGet("api/abp/application-configuration")] - public virtual Task GetAsync(MyApplicationConfigurationRequestOptions options) - { - return Task.FromResult(new MyApplicationConfigurationDto()); - } -} - -public class MyApplicationConfigurationRequestOptions : ApplicationConfigurationRequestOptions -{ - -} - -public class MyApplicationConfigurationDto : ApplicationConfigurationDto -{ - -} -```` - -### Remove controller - -Configure `ControllersToRemove` of `AbpAspNetCoreMvcOptions` to remove the controllers. - -````csharp -services.Configure(options => -{ - options.ControllersToRemove.Add(typeof(AbpLanguagesController)); -}); -```` -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/auto-api-controllers) \ No newline at end of file diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md deleted file mode 100644 index de9b87e206..0000000000 --- a/docs/en/API/Dynamic-CSharp-API-Clients.md +++ /dev/null @@ -1,209 +0,0 @@ -# Dynamic C# API Client Proxies - -ABP can dynamically create C# API client proxies to call your remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level details to call remote services and get results. - -Dynamic C# proxies automatically handle the following stuff for you; - -* Maps C# **method calls** to remote server **HTTP calls** by considering the HTTP method, route, query string parameters, request payload and other details. -* **Authenticates** the HTTP Client by adding access token to the HTTP header. -* **Serializes** to and deserialize from JSON. -* Handles HTTP API **versioning**. -* Add **correlation id**, current **tenant** id and the current **culture** to the request. -* Properly **handles the error messages** sent by the server and throws proper exceptions. - -This system can be used by any type of .NET client to consume your HTTP APIs. - -## Static vs Dynamic Client Proxies - -ABP provides **two types** of client proxy generation system. This document explains the **dynamic client proxies**, which generates client-side proxies on runtime. You can also see the [Static C# API Client Proxies](Static-CSharp-API-Clients.md) documentation to learn how to generate proxies on development time. - -Development-time (static) client proxy generation has a **performance advantage** since it doesn't need to obtain the HTTP API definition on runtime. However, you should **re-generate** the client proxy code whenever you change your API endpoint definition. On the other hand, dynamic client proxies are generated on runtime and provides an **easier development experience**. - -## Service Interface - -Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project, typically in the `Application.Contracts` project if you've created your solution using the startup templates. - -Example: - -````csharp -public interface IBookAppService : IApplicationService -{ - Task> GetListAsync(); -} -```` - -> Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. - -Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. - -## Client Proxy Generation - -> The startup templates already comes pre-configured for the client proxy generation, in the `HttpApi.Client` project. - -If you're not using a startup template, then execute the following command in the folder that contains the .csproj file of your client project: - -```` -abp add-package Volo.Abp.Http.Client -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Http.Client). - -Now, it's ready to create the client proxies. Example: - -````csharp -[DependsOn( - typeof(AbpHttpClientModule), //used to create client proxies - typeof(BookStoreApplicationContractsModule) //contains the application service interfaces - )] -public class MyClientAppModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //Create dynamic client proxies - context.Services.AddHttpClientProxies( - typeof(BookStoreApplicationContractsModule).Assembly - ); - } -} -```` - -`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. - -### Endpoint Configuration - -`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. The simplest configuration is shown below: - -```json -{ - "RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:53929/" - } - } -} -``` - -See the "AbpRemoteServiceOptions" section below for more detailed configuration. - -## Usage - -It's straightforward to use. Just inject the service interface in the client application code: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IBookAppService _bookService; - - public MyService(IBookAppService bookService) - { - _bookService = bookService; - } - - public async Task DoIt() - { - var books = await _bookService.GetListAsync(); - foreach (var book in books) - { - Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); - } - } -} -```` - -This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client. - -### IHttpClientProxy Interface - -While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy` interface. - -## Configuration - -### AbpRemoteServiceOptions - -`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can configure it in the `ConfigureServices` method of your [module](../Module-Development-Basics.md) to set or override it. Example: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.Configure(options => - { - options.RemoteServices.Default = - new RemoteServiceConfiguration("http://localhost:53929/"); - }); - - //... -} -```` - -### Multiple Remote Service Endpoints - -The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: - -````json -{ - "RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:53929/" - }, - "BookStore": { - "BaseUrl": "http://localhost:48392/" - } - } -} -```` - -`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example: - -````csharp -context.Services.AddHttpClientProxies( - typeof(BookStoreApplicationContractsModule).Assembly, - remoteServiceConfigurationName: "BookStore" -); -```` - -`remoteServiceConfigurationName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. - -### As Default Services - -When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature. - -````csharp -context.Services.AddHttpClientProxies( - typeof(BookStoreApplicationContractsModule).Assembly, - asDefaultServices: false -); -```` - -Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. - -> If you disable `asDefaultServices`, you can only use `IHttpClientProxy` interface to use the client proxies. See the *IHttpClientProxy Interface* section above. - -### Retry/Failure Logic & Polly Integration - -If you want to add retry logic for the failing remote HTTP calls for the client proxies, you can configure the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module class. - -**Example: Use the [Polly](https://github.com/App-vNext/Polly) library to re-try 3 times on a failure** - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => - { - clientBuilder.AddTransientHttpErrorPolicy(policyBuilder => - policyBuilder.WaitAndRetryAsync( - 3, - i => TimeSpan.FromSeconds(Math.Pow(2, i)) - ) - ); - }); - }); -} -```` - -This example uses the [Microsoft.Extensions.Http.Polly](https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly) package. You also need to import the `Polly` namespace (`using Polly;`) to be able to use the `WaitAndRetryAsync` method. - -## See Also - -* [Static C# Client Proxies](Static-CSharp-API-Clients.md) diff --git a/docs/en/API/Static-CSharp-API-Clients.md b/docs/en/API/Static-CSharp-API-Clients.md deleted file mode 100644 index 5bcbc252c7..0000000000 --- a/docs/en/API/Static-CSharp-API-Clients.md +++ /dev/null @@ -1,275 +0,0 @@ -# Static C# API Client Proxies - -ABP can create C# API client proxy code to call your remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level details to call remote services and get results. - -Static C# proxies automatically handle the following stuff for you; - -* Maps C# **method calls** to remote server **HTTP calls** by considering the HTTP method, route, query string parameters, request payload and other details. -* **Authenticates** the HTTP Client by adding access token to the HTTP header. -* **Serializes** to and deserialize from JSON. -* Handles HTTP API **versioning**. -* Add **correlation id**, current **tenant** id and the current **culture** to the request. -* Properly **handles the error messages** sent by the server and throws proper exceptions. - -This system can be used by any type of .NET client to consume your HTTP APIs. - -## Static vs Dynamic Client Proxies - -ABP provides **two types** of client proxy generation system. This document explains the **static client proxies**, which generates client-side code in your development time. You can also see the [Dynamic C# API Client Proxies](Dynamic-CSharp-API-Clients.md) documentation to learn how to use proxies generated on runtime. - -Development-time (static) client proxy generation has a **performance advantage** since it doesn't need to obtain the HTTP API definition on runtime. However, you should **re-generate** the client proxy code whenever you change your API endpoint definition. On the other hand, dynamic client proxies are generated on runtime and provides an **easier development experience**. - -## Service Interface - -Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project, typically in the `Application.Contracts` project if you've created your solution using the startup templates. - -Example: - -````csharp -public interface IBookAppService : IApplicationService -{ - Task> GetListAsync(); -} -```` - -> Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. - -Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. - -## With Contracts or Without Contracts - -`Without Contracts` depending on target service's `application.contracts` package, so they can reuse the DTOs and other related classes. However, that can be a problem when we want to create fully independently developed and deployed microservices. We want to use the static proxy generation even without depending target service's application.contracts package. - -`With Contracts` generate all the `classes/enums/other` types in the client side (including application service interfaces) , This is also the default behavior of the `generate-proxy` command. - -## Client Proxy Generation - -First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: - -```` -Install-Package Volo.Abp.Http.Client -```` - -Then add `AbpHttpClientModule` dependency to your module: - -````csharp -[DependsOn(typeof(AbpHttpClientModule))] //add the dependency -public class MyClientAppModule : AbpModule -{ -} -```` - -Now, it's ready to configure the application for the static client proxy generation. - -### With Contracts Example - -````csharp -[DependsOn( - typeof(AbpHttpClientModule), //used to create client proxies - typeof(AbpVirtualFileSystemModule) //virtual file system -)] -public class MyClientAppModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - // Prepare for static client proxy generation - context.Services.AddStaticHttpClientProxies( - typeof(MyClientAppModule).Assembly - ); - - // Include the generated app-generate-proxy.json in the virtual file system - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - } -} -```` - -### Without Contracts Example - -````csharp -[DependsOn( - typeof(AbpHttpClientModule), //used to create client proxies - typeof(AbpVirtualFileSystemModule), //virtual file system - typeof(BookStoreApplicationContractsModule) //contains the application service interfaces -)] -public class MyClientAppModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - // Prepare for static client proxy generation - context.Services.AddStaticHttpClientProxies( - typeof(BookStoreApplicationContractsModule).Assembly - ); - - // Include the generated app-generate-proxy.json in the virtual file system - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - } -} -```` - -`AddStaticHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, and prepares for static client proxy generation. - -> The [application startup template](../Startup-Templates/Application.md) comes pre-configured for the **dynamic** client proxy generation, in the `HttpApi.Client` project. If you want to switch to the **static** client proxies, change `context.Services.AddHttpClientProxies` to `context.Services.AddStaticHttpClientProxies` in the module class of your `HttpApi.Client` project. - -### Endpoint Configuration - -`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. The simplest configuration is shown below: - -```json -{ - "RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:53929/" - } - } -} -``` - -See the *AbpRemoteServiceOptions* section below for more detailed configuration. - -### Code Generation - -Server side must be up and running while generating the client proxy code. So, run your application that serves the HTTP APIs on the `BaseUrl` that is configured like explained in the *Endpoint Configuration* section. - -Open a command-line terminal in the root folder of your client project (`.csproj`) and type the following command: - -#### With Contracts - -````bash -abp generate-proxy -t csharp -u http://localhost:53929/ -```` - -> If you haven't installed yet, you should install the [ABP CLI](../CLI.md). - -This command should generate the following files under the `ClientProxies` folder: - -![generated-static-client-proxies](../images/generated-static-client-proxies-with-contracts.png) - -* `BookClientProxy.Generated.cs` is the actual generated proxy class in this example. `BookClientProxy` is a `partial` class * where you can write your custom code (ABP won't override it). -* `IBookAppService.cs` is the app service. -* `BookDto.cs` is the Dto class which uses by app service. -* `app-generate-proxy.json` contains information about the remote HTTP endpoint, so ABP can properly perform HTTP requests. This file must be configured as an embedded resource in your project, so that it can be found by the virtual file system. - -#### Without Contracts - -````bash -abp generate-proxy -t csharp -u http://localhost:53929/ --without-contracts -```` - -This command should generate the following files under the `ClientProxies` folder: - -![generated-static-client-proxies](../images/generated-static-client-proxies-without-contracts.png) - -* `BookClientProxy.Generated.cs` is the actual generated proxy class in this example. `BookClientProxy` is a `partial` class where you can write your custom code (ABP won't override it). -* `app-generate-proxy.json` contains information about the remote HTTP endpoint, so ABP can properly perform HTTP requests. This file must be configured as an embedded resource in your project, so that it can be found by the virtual file system. - -> `generate-proxy` command generates proxies for only the APIs you've defined in your application. If you are developing a modular application, you can specify the `-m` (or `--module`) parameter to specify the module you want to generate proxies. See the *generate-proxy* section in the [ABP CLI](../CLI.md) documentation for other options. - -## Usage - -It's straightforward to use the client proxies. Just inject the service interface in the client application code: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IBookAppService _bookService; - - public MyService(IBookAppService bookService) - { - _bookService = bookService; - } - - public async Task DoItAsync() - { - var books = await _bookService.GetListAsync(); - foreach (var book in books) - { - Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); - } - } -} -```` - -This sample injects the `IBookAppService` service interface defined above. The static client proxy implementation makes an HTTP call whenever a service method is called by the client. - -## Configuration - -### AbpRemoteServiceOptions - -`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can configure it in the `ConfigureServices` method of your [module](../Module-Development-Basics.md) to set or override it. Example: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.Configure(options => - { - options.RemoteServices.Default = - new RemoteServiceConfiguration("http://localhost:53929/"); - }); - - //... -} -```` - -### Multiple Remote Service Endpoints - -The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: - -````json -{ - "RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:53929/" - }, - "BookStore": { - "BaseUrl": "http://localhost:48392/" - } - } -} -```` - -`AddStaticHttpClientProxies` method can get an additional parameter for the remote service name. Example: - -````csharp -context.Services.AddStaticHttpClientProxies( - typeof(BookStoreApplicationContractsModule).Assembly, - remoteServiceConfigurationName: "BookStore" -); -```` - -`remoteServiceConfigurationName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. - -### Retry/Failure Logic & Polly Integration - -If you want to add retry logic for the failing remote HTTP calls for the client proxies, you can configure the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module class. - -**Example: Use the [Polly](https://github.com/App-vNext/Polly) library to re-try 3 times on a failure** - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => - { - clientBuilder.AddTransientHttpErrorPolicy(policyBuilder => - policyBuilder.WaitAndRetryAsync( - 3, - i => TimeSpan.FromSeconds(Math.Pow(2, i)) - ) - ); - }); - }); -} -```` - -This example uses the [Microsoft.Extensions.Http.Polly](https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly) package. You also need to import the `Polly` namespace (`using Polly;`) to be able to use the `WaitAndRetryAsync` method. - -## See Also - -* [Dynamic C# Client Proxies](Dynamic-CSharp-API-Clients.md) diff --git a/docs/en/API/Swagger-Integration.md b/docs/en/API/Swagger-Integration.md deleted file mode 100644 index 395b1830d7..0000000000 --- a/docs/en/API/Swagger-Integration.md +++ /dev/null @@ -1,194 +0,0 @@ -# Swagger Integration - -[Swagger (OpenAPI)](https://swagger.io/) is a language-agnostic specification for describing REST APIs. It allows both computers and humans to understand the capabilities of a REST API without direct access to the source code. Its main goals are to: - -- Minimize the amount of work needed to connect decoupled services. -- Reduce the amount of time needed to accurately document a service. - -ABP Framework offers a prebuilt module for full Swagger integration with small configurations. - -## Installation - -> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. - -If installation is needed, it is suggested to use the [ABP CLI](../CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the `Web` or `HttpApi.Host` project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.Swashbuckle -``` - -> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Swashbuckle). - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.Swashbuckle](https://www.nuget.org/packages/Volo.Abp.Swashbuckle) NuGet package to your `Web` or `HttpApi.Host` project: - - `Install-Package Volo.Abp.Swashbuckle` - -2. Add the `AbpSwashbuckleModule` to the dependency list of your module: - - ```csharp - [DependsOn( - //...other dependencies - typeof(AbpSwashbuckleModule) // <-- Add module dependency like that - )] - public class YourModule : AbpModule - { - } - ``` - -## Configuration - -First, we need to use `AddAbpSwaggerGen` extension to configure Swagger in `ConfigureServices` method of our module: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var services = context.Services; - - //... other configurations. - - services.AddAbpSwaggerGen( - options => - { - options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "v1" }); - options.DocInclusionPredicate((docName, description) => true); - options.CustomSchemaIds(type => type.FullName); - } - ); -} -``` - -Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module: - -```csharp -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - var app = context.GetApplicationBuilder(); - - //... other configurations. - - app.UseStaticFiles(); - - app.UseSwagger(); - - app.UseAbpSwaggerUI(options => - { - options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API"); - }); - - //... other configurations. -} -``` - -### Hide ABP Endpoints on Swagger UI - -If you want to hide ABP's default endpoints, call the `HideAbpEndpoints` method in your Swagger configuration as shown in the following example: - -```csharp -services.AddAbpSwaggerGen( - options => - { - //... other options - - //Hides ABP Related endpoints on Swagger UI - options.HideAbpEndpoints(); - } -) -``` - -## Using Swagger with OAUTH - -For non MVC/Tiered applications, we need to configure Swagger with OAUTH to handle authorization. - -> ABP Framework uses OpenIddict by default. To get more information about OpenIddict, check this [documentation](../Modules/OpenIddict.md). - -To do that, we need to use `AddAbpSwaggerGenWithOAuth` extension to configure Swagger with OAuth issuer and scopes in `ConfigureServices` method of our module: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var services = contex.Services; - - //... other configarations. - - services.AddAbpSwaggerGenWithOAuth( - "https://localhost:44341", // authority issuer - new Dictionary // - { // scopes - {"Test", "Test API"} // - }, // - options => - { - options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "v1" }); - options.DocInclusionPredicate((docName, description) => true); - options.CustomSchemaIds(type => type.FullName); - } - ); -} -``` - -Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module: - -```csharp -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - var app = context.GetApplicationBuilder(); - - //... other configurations. - - app.UseAbpSwaggerUI(options => - { - options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API"); - - var configuration = context.ServiceProvider.GetRequiredService(); - options.OAuthClientId("Test_Swagger"); // clientId - options.OAuthClientSecret("1q2w3e*"); // clientSecret - }); - - //... other configurations. -} -``` - -> Do not forget to set `OAuthClientId` and `OAuthClientSecret`. - -## Using Swagger with OIDC - -You may also want to configure swagger using **OpenIdConnect** instead of OAUTH. This is especially useful when you need to configure different metadata address than the issuer in cases such as when you deploy your application to kubernetes cluster or docker. In these cases, metadata address will be used in sign-in process to reach the valid authentication server discovery endpoint over the internet and use the internal network to validate the obtained token. - -To do that, we need to use `AddAbpSwaggerGenWithOidc` extension to configure Swagger with OAuth issuer and scopes in `ConfigureServices` method of our module: - -```csharp -context.Services.AddAbpSwaggerGenWithOidc( - configuration["AuthServer:Authority"], - scopes: new[] { "SwaggerDemo" }, - // "authorization_code" - flows: new[] { AbpSwaggerOidcFlows.AuthorizationCode }, - // When deployed on K8s, should be metadata URL of the reachable DNS over internet like https://myauthserver.company.com - discoveryEndpoint: configuration["AuthServer:Authority"], - options => - { - options.SwaggerDoc("v1", new OpenApiInfo { Title = "SwaggerDemo API", Version = "v1" }); - options.DocInclusionPredicate((docName, description) => true); - options.CustomSchemaIds(type => type.FullName); - }); -``` - -The `flows` is a list of default oidc flows that is supported by the oidc-provider (authserver). You can see the default supported flows below: - -- `AbpSwaggerOidcFlows.AuthorizationCode`: The `"authorization_code"` flow is the **default and suggested** flow. **Doesn't require a client secret** when even there is a field for it. -- `AbpSwaggerOidcFlows.Implicit`: The deprecated `"implicit"` flow that was used for javascript applications. -- `AbpSwaggerOidcFlows.Password`: The legacy `password` flow which is also known as Resource Ownder Password flow. You need to provide a user name, password and client secret for it. -- `AbpSwaggerOidcFlows.ClientCredentials`: The `"client_credentials"` flow that is used for server to server interactions. - -You can define one or many flows which will be shown in the Authorize modal. You can set it **null which will use the default "authorization_code"** flow. - -The `discoveryEndpoint` is the reachable openid-provider endpoint for the `.well-known/openid-configuration`. You can set it to **null which will use default AuthServer:Authority** appsettings configuration. If you are deploying your applications to a kubernetes cluster or docker swarm, you should to set the `discoveryEndpoint` as real DNS that should be reachable over the internet. - -> If are having problems with seeing the authorization modal, check the browser console logs and make sure you have a correct and reachable `discoveryEndpoint` diff --git a/docs/en/Ambient-Context-Pattern.md b/docs/en/Ambient-Context-Pattern.md deleted file mode 100644 index d00a721ce2..0000000000 --- a/docs/en/Ambient-Context-Pattern.md +++ /dev/null @@ -1,3 +0,0 @@ -## Ambient Context Pattern - -TODO \ No newline at end of file diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md deleted file mode 100644 index 0bd71f08c9..0000000000 --- a/docs/en/Application-Services.md +++ /dev/null @@ -1,568 +0,0 @@ -# Application Services - -Application services are used to implement the **use cases** of an application. They are used to **expose domain logic to the presentation layer**. - -An Application Service is called from the presentation layer (optionally) with a **DTO ([Data Transfer Object](Data-Transfer-Objects.md))** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer. - -## Example - -### Book Entity - -Assume that you have a `Book` entity (actually, an aggregate root) defined as shown below: - -````csharp -public class Book : AggregateRoot -{ - public const int MaxNameLength = 128; - - public virtual string Name { get; protected set; } - - public virtual BookType Type { get; set; } - - public virtual float? Price { get; set; } - - protected Book() - { - - } - - public Book(Guid id, [NotNull] string name, BookType type, float? price = 0) - { - Id = id; - Name = CheckName(name); - Type = type; - Price = price; - } - - public virtual void ChangeName([NotNull] string name) - { - Name = CheckName(name); - } - - private static string CheckName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentException( - $"name can not be empty or white space!"); - } - - if (name.Length > MaxNameLength) - { - throw new ArgumentException( - $"name can not be longer than {MaxNameLength} chars!"); - } - - return name; - } -} -```` - -* `Book` entity has a `MaxNameLength` that defines the maximum length of the `Name` property. -* `Book` constructor and `ChangeName` method to ensure that the `Name` is always a valid value. Notice that `Name`'s setter is not `public`. - -> ABP does not force you to design your entities like that. It just can have public get/set for all properties. It's your decision to fully implement DDD practices. - -### IBookAppService Interface - -In ABP, an application service should implement the `IApplicationService` interface. It's good to create an interface for each application service: - -````csharp -public interface IBookAppService : IApplicationService -{ - Task CreateAsync(CreateBookDto input); -} -```` - -A Create method will be implemented as the example. `CreateBookDto` is defined like that: - -````csharp -public class CreateBookDto -{ - [Required] - [StringLength(Book.MaxNameLength)] - public string Name { get; set; } - - public BookType Type { get; set; } - - public float? Price { get; set; } -} -```` - -> See [data transfer objects document](Data-Transfer-Objects.md) for more about DTOs. - -### BookAppService (Implementation) - -````csharp -public class BookAppService : ApplicationService, IBookAppService -{ - private readonly IRepository _bookRepository; - - public BookAppService(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task CreateAsync(CreateBookDto input) - { - var book = new Book( - GuidGenerator.Create(), - input.Name, - input.Type, - input.Price - ); - - await _bookRepository.InsertAsync(book); - } -} -```` - -* `BookAppService` inherits from the `ApplicationService` base class. It's not required, but the `ApplicationService` class provides helpful properties for common application service requirements like `GuidGenerator` used in this service. If we didn't inherit from it, we would need to inject the `IGuidGenerator` service manually (see [guid generation](Guid-Generation.md) document). -* `BookAppService` implements the `IBookAppService` as expected. -* `BookAppService` [injects](Dependency-Injection.md) `IRepository` (see [repositories](Repositories.md)) and uses it inside the `CreateAsync` method to insert a new entity to the database. -* `CreateAsync` uses the constructor of the `Book` entity to create a new book from the properties of given `input`. - -## Data Transfer Objects - -Application services get and return DTOs instead of entities. ABP does not force this rule. However, exposing entities to the presentation layer (or to remote clients) has significant problems and is not suggested. - -See the [DTO documentation](Data-Transfer-Objects.md) for more. - -## Object to Object Mapping - -The `CreateAsync` method above manually creates a `Book` entity from given `CreateBookDto` object, because the `Book` entity enforces it (we designed it like that). - -However, in many cases, it's very practical to use **auto object mapping** to set properties of an object from a similar object. ABP provides an [object to object mapping](Object-To-Object-Mapping.md) infrastructure to make this even easier. - -Object to object mapping provides abstractions and it is implemented by the [AutoMapper](https://automapper.org/) library by default. - -Let's create another method to get a book. First, define the method in the `IBookAppService` interface: - -````csharp -public interface IBookAppService : IApplicationService -{ - Task CreateAsync(CreateBookDto input); - - Task GetAsync(Guid id); //New method -} -```` - -`BookDto` is a simple [DTO](Data-Transfer-Objects.md) class defined as below: - -````csharp -public class BookDto -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public BookType Type { get; set; } - - public float? Price { get; set; } -} -```` - -AutoMapper requires to create a mapping [profile class](https://docs.automapper.org/en/stable/Configuration.html#profile-instances). Example: - -````csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap(); - } -} -```` - -You should then register profiles using the `AbpAutoMapperOptions`: - -````csharp -[DependsOn(typeof(AbpAutoMapperModule))] -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - //Add all mappings defined in the assembly of the MyModule class - options.AddMaps(); - }); - } -} -```` - -`AddMaps` registers all profile classes defined in the assembly of the given class, typically your module class. It also registers for the [attribute mapping](https://docs.automapper.org/en/stable/Attribute-mapping.html). - -Then you can implement the `GetAsync` method as shown below: - -````csharp -public async Task GetAsync(Guid id) -{ - var book = await _bookRepository.GetAsync(id); - return ObjectMapper.Map(book); -} -```` - -See the [object to object mapping document](Object-To-Object-Mapping.md) for more. - -## Validation - -Inputs of application service methods are automatically validated (like ASP.NET Core controller actions). You can use the standard data annotation attributes or a custom validation method to perform the validation. ABP also ensures that the input is not null. - -See the [validation document](Validation.md) for more. - -## Authorization - -It's possible to use declarative and imperative authorization for application service methods. - -See the [authorization document](Authorization.md) for more. - -## CRUD Application Services - -If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from the `CrudAppService`. - -### Example - -Create an `IBookAppService` interface inheriting from the `ICrudAppService` interface. - -````csharp -public interface IBookAppService : - ICrudAppService< //Defines CRUD methods - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books - CreateUpdateBookDto, //Used to create a new book - CreateUpdateBookDto> //Used to update a book -{ -} -```` - -`ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface). - -> Creating an interface for an application service is good practice, but not required by the ABP Framework. You can skip the interface part. - -`ICrudAppService` declares the following methods: - -````csharp -public interface ICrudAppService< - TEntityDto, - in TKey, - in TGetListInput, - in TCreateInput, - in TUpdateInput> - : IApplicationService - where TEntityDto : IEntityDto -{ - Task GetAsync(TKey id); - - Task> GetListAsync(TGetListInput input); - - Task CreateAsync(TCreateInput input); - - Task UpdateAsync(TKey id, TUpdateInput input); - - Task DeleteAsync(TKey id); -} -```` - -DTO classes used in this example are `BookDto` and `CreateUpdateBookDto`: - -````csharp -public class BookDto : AuditedEntityDto -{ - public string Name { get; set; } - - public BookType Type { get; set; } - - public float Price { get; set; } -} - -public class CreateUpdateBookDto -{ - [Required] - [StringLength(128)] - public string Name { get; set; } - - [Required] - public BookType Type { get; set; } = BookType.Undefined; - - [Required] - public float Price { get; set; } -} -```` - -[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances) class of DTO class. - -```csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap(); - CreateMap(); - } -} -``` - -* `CreateUpdateBookDto` is shared by create and update operations, but you could use separated DTO classes as well. - -And finally, the `BookAppService` implementation is very simple: - -````csharp -public class BookAppService : - CrudAppService, - IBookAppService -{ - public BookAppService(IRepository repository) - : base(repository) - { - } -} -```` - -`CrudAppService` implements all methods declared in the `ICrudAppService` interface. You can then add your own custom methods or override and customize base methods. - -> `CrudAppService` has different versions gets different number of generic arguments. Use the one suitable for you. - -### AbstractKeyCrudAppService - -`CrudAppService` requires to have an Id property as the primary key of your entity. If you are using composite keys then you can not utilize it. - -`AbstractKeyCrudAppService` implements the same `ICrudAppService` interface, but this time without making assumption about your primary key. - -#### Example - -Assume that you have a `District` entity with `CityId` and `Name` as a composite primary key. Using `AbstractKeyCrudAppService` requires to implement `DeleteByIdAsync` and `GetEntityByIdAsync` methods yourself: - -````csharp -public class DistrictAppService - : AbstractKeyCrudAppService -{ - public DistrictAppService(IRepository repository) - : base(repository) - { - } - - protected async override Task DeleteByIdAsync(DistrictKey id) - { - await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name); - } - - protected async override Task GetEntityByIdAsync(DistrictKey id) - { - var queryable = await Repository.GetQueryableAsync(); - return await AsyncQueryableExecuter.FirstOrDefaultAsync( - queryable.Where(d => d.CityId == id.CityId && d.Name == id.Name) - ); - } -} -```` - -This implementation requires you to create a class that represents your composite key: - -````csharp -public class DistrictKey -{ - public Guid CityId { get; set; } - - public string Name { get; set; } -} -```` - -### Authorization (for CRUD App Services) - -There are two ways of authorizing the base application service methods; - -1. You can set the policy properties (xxxPolicyName) in the constructor of your service. Example: - -```csharp -public class MyPeopleAppService : CrudAppService -{ - public MyPeopleAppService(IRepository repository) - : base(repository) - { - GetPolicyName = "..."; - GetListPolicyName = "..."; - CreatePolicyName = "..."; - UpdatePolicyName = "..."; - DeletePolicyName = "..."; - } -} -``` - -`CreatePolicyName` is checked by the `CreateAsync` method and so on... You should specify a policy (permission) name defined in your application. - -2. You can override the check methods (CheckXxxPolicyAsync) in your service. Example: - -```csharp -public class MyPeopleAppService : CrudAppService -{ - public MyPeopleAppService(IRepository repository) - : base(repository) - { - } - - protected async override Task CheckDeletePolicyAsync() - { - await AuthorizationService.CheckAsync("..."); - } -} -``` - -You can perform any logic in the `CheckDeletePolicyAsync` method. It is expected to throw an `AbpAuthorizationException` in any unauthorized case, like `AuthorizationService.CheckAsync` already does. - -### Base Properties & Methods - -CRUD application service base class provides many useful base methods that **you can override** to customize it based on your requirements. - -#### CRUD Methods - -These are the essential CRUD methods. You can override any of them to completely customize the operation. Here, the definitions of the methods: - -````csharp -Task GetAsync(TKey id); -Task> GetListAsync(TGetListInput input); -Task CreateAsync(TCreateInput input); -Task UpdateAsync(TKey id, TUpdateInput input); -Task DeleteAsync(TKey id); -```` - -#### Querying - -These methods are low level methods that can control how to query entities from the database. - -* `CreateFilteredQuery` can be overridden to create an `IQueryable` that is filtered by the given input. If your `TGetListInput` class contains any filter, it is proper to override this method and filter the query. It returns the (unfiltered) repository (which is already `IQueryable`) by default. -* `ApplyPaging` is used to make paging on the query. If your `TGetListInput` already implements `IPagedResultRequest`, you don't need to override this since the ABP Framework automatically understands it and performs the paging. -* `ApplySorting` is used to sort (order by...) the query. If your `TGetListInput` already implements the `ISortedResultRequest`, ABP Framework automatically sorts the query. If not, it fallbacks to the `ApplyDefaultSorting` which tries to sort by creation time, if your entity implements the standard `IHasCreationTime` interface. -* `GetEntityByIdAsync` is used to get an entity by id, which calls `Repository.GetAsync(id)` by default. -* `DeleteByIdAsync` is used to delete an entity by id, which calls `Repository.DeleteAsync(id)` by default. - -#### Object to Object Mapping - -These methods are used to convert Entities to DTOs and vice verse. They use the [IObjectMapper](Object-To-Object-Mapping.md) by default. - -* `MapToGetOutputDtoAsync` is used to map the entity to the DTO returned from the `GetAsync`, `CreateAsync` and `UpdateAsync` methods. Alternatively, you can override the `MapToGetOutputDto` if you don't need to perform any async operation. -* `MapToGetListOutputDtosAsync` is used to map a list of entities to a list of DTOs returned from the `GetListAsync` method. It uses the `MapToGetListOutputDtoAsync` to map each entity in the list. You can override one of them based on your case. Alternatively, you can override the `MapToGetListOutputDto` if you don't need to perform any async operation. -* `MapToEntityAsync` method has two overloads; - * `MapToEntityAsync(TCreateInput)` is used to create an entity from `TCreateInput`. - * `MapToEntityAsync(TUpdateInput, TEntity)` is used to update an existing entity from `TUpdateInput`. - -## Miscellaneous - -### Working with Streams - -`Stream` object itself is not serializable. So, you may have problems if you directly use `Stream` as the parameter or the return value for your application service. ABP Framework provides a special type, `IRemoteStreamContent` to be used to get or return streams in the application services. - -**Example: Application Service Interface that can be used to get and return streams** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using Volo.Abp.Content; - -namespace MyProject.Test -{ - public interface ITestAppService : IApplicationService - { - Task Upload(Guid id, IRemoteStreamContent streamContent); - Task Download(Guid id); - - Task CreateFile(CreateFileInput input); - Task CreateMultipleFile(CreateMultipleFileInput input); - } - - public class CreateFileInput - { - public Guid Id { get; set; } - - public IRemoteStreamContent Content { get; set; } - } - - public class CreateMultipleFileInput - { - public Guid Id { get; set; } - - public IEnumerable Contents { get; set; } - } -} -```` - -**You need to configure `AbpAspNetCoreMvcOptions` to add DTO class to `FormBodyBindingIgnoredTypes` to use `IRemoteStreamContent` in** **DTO ([Data Transfer Object](Data-Transfer-Objects.md))** - -````csharp -Configure(options => -{ - options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateFileInput)); - options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateMultipleFileInput)); -}); -```` - -**Example: Application Service Implementation that can be used to get and return streams** - -````csharp -using System; -using System.IO; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Application.Services; -using Volo.Abp.Content; - -namespace MyProject.Test -{ - public class TestAppService : ApplicationService, ITestAppService - { - public Task Download(Guid id) - { - var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.OpenOrCreate); - return Task.FromResult( - (IRemoteStreamContent) new RemoteStreamContent(fs) { - ContentType = "application/octet-stream" - } - ); - } - - public async Task Upload(Guid id, IRemoteStreamContent streamContent) - { - using (var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.Create)) - { - await streamContent.GetStream().CopyToAsync(fs); - await fs.FlushAsync(); - } - } - - public async Task CreateFileAsync(CreateFileInput input) - { - using (var fs = new FileStream("C:\\Temp\\" + input.Id + ".blob", FileMode.Create)) - { - await input.Content.GetStream().CopyToAsync(fs); - await fs.FlushAsync(); - } - } - - public async Task CreateMultipleFileAsync(CreateMultipleFileInput input) - { - using (var fs = new FileStream("C:\\Temp\\" + input.Id + ".blob", FileMode.Append)) - { - foreach (var content in input.Contents) - { - await content.GetStream().CopyToAsync(fs); - } - await fs.FlushAsync(); - } - } - } -} -```` - -`IRemoteStreamContent` is compatible with the [Auto API Controller](API/Auto-API-Controllers.md) and [Dynamic C# HTTP Proxy](API/Dynamic-CSharp-API-Clients.md) systems. - -## Lifetime - -Lifetime of application services are [transient](Dependency-Injection.md) and they are automatically registered to the dependency injection system. - -## See Also - -* [Video tutorial Part-1](https://abp.io/video-courses/essentials/application-services-part-1) -* [Video tutorial Part-2](https://abp.io/video-courses/essentials/application-services-part-2) \ No newline at end of file diff --git a/docs/en/Application-Startup.md b/docs/en/Application-Startup.md deleted file mode 100644 index afa13fd050..0000000000 --- a/docs/en/Application-Startup.md +++ /dev/null @@ -1,320 +0,0 @@ -## ABP Application Startup - -You typically use the [ABP CLI](CLI.md)'s `abp new` command to [get started](Getting-Started.md) with one of the pre-built [startup solution templates](Startup-Templates/Index.md). When you do that, you generally don't need to know the details of how the ABP Framework is integrated with your application or how it is configured and initialized. The startup template also comes with the fundamental ABP packages and [application modules](Modules/Index) are pre-installed and configured for you. - -> It is always suggested to [get started with a startup template](Getting-Started.md) and modify it for your requirements. Read this document only if you want to understand the details or if you need to modify how the ABP Framework starts. - -While the ABP Framework has a lot of features and integrations, it is built as a lightweight and modular framework. It consists of [hundreds of NuGet and NPM packages](https://abp.io/packages), so you can only use the features you need. If you follow the [Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application](Getting-Started-AspNetCore-Application.md) document, you'll see how easy it is to install the ABP Framework into an empty ASP.NET Core project from scratch. You only need to install a single NuGet package and make a few small changes. - -This document is for who wants to better understand how the ABP Framework is initialized and configured on startup. - -## Installing to a Console Application - -A .NET Console application is the minimalist .NET application. So, it is best to show the installing of the ABP Framework to a console application as a minimalist example. - -If you [create a new console application with Visual Studio](https://learn.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio) (for .NET 8.0 or later), you will see the following solution structure (I named the solution as `MyConsoleDemo`): - -![app-startup-console-initial](images/app-startup-console-initial.png) - -This example uses the [top level statements](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements), so it consists of only a single line of code. - -The first step is to install the [Volo.Abp.Core](https://www.nuget.org/packages/Volo.Abp.Core) NuGet package, which is the most core NuGet package of the ABP framework. You can install it using the ABP CLI. Execute the following command in the folder of the .csproj file that you want to install the package on: - -````bash -abp add-package Volo.Abp.Core -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Core). - -Alternatively, you can use a command-line terminal in the root folder of the project (the folder containing the `MyConsoleDemo.csproj` file, for this example): - -````bash -dotnet add package Volo.Abp.Core -```` - -After adding the NuGet package, we should create a root [module class](Module-Development-Basics.md) for our application. We can create the following class in the project: - -````csharp -using Volo.Abp.Modularity; - -namespace MyConsoleDemo -{ - public class MyConsoleDemoModule : AbpModule - { - } -} -```` - -This is an empty class deriving from the `AbpModule` class. It is the main class that you will control your application's dependencies with, and implement your configuration and startup/shutdown logic. For more information, please check the [Modularity](Module-Development-Basics.md) document. - -As the second and the last step, change the `Program.cs` as shown in the following code block: - -````csharp -using MyConsoleDemo; -using Volo.Abp; - -// 1: Create the ABP application container -using var application = await AbpApplicationFactory.CreateAsync(); - -// 2: Initialize/start the ABP Framework (and all the modules) -await application.InitializeAsync(); - -Console.WriteLine("ABP Framework has been started..."); - -// 3: Stop the ABP Framework (and all the modules) -await application.ShutdownAsync(); -```` - -That's all. Now, ABP Framework is installed, integrated, started and stopped in your application. From now, you can install [ABP packages](https://abp.io/packages) to your application whenever you need them. - -## Installing a Framework Package - -If you want to send emails from your .NET application, you can use .NET's standard [SmtpClient class](https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient). ABP also provides an `IEmailSender` service that simplifies [sending emails](Emailing.md) and configuring the email settings in a central place. If you want to use it, you should install the [Volo.Abp.Emailing](https://www.nuget.org/packages/Volo.Abp.Emailing) NuGet package to your project: - -````bash -dotnet add package Volo.Abp.Emailing -```` - -Once you add a new ABP package/module, you also need to specify the module dependency from your module class. So, change the `MyConsoleDemoModule` class as shown below: - -````csharp -using Volo.Abp.Emailing; -using Volo.Abp.Modularity; - -namespace MyConsoleDemo -{ - [DependsOn(typeof(AbpEmailingModule))] // Added the module dependency - public class MyConsoleDemoModule : AbpModule - { - } -} -```` - -I've just added a `[DependsOn]` attribute to declare that I want to use the ABP Emailing Module (`AbpEmailingModule`). Now, I can use the `IEmailSender` service in my `Program.cs`: - -````csharp -using Microsoft.Extensions.DependencyInjection; -using MyConsoleDemo; -using Volo.Abp; -using Volo.Abp.Emailing; - -using var application = await AbpApplicationFactory.CreateAsync(); -await application.InitializeAsync(); - -// Sending emails using the IEmailSender service -var emailsender = application.ServiceProvider.GetRequiredService(); -await emailsender.SendAsync( - to: "info@acme.com", - subject: "Hello World", - body: "My message body..." -); - -await application.ShutdownAsync(); -```` - -> If you run that application, you get a runtime error indicating that the email sending settings haven't been done yet. You can check the [Email Sending document](Emailing.md) to learn how to configure it. - -That's all. Install an ABP NuGet package, add the module dependency (using the `[DependsOn]` attribute) and use any service inside the NuGet package. - -The [ABP CLI](CLI.md) already has a special command to perform the addition of an ABP NuGet and also adding the `[DependsOn]` attribute to your module class for you with a single command: - -````bash -abp add-package Volo.Abp.Emailing -```` - -We suggest you to use the `abp add-package` command instead of manually doing it. - -## AbpApplicationFactory - -`AbpApplicationFactory` is the main class that creates an ABP application container. It provides a single static `CreateAsync` (and `Create` if you can't use asynchronous programming) method with multiple overloads. Let's investigate these overloads to understand where you can use them. - -The first overload gets a generic module class parameter as we've used before in this document: - -````csharp -AbpApplicationFactory.CreateAsync(); -```` - -The generic class parameter should be the root module class of your application. All the other modules are resolved as dependencies of that module. - -The second overload gets the module class as a `Type` parameter, instead of the generic parameter. So, the previous code block could be re-written as shown below: - -````csharp -AbpApplicationFactory.CreateAsync(typeof(MyConsoleDemoModule)); -```` - -Both overloads work exactly the same. So, you can use the second one if you don't know the module class type on development time and you (somehow) calculate it on runtime. - -If you use one of the methods above, ABP creates an internal service collection (`IServiceCollection`) and an internal service provider (`IServiceProvider`) to setup the [dependency injection](Dependency-Injection.md) system internally. Notice that we've used the `application.ServiceProvider` property in the *Installing a Framework Package* section to resolve the `IEmailSender` service from the dependency injection system. - -The next overload gets an `IServiceCollection` parameter from you to allow you to setup the dependency injection system yourself, or integrate to another framework (like ASP.NET Core) that also sets up the dependency injection system internally. - -We can change the `Program.cs` as shown below to externally manage the dependency injection setup: - -````csharp -using Microsoft.Extensions.DependencyInjection; -using MyConsoleDemo; -using Volo.Abp; - -// 1: Manually created the IServiceCollection -IServiceCollection services = new ServiceCollection(); - -// 2: Pass the IServiceCollection externally to the ABP Framework -using var application = await AbpApplicationFactory - .CreateAsync(services); - -// 3: Manually built the IServiceProvider object -IServiceProvider serviceProvider = services.BuildServiceProvider(); - -// 4: Pass the IServiceProvider externally to the ABP Framework -await application.InitializeAsync(serviceProvider); - -Console.WriteLine("ABP Framework has been started..."); - -await application.ShutdownAsync(); -```` - -In this example, we've used .NET's standard dependency injection container. The `services.BuildServiceProvider()` call creates the standard container. However, ABP provides an alternative extension method, `BuildServiceProviderFromFactory()`, that properly works even if you are using another dependency injection container: - -````csharp -IServiceProvider serviceProvider = services.BuildServiceProviderFromFactory(); -```` - -> You can check the [Autofac Integration](Autofac-Integration.md) document if you want to learn how you can integrate the [Autofac](https://autofac.org/) dependency injection container with the ABP Framework. - -Finally, the `CreateAsync` method has a last overload that takes the module class name as a `Type` parameter and a `IServiceCollection` object. So, we could re-write the last `CreateAsync` method usage as in the following code block: - -````csharp -using var application = await AbpApplicationFactory - .CreateAsync(typeof(MyConsoleDemoModule), services); -```` - -> All of the `CreateAsync` method overloads have `Create` counterparts. If your application type can not utilize asynchronous programming (that means you can't use the `await` keyword), then you can use the `Create` method instead of the `CreateAsync` method. - -### AbpApplicationCreationOptions - -All of the `CreateAsync` overloads can get an optional `Action` parameter to configure the options that are used on the application creation. See the following example: - -````csharp -using var application = await AbpApplicationFactory - .CreateAsync(options => - { - options.ApplicationName = "MyApp"; - }); -```` - -We've passed a lambda method to configure the `ApplicationName` option. Here's a list of all standard options: - -* `ApplicationName`: A human-readable name for the application. It is a unique value for an application. -* `Configuration`: Can be used to setup the [application configuration](Configuration.md) when it is not provided by the hosting system. It is not needed for ASP.NET Core and other .NET hosted applications. However, if you've used `AbpApplicationFactory` with an internal service provider, you can use this option to configure how the application configuration is built. -* `Environment`: Environment name for the application. -* `PlugInSources`: A list of plugin sources. See the [Plug-In Modules documentation](PlugIn-Modules) to learn how to work with plugins. -* `Services`: The `IServiceCollection` object that can be used to register service dependencies. You generally don't need that, because you configure your services in your [module class](Module-Development-Basics.md). However, it can be used while writing extension methods for the `AbpApplicationCreationOptions` class. - -#### The ApplicationName option - -As defined above, the `ApplicationName` option is a human-readable name for the application. It is a unique value for an application. - -`ApplicationName` is used by the ABP Framework in several places to distinguish the application. For example, the [audit logging](Audit-Logging.md) system saves the `ApplicationName` in each audit log record written by the related application, so you can understand which application has created the audit log entry. So, if your system consists of multiple applications (like a microservice solution) that are saving audit logs to a single point, you should be sure that each application has a different `ApplicationName`. - -The `ApplicationName` property's value is set automatically from the **entry assembly's name** (generally, the project name in a .NET solution) by default, which is proper for most cases, since each application typically has a unique entry assembly name. - -There are two ways to set the application name to a different value. In this first approach, you can set the `ApplicationName` property in your application's [configuration](Configuration.md). The easiest way is to add an `ApplicationName` field to your `appsettings.json` file: - -````json -{ - "ApplicationName": "Services.Ordering" -} -```` - -Alternatively, you can set `AbpApplicationCreationOptions.ApplicationName` while creating the ABP application. You can find the `AddApplication` or `AddApplicationAsync` call in your solution (typically in the `Program.cs` file), and set the `ApplicationName` option as shown below: - -````csharp -await builder.AddApplicationAsync(options => -{ - options.ApplicationName = "Services.Ordering"; -}); -```` - -#### IApplicationInfoAccessor - -If you need to access the `ApplicationName` later in your solution, you can inject the `IApplicationInfoAccessor` service and get the value from its `ApplicationName` property. - -`IApplicationInfoAccessor` also provides an `InstanceId` value, that is a random GUID value that is generated when your application starts. You can use that value to distinguish application instances from each other. - -## IAbpApplication - -`AbpApplicationFactory` returns an `IAbpApplication` object from its `CreateAsync` (or `Create`) method. `IAbpApplication` is the main container for an ABP application. It is also registered to the [dependency injection](Dependency-Injection.md) system, so you can inject `IAbpApplication` in your services to use its properties and methods. - -Here's a list of `IAbpApplication` properties you may want to know: - -* `StartupModuleType`: Gets the root module of the application that was used while creating the application container (on the `AbpApplicationFactory.CreateAsync` method). -* `Services`: A list of all service registrations (the `IServiceCollection` object). You can not add new services to this collection after application initialization (you can actually add, but it won't have any effect). -* `ServiceProvider`: A reference to the root service provider used by the application. This can not be used before initializing the application. If you need to resolve non-singleton services from that `IServiceProvider` object, always create a new service scope and dispose it after usage. Otherwise, your application will have memory leak problems. See the *Releasing/Disposing Services* section of the [dependency injection](Dependency-Injection.md) document for more information about service scopes. -* `Modules`: A read-only list of all the modules loaded into the current application. Alternatively, you can inject the `IModuleContainer` service if you need to access the module list in your application code. - -The `IAbpApplication` interface extends the `IApplicationInfoAccessor` interface, so you can get the `ApplicationName` and `InstanceId` values from it. However, if you only need to access these properties, inject and use the `IApplicationInfoAccessor` service instead. - -`IAbpApplication` is disposable. Always dispose of it before exiting your application. - -## IAbpHostEnvironment - -Sometimes, while creating an application, we need to get the current hosting environment and take actions according to that. In such cases, we can use some services such as [IWebHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.iwebhostenvironment?view=aspnetcore-8.0) or [IWebAssemblyHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.iwebassemblyhostenvironment) provided by .NET, in the final application. - -However, we can not use these services in a class library, which is used by the final application. ABP Framework provides the `IAbpHostEnvironment` service, which allows you to get the current environment name whenever you want. `IAbpHostEnvironment` is used by the ABP Framework in several places to perform specific actions by the environment. For example, ABP Framework reduces the cache duration on the **Development** environment for some services. - -`IAbpHostEnvironment` obtains the current environment name by the following order: - -1. Gets and sets the environment name if it's specified in the `AbpApplicationCreationOptions`. -2. Tries to obtain the environment name from the `IWebHostEnvironment` or `IWebAssemblyHostEnvironment` services for ASP.NET Core & Blazor WASM applications if the environment name isn't specified in the `AbpApplicationCreationOptions`. -3. Sets the environment name as **Production**, if the environment name is not specified or can not be obtained from the services. - -You can configure the `AbpApplicationCreationOptions` [options class](Options.md) while creating the ABP application and set an environment name to its `Environment` property. You can find the `AddApplication` or `AddApplicationAsync` call in your solution (typically in the `Program.cs` file), and set the `Environment` option as shown below: - -```csharp -await builder.AddApplicationAsync(options => -{ - options.Environment = Environments.Staging; //or directly set as "Staging" -}); -``` - -Then, whenever you need to get the current environment name or check the environment, you can use the `IAbpHostEnvironment` interface: - -```csharp -public class MyDemoService -{ - private readonly IAbpHostEnvironment _abpHostEnvironment; - - public MyDemoService(IAbpHostEnvironment abpHostEnvironment) - { - _abpHostEnvironment = abpHostEnvironment; - } - - public void MyMethod() - { - var environmentName = _abpHostEnvironment.EnvironmentName; - - if (_abpHostEnvironment.IsDevelopment()) { /* ... */ } - - if (_abpHostEnvironment.IsStaging()) { /* ... */ } - - if (_abpHostEnvironment.IsProduction()) { /* ... */ } - - if (_abpHostEnvironment.IsEnvironment("custom-environment")) { /* ... */ } - } -} -``` - -## .NET Generic Host & ASP.NET Core Integrations - -`AbpApplicationFactory` can create a standalone ABP application container without any external dependency. However, in most cases, you will want to integrate it with [.NET's generic host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) or ASP.NET Core. For such usages, ABP provides built-in extension methods to easily create an ABP application container that is well-integrated to these systems. - -The [Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application](Getting-Started-AspNetCore-Application.md) document clearly explains how you can create an ABP application container in an ASP.NET Core application. - -You can also [create a console application](Startup-Templates/Console) to see how it is integrated with .NET Generic Host. - -> Most of the times, you will directly create ABP applications using the ABP CLI's `new` command. So, you don't need to care about these integration details. - -## See Also - -* [Dependency injection](Dependency-Injection.md) -* [Modularity](Module-Development-Basics.md) diff --git a/docs/en/Apps/VoloDocs.md b/docs/en/Apps/VoloDocs.md deleted file mode 100644 index 06d8d0bf66..0000000000 --- a/docs/en/Apps/VoloDocs.md +++ /dev/null @@ -1,183 +0,0 @@ -# VoloDocs - -## What is VoloDocs? - -VoloDocs is a cross-platform web application that allows you to easily create beautiful documentation and build developer communities. It simplifies software documentation with the help of GitHub integration. You use the power of GitHub for versioning, hosting of your docs. You let your users to edit a document. - -## Main Features - -- Serves documents from your GitHub repository. -- Supports Markdown / HTML document formatting. -- Supports versioning (integrated to GitHub releases). -- Supports multiple projects. -- Allows users to edit a document on GitHub. -- Cross-platform; deployable to Windows / Linux / macOS. - -## GitHub Repository - -It's free & open-source. You can browse VoloDocs source-code and contribute on GitHub: - -https://github.com/abpframework/abp/tree/master/modules/docs - -## Download - -You can download the VoloDocs release from the following links: - -https://apps.abp.io/VoloDocs/VoloDocs.win-x64.zip - **Windows 64 bit** - -https://apps.abp.io/VoloDocs/VoloDocs.win-x86.zip - **Windows 32 bit** - -https://apps.abp.io/VoloDocs/VoloDocs.osx-x64.zip - **MacOS** - -https://apps.abp.io/VoloDocs/VoloDocs.linux-x64.zip - **Linux** - -Notice that, all installations are self-contained deployments. It means all the required third-party dependencies along with the version of .NET Core is included. So you don't need to install any .NET Core SDK / Runtime. - -## Folder Structure - -When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `Migrator` folder. The `Web` folder contains the website files and `Migrator` contains the application to build your database. Before publishing your website, you need to create a new database or update your existing database to the latest. If this is the first time you install VoloDocs, `Migrator` will create a new database for you, otherwise it updates to the latest version. The only setting you need to configure, is the `ConnectionString` which is located in the `appsettings.json` file. See the next section to learn how to configure your VoloDocs application. - -## Steps by Step Deployment - -- ### Database Migration - - To update your existing database or create your initial database, go to `Migrator` folder in your VoloDocs directory. - - Open `appsettings.json` in your text editor and set your database connection string. If you don't know how to write the connection string for your database system, you can check out https://www.connectionstrings.com/. - - After you set your connection string, run `Migrate.bat` for Windows platform and `VoloDocs.Migrator` for other operating systems. That's it now configure your website. - -- ### Configuring Website - - Go to `Web` folder in your VoloDocs directory. Open `appsettings.json` in your text editor. Set your connection string (same as in the `Migrator`'s `appsettings.json`). That's it! Now you can publish your website. - - If you want to run - -- ### Deploying Website - - In the previous step, you created or updated your database. Ensure that your database exists on the specified connection string. - - - #### Deploying to IIS - - - Move `Web` folder to your `wwwroot ` folder. - - Rename `Web` folder to `VoloDocs` (Now you have `C:\inetpub\wwwroot\VoloDocs`).![Add IIS Website](../images/volodocs-iis-add-website.png) - - The `VoloDocs` application pool is being created automatically. Open **Application Pools** and double click `VoloDocs` application pool and set - - **.NET CLR version**: `No Managed Code` - - **Managed pipeline mode**: `Integrated` - - ![Add IIS Website](../images/volodocs-iis-application-pool.png) - - - - - If you get the below error, it means don't have the hosting bundle installed on the server. See [this document](https://docs.microsoft.com/aspnet/core/host-and-deploy/iis/#install-the-net-core-hosting-bundle) to learn how to install it or [download Hosting Bundle](https://www.microsoft.com/net/permalink/dotnetcore-current-windows-runtime-bundle-installer) and run on your server. - - ``` - Handler "aspNetCore" has a bad module "AspNetCoreModuleV2" in its module list using IIS - ``` - - - Further information about hosting VoloDocs check out [Microsoft's official document for hosting ASP.NET Core application on IIS](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis). - - - #### Deploying to Azure - - Microsoft has a good document on how to deploy your ASP.NET Core web app to Azure App Service. We recommend you to read this document https://docs.microsoft.com/en-us/azure/app-service/app-service-web-get-started-dotnet. - - - #### Running the Application From Command Line - - Alternatively you can run the application from command line, navigate to `VoloDocs\Web` folder and run `VoloDocs.Web.exe` for Windows or `VoloDocs.Web` for MacOS / Linux. - -- ### First Run - - To start the website, navigate to your address (as configured in the previous section). - - When you first open the website, you need to create a project. - - #### Creating a Project - - Go to the following address to create project - - - `http:///Account/Login?returnUrl=/Docs/Admin/Projects` - - ##### Default credentials - - To login the admin side, use the following credentials: - - * **Username**: `admin` - - * **Password**: `1q2w3E*` - - ##### An example project definition - - Here's a sample project information that uses GitHub source. - - We will configure the VoloDocs to show ABP Framework's documentation that's stored in GitHub. - - Here's the link to ABP Framework GitHub docs folder: - - https://github.com/abpframework/abp/tree/master/docs/en - - - - * **Name**: `ABP Framework` - - * **Short name**: `abp` - - * **Format**: `markdown` - - * **Default document name**: `Index` - - * **Navigation document name**: `docs-nav.json` ([see the sample navigation](https://github.com/abpframework/abp/blob/master/docs/en/docs-nav.json)) - - * **Parameters Document Name**: `docs-params.json` ([see the sample parameters](https://github.com/abpframework/abp/blob/dev/docs/en/docs-params.json)) - - * **Minimum version**: *leave empty* *(hides the previous versions)* - - * **Main web site URL**: `/` - - * **Latest version branch name**: leave empty () - - * **GitHub root URL**: `https://github.com/abpframework/abp/tree/{version}/docs/` - - * **GitHub access token**: [see how to retrieve GitHub access token](#retrieving-github-access-token) - - * **GitHub user agent**: [see how to learn your GitHub username](#learn-your-github-username) - - * **GitHub version provider source**: `Releases` (other option is `Branches`) - - * **Version branch prefix**: leave empty () - - ![Creating a new project](../images/docs-create-project-v4.4.0.png) - - ##### Retrieving GitHub Access Token - - To create a personal access token in GitHub, you need to visit the **Settings** of the user account and under **Developer settings** you will find **Personal access tokens**. Select **Generate new token**, enter in a name as the Token description and enable the repo checkbox. Alternatively, to enter generate new token, browse to https://github.com/settings/tokens/new. - - ###### Generate Token for Public Repositories - - To access public repositories, check `public_repo` under the `repo` section. This will enable VoloDocs to access your public GitHub repositories. Click `Generate Token` button on the bottom of the page. - - ![Retrieve GitHub Access Token for Public Repo](../images/github-access-token-public-repo.jpg) - - ###### Generate Token for Private Repositories - - To access public repositories, check all items under the `repo` section. This will enable VoloDocs to access your private GitHub repositories. Click `Generate Token` button on the bottom of the page. - - ![Retrieve GitHub Access Token for Private Repo](../images/github-access-token-private-repo.jpg) - - ###### Learn Your GitHub Username - - To learn your GitHub username, click on your profile picture on the top-right corner of the GitHub page. You will see your username right after the text "Signed in as ..." - - ![Your GitHub Username](../images/github-myusername.jpg) - - - -After you save the project, go to root website address and you will see your documentation. - -`http:///documents` - -### Any Issues? - -If you encounter any problem or issues about installation, usage or report a bug, follow the link: - -https://github.com/abpframework/abp/issues/new - diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md deleted file mode 100644 index e337b78a6d..0000000000 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ /dev/null @@ -1,731 +0,0 @@ -# Migrating from ASP.NET Boilerplate to the ABP Framework - -ABP Framework is **the successor** of the open source [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework. This guide aims to help you to **migrate your existing solutions** (you developed with the ASP.NET Boilerplate framework) to the ABP Framework. - -## Introduction - -**ASP.NET Boilerplate** is being **actively developed** [since 2013](https://github.com/aspnetboilerplate/aspnetboilerplate/graphs/contributors). It is loved, used and contributed by the community. It started as a side project of [a developer](http://halilibrahimkalkan.com/), but now it is officially maintained and improved by the company [Volosoft](https://volosoft.com/) in addition to the great community support. - -ABP Framework has the same goal of the ASP.NET Boilerplate framework: **Don't Repeat Yourself**! It provides infrastructure, tools and startup templates to make a developer's life easier while developing enterprise software solutions. - -See [the introduction blog post](https://blog.abp.io/abp/Abp-vNext-Announcement) if you wonder why we needed to re-write the ASP.NET Boilerplate framework. - -### Should I Migrate? - -No, you don't have to! - -* ASP.NET Boilerplate is still in active development and maintenance. -* It also works on the latest ASP.NET Core and related libraries and tools. It is up to date. - -However, if you want to take the advantage of the new ABP Framework [features](https://abp.io/features) and the new architecture opportunities (like support for NoSQL databases, microservice compatibility, advanced modularity), you can use this document as a guide. - -### What About the ASP.NET Zero? - -[ASP.NET Zero](https://aspnetzero.com/) is a commercial product developed by the core ASP.NET Boilerplate team, on top of the ASP.NET Boilerplate framework. It provides pre-built application [features](https://aspnetzero.com/Features), code generation tooling and a nice looking modern UI. It is trusted and used by thousands of companies from all around the World. - -We have created the [ABP Commercial](https://commercial.abp.io/) as an alternative to the ASP.NET Zero. ABP Commercial is more modular and upgradeable compared to the ASP.NET Zero. It currently has less features compared to ASP.NET Zero, but the gap will be closed by the time (it also has some features don't exist in the ASP.NET Zero). - -We think ASP.NET Zero is still a good choice while starting a new application. It is production ready and mature solution delivered as a full source code. It is being actively developed and we are constantly adding new features. - -We don't suggest to migrate your ASP.NET Zero based solution to the ABP Commercial if; - -* Your ASP.NET Zero solution is mature and it is in maintenance rather than a rapid development. -* You don't have enough development time to perform the migration. -* A monolithic solution fits in your business. -* You've customized existing ASP.NET Zero features too much based on your requirements. - -We also suggest you to compare the features of two products based on your needs. - -If you have an ASP.NET Zero based solution and want to migrate to the ABP Commercial, this guide will also help you. - -### ASP.NET MVC 5.x Projects - -The ABP Framework doesn't support ASP.NET MVC 5.x, it only works with ASP.NET Core. So, if you migrate your ASP.NET MVC 5.x based projects, you will also deal with the .NET Core migration. - -## The Migration Progress - -We've designed the ABP Framework by **getting the best parts** of the ASP.NET Boilerplate framework, so it will be familiar to you if you've developed ASP.NET Boilerplate based applications. - -In the ASP.NET Boilerplate, we have not worked much on the UI side, but used some free themes (we've used [metronic theme](https://keenthemes.com/metronic/) for ASP.NET Zero on the other side). In the ABP Framework, we worked a lot on the UI side (especially for the MVC / Razor Pages UI, because Angular already has a good modular system of its own). So, the **most challenging part** of the migration will be the **User Interface** of your solution. - -ABP Framework is (and ASP.NET Boilerplate was) designed based on the [Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) patterns & principles and the startup templates are layered based on the DDD layers. So, this guide respects to that layering model and explains the migration layer by layer. - -## Creating the Solution - -First step of the migration is to create a new solution. We suggest you to create a fresh new project using [the startup templates](https://abp.io/get-started) (see [this document](https://docs.abp.io/en/commercial/latest/getting-started) for the ABP Commercial). - -After creating the project and running the application, you can copy your code from your existing solution to the new solution step by step, layer by layer. - -### About Pre-Built Modules - -The startup projects for the ABP Framework use the [pre-built modules](https://docs.abp.io/en/abp/latest/Modules/Index) (not all of them, but the essentials) and themes as NuGet/NPM packages. So, you don't see the source code of the modules/themes in your solution. This has an advantage that you can easily update these packages when a new version is released. However, you can not easily customize them as their source code in your hands. - -We suggest to continue to use these modules as package references, in this way you can get new features easily (see [abp update command](https://docs.abp.io/en/abp/latest/CLI#update)). In this case, you have a few options to customize or extend the functionality of the used modules; - -* You can create your own entity and share the same database table with an entity in a used module. An example of this is the `AppUser` entity comes in the startup template. -* You can [replace](https://docs.abp.io/en/abp/latest/Dependency-Injection#replace-a-service) a domain service, application service, controller, page model or other types of services with your own implementation. We suggest you to inherit from the existing implementation and override the method you need. -* You can replace a `.cshtml` view, page, view component, partial view... with your own one using the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). -* You can override javascript, css, image or any other type of static files using the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). - -More extend/customization options will be developed and documented by the time. However, if you need to fully change the module implementation, it is best to add the [source code](https://github.com/abpframework/abp/tree/dev/modules) of the related module into your own solution and remove the package dependencies. - -The source code of the modules and the themes are [MIT](https://opensource.org/licenses/MIT) licensed, you can fully own and customize it without any limitation (for the ABP Commercial, you can download the source code of a [module](https://commercial.abp.io/modules)/[theme](https://commercial.abp.io/themes) if you have a [license](https://commercial.abp.io/pricing) type that includes the source code). - -## The Domain Layer - -Most of your domain layer code will remain same, while you need to perform some minor changes in your domain objects. - -### Aggregate Roots & Entities - -The ABP Framework and the ASP.NET Boilerplate both have the `IEntity` and `IEntity` interfaces and `Entity` and `Entity` base classes to define entities but they have some differences. - -If you have an entity in the ASP.NET Boilerplate application like that: - -````csharp -public class Person : Entity //Default PK is int for the ASP.NET Boilerplate -{ - ... -} -```` - -Then your primary key (the `Id` property in the base class) is `int` which is the **default primary key** (PK) type for the ASP.NET Boilerplate. If you want to set another type of PK, you need to explicitly declare it: - -````csharp -public class Person : Entity //Set explicit PK in the ASP.NET Boilerplate -{ - ... -} -```` - -ABP Framework behaves differently and expects to **always explicitly set** the PK type: - -````csharp -public class Person : Entity //Set explicit PK in the ASP.NET Boilerplate -{ - ... -} -```` - -`Id` property (and the corresponding PK in the database) will be `Guid` in this case. - -#### Composite Primary Keys - -ABP Framework also has a non-generic `Entity` base class, but this time it has no `Id` property. Its purpose is to allow you to create entities with composite PKs. See [the documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys) to learn more about the composite PKs. - -#### Aggregate Root - -It is best practice now to use the `AggregateRoot` base class instead of `Entity` for aggregate root entities. See [the documentation](https://docs.abp.io/en/abp/latest/Entities#aggregateroot-class) to learn more about the aggregate roots. - -In opposite to the ASP.NET Boilerplate, the ABP Framework creates default repositories (`IRepository`) **only for the aggregate roots**. It doesn't create for other types derived from the `Entity`. - -If you still want to create default repositories for all entity types, find the *YourProjectName*EntityFrameworkCoreModule class in your solution and change `options.AddDefaultRepositories()` to `options.AddDefaultRepositories(includeAllEntities: true)` (it may be already like that for the application startup template). - -#### Migrating the Existing Entities - -We suggest & use the GUID as the PK type for all the ABP Framework modules. However, you can continue to use your existing PK types to migrate your database tables easier. - -The challenging part will be the primary keys of the ASP.NET Boilerplate related entities, like Users, Roles, Tenants, Settings... etc. Our suggestion is to copy data from existing database to the new database tables using a tool or in a manual way (be careful about the foreign key values). - -#### Documentation - -See the documentation for details on the entities: - -* [ASP.NET Boilerplate - Entity documentation](https://aspnetboilerplate.com/Pages/Documents/Entities) -* [ABP Framework - Entity documentation](https://docs.abp.io/en/abp/latest/Entities) - -### Repositories - -> ABP Framework creates default repositories (`IRepository`) **only for the aggregate roots**. It doesn't create for other types derived from the `Entity`. See the "Aggregate Root" section above for more information. - -The ABP Framework and the ASP.NET Boilerplate both have the default generic repository system, but has some differences. - -#### Injecting the Repositories - -In the ASP.NET Boilerplate, there are two default repository interfaces you can directly inject and use: - -* `IRepository` (e.g. `IRepository`) is used for entities with `int` primary key (PK) which is the default PK type. -* `IRepository` (e.g. `IRepository`) is used for entities with other types of PKs. - -ABP Framework doesn't have a default PK type, so you need to **explicitly declare the PK type** of your entity, like `IRepository` or `IRepository`. - -ABP Framework also has the `IRepository` (without PK), but it is mostly used when your entity has a composite PK (because this repository has no methods work with the `Id` property). See [the documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys) to learn more about the **composite PKs**. - -#### Restricted Repositories - -ABP Framework additionally provides a few repository interfaces: - -* `IBasicRepository` has the same methods with the `IRepository` except it doesn't have `IQueryable` support. It can be useful if you don't want to expose complex querying code to the application layer. In this case, you typically want to create custom repositories to encapsulate the querying logic. It is also useful for database providers those don't support `IQueryable`. -* `IReadOnlyRepository` has the methods get data from the database, but doesn't contain any method change the database. -* `IReadOnlyBasicRepository` is similar to the read only repository but also doesn't support `IQueryable`. - -All the interfaces also have versions without `TKey` (like ``IReadOnlyRepository`) those can be used for composite PKs just like explained above. - -#### GetAll() vs IQueryable - -ASP.NET Boilerplate's repository has a `GetAll()` method that is used to obtain an `IQueryable` object to execute LINQ on it. An example application service calls the `GetAll()` method: - -````csharp -public class PersonAppService : ApplicationService, IPersonAppService -{ - private readonly IRepository _personRepository; - - public PersonAppService(IRepository personRepository) - { - _personRepository = personRepository; - } - - public async Task DoIt() - { - var people = await _personRepository - .GetAll() //GetAll() returns IQueryable - .Where(p => p.BirthYear > 2000) //Use LINQ extension methods - .ToListAsync(); - } -} -```` - -ABP Framework's repository have `GetQueryableAsync` instead: - -````csharp -public class PersonAppService : ApplicationService, IPersonAppService -{ - private readonly IRepository _personRepository; - - public PersonAppService(IRepository personRepository) - { - _personRepository = personRepository; - } - - public async Task DoIt() - { - var queryable = await _personRepository.GetQueryableAsync(); - var people = await queryable - .Where(p => p.BirthYear > 2000) //Use LINQ extension methods - .ToListAsync(); - } -} -```` - -> Note that in order to use the async LINQ extension methods (like `ToListAsync` here), you may need to depend on the database provider (like EF Core) since these methods are defined in the database provider package, they are not standard LINQ methods. See the [repository document](Repositories.md) for alternative approaches for async query execution. - -#### FirstOrDefault(predicate), Single()... Methods - -ABP Framework repository has not such methods get predicate (expression) since the repository itself is `IQueryable` and all these methods are already standard LINQ extension methods those can be directly used. - -However, it provides the following methods those can be used to query a single entity by its Id: - -* `FindAsync(id)` returns the entity or null if not found. -* `GetAsync(id)` method returns the entity or throws an `EntityNotFoundException` (which causes HTTP 404 status code) if not found. - -#### Sync vs Async - -ABP Framework repository has no sync methods (like `Insert`). All the methods are async (like `InsertAsync`). So, if your application has sync repository method usages, convert them to async versions. - -In general, ABP Framework forces you to completely use async everywhere, because mixing async & sync methods is not a recommended approach. - -#### Documentation - -See the documentation for details on the repositories: - -* [ASP.NET Boilerplate - Repository documentation](https://aspnetboilerplate.com/Pages/Documents/Repositories) -* [ABP Framework - Repository documentation](https://docs.abp.io/en/abp/latest/Repositories) - -### Domain Services - -Your domain service logic mostly remains same on the migration. ABP Framework also defines the base `DomainService` class and the `IDomainService` interface just works like the ASP.NET Boilerplate. - -## The Application Layer - -Your application service logic remains similar on the migration. ABP Framework also defines the base `ApplicationService` class and the `IApplicationService` interface just works like the ASP.NET Boilerplate, but there are some differences in details. - -### Declarative Authorization - -ASP.NET Boilerplate has `AbpAuthorize` and `AbpMvcAuthorize` attributes for declarative authorization. Example usage: - -````csharp -[AbpAuthorize("MyUserDeletionPermissionName")] -public async Task DeleteUserAsync(...) -{ - ... -} -```` - -ABP Framework doesn't has such a custom attribute. It uses the standard `Authorize` attribute in all layers. - -````csharp -[Authorize("MyUserDeletionPermissionName")] -public async Task DeleteUserAsync(...) -{ - ... -} -```` - -This is possible with the better integration to the Microsoft Authorization Extensions libraries. See the Authorization section below for more information about the authorization system. - -### CrudAppService and AsyncCrudAppService Classes - -ASP.NET Boilerplate has `CrudAppService` (with sync service methods) and `AsyncCrudAppService` (with async service methods) classes. - -ABP Framework only has the `CrudAppService` which actually has only the async methods (instead of sync methods). - -ABP Framework's `CrudAppService` method signatures are slightly different than the old one. For example, old update method signature was ` Task UpdateAsync(TUpdateInput input) ` while the new one is ` Task UpdateAsync(TKey id, TUpdateInput input) `. The main difference is that it gets the Id of the updating entity as a separate parameter instead of including in the input DTO. - -### Data Transfer Objects (DTOs) - -There are similar base DTO classes (like `EntityDto`) in the ABP Framework too. So, you can find the corresponding DTO base class if you need. - -#### Validation - -You can continue to use the data annotation attributes to validate your DTOs just like in the ASP.NET Boilerplate. - -ABP Framework doesn't include the ` ICustomValidate ` that does exists in the ASP.NET Boilerplate. Instead, you should implement the standard `IValidatableObject` interface for your custom validation logic. - -## The Infrastructure Layer - -### Namespaces - -ASP.NET Boilerplate uses the `Abp.*` namespaces while the ABP Framework uses the `Volo.Abp.*` namespaces for the framework and pre-built fundamental modules. - -In addition, there are also some pre-built application modules (like docs and blog modules) those are using the `Volo.*` namespaces (like `Volo.Blogging.*` and `Volo.Docs.*`). We consider these modules as standalone open source products developed by Volosoft rather than add-ons or generic modules completing the ABP Framework and used in the applications. We've developed them as a module to make them re-usable as a part of a bigger solution. - -### Module System - -Both of the ASP.NET Boilerplate and the ABP Framework have the `AbpModule` while they are a bit different. - -ASP.NET Boilerplate's `AbpModule` class has `PreInitialize`, `Initialize` and `PostInitialize` methods you can override and configure the framework and the depended modules. You can also register and resolve dependencies in these methods. - -ABP Framework's `AbpModule` class has the `ConfigureServices` and `OnApplicationInitialization` methods (and their Pre and Post versions). It is similar to ASP.NET Core's Startup class. You configure other services and register dependencies in the `ConfigureServices`. However, you can now resolve dependencies in that point. You can resolve dependencies and configure the ASP.NET Core pipeline in the `OnApplicationInitialization` method while you can not register dependencies here. So, the new module classes separate dependency registration phase from dependency resolution phase since it follows the ASP.NET Core's approach. - -### Dependency Injection - -#### The DI Framework - -ASP.NET Boilerplate is using the [Castle Windsor](http://www.castleproject.org/projects/windsor/) as the dependency injection framework. This is a fundamental dependency of the ASP.NET Boilerplate framework. We've got a lot of feedback to make the ASP.NET Boilerplate DI framework agnostic, but it was not so easy because of the design. - -ABP Framework is dependency injection framework independent since it uses Microsoft's [Dependency Injection Extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) library as an abstraction. None of the ABP Framework or module packages depends on any specific library. - -However, ABP Framework doesn't use the Microsoft's base DI library because it has some missing features ABP Framework needs to: Property Injection and Interception. All the startup templates and the samples are using the [Autofac](https://autofac.org/) as the DI library and it is the only [officially integrated](Autofac-Integration.md) library to the ABP Framework. We suggest you to use the Autofac with the ABP Framework if you have not a good reason. If you have a good reason, please create an [issue](https://github.com/abpframework/abp/issues/new) on GitHub to request it or just implement it and send a pull request :) - -#### Registering the Dependencies - -Registering the dependencies are similar and mostly handled by the framework conventionally (like repositories, application services, controllers... etc). Implement the same `ITransientDependency`, `ISingletonDependency` and `IScopedDependency` interfaces for the services not registered by conventions. - -When you need to manually register dependencies, use the `context.Services` in the `ConfigureServices` method of your module. Example: - -````csharp -public class BlogModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //Register an instance as singleton - context.Services.AddSingleton(new TaxCalculator(taxRatio: 0.18)); - - //Register a factory method that resolves from IServiceProvider - context.Services.AddScoped( - sp => sp.GetRequiredService() - ); - } -} -```` - -See the ABP Framework [dependency injection document](https://docs.abp.io/en/abp/latest/Dependency-Injection) for details. - -### Configuration vs Options System - -ASP.NET Boilerplate has its own configuration system to configure the framework and the modules. For example, you could disable the audit logging in the `Initialize` method of your [module](https://aspnetboilerplate.com/Pages/Documents/Module-System): - -````csharp -public override void Initialize() -{ - Configuration.Auditing.IsEnabled = false; -} -```` - -ABP Framework uses [the options pattern](Options.md) to configure the framework and the modules. You typically configure the options in the `ConfigureServices` method of your [module](Module-Development-Basics.md): - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - Configure(options => - { - options.IsEnabled = false; - }); -} -```` - -Instead of a central configuration object, there are separated option classes for every module and feature those are defined in the related documents. - -### IAbpSession vs ICurrentUser and ICurrentTenant - -ASP.NET Boilerplate's `IAbpSession` service is used to obtain the current user and tenant information, like ` UserId ` and `TenantId`. - -ABP Framework doesn't have the same service. Instead, use `ICurrentUser` and `ICurrentTenant` services. These services are defined as base properties in some common classes (like `ApplicationService` and `AbpController`), so you generally don't need to manually inject them. They also have much properties compared to the `IAbpSession`. - -### Authorization - -ABP Framework extends the [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing the authorization system to be usable in the [application services](Application-Services.md) too. - -#### AbpAuthorize vs Authorize - -Use the standard `[Authorize]` and `[AllowAnonymous]` attributes instead of ASP.NET Boilerplate's custom `[AbpAuthorize]` and `[AbpAllowAnonymous]` attributes. - -#### IPermissionChecker vs IAuthorizationService - -Use the standard `IAuthorizationService` to check permissions instead of the ASP.NET Boilerplate's `IPermissionChecker` service. While `IPermissionChecker` also exists in the ABP Framework, it is used to explicitly use the permissions. Using `IAuthorizationService` is the recommended way since it covers other type of policy checks too. - -#### AuthorizationProvider vs PermissionDefinitionProvider - -You inherit from the `AuthorizationProvider` in the ASP.NET Boilerplate to define your permissions. ABP Framework replaces it by the `PermissionDefinitionProvider` base class. So, define your permissions by inheriting from the `PermissionDefinitionProvider` class. - -### Unit of Work - -Unit of work system has been designed to work seamlessly. For most of the cases, you don't need to change anything. - -`UnitOfWork` attribute of the ABP Framework doesn't have the `ScopeOption` (type of `TransactionScopeOption`) property. Instead, use `IUnitOfWorkManager.Begin()` method with `requiresNew = true` to create an independent inner transaction in a transaction scope. - -#### Data Filters - -ASP.NET Boilerplate implements the data filtering system as a part of the unit of work. ABP Framework has a separate `IDataFilter` service. - -See the [data filtering document](Data-Filtering.md) to learn how to enable/disable a filter. - -See [the UOW documentation](Unit-Of-Work.md) for more about the UOW system. - -### Multi-Tenancy - -#### IMustHaveTenant & IMayHaveTenant vs IMultiTenant - -ASP.NET Boilerplate defines `IMustHaveTenant` and `IMayHaveTenant` interfaces to implement them for your entities. In this way, your entities are automatically filtered according to the current tenant. Because of the design, there was a problem: You had to create a "Default" tenant in the database with "1" as the Id if you want to create a non multi-tenant application (this "Default" tenant was used as the single tenant). - -ABP Framework has a single interface for multi-tenant entities: `IMultiTenant` which defines a nullable `TenantId` property of type `Guid`. If your application is not multi-tenant, then your entities will have null TenantId (instead of a default one). - -On the migration, you need to change the TenantId field type and replace these interfaces with the `IMultiTenant` - -#### Switch Between Tenants - -In some cases you might need to switch to a tenant for a code scope and work with the tenant's data in this scope. - -In ASP.NET Boilerplate, it is done using the `IUnitOfWorkManager` service: - -````csharp -public async Task> GetProducts(int tenantId) -{ - using (_unitOfWorkManager.Current.SetTenantId(tenantId)) - { - return await _productRepository.GetAllListAsync(); - } -} -```` - -In the ABP Framework it is done with the `ICurrentTenant` service: - -````csharp -public async Task> GetProducts(Guid tenantId) -{ - using (_currentTenant.Change(tenantId)) - { - return await _productRepository.GetListAsync(); - } -} -```` - -Pass `null` to the `Change` method to switch to the host side. - -### Caching - -ASP.NET Boilerplate has its [own distributed caching abstraction](https://aspnetboilerplate.com/Pages/Documents/Caching) which has in-memory and Redis implementations. You typically inject the `ICacheManager` service and use its `GetCache(...)` method to obtain a cache, then get and set objects in the cache. - -ABP Framework uses and extends ASP.NET Core's [distributed caching abstraction](Caching.md). It defines the `IDistributedCache` services to inject a cache and get/set objects. - -### Logging - -ASP.NET Boilerplate uses Castle Windsor's [logging facility](https://github.com/castleproject/Windsor/blob/master/docs/logging-facility.md) as an abstraction and supports multiple logging providers including Log4Net (the default one comes with the startup projects) and Serilog. You typically property-inject the logger: - -````csharp -using Castle.Core.Logging; //1: Import Logging namespace - -public class TaskAppService : ITaskAppService -{ - //2: Getting a logger using property injection - public ILogger Logger { get; set; } - - public TaskAppService() - { - //3: Do not write logs if no Logger supplied. - Logger = NullLogger.Instance; - } - - public void CreateTask(CreateTaskInput input) - { - //4: Write logs - Logger.Info("Creating a new task with description: " + input.Description); - //... - } -} -```` - -ABP Framework depends on Microsoft's [logging extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) library which is also an abstraction and there are many providers implement it. Startup templates are using the Serilog as the pre-configured logging libary while it is easy to change in your project. The usage pattern is similar: - -````csharp -//1: Import the Logging namespaces -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -public class TaskAppService : ITaskAppService -{ - //2: Getting a logger using property injection - public ILogger Logger { get; set; } - - public TaskAppService() - { - //3: Do not write logs if no Logger supplied. - Logger = NullLogger.Instance; - } - - public void CreateTask(CreateTaskInput input) - { - //4: Write logs - Logger.Info("Creating a new task with description: " + input.Description); - //... - } -} -```` - -You inject the `ILogger` instead of the `ILogger`. - -### Object to Object Mapping - -#### IObjectMapper Service - -ASP.NET Boilerplate defines an `IObjectMapper` service ([see](https://aspnetboilerplate.com/Pages/Documents/Object-To-Object-Mapping)) and has an integration to the [AutoMapper](https://automapper.org/) library. - -Example usage: Create a `User` object with the given `CreateUserInput` object: - -````csharp -public void CreateUser(CreateUserInput input) -{ - var user = ObjectMapper.Map(input); - ... -} -```` - -Example: Update an existing `User` properties with the given `UpdateUserInput` object: - -````csharp -public async Task UpdateUserAsync(Guid id, UpdateUserInput input) -{ - var user = await _userRepository.GetAsync(id); - ObjectMapper.Map(input, user); -} -```` - -ABP Framework has the same `IObjectMapper` service ([see](Object-To-Object-Mapping.md)) and the AutoMapper integration with a slightly different mapping methods. - -Example usage: Create a `User` object with the given `CreateUserInput` object: - -````csharp -public void CreateUser(CreateUserInput input) -{ - var user = ObjectMapper.Map(input); -} -```` - -This time you need to explicitly declare the source type and target type (while ASP.NET Boilerplate was requiring only the target type). - -Example: Update an existing `User` properties with the given `UpdateUserInput` object: - -````csharp -public async Task UpdateUserAsync(Guid id, UpdateUserInput input) -{ - var user = await _userRepository.GetAsync(id); - ObjectMapper.Map(input, user); -} -```` - -Again, ABP Framework expects to explicitly set the source and target types. - -#### AutoMapper Integration - -##### Auto Mapping Attributes - -ASP.NET Boilerplate has `AutoMapTo`, `AutoMapFrom` and `AutoMap` attributes to automatically create mappings for the declared types. Example: - -````csharp -[AutoMapTo(typeof(User))] -public class CreateUserInput -{ - public string Name { get; set; } - public string Surname { get; set; } - ... -} -```` - -ABP Framework has no such attributes, because AutoMapper as a [similar attribute](https://automapper.readthedocs.io/en/latest/Attribute-mapping.html) now. You need to switch to AutoMapper's attribute. - -##### Mapping Definitions - -ABP Framework follows AutoMapper principles closely. You can define classes derived from the `Profile` class to define your mappings. - -##### Configuration Validation - -Configuration validation is a best practice for the AutoMapper to maintain your mapping configuration in a safe way. - -See [the documentation](Object-To-Object-Mapping.md) for more information related to the object mapping. - -### Setting Management - -#### Defining the Settings - -In an ASP.NET Boilerplate based application, you create a class deriving from the `SettingProvider` class, implement the `GetSettingDefinitions` method and add your class to the `Configuration.Settings.Providers` list. - -In the ABP Framework, you need to derive your class from the `SettingDefinitionProvider` and implement the `Define` method. You don't need to register your class since the ABP Framework automatically discovers it. - -#### Getting the Setting Values - -ASP.NET Boilerplate provides the `ISettingManager` to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side. - -ABP Framework has the `ISettingProvider` service to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side. - -#### Setting the Setting Values - -For ASP.NET Boilerplate, you use the same `ISettingManager` service to change the setting values. - -ABP Framework separates it and provides the setting management module (pre-added to the startup projects) which has the ` ISettingManager ` to change the setting values. This separation was introduced to support tiered deployment scenarios (where `ISettingProvider` can also work in the client application while `ISettingManager ` can also work in the server (API) side). - -### Clock - -ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplate.com/Pages/Documents/Timing)) which is used to abstract the `DateTime` kind, so you can easily switch between Local and UTC times. You don't inject it, but just use the `Clock.Now` static method to obtain the current time. - -ABP Framework has the `IClock` service ([see](Timing.md)) which has a similar goal, but now you need to inject it whenever you need it. - -### Event Bus - -ASP.NET Boilerplate has an in-process event bus system. You typically inject the `IEventBus` (or use the static instance `EventBus.Default`) to trigger an event. It automatically triggers events for entity changes (like `EntityCreatingEventData` and `EntityUpdatedEventData`). You create a class by implementing the `IEventHandler` interface. - -ABP Framework separates the event bus into two services: `ILocalEventBus` and `IDistributedEventBus`. - -The local event bus is similar to the event bus of the ASP.NET Boilerplate while the distributed event bus is new feature introduced in the ABP Framework. - -So, to migrate your code; - -* Use the `ILocalEventBus` instead of the `IEventBus`. -* Implement the `ILocalEventHandler` instead of the `IEventHandler`. - -> Note that ABP Framework has also an `IEventBus` interface, but it does exists to be a common interface for the local and distributed event bus. It is not injected and directly used. - -### Feature Management - -Feature system is used in multi-tenant applications to define features of your application check if given feature is available for the current tenant. - -#### Defining Features - -In the ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Feature-Management)), you create a class inheriting from the `FeatureProvider`, override the `SetFeatures` method and add your class to the `Configuration.Features.Providers` list. - -In the ABP Framework ([see](Features.md)), you derive your class from the `FeatureDefinitionProvider` and override the `Define` method. No need to add your class to the configuration, it is automatically discovered by the framework. - -#### Checking Features - -You can continue to use the `RequiresFeature` attribute and `IFeatureChecker` service to check if a feature is enabled for the current tenant. - -#### Changing the Feature Values - -In the ABP Framework you use the `IFeatureManager` to change a feature value for a tenant. - -### Audit Logging - -The ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Audit-Logging)) and the ABP Framework ([see](Audit-Logging.md)) has similar audit logging systems. ABP Framework requires to add `UseAuditing()` middleware to the ASP.NET Core pipeline, which is already added in the startup templates. So, most of the times it will be work out of the box. - -### Localization - -ASP.NET Boilerplate supports XML and JSON files to define the localization key-values for the UI ([see](https://aspnetboilerplate.com/Pages/Documents/Localization)). ABP Framework only supports the JSON formatter localization files ([see](Localization.md)). So, you need to convert your XML file to JSON. - -The ASP.NET Boilerplate has its own the `ILocalizationManager` service to be injected and used for the localization in the server side. - -The ABP Framework uses [Microsoft localization extension](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) library, so it is completely integrated to ASP.NET Core. You use the `IStringLocalizer` service to get a localized text. Example: - -````csharp -public class MyService -{ - private readonly IStringLocalizer _localizer; - - public MyService(IStringLocalizer localizer) - { - _localizer = localizer; - } - - public void Foo() - { - var str = _localizer["HelloWorld"]; //Get a localized text - } -} -```` - -So, you need to replace `ILocalizationManager` usage by the `IStringLocalizer`. - -It also provides API used in the client side: - -````js -var testResource = abp.localization.getResource('Test'); -var str = testResource('HelloWorld'); -```` - -It was like `abp.localization.localize(...)` in the ASP.NET Boilerplate. - -### Navigation vs Menu - -In ASP.NET Boilerplate you create a class deriving from the `NavigationProvider` to define your menu elements. Menu items has `requiredPermissionName` attributes to restrict access to a menu element. Menu items were static and your class is executed only one time. - -In the ABP Framework you need to create a class implements the `IMenuContributor` interface. Your class is executed whenever the menu needs to be rendered. So, you can conditionally add menu items. - -As an example, this is the menu contributor of the tenant management module: - -````csharp -public class AbpTenantManagementWebMainMenuContributor : IMenuContributor -{ - public async Task ConfigureMenuAsync(MenuConfigurationContext context) - { - //Add items only to the main menu - if (context.Menu.Name != StandardMenus.Main) - { - return; - } - - //Get the standard administration menu item - var administrationMenu = context.Menu.GetAdministration(); - - //Resolve some needed services from the DI container - var l = context.GetLocalizer(); - - var tenantManagementMenuItem = new ApplicationMenuItem( - TenantManagementMenuNames.GroupName, - l["Menu:TenantManagement"], - icon: "fa fa-users"); - - administrationMenu.AddItem(tenantManagementMenuItem); - - //Conditionally add the "Tenants" menu item based on the permission - if (await context.IsGrantedAsync(TenantManagementPermissions.Tenants.Default)) - { - tenantManagementMenuItem.AddItem( - new ApplicationMenuItem( - TenantManagementMenuNames.Tenants, - l["Tenants"], - url: "/TenantManagement/Tenants")); - } - } -} -```` - -So, you need to check permission using the `IAuthorizationService` if you want to show a menu item only when the user has the related permission. - -> Navigation/Menu system is only for ASP.NET Core MVC / Razor Pages applications. Angular applications has a different system implemented in the startup templates. - -## Missing Features - -The following features are not present for the ABP Framework. Here, a list of some major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): - -* [Multi-Lingual Entities](https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities) ([#1754](https://github.com/abpframework/abp/issues/1754)) -* [Real time notification system](https://aspnetboilerplate.com/Pages/Documents/Notification-System) ([#633](https://github.com/abpframework/abp/issues/633)) -* [NHibernate Integration](https://aspnetboilerplate.com/Pages/Documents/NHibernate-Integration) ([#339](https://github.com/abpframework/abp/issues/339)) - We don't intent to work on this, but any community contribution welcome. - -Some of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework, it is appreciated. diff --git a/docs/en/AspNetCore/Auto-API-Controllers.md b/docs/en/AspNetCore/Auto-API-Controllers.md deleted file mode 100644 index 52db2bde06..0000000000 --- a/docs/en/AspNetCore/Auto-API-Controllers.md +++ /dev/null @@ -1,3 +0,0 @@ -This document has moved. - -[Click to navigate to Auto API Controllers document](../API/Auto-API-Controllers.md) \ No newline at end of file diff --git a/docs/en/AspNetCore/Bundling-Minification.md b/docs/en/AspNetCore/Bundling-Minification.md deleted file mode 100644 index a67fbe429d..0000000000 --- a/docs/en/AspNetCore/Bundling-Minification.md +++ /dev/null @@ -1,4 +0,0 @@ - -This document has moved. - -[Click to navigate to ASP.NET Core MVC Bundling & Minification document](../UI/AspNetCore/Bundling-Minification.md) \ No newline at end of file diff --git a/docs/en/AspNetCore/Client-Side-Package-Management.md b/docs/en/AspNetCore/Client-Side-Package-Management.md deleted file mode 100644 index 780633de50..0000000000 --- a/docs/en/AspNetCore/Client-Side-Package-Management.md +++ /dev/null @@ -1,4 +0,0 @@ - -This document has moved. - -[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md) diff --git a/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md b/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md deleted file mode 100644 index 4ca26e8220..0000000000 --- a/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md +++ /dev/null @@ -1,3 +0,0 @@ -This document has moved. - -[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md) diff --git a/docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md b/docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md deleted file mode 100644 index 35382845ce..0000000000 --- a/docs/en/AspNetCore/Tag-Helpers/Dynamic-Forms.md +++ /dev/null @@ -1,3 +0,0 @@ -This document has moved. - -[Click to navigate to Dynamic Forms document](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) \ No newline at end of file diff --git a/docs/en/AspNetCore/Tag-Helpers/Index.md b/docs/en/AspNetCore/Tag-Helpers/Index.md deleted file mode 100644 index c15c645feb..0000000000 --- a/docs/en/AspNetCore/Tag-Helpers/Index.md +++ /dev/null @@ -1,3 +0,0 @@ -This document has moved. - -[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md) diff --git a/docs/en/AspNetCore/Theming.md b/docs/en/AspNetCore/Theming.md deleted file mode 100644 index 716cf5fa9e..0000000000 --- a/docs/en/AspNetCore/Theming.md +++ /dev/null @@ -1,4 +0,0 @@ - -This document has moved. - -[Click to navigate to Theming document](../UI/AspNetCore/Theming.md) \ No newline at end of file diff --git a/docs/en/AspNetCore/Widgets.md b/docs/en/AspNetCore/Widgets.md deleted file mode 100644 index 562678c1d8..0000000000 --- a/docs/en/AspNetCore/Widgets.md +++ /dev/null @@ -1,4 +0,0 @@ - -This document has moved. - -[Click to navigate to Widgets document](../UI/AspNetCore/Widgets.md) diff --git a/docs/en/Aspect-Oriented-Programming.md b/docs/en/Aspect-Oriented-Programming.md deleted file mode 100644 index 76a23daa8f..0000000000 --- a/docs/en/Aspect-Oriented-Programming.md +++ /dev/null @@ -1,3 +0,0 @@ -## Dynamic Proxying / Interceptors - -TODO \ No newline at end of file diff --git a/docs/en/Audit-Logging.md b/docs/en/Audit-Logging.md deleted file mode 100644 index ffacc1b10f..0000000000 --- a/docs/en/Audit-Logging.md +++ /dev/null @@ -1,391 +0,0 @@ -# Audit Logging - -[Wikipedia](https://en.wikipedia.org/wiki/Audit_trail): "*An audit trail (also called **audit log**) is a security-relevant chronological record, set of records, and/or destination and source of records that provide documentary evidence of the sequence of activities that have affected at any time a specific operation, procedure, or event*". - -ABP Framework provides an **extensible audit logging system** that automates the audit logging by **convention** and provides **configuration** points to control the level of the audit logs. - -An **audit log object** (see the Audit Log Object section below) is typically created & saved per web request. It includes; - -* **Request & response details** (like URL, Http method, Browser info, HTTP status code... etc.). -* **Performed actions** (controller actions and application service method calls with their parameters). -* **Entity changes** occurred in the web request. -* **Exception** information (if there was an error while executing the request). -* **Request duration** (to measure the performance of the application). - -> [Startup templates](Startup-Templates/Index.md) are configured for the audit logging system which is suitable for most of the applications. Use this document for a detailed control over the audit log system. - -### Database Provider Support - -* Fully supported by the [Entity Framework Core](Entity-Framework-Core.md) provider. -* Entity change logging is not supported by the [MongoDB](MongoDB.md) provider. Other features work as expected. - -## UseAuditing() - -`UseAuditing()` middleware should be added to the ASP.NET Core request pipeline in order to create and save the audit logs. If you've created your applications using [the startup templates](Startup-Templates/Index.md), it is already added. - -## AbpAuditingOptions - -`AbpAuditingOptions` is the main [options object](Options.md) to configure the audit log system. You can configure it in the `ConfigureServices` method of your [module](Module-Development-Basics.md): - -````csharp -Configure(options => -{ - options.IsEnabled = false; //Disables the auditing system -}); -```` - -Here, a list of the options you can configure: - -* `IsEnabled` (default: `true`): A root switch to enable or disable the auditing system. Other options is not used if this value is `false`. -* `HideErrors` (default: `true`): Audit log system hides and write regular [logs](Logging.md) if any error occurs while saving the audit log objects. If saving the audit logs is critical for your system, set this to `false` to throw exception in case of hiding the errors. -* `IsEnabledForAnonymousUsers` (default: `true`): If you want to write audit logs only for the authenticated users, set this to `false`. If you save audit logs for anonymous users, you will see `null` for `UserId` values for these users. -* `AlwaysLogOnException` (default: `true`): If you set to true, it always saves the audit log on an exception/error case without checking other options (except `IsEnabled`, which completely disables the audit logging). -* `IsEnabledForIntegrationService` (default: `false`): Audit Logging is disabled for [integration services](Integration-Services.md) by default. Set this property as `true` to enable it. -* `IsEnabledForGetRequests` (default: `false`): HTTP GET requests should not make any change in the database normally and audit log system doesn't save audit log objects for GET request. Set this to `true` to enable it also for the GET requests. -* `DisableLogActionInfo` (default: `false`):If you set to true, Will no longer log `AuditLogActionInfo`. -* `ApplicationName`: If multiple applications are saving audit logs into a single database, set this property to your application name, so you can distinguish the logs of different applications. If you don't set, it will set from the `IApplicationInfoAccessor.ApplicationName` value, which is the entry assembly name by default. -* `IgnoredTypes`: A list of `Type`s to be ignored for audit logging. If this is an entity type, changes for this type of entities will not be saved. This list is also used while serializing the action parameters. -* `EntityHistorySelectors`: A list of selectors those are used to determine if an entity type is selected for saving the entity change. See the section below for details. -* `SaveEntityHistoryWhenNavigationChanges` (default: `true`): If you set to true, it will save entity changes to audit log when any navigation property changes. -* `Contributors`: A list of `AuditLogContributor` implementations. A contributor is a way of extending the audit log system. See the "Audit Log Contributors" section below. -* `AlwaysLogSelectors`: A list of selectors to save the audit logs for the matched criteria. - -### Entity History Selectors - -Saving all changes of all your entities would require a lot of database space. For this reason, **audit log system doesn't save any change for the entities unless you explicitly configure it**. - -To save all changes of all entities, simply use the `AddAllEntities()` extension method. - -````csharp -Configure(options => -{ - options.EntityHistorySelectors.AddAllEntities(); -}); -```` - -`options.EntityHistorySelectors` actually a list of type predicate. You can write a lambda expression to define your filter. - -The example selector below does the same of the `AddAllEntities()` extension method defined above: - -````csharp -Configure(options => -{ - options.EntityHistorySelectors.Add( - new NamedTypeSelector( - "MySelectorName", - type => - { - if (typeof(IEntity).IsAssignableFrom(type)) - { - return true; - } - else - { - return false; - } - } - ) - ); -}); -```` - -The condition `typeof(IEntity).IsAssignableFrom(type)` will be `true` for any class implements the `IEntity` interface (this is technically all the entities in your application). You can conditionally check and return `true` or `false` based on your preference. - -`options.EntityHistorySelectors` is a flexible and dynamic way of selecting the entities for audit logging. Another way is to use the `Audited` and `DisableAuditing` attributes per entity. - -## AbpAspNetCoreAuditingOptions - -`AbpAspNetCoreAuditingOptions` is the [options object](Options.md) to configure audit logging in the ASP.NET Core layer. You can configure it in the `ConfigureServices` method of your [module](Module-Development-Basics.md): - -````csharp -Configure(options => -{ - options.IgnoredUrls.Add("/products"); -}); -```` - -`IgnoredUrls` is the only option. It is a list of ignored URLs prefixes. In the preceding example, all URLs starting with `/products` will be ignored for audit logging. - -## Enabling/Disabling Audit Logging for Services - -### Enable/Disable for Controllers & Actions - -All the controller actions are logged by default (see `IsEnabledForGetRequests` above for GET requests). - -You can use the `[DisableAuditing]` to disable it for a specific controller type: - -````csharp -[DisableAuditing] -public class HomeController : AbpController -{ - //... -} -```` - -Use `[DisableAuditing]` for any action to control it in the action level: - -````csharp -public class HomeController : AbpController -{ - [DisableAuditing] - public async Task Home() - { - //... - } - - public async Task OtherActionLogged() - { - //... - } -} -```` - -### Enable/Disable for Application Services & Methods - -[Application service](Application-Services.md) method calls also included into the audit log by default. You can use the `[DisableAuditing]` in service or method level. - -#### Enable/Disable for Other Services - -Action audit logging can be enabled for any type of class (registered to and resolved from the [dependency injection](Dependency-Injection.md)) while it is only enabled for the controllers and the application services by default. - -Use `[Audited]` and `[DisableAuditing]` for any class or method that need to be audit logged. In addition, your class can (directly or inherently) implement the `IAuditingEnabled` interface to enable the audit logging for that class by default. - -### Enable/Disable for Entities & Properties - -An entity is ignored on entity change audit logging in the following cases; - -* If you add an entity type to the `AbpAuditingOptions.IgnoredTypes` (as explained before), it is completely ignored in the audit logging system. -* If the object is not an [entity](Entities.md) (not implements `IEntity` directly or inherently - All entities implement this interface by default). -* If entity type is not public. - -Otherwise, you can use `Audited` to enable entity change audit logging for an entity: - -````csharp -[Audited] -public class MyEntity : Entity -{ - //... -} -```` - -Or disable it for an entity: - -````csharp -[DisableAuditing] -public class MyEntity : Entity -{ - //... -} -```` - -Disabling audit logging can be necessary only if the entity is being selected by the `AbpAuditingOptions.EntityHistorySelectors` that explained before. - -You can disable auditing only some properties of your entities for a detailed control over the audit logging: - -````csharp -[Audited] -public class MyUser : Entity -{ - public string Name { get; set; } - - public string Email { get; set; } - - [DisableAuditing] //Ignore the Passoword on audit logging - public string Password { get; set; } -} -```` - -Audit log system will save changes for the `MyUser` entity while it ignores the `Password` property which can be dangerous to save for security purposes. - -In some cases, you may want to save a few properties but ignore all others. Writing `[DisableAuditing]` for all the other properties would be tedious. In such cases, use `[Audited]` only for the desired properties and mark the entity with the `[DisableAuditing]` attribute: - -````csharp -[DisableAuditing] -public class MyUser : Entity -{ - [Audited] //Only log the Name change - public string Name { get; set; } - - public string Email { get; set; } - - public string Password { get; set; } -} -```` - -## IAuditingStore - -`IAuditingStore` is an interface that is used to save the audit log objects (explained below) by the ABP Framework. If you need to save the audit log objects to a custom data store, you can implement the `IAuditingStore` in your own application and replace using the [dependency injection system](Dependency-Injection.md). - -`SimpleLogAuditingStore` is used if no audit store was registered. It simply writes the audit object to the standard [logging system](Logging.md). - -[The Audit Logging Module](Modules/Audit-Logging.md) has been configured in [the startup templates](Startup-Templates/Index.md) saves audit log objects to a database (it supports multiple database providers). So, most of the times you don't care about how `IAuditingStore` was implemented and used. - -## Audit Log Object - -An **audit log object** is created for each **web request** by default. An audit log object can be represented by the following relation diagram: - -![**auditlog-object-diagram**](images/auditlog-object-diagram.png) - -* **AuditLogInfo**: The root object with the following properties: - * `ApplicationName`: When you save audit logs of different applications to the same database, this property is used to distinguish the logs of the applications. - * `UserId`: Id of the current user, if the user has logged in. - * `UserName`: User name of the current user, if the user has logged in (this value is here to not depend on the identity module/system for lookup). - * `TenantId`: Id of the current tenant, for a multi-tenant application. - * `TenantName`: Name of the current tenant, for a multi-tenant application. - * `ExecutionTime`: The time when this audit log object has been created. - * `ExecutionDuration`: Total execution duration of the request, in milliseconds. This can be used to observe the performance of the application. - * `ClientId`: Id of the current client, if the client has been authenticated. A client is generally a 3rd-party application using the system over an HTTP API. - * `ClientName`: Name of the current client, if available. - * `ClientIpAddress`: IP address of the client/user device. - * `CorrelationId`: Current [Correlation Id](CorrelationId.md). Correlation Id is used to relate the audit logs written by different applications (or microservices) in a single logical operation. - * `BrowserInfo`: Browser name/version info of the current user, if available. - * `HttpMethod`: HTTP method of the current request (GET, POST, PUT, DELETE... etc.). - * `HttpStatusCode`: HTTP response status code for this request. - * `Url`: URL of the request. -* **AuditLogActionInfo**: An audit log action is typically a controller action or an [application service](Application-Services.md) method call during the web request. One audit log may contain multiple actions. An action object has the following properties: - * `ServiceName`: Name of the executed controller/service. - * `MethodName`: Name of the executed method of the controller/service. - * `Parameters`: A JSON formatted text representing the parameters passed to the method. - * `ExecutionTime`: The time when this method was executed. - * `ExecutionDuration`: Duration of the method execution, in milliseconds. This can be used to observe the performance of the method. -* **EntityChangeInfo**: Represents a change of an entity in this web request. An audit log may contain zero or more entity changes. An entity change has the following properties: - * `ChangeTime`: The time when the entity was changed. - * `ChangeType`: An enum with the following fields: `Created` (0), `Updated` (1) and `Deleted` (2). - * `EntityId`: Id of the entity that was changed. - * `EntityTenantId`: Id of the tenant this entity belongs to. - * `EntityTypeFullName`: Type (class) name of the entity with full namespace (like *Acme.BookStore.Book* for the Book entity). -* **EntityPropertyChangeInfo**: Represents a change of a property of an entity. An entity change info (explained above) may contain one or more property change with the following properties: - * `NewValue`: New value of the property. It is `null` if the entity was deleted. - * `OriginalValue`: Old/original value before the change. It is `null` if the entity was newly created. - * `PropertyName`: The name of the property on the entity class. - * `PropertyTypeFullName`: Type (class) name of the property with full namespace. -* **Exception**: An audit log object may contain zero or more exception. In this way, you can get a report of the failed requests. -* **Comment**: An arbitrary string value to add custom messages to the audit log entry. An audit log object may contain zero or more comments. - -In addition to the standard properties explained above, `AuditLogInfo`, `AuditLogActionInfo` and `EntityChangeInfo` objects implement the `IHasExtraProperties` interface, so you can add custom properties to these objects. - -## Audit Log Contributors - -You can extend the auditing system by creating a class that is derived from the `AuditLogContributor` class which defines the `PreContribute` and the `PostContribute` methods. - -The only pre-built contributor is the `AspNetCoreAuditLogContributor` class which sets the related properties for an HTTP request. - -A contributor can set properties and collections of the `AuditLogInfo` class to add more information. - -Example: - -````csharp -public class MyAuditLogContributor : AuditLogContributor -{ - public override void PreContribute(AuditLogContributionContext context) - { - var currentUser = context.ServiceProvider.GetRequiredService(); - context.AuditInfo.SetProperty( - "MyCustomClaimValue", - currentUser.FindClaimValue("MyCustomClaim") - ); - } - - public override void PostContribute(AuditLogContributionContext context) - { - context.AuditInfo.Comments.Add("Some comment..."); - } -} -```` - -* `context.ServiceProvider` can be used to resolve services from the [dependency injection](Dependency-Injection.md). -* `context.AuditInfo` can be used to access to the current audit log object to manipulate it. - -After creating such a contributor, you must add it to the `AbpAuditingOptions.Contributors` list: - -````csharp -Configure(options => -{ - options.Contributors.Add(new MyAuditLogContributor()); -}); -```` - -## IAuditLogScope & IAuditingManager - -This section explains the `IAuditLogScope` & `IAuditingManager` services for advanced use cases. - -An **audit log scope** is an [ambient scope](Ambient-Context-Pattern.md) that **builds** and **saves** an audit log object (explained before). By default, an audit log scope is created for a web request by the Audit Log Middleware (see `UseAuditing()` section above). - -### Access to the Current Audit Log Scope - -Audit log contributors, was explained above, is a global way of manipulating the audit log object. It is good if you can get a value from a service. - -If you need to manipulate the audit log object in an arbitrary point of your application, you can access to the current audit log scope and get the current audit log object (independent of how the scope is managed). Example: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IAuditingManager _auditingManager; - - public MyService(IAuditingManager auditingManager) - { - _auditingManager = auditingManager; - } - - public async Task DoItAsync() - { - var currentAuditLogScope = _auditingManager.Current; - if (currentAuditLogScope != null) - { - currentAuditLogScope.Log.Comments.Add( - "Executed the MyService.DoItAsync method :)" - ); - - currentAuditLogScope.Log.SetProperty("MyCustomProperty", 42); - } - } -} -```` - -Always check if `_auditingManager.Current` is null or not, because it is controlled in an outer scope and you can't know if an audit log scope was created before calling your method. - -### Manually Create an Audit Log Scope - -You rarely need to create a manual audit log scope, but if you need, you can create an audit log scope using the `IAuditingManager` as like in the following example: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IAuditingManager _auditingManager; - - public MyService(IAuditingManager auditingManager) - { - _auditingManager = auditingManager; - } - - public async Task DoItAsync() - { - using (var auditingScope = _auditingManager.BeginScope()) - { - try - { - //Call other services... - } - catch (Exception ex) - { - //Add exceptions - _auditingManager.Current.Log.Exceptions.Add(ex); - throw; - } - finally - { - //Always save the log - await auditingScope.SaveAsync(); - } - } - } -} -```` - -You can call other services, they may call others, they may change entities and so on. All these interactions are saved as a single audit log object in the finally block. - -## The Audit Logging Module - -The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. It supports multiple database providers. This module is added to the startup templates by default. - -See [the Audit Logging Module document](Modules/Audit-Logging.md) for more about it. diff --git a/docs/en/Authentication/Social-External-Logins.md b/docs/en/Authentication/Social-External-Logins.md deleted file mode 100644 index e80e53d4bd..0000000000 --- a/docs/en/Authentication/Social-External-Logins.md +++ /dev/null @@ -1,3 +0,0 @@ -# Social/External Logins - -> This document has been moved. See the [Account Module](../Modules/Account.md) documentation. \ No newline at end of file diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md deleted file mode 100644 index 37e50279e9..0000000000 --- a/docs/en/Authorization.md +++ /dev/null @@ -1,476 +0,0 @@ -# Authorization - -Authorization is used to check if a user is allowed to perform some specific operations in the application. - -ABP extends [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing authorization system to be usable in the **[application services](Application-Services.md)** too. - -So, all the ASP.NET Core authorization features and the documentation are valid in an ABP based application. This document focuses on the features that are added on top of ASP.NET Core authorization features. - -## Authorize Attribute - -ASP.NET Core defines the [**Authorize**](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/simple) attribute that can be used for an action, a controller or a page. ABP allows you to use the same attribute for an [application service](Application-Services.md) too. - -Example: - -```csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Application.Services; - -namespace Acme.BookStore -{ - [Authorize] - public class AuthorAppService : ApplicationService, IAuthorAppService - { - public Task> GetListAsync() - { - ... - } - - [AllowAnonymous] - public Task GetAsync(Guid id) - { - ... - } - - [Authorize("BookStore_Author_Create")] - public Task CreateAsync(CreateAuthorDto input) - { - ... - } - } -} - -``` - -- `Authorize` attribute forces the user to login into the application in order to use the `AuthorAppService` methods. So, `GetListAsync` method is only available to the authenticated users. -- `AllowAnonymous` suppresses the authentication. So, `GetAsync` method is available to everyone including unauthorized users. -- `[Authorize("BookStore_Author_Create")]` defines a policy (see [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies)) that is checked to authorize the current user. - -"BookStore_Author_Create" is an arbitrary policy name. If you declare an attribute like that, ASP.NET Core authorization system expects a policy to be defined before. - -You can, of course, implement your policies as stated in the ASP.NET Core documentation. But for simple true/false conditions like a policy was granted to a user or not, ABP defines the permission system which will be explained in the next section. - -## Permission System - -A permission is a simple policy that is granted or prohibited for a particular user, role or client. - -### Defining Permissions - -To define permissions, create a class inheriting from the `PermissionDefinitionProvider` as shown below: - -```csharp -using Volo.Abp.Authorization.Permissions; - -namespace Acme.BookStore.Permissions -{ - public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider - { - public override void Define(IPermissionDefinitionContext context) - { - var myGroup = context.AddGroup("BookStore"); - - myGroup.AddPermission("BookStore_Author_Create"); - } - } -} -``` - -> ABP automatically discovers this class. No additional configuration required! - -> You typically define this class inside the `Application.Contracts` project of your [application](Startup-Templates/Application.md). The startup template already comes with an empty class named *YourProjectNamePermissionDefinitionProvider* that you can start with. - -In the `Define` method, you first need to add a **permission group** or get an existing group then add **permissions** to this group. - -When you define a permission, it becomes usable in the ASP.NET Core authorization system as a **policy** name. It also becomes visible in the UI. See permissions dialog for a role: - -![authorization-new-permission-ui](images/authorization-new-permission-ui.png) - -- The "BookStore" group is shown as a new tab on the left side. -- "BookStore_Author_Create" on the right side is the permission name. You can grant or prohibit it for the role. - -When you save the dialog, it is saved to the database and used in the authorization system. - -> The screen above is available when you have installed the identity module, which is basically used for user and role management. Startup templates come with the identity module pre-installed. - -#### Localizing the Permission Name - -"BookStore_Author_Create" is not a good permission name for the UI. Fortunately, `AddPermission` and `AddGroup` methods can take `LocalizableString` as second parameters: - -```csharp -var myGroup = context.AddGroup( - "BookStore", - LocalizableString.Create("BookStore") -); - -myGroup.AddPermission( - "BookStore_Author_Create", - LocalizableString.Create("Permission:BookStore_Author_Create") -); -``` - -Then you can define texts for "BookStore" and "Permission:BookStore_Author_Create" keys in the localization file: - -```json -"BookStore": "Book Store", -"Permission:BookStore_Author_Create": "Creating a new author" -``` - -> For more information, see the [localization document](Localization.md) on the localization system. - -The localized UI will be as seen below: - -![authorization-new-permission-ui-localized](images/authorization-new-permission-ui-localized.png) - -#### Multi-Tenancy - -ABP supports [multi-tenancy](Multi-Tenancy.md) as a first class citizen. You can define multi-tenancy side option while defining a new permission. It gets one of the three values defined below: - -- **Host**: The permission is available only for the host side. -- **Tenant**: The permission is available only for the tenant side. -- **Both** (default): The permission is available both for tenant and host sides. - -> If your application is not multi-tenant, you can ignore this option. - -To set the multi-tenancy side option, pass to the third parameter of the `AddPermission` method: - -```csharp -myGroup.AddPermission( - "BookStore_Author_Create", - LocalizableString.Create("Permission:BookStore_Author_Create"), - multiTenancySide: MultiTenancySides.Tenant //set multi-tenancy side! -); -``` - -#### Enable/Disable Permissions - -A permission is enabled by default. It is possible to disable a permission. A disabled permission will be prohibited for everyone. You can still check for the permission, but it will always return prohibited. - -Example definition: - -````csharp -myGroup.AddPermission("Author_Management", isEnabled: false); -```` - -You normally don't need to define a disabled permission (unless you temporary want disable a feature of your application). However, you may want to disable a permission defined in a depended module. In this way you can disable the related application functionality. See the "*Changing Permission Definitions of a Depended Module*" section below for an example usage. - -> Note: Checking an undefined permission will throw an exception while a disabled permission check simply returns prohibited (false). - -#### Child Permissions - -A permission may have child permissions. It is especially useful when you want to create a hierarchical permission tree where a permission may have additional sub permissions which are available only if the parent permission has been granted. - -Example definition: - -```csharp -var authorManagement = myGroup.AddPermission("Author_Management"); -authorManagement.AddChild("Author_Management_Create_Books"); -authorManagement.AddChild("Author_Management_Edit_Books"); -authorManagement.AddChild("Author_Management_Delete_Books"); -``` - -The result on the UI is shown below (you probably want to localize permissions for your application): - -![authorization-new-permission-ui-hierarcy](images/authorization-new-permission-ui-hierarcy.png) - -For the example code, it is assumed that a role/user with "Author_Management" permission granted may have additional permissions. Then a typical application service that checks permissions can be defined as shown below: - -```csharp -[Authorize("Author_Management")] -public class AuthorAppService : ApplicationService, IAuthorAppService -{ - public Task> GetListAsync() - { - ... - } - - public Task GetAsync(Guid id) - { - ... - } - - [Authorize("Author_Management_Create_Books")] - public Task CreateAsync(CreateAuthorDto input) - { - ... - } - - [Authorize("Author_Management_Edit_Books")] - public Task UpdateAsync(CreateAuthorDto input) - { - ... - } - - [Authorize("Author_Management_Delete_Books")] - public Task DeleteAsync(CreateAuthorDto input) - { - ... - } -} -``` - -- `GetListAsync` and `GetAsync` will be available to users if they have `Author_Management` permission is granted. -- Other methods require additional permissions. - -### Overriding a Permission by a Custom Policy - -If you define and register a policy to the ASP.NET Core authorization system with the same name of a permission, your policy will override the existing permission. This is a powerful way to extend the authorization for a pre-built module that you are using in your application. - -See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) document to learn how to define a custom policy. - -### Changing Permission Definitions of a Depended Module - -A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. - -Example: - -````csharp -context - .GetPermissionOrNull(IdentityPermissions.Roles.Delete) - .IsEnabled = false; -```` - -When you write this code inside your permission definition provider, it finds the "role deletion" permission of the [Identity Module](Modules/Identity.md) and disabled the permission, so no one can delete a role on the application. - -> Tip: It is better to check the value returned by the `GetPermissionOrNull` method since it may return null if the given permission was not defined. - -### Permission Depending on a Condition - -You may want to disable a permission based on a condition. Disabled permissions are not visible on the UI and always returns `prohibited` when you check them. There are two built-in conditional dependencies for a permission definition; - -* A permission can be automatically disabled if a [Feature](Features.md) was disabled. -* A permission can be automatically disabled if a [Global Feature](Global-Features.md) was disabled. - -In addition, you can create your custom extensions. - -#### Depending on a Features - -Use the `RequireFeatures` extension method on your permission definition to make the permission available only if a given feature is enabled: - -````csharp -myGroup.AddPermission("Book_Creation") - .RequireFeatures("BookManagement"); -```` - -#### Depending on a Global Feature - -Use the `RequireGlobalFeatures` extension method on your permission definition to make the permission available only if a given feature is enabled: - -````csharp -myGroup.AddPermission("Book_Creation") - .RequireGlobalFeatures("BookManagement"); -```` - -#### Creating a Custom Permission Dependency - -`PermissionDefinition` supports state check, Please refer to [Simple State Checker's documentation](SimpleStateChecker.md) - -## IAuthorizationService - -ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization. - -Example: - -```csharp -public async Task CreateAsync(CreateAuthorDto input) -{ - var result = await AuthorizationService - .AuthorizeAsync("Author_Management_Create_Books"); - if (result.Succeeded == false) - { - //throw exception - throw new AbpAuthorizationException("..."); - } - - //continue to the normal flow... -} -``` - -> `AuthorizationService` is available as a property when you derive from ABP's `ApplicationService` base class. Since it is widely used in application services, `ApplicationService` pre-injects it for you. Otherwise, you can directly [inject](Dependency-Injection.md) it into your class. - -Since this is a typical code block, ABP provides extension methods to simplify it. - -Example: - -```csharp -public async Task CreateAsync(CreateAuthorDto input) -{ - await AuthorizationService.CheckAsync("Author_Management_Create_Books"); - - //continue to the normal flow... -} -``` - -`CheckAsync` extension method throws `AbpAuthorizationException` if the current user/client is not granted for the given permission. There is also `IsGrantedAsync` extension method that returns `true` or `false`. - -`IAuthorizationService` has some overloads for the `AuthorizeAsync` method. These are explained in the [ASP.NET Core authorization documentation](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). - -> Tip: Prefer to use the `Authorize` attribute wherever possible, since it is declarative & simple. Use `IAuthorizationService` if you need to conditionally check a permission and run a business code based on the permission check. - -## Check a Permission in JavaScript - -See the following documents to learn how to re-use the authorization system on the client side: - -* [ASP.NET Core MVC / Razor Pages UI: Authorization](UI/AspNetCore/JavaScript-API/Auth.md) -* [Angular UI Authorization](UI/Angular/Permission-Management.md) -* [Blazor UI Authorization](UI/Blazor/Authorization.md) - -## Permission Management - -Permission management is normally done by an admin user using the permission management modal: - -![authorization-new-permission-ui-localized](images/authorization-new-permission-ui-localized.png) - -If you need to manage permissions by code, inject the `IPermissionManager` and use as shown below: - -```csharp -public class MyService : ITransientDependency -{ - private readonly IPermissionManager _permissionManager; - - public MyService(IPermissionManager permissionManager) - { - _permissionManager = permissionManager; - } - - public async Task GrantPermissionForUserAsync(Guid userId, string permissionName) - { - await _permissionManager.SetForUserAsync(userId, permissionName, true); - } - - public async Task ProhibitPermissionForUserAsync(Guid userId, string permissionName) - { - await _permissionManager.SetForUserAsync(userId, permissionName, false); - } -} -``` - -`SetForUserAsync` sets the value (true/false) for a permission of a user. There are more extension methods like `SetForRoleAsync` and `SetForClientAsync`. - -`IPermissionManager` is defined by the permission management module. See the [permission management module documentation](Modules/Permission-Management.md) for more information. - -## Advanced Topics - -### Permission Value Providers - -Permission checking system is extensible. Any class derived from `PermissionValueProvider` (or implements `IPermissionValueProvider`) can contribute to the permission check. There are three pre-defined value providers: - -- `UserPermissionValueProvider` checks if the current user is granted for the given permission. It gets user id from the current claims. User claim name is defined with the `AbpClaimTypes.UserId` static property. -- `RolePermissionValueProvider` checks if any of the roles of the current user is granted for the given permission. It gets role names from the current claims. Role claims name is defined with the `AbpClaimTypes.Role` static property. -- `ClientPermissionValueProvider` checks if the current client is granted for the given permission. This is especially useful on a machine to machine interaction where there is no current user. It gets the client id from the current claims. Client claim name is defined with the `AbpClaimTypes.ClientId` static property. - -You can extend the permission checking system by defining your own permission value provider. - -Example: - -```csharp -public class SystemAdminPermissionValueProvider : PermissionValueProvider -{ - public SystemAdminPermissionValueProvider(IPermissionStore permissionStore) - : base(permissionStore) - { - } - - public override string Name => "SystemAdmin"; - - public async override Task - CheckAsync(PermissionValueCheckContext context) - { - if (context.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin") - { - return PermissionGrantResult.Granted; - } - - return PermissionGrantResult.Undefined; - } -} -``` - -This provider allows for all permissions to a user with a `User_Type` claim that has `SystemAdmin` value. It is common to use current claims and `IPermissionStore` in a permission value provider. - -A permission value provider should return one of the following values from the `CheckAsync` method: - -- `PermissionGrantResult.Granted` is returned to grant the user for the permission. If any of the providers return `Granted`, the result will be `Granted`, if no other provider returns `Prohibited`. -- `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return. -- `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission. - -Once a provider is defined, it should be added to the `AbpPermissionOptions` as shown below: - -```csharp -Configure(options => -{ - options.ValueProviders.Add(); -}); -``` - -### Permission Store - -`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. The Permission Management module implements it and pre-installed in the application startup template. See the [permission management module documentation](Modules/Permission-Management.md) for more information - -### AlwaysAllowAuthorizationService - -`AlwaysAllowAuthorizationService` is a class that is used to bypass the authorization service. It is generally used in integration tests where you may want to disable the authorization system. - -Use `IServiceCollection.AddAlwaysAllowAuthorization()` extension method to register the `AlwaysAllowAuthorizationService` to the [dependency injection](Dependency-Injection.md) system: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.AddAlwaysAllowAuthorization(); -} -``` - -This is already done for the startup template integration tests. - -### Claims Principal Factory - -Claims are important elements of authentication and authorization. ABP uses the `IAbpClaimsPrincipalFactory` service to create claims on authentication. This service was designed as extensible. If you need to add your custom claims to the authentication ticket, you can implement the `IAbpClaimsPrincipalContributor` in your application. - -**Example: Add a `SocialSecurityNumber` claim and get it:** - -```csharp -public class SocialSecurityNumberClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency -{ - public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context) - { - var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); - var userId = identity?.FindUserId(); - if (userId.HasValue) - { - var userService = context.ServiceProvider.GetRequiredService(); //Your custom service - var socialSecurityNumber = await userService.GetSocialSecurityNumberAsync(userId.Value); - if (socialSecurityNumber != null) - { - identity.AddClaim(new Claim("SocialSecurityNumber", socialSecurityNumber)); - } - } - } -} - - -public static class CurrentUserExtensions -{ - public static string GetSocialSecurityNumber(this ICurrentUser currentUser) - { - return currentUser.FindClaimValue("SocialSecurityNumber"); - } -} -``` - -> If you use Identity Server please add your claims to `RequestedClaims` of `AbpClaimsServiceOptions`. - -```csharp -Configure(options => -{ - options.RequestedClaims.AddRange(new[]{ "SocialSecurityNumber" }); -}); -``` - -## See Also - -* [Permission Management Module](Modules/Permission-Management.md) -* [ASP.NET Core MVC / Razor Pages JavaScript Auth API](UI/AspNetCore/JavaScript-API/Auth.md) -* [Permission Management in Angular UI](UI/Angular/Permission-Management.md) -* [Video tutorial](https://abp.io/video-courses/essentials/authorization) \ No newline at end of file diff --git a/docs/en/Autofac-Integration.md b/docs/en/Autofac-Integration.md deleted file mode 100644 index 43da50299b..0000000000 --- a/docs/en/Autofac-Integration.md +++ /dev/null @@ -1,100 +0,0 @@ -# Autofac Integration - -[Autofac](https://autofac.org/) is one of the most used dependency injection frameworks for .NET. It provides advanced features compared to .Net Core's standard DI library, like dynamic proxying and property injection. - -## Install Autofac Integration - -> All the [startup templates](Startup-Templates/Index.md) and samples are Autofac integrated. So, most of the time you don't need to manually install this package. - -If you're not using a startup template, you can use the [ABP CLI](CLI.md) to install it to your project. Execute the following command in the folder that contains the .csproj file of your project (suggested to add it to the executable/web project): - -````bash -abp add-package Volo.Abp.Autofac -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Autofac). -> - -Finally, configure `AbpApplicationCreationOptions` to replace default dependency injection services by Autofac. It depends on the application type. - -### ASP.NET Core Application - -Call `UseAutofac()` in the **Program.cs** file as shown below: - -````csharp -public class Program -{ - public static int Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - internal static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseAutofac(); //Integrate Autofac! -} -```` - -If you are using the static `WebApplication` class, you can call the `UseAutofac()` extension method as shown below: - -````csharp -public class Program -{ - public async static Task Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - builder.Host.UseAutofac(); // Integrate Autofac! - await builder.AddApplicationAsync(); - var app = builder.Build(); - await app.InitializeApplicationAsync(); - await app.RunAsync(); - } -} -```` - -### Console Application - -Call `UseAutofac()` method in the `AbpApplicationFactory.Create` options as shown below: - -````csharp -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create(options => - { - options.UseAutofac(); //Autofac integration - })) - { - //... - } - } - } -} -```` - -## Using the Autofac Registration API - -If you want to use Autofac's advanced [registration API](https://autofac.readthedocs.io/en/latest/register/registration.html), you need to access the `ContainerBuilder` object. [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) nuget package defines the `IServiceCollection.GetContainerBuilder()` extension method to obtain the `ContainerBuilder` object. - -**Example: Get the `ContainerBuilder` object in the `ConfigureServices` method of your [module class](Module-Development-Basics.md)** - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var containerBuilder = context.Services.GetContainerBuilder(); - containerBuilder.RegisterType(); // Using Autofac's registration API -} -```` - -> You should install the [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) nuget package to the project that you want to use the Autofac API. diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md deleted file mode 100644 index 69903e1c85..0000000000 --- a/docs/en/Background-Jobs-Hangfire.md +++ /dev/null @@ -1,156 +0,0 @@ -# Hangfire Background Job Manager - -[Hangfire](https://www.hangfire.io/) is an advanced background job manager. You can integrate Hangfire with the ABP Framework to use it instead of the [default background job manager](Background-Jobs.md). In this way, you can use the same background job API for Hangfire and your code will be independent of Hangfire. If you like, you can directly use Hangfire's API, too. - -> See the [background jobs document](Background-Jobs.md) to learn how to use the background job system. This document only shows how to install and configure the Hangfire integration. - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.BackgroundJobs.HangFire -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.HangFire). - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.BackgroundJobs.HangFire](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.HangFire) NuGet package to your project: - - ```` - Install-Package Volo.Abp.BackgroundJobs.HangFire - ```` - -2. Add the `AbpBackgroundJobsHangfireModule` to the dependency list of your module: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundJobsHangfireModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -```` - -## Configuration - -You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package). - -After you have installed these NuGet packages, you need to configure your project to use Hangfire. - -1.First, we change the `Module` class (example: `HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method: - -````csharp - public override void ConfigureServices(ServiceConfigurationContext context) - { - var configuration = context.Services.GetConfiguration(); - var hostingEnvironment = context.Services.GetHostingEnvironment(); - - //... other configurations. - - ConfigureHangfire(context, configuration); - } - - private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddHangfire(config => - { - config.UseSqlServerStorage(configuration.GetConnectionString("Default")); - }); - } -```` - -> You have to configure a storage for Hangfire. - -2. If you want to use hangfire's dashboard, you can add `UseAbpHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class: - -````csharp - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - var app = context.GetApplicationBuilder(); - - // ... others - - app.UseAbpHangfireDashboard(); //should add to the request pipeline before the app.UseConfiguredEndpoints() - app.UseConfiguredEndpoints(); - } -```` - -### Specifying Queue - -You can use the [`QueueAttribute`](https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html) to specify the queue: - -````csharp -using System.Threading.Tasks; -using Volo.Abp.BackgroundJobs; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing; - -namespace MyProject -{ - [Queue("alpha")] - public class EmailSendingJob - : AsyncBackgroundJob, ITransientDependency - { - private readonly IEmailSender _emailSender; - - public EmailSendingJob(IEmailSender emailSender) - { - _emailSender = emailSender; - } - - public override async Task ExecuteAsync(EmailSendingArgs args) - { - await _emailSender.SendAsync( - args.EmailAddress, - args.Subject, - args.Body - ); - } - } -} -```` - -### Dashboard Authorization - -Hangfire Dashboard provides information about your background jobs, including method names and serialized arguments as well as gives you an opportunity to manage them by performing different actions – retry, delete, trigger, etc. So it is important to restrict access to the Dashboard. -To make it secure by default, only local requests are allowed, however you can change this by following the [official documentation](http://docs.hangfire.io/en/latest/configuration/using-dashboard.html) of Hangfire. - -You can integrate the Hangfire dashboard to [ABP authorization system](Authorization.md) using the **AbpHangfireAuthorizationFilter** -class. This class is defined in the `Volo.Abp.Hangfire` package. The following example, checks if the current user is logged in to the application: - -```csharp -app.UseAbpHangfireDashboard("/hangfire", options => -{ - options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() }; -}); -``` - -* `AbpHangfireAuthorizationFilter` is an implementation of an authorization filter. - -#### AbpHangfireAuthorizationFilter - -`AbpHangfireAuthorizationFilter` class has the following fields: - -* **`enableTenant` (`bool`, default: `false`):** Enables/disables accessing the Hangfire dashboard on tenant users. -* **`requiredPermissionName` (`string`, default: `null`):** Hangfire dashboard is accessible only if the current user has the specified permission. In this case, if we specify a permission name, we don't need to set `enableTenant` `true` because the permission system already does it. - -If you want to require an additional permission, you can pass it into the constructor as below: - -```csharp -app.UseAbpHangfireDashboard("/hangfire", options => -{ - options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName") }; -}); -``` - -**Important**: `UseAbpHangfireDashboard` should be called after the authentication and authorization middlewares in your `Startup` class (probably at the last line). Otherwise, -authorization will always fail! diff --git a/docs/en/Background-Jobs-Quartz.md b/docs/en/Background-Jobs-Quartz.md deleted file mode 100644 index 4c1441da7f..0000000000 --- a/docs/en/Background-Jobs-Quartz.md +++ /dev/null @@ -1,159 +0,0 @@ -# Quartz Background Job Manager - -[Quartz](https://www.quartz-scheduler.net/) is an advanced background job manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background job manager](Background-Jobs.md). In this way, you can use the same background job API for Quartz and your code will be independent of Quartz. If you like, you can directly use Quartz's API, too. - -> See the [background jobs document](Background-Jobs.md) to learn how to use the background job system. This document only shows how to install and configure the Quartz integration. - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.BackgroundJobs.Quartz -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.Quartz). - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.BackgroundJobs.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.Quartz) NuGet package to your project: - - ```` - Install-Package Volo.Abp.BackgroundJobs.Quartz - ```` - -2. Add the `AbpBackgroundJobsQuartzModule` to the dependency list of your module: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -```` - -## Configuration - -Quartz is a very configurable library,and the ABP framework provides `AbpQuartzOptions` for this. You can use the `PreConfigure` method in your module class to pre-configure this option. ABP will use it when initializing the Quartz module. For example: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ - public override void PreConfigureServices(ServiceConfigurationContext context) - { - var configuration = context.Services.GetConfiguration(); - - PreConfigure(options => - { - options.Properties = new NameValueCollection - { - ["quartz.jobStore.dataSource"] = "BackgroundJobsDemoApp", - ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", - ["quartz.jobStore.tablePrefix"] = "QRTZ_", - ["quartz.serializer.type"] = "json", - ["quartz.dataSource.BackgroundJobsDemoApp.connectionString"] = configuration.GetConnectionString("Quartz"), - ["quartz.dataSource.BackgroundJobsDemoApp.provider"] = "SqlServer", - ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz", - }; - }); - } -} -```` - -Starting from ABP 3.1 version, we have added `Configurator` to `AbpQuartzOptions` to configure Quartz. For example: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ - public override void PreConfigureServices(ServiceConfigurationContext context) - { - var configuration = context.Services.GetConfiguration(); - - PreConfigure(options => - { - options.Configurator = configure => - { - configure.UsePersistentStore(storeOptions => - { - storeOptions.UseProperties = true; - storeOptions.UseJsonSerializer(); - storeOptions.UseSqlServer(configuration.GetConnectionString("Quartz")); - storeOptions.UseClustering(c => - { - c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20); - c.CheckinInterval = TimeSpan.FromSeconds(10); - }); - }); - }; - }); - } -} -```` - -> You can choose the way you favorite to configure Quaratz. - -Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/). - -## Exception handling - -### Default exception handling strategy - -When an exception occurs in the background job,ABP provide the **default handling strategy** retrying once every 3 seconds, up to 3 times. You can change the retry count and retry interval via `AbpBackgroundJobQuartzOptions` options: - -```csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.RetryCount = 1; - options.RetryIntervalMillisecond = 1000; - }); - } -} -``` - -### Customize exception handling strategy - -You can customize the exception handling strategy via `AbpBackgroundJobQuartzOptions` options: - -```csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.RetryStrategy = async (retryIndex, executionContext, exception) => - { - // customize exception handling - }; - }); - } -} -``` \ No newline at end of file diff --git a/docs/en/Background-Jobs-RabbitMq.md b/docs/en/Background-Jobs-RabbitMq.md deleted file mode 100644 index 8eec3733fb..0000000000 --- a/docs/en/Background-Jobs-RabbitMq.md +++ /dev/null @@ -1,159 +0,0 @@ -# RabbitMQ Background Job Manager - -RabbitMQ is an industry standard message broker. While it is typically used for inter-process communication (messaging / distributed events), it is pretty useful to store and execute background jobs in FIFO (First In First Out) order. - -ABP Framework provides the [Volo.Abp.BackgroundJobs.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.RabbitMQ) NuGet package to use the RabbitMQ for background job execution. - -> See the [background jobs document](Background-Jobs.md) to learn how to use the background job system. This document only shows how to install and configure the RabbitMQ integration. - -## Installation - -Use the ABP CLI to add [Volo.Abp.BackgroundJobs.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.RabbitMQ) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BackgroundJobs.RabbitMQ` package. -* Run `abp add-package Volo.Abp.BackgroundJobs.RabbitMQ` command. - -If you want to do it manually, install the [Volo.Abp.BackgroundJobs.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.RabbitMQ) NuGet package to your project and add `[DependsOn(typeof(AbpBackgroundJobsRabbitMqModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -### Default Configuration - -The default configuration automatically connects to the local RabbitMQ server (localhost) with the standard port. **In this case, no configuration needed.** - -### RabbitMQ Connection(s) - -You can configure the RabbitMQ connections using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes. - -#### `appsettings.json` file configuration - -This is the simplest way to configure the RabbitMQ connections. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/). - -**Example: Configuring the Default RabbitMQ Connection** - -````json -{ - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "123.123.123.123", - "Port": "5672" - } - } - } -} -```` - -You can use any of the [ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) properties as the connection properties. See [the RabbitMQ document](https://www.rabbitmq.com/dotnet-api-guide.html#exchanges-and-queues) to understand these options better. - -Defining multiple connections is allowed. In this case, you can use different connections for different background job types (see the `AbpRabbitMqBackgroundJobOptions` section below). - -**Example: Declare two connections** - -````json -{ - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "123.123.123.123" - }, - "SecondConnection": { - "HostName": "321.321.321.321" - } - } - } -} -```` - -If you need to connect to the RabbitMQ cluster, you can use the `;` character to separate the host names. - -**Example: Connect to the RabbitMQ cluster** - -```json -{ - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "123.123.123.123;234.234.234.234" - } - }, - "EventBus": { - "ClientName": "MyClientName", - "ExchangeName": "MyExchangeName" - } - } -} -``` - -#### AbpRabbitMqOptions - -`AbpRabbitMqOptions` class can be used to configure the connection strings for the RabbitMQ. You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md). - -**Example: Configure the connection** - -````csharp -Configure(options => -{ - options.Connections.Default.UserName = "user"; - options.Connections.Default.Password = "pass"; - options.Connections.Default.HostName = "123.123.123.123"; - options.Connections.Default.Port = 5672; -}); -```` - -Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. - -### AbpRabbitMqBackgroundJobOptions - -#### Job Queue Names - -By default, each job type uses a separate queue. Queue names are calculated by combining a standard prefix and the job name. Default prefix is `AbpBackgroundJobs.` So, if the job name is `EmailSending` then the queue name in the RabbitMQ becomes `AbpBackgroundJobs.EmailSending` - -> Use `BackgroundJobName` attribute on the background **job argument** class to specify the job name. Otherwise, the job name will be the full name (with namespace) of the job class. - -#### Job Connections - -By default, all the job types use the `Default` RabbitMQ connection. - -#### Customization - -`AbpRabbitMqBackgroundJobOptions` can be used to customize the queue names and the connections used by the jobs. - -**Example:** - -````csharp -Configure(options => -{ - options.DefaultQueueNamePrefix = "my_app_jobs."; - options.DefaultDelayedQueueNamePrefix = "my_app_jobs.delayed" - options.PrefetchCount = 1; - options.JobQueues[typeof(EmailSendingArgs)] = - new JobQueueConfiguration( - typeof(EmailSendingArgs), - queueName: "my_app_jobs.emails", - connectionName: "SecondConnection", - delayedQueueName:"my_app_jobs.emails.delayed" - ); -}); -```` - -* This example sets the default queue name prefix to `my_app_jobs.` and default delayed queue name prefix to `my_app_jobs.delayed`. If different applications use the same RabbitMQ server, it would be important to use different prefixes for each application to not consume jobs of each other. -* Sets `PrefetchCount` for all queues. -* Also specifies a different connection string for the `EmailSendingArgs`. - -`JobQueueConfiguration` class has some additional options in its constructor; - -* `queueName`: The queue name that is used for this job. The prefix is not added, so you need to specify the full name of the queue. -* `DelayedQueueName`: The delayed queue name that is used for delayed execution of job. The prefix is not added, so you need to specify the full name of the queue. -* `connectionName`: The RabbitMQ connection name (see the connection configuration above). This is optional and the default value is `Default`. -* `durable` (optional, default: `true`). -* `exclusive` (optional, default: `false`). -* `autoDelete` (optional, default: `false`). -* `PrefetchCount` (optional, default: null) - -See the RabbitMQ documentation if you want to understand the `durable`, `exclusive` and `autoDelete` options better, while most of the times the default configuration is what you want. - -## See Also - -* [Background Jobs](Background-Jobs.md) \ No newline at end of file diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md deleted file mode 100644 index c6e19fb48a..0000000000 --- a/docs/en/Background-Jobs.md +++ /dev/null @@ -1,254 +0,0 @@ -# Background Jobs - -## Introduction - -Background jobs are used to queue some tasks to be executed in the background. You may need background jobs for several reasons. Here are some examples: - -- To perform **long-running tasks** without having the users wait. For example, a user presses a 'report' button to start a long-running reporting job. You add this job to the **queue** and send the report's result to your user via email when it's completed. -- To create **re-trying** and **persistent tasks** to **guarantee** that a code will be **successfully executed**. For example, you can send emails in a background job to overcome **temporary failures** and **guarantee** that it eventually will be sent. That way users do not wait while sending emails. - -Background jobs are **persistent** that means they will be **re-tried** and **executed** later even if your application crashes. - -## Abstraction Package - -ABP provides an **abstraction** module and **several implementations** for background jobs. It has a built-in/default implementation as well as Hangfire, RabbitMQ and Quartz integrations. - -`Volo.Abp.BackgroundJobs.Abstractions` NuGet package provides needed services to create background jobs and queue background job items. If your module only depend on this package, it can be independent from the actual implementation/integration. - -> `Volo.Abp.BackgroundJobs.Abstractions` package is installed to the startup templates by default. - -### Create a Background Job - -A background job is a class that implements the `IBackgroundJob` interface or derives from the `BackgroundJob` class. `TArgs` is a simple plain C# class to store the job data. - -This example is used to send emails in background. First, define a class to store arguments of the background job: - -````csharp -namespace MyProject -{ - public class EmailSendingArgs - { - public string EmailAddress { get; set; } - public string Subject { get; set; } - public string Body { get; set; } - } -} -```` - -Then create a background job class that uses an `EmailSendingArgs` object to send an email: - -````csharp -using System.Threading.Tasks; -using Volo.Abp.BackgroundJobs; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing; - -namespace MyProject -{ - public class EmailSendingJob - : AsyncBackgroundJob, ITransientDependency - { - private readonly IEmailSender _emailSender; - - public EmailSendingJob(IEmailSender emailSender) - { - _emailSender = emailSender; - } - - public override async Task ExecuteAsync(EmailSendingArgs args) - { - await _emailSender.SendAsync( - args.EmailAddress, - args.Subject, - args.Body - ); - } - } -} -```` - -This job simply uses `IEmailSender` to send emails (see [email sending document](Emailing.md)). - -> `AsyncBackgroundJob` is used to create a job needs to perform async calls. You can inherit from `BackgroundJob` and override the `Execute` method if the method doesn't need to perform any async call. - -#### Exception Handling - -A background job should not hide exceptions. If it throws an exception, the background job is automatically re-tried after a calculated waiting time. Hide exceptions only if you don't want to re-run the background job for the current argument. - -#### Cancelling Background Jobs - -If your background task is cancellable, then you can use the standard [Cancellation Token](Cancellation-Token-Provider.md) system to obtain a `CancellationToken` to cancel your job when requested. See the following example that uses the `ICancellationTokenProvider` to obtain the cancellation token: - -```csharp -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Threading; - -namespace MyProject -{ - public class LongRunningJob : AsyncBackgroundJob, ITransientDependency - { - private readonly ICancellationTokenProvider _cancellationTokenProvider; - - public LongRunningJob(ICancellationTokenProvider cancellationTokenProvider) - { - _cancellationTokenProvider = cancellationTokenProvider; - } - - public override async Task ExecuteAsync(LongRunningJobArgs args) - { - foreach (var id in args.Ids) - { - _cancellationTokenProvider.Token.ThrowIfCancellationRequested(); - await ProcessAsync(id); // code omitted for brevity - } - } - } -} -``` - -> A cancellation operation might be needed if the application is shutting down and we don't want to block the application in the background job. This example throws an exception if the cancellation is requested. So, the job will be retried the next time the application starts. If you don't want that, just return from the `ExecuteAsync` method without throwing any exception (you can simply check the `_cancellationTokenProvider.Token.IsCancellationRequested` property). - -#### Job Name - -Each background job has a name. Job names are used in several places. For example, RabbitMQ provider uses job names to determine the RabbitMQ Queue names. - -Job name is determined by the **job argument type**. For the `EmailSendingArgs` example above, the job name is `MyProject.EmailSendingArgs` (full name, including the namespace). You can use the `BackgroundJobName` attribute to set a different job name. - -**Example** - -```csharp -using Volo.Abp.BackgroundJobs; - -namespace MyProject -{ - [BackgroundJobName("emails")] - public class EmailSendingArgs - { - public string EmailAddress { get; set; } - public string Subject { get; set; } - public string Body { get; set; } - } -} -``` - -### Queue a Job Item - -Now, you can queue an email sending job using the `IBackgroundJobManager` service: - -````csharp -public class RegistrationService : ApplicationService -{ - private readonly IBackgroundJobManager _backgroundJobManager; - - public RegistrationService(IBackgroundJobManager backgroundJobManager) - { - _backgroundJobManager = backgroundJobManager; - } - - public async Task RegisterAsync(string userName, string emailAddress, string password) - { - //TODO: Create new user in the database... - - await _backgroundJobManager.EnqueueAsync( - new EmailSendingArgs - { - EmailAddress = emailAddress, - Subject = "You've successfully registered!", - Body = "..." - } - ); - } -} -```` - -Just injected `IBackgroundJobManager` service and used its `EnqueueAsync` method to add a new job to the queue. - -Enqueue method gets some optional arguments to control the background job: - -* **priority** is used to control priority of the job item. It gets an `BackgroundJobPriority` enum which has `Low`, `BelowNormal`, `Normal` (default), `AboveNormal` and `Hight` fields. -* **delay** is used to wait a while (`TimeSpan`) before first try. - -### Disable Job Execution - -You may want to disable background job execution for your application. This is generally needed if you want to execute background jobs in another process and disable it for the current process. - -Use `AbpBackgroundJobOptions` to configure the job execution: - -````csharp -[DependsOn(typeof(AbpBackgroundJobsModule))] -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.IsJobExecutionEnabled = false; //Disables job execution - }); - } -} -```` - -## Default Background Job Manager - -ABP framework includes a simple `IBackgroundJobManager` implementation that; - -- Works as **FIFO** in a **single thread**. -- **Retries** job execution until the job **successfully runs** or **timeouts**. Default timeout is 2 days for a job. Logs all exceptions. -- **Deletes** a job from the store (database) when it's successfully executed. If it's timed out, it sets it as **abandoned** and leaves it in the database. -- **Increasingly waits between retries** for a job. It waits 1 minute for the first retry, 2 minutes for the second retry, 4 minutes for the third retry and so on. -- **Polls** the store for jobs in fixed intervals. It queries jobs, ordering by priority (asc) and then by try count (asc). - -> `Volo.Abp.BackgroundJobs` nuget package contains the default background job manager and it is installed to the startup templates by default. - -### Configuration - -Use `AbpBackgroundJobWorkerOptions` in your [module class](Module-Development-Basics.md) to configure the default background job manager. The example below changes the timeout duration for background jobs: - -````csharp -[DependsOn(typeof(AbpBackgroundJobsModule))] -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.DefaultTimeout = 864000; //10 days (as seconds) - }); - } -} -```` - -### Data Store - -The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction to store the jobs. - -Background Jobs module implements `IBackgroundJobStore` using various data access providers. See its own [documentation](Modules/Background-Jobs.md). If you don't want to use this module, you should implement the `IBackgroundJobStore` interface yourself. - -> Background Jobs module is already installed to the startup templates by default and it works based on your ORM/data access choice. - -### Clustered Deployment - -The default background job manager is compatible with [clustered environments](Deployment/Clustered-Environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance at a time. - -However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, **please follow the [distributed lock](Distributed-Locking.md) document to configure a provider for your application**, if it is not already configured. - -If you don't want to use a distributed lock provider, you may go with the following options: - -* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). -* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance. - -## Integrations - -Background job system is extensible and you can change the default background job manager with your own implementation or on of the pre-built integrations. - -See pre-built job manager alternatives: - -* [Hangfire Background Job Manager](Background-Jobs-Hangfire.md) -* [RabbitMQ Background Job Manager](Background-Jobs-RabbitMq.md) -* [Quartz Background Job Manager](Background-Jobs-Quartz.md) - -## See Also -* [Background Workers](Background-Workers.md) \ No newline at end of file diff --git a/docs/en/Background-Workers-Hangfire.md b/docs/en/Background-Workers-Hangfire.md deleted file mode 100644 index 70c8d0cf95..0000000000 --- a/docs/en/Background-Workers-Hangfire.md +++ /dev/null @@ -1,165 +0,0 @@ -# Hangfire Background Worker Manager - -[Hangfire](https://www.hangfire.io/) is an advanced background jobs and worker manager. You can integrate Hangfire with the ABP Framework to use it instead of the [default background worker manager](Background-Workers.md). - -The major advantage is that you can use the same server farm to manage your Background Jobs and Workers, as well as leverage the advanced scheduling that is available from Hangfire for [Recurring Jobs](https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html?highlight=recurring), aka Background Workers. - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.BackgroundWorkers.Hangfire -```` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.BackgroundWorkers.Hangfire](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Hangfire) NuGet package to your project: - - ```` - Install-Package Volo.Abp.BackgroundWorkers.Hangfire - ```` - -2. Add the `AbpBackgroundWorkersHangfireModule` to the dependency list of your module: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundWorkersHangfireModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -```` - -> Hangfire background worker integration provides an adapter `HangfirePeriodicBackgroundWorkerAdapter` to automatically load any `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived classes as `IHangfireBackgroundWorker` instances. This allows you to still to easily switch over to use Hangfire as the background manager even you have existing background workers that are based on the [default background workers implementation](Background-Workers.md). - -## Configuration - -You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package). - -After you have installed these NuGet packages, you need to configure your project to use Hangfire. - -1.First, we change the `Module` class (example: `HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method: - -````csharp - public override void ConfigureServices(ServiceConfigurationContext context) - { - var configuration = context.Services.GetConfiguration(); - var hostingEnvironment = context.Services.GetHostingEnvironment(); - - //... other configarations. - - ConfigureHangfire(context, configuration); - } - - private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddHangfire(config => - { - config.UseSqlServerStorage(configuration.GetConnectionString("Default")); - }); - } -```` - -> You have to configure a storage for Hangfire. - -2. If you want to use hangfire's dashboard, you can add `UseAbpHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class - -````csharp - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - var app = context.GetApplicationBuilder(); - - // ... others - - app.UseAbpHangfireDashboard(); //should add to the request pipeline before the app.UseConfiguredEndpoints() - app.UseConfiguredEndpoints(); - } -```` - -## Create a Background Worker - -`HangfireBackgroundWorkerBase` is an easy way to create a background worker. - -```` csharp -public class MyLogWorker : HangfireBackgroundWorkerBase -{ - public MyLogWorker() - { - RecurringJobId = nameof(MyLogWorker); - CronExpression = Cron.Daily(); - } - - public override Task DoWorkAsync(CancellationToken cancellationToken = default) - { - Logger.LogInformation("Executed MyLogWorker..!"); - return Task.CompletedTask; - } -} -```` - -* **RecurringJobId** Is an optional parameter, see [Hangfire document](https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html) -* **CronExpression** Is a CRON expression, see [CRON expression](https://en.wikipedia.org/wiki/Cron#CRON_expression) - -> You can directly implement the `IHangfireBackgroundWorker`, but `HangfireBackgroundWorkerBase` provides some useful properties like Logger. - -### UnitOfWork - -```csharp -public class MyLogWorker : HangfireBackgroundWorkerBase, IMyLogWorker -{ - public MyLogWorker() - { - RecurringJobId = nameof(MyLogWorker); - CronExpression = Cron.Daily(); - } - - public override Task DoWorkAsync(CancellationToken cancellationToken = default) - { - using (var uow = LazyServiceProvider.LazyGetRequiredService().Begin()) - { - Logger.LogInformation("Executed MyLogWorker..!"); - return Task.CompletedTask; - } - } -} -``` - -## Register BackgroundWorkerManager - -After creating a background worker class, you should add it to the `IBackgroundWorkerManager`. The most common place is the `OnApplicationInitializationAsync` method of your module class: - -```` csharp -[DependsOn(typeof(AbpBackgroundWorkersModule))] -public class MyModule : AbpModule -{ - public override async Task OnApplicationInitializationAsync( - ApplicationInitializationContext context) - { - await context.AddBackgroundWorkerAsync(); - } -} -```` - -`context.AddBackgroundWorkerAsync(...)` is a shortcut extension method for the expression below: - -```` csharp -context.ServiceProvider - .GetRequiredService() - .AddAsync( - context - .ServiceProvider - .GetRequiredService() - ); -```` - -So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`. - -While we generally add workers in `OnApplicationInitializationAsync`, there are no restrictions on that. You can inject `IBackgroundWorkerManager` anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down. diff --git a/docs/en/Background-Workers-Quartz.md b/docs/en/Background-Workers-Quartz.md deleted file mode 100644 index b3ebf32a91..0000000000 --- a/docs/en/Background-Workers-Quartz.md +++ /dev/null @@ -1,146 +0,0 @@ -# Quartz Background Worker Manager - -[Quartz](https://www.quartz-scheduler.net/) is an advanced background worker manager. You can integrate Quartz with the ABP Framework to use it instead of the [default background worker manager](Background-Workers.md). ABP simply integrates quartz. - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.BackgroundWorkers.Quartz -```` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.BackgroundWorkers.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Quartz) NuGet package to your project: - - ```` - Install-Package Volo.Abp.BackgroundWorkers.Quartz - ```` - -2. Add the `AbpBackgroundWorkersQuartzModule` to the dependency list of your module: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundWorkersQuartzModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -```` - -> Quartz background worker integration provided `QuartzPeriodicBackgroundWorkerAdapter` to adapt `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived class. So, you can still fllow the [background workers document](Background-Workers.md) to define the background worker. - -## Configuration - -See [Configuration](Background-Jobs-Quartz#Configuration). - -## Create a Background Worker - -A background work is a class that derives from the `QuartzBackgroundWorkerBase` base class. for example. A simple worker class is shown below: - -```` csharp -public class MyLogWorker : QuartzBackgroundWorkerBase -{ - public MyLogWorker() - { - JobDetail = JobBuilder.Create().WithIdentity(nameof(MyLogWorker)).Build(); - Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).StartNow().Build(); - } - - public override Task Execute(IJobExecutionContext context) - { - Logger.LogInformation("Executed MyLogWorker..!"); - return Task.CompletedTask; - } -} -```` - -We simply implemented the Execute method to write a log. The background worker is a **singleton by default**. If you want, you can also implement a [dependency interface](Dependency-Injection#DependencyInterfaces) to register it as another life cycle. - -> Tips: Add identity to background workers is a best practice,because quartz distinguishes different jobs based on identity. - -## Add to BackgroundWorkerManager - -Default background workers are **automatically** added to the BackgroundWorkerManager when the application is **initialized**. You can set `AutoRegister` property value to `false`,if you want to add it manually: - -```` csharp -public class MyLogWorker : QuartzBackgroundWorkerBase -{ - public MyLogWorker() - { - AutoRegister = false; - JobDetail = JobBuilder.Create().WithIdentity(nameof(MyLogWorker)).Build(); - Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).StartNow().Build(); - } - - public override Task Execute(IJobExecutionContext context) - { - Logger.LogInformation("Executed MyLogWorker..!"); - return Task.CompletedTask; - } -} -```` - -If you want to globally disable auto add worker, you can global disable via `AbpBackgroundWorkerQuartzOptions` options: - -```csharp -[DependsOn( - //...other dependencies - typeof(AbpBackgroundWorkersQuartzModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.IsAutoRegisterEnabled = false; - }); - } -} -``` - -## Advanced topics - -### Customize ScheduleJob - -Assume you have a worker executes every 10 minutes,but because server is unavailable for 30 minutes, 3 executions are missed. You want to execute all missed times after the server is available. You should define your background worker like this: - -```csharp -public class MyLogWorker : QuartzBackgroundWorkerBase -{ - public MyLogWorker() - { - JobDetail = JobBuilder.Create().WithIdentity(nameof(MyLogWorker)).Build(); - Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).WithSimpleSchedule(s=>s.WithIntervalInMinutes(1).RepeatForever().WithMisfireHandlingInstructionIgnoreMisfires()).Build(); - - ScheduleJob = async scheduler => - { - if (!await scheduler.CheckExists(JobDetail.Key)) - { - await scheduler.ScheduleJob(JobDetail, Trigger); - } - }; - } - - public override Task Execute(IJobExecutionContext context) - { - Logger.LogInformation("Executed MyLogWorker..!"); - return Task.CompletedTask; - } -} -``` - -In the example we defined the worker execution interval to be 10 minutes and set `WithMisfireHandlingInstructionIgnoreMisfires`. we customized `ScheduleJob` and add worker to quartz only when the background worker does not exist. - -### More - -Please see Quartz's [documentation](https://www.quartz-scheduler.net/documentation/index.html) for more information. diff --git a/docs/en/Background-Workers.md b/docs/en/Background-Workers.md deleted file mode 100644 index 06535cccfc..0000000000 --- a/docs/en/Background-Workers.md +++ /dev/null @@ -1,146 +0,0 @@ -# Background Workers - -## Introduction - -Background workers are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples; - -* A background worker can run periodically to **delete old logs**. -* A background worker can run periodically to **determine inactive users** and **send emails** to get users to return to your application. - - -## Create a Background Worker - -A background worker should directly or indirectly implement the `IBackgroundWorker` interface. - -> A background worker is inherently [singleton](Dependency-Injection.md). So, only a single instance of your worker class is instantiated and run. - -### BackgroundWorkerBase - -`BackgroundWorkerBase` is an easy way to create a background worker. - -````csharp -public class MyWorker : BackgroundWorkerBase -{ - public override Task StartAsync(CancellationToken cancellationToken = default) - { - //... - } - - public override Task StopAsync(CancellationToken cancellationToken = default) - { - //... - } -} -```` - -Start your worker in the `StartAsync` (which is called when the application begins) and stop in the `StopAsync` (which is called when the application shuts down). - -> You can directly implement the `IBackgroundWorker`, but `BackgroundWorkerBase` provides some useful properties like `Logger`. - -### AsyncPeriodicBackgroundWorkerBase - -Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below: - -````csharp -public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase -{ - public PassiveUserCheckerWorker( - AbpAsyncTimer timer, - IServiceScopeFactory serviceScopeFactory - ) : base( - timer, - serviceScopeFactory) - { - Timer.Period = 600000; //10 minutes - } - - protected async override Task DoWorkAsync( - PeriodicBackgroundWorkerContext workerContext) - { - Logger.LogInformation("Starting: Setting status of inactive users..."); - - //Resolve dependencies - var userRepository = workerContext - .ServiceProvider - .GetRequiredService(); - - //Do the work - await userRepository.UpdateInactiveUserStatusesAsync(); - - Logger.LogInformation("Completed: Setting status of inactive users..."); - } -} -```` - -* `AsyncPeriodicBackgroundWorkerBase` uses the `AbpAsyncTimer` (a thread-safe timer) object to determine **the period**. We can set its `Period` property in the constructor. -* It required to implement the `DoWorkAsync` method to **execute** the periodic work. -* It is a good practice to **resolve dependencies** from the `PeriodicBackgroundWorkerContext` instead of constructor injection. Because `AsyncPeriodicBackgroundWorkerBase` uses a `IServiceScope` that is **disposed** when your work finishes. -* `AsyncPeriodicBackgroundWorkerBase` **catches and logs exceptions** thrown by the `DoWorkAsync` method. - - -## Register Background Worker - -After creating a background worker class, you should add it to the `IBackgroundWorkerManager`. The most common place is the `OnApplicationInitializationAsync` method of your module class: - -````csharp -[DependsOn(typeof(AbpBackgroundWorkersModule))] -public class MyModule : AbpModule -{ - public override async Task OnApplicationInitializationAsync( - ApplicationInitializationContext context) - { - await context.AddBackgroundWorkerAsync(); - } -} -```` - -`context.AddBackgroundWorkerAsync(...)` is a shortcut extension method for the expression below: - -````csharp -await context.ServiceProvider - .GetRequiredService() - .AddAsync( - context - .ServiceProvider - .GetRequiredService() - ); -```` - -So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`. - -While we generally add workers in `OnApplicationInitializationAsync`, there are no restrictions on that. You can inject `IBackgroundWorkerManager` anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down. - -## Options - -`AbpBackgroundWorkerOptions` class is used to [set options](Options.md) for the background workers. Currently, there is only one option: - -* `IsEnabled` (default: true): Used to **enable/disable** the background worker system for your application. - -> See the [Options](Options.md) document to learn how to set options. - -## Making Your Application Always Run - -Background workers only work if your application is running. If you host the background job execution in your web application (this is the default behavior), you should ensure that your web application is configured to always be running. Otherwise, background jobs only work while your application is in use. - -## Running On a Cluster - -Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example). - -If that's a problem for your workers, you have the following options: - -* Implement your background workers so that they work in a clustered environment without any problem. Using the [distributed lock](Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait for the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring about how the application is deployed. -* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. -* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance. - -## Integrations - -Background worker system is extensible and you can change the default background worker manager with your own implementation or on of the pre-built integrations. - -See pre-built worker manager alternatives: - -* [Quartz Background Worker Manager](Background-Workers-Quartz.md) -* [Hangfire Background Worker Manager](Background-Workers-Hangfire.md) - -## See Also - -* [Background Jobs](Background-Jobs.md) diff --git a/docs/en/Best-Practices/Application-Services.md b/docs/en/Best-Practices/Application-Services.md deleted file mode 100644 index c209f01c5e..0000000000 --- a/docs/en/Best-Practices/Application-Services.md +++ /dev/null @@ -1,236 +0,0 @@ -## Application Services Best Practices & Conventions - -* **Do** create an application service for each **aggregate root**. - -### Application Service Interface - -* **Do** define an `interface` for each application service in the **application contracts** package. -* **Do** inherit from the `IApplicationService` interface. -* **Do** use the `AppService` postfix for the interface name (ex: `IProductAppService`). -* **Do** create DTOs (Data Transfer Objects) for inputs and outputs of the service. -* **Do not** get/return entities for the service methods. -* **Do** define DTOs based on the [DTO best practices](Data-Transfer-Objects.md). - -#### Outputs - -* **Avoid** to define too many output DTOs for same or related entities. Instead, define a **basic** and a **detailed** DTO for an entity. - -##### Basic DTO - -**Do** define a **basic** DTO for an aggregate root. - -- Include all the **primitive properties** directly on the aggregate root. - - Exception: Can **exclude** properties for **security** reasons (like `User.Password`). -- Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**. -- Inherit from one of the **extensible entity DTO** classes for aggregate roots (and entities implement the `IHasExtraProperties`). - -Example: - -```c# -[Serializable] -public class IssueDto : ExtensibleFullAuditedEntityDto -{ - public string Title { get; set; } - public string Text { get; set; } - public Guid? MilestoneId { get; set; } - public Collection Labels { get; set; } -} - -[Serializable] -public class IssueLabelDto -{ - public Guid IssueId { get; set; } - public Guid LabelId { get; set; } -} -``` - -##### Detailed DTO - -**Do** define a **detailed** DTO for an entity if it has reference(s) to other aggregate roots. - -* Include all the **primitive properties** directly on the entity. - - Exception-1: Can **exclude** properties for **security** reasons (like `User.Password`). - - Exception-2: **Do** exclude reference properties (like `MilestoneId` in the example above). Will already add details for the reference properties. -* Include a **basic DTO** property for every reference property. -* Include all the **sub collections** of the entity where every item in the collection is the **basic DTO** of the related entity. - -Example: - -````C# -[Serializable] -public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto -{ - public string Title { get; set; } - public string Text { get; set; } - public MilestoneDto Milestone { get; set; } - public Collection Labels { get; set; } -} - -[Serializable] -public class MilestoneDto : ExtensibleEntityDto -{ - public string Name { get; set; } - public bool IsClosed { get; set; } -} - -[Serializable] -public class LabelDto : ExtensibleEntityDto -{ - public string Name { get; set; } - public string Color { get; set; } -} -```` - -#### Inputs - -* **Do not** define any property in an input DTO that is not used in the service class. -* **Do not** share input DTOs between application service methods. -* **Do not** inherit an input DTO class from another one. - * **May** inherit from an abstract base DTO class and share some properties between different DTOs in that way. However, should be very careful in that case because manipulating the base DTO would effect all related DTOs and service methods. Avoid from that as a good practice. - -#### Methods - -* **Do** define service methods as asynchronous with **Async** postfix. -* **Do not** repeat the entity name in the method names. - * Example: Define `GetAsync(...)` instead of `GetProductAsync(...)` in the `IProductAppService`. - -##### Getting A Single Entity - -* **Do** use the `GetAsync` **method name**. -* **Do** get Id with a **primitive** method parameter. -* Return the **detailed DTO**. Example: - -````C# -Task GetAsync(Guid id); -```` - -##### Getting A List Of Entities - -* **Do** use the `GetListAsync` **method name**. -* **Do** get a single DTO argument for **filtering**, **sorting** and **paging** if necessary. - * **Do** implement filters optional where possible. - * **Do** implement sorting & paging properties as optional and provide default values. - * **Do** limit maximum page size (for performance reasons). -* **Do** return a list of **detailed DTO**s. Example: - -````C# -Task> GetListAsync(QuestionListQueryDto queryDto); -```` - -##### Creating A New Entity - -* **Do** use the `CreateAsync` **method name**. -* **Do** get a **specialized input** DTO to create the entity. -* **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed. -* **Do** use **data annotations** for input validation. - * Share constants between domain wherever possible (via constants defined in the **domain shared** package). -* **Do** return **the detailed** DTO for new created entity. -* **Do** only require the **minimum** info to create the entity but provide possibility to set others as optional properties. - -Example **method**: - -````C# -Task CreateAsync(CreateQuestionDto questionDto); -```` - -The related **DTO**: - -````C# -[Serializable] -public class CreateQuestionDto : ExtensibleObject -{ - [Required] - [StringLength(QuestionConsts.MaxTitleLength, - MinimumLength = QuestionConsts.MinTitleLength)] - public string Title { get; set; } - - [StringLength(QuestionConsts.MaxTextLength)] - public string Text { get; set; } //Optional - - public Guid? CategoryId { get; set; } //Optional -} -```` - -##### Updating An Existing Entity - -- **Do** use the `UpdateAsync` **method name**. -- **Do** get a **specialized input** DTO to update the entity. -- **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed. -- **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO. -- **Do** use **data annotations** for input validation. - - Share constants between domain wherever possible (via constants defined in the **domain shared** package). -- **Do** return **the detailed** DTO for the updated entity. - -Example: - -````C# -Task UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto); -```` - -##### Deleting An Existing Entity - -- **Do** use the `DeleteAsync` **method name**. -- **Do** get Id with a **primitive** method parameter. Example: - -````C# -Task DeleteAsync(Guid id); -```` - -##### Other Methods - -* **Can** define additional methods to perform operations on the entity. Example: - -````C# -Task VoteAsync(Guid id, VoteType type); -```` - -This method votes a question and returns the current score of the question. - -### Application Service Implementation - -* **Do** develop the application layer **completely independent from the web layer**. -* **Do** implement application service interfaces in the **application layer**. - * **Do** use the naming convention. Ex: Create `ProductAppService` class for the `IProductAppService` interface. - * **Do** inherit from the `ApplicationService` base class. -* **Do** make all public methods **virtual**, so developers may inherit and override them. -* **Do not** make **private** methods. Instead make them **protected virtual**, so developers may inherit and override them. - -#### Using Repositories - -* **Do** use the specifically designed repositories (like `IProductRepository`). -* **Do not** use generic repositories (like `IRepository`). - -#### Querying Data - -* **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source. - -#### Extra Properties - -* **Do** use either `MapExtraPropertiesTo` extension method ([see](../Object-Extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services. - -#### Manipulating / Deleting Entities - -* **Do** always get all the related entities from repositories to perform the operations on them. -* **Do** call repository's Update/UpdateAsync method after updating an entity. Because, not all database APIs support change tracking & auto update. - -#### Handle files - -* **Do not** use any web components like `IFormFile` or `Stream` in the application services. If you want to serve a file you can use `byte[]`. -* **Do** use a `Controller` to handle file uploading then pass the `byte[]` of the file to the application service method. - -#### Using Other Application Services - -* **Do not** use other application services of the same module/application. Instead; - * Use domain layer to perform the required task. - * Extract a new class and share between the application services to accomplish the code reuse when necessary. But be careful to don't couple two use cases. They may seem similar at the beginning, but may evolve to different directions by time. So, use code sharing carefully. -* **Can** use application services of others only if; - * They are parts of another module / microservice. - * The current module has only reference to the application contracts of the used module. - - -## See Also - -* [Video tutorial Part-1](https://abp.io/video-courses/essentials/application-services-part-1) -* [Video tutorial Part-2](https://abp.io/video-courses/essentials/application-services-part-2) - - diff --git a/docs/en/Best-Practices/Data-Transfer-Objects.md b/docs/en/Best-Practices/Data-Transfer-Objects.md deleted file mode 100644 index cd3a34513a..0000000000 --- a/docs/en/Best-Practices/Data-Transfer-Objects.md +++ /dev/null @@ -1,13 +0,0 @@ -## Data Transfer Objects Best Practices & Conventions - -* **Do** define DTOs in the **application contracts** package. -* **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto`, `CreationAuditedEntityDto`, `AuditedEntityDto`, `FullAuditedEntityDto` and so on). - * **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way. -* **Do** define DTO members with **public getter and setter**. -* **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service. -* **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary. -* **Do** mark all DTOs as **[Serializable]** since they are already serializable and developers may want to binary serialize them. - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/data-transfer-objects) \ No newline at end of file diff --git a/docs/en/Best-Practices/Domain-Services.md b/docs/en/Best-Practices/Domain-Services.md deleted file mode 100644 index 89d3125857..0000000000 --- a/docs/en/Best-Practices/Domain-Services.md +++ /dev/null @@ -1,68 +0,0 @@ -## Domain Services Best Practices & Conventions - - - -### Domain Service - -- **Do** define domain services in the **domain layer**. -- **Do not** create interfaces for the domain services **unless** you have a good reason to (like mock and test different implementations). -- **Do** name your domain service with `Manager` suffix. - -For the example of a domain service: -```cs -public class IssueManager : DomainService -{ - //... -} -``` - -### Domain Service Methods - -- **Do not** define `GET` methods. `GET` methods do not change the state of an entity. Hence, use the repository directly in the Application Service instead of Domain Service method. - -- **Do** define methods that only mutates data; changes the state of an entity or an aggregate root. - -- **Do not** define methods with generic names (like `UpdateIssueAsync`). - -- **Do** define methods with self explanatory names (like `AssignToAsync`) that implements the specific domain logic. - - -- **Do** accept valid domain objects as parameters. - -```cs -public async Task AssignToAsync(Issue issue, IdentityUser user) -{ - //... -} -``` - -- **Do** throw `BusinessException` or custom business exception if a validation fails. - - - **Do** use domain error codes with unique code-namespace for exception localization. - -```cs -public async Task AssignToAsync(Issue issue, IdentityUser user) -{ - var openIssueCount = await _issueRepository.GetCountAsync( - i => i.AssignedUserId == user.Id && !i.IsClosed - ); - - if (openIssueCount >= 3) - { - throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); - } - - issue.AssignedUserId = user.Id; -} -``` - -- **Do not** return `DTO`. Return only domain objects when you need. -- **Do not** involve authenticated user logic. Instead, define extra parameter and send the related data of ` CurrentUser` from the Application Service layer. - - - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/domain-services) -* [Domain Services](../Domain-Services.md) -* [Exception Handling](../Exception-Handling.md) \ No newline at end of file diff --git a/docs/en/Best-Practices/Entities.md b/docs/en/Best-Practices/Entities.md deleted file mode 100644 index b16234d52b..0000000000 --- a/docs/en/Best-Practices/Entities.md +++ /dev/null @@ -1,161 +0,0 @@ -## Entity Best Practices & Conventions - -### Entities - -Every aggregate root is also an entity. So, these rules are valid for aggregate roots too unless aggregate root rules override them. - -- **Do** define entities in the **domain layer**. - -#### Primary Constructor - -* **Do** define a **primary constructor** that ensures the validity of the entity on creation. Primary constructors are used to create a new instance of the entity by the application code. - -- **Do** define primary constructor as `public`, `internal` or `protected internal` based on the requirements. If it's not public, the entity is expected to be created by a domain service. -- **Do** always initialize sub collections in the primary constructor. -- **Do not** generate `Guid` keys inside the constructor. Get it as a parameter, so the calling code will use `IGuidGenerator` to generate a new `Guid` value. - -#### Parameterless Constructor - -- **Do** always define a `protected` parameterless constructor to be compatible with ORMs. - -#### References - -- **Do** always **reference** to other aggregate roots **by Id**. Never add navigation properties to other aggregate roots. - -#### Other Class Members - -- **Do** always define properties and methods as `virtual` (except `private` methods, obviously). Because some ORMs and dynamic proxy tools require it. -- **Do** keep the entity as always **valid** and **consistent** within its own boundary. - - **Do** define properties with `private`, `protected`, `internal ` or `protected internal` setter where it is needed to protect the entity consistency and validity. - - **Do** define `public `, `internal` or `protected internal` (virtual) **methods** to change the properties (with non-public setters) if necessary. - - **Do** return the entity object (`this`) from the setter methods. - -### Aggregate Roots - -#### Primary Keys - -* **Do** always use a **Id** property for the aggregate root key. -* **Do not** use **composite keys** for aggregate roots. -* **Do** use **Guid** as the **primary key** of all aggregate roots. - -#### Base Class - -* **Do** inherit from the `AggregateRoot` or one of the audited classes (`CreationAuditedAggregateRoot`, `AuditedAggregateRoot` or `FullAuditedAggregateRoot`) based on requirements. - -#### Aggregate Boundary - -* **Do** keep aggregates **as small as possible**. Most of the aggregates will only have primitive properties and will not have sub collections. Consider these as design decisions: - * **Performance** & **memory** cost of loading & saving aggregates (keep in mind that an aggregate is normally loaded & saved as a single unit). Larger aggregates will consume more CPU & memory. - * **Consistency** & **validity** boundary. - -### Example - -#### Aggregate Root - -````C# -public class Issue : FullAuditedAggregateRoot //Using Guid as the key/identifier -{ - public virtual string Title { get; private set; } //Changed using the SetTitle() method - public virtual string Text { get; set; } //Can be directly changed. null values are allowed - public virtual Guid? MilestoneId { get; set; } //Reference to another aggregate root - public virtual bool IsClosed { get; private set; } - public virtual IssueCloseReason? CloseReason { get; private set; } //Just an enum type - public virtual Collection Labels { get; protected set; } //Sub collection - - protected Issue() - { - /* This constructor is for ORMs to be used while getting the entity from database. - * - No need to initialize the Labels collection - since it will be overrided from the database. - - It's protected since proxying and deserialization tools - may not work with private constructors. - */ - } - - //Primary constructor - public Issue( - Guid id, //Get Guid value from the calling code - [NotNull] string title, //Indicate that the title can not be null. - string text = null, - Guid? milestoneId = null) //Optional argument - { - Id = id; - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //Validate - Text = text; - MilestoneId = milestoneId; - - Labels = new Collection(); //Always initialize the collection - } - - public virtual Issue SetTitle([NotNull] string title) - { - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //Validate - return this; - } - - /* AddLabel & RemoveLabel methods manages the Labels collection - * in a safe way (prevents adding the same label twice) */ - - public virtual Issue AddLabel(Guid labelId) - { - if (Labels.Any(l => l.LabelId == labelId)) - { - return; - } - - Labels.Add(new IssueLabel(Id, labelId)); - return this; - } - - public virtual Issue RemoveLabel(Guid labelId) - { - Labels.RemoveAll(l => l.LabelId == labelId); - return this; - } - - /* Close & ReOpen methods protect the consistency - * of the IsClosed and the CloseReason properties. */ - - public virtual void Close(IssueCloseReason reason) - { - IsClosed = true; - CloseReason = reason; - } - - public virtual void ReOpen() - { - IsClosed = false; - CloseReason = null; - } -} -```` - -#### The Entity - -````C# -public class IssueLabel : Entity -{ - public virtual Guid IssueId { get; private set; } - public virtual Guid LabelId { get; private set; } - - protected IssueLabel() - { - - } - - public IssueLabel(Guid issueId, Guid labelId) - { - IssueId = issueId; - LabelId = labelId; - } -} -```` - -### References - -* Effective Aggregate Design by Vaughn Vernon - http://dddcommunity.org/library/vernon_2011 - - ## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/entities) \ No newline at end of file diff --git a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md b/docs/en/Best-Practices/Entity-Framework-Core-Integration.md deleted file mode 100644 index 4a6c82c379..0000000000 --- a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md +++ /dev/null @@ -1,197 +0,0 @@ -## Entity Framework Core Integration Best Practices - -> See [Entity Framework Core Integration document](../Entity-Framework-Core.md) for the basics of the EF Core integration. - -- **Do** define a separated `DbContext` interface and class for each module. -- **Do not** rely on lazy loading on the application development. -- **Do not** enable lazy loading for the `DbContext`. - -### DbContext Interface - -- **Do** define an **interface** for the `DbContext` that inherits from `IEfCoreDbContext`. -- **Do** add a `ConnectionStringName` **attribute** to the `DbContext` interface. -- **Do** add `DbSet` **properties** to the `DbContext` interface for only aggregate roots. Example: - -````C# -[ConnectionStringName("AbpIdentity")] -public interface IIdentityDbContext : IEfCoreDbContext -{ - DbSet Users { get; } - DbSet Roles { get; } -} -```` - -* **Do not** define `set;` for the properties in this interface. - -### DbContext class - -* **Do** inherit the `DbContext` from the `AbpDbContext` class. -* **Do** add a `ConnectionStringName` attribute to the `DbContext` class. -* **Do** implement the corresponding `interface` for the `DbContext` class. Example: - -````C# -[ConnectionStringName("AbpIdentity")] -public class IdentityDbContext : AbpDbContext, IIdentityDbContext -{ - public DbSet Users { get; set; } - public DbSet Roles { get; set; } - - public IdentityDbContext(DbContextOptions options) - : base(options) - { - - } - - //code omitted for brevity -} -```` - -### Table Prefix and Schema - -- **Do** add static `TablePrefix` and `Schema` **properties** to the `DbContext` class. Set default value from a constant. Example: - -````C# -public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix; -public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema; -```` - - - **Do** always use a short `TablePrefix` value for a module to create **unique table names** in a shared database. `Abp` table prefix is reserved for ABP core modules. - - **Do** set `Schema` to `null` as default. - -### Model Mapping - -- **Do** explicitly **configure all entities** by overriding the `OnModelCreating` method of the `DbContext`. Example: - -````C# -protected override void OnModelCreating(ModelBuilder builder) -{ - base.OnModelCreating(builder); - builder.ConfigureIdentity(); -} -```` - -- **Do not** configure model directly in the `OnModelCreating` method. Instead, create an **extension method** for `ModelBuilder`. Use Configure*ModuleName* as the method name. Example: - -````C# -public static class IdentityDbContextModelBuilderExtensions -{ - public static void ConfigureIdentity([NotNull] this ModelBuilder builder) - { - Check.NotNull(builder, nameof(builder)); - - builder.Entity(b => - { - b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users", AbpIdentityDbProperties.DbSchema); - b.ConfigureByConvention(); - //code omitted for brevity - }); - - builder.Entity(b => - { - b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "UserClaims", AbpIdentityDbProperties.DbSchema); - b.ConfigureByConvention(); - //code omitted for brevity - }); - - //code omitted for brevity - } -} -```` - -* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above). - -### Repository Implementation - -- **Do** **inherit** the repository from the `EfCoreRepository` class and implement the corresponding repository interface. Example: - -````C# -public class EfCoreIdentityUserRepository - : EfCoreRepository, IIdentityUserRepository -{ - public EfCoreIdentityUserRepository( - IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } -} -```` - -* **Do** use the `DbContext` interface as the generic parameter, not the class. -* **Do** pass the `cancellationToken` to EF Core using the `GetCancellationToken` helper method. Example: - -````C# -public virtual async Task FindByNormalizedUserNameAsync( - string normalizedUserName, - bool includeDetails = true, - CancellationToken cancellationToken = default) -{ - return await (await GetDbSetAsync()) - .IncludeDetails(includeDetails) - .FirstOrDefaultAsync( - u => u.NormalizedUserName == normalizedUserName, - GetCancellationToken(cancellationToken) - ); -} -```` - -`GetCancellationToken` fallbacks to the `ICancellationTokenProvider.Token` to obtain the cancellation token if it is not provided by the caller code. - -- **Do** create a `IncludeDetails` **extension method** for the `IQueryable` for each aggregate root which has **sub collections**. Example: - -````C# -public static IQueryable IncludeDetails( - this IQueryable queryable, - bool include = true) -{ - if (!include) - { - return queryable; - } - - return queryable - .Include(x => x.Roles) - .Include(x => x.Logins) - .Include(x => x.Claims) - .Include(x => x.Tokens); -} -```` - -* **Do** use the `IncludeDetails` extension method in the repository methods just like used in the example code above (see `FindByNormalizedUserNameAsync`). - -- **Do** override `WithDetails` method of the repository for aggregates root which have **sub collections**. Example: - -````C# -public override async Task> WithDetailsAsync() -{ - // Uses the extension method defined above - return (await GetQueryableAsync()).IncludeDetails(); -} -```` - -### Module Class - -- **Do** define a module class for the Entity Framework Core integration package. -- **Do** add `DbContext` to the `IServiceCollection` using the `AddAbpDbContext` method. -- **Do** add implemented repositories to the options for the `AddAbpDbContext` method. Example: - -````C# -[DependsOn( - typeof(AbpIdentityDomainModule), - typeof(AbpEntityFrameworkCoreModule) - )] -public class AbpIdentityEntityFrameworkCoreModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddAbpDbContext(options => - { - options.AddRepository(); - options.AddRepository(); - }); - } -} -```` - - ## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/abp-ef-core) \ No newline at end of file diff --git a/docs/en/Best-Practices/Index.md b/docs/en/Best-Practices/Index.md deleted file mode 100644 index 3cc10a17cc..0000000000 --- a/docs/en/Best-Practices/Index.md +++ /dev/null @@ -1,30 +0,0 @@ -## Module Development Best Practices & Conventions - -### Introduction - -This document describes the **best practices** and **conventions** for those who want to develop **modules** that satisfy the following specifications: - -* Develop modules that conform to the **Domain Driven Design** patterns & best practices. -* Develop modules with **DBMS and ORM independence**. -* Develop modules that can be used as a **remote service / microservice** as well as being compatible with a **monolithic** application. - -Also, this guide is mostly usable for general **application development**. - -### Guides - -* Overall - * [Module Architecture](Module-Architecture.md) -* Domain Layer - * [Entities](Entities.md) - * [Repositories](Repositories.md) - * [Domain Services](Domain-Services.md) -* Application Layer - * [Application Services](Application-Services.md) - * [Data Transfer Objects](Data-Transfer-Objects.md) -* Data Access - * [Entity Framework Core Integration](Entity-Framework-Core-Integration.md) - * [MongoDB Integration](MongoDB-Integration.md) - -## See Also - -* [E-Book: Implementing Domain Driven Design](https://abp.io/books/implementing-domain-driven-design) diff --git a/docs/en/Best-Practices/Module-Architecture.md b/docs/en/Best-Practices/Module-Architecture.md deleted file mode 100644 index b7e0d03db9..0000000000 --- a/docs/en/Best-Practices/Module-Architecture.md +++ /dev/null @@ -1,89 +0,0 @@ -## Module Architecture Best Practices & Conventions - -### Solution Structure - -* **Do** create a separated Visual Studio solution for every module. -* **Do** name the solution as *CompanyName.ModuleName* (for core ABP modules, it's *Volo.Abp.ModuleName*). -* **Do** develop the module as layered, so it has several packages (projects) those are related to each other. - * Every package has its own module definition file and explicitly declares the dependencies for the depended packages/modules. - -### Layers & Packages - -The following diagram shows the packages of a well-layered module and dependencies of those packages between them: - -![module-layers-and-packages](../images/module-layers-and-packages.jpg) - -The ultimate goal is to allow an application to use the module in a flexible manner. Example applications: - -* **A)** A **monolithic** application; - * Adds references to the **Web** and the **Application** packages. - * Adds a reference to one of the **EF Core** or the **MongoDB** packages based on the preference. - * The result; - * The application **can show UI** of the module. - * It hosts the **application** and **domain** layers in the **same process** (that's why it needs to have a reference to a database integration package). - * This application also **serves** the module's **HTTP API** (since it includes the HttpApi package through the Web package). -* **B)** An application that just serves the module as a **microservice**; - * Adds a reference to **HttpApi** and **Application** packages. - * Adds a reference to one of the **EF Core** or the **MongoDB** packages based on the preference. - * The result; - * The application **can not show UI** of the module since it does not have a reference to the Web package. - * It hosts the **application** and **domain** layers in the **same process** (that's why it needs to have a reference to a database integration package). - * This application **serves** the module's **HTTP API** (as the main goal of the application). -* **C)** An application that shows the module **UI** but does not host the application (just uses it as a remote service that is hosted by the application A or B); - * Adds a reference to the **Web** and the **HttpApi.Client** packages. - * Configures the remote endpoint for the HttpApi.Client package. - * The result; - * The application **can show UI** of the module. - * It does not host the application and domain layers of the module in the same process. Instead, uses it as a **remote service**. - * This application also **serves** the module's **HTTP API** (since it includes the HttpApi package through the Web package). -* **D)** A **client** application (or microservice) that just uses the module as a remote service (that is hosted by the application A, B or C); - * Adds a reference to the **HttpApi.Client** package. - * Configures the remote endpoint for the HttpApi.Client package. - * The result; - * The application can use all the functionality of the module as a **remote client**. - * The application is just a client and **can not serve** the **HTTP API** of the module. - * The application is just a client and **can not show** the **UI** of the module. -* **E**) A proxy application that hosts the HTTP API of the module but just forwards all requests to another application (that is hosted by the application A, B or C); - * Adds a reference to the **HttpApi** and **HttpApi.Client** packages. - * Configures the remote endpoint for the HttpApi.Client package. - * The result; - * The application can use all the functionality of the module as a **remote client**. - * This application also **serves** the module's **HTTP API**, but actually works just like a proxy by redirecting all requests (for the module) to another remote server. - -Next section describes the packages in more details. - -#### Domain Layer - -* **Do** divide the domain layer into two projects: - * **Domain.Shared** package, named as *CompanyName.ModuleName.Domain.Shared*, that contains constants, enums and other types those can be safely shared with the all layers of the module. This package can also be shared to 3rd-party clients. It can not contain entities, repositories, domain services or any other business objects. - * **Domain** package, named as *CompanyName.ModuleName.Domain*, that contains entities, repository interfaces, domain service interfaces and their implementations and other domain objects. - * Domain package depends on the **Domain.Shared** package. - -#### Application Layer - -* **Do** divide the application layer into two projects: - * **Application.Contracts** package, named as *CompanyName.ModuleName.Application.Contracts*, that contains application service interfaces and related data transfer objects. - * Application contract package depends on the **Domain.Shared** package. - * **Application** package, named as *CompanyName.ModuleName.Application*, that contains application service implementations. - * Application package depends on the **Domain** and the **Application.Contracts** packages. - -#### Infrastructure Layer - -* **Do** create a separated integration package for each ORM/database integration like Entity Framework Core and MongoDB. - * **Do**, for instance, create a *CompanyName.ModuleName.EntityFrameworkCore* package that abstracts the Entity Framework Core integration. ORM integration packages depend on the **Domain** package. - * **Do not** depend on other layers from the ORM/database integration package. -* **Do** create a separated integration package for each major library that is planned to be replaceable by another library without effecting the other packages. - -#### HTTP Layer - -* **Do** create an **HTTP API** package, named as *CompanyName.ModuleName.HttpApi*, to develop a REST style HTTP API for the module. - * HTTP API package only depends on the **Application.Contracts** package. It does not depend on the Application package. - * **Do** create a Controller for each application service (generally by implementing their interfaces). These controllers uses the application service interfaces to delegate the actions. It just configures routes, HTTP methods and other web related stuffs if needed. -* **Do** create an **HTTP API Client** package, named as *CompanyName.ModuleName.HttpApi.Client*, to provide client services for the HTTP API package. Those client services implement application interfaces as clients to a remote endpoint. - * HTTP API Client package only depends on the **Application.Contracts** package. - * **Do** use dynamic HTTP C# client proxy feature of the ABP framework. - -#### Web Layer - -* **Do** create a **Web** package, named as *CompanyName.ModuleName.Web*, that contains pages, views, scripts, styles, images and other UI components. - * Web package only depends on the **HttpApi** package. \ No newline at end of file diff --git a/docs/en/Best-Practices/MongoDB-Integration.md b/docs/en/Best-Practices/MongoDB-Integration.md deleted file mode 100644 index 16a6a7d58f..0000000000 --- a/docs/en/Best-Practices/MongoDB-Integration.md +++ /dev/null @@ -1,155 +0,0 @@ -## MongoDB Integration - -* Do define a separated `MongoDbContext` interface and class for each module. - -### MongoDbContext Interface - -- **Do** define an **interface** for the `MongoDbContext` that inherits from `IAbpMongoDbContext`. -- **Do** add a `ConnectionStringName` **attribute** to the `MongoDbContext` interface. -- **Do** add `IMongoCollection` **properties** to the `MongoDbContext` interface only for the aggregate roots. Example: - -````C# -[ConnectionStringName("AbpIdentity")] -public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext -{ - IMongoCollection Users { get; } - IMongoCollection Roles { get; } -} -```` - -### MongoDbContext class - -- **Do** inherit the `MongoDbContext` from the `AbpMongoDbContext` class. -- **Do** add a `ConnectionStringName` attribute to the `MongoDbContext` class. -- **Do** implement the corresponding `interface` for the `MongoDbContext` class. Example: - -```c# -[ConnectionStringName("AbpIdentity")] -public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbContext -{ - public IMongoCollection Users => Collection(); - public IMongoCollection Roles => Collection(); - - //code omitted for brevity -} -``` - -### Collection Prefix - -- **Do** add static `CollectionPrefix` **property** to the `DbContext` class. Set default value from a constant. Example: - -```c# -public static string CollectionPrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix; -``` - -Used the same constant defined for the EF Core integration table prefix in this example. - -- **Do** always use a short `CollectionPrefix` value for a module to create **unique collection names** in a shared database. `Abp` collection prefix is reserved for ABP core modules. - -### Collection Mapping - -- **Do** explicitly **configure all aggregate roots** by overriding the `CreateModel` method of the `MongoDbContext`. Example: - -```c# -protected override void CreateModel(IMongoModelBuilder modelBuilder) -{ - base.CreateModel(modelBuilder); - - modelBuilder.ConfigureIdentity(); -} -``` - -- **Do not** configure model directly in the `CreateModel` method. Instead, create an **extension method** for the `IMongoModelBuilder`. Use Configure*ModuleName* as the method name. Example: - -```c# -public static class AbpIdentityMongoDbContextExtensions -{ - public static void ConfigureIdentity( - this IMongoModelBuilder builder, - Action optionsAction = null) - { - Check.NotNull(builder, nameof(builder)); - - builder.Entity(b => - { - b.CollectionName = AbpIdentityDbProperties.DbTablePrefix + "Users"; - }); - - builder.Entity(b => - { - b.CollectionName = AbpIdentityDbProperties.DbTablePrefix + "Roles"; - }); - } -} -``` - -### Repository Implementation - -- **Do** **inherit** the repository from the `MongoDbRepository` class and implement the corresponding repository interface. Example: - -```c# -public class MongoIdentityUserRepository - : MongoDbRepository, - IIdentityUserRepository -{ - public MongoIdentityUserRepository( - IMongoDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } -} -``` - -- **Do** pass the `cancellationToken` to the MongoDB Driver using the `GetCancellationToken` helper method. Example: - -```c# -public async Task FindByNormalizedUserNameAsync( - string normalizedUserName, - bool includeDetails = true, - CancellationToken cancellationToken = default) -{ - return await (await GetMongoQueryableAsync()) - .FirstOrDefaultAsync( - u => u.NormalizedUserName == normalizedUserName, - GetCancellationToken(cancellationToken) - ); -} -``` - -`GetCancellationToken` fallbacks to the `ICancellationTokenProvider.Token` to obtain the cancellation token if it is not provided by the caller code. - -* **Do** ignore the `includeDetails` parameters for the repository implementation since MongoDB loads the aggregate root as a whole (including sub collections) by default. -* **Do** use the `GetMongoQueryableAsync()` method to obtain an `IQueryable` to perform queries wherever possible. Because; - * `GetMongoQueryableAsync()` method automatically uses the `ApplyDataFilters` method to filter the data based on the current data filters (like soft delete and multi-tenancy). - * Using `IQueryable` makes the code as much as similar to the EF Core repository implementation and easy to write and read. -* **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method. - -### Module Class - -- **Do** define a module class for the MongoDB integration package. -- **Do** add `MongoDbContext` to the `IServiceCollection` using the `AddMongoDbContext` method. -- **Do** add implemented repositories to the options for the `AddMongoDbContext` method. Example: - -```c# -[DependsOn( - typeof(AbpIdentityDomainModule), - typeof(AbpUsersMongoDbModule) - )] -public class AbpIdentityMongoDbModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddMongoDbContext(options => - { - options.AddRepository(); - options.AddRepository(); - }); - } -} -``` - -Notice that this module class also calls the static `BsonClassMap` configuration method defined above. - - ## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/abp-mongodb) \ No newline at end of file diff --git a/docs/en/Best-Practices/Repositories.md b/docs/en/Best-Practices/Repositories.md deleted file mode 100644 index 97cae7c35e..0000000000 --- a/docs/en/Best-Practices/Repositories.md +++ /dev/null @@ -1,75 +0,0 @@ -## Repository Best Practices & Conventions - -### Repository Interfaces - -* **Do** define repository interfaces in the **domain layer**. -* **Do** define a repository interface (like `IIdentityUserRepository`) and create its corresponding implementations for **each aggregate root**. - * **Do** always use the created repository interface from the application code. - * **Do not** use generic repository interfaces (like `IRepository`) from the application code. - * **Do not** use `IQueryable` features in the application code (domain, application... layers). - -For the example aggregate root: - -````C# -public class IdentityUser : AggregateRoot -{ - //... -} -```` - -Define the repository interface as below: - -````C# -public interface IIdentityUserRepository : IBasicRepository -{ - //... -} -```` - -* **Do not** inherit the repository interface from the `IRepository` interface. Because it inherits the `IQueryable` and the repository should not expose `IQueryable` to the application. -* **Do** inherit the repository interface from `IBasicRepository` (as normally) or a lower-featured interface, like `IReadOnlyRepository` (if it's needed). -* **Do not** define repositories for entities those are **not aggregate roots**. - -### Repository Methods - -* **Do** define all repository methods as **asynchronous**. -* **Do** add an **optional** `cancellationToken` parameter to every method of the repository. Example: - -````C# -Task FindByNormalizedUserNameAsync( - [NotNull] string normalizedUserName, - CancellationToken cancellationToken = default -); -```` - -* **Do** add an optional `bool includeDetails = true` parameter (default value is `true`) for every repository method which returns a **single entity**. Example: - -````C# -Task FindByNormalizedUserNameAsync( - [NotNull] string normalizedUserName, - bool includeDetails = true, - CancellationToken cancellationToken = default -); -```` - -This parameter will be implemented for ORMs to eager load sub collections of the entity. - -* **Do** add an optional `bool includeDetails = false` parameter (default value is `false`) for every repository method which returns a **list of entities**. Example: - -````C# -Task> GetListByNormalizedRoleNameAsync( - string normalizedRoleName, - bool includeDetails = false, - CancellationToken cancellationToken = default -); -```` - -* **Do not** create composite classes to combine entities to get from repository with a single method call. Examples: *UserWithRoles*, *UserWithTokens*, *UserWithRolesAndTokens*. Instead, properly use `includeDetails` option to add all details of the entity when needed. -* **Avoid** to create projection classes for entities to get less property of an entity from the repository. Example: Avoid to create BasicUserView class to select a few properties needed for the use case needs. Instead, directly use the aggregate root class. However, there may be some exceptions for this rule, where: - * Performance is so critical for the use case and getting the whole aggregate root highly impacts the performance. - -### See Also - -* [Entity Framework Core Integration](Entity-Framework-Core-Integration.md) -* [MongoDB Integration](MongoDB-Integration.md) -* [Video tutorial](https://abp.io/video-courses/essentials/generic-repositories) \ No newline at end of file diff --git a/docs/en/Best-Practices/images/postgresql-delete-initial-migrations.png b/docs/en/Best-Practices/images/postgresql-delete-initial-migrations.png deleted file mode 100644 index 14788c5fb8..0000000000 Binary files a/docs/en/Best-Practices/images/postgresql-delete-initial-migrations.png and /dev/null differ diff --git a/docs/en/Best-Practices/images/postgresql-update-database.png b/docs/en/Best-Practices/images/postgresql-update-database.png deleted file mode 100644 index 30a5f3abe1..0000000000 Binary files a/docs/en/Best-Practices/images/postgresql-update-database.png and /dev/null differ diff --git a/docs/en/Blob-Storing-Aliyun.md b/docs/en/Blob-Storing-Aliyun.md deleted file mode 100644 index 4060e5ba26..0000000000 --- a/docs/en/Blob-Storing-Aliyun.md +++ /dev/null @@ -1,78 +0,0 @@ -# BLOB Storing Aliyun Provider - -BLOB Storing Aliyun Provider can store BLOBs in [Aliyun Blob storage](https://help.aliyun.com/product/31815.html). - -> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Aliyun BLOB as the storage provider. - -## Installation - -Use the ABP CLI to add [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Aliyun` package. -* Run `abp add-package Volo.Abp.BlobStoring.Aliyun` command. - -If you want to do it manually, install the [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAliyunModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). - -**Example: Configure to use the Aliyun storage provider by default** - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseAliyun(aliyun => - { - aliyun.AccessKeyId = "your aliyun access key id"; - aliyun.AccessKeySecret = "your aliyun access key secret"; - aliyun.Endpoint = "your oss endpoint"; - aliyun.RegionId = "your sts region id"; - aliyun.RoleArn = "the arn of ram role"; - aliyun.RoleSessionName = "the name of the certificate"; - aliyun.Policy = "policy"; - aliyun.DurationSeconds = "expiration date"; - aliyun.ContainerName = "your aliyun container name"; - aliyun.CreateContainerIfNotExists = true; - }); - }); -}); -```` - -> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. - -### Options - -* **AccessKeyId** ([NotNull]string): AccessKey is the key to access the Alibaba Cloud API. It has full permissions for the account. Please keep it safe! Recommend to follow [Alibaba Cloud security best practicess](https://help.aliyun.com/document_detail/102600.html),Use RAM sub-user AccessKey to call API. -* **AccessKeySecret** ([NotNull]string): Same as above. -* **Endpoint** ([NotNull]string): Endpoint is the external domain name of OSS. See the [document](https://help.aliyun.com/document_detail/31837.html) for details. -* **UseSecurityTokenService** (bool): Use [STS temporary credentials](https://help.aliyun.com/document_detail/100624.html) to access OSS services,default: `false`. -* **RegionId** (string): Access address of STS service. See the [document](https://help.aliyun.com/document_detail/66053.html) for details. -* **RoleArn** ([NotNull]string): STS required role ARN. See the [document](https://help.aliyun.com/document_detail/100624.html) for details. -* **RoleSessionName** ([NotNull]string): Used to identify the temporary access credentials, it is recommended to use different application users to distinguish. -* **Policy** (string): Additional permission restrictions. See the [document](https://help.aliyun.com/document_detail/100680.html) for details. -* **DurationSeconds** (int): Validity period(s) of a temporary access certificate,minimum is 900 and the maximum is 3600. -* **ContainerName** (string): You can specify the container name in Aliyun. If this is not specified, it uses the name of the BLOB container defined with the `BlobContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Aliyun has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://help.aliyun.com/knowledge_detail/39668.html): - * Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character. - * Container names Must start and end with lowercase letters and numbers. - * Container names must be from **3** through **63** characters long. -* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Aliyun, `AliyunBlobProvider` will try to create it. -* **TemporaryCredentialsCacheKey** (bool): The cache key of STS credentials. - - -## Aliyun Blob Name Calculator - -Aliyun Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: - -* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). -* Appends `tenants/` string if current tenant is not `null`. -* Appends the BLOB name. - -## Other Services - -* `AliyunBlobProvider` is the main service that implements the Aliyun BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AliyunBlobProvider` class). -* `IAliyunBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAliyunBlobNameCalculator` by default. -* `IOssClientFactory` is used create OSS client. It is implemented by the `DefaultOssClientFactory` by default. You can override/replace it,if you want customize. diff --git a/docs/en/Blob-Storing-Aws.md b/docs/en/Blob-Storing-Aws.md deleted file mode 100644 index c69024db28..0000000000 --- a/docs/en/Blob-Storing-Aws.md +++ /dev/null @@ -1,85 +0,0 @@ -# BLOB Storing Aws Provider - -BLOB Storing Aws Provider can store BLOBs in [Amazon Simple Storage Service](https://aws.amazon.com/s3/). - -> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Aws BLOB as the storage provider. - -## Installation - -Use the ABP CLI to add [Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Aws` package. -* Run `abp add-package Volo.Abp.BlobStoring.Aws` command. - -If you want to do it manually, install the [Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAwsModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). - -**Example: Configure to use the Aws storage provider by default** - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseAws(Aws => - { - Aws.AccessKeyId = "your Aws access key id"; - Aws.SecretAccessKey = "your Aws access key secret"; - Aws.UseCredentials = "set true to use credentials"; - Aws.UseTemporaryCredentials = "set true to use temporary credentials"; - Aws.UseTemporaryFederatedCredentials = "set true to use temporary federated credentials"; - Aws.ProfileName = "the name of the profile to get credentials from"; - Aws.ProfilesLocation = "the path to the aws credentials file to look at"; - Aws.Region = "the system name of the service"; - Aws.Name = "the name of the federated user"; - Aws.Policy = "policy"; - Aws.DurationSeconds = "expiration date"; - Aws.ContainerName = "your Aws container name"; - Aws.CreateContainerIfNotExists = true; - }); - }); -}); - -```` - -> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. - -### Options - -* **AccessKeyId** (string): AWS Access Key ID. -* **SecretAccessKey** (string): AWS Secret Access Key. -* **UseCredentials** (bool): Use [credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html) to access AWS services,default : `false`. -* **UseTemporaryCredentials** (bool): Use [temporary credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempSessionToken.html) to access AWS services,default : `false`. -* **UseTemporaryFederatedCredentials** (bool): Use [federated user temporary credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempFederationToken.html) to access AWS services, default : `false`. -* **ProfileName** (string): The [name of the profile](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html) to get credentials from. -* **ProfilesLocation** (string): The path to the aws credentials file to look at. -* **Region** (string): The system name of the service. -* **Policy** (string): An IAM policy in JSON format that you want to use as an inline session policy. -* **DurationSeconds** (int): Validity period(s) of a temporary access certificate,minimum is 900 and the maximum is 3600. **note**: Using sub-accounts operated OSS,if the value is 0. -* **ContainerName** (string): You can specify the container name in Aws. If this is not specified, it uses the name of the BLOB container defined with the `BlobContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Aws has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html): - * Bucket names must be between **3** and **63** characters long. - * Bucket names can consist only of **lowercase** letters, numbers, dots (.), and hyphens (-). - * Bucket names must begin and end with a letter or number. - * Bucket names must not be formatted as an IP address (for example, 192.168.5.4). - * Bucket names can't begin with **xn--** (for buckets created after February 2020). - * Bucket names must be unique within a partition. - * Buckets used with Amazon S3 Transfer Acceleration can't have dots (.) in their names. For more information about transfer acceleration, see Amazon S3 Transfer Acceleration. -* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Aws, `AwsBlobProvider` will try to create it. - -## Aws Blob Name Calculator - -Aws Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: - -* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). -* Appends `tenants/` string if current tenant is not `null`. -* Appends the BLOB name. - -## Other Services - -* `AwsBlobProvider` is the main service that implements the Aws BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AwsBlobProvider` class). -* `IAwsBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAwsBlobNameCalculator` by default. -* `IAmazonS3ClientFactory` is used create OSS client. It is implemented by the `DefaultAmazonS3ClientFactory` by default. You can override/replace it,if you want customize. diff --git a/docs/en/Blob-Storing-Azure.md b/docs/en/Blob-Storing-Azure.md deleted file mode 100644 index 99833e71c0..0000000000 --- a/docs/en/Blob-Storing-Azure.md +++ /dev/null @@ -1,62 +0,0 @@ -# BLOB Storing Azure Provider - -BLOB Storing Azure Provider can store BLOBs in [Azure Blob storage](https://azure.microsoft.com/en-us/services/storage/blobs/). - -> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Azure BLOB as the storage provider. - -## Installation - -Use the ABP CLI to add [Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Azure` package. -* Run `abp add-package Volo.Abp.BlobStoring.Azure` command. - -If you want to do it manually, install the [Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAzureModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). - -**Example: Configure to use the azure storage provider by default** - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseAzure(azure => - { - azure.ConnectionString = "your azure connection string"; - azure.ContainerName = "your azure container name"; - azure.CreateContainerIfNotExists = true; - }); - }); -}); -```` - -> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. - -### Options - -* **ConnectionString** (string): A connection string includes the authorization information required for your application to access data in an Azure Storage account at runtime using Shared Key authorization. Please refer to Azure documentation: https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string -* **ContainerName** (string): You can specify the container name in azure. If this is not specified, it uses the name of the BLOB container defined with the `BlobContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Azure has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names): - * Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character. - * Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names. - * All letters in a container name must be **lowercase**. - * Container names must be from **3** through **63** characters long. -* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in azure, `AzureBlobProvider` will try to create it. - - -## Azure Blob Name Calculator - -Azure Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: - -* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). -* Appends `tenants/` string if current tenant is not `null`. -* Appends the BLOB name. - -## Other Services - -* `AzureBlobProvider` is the main service that implements the Azure BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AzureBlobProvider` class). -* `IAzureBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAzureBlobNameCalculator` by default. diff --git a/docs/en/Blob-Storing-Custom-Provider.md b/docs/en/Blob-Storing-Custom-Provider.md deleted file mode 100644 index 0b1255e0b7..0000000000 --- a/docs/en/Blob-Storing-Custom-Provider.md +++ /dev/null @@ -1,177 +0,0 @@ -# BLOB Storing: Creating a Custom Provider - -This document explains how you can create a new storage provider for the BLOB storing system with an example. - -> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to create a new storage provider. - -## Example Implementation - -The first step is to create a class implements the `IBlobProvider` interface or inherit from the `BlobProviderBase` abstract class. - -````csharp -using System.IO; -using System.Threading.Tasks; -using Volo.Abp.BlobStoring; -using Volo.Abp.DependencyInjection; - -namespace AbpDemo -{ - public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency - { - public override Task SaveAsync(BlobProviderSaveArgs args) - { - //TODO... - } - - public override Task DeleteAsync(BlobProviderDeleteArgs args) - { - //TODO... - } - - public override Task ExistsAsync(BlobProviderExistsArgs args) - { - //TODO... - } - - public override Task GetOrNullAsync(BlobProviderGetArgs args) - { - //TODO... - } - } -} -```` - -* `MyCustomBlobProvider` inherits from the `BlobProviderBase` and overrides the `abstract` methods. The actual implementation is up to you. -* Implementing `ITransientDependency` registers this class to the [Dependency Injection](Dependency-Injection.md) system as a transient service. - -> **Notice: Naming conventions are important**. If your class name doesn't end with `BlobProvider`, you must manually register/expose your service for the `IBlobProvider`. - -That's all. Now, you can configure containers (inside the `ConfigureServices` method of your [module](Module-Development-Basics.md)) to use the `MyCustomBlobProvider` class: - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.ProviderType = typeof(MyCustomBlobProvider); - }); -}); -```` - -> See the [BLOB Storing document](Blob-Storing.md) if you want to configure a specific container. - -### BlobContainerConfiguration Extension Method - -If you want to provide a simpler configuration, create an extension method for the `BlobContainerConfiguration` class: - -````csharp -public static class MyBlobContainerConfigurationExtensions -{ - public static BlobContainerConfiguration UseMyCustomBlobProvider( - this BlobContainerConfiguration containerConfiguration) - { - containerConfiguration.ProviderType = typeof(MyCustomBlobProvider); - return containerConfiguration; - } -} -```` - -Then you can configure containers easier using the extension method: - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseMyCustomBlobProvider(); - }); -}); -```` - -### Extra Configuration Options - -`BlobContainerConfiguration` allows to add/remove provider specific configuration objects. If your provider needs to additional configuration, you can create a wrapper class to the `BlobContainerConfiguration` for a type-safe configuration option: - -````csharp - public class MyCustomBlobProviderConfiguration - { - public string MyOption1 - { - get => _containerConfiguration - .GetConfiguration("MyCustomBlobProvider.MyOption1"); - set => _containerConfiguration - .SetConfiguration("MyCustomBlobProvider.MyOption1", value); - } - - private readonly BlobContainerConfiguration _containerConfiguration; - - public MyCustomBlobProviderConfiguration( - BlobContainerConfiguration containerConfiguration) - { - _containerConfiguration = containerConfiguration; - } - } -```` - -Then you can change the `MyBlobContainerConfigurationExtensions` class like that: - -````csharp -public static class MyBlobContainerConfigurationExtensions -{ - public static BlobContainerConfiguration UseMyCustomBlobProvider( - this BlobContainerConfiguration containerConfiguration, - Action configureAction) - { - containerConfiguration.ProviderType = typeof(MyCustomBlobProvider); - - configureAction.Invoke( - new MyCustomBlobProviderConfiguration(containerConfiguration) - ); - - return containerConfiguration; - } - - public static MyCustomBlobProviderConfiguration GetMyCustomBlobProviderConfiguration( - this BlobContainerConfiguration containerConfiguration) - { - return new MyCustomBlobProviderConfiguration(containerConfiguration); - } -} -```` - -* Added an action parameter to the `UseMyCustomBlobProvider` method to allow developers to set the additional options. -* Added a new `GetMyCustomBlobProviderConfiguration` method to be used inside `MyCustomBlobProvider` class to obtain the configured values. - -Then anyone can set the `MyOption1` as shown below: - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseMyCustomBlobProvider(provider => - { - provider.MyOption1 = "my value"; - }); - }); -}); -```` - -Finally, you can access to the extra options using the `GetMyCustomBlobProviderConfiguration` method: - -````csharp -public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency -{ - public override Task SaveAsync(BlobProviderSaveArgs args) - { - var config = args.Configuration.GetMyCustomBlobProviderConfiguration(); - var value = config.MyOption1; - - //... - } -} -```` - -## Contribute? - -If you create a new provider and you think it can be useful for other developers, please consider to [contribute](Contribution/Index.md) to the ABP Framework on GitHub. diff --git a/docs/en/Blob-Storing-Database.md b/docs/en/Blob-Storing-Database.md deleted file mode 100644 index c14086a2f4..0000000000 --- a/docs/en/Blob-Storing-Database.md +++ /dev/null @@ -1,98 +0,0 @@ -# BLOB Storing Database Provider - -BLOB Storing Database Storage Provider can store BLOBs in a relational or non-relational database. - -There are two database providers implemented; - -* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) package implements for [EF Core](Entity-Framework-Core.md), so it can store BLOBs in [any DBMS supported](https://docs.microsoft.com/en-us/ef/core/providers/) by the EF Core. -* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) package implements for [MongoDB](MongoDB.md). - -> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a database as the storage provider. - -## Installation - -### Automatic Installation - -If you've created your solution based on the [application startup template](Startup-Templates/Application.md), you can use the `abp add-module` [CLI](CLI.md) command to automatically add related packages to your solution. - -Open a command prompt (terminal) in the folder containing your solution (`.sln`) file and run the following command: - -````bash -abp add-module Volo.Abp.BlobStoring.Database -```` - -This command adds all the NuGet packages to corresponding layers of your solution. If you are using EF Core, it adds necessary configuration, adds a new database migration and updates the database. - -### Manual Installation - -Here, all the NuGet packages defined by this provider; - -* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain.Shared) -* [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain) -* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) -* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) - -You can only install Volo.Abp.BlobStoring.Database.EntityFrameworkCore or Volo.Abp.BlobStoring.Database.MongoDB (based on your preference) since they depends on the other packages. - -After installation, add `DepenedsOn` attribute to your related [module](Module-Development-Basics.md). Here, the list of module classes defined by the related NuGet packages listed above: - -* `BlobStoringDatabaseDomainModule` -* `BlobStoringDatabaseDomainSharedModule` -* `BlobStoringDatabaseEntityFrameworkCoreModule` -* `BlobStoringDatabaseMongoDbModule` - -Whenever you add a NuGet package to a project, also add the module class dependency. - -If you are using EF Core, you also need to configure your **Migration DbContext** to add BLOB storage tables to your database schema. Call `builder.ConfigureBlobStoring()` extension method inside the `OnModelCreating` method to include mappings to your DbContext. Then you can use the standard `Add-Migration` and `Update-Database` [commands](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create necessary tables in your database. - -## Configuration - -### Connection String - -If you will use your `Default` connection string, you don't need to any additional configuration. - -If you want to use a separate database for BLOB storage, use the `AbpBlobStoring` as the [connection string](Connection-Strings.md) name in your configuration file (`appsettings.json`). In this case, also read the [EF Core Migrations](Entity-Framework-Core-Migrations.md) document to learn how to create and use a different database for a desired module. - -### Configuring the Containers - -If you are using only the database storage provider, you don't need to manually configure it, since it is automatically done. If you are using multiple storage providers, you may want to configure it. - -Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). - -**Example: Configure to use the database storage provider by default** - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseDatabase(); - }); -}); -```` - -> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. - -## Additional Information - -It is expected to use the [BLOB Storing services](Blob-Storing.md) to use the BLOB storing system. However, if you want to work on the database tables/entities, you can use the following information. - -### Entities - -Entities defined for this module: - -* `DatabaseBlobContainer` (aggregate root) represents a container stored in the database. -* `DatabaseBlob` (aggregate root) represents a BLOB in the database. - -See the [entities document](Entities.md) to learn what is an entity and aggregate root. - -### Repositories - -* `IDatabaseBlobContainerRepository` -* `IDatabaseBlobRepository` - -You can also use `IRepository` and `IRepository` to take the power of IQueryable. See the [repository document](Repositories.md) for more. - -### Other Services - -* `DatabaseBlobProvider` is the main service that implements the database BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `DatabaseBlobProvider` class). \ No newline at end of file diff --git a/docs/en/Blob-Storing-File-System.md b/docs/en/Blob-Storing-File-System.md deleted file mode 100644 index 15ecda4ed7..0000000000 --- a/docs/en/Blob-Storing-File-System.md +++ /dev/null @@ -1,59 +0,0 @@ -# BLOB Storing File System Provider - -File System Storage Provider is used to store BLOBs in the local file system as standard files inside a folder. - -> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use the file system. - -## Installation - -Use the ABP CLI to add [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.FileSystem` package. -* Run `abp add-package Volo.Abp.BlobStoring.FileSystem` command. - -If you want to do it manually, install the [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringFileSystemModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). - -**Example: Configure to use the File System storage provider by default** - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseFileSystem(fileSystem => - { - fileSystem.BasePath = "C:\\my-files"; - }); - }); -}); -```` - -`UseFileSystem` extension method is used to set the File System Provider for a container and configure the file system options. - -> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. - -### Options - -* **BasePath** (string): The base folder path to store BLOBs. It is required to set this option. -* **AppendContainerNameToBasePath** (bool; default: `true`): Indicates whether to create a folder with the container name inside the base folder. If you store multiple containers in the same `BaseFolder`, leave this as `true`. Otherwise, you can set it to `false` if you don't like an unnecessarily deeper folder hierarchy. - -## File Path Calculation - -File System Provider organizes BLOB files inside folders and implements some conventions. The full path of a BLOB file is determined by the following rules by default: - -* It starts with the `BasePath` configured as shown above. -* Appends `host` folder if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). -* Appends `tenants/` folder if current tenant is not `null`. -* Appends the container's name if `AppendContainerNameToBasePath` is `true`. If container name contains `/`, this will result with nested folders. -* Appends the BLOB name. If the BLOB name contains `/` it creates folders. If the BLOB name contains `.` it will have a file extension. - -## Extending the File System BLOB Provider - -* `FileSystemBlobProvider` is the main service that implements the File System storage. You can inherit from this class and [override](Customizing-Application-Modules-Overriding-Services.md) methods to customize it. - -* The `IBlobFilePathCalculator` service is used to calculate the file paths. Default implementation is the `DefaultBlobFilePathCalculator`. You can replace/override it if you want to customize the file path calculation. \ No newline at end of file diff --git a/docs/en/Blob-Storing-Minio.md b/docs/en/Blob-Storing-Minio.md deleted file mode 100644 index 6e0d7626b7..0000000000 --- a/docs/en/Blob-Storing-Minio.md +++ /dev/null @@ -1,69 +0,0 @@ -# BLOB Storing Minio Provider - -BLOB Storing Minio Provider can store BLOBs in [MinIO Object storage](https://min.io/). - -> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Minio BLOB as the storage provider. - -## Installation - -Use the ABP CLI to add [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Minio` package. -* Run `abp add-package Volo.Abp.BlobStoring.Minio` command. - -If you want to do it manually, install the [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringMinioModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). - -**Example: Configure to use the minio storage provider by default** - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - container.UseMinio(minio => - { - minio.EndPoint = "your minio endPoint"; - minio.AccessKey = "your minio accessKey"; - minio.SecretKey = "your minio secretKey"; - minio.BucketName = "your minio bucketName"; - }); - }); -}); -```` - -> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. - -### Options - -* **EndPoint** (string): URL to object storage service. Please refer to MinIO Client SDK for .NET: https://docs.min.io/docs/dotnet-client-quickstart-guide.html -* **AccessKey** (string): Access key is the user ID that uniquely identifies your account. -* **SecretKey** (string): Secret key is the password to your account. -* **BucketName** (string): You can specify the bucket name in MinIO. If this is not specified, it uses the name of the BLOB container defined with the `BlobContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)).MinIO is the defacto standard for S3 compatibility, So MinIO has some **rules for naming bucket**. The [following rules](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html) apply for naming MinIO buckets: - * Bucket names must be between **3** and **63** characters long. - * Bucket names can consist only of **lowercase** letters, numbers, dots (.), and hyphens (-). - * Bucket names must begin and end with a letter or number. - * Bucket names must not be formatted as an IP address (for example, 192.168.5.4). - * Bucket names can't begin with **xn--** (for buckets created after February 2020). - * Bucket names must be unique within a partition. - * Buckets used with Amazon S3 Transfer Acceleration can't have dots (.) in their names. For more information about transfer acceleration, see Amazon S3 Transfer Acceleration. -* **WithSSL** (bool): Default value is `false`,Chain to MinIO Client object to use https instead of http. -* **CreateContainerIfNotExists** (bool): Default value is `false`, If a bucket does not exist in minio, `MinioBlobProvider` will try to create it. - - -## Minio Blob Name Calculator - -Minio Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: - -* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). -* Appends `tenants/` string if current tenant is not `null`. -* Appends the BLOB name. - -## Other Services - -* `MinioBlobProvider` is the main service that implements the Minio BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `MinioBlobProvider` class). -* `IMinioBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultMinioBlobNameCalculator` by default. diff --git a/docs/en/Blob-Storing.md b/docs/en/Blob-Storing.md deleted file mode 100644 index 82c2a861ab..0000000000 --- a/docs/en/Blob-Storing.md +++ /dev/null @@ -1,308 +0,0 @@ -# BLOB Storing - -It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. [BLOB](https://en.wikipedia.org/wiki/Binary_large_object)s, into a **storage**. For example, you may want to save user profile pictures. - -A BLOB is a typically **byte array**. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be options. - -The ABP Framework provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits; - -* You can **easily integrate** to your favorite BLOB storage provides with a few lines of configuration. -* You can then **easily change** your BLOB storage without changing your application code. -* If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored. - -ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](Multi-Tenancy.md). - -## BLOB Storage Providers - -The ABP Framework has already the following storage provider implementations: - -* [File System](Blob-Storing-File-System.md): Stores BLOBs in a folder of the local file system, as standard files. -* [Database](Blob-Storing-Database.md): Stores BLOBs in a database. -* [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/). -* [Aliyun](Blob-Storing-Aliyun.md): Stores BLOBs on the [Aliyun Storage Service](https://help.aliyun.com/product/31815.html). -* [Minio](Blob-Storing-Minio.md): Stores BLOBs on the [MinIO Object storage](https://min.io/). -* [Aws](Blob-Storing-Aws.md): Stores BLOBs on the [Amazon Simple Storage Service](https://aws.amazon.com/s3/). - -More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](Blob-Storing-Custom-Provider.md) and [contribute](Contribution/Index.md) to the ABP Framework. - -Multiple providers **can be used together** by the help of the **container system**, where each container can uses a different provider. - -> BLOB storing system can not work unless you **configure a storage provider**. Refer to the linked documents for the storage provider configurations. - -## Installation - -[Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring) is the main package that defines the BLOB storing services. You can use this package to use the BLOB Storing system without depending a specific storage provider. - -Use the ABP CLI to add this package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), if you haven't installed it. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring` package. -* Run `abp add-package Volo.Abp.BlobStoring` command. - -If you want to do it manually, install the [Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## The IBlobContainer - -`IBlobContainer` is the main interface to store and read BLOBs. Your application may have multiple containers and each container can be separately configured. But, there is a **default container** that can be simply used by [injecting](Dependency-Injection.md) the `IBlobContainer`. - -**Example: Simply save and read bytes of a named BLOB** - -````csharp -using System.Threading.Tasks; -using Volo.Abp.BlobStoring; -using Volo.Abp.DependencyInjection; - -namespace AbpDemo -{ - public class MyService : ITransientDependency - { - private readonly IBlobContainer _blobContainer; - - public MyService(IBlobContainer blobContainer) - { - _blobContainer = blobContainer; - } - - public async Task SaveBytesAsync(byte[] bytes) - { - await _blobContainer.SaveAsync("my-blob-1", bytes); - } - - public async Task GetBytesAsync() - { - return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1"); - } - } -} -```` - -This service saves the given bytes with the `my-blob-1` name and then gets the previously saved bytes with the same name. - -> A BLOB is a named object and **each BLOB should have a unique name**, which is an arbitrary string. - -`IBlobContainer` can work with `Stream` and `byte[]` objects, which will be detailed in the next sections. - -### Saving BLOBs - -`SaveAsync` method is used to save a new BLOB or replace an existing BLOB. It can save a `Stream` by default, but there is a shortcut extension method to save byte arrays. - -`SaveAsync` gets the following parameters: - -* **name** (string): Unique name of the BLOB. -* **stream** (Stream) or **bytes** (byte[]): The stream to read the BLOB content or a byte array. -* **overrideExisting** (bool): Set `true` to replace the BLOB content if it does already exists. Default value is `false` and throws `BlobAlreadyExistsException` if there is already a BLOB in the container with the same name. - -### Reading/Getting BLOBs - -* `GetAsync`: Only gets a BLOB name and returns a `Stream` object that can be used to read the BLOB content. Always **dispose the stream** after using it. This method throws exception, if it can not find the BLOB with the given name. -* `GetOrNullAsync`: In opposite to the `GetAsync` method, this one returns `null` if there is no BLOB found with the given name. -* `GetAllBytesAsync`: Returns a `byte[]` instead of a `Stream`. Still throws exception if can not find the BLOB with the given name. -* `GetAllBytesOrNullAsync`: In opposite to the `GetAllBytesAsync` method, this one returns `null` if there is no BLOB found with the given name. - -### Deleting BLOBs - -`DeleteAsync` method gets a BLOB name and deletes the BLOB data. It doesn't throw any exception if given BLOB was not found. Instead, it returns a `bool` indicating that the BLOB was actually deleted or not, if you care about it. - -### Other Methods - -* `ExistsAsync` method simply checks if there is a BLOB in the container with the given name. - -### About Naming the BLOBs - -There is not a rule for naming the BLOBs. A BLOB name is just a string that is unique per container (and per tenant - see the "*Multi-Tenancy*" section). However, different storage providers may conventionally implement some practices. For example, the [File System Provider](Blob-Storing-File-System.md) use directory separators (`/`) and file extensions in your BLOB name (if your BLOB name is `images/common/x.png` then it is saved as `x.png` in the `images/common` folder inside the root container folder). - -## Typed IBlobContainer - -Typed BLOB container system is a way of creating and managing **multiple containers** in an application; - -* **Each container is separately stored**. That means the BLOB names should be unique in a container and two BLOBs with the same name can live in different containers without effecting each other. -* **Each container can be separately configured**, so each container can use a different storage provider based on your configuration. - -To create a typed container, you need to create a simple class decorated with the `BlobContainerName` attribute: - -````csharp -using Volo.Abp.BlobStoring; - -namespace AbpDemo -{ - [BlobContainerName("profile-pictures")] - public class ProfilePictureContainer - { - - } -} -```` - -> If you don't use the `BlobContainerName` attribute, ABP Framework uses the full name of the class (with namespace), but it is always recommended to use a container name which is stable and does not change even if you rename the class. - -Once you create the container class, you can inject `IBlobContainer` for your container type. - -**Example: An [application service](Application-Services.md) to save and read profile picture of the [current user](CurrentUser.md)** - -````csharp -[Authorize] -public class ProfileAppService : ApplicationService -{ - private readonly IBlobContainer _blobContainer; - - public ProfileAppService(IBlobContainer blobContainer) - { - _blobContainer = blobContainer; - } - - public async Task SaveProfilePictureAsync(byte[] bytes) - { - var blobName = CurrentUser.GetId().ToString(); - await _blobContainer.SaveAsync(blobName, bytes); - } - - public async Task GetProfilePictureAsync() - { - var blobName = CurrentUser.GetId().ToString(); - return await _blobContainer.GetAllBytesOrNullAsync(blobName); - } -} -```` - -`IBlobContainer` has the same methods with the `IBlobContainer`. - -> It is a good practice to **always use a typed container while developing re-usable modules**, so the final application can configure the provider for your container without effecting the other containers. - -### The Default Container - -If you don't use the generic argument and directly inject the `IBlobContainer` (as explained before), you get the default container. Another way of injecting the default container is using `IBlobContainer`, which returns exactly the same container. - -The name of the default container is `default`. - -### Named Containers - -Typed containers are just shortcuts for named containers. You can inject and use the `IBlobContainerFactory` to get a BLOB container by its name: - -````csharp -public class ProfileAppService : ApplicationService -{ - private readonly IBlobContainer _blobContainer; - - public ProfileAppService(IBlobContainerFactory blobContainerFactory) - { - _blobContainer = blobContainerFactory.Create("profile-pictures"); - } - - //... -} -```` - -## IBlobContainerFactory - -`IBlobContainerFactory` is the service that is used to create the BLOB containers. One example was shown above. - -**Example: Create a container by name** - -````csharp -var blobContainer = blobContainerFactory.Create("profile-pictures"); -```` - -**Example: Create a container by type** - -````csharp -var blobContainer = blobContainerFactory.Create(); -```` - -> You generally don't need to use the `IBlobContainerFactory` since it is used internally, when you inject a `IBlobContainer` or `IBlobContainer`. - -## Configuring the Containers - -Containers should be configured before using them. The most fundamental configuration is to **select a BLOB storage provider** (see the "*BLOB Storage Providers*" section above). - -`AbpBlobStoringOptions` is the [options class](Options.md) to configure the containers. You can configure the options inside the `ConfigureServices` method of your [module](Module-Development-Basics.md). - -### Configure a Single Container - -````csharp -Configure(options => -{ - options.Containers.Configure(container => - { - //TODO... - }); -}); -```` - -This example configures the `ProfilePictureContainer`. You can also configure by the container name: - -````csharp -Configure(options => -{ - options.Containers.Configure("profile-pictures", container => - { - //TODO... - }); -}); -```` - -### Configure the Default Container - -````csharp -Configure(options => -{ - options.Containers.ConfigureDefault(container => - { - //TODO... - }); -}); -```` - -> There is a special case about the default container; If you don't specify a configuration for a container, it **fallbacks to the default container configuration**. This is a good way to configure defaults for all containers and specialize configuration for a specific container when needed. - -### Configure All Containers - -````csharp -Configure(options => -{ - options.Containers.ConfigureAll((containerName, containerConfiguration) => - { - //TODO... - }); -}); -```` - -This is a way to configure all the containers. - -> The main difference from configuring the default container is that `ConfigureAll` overrides the configuration even if it was specialized for a specific container. - -## Multi-Tenancy - -If your application is set as multi-tenant, the BLOB Storage system **works seamlessly with the [multi-tenancy](Multi-Tenancy.md)**. All the providers implement multi-tenancy as a standard feature. They **isolate BLOBs** of different tenants from each other, so they can only access to their own BLOBs. It means you can use the **same BLOB name for different tenants**. - -If your application is multi-tenant, you may want to control **multi-tenancy behavior** of the containers individually. For example, you may want to **disable multi-tenancy** for a specific container, so the BLOBs inside it will be **available to all the tenants**. This is a way to share BLOBs among all tenants. - -**Example: Disable multi-tenancy for a specific container** - -````csharp -Configure(options => -{ - options.Containers.Configure(container => - { - container.IsMultiTenant = false; - }); -}); -```` - -> If your application is not multi-tenant, no worry, it works as expected. You don't need to configure the `IsMultiTenant` option. - -## Extending the BLOB Storing System - -Most of the times, you won't need to customize the BLOB storage system except [creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md). However, you can replace any service (injected via [dependency injection](Dependency-Injection.md)), if you need. Here, some other services not mentioned above, but you may want to know: - -* `IBlobProviderSelector` is used to get a `IBlobProvider` instance by a container name. Default implementation (`DefaultBlobProviderSelector`) selects the provider using the configuration. -* `IBlobContainerConfigurationProvider` is used to get the `BlobContainerConfiguration` for a given container name. Default implementation (`DefaultBlobContainerConfigurationProvider`) gets the configuration from the `AbpBlobStoringOptions` explained above. - -## BLOB Storing vs File Management System - -Notice that BLOB storing is not a file management system. It is a low level system that is used to save, get and delete named BLOBs. It doesn't provide a hierarchical structure like directories, you may expect from a typical file system. - -If you want to create folders and move files between folders, assign permissions to files and share files between users then you need to implement your own application on top of the BLOB Storage system. - -## See Also - -* [Creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md) diff --git a/docs/en/Blog-Posts/2018-09-24-Announcement/Post.md b/docs/en/Blog-Posts/2018-09-24-Announcement/Post.md deleted file mode 100644 index dc6f5fa3e8..0000000000 --- a/docs/en/Blog-Posts/2018-09-24-Announcement/Post.md +++ /dev/null @@ -1,174 +0,0 @@ -### Introduction -For a while, we were working to design a new major version of the ASP.NET Boilerplate framework. Now, it’s time to share it with the community. We are too excited and we believe that you are too. - -#### Naming -The name of the framework remains same, except we will call it only as “ABP” instead of “ASP.NET Boilerplate”. Because, the “boilerplate” word leads to misunderstandings and does not reflect that it is a framework (instead of some boilerplate code). We continue to use the “ABP” name since it’s the successor of the current ASP.NET Boilerplate framework, except it’s a rewrite. - -### How To Start - -We have created a startup template. You can just create a new project from https://abp.io/Templates and start your development. For more information, visit [abp.io](https://abp.io). - -### Why A Complete Rewrite? -Why we spent our valuable time to rewrite it from scratch instead of incremental changes and improvements. Why? - -#### ASP.NET Core -When we first introduced the ABP framework, it was 2013 (5 years ago)! There was no .Net Core & ASP.NET Core and there was no Angular2+. They were all developed from scratch after ABP’s release. - -ASP.NET Core introduced many built-in solutions (extension libraries) for dependency injection, logging, caching, localization, configuration and so on. These are actually independent from the ASP.NET Core and usable for any type of application. - -We were using 3rd-party libraries and our own solutions for these requirements. We immediately integrated to ASP.NET Core features once they were released. But that was an integration, instead of building the ABP framework on top of these extension libraries. For instance, current ASP.NET Boilerplate still depends on Castle Windsor for dependency injection even it’s integrated to ASP.NET Core’s DI system. - -We wanted to depend on these new extension libraries instead of 3rd-party and custom solutions and this changes fundamental structures of the framework. - -#### Self Modularization -While current ABP is already modular itself and consists of dozens of packages, we still wanted to split the functionalities to more fine grained nuget packages. - -For example, the core Abp package contains many features like DDD classes, auditing, authorization, background jobs, event bus, json serialization, localization, multi-tenancy, threading, timing and so on… We wanted to split all these functionality into their own packages and make them optional. - -#### Dropping Support for Legacy Technologies -Yes, the new ABP framework will not support ASP.NET MVC 5.x, Entity Framework 6.x and other legacy technologies. - -These legacy technologies are maintained by Microsoft but no new feature is being added. So, if you are still using these technologies, you can continue with the current ASP.NET Boilerplate framework. We will continue to maintain it, fix bugs and will add new features. - -Dropping support for these legacy libraries will improve our development speed (since we currently duplicate our work for some features) and concentrate on the .Net Core & ASP.NET Core. - -The new ABP framework will be based on .net standard. So, it’s still possible to use full .net framework or .net core with the new ABP framework. - -### Goals -We have learnt much from the community and had experience of developing the current ASP.NET Boilerplate framework. New ABP framework has significant and exciting goals. - -#### Application Modularity -The first goal is to provide a good infrastructure to develop application modules. We think a module as a set of application features with its own database, its own entities, services, APIs, UI pages, components and so on. - -We will create a module market which will contain free & paid application modules. You will also be able to publish your own modules on the market. More information will be coming soon. - -#### Microservices -We are designing the new ABP framework to be ready to develop microservices and communicate them to each other. - -We are designing application modules so that they can be separately deployable as microservices or they can be embedded into a monolithic application. - -We are creating a [specification / best practice documentation](https://github.com/abpframework/abp/blob/master/docs/Best-Practices/Index.md) for that. - -#### Theming and UI Composition -The new ABP framework will provide a theming infrastructure based on the latest Twitter Bootstrap 4.x. We developed a basic theme that only uses the plain Bootstrap 4.x styling. It’s free and open source. We are also developing premium & paid themes. - -UI Composition is one of the main goals. For this purpose, theme system will provide menus, toolbars and other extensible areas to allow other modules to contribute. - -#### ORM/Database Independence & MongoDB Integration -While current ASP.NET Boilerplate framework has implemented the repository pattern for ORM/Database independence, identity integration module (Abp.Zero* packages) has never worked well with ORMs other than EF. - -With the new ABP framework, the ultimate goal is completely abstract underlying data store system and develop modules EF Core independent. - -We embrace the MongoDB as a first-class citizen database and designing entities and repositories without any relational database or ORM assumption. - -#### More Extensibility -New ABP framework provides more extensibility points and overriding capabilities for built-in services. - -### Some Features -In this section, I will introduce some exciting new features of the new ABP framework. - -#### Bootstrap Tag Helpers -We are creating a library to wrap twitter bootstrap 4.x elements/components into tag helpers. Example: - -````C# - - - - Card title - -

- This is a sample card component built by ABP bootstrap - card tag helper. ABP has tag helper wrappers for most of - the bootstrap components. -

-
- Go somewhere → -
-
-```` - -"abp-*" tags are ABP tag helpers to simplify writing HTML for Bootstrap 4.x. - -#### Dynamic Forms -Dynamic forms tag helper allows you to dynamically create forms for given model classes. Example: - -````C# - -```` - -Output: - -![dynamic-forms](dynamic-forms.png) - -It currently supports most used input types and more in the development. - -#### Virtual File System -Virtual File System allows you to embed views, pages, components, javascript, css, json and other type of files into your module assembly/package (dll) and use your assembly in any application. Your virtual files behave just like physical files in the containing application with complete ASP.NET Core Integration. - -Read more [about the Virtual File System](https://medium.com/volosoft/designing-modularity-on-asp-net-core-virtual-file-system-2dd2cc2078bd) and see [its documentation](https://github.com/abpframework/abp/blob/master/docs/Virtual-File-System.md). - -#### Dynamic Bundling & Minification System -Dynamic bundling & minification system works on the virtual file system and allows modules to create, modify and contribute to bundles in a modular, dynamic and powerful way. An example: - -````C# - - - - - -```` - -This code creates a new style bundle on the fly by including bootstrap (and its dependencies if there are) and two more css files. These files are bundled & minified on production environment, but will be added individually on the development environment. - -See [the documentation](https://github.com/abpframework/abp/blob/master/docs/UI/AspNetCore/Bundling-Minification.md) for more. - -#### Distributed Event Bus -In current ABP, there is an IEventBus service to trigger and handle events inside the application. In addition to this local event bus, we are creating a distributed event bus abstraction (and RabbitMQ integration) to implement distributed messaging patterns. - -#### Dynamic C# HTTP Client Proxies -ABP was already creating dynamic javascript proxies for all HTTP APIs. This feature does also exists in the new ABP framework. In addition, it now can create dynamic C# proxies for all HTTP APIs. - -### Future Works -All the stuffs mentioned above are already in development. However, we haven’t started some concepts yet. - -#### Single Page Applications -We designed the new framework SPAs in mind. However, we haven’t tried it with any SPA framework and we haven’t prepared a startup template for it yet. - -### What About ASP.NET Boilerplate (Current Version) and ASP.NET Zero? - -We have dedicated development & support teams actively working on the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) and [ASP.NET Zero](https://aspnetzero.com/) projects. These projects have a big community and we are also getting contributions from the community. - -We will continue to make enhancements, add new features and fix bugs for these projects for a long time. So, you can safely continue to use them. - -### Is New ABP Production Ready? -No, not yet. Our first goal is to make fundamental features stable then incrementally complete other features. - -We will frequently release new versions and every new version will probably have breaking changes. We will write breaking changes on the release notes. - -We currently define it experimental. But we hope that this will not continue for a long time. We can not declare a date yet, follow our releases. - -### Packages & Versioning -New ABP framework will start with v1.0 instead of following current ASP.NET Boilerplate's version to reflect the fact that it’s a rewrite. - -We will frequently [release](https://github.com/abpframework/abp/releases) it. You can expect many breaking changes until v1.0. Starting with the v1.0, we will pay attention to not introduce breaking changes in 1.x releases. - -Current ABP’s package names start with [Abp](https://www.nuget.org/packages/Abp) prefix (like Abp.EntityFrameworkCore). New package names start with [Volo.Abp](https://www.nuget.org/packages/Volo.Abp.Core) prefix (like Volo.Abp.EntityFrameworkCore). - -### Which One Should I Start With? -If you are creating a new project, we suggest to continue with the current ASP.NET Boilerplate framework since it’s very mature, feature rich and production ready. - -If you are open to breaking changes and want to have experience on the new framework, you can start with the new ABP. We don’t suggest it yet for projects with close deadlines and go to the production in a short term. - -### Contribution -Just like the current ABP framework, the new framework is available for your contribution. - -* You can send pull requests for code or documentation. -* You can write blog posts or tutorials about it. -* You can try it and share your experiences. -* You can create enhancement and feature requests. -* You can report bugs and other issues. - -### Communication / Links -* **Official web site**: [abp.io](https://abp.io) -* **Github**: [github.com/abpframework](https://github.com/abpframework) -* **Twitter**: [@abpframework](https://twitter.com/abpframework) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2018-09-24-Announcement/dynamic-forms.png b/docs/en/Blog-Posts/2018-09-24-Announcement/dynamic-forms.png deleted file mode 100644 index 22911d4b72..0000000000 Binary files a/docs/en/Blog-Posts/2018-09-24-Announcement/dynamic-forms.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2019-02-22/Post.md b/docs/en/Blog-Posts/2019-02-22/Post.md deleted file mode 100644 index b27a50ec05..0000000000 --- a/docs/en/Blog-Posts/2019-02-22/Post.md +++ /dev/null @@ -1,54 +0,0 @@ -# Microservice Demo, Projects Status and Road Map - -After [the first announcement](https://blog.abp.io/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project. - -## Microservice Demo Solution - -One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](https://abp.io/documents/abp/latest/Microservice-Architecture). - -We've been working to develop a microservice solution demo. Initial version was completed and [documented](https://abp.io/documents/abp/latest/Samples/Microservice-Demo). This sample solution aims to demonstrate a simple yet complete microservice solution; - -- Has multiple, independent, self-deployable **microservices**. -- Multiple **web applications**, each uses a different API gateway. -- Has multiple **gateways** / BFFs (Backend for Frontends) developed using the [Ocelot](https://github.com/ThreeMammals/Ocelot) library. -- Has an **authentication service** developed using the [IdentityServer](https://identityserver.io/) framework. It's also a SSO (Single Sign On) application with necessary UIs. -- Has **multiple databases**. Some microservices has their own database while some services/applications shares a database (to demonstrate different use cases). -- Has different types of databases: **SQL Server** (with **Entity Framework Core** ORM) and **MongoDB**. -- Has a **console application** to show the simplest way of using a service by authenticating. -- Uses [Redis](https://redis.io/) for **distributed caching**. -- Uses [RabbitMQ](https://www.rabbitmq.com/) for service-to-service **messaging**. -- Uses [Docker](https://www.docker.com/) & [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications. -- Uses [Elasticsearch](https://www.elastic.co/products/elasticsearch) & [Kibana](https://www.elastic.co/products/kibana) to store and visualize the logs (written using [Serilog](https://serilog.net/)). - -See [its documentation](https://abp.io/documents/abp/latest/Samples/Microservice-Demo) for a detailed explanation of the solution. - -## Improvements/Features - -We've worked on so many features including **distributed event bus** (with RabbitMQ integration), **IdentityServer4 integration** and enhancements for almost all features. We are continuously refactoring and adding tests to make the framework more stable and production ready. It is [rapidly growing](https://github.com/abpframework/abp/graphs/contributors). - -## Road Map - -There are still too much work to be done before the first stable release (v1.0). You can see [prioritized backlog items](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog) on the GitHub repo. - -According to our estimation, we have planned to release v1.0 in Q2 of 2019 (probably in May or June). So, not too much time to wait. We are also very excited for the first stable release. - -We will also work on [the documentation](https://abp.io/documents/abp/latest) since it is far from complete now. - -First release may not include a SPA template. However, we want to prepare a simple one if it can be possible. Haven't decided yet about the SPA framework. Alternatives: **Angular, React and Blazor**. Please write your thought as a comment to this post. - -## Chinese Web Site - -There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming). - -## NDC {London} 2019 - -It was a pleasure to be in [NDC {London}](https://ndc-london.com/) 2019 as a partner. We've talked to many developers about the current ASP.NET Boilerplate and the ABP vNext and we got good feedbacks. - -We also had a chance to talk with [Scott Hanselman](https://twitter.com/shanselman) and [Jon Galloway](https://twitter.com/jongalloway). They visited our booth and we talked about the ideas for ABP vNext. They liked features, approaches and the goal of new ABP framework. See some photos and comments on twitter: - -![scott-and-jon](scott-and-jon.png) - -## Follow It - -* You can star and follow the **GitHub** repository: https://github.com/abpframework/abp -* You can follow the official **Twitter** account for news: https://twitter.com/abpframework \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png b/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png deleted file mode 100644 index 79ad21aee7..0000000000 Binary files a/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2019-06-19 v0_18_Release/Post.md b/docs/en/Blog-Posts/2019-06-19 v0_18_Release/Post.md deleted file mode 100644 index b714565f4b..0000000000 --- a/docs/en/Blog-Posts/2019-06-19 v0_18_Release/Post.md +++ /dev/null @@ -1,89 +0,0 @@ -# ABP CLI, New Templates & Other Features with the v0.18 Release - -ABP v0.18 has been released with [80+ issues](https://github.com/abpframework/abp/milestone/16?closed=1) resolved and [550+ commits](https://github.com/abpframework/abp/compare/0.17.0.0...0.18.0) pushed. - -## Web Site Changes - -[abp.io](https://abp.io) web site is **completely renewed** to highlight the goals and important features of the ABP framework. Document & blog URLs are also changed: - -- `abp.io/documents` moved to [docs.abp.io](https://docs.abp.io). -- `abp.io/blog` moved to [blog.abp.io](https://blog.abp.io). - -## ABP CLI - -ABP CLI (Command Line Interface) is a new global command line tool to perform some common operations for ABP based solutions. Main functions are; - -* **Creating a new application** or module project. -* **Adding a new module** to an application. -* **Updating** all ABP related packages in a solution. - -ABP CLI is now the preferred way to create a new project, while you can still download a new project from the [get started](https://abp.io/get-started) page. - -### Usage - -Install the ABP CLI using a command line window: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Create a new application: - -````bash -abp new Acme.BookStore -```` - -Add a module to an application: - -````bash -abp add-module Volo.Blogging -```` - -Update all ABP related packages in a solution: - -````bash -abp update -```` - -See [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for details. - -## New Templates - -In this release, we've renewed all startup templates. The main goal is to provide better startup templates based on Domain Driven Design layers those also allow to create tiered solutions (where Web and API layers can be physically separated). It also includes unit & integration test projects separated for different layers. - -The image below shows the new startup project for an MVC application. - -![mvc-template-solution](mvc-template-solution.png) - -See the [startup templates document](https://docs.abp.io/en/abp/latest/Startup-Templates/Index) for details. - -## Change Logs - -Here are some other features and enhancements coming with this release: - -* New [Volo.Abp.Dapper](https://www.nuget.org/packages/Volo.Abp.Dapper) package. -* New [Volo.Abp.Specifications](https://www.nuget.org/packages/Volo.Abp.Specifications) package. -* New data seed system with `IDataSeeder` service & `IDataSeedContributor` interface to allow a modular initial data seed system. -* Improved MemoryDB implementation to serialize/deserialize objects stored in memory, so it provides more realistic infrastructure for mocking database in unit/integration tests. -* Added multi-language support for the docs module. Used it for the [ABP documentation](https://docs.abp.io). - -See the [GitHub Release Notes](https://github.com/abpframework/abp/releases/tag/0.18.0) for all features, enhancements & bugfixes in this release. - -## Road Map - -One thing related to the ABP v1.0 release is .NET Core / ASP.NET Core 3.0 release. According to the [.NET Core road map](https://github.com/dotnet/core/blob/master/roadmap.md), 3.0 release has been scheduled for September 2019. - -ASP.NET Core comes with big changes and features. As a big breaking change, it will [only run on .NET Core](https://github.com/aspnet/Announcements/issues/324) (dropping .net standard support), so it will not work with full .net framework anymore. - -We had declared to release v1.0 in 2019 Q2. The main works we should do for v1.0 are; - -* Fill the gaps in current features. -* Refactor & improve the current APIs. -* Fix known bugs. -* Complete the documentation & tutorials. - -In addition to the work we should do, we are also considering to wait ASP.NET Core 3.0 release. Because, if we release ABP v1.0 before ASP.NET Core 3.0, we will have to release ABP v2.0 again in a short time and drop v1.0 support. So, we are considering to publish ABP v1.0 RC with ASP.NET Core 3.0 RC and align the final release date with Microsoft. - -## Want to Contribute? - -Thanks to the community for their support for ABP development. It is very appreciated. If you also want to contribute, see [this guide](https://github.com/abpframework/abp/blob/master/docs/en/Contribution/Index.md) as the beginning. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-06-19 v0_18_Release/mvc-template-solution.png b/docs/en/Blog-Posts/2019-06-19 v0_18_Release/mvc-template-solution.png deleted file mode 100644 index ce821eba72..0000000000 Binary files a/docs/en/Blog-Posts/2019-06-19 v0_18_Release/mvc-template-solution.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md b/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md deleted file mode 100644 index feeb3c2bb6..0000000000 --- a/docs/en/Blog-Posts/2019-08-16 v0_19_Release/Post.md +++ /dev/null @@ -1,46 +0,0 @@ -# ABP v0.19 Release With New Angular UI - -ABP v0.19 has been released with [90 issues](https://github.com/abpframework/abp/milestone/17?closed=1) resolved and [650+ commits](https://github.com/abpframework/abp/compare/0.18.1...0.19.0) pushed. - -## New Features - -### Angular UI - -Finally, ABP has a **SPA UI** option with the latest [Angular](https://angular.io/) framework. Angular integration was not simply creating a startup template. - -* Created a base infrastructure to handle ABP's modularity, theming and some other features. This infrastructure has been deployed as [NPM packages](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages). -* Created Angular UI packages for the modules like account, identity and tenant-management. -* Created a minimal startup template that authenticates using IdentityServer and uses the ASP.NET Core backend. This template uses the packages mentioned above. -* Worked on the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) and the [download page](https://abp.io/get-started) to be able to generate projects with the new UI option. -* Created a [tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG) to jump start with the new UI option. - -We've created the template, document and infrastructure based on the latest Angular tools and trends: - -* Uses [NgBootstrap](https://ng-bootstrap.github.io/) as the UI component library. You can use your favorite library, but pre-built modules work with these libraries. -* Uses [NGXS](https://ngxs.gitbook.io/ngxs/) as the state management library. - -Angular was the first SPA UI option, but it is not the last. After v1.0 release, we will start to work on a second UI option. Not decided yet, but candidates are Blazor, React and Vue.js. Waiting your feedback. You can thumb up using the following issues: - -* [Blazor](https://github.com/abpframework/abp/issues/394) -* [Vue.js](https://github.com/abpframework/abp/issues/1168) -* [React](https://github.com/abpframework/abp/issues/1638) - -### Widget System - -[Widget system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Widgets) allows to **define and reuse** widgets for ASP.NET Core MVC applications. Widgets may have their own script and style resources and dependencies to 3rd-party libraries which are managed by the ABP framework. - -### Others - -We've solved many bugs and worked on existing features based on the community feedback. See the [v0.19 milestone](https://github.com/abpframework/abp/milestone/17?closed=1) for all the closed issues. - -## Road Map - -We had decided to wait for **ASP.NET Core 3.0** final release. Microsoft has announced to released it at [.NET Conf](https://www.dotnetconf.net/), between 23-25 September. - -We have planned to finalize our work and move to ASP.NET Core 3.0 (with preview or RC) before its release. Once Microsoft releases it, we will immediately start to upgrade and test with the final release. - -So, you can expect ABP **v1.0** to be released in the **first half of the October**. We are very excited and working hard on it. - -You can follow the progress from [the GitHub milestones](https://github.com/abpframework/abp/milestones). - -We will not add major features until v1.0. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-09-25 v0_21_Release/Post.md b/docs/en/Blog-Posts/2019-09-25 v0_21_Release/Post.md deleted file mode 100644 index d44ab5d7df..0000000000 --- a/docs/en/Blog-Posts/2019-09-25 v0_21_Release/Post.md +++ /dev/null @@ -1,21 +0,0 @@ -# ABP v0.21 Has Been Released based on the ASP.NET Core 3.0 - -Just one hour after Microsoft released it, ABP v0.21 [has been released](https://twitter.com/abpframework/status/1176185493119258624) based on the ASP.NET Core 3.0. - -v0.21 has no new feature. It just upgrades to the stable ASP.NET Core 3.0. Check [v0.20 release notes](https://github.com/abpframework/abp/releases/tag/0.20.0) for new features, enhancements and bug fixes. - -## About v1.0 - -ABP framework is getting closer to v1.0. We intent to release it in the middle of this October. In this time, we will test and document more. - -## .NET Conf 2019 - -Microsoft has lunched ASP.NET Core 3.0 in the .NET Conf 2019, a 3-days virtual conference. ABP's lead developer [Halil ibrahim Kalkan](https://twitter.com/hibrahimkalkan) has also talked in the conference to introduce the ABP framework. It was great to be a part of this important event. - -## Techorama Netherlands 2019 - -[Techorama NL](https://techorama.nl/) is one of the biggest conferences in Europe. This year, Volosoft is a sponsor of the conference and will have a booth to talk to software developers about the ABP framework and software development. Our booth wall will look like shown below: - -![volosoft-booth](volosoft-booth.png) - -If you are in the conference, come to out booth to talk about the ABP framework. We will also have nice swags for you :) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-09-25 v0_21_Release/volosoft-booth.png b/docs/en/Blog-Posts/2019-09-25 v0_21_Release/volosoft-booth.png deleted file mode 100644 index 3dca4409e4..0000000000 Binary files a/docs/en/Blog-Posts/2019-09-25 v0_21_Release/volosoft-booth.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2019-10-22 v1_0_Release/Post.md b/docs/en/Blog-Posts/2019-10-22 v1_0_Release/Post.md deleted file mode 100644 index ae7b30a1b0..0000000000 --- a/docs/en/Blog-Posts/2019-10-22 v1_0_Release/Post.md +++ /dev/null @@ -1,29 +0,0 @@ -# ABP v1.0 Has Been Finally Released! - -![banner](banner.jpg) - -Today is the big day! After ~3 years of continuous development, first stable ABP release, 1.0, has been released. Thanks to everyone contributed to the project or tried it so far. - -Start playing with the new ABP framework now: [abp.io/get-started](https://abp.io/get-started) - -![contribution-graph](D:\Github\abp\docs\en\Blog-Posts\2019-10-22 v1_0_Release\contribution-graph.png) - -Here, a few GitHub & NuGet statistics about the project: - -* 2,360 stars. -* 5,917 commits. -* 72 contributors. -* 1,136 issues were closed, 276 open. -* 566 PRs were closed, 5 open. -* 39 releases. -* 122,795 downloads on NuGet. - -There was an excellent demand even before the first release. - -## Road Map - -The first priority is to complete the documentation, since there are still a lot of missing documents for the framework features & the modules. Then we will continue to work on the issues on GitHub, based on the labeled priorities. - -See the [GitHub milestone items](https://github.com/abpframework/abp/milestones). - -ABP is a community-driven project. So, we are prioritizing the issues mostly based on the community feedback and demand. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-10-22 v1_0_Release/banner.jpg b/docs/en/Blog-Posts/2019-10-22 v1_0_Release/banner.jpg deleted file mode 100644 index c1fc4b42c1..0000000000 Binary files a/docs/en/Blog-Posts/2019-10-22 v1_0_Release/banner.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2019-10-22 v1_0_Release/contribution-graph.png b/docs/en/Blog-Posts/2019-10-22 v1_0_Release/contribution-graph.png deleted file mode 100644 index abb137846f..0000000000 Binary files a/docs/en/Blog-Posts/2019-10-22 v1_0_Release/contribution-graph.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/Post.md b/docs/en/Blog-Posts/2020-01-15 v2_0_Release/Post.md deleted file mode 100644 index d3a525c63f..0000000000 --- a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/Post.md +++ /dev/null @@ -1,164 +0,0 @@ -# ABP Framework v2.0 and the ABP Commercial - -ABP Framework v2.0 has been released in this week. This post explains why we have released an **early major version** and what is changed with version 2.0. - -In addition to the v2.0 release, we are excited to announce the **ABP Commercial**, which is a set of professional modules, tools, themes, and services built on top of the open-source ABP framework. - -## ABP Framework v2.0 - -### Why 2.0 instead of 1.2? - -It was planned to release v1.2 after the [v1.1.2](https://github.com/abpframework/abp/releases/tag/1.1.2) release. However, [it is reported](https://github.com/abpframework/abp/issues/2026) that v1.x has some **performance** and **stability** issues on Linux, especially when you deploy your application to **Linux** containers with **low CPU and memory** resources. - -We have investigated the problem deeply and have seen that the root cause of the problem was related to the implementation of **intercepting `async` methods**. Besides, there were some **`async` over `sync`** usages that effected the thread pool optimization. - -Finally, we **solved all the problems** with the great help of the **community**. But we also had some important **design decisions** which cause some **breaking changes** and we had to change the major version number of the framework because of the [semantic versioning](https://semver.org/). - -Most of the applications won't be affected by [the breaking changes](https://github.com/abpframework/abp/releases), or it will be trivial to make these necessary changes. - -### Breaking Changes - -#### Removed Some Sync APIs - -Some of the interceptors are required to use `async` APIs. When they intercept `sync` methods, they need to call `async` over `sync`. This eventually ends up with `async` over `sync` problem. That's why we have [removed some sync APIs](https://github.com/abpframework/abp/pull/2464). - -**`Async` over `sync`** pattern is a classical problem of `C#` when you need to **call an `async` method inside a `sync` method**. While there are some workarounds to this problem, they all have **disadvantages** and it is suggested to **not write** such code at all. You can find many documents related to this topic on the web. - -To avoid this problem, we have removed: - -- `sync` [repository](https://docs.abp.io/en/abp/latest/Repositories) methods (like `insert`, `update`, etc...), -- `sync` APIs of the [unit of work](https://docs.abp.io/en/abp/latest/Unit-Of-Work), -- `sync` APIs of the [background jobs](https://docs.abp.io/en/abp/latest/Background-Jobs), -- `sync` APIs of the [audit logging](https://docs.abp.io/en/abp/latest/Audit-Logging), -- some other rarely used `sync` APIs. - -If you get any compile error, just use the `async` versions of these APIs. - -#### Always Async! - -Beginning from the v2.0, the ABP framework assumes that you are writing your application code `async` first. Otherwise, some framework functionalities may not properly work. - -It is suggested to write `async` to all your [application services](https://docs.abp.io/en/abp/latest/Application-Services), [repository methods](https://docs.abp.io/en/abp/latest/Repositories), controller actions, page handlers. - -Even if your application service method doesn't need to be `async` , set it as `async` , because interceptors perform `async` operations (for authorization, unit of work, etc...). You can return `Task.Completed` from a method that doesn't make an `async` call. - -Example: - -````csharp -public Task GetValueAsync() -{ - //this method doesn't make any async call. - return Task.CompletedTask(42); -} -```` - -The example above normally doesn't need to be `async` because it doesn't perform an `async` call. However, making it `async` helps the ABP framework to run interceptors without `async` over sync calls. - -This rule doesn't force you to write every method `async` . This would not be good and would be tedious. It is only needed for the intercepted services (especially for [application services](https://docs.abp.io/en/abp/latest/Application-Services) and [repository methods](https://docs.abp.io/en/abp/latest/Repositories)) - -#### Other Breaking Changes - -See [the release notes](https://github.com/abpframework/abp/releases/tag/2.0.0) for the other breaking changes. Most of them will not affect your application code. - -### New Features - -This release also contains some new features and tens of enhancements: - -- [#2597](https://github.com/abpframework/abp/pull/2597) New `Volo.Abp.AspNetCore.Serilog` package. -- [#2526](https://github.com/abpframework/abp/issues/2526) Client-side validation for the dynamic `C#` client proxies. -- [#2374](https://github.com/abpframework/abp/issues/2374) `Async` background jobs. -- [#265](https://github.com/abpframework/abp/issues/265) Managing the application shutdown. -- [#2472](https://github.com/abpframework/abp/issues/2472) Implemented `DeviceFlowCodes` and `TokenCleanupService` for the `IdentityServer` module. - -See [the release notes](https://github.com/abpframework/abp/releases/tag/2.0.0) for the complete list of features, enhancements and bug fixes. - -### Documentation - -We have completed some missing documentation with the v2.0 release. In the following weeks, we will mostly focus on the documentation and tutorials. - -## ABP Commercial - -[ABP Commercial](https://commercial.abp.io/) is a set of professional **modules, tools, themes, and services** built on top of the open-source ABP framework. - -- It provides [professional modules](https://commercial.abp.io/modules) in addition to the ABP Framework's free & [open source modules](https://docs.abp.io/en/abp/latest/Modules/Index). -- It includes a beautiful a [UI theme](https://commercial.abp.io/themes) with 5 different styles. -- It provides the [ABP Suite](https://commercial.abp.io/tools/suite); A tool to assist your development to make you more productive. It currently can create full-stack CRUD pages in a few seconds by configuring your entity properties. More functionalities will be added over time. -- [Premium support](https://commercial.abp.io/support) for enterprise companies. - -In addition to these standard set of features, we will provide customer basis services. See the [commercial.abp.io](https://commercial.abp.io/) web site for other details. - -### ABP Framework vs the ABP Commercial - -The ABP Commercial **is not a paid version** of the ABP Framework. You can consider it as **set of additional benefits** for professional companies. You can use it to save your time and develop your product faster. - -ABP Framework is **open source & free** and will always be like that! - -As a principle, we build the main infrastructure as open-source and sell additional pre-built application features, themes, and tools. The main idea similar to the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) & the [ASP.NET Zero](https://aspnetzero.com/) products. - -Buying a commercial license saves your significant time and effort and you can focus on your own business, besides you get dedicated and high priority support. Also, you will be supporting the ABP core team since we are spending most of our time to develop, maintain and support the open-source ABP Framework. - -With the introduction of the ABP Commercial, now ABP becomes a platform. We call it as the **ABP.IO Platform** which consists of the open source ABP Framework and the ABP Commercial. - -### Demo - -If you are wondering how exactly looks like the ABP Commercial application startup template, you can easily [create a demo](https://commercial.abp.io/demo) and see it in action. The demo includes all the pre-built modules and the theme. - -Here, a screenshot from the IdentityServer management module UI: - -![abp-commercial-demo](abp-commercial-demo.png) - -This is another screenshot from a demo application using the material design style of the theme: - -![lepton-theme-material](lepton-theme-material.png) - -### Pricing - -You can build **unlimited projects/products**, sell to **unlimited customers**, host **unlimited servers** without any restriction. Pricing is mostly based on the **developer count**, **support level** and **source code** requirement. There are three main packages; - -- **Team license**: Includes all the modules, themes and tools. Allows developing your product with up to 3 developers. You can buy additional developer licenses. -- **Business license**: Allows downloading the source code of all the modules and the themes. Also, it includes 5 developer licenses by default. You can buy additional developer licenses. -- **Enterprise license**: Provides unlimited and private support in addition to the benefits of the business license. - -See the [pricing page](https://commercial.abp.io/pricing) for details. In addition to the standard packages, we are also providing custom services and custom licensing. [Contact us](https://commercial.abp.io/contact) if you have any questions. - -#### License Comparison - -The license price changes based on your developer count, support level and source-code access. - -##### The Source-Code - -Team license doesn't include the source-code of the pre-built modules & themes. It uses all these modules as **NuGet & NPM packages**. In this way, you can easily **get new features and bug fixes** by just updating the package dependencies. But you can't access their source-code. So you don't have the possibility to embed a module's source code into your application and freely change the source-code. - -Pre-built modules provide some level of **customization** and **extensibility** and allow you to override services, UI parts and so on. We are working on to make them much more customizable and extensible. If you don't need to make major changes in the pre-built modules, the team license will be ideal for you, because it is cheaper and allows you to easily get new features and bug fixes. - -Business and Enterprise licenses allow you to **download the source-code** of any module or the theme when you need it. They also use the same startup template with the team license, so all modules are used as `NuGet` & `NPM` packages by default. But in case of need, you can remove the package dependencies for a module and embed its source-code into your own solution to completely customize it. In this case, upgrading the module will not be as easy as before when a new version is available. You don't have to upgrade it, surely! But if you want, you should do it yourself using some merge tool or Git branch system. - -#### License Lifetime - -ABP Commercial license is **perpetual**, which means you can **use it forever** and continue to develop your applications. - -However, the following services are covered for one year: - -- Premium **support** ends after one year. You can continue to get community support. -- You can not get **updates** of the modules & the themes after one year. You can continue to use the last obtained version. You can even get bug fixes and enhancements for your current major version. -- You can use the **ABP Suite** tool for one year. - -If you want to continue to get these benefits, you can extend your license period. Renewing price is 20% less than the regular price. - -## NDC London 2020 - -Just like the [previous year](https://medium.com/volosoft/impressions-of-ndc-london-2019-f8f391bb7a9c), we are a partner of the famous software development conference: [NDC London](https://ndc-london.com/)! In the previous year, we were there with the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) & [ASP.NET Zero](https://aspnetzero.com/) theme: - -![ndc-london-volosoft](ndc-london-volosoft.png) - -This year, we will be focusing on the **ABP.IO Platform** (The Open Source ABP Framework and the ABP Commercial). Our booth wall will be like that: - -![ndc-london-volosoft](ndc-2020-volosoft-booth-wall.png) - -If you attend to the conference, remember to visit our booth. We would be glad to talk about the ABP platform features, goals and software development in general. - -### Would you like to meet the ABP Team? - -If you are in London and want to have a coffee with us, we will be available at February 1st afternoon. [@hibrahimkalkan](https://twitter.com/hibrahimkalkan) and [@ismcagdas](https://twitter.com/ismcagdas) will be there. - -Just write to info@abp.io if you want to meet :) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/abp-commercial-demo.png b/docs/en/Blog-Posts/2020-01-15 v2_0_Release/abp-commercial-demo.png deleted file mode 100644 index 9153adeb86..0000000000 Binary files a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/abp-commercial-demo.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/abp-io-abpcommercial-release.png b/docs/en/Blog-Posts/2020-01-15 v2_0_Release/abp-io-abpcommercial-release.png deleted file mode 100644 index 6d993b303c..0000000000 Binary files a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/abp-io-abpcommercial-release.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/lepton-theme-material.png b/docs/en/Blog-Posts/2020-01-15 v2_0_Release/lepton-theme-material.png deleted file mode 100644 index 279f4b2cf1..0000000000 Binary files a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/lepton-theme-material.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/ndc-2020-volosoft-booth-wall.png b/docs/en/Blog-Posts/2020-01-15 v2_0_Release/ndc-2020-volosoft-booth-wall.png deleted file mode 100644 index 428ecc284d..0000000000 Binary files a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/ndc-2020-volosoft-booth-wall.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/ndc-london-volosoft.png b/docs/en/Blog-Posts/2020-01-15 v2_0_Release/ndc-london-volosoft.png deleted file mode 100644 index afaed37a7a..0000000000 Binary files a/docs/en/Blog-Posts/2020-01-15 v2_0_Release/ndc-london-volosoft.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-03-19 v2_3_Release/Post.md b/docs/en/Blog-Posts/2020-03-19 v2_3_Release/Post.md deleted file mode 100644 index dd081f38e5..0000000000 --- a/docs/en/Blog-Posts/2020-03-19 v2_3_Release/Post.md +++ /dev/null @@ -1,142 +0,0 @@ -# ABP Framework v2.3.0 Has Been Released! - -In the days of **coronavirus**, we have released **ABP Framework v2.3** and this post will explain **what's new** with this release and **what we've done** in the last two weeks. - -## About the Coronavirus & Our Team - -**We are very sad** about the coronavirus case. As [Volosoft](https://volosoft.com/) team, we have **remote workers** working in their home in different countries. Beginning from the last week, we've **completely started to work remotely** from home including our main office employees. - -We believe in and pray for that the humanity will overcome this issue in a short time. - -## About the Release Cycle - -Beginning from the ABP v2.1.0, we have started to release feature versions once **in two weeks**, on Thursdays. This is the 3rd release after that decision and we see that it works fine for now and improved our agility. - -We will continue to release **feature versions** (like v2.4, v2.5) in every two weeks. In addition, we may release **hotfix versions** (like v2.3.1, v2.3.2) whenever needed. - -## What's New in ABP Framework v2.3.0 - -We've completed & merged **[104](https://github.com/abpframework/abp/milestone/30?closed=1) issues and pull requests** with **393 commits** in this two weeks development period. - -I will introduce some new features and enhancements introduced with this release. - -### React Native Mobile Application - -We have finally completed the **react native mobile application**. It currently allows you to **login**, manage your **users** and **tenants**. It utilizes the same setting, authorization and localization systems of the ABP Framework. - -A few screenshots from the application: - -![mobile-ui](react-native-ui.png) - -It doesn't have much functionality but it is a **perfect starting point** for your own mobile application since it is completely integrated to the backend and supports multi-tenancy. - -### Angular TypeScript Proxy Generator - -It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on he server side) and **model objects** (matches to [DTOs](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects) in the server side). - -In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced: - -* It generates a **big, single** .ts file which has some problems; - * It get **too large** when your application grows. - * It doesn't fit into the **[modular](https://docs.abp.io/en/abp/latest/Module-Development-Basics) approach** of the ABP framework. -* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually). -* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies. - -So, we've decided to create an ABP CLI command to automatically generate the typescript client proxies ([#2222](https://github.com/abpframework/abp/issues/2222)) for your REST API developed with the ABP Framework. - -It is easy to use. Just run the following command in the **root folder** of the angular application: - -````bash -abp generate-proxy -```` - -It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](https://docs.abp.io/en/abp/latest/CLI). - -### CRUD Application Services for Entities with Composite Keys - -` CrudAppService ` is a useful base class to create CRUD application services for your entities. But it doesn't support entities with **composite primary keys**. `AbstractKeyCrudAppService` is the new base class that is developed to support entities with composite primary keys. See [the documentation](https://docs.abp.io/en/abp/latest/Application-Services#abstractkeycrudappservice) for more. - -### Add Source Code of the Modules - -The application startup template comes with some [application modules](https://docs.abp.io/en/abp/latest/Modules/Index) **pre-installed** as **NuGet & NPM packages**. This have a few important advantages: - -* You can **easily [upgrade](https://docs.abp.io/en/abp/latest/CLI#update)** these modules when a new version is available. -* Your solution becomes **cleaner**, so you can focus on your own code. - -However, when you need to make **major customizations** for a depended module, it is not easy as its source code is in your applications. To solve this problem, we've introduces a new command to the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) that **replaces** NuGet packages with their **source code** in your solution. The usage is simple: - -````bash -abp add-module --with-source-code -```` - -This command adds a module with source code or replaces with its source code if it is already added as package references. - -> It is suggested to **save your changes** to your source control system before using this command since it makes a lot of changes in your source code. - -In addition, we've documented how to customize depended modules without changing their source code (see the section below). It is suggested to use modules as packages to easily upgrade them in the future. - -> Source code of the free modules are licensed under **MIT**, so you can freely change them and add into your solution. - -### Switch to Preview - -ABP Framework is rapidly evolving and we are frequently releasing new versions. However, if you want to follow it closer, you can use the **daily preview packages**. - -We've created an ABP CLI command to easily **update to the latest preview packages** for your solution. Run the following command in the root folder of your solution: - -````bash -abp switch-to-preview -```` - -It will change the versions of all ABP related NuGet and NPM packages. You can **switch back to the latest stable** when you want: - -````bash -abp switch-to-stable -```` - -See the [ABP CLI document](https://docs.abp.io/en/abp/latest/CLI#switch-to-preview) fore more. - -### Documentation Improvements - -#### Extending/Customizing Depended Application Modules - -We've created a huge documentation that explains how to customize a depended module without changing its source code. See [the documentation](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide). - -In addition to the documentation, we've revised all the modules ([#3166](https://github.com/abpframework/abp/issues/3166)) to make their services easily extensible & customizable. - -#### EF Core Migration Guide - -We've recently created a guide to explain the migration system that is used by the ABP startup templates. [This guide](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Migrations) also explains how to customize the migration structure, split your modules across multiple databases, reusing a module's table and son on. - -#### Migration from the ASP.NET Boilerplate - -If you have a solution built on the ASP.NET Boilerplate, we've [created a guide](https://docs.abp.io/en/abp/latest/AspNet-Boilerplate-Migration-Guide) that tries to help you if you want to migrate your solution to the new ABP Framework. - -### Some Other Features - -#### The Framework - -* Add `IRepository.GetAsync` and `IRepository.FindAsync` methods ([#3184](https://github.com/abpframework/abp/issues/3148)). - -#### Modules - -* Get password & email address of the admin while creating a new tenant, for the tenant management module ([#3088](https://github.com/abpframework/abp/issues/3088)). -* Elastic search integrated full text search for the docs module ([#2901](https://github.com/abpframework/abp/pull/2901)). -* New Quartz background worker module ([#2762](https://github.com/abpframework/abp/issues/2762)) - -#### Samples - -* Add multi-tenancy support to the microservice demo ([#3032](https://github.com/abpframework/abp/pull/3032)). - -See [the release notes](https://github.com/abpframework/abp/releases/tag/2.3.0) for all feature, enhancement and bugfixes. - -## What's Next? - -We have the following goals for the next few months: - -* Complete the **documentation and samples**, write more tutorials. -* Make the framework and existing modules more **customizable and extensible**. -* Integrate to **gRPC** & implement gRPC endpoint for pre-built modules ([#2882](https://github.com/abpframework/abp/issues/2882)). -* Create a **Blazor UI** for the ABP Framework & implement it for all the modules and startup templates ([#394](https://github.com/abpframework/abp/issues/394)). -* Add **new features** to pre-built modules and create new modules for the [ABP Commercial](https://commercial.abp.io/). - -See [the GitHub milestones](https://github.com/abpframework/abp/milestones) for details. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-03-19 v2_3_Release/abp-io-release-2-3-0-blog.png b/docs/en/Blog-Posts/2020-03-19 v2_3_Release/abp-io-release-2-3-0-blog.png deleted file mode 100644 index 8c0a2bf241..0000000000 Binary files a/docs/en/Blog-Posts/2020-03-19 v2_3_Release/abp-io-release-2-3-0-blog.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-03-19 v2_3_Release/react-native-ui.png b/docs/en/Blog-Posts/2020-03-19 v2_3_Release/react-native-ui.png deleted file mode 100644 index ab054e7cb0..0000000000 Binary files a/docs/en/Blog-Posts/2020-03-19 v2_3_Release/react-native-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md deleted file mode 100644 index c09b0eb75c..0000000000 --- a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md +++ /dev/null @@ -1,247 +0,0 @@ -# ABP Framework v2.7.0 Has Been Released! - -The **ABP Framework** & and the **ABP Commercial** v2.7 have been released. We hadn't created blog post for the 2.4, 2.4 and 2.6 releases, so this post will also cover **what's new** with these releases and **what we've done** in the last 2 months. - -## About the Release Cycle & Development - -Reminding that we had started to release a new minor feature version **in every two weeks**, generally on Thursdays. Our goal is to deliver new features as soon as possible. - -We've completed & merged hundreds of issues and pull requests with **1,300+ commits** in the last 7-8 weeks, only for the ABP Framework repository. Daily commit counts are constantly increasing: - -![github-contribution-graph](github-contribution-graph.png) - -ABP.IO Platform is rapidly growing and we are getting more and more contributions from the community. - -## What's New in the ABP Framework? - -### Object Extending System - -In the last few releases, we've mostly focused on providing ways to extend existing modules when you use them as NuGet/NPM Packages. - -The Object Extending System allows module developers to create extensible modules and allows application developers to customize and extend a module easily. - -For example, you can add two extension properties to the user entity of the identity module: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdate(options => - { - options.AddOrUpdateProperty("SocialSecurityNumber"); - options.AddOrUpdateProperty("IsSuperUser"); - } - ); -```` - -It is easy to define validation rules for the properties: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - options.Attributes.Add(new RequiredAttribute()); - options.Attributes.Add( - new StringLengthAttribute(32) { - MinimumLength = 6 - } - ); - }); -```` - -You can even write custom code to validate the property. It automatically works for the objects those are parameters of an application service, controller or a page. - -While extension properties of an entity are normally stored in a single JSON formatted field in the database table, you can easily configure to store a property as a table field using the EF Core mapping: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - options.MapEfCore(b => b.HasMaxLength(32)); - } - ); -```` - -See the [Object Extensions document](https://docs.abp.io/en/abp/latest/Object-Extensions) for details about this system. - -See also the [Customizing the Existing Modules](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide) guide to learn all the possible customization options. - -### Text Templating Package - -[Volo.Abp.TextTemplating](https://www.nuget.org/packages/Volo.Abp.TextTemplating) is a new package introduced with the v2.7.0. Previously, [Volo.Abp.Emailing](https://www.nuget.org/packages/Volo.Abp.Emailing) package had a similar functionality but it was limited, experimental and tightly coupled to the emailing. - -The new text templating package allows you to define text based templates those can be easily localized and reused. You can define layout templates and share the layout from other templates. - -We are currently using it for email sending. A module needs to send an email typically defines a template. Example: - -````xml -

{{L "PasswordReset"}}

- -

{{L "PasswordResetInfoInEmail"}}

- - -```` - -This is a typical password reset email template. - -* The template system is based on the open source [Scriban library](https://github.com/lunet-io/scriban). So it supports if conditions, loops and much more. -* `model` is used to pass data to the template (just like the ASP.NET Core MVC). -* `L` is a special function that localizes the given string. - -It is typical to use the same layout for all emails. So, you can define a layout template. This is the standard layout template comes with the framework: - -````xml - - - - - - - {{content}} - - -```` - -A layout should have a `{{content}}` area to render the child content (just like the `RenderBody()` in the MVC). - -It is very easy to override a template content by the final application to customize it. - -Whenever you need to render a template, use the `ITemplateRenderer` service by providing the template name and a model. See the [text templating documentation](https://docs.abp.io/en/abp/latest/Text-Templating) for details. We've even created a UI for the ABP Commercial (see the related section below). - -### Subscribing to the Exceptions - -ABP Framework's [exception handling system](https://docs.abp.io/en/abp/latest/Exception-Handling) automatically handles exceptions and returns an appropriate result to the client. In some cases, you may want to have a callback that is notified whenever an exception occurs. In this way, for example, you can send an email or take any action based on the exception. - -Just create a class derived from the `ExceptionSubscriber` class in your application: - -````csharp -public class MyExceptionSubscriber : ExceptionSubscriber -{ - public async override Task HandleAsync(ExceptionNotificationContext context) - { - //TODO... - } -} -```` - -See the [exception handling](https://docs.abp.io/en/abp/latest/Exception-Handling) document for more. - -### Others - -There are many minor features and enhancements made to the framework in the past releases. Here, a few ones: - -* Added `AbpLocalizationOptions.DefaultResourceType` to set the default resource type for the application. In this way, the localization system uses the default resource whenever the resource was not specified. The latest application startup template already configures it, but you may want to set it for your existing applications. -* Added `IsEnabled` to permission definition. In this way, you can completely disable a permission and hide the related functionality from the application. This can be a way of feature switch for some applications. See [#3486](https://github.com/abpframework/abp/issues/3486) for usage. -* Added Dutch and German localizations to all the localization resources defined by the framework. Thanks to the contributors. - -## What's New in the ABP Commercial - -The goal of the [ABP Commercial](https://commercial.abp.io/) is to provide pre-build application functionalities, code generation tools, professional themes, advanced samples and premium support for ABP Framework based projects. - -We are working on the ABP Commercial in the parallel to align with the ABP Framework features and provide more modules, theme options and tooling. - -This section explains what's going on the ABP Commercial side. - -### Module Entity Extension System - -Module entity extension system is a higher level API that uses the object extension system (introduced above) and provides an easy way to add extension properties to existing entities. A new extension property easily automatically becomes a part of the HTTP API and the User Interface. - -Example: Add a `SocialSecurityNumber` to the user entity of the identity module - -````csharp -ObjectExtensionManager.Instance.Modules() - .ConfigureIdentity(identity => - { - identity.ConfigureUser(user => - { - user.AddOrUpdateProperty( //property type: string - "SocialSecurityNumber", //property name - property => - { - //validation rules - property.Attributes.Add(new RequiredAttribute()); - property.Attributes.Add( - new StringLengthAttribute(64) { - MinimumLength = 4 - } - ); - - //...other configurations for this property - } - ); - }); - }); -```` - -With just such a configuration, the user interface will have the new property (on the table and on the create/edit forms): - -![module-entity-extended-ui](module-entity-extended-ui.png) - -The new property can be easily localized and validated. Currently, it supports primitive types like string, number and boolean, but we planned to add more advanced scenarios by the time (like navigation/lookup properties). - -See the [Module Entity Extensions](https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions) guide to learn how to use it and configure details. - -#### Other Extension Points - -There are also some other pre-defined points to customize and extend the user interface of a depended module: - -* You can add a new action for an entity on the data table (left side on the picture below). -* You can add new buttons (or other controls) to the page toolbar (right side on the picture below). -* You can add custom columns to a data table. - -![abp-commercial-ui-extensions](abp-commercial-ui-extensions.png) - -See the [Customizing the Modules](https://docs.abp.io/en/commercial/latest/guides/customizing-modules) guide to learn all the possible ways to customize a depended module. - -### Text Template Management Module - -We are introducing a new module with the v2.7 release: [Text Template Management](https://docs.abp.io/en/commercial/latest/modules/text-template-management). It is basically used to edit text/email templates (introduced with the ABP Framework 2.7) on the user interface and save changed in the database. - -A screenshot from the content editing for the password reset email template: - -![text-template-content-ui](text-template-content-ui.png) - -This module comes pre-installed when you create a new project. - -### Entity History Views - -Audit logging UI module now shows all the entity changes in the application with property change details. - -![audit-log-entity-changes](audit-log-entity-changes.png) - -You can also check history for an entity when you click to the actions menu for the entity: - -![tenant-entity-changes](tenant-entity-changes.png) - -### More Samples - -We are creating more advanced sample applications built with the ABP Commercial. Easy CRM is one of them which will be available in a few days to the commercial customers. - -Here, a screenshot from the Easy CRM dashboard: - -![easy-crm](easy-crm.png) - -It has accounts, contacts, product groups, products, orders and so on. - -### New Modules - -We continue to improve existing modules and creating new modules. In addition to the new [text template management](https://docs.abp.io/en/commercial/latest/modules/text-template-management) module introduced above; - -* We've recently released a [payment module](https://commercial.abp.io/modules/Volo.Payment) that currently works with PayU and 2Checkout payment gateways. More gateways will be added by the time. -* We've created a simple [Twilio SMS integration](https://docs.abp.io/en/commercial/latest/modules/twilio-sms) module to send SMS over the Twilio. -* We are working on a **chat module** that is currently being developed and will be available in the next weeks. -* We are working on the **organization unit management** system for the identity module to create hierarchical organization units (domain layer will be open source & free). - -More modules, theme and tooling options are being developed for the ABP Commercial and the ABP Framework. - -## ABP Framework vs ABP Commercial - -We ([Volosoft](https://volosoft.com/) - the core team behind the ABP.IO platform), are spending almost equal time on the ABP Framework and the ABP Commercial and we consider the ABP.IO platform as a whole. - -[ABP Framework](https://abp.io/) provides all the infrastructure and application independent framework features to make you more productive, focus on your own business code and implement software development best practices. It provides you a well defined and comfortable development experience without repeating yourself. - -[ABP Commercial](https://commercial.abp.io/) provides pre-built functionalities, themes and tooling to save your time if your requirements involve these functionalities in addition to the premium support for the framework and the pre-built modules. diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png deleted file mode 100644 index 252af6b9e4..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png deleted file mode 100644 index 6a1ec44810..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png deleted file mode 100644 index 2bfa53cbcc..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png deleted file mode 100644 index e624e75e38..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png deleted file mode 100644 index 25621aabb7..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png deleted file mode 100644 index 31524187b0..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png b/docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png deleted file mode 100644 index 99c7972da3..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/Post.md b/docs/en/Blog-Posts/2020-05-22 v2_8_Release/Post.md deleted file mode 100644 index 6df6d84405..0000000000 --- a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/Post.md +++ /dev/null @@ -1,214 +0,0 @@ -# ABP v2.8.0 Releases & Road Map - -The **ABP Framework** & and the **ABP Commercial** v2.8 have been released. This post will cover **what's new** with these releases and the **middle-term road maps** for the projects. - -## What's New in the ABP Framework 2.8? - -You can see all the changes on the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.8.0). This post will only cover the important features/changes. - -### SignalR Integration Package - -We've published [a new package](https://www.nuget.org/packages/Volo.Abp.AspNetCore.SignalR) to integrate SignalR to ABP framework based applications. - -> It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides a SignalR integration packages those simplify the integration and usage. - -See the [SignalR Integration document](https://docs.abp.io/en/abp/latest/SignalR-Integration) to start with the SignalR. - -#### SignalR Demo Application - -We've also created a simple chat application to demonstrate how to use it. - -![signalr-chat-demo](signalr-chat-demo.png) - -See [the source code of the application.](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) - -### Console Application Startup Template - -The new console application template can be used to create a new console application that has the ABP Framework integrated. - -Use ABP CLI to create a new console application, specifying the `console` as the `-t` (template) option: - -````bash -abp new MyApp -t console -```` - -Thanks to the contribution of [@liangshiw](https://github.com/liangshiw) for this template. - -### RTL Support for the MVC UI & Arabic Localization - -[@kgamalseif](https://github.com/kgamalseif) has contributed a RTL implementation for the MVC UI which looks pretty fine: - -![rtl-ui](rtl-ui.png) - -He also localized all the framework and module resources. Thanks to him for this great contribution. - -### Others - -Some other highlights from this release: - -* Converted HttpApi.Client packages of the modules to .netstandard 2.0 to be compatible with other kind of applications. -* Improved the object extensibility system to better handle UI, localization and validation. -* Implemented disabling background job execution for HangFire & Quartz intergrations. -* New JsTree integration package for the MVC UI. -* Moved all samples to the new [abp-samples](https://github.com/abpframework/abp-samples) repository and created an [index page](https://docs.abp.io/en/abp/latest/Samples/Index) to see all. - -### Deprecations - -* Deprecated the `app.UseMvcWithDefaultRouteAndArea()` and introduced the `app.UseConfiguredEndpoints()` (see [#3880](https://github.com/abpframework/abp/issues/3880)). -* Deprecated the `UsePostgreSql()` and introduced the `UseNpgsql()` for the [Volo.Abp.EntityFrameworkCore.PostgreSql](http://nuget.org/packages/Volo.Abp.EntityFrameworkCore.PostgreSql) package. Switch to `UseNpgsql()` if you are using PostgreSQL. - -Old methods are marked as `Obsolete` and will be removed in the next major versions. - -## What's New in the ABP Commercial 2.8? - -### The New Lepton Theme - -We've completely revised [the lepton theme](https://commercial.abp.io/themes). See with different styles: - -![lepton-themes](lepton-themes.gif) - -Example screenshots from the language management page of the ABP Commercial: - -![lepton-abp-default-theme](lepton-abp-default-theme.png) - -(Default style UI) - -![lepton-abp-material-theme](lepton-abp-material-theme.png) - -(Material style UI) - -[Create a demo](https://commercial.abp.io/demo) to test all the styles in live. You can change the style from the settings page. - -### The New Chat Module - -The first version of [the chat module](https://commercial.abp.io/modules/Volo.Chat) has been released with this version. It has only the MVC / Razor Pages UI. Angular UI is on the way. - -![abp-chat-module](abp-chat-module.png) - -It currently has a simple **real time text messaging** functionality. More features like group messaging, sending images/files are on the road map. - -### Others - -* Implemented [module entity extension](https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions) system for the Angular UI. Also improved the system to better handle float/double/decimal, date, datetime, enum and boolean properties. -* Managing product groups on a tree view for the [EasyCRM sample application](https://docs.abp.io/en/commercial/latest/samples/easy-crm). - -## About the Next Versions - -We publish feature releases in **every 2 weeks**. So, the planned date of the next feature version is **June 04** and the version number is **2.9**. This (probably) will be the **last 2.x version** and the following version will be **3.0**. - -### ABP Framework 2.9 & 3.0 - -#### Organization Unit System - -Organization Unit system for the Identity module was intended to be released with 2.8, but unfortunately we couldn't be sure about the stability of the feature, so deferred it to the 2.9. - -#### gRPC - -We planned to work on a gRPC integrated example application. Then we will plan to create gRPC endpoints for all [pre-built modules](https://docs.abp.io/en/abp/latest/Modules/Index) and to [the startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Index). We want to use these endpoints with the new planned [Blazor](https://docs.microsoft.com/en-us/aspnet/core/blazor/) UI option (there is a [huge demand](https://github.com/abpframework/abp/issues/394) on a Blazor UI, we know). It doesn't mean that we'll finish the whole work in 3.0, but we are starting and will continue in 3.0+ versions. - -#### Oracle with EF Core - -We see that the people using Oracle with EF Core has some pains, independent from the ABP Framework. Because there is no stable & free Oracle provider for EF Core 3.1 yet. We only see the [Devart](https://www.devart.com/) has created a [paid package](https://www.nuget.org/packages/Devart.Data.Oracle.EFCore). - -[@ebicoglu](https://github.com/ebicoglu) has [created a gist](https://gist.github.com/ebicoglu/9f364c7eff9d87315af0178866186401) to demonstrate how to use it. We [planned](https://github.com/abpframework/abp/issues/3983) to work on an integration package to make it even easier. - -#### API Documentation - -We are [working](https://github.com/abpframework/abp/issues/1184) to create an API documentation for the framework and build a CD pipeline to automatically publish it in every new release. This will make easier to explore the framework classes. - -#### Sample Application: Using SignalR on a Tiered/Distributed system - -Using SignalR on a distributed/microservice system can be tricky since the services are not connected to clients and can not directly call client functions from the server. One way to overcome this problem is using a distributed message bus (like RabbitMQ) that transfers the message from the service to the web application to deliver to the client. - -We will create an example application and document it to demonstrate such an architecture and how it is easy by using the ABP Framework. - -While this topic is not directly related to the ABP Framework and the problem is not unique to an ABP based application, we find useful to create such guides to developers. - -#### And... - -We will spend more time to write more documentation, implement performance improvements, make more tests, creating more extensibility points and so on. - -### ABP Commercial 2.9 & 3.0 - -#### Organization Unit Management UI - -In parallel to the OU system in the ABP Framework (mentioned above), we are creating a UI to manage the organization units, which will be released with the 2.9. - -#### Angular UI for the Chat Module - -The Chat Module (mentioned above) only has the ASP.NET Core MVC / Razor Pages UI now. We are working to create the Angular UI for this module. - -#### New Module Idea: File Management - -We are looking to create a File Management Module that is used to manage (upload/download) and share files between users. You may think as a very simple and lightweight Google Drive :). - -#### Easy CRM Angular UI - -[Easy CRM](https://docs.abp.io/en/commercial/latest/samples/easy-crm) is a sample application we've released with the previous version of the ABP Commercial. In this version, we've added more features to this application. In the next version, we will work on the Angular UI for it. - -We found this application very useful since it is very close to a real world application compared to the simple [BookStore](https://docs.abp.io/en/commercial/latest/samples/index#book-store) example. - -#### And... - -We are working to improve current [modules](https://commercial.abp.io/modules), [themes](https://commercial.abp.io/themes) and the [tooling](https://commercial.abp.io/tools) to provide a more comfortable developer experience with the version 3.0. - -## The Road Map - -We are frequently asked about the road map of the [ABP Framework](https://abp.io/) and the [ABP Commercial](https://commercial.abp.io/). While we've answered to it in various platforms, with this release, we've adding road map pages for these products to their documentation: - -* [ABP Framework Road Map](https://docs.abp.io/en/abp/latest/Road-Map) -* [ABP Commercial Road Map](https://docs.abp.io/en/commercial/latest/road-map) - -I am also writing the road map here, in the following sections; - -### ABP Framework Road Map - -You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones). - -While we will **continue to add other exciting features**, we will work on the following major items in the middle term: - -* **gRPC integration** and implementation for all the pre-built modules. -* **Blazor UI** for the framework and all the pre-built modules. -* **.NET 5.0**! As Microsoft has announced that the .NET 5.0 will be released in November 2020, we will prepare for this change before and move to the .NET 5.0 just after Microsoft releases it. We hope a smooth transition. - -### ABP Commercial Road Map - -We will work on the same items in parallel to to ABP Framework to implement them in the ABP Commercial side: - -* gRPC integration -* Blazor UI -* .NET 5.0 - -In addition, we will be working on the following items in the middle term: - -* A startup template to create microservice solutions (that has Ocelot, Redis, RabbitMQ, ElasticSearch, IdentityServer... etc. pre-integrated and configured). -* More module extension points. -* Dynamic dashboard system. -* Real-time notification system. -* Subscription and payment system for the SaaS module. -* More authentication options. -* New application modules (we have tens of module ideas and will share by the time - the "file management" announced above was one of them). -* New themes & theme styles (including public/corporate web site themes). - -## BONUS: ABP.IO Platform Road Map - -While the ABP Framework and the ABP Commercial are the fundamental components of the ABP.IO Platform, we want to create a much bigger platform to bring the .NET community together to create reusable modules, share knowledge, help each other by taking the advantage of the ABP Framework's unified and standardized development model. - -So, we have new *.abp.io web site ideas I want to share with the community - -#### market.abp.io - -A platform that is used by developers/companies to publish their reusable application modules, themes, libraries and tools base don the ABP Framework. There will be free/open source and commercial products on this web site. - -#### jobs.abp.io - -We are getting too many emails from companies want to hire developers or other other companies to build their products based on the ABP.IO Platform. We, as [Volosoft](https://volosoft.com/), want to stay in the product side rather than customer basis projects. We generally lead them to experienced developers and companies. - -We have a plan to create a web site to meet each side, so you can find developers for your projects or you find short or long term works to do. - -## Follow the ABP! - -Follow the social media accounts to get informed about happenings on the ABP.IO Platform: - -* [@abpframework](https://twitter.com/abpframework): ABP Framework official Twitter account -* [@abpcommercial](https://twitter.com/abpcommercial): ABP Commercial official Twitter account diff --git a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/abp-chat-module.png b/docs/en/Blog-Posts/2020-05-22 v2_8_Release/abp-chat-module.png deleted file mode 100644 index 5ae351ebba..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/abp-chat-module.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-abp-default-theme.png b/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-abp-default-theme.png deleted file mode 100644 index f3c3d16581..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-abp-default-theme.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-abp-material-theme.png b/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-abp-material-theme.png deleted file mode 100644 index e22c3003d9..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-abp-material-theme.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-themes.gif b/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-themes.gif deleted file mode 100644 index 7ce3935ef5..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/lepton-themes.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/rtl-ui.png b/docs/en/Blog-Posts/2020-05-22 v2_8_Release/rtl-ui.png deleted file mode 100644 index 34cd6581dd..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/rtl-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/signalr-chat-demo.png b/docs/en/Blog-Posts/2020-05-22 v2_8_Release/signalr-chat-demo.png deleted file mode 100644 index c93595511a..0000000000 Binary files a/docs/en/Blog-Posts/2020-05-22 v2_8_Release/signalr-chat-demo.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/Post.md b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/Post.md deleted file mode 100644 index 2246b7df14..0000000000 --- a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/Post.md +++ /dev/null @@ -1,296 +0,0 @@ -# ABP Framework v2.9 Has Been Released - -The **ABP Framework** & and the **ABP Commercial** version 2.9 have been released, which are the last versions before v3.0! This post will cover **what's new** with these this release. - -## What's New with the ABP Framework 2.9? - -You can see all the changes on the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0). This post will only cover the important features/changes. - -### Pre-Compiling Razor Pages - -Pre-built pages (for [the application modules](https://docs.abp.io/en/abp/latest/Modules/Index)) and view components were compiling on runtime until this version. Now, they are pre-compiled and we've measured that the application startup time (especially for the MVC UI) has been reduced more than 50%. In other words, it is **two-times faster** than the previous version. The speed change also effects when you visit a page for the first time. - -Here, a test result for the startup application template with v2.8 and v.2.9: - -```` -### v2.8 - -2020-06-04 22:59:04.891 +08:00 [INF] Starting web host. -2020-06-04 22:59:07.662 +08:00 [INF] Now listening on: https://localhost:44391 -2020-06-04 22:59:17.315 +08:00 [INF] Request finished in 7756.6218ms 200 text/html; - -Total: 12.42s - -### v2.9 - -2020-06-04 22:59:13.720 +08:00 [INF] Starting web host. -2020-06-04 22:59:16.639 +08:00 [INF] Now listening on: https://localhost:44369 -2020-06-04 22:59:18.957 +08:00 [INF] Request finished in 1780.5461ms 200 text/html; - -Total: 5.24s -```` - -You do nothing to get the benefit of the new approach. [Overriding UI pages/components](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface) are also just working as before. We will be working on more performance improvements in the v3.0. - -### Organization Unit System - -[The Identity Module](https://docs.abp.io/en/abp/latest/Modules/Identity) now has the most requested feature: Organization Units! - -Organization unit system is used to create a hierarchical organization tree in your application. You can then use this organization tree to authorize data and functionality in your application. - -The documentation will come soon... - -### New Blob Storing Package - -We've created a new [Blob Storing package](https://www.nuget.org/packages/Volo.Abp.BlobStoring) to store arbitrary binary objects. It is generally used to store the content of the files in your application. This package provides an abstraction, so any application or [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics) can save and retrieve files independent from the actual storing provider. - -There are two storage provider currently implemented: - -* [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) package stores objects/files in the local file system. -* [Volo.Abp.BlobStoring.Database](https://github.com/abpframework/abp/tree/dev/modules/blob-storing-database) module stores objects/files in a database. It currently supports [Entity Framework Core](https://docs.abp.io/en/abp/latest/Entity-Framework-Core) (so, you can use [any relational DBMS](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS)) and [MongoDB](https://docs.abp.io/en/abp/latest/MongoDB). - -[Azure BLOB provider](https://github.com/abpframework/abp/issues/4098) will be available with v3.0. You can request other cloud providers or contribute yourself on the [GitHub repository](https://github.com/abpframework/abp/issues/new). - -One of the benefits of the blob storing system is that it allows you to create multiple containers (each container is a blob storage) and use different storage providers for each container. - -**Example: Use the default container to save and get a byte array** - -````csharp -public class MyService : ITransientDependency -{ - private readonly IBlobContainer _container; - - public MyService(IBlobContainer container) - { - _container = container; - } - - public async Task FooAsync() - { - //Save a BLOB - byte[] bytes = GetBytesFromSomeWhere(); - await _container.SaveAsync("my-unique-blob-name", bytes); - - //Retrieve a BLOB - bytes = await _container.GetAllBytesAsync("my-unique-blob-name"); - } -} -```` - -It can work with `byte[]` and `Stream` objects. - -**Example: Use a typed (named) container to save and get a stream** - -````csharp -public class MyService : ITransientDependency -{ - private readonly IBlobContainer _container; - - public MyService(IBlobContainer container) - { - _container = container; - } - - public async Task FooAsync() - { - //Save a BLOB - Stream stream = GetStreamFromSomeWhere(); - await _container.SaveAsync("my-unique-blob-name", stream); - - //Retrieve a BLOB - stream = await _container.GetAsync("my-unique-blob-name"); - } -} -```` - -`TestContainer` is an empty class that has no purpose than identifying the container: - -````csharp -[BlobContainerName("test")] //specifies the name of the container -public class TestContainer -{ - -} -```` - -A typed (named) container can be configured to use a different storing provider than the default one. It is a good practice to always use a typed container while developing re-usable modules, so the final application can configure provider for this container without effecting the other containers. - -**Example: Configure the File System provider for the `TestContainer`** - -````csharp -Configure(options => -{ - options.Containers.Configure(configuration => - { - configuration.UseFileSystem(fileSystem => - { - fileSystem.BasePath = "C:\\MyStorageFolder"; - }); - }); -}); -```` - -See the [blob storing documentation](https://docs.abp.io/en/abp/latest/Blob-Storing) for more information. - -### Oracle Integration Package for Entity Framework Core - -We've created an [integration package for Oracle](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart), so you can easily switch to the Oracle for the EF Core. It is tested for the framework and pre-built modules. - -[See the documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle) to start using the Oracle integration package. - -### Automatically Determining the Database Provider - -When you develop a **reusable application module** with EF Core integration, you generally want to develop your module **DBMS independent**. However, there are minor (sometimes major) differences between different DBMSs. If you perform a custom mapping based on the DBMS, you can now use `ModelBuilder.IsUsingXXX()` extension methods: - -````csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) -{ - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - //... - if (modelBuilder.IsUsingPostgreSql()) //Check if using PostgreSQL! - { - b.Property(x => x.Number).HasMaxLength(20); - } - else - { - b.Property(x => x.Number).HasMaxLength(32); - } - }); -} -```` - -Beside the stupid example above, you can configure your mapping however you need! - -### ABP CLI: Translate Command - -`abp translate` is a new command that simplifies to translate [localization](https://docs.abp.io/en/abp/latest/Localization) files when you have multiple JSON localization files in a source control repository. - -The main purpose of this command is to **translate the ABP Framework** localization files (since the [abp repository](https://github.com/abpframework/abp) has tens of localization files to be translated in different folders). - -It is appreciated if you use this command to translate the framework resources **for your mother language**. - -See [the documentation](https://docs.abp.io/en/abp/latest/CLI#translate) to learn how to use it. Also see [the contribution guide](https://docs.abp.io/en/abp/latest/Contribution/Index). - -### The New Virtual File System Explorer Module - -Thanks to [@liangshiw](https://github.com/liangshiw) created and contributed a new module to explore files in the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). It works for MVC UI and shows all the virtual files in the application. Example screenshots: - -![virtual-file-explorer-1](virtual-file-explorer-1.png) - -![virtual-file-explorer-2](virtual-file-explorer-2.png) - -[See the documentation](https://docs.abp.io/en/abp/latest/Modules/Virtual-File-Explorer) to learn how to use it. - -### Sample Application: SignalR with Tiered Architecture - -Implementing SignalR in a distributed/tiered architecture can be challenging. We've created a sample application that demonstrate how to implement it using the [SignalR integration](https://docs.abp.io/en/abp/latest/SignalR-Integration) and the [distributed event bus](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) system easily. - -See [the source code](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo) of the sample solution. - -**An article is on the road** that will deeply explain the solution. Follow the [@abpframework](https://twitter.com/abpframework) Twitter account. - -![signalr-tiered-demo](signalr-tiered-demo.png) - -*A picture from the article that shows the communication diagram of the solution* - -### About gRPC - -We've created a sample application to show how to create and consume gRPC endpoints in your ABP based applications. - -See [the source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) on GitHub. - -We were planning to create gRPC endpoints for all the pre-built application modules, but we see that ASP.NET Core gRPC integration is not mature enough and doesn't support some common deployment scenarios yet. So, deferring this to the next versions ([see this comment](https://github.com/abpframework/abp/issues/2882#issuecomment-633080242) for more). However, it is pretty standard if you want to use gRPC in your applications. ABP Framework has no issue with gRPC. Just check the [sample application](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo). - -### Others - -* [Time zone system](https://github.com/abpframework/abp/pull/3933) to support different time zones for an application. -* Support for [virtual path deployment](https://github.com/abpframework/abp/issues/4089) on IIS. -* RTL support for the Angular UI. - -See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0) for others updates. - -## What's New with the ABP Commercial 2.9 - -In addition to all the features coming with the ABP Framework, the ABP Commercial has additional features with this release, as always. This section covers the [ABP Commercial](https://commercial.abp.io/) highlights in the version 2.9. - -### Organization Unit Management UI - -We've created the UI for manage organization units, their members and roles for the ABP Commercial [Identity Module](https://commercial.abp.io/modules/Volo.Identity.Pro): - -![organization-units](organization-units.png) - -OU management is available for both of the MVC (Razor Pages) and the Angular user interfaces. - -> See [this entry](https://support.abp.io/QA/Questions/222/Bugs--Problems-v290#answer-3cf5eba3-0bf1-2aa1-cc5e-39f5a0750329) if you're upgrading your solution from an earlier version. - -### Chat Module Angular UI - -We had introduced a new [chat module](https://commercial.abp.io/modules/Volo.Chat) in the previous version, which was only supporting the ASP.NET Core MVC / Razor Pages UI. Now, it has also an Angular UI option. - -![abp-chat-module](abp-chat-module.png) - -*A screenshot from the chat module - two users are sending messages to each other* - -### Easy CRM Angular UI - -Easy CRM is a sample application that is built on the ABP Commercial to provide a relatively complex application to the ABP Commercial customers. In the version 2.7, we have lunched it with MVC / Razor Pages UI. With the 2.9 version, we are releasing the Angular UI for the Easy CRM application. - -![easy-crm](easy-crm.png) - -*A screenshot from the "Order Details" page of the Easy CRM application.* - -See the [Easy CRM document](https://docs.abp.io/en/commercial/latest/samples/easy-crm) to learn how to download and run it. - -### Module Code Generation for the ABP Suite - -[ABP Suite](https://commercial.abp.io/tools/suite) is a tool that's main feature is to [generate code](https://docs.abp.io/en/commercial/latest/abp-suite/generating-crud-page) for complete CRUD functionality for an entity, from database to the UI layer. - -![suite](suite.png) - -*A screenshot from the ABP Suite: Define the properties of a new entity and let it to create the application code for you!* - -It was working only for [the application template](https://docs.abp.io/en/commercial/latest/startup-templates/application/index) until this release. Now, it supports to generate code for the [module projects](https://docs.abp.io/en/commercial/latest/startup-templates/module/index) too. That's a great way to create reusable application modules by taking the power of the code generation. - -In addition to this main feature, we added many minor enhancements on the ABP Suite in this release. - -> Notice: Generating code for the module template is currently in beta. Please inform us if you find any bug. - -### Lepton Theme - -[Lepton Theme](https://commercial.abp.io/themes) is the commercial theme we've developed for the ABP Commercial; - -* It is 100% bootstrap compatible - so you don't write theme specific HTML! -* Provides different kind of styles - you see the material style in the picture below. -* Provides different kind of layouts (side/top menu, fluid/boxed layout...). -* It is lightweight, responsive and modern. -* And... it is upgradeable with no cost! You just update a NuGet/NPM package to get the new features. - -We've create its own web site: [http://leptontheme.com/](http://leptontheme.com/) - -You can view all the components together, independent from an application: - -![lepton-theme](lepton-theme.png) - -This web site is currently in a very early stage. We will be documenting and improving this web site to be a reference for your development and explore the features of the theme. - -### Coming Soon: The File management Module - -Based on the new blob storing system (introduced above), we've started to build a file management module that is used to manage (navigate/upload/download) a hierarchical file system on your application and share the files between your users and with your customers. - -We plan to release the initial version with the ABP Commercial v3.0 and continue to improve it with the subsequent releases. - -## About the Next Version: 3.0 - -We have added many new features with the [v2.8](https://blog.abp.io/abp/ABP-v2.8.0-Releases-%26-Road-Map) and v2.9. In the next version, we will completely focus on the **documentation, performance improvements** and and other enhancements as well as bug fixes. - -For a long time, we were releasing a new feature version in every 2 weeks. We will continue to this approach after v3.0. But, as an exception to the v3.0, the development cycle will be ~4 weeks. **The planned release date for the v3.0 is the July 1, 2020**. - -## Bonus: Articles! - -Beside developing our products, our team are constantly writing articles/tutorials on various topics. You may want to check the latest articles: - -* [ASP.NET Core 3.1 WebHook Implementation Using Pub/Sub](https://volosoft.com/blog/ASP.NET-CORE-3.1-Webhook-Implementation-Using-Pub-Sub) -* [Using Azure Key Vault with ASP.NET Core](https://volosoft.com/blog/Using-Azure-Key-Vault-with-ASP.NET-Core) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/abp-chat-module.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/abp-chat-module.png deleted file mode 100644 index 5ae351ebba..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/abp-chat-module.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/easy-crm.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/easy-crm.png deleted file mode 100644 index 07d7a2b54d..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/easy-crm.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/lepton-theme.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/lepton-theme.png deleted file mode 100644 index f6a85115e0..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/lepton-theme.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/organization-units.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/organization-units.png deleted file mode 100644 index 2a6ad72ffb..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/organization-units.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/signalr-tiered-demo.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/signalr-tiered-demo.png deleted file mode 100644 index c4ca5e2b14..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/signalr-tiered-demo.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/suite.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/suite.png deleted file mode 100644 index 7e08cb3ef3..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/suite.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-1.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-1.png deleted file mode 100644 index 0db07459ca..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-1.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-2.png b/docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-2.png deleted file mode 100644 index 5d4d85755d..0000000000 Binary files a/docs/en/Blog-Posts/2020-06-05 v2_9_Release/virtual-file-explorer-2.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md b/docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md deleted file mode 100644 index 92ec1561d3..0000000000 --- a/docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md +++ /dev/null @@ -1,177 +0,0 @@ -# ABP Framework v3.0 Has Been Released - -We are excited to announce that the **ABP Framework** & and the **ABP Commercial** version 3.0 have been released. As different than the regular release lifecycle, which is 2-weeks, this version has taken 4-weeks with **119 [issues](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0)** closed, **89 [pull requests](https://github.com/abpframework/abp/pulls?q=is%3Aopen+is%3Apr+milestone%3A3.0)** merged and **798 commits** done in the main framework [repository](https://github.com/abpframework/abp). - -Since this is a **major version**, it also includes some **breaking changes**. Don't panic, the changes are easy to adapt and will be explained below. - -> See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for a detailed change log. - -## What's New with the ABP Framework 3.0? - -This post will only cover the important features/changes. You can see all the changes on the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0). - -### Angular 10! - -Angular version 10 has just been [released](https://blog.angular.io/version-10-of-angular-now-available-78960babd41) and we've immediately migrated the [startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) to Angular 10! So, when you [create a new solution](https://abp.io/get-started) with the Angular UI, you will take the advantage of the new Angular. - -We've prepared a [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for the projects created an older version and want to migrate to Angular 10. - -### The Oracle Integration Package - -We had created [an integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) for the Oracle for EF Core based applications using the Devart's library since the official Oracle EF Core package was not supporting the EF Core 3.1. It now supports as a [beta release](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/3.19.0-beta1). While it is in beta, we've created [the integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle), so you can use it in your application. - -See [the documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle) for details. - -### Azure BLOB Storage Provider - -We had created a [BLOB storing system](https://docs.abp.io/en/abp/latest/Blob-Storing) in the previous version with a file system and database storage provider. This release introduces the Azure BLOB Storage provider. See [the documentation](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure). - -### Distributed Cache Bulk Operations & the New Redis Cache Package - -The [standard IDistributeCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) interface of the ASP.NET Core doesn't contain **bulk operations**, like setting multiple items with a single method/server call. ABP Framework introduces new methods those can be used for bulk operations on the ABP's `IDistributedCache` interface: - -* GetManyAsync / GetMany -* SetManyAsync / SetMany - -Then we needed to implement these new methods for Redis cache and [had to create](https://github.com/abpframework/abp/issues/4483) a Redis integration package which extends the Microsoft's implementation. - -These methods are also used by the ABP Framework to cache settings, features and permissions for a user/role/tenant and brings a **significant performance improvement**. - -See the [caching document](https://docs.abp.io/en/abp/latest/Caching) for details. - -### Embedded Files Manifest Support for the Virtual File System - -Virtual File System now supports to use `GenerateEmbeddedFilesManifest` in your projects to add the **real file/directory structure** of your embedded resources in the compiled assembly. So, you can now access to the files without any file name restriction (previously, some special chars like `.` in the directory names was a problem in some cases) - -See [the documentation](https://docs.abp.io/en/abp/latest/Virtual-File-System) to learn how to take the advantage of new system. - -### New Samples - -Based on the requests from the community, we've prepared two new sample applications: - -* [StoredProcedureDemo](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) demonstrates how to call stored procedures, views and functions inside a custom repository. -* [OrganizationUnitSample](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample) shows how to use the organization unit system of the [Identity module](https://docs.abp.io/en/abp/latest/Modules/Identity) for your entities. - -### DynamicStringLength & DynamicMaxLength Attributes - -The standard `StringLength` and `MaxLength` data annotation attributes is useful to validate properties of a class when the class is used as a Model or [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects). However, just like any other attribute, the length values should be literal (constant) values known at **compile time**. - -**Example: Using the `StringLength`** - -````csharp -public class CreateBookDto -{ - public const int MaxNameLength = 128; //CONSTANT! - - [StringLength(MaxNameLength)] - public string Name { get; set; } -} -```` - -ABP Framework now has the `DynamicStringLength` & `DynamicMaxLength` properties to allow to determine the lengths at **runtime**. - -**Example: Using the `DynamicStringLength`** - -````csharp -public class CreateBookDto -{ - public static int MaxNameLength { get; set; } = 128; - - [DynamicStringLength(typeof(CreateBookDto), nameof(MaxNameLength))] - public string Name { get; set; } -} -```` - -`DynamicStringLength` gets a class **type** and the **name** of a static property on this class to read the max length (there is also a minimum length option just like the `StringLength`). - -This allows you to get the max value from a configuration and set on the application startup (generally, in the `PreConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics)): - -````csharp -CreateBookDto.MaxNameLength = 200; -```` - -This feature is used by the [pre-built application modules](https://docs.abp.io/en/abp/latest/Modules/Index), so you can now override the max lengths of the properties defined in these modules. - -### Auto Distributed Events - -ABP can **automatically publish distributed events** for all entities on their create, update and delete events. That's pretty useful since you commonly interest in these basic events in a distributed system. - -This feature is **mature and [documented](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#pre-defined-events)** with the v3.0. You can easily configure some or all the entities to be published. - -### IAsyncQueryableExecuter - -When you work with LINQ extension methods, you need to call `ToListAsync()`, `FirstOrDefaultAsync()`... methods on your queries. Unfortunately, these methods are **not standard** LINQ extension methods. They are defined in the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) package (or in the [MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver/) package if you are using the MongoDB). - -So, you need to depend on this package if you want to use the async extension methods. That breaks the layering and makes your application or domain layer depends on the EF Core / MongoDB package. - -`IAsyncQueryableExecuter` is a service defined by the ABP Framework to **execute queries asynchronously without depending the specific provider** (EF Core / MongoDB) package. - -See [the documentation](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations) to read the details and learn our recommendations. - -### API documentation - -We are now publishing [API documents](https://docs.abp.io/api-docs/abp/2.9/api/index.html) for the ABP Framework and modules in every release. So, you can explore the ABP Framework classes much more easier than before. Click the the **API Documentation** link on the navigation menu of the [documentation](https://docs.abp.io/en/abp/latest/). - -### Package List - -We have [created a page](http://abp.io/packages) to list all the ABP-related official NuGet and NPM packages. - -### Others - -* Implemented front-channel server-side clients [sign out](https://identityserver4.readthedocs.io/en/latest/topics/signout.html) for the identity server. -* `abp.currentUser` (`CurrentUser` service in the Angular UI) now has a `roles` array that contains role names of the current user. -* Upgraded all the NuGet and NPM package dependencies. -* Introduced `ReadOnlyAppService` base class (which has only the get operations) in addition to the `CrudAppService` base class (which has get, create, update and delete operations). - -See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for others updates. - -## What's New with the ABP Commercial 3.0? - -In addition to all the features coming with the ABP Framework, the ABP Commercial has additional features with this release, as always. This section covers the [ABP Commercial](https://commercial.abp.io/) highlights in the version 2.9. - -### New File Management Module - -We've created a new module that is used to store and manage files in your application. This new module is based on the [BLOB Storing system](https://docs.abp.io/en/abp/latest/Blob-Storing), so it can use different storage providers to store the file contents. - -**Example screenshot** - -![file-management-ui](file-management-ui.png) - -You can upload, download and organize files in a hierarchical folder structure. It is also compatible to multi-tenancy and you can determine total size limit for your tenants. In the next versions, we will be working on a "share" system to share files between users in a more controlled way or share your files with your customers with a public link. - -> File Management module is currently available only for the MVC / Razor Pages UI. We are working on the Angular UI and it will be released in the next versions. - -## Breaking Changes - -Since this is a major version, we've redesigned some APIs and introduced a few "easy to fix" breaking changes. - -### ABP Framework - -* Changed some **consts** in the pre-built application modules to static properties that is possible to change by your code. If you've used these consts on an attribute, then use the `DynamicStringLength` as explained above. -* Changed `ConcurrencyStamp` max length to 40. You need to **add a database migration** and update your database after upgrading the ABP Framework. -* Using `~` instead of `^` for NPM package dependencies anymore, to be more stable. - -### ABP Commercial - -* Changed file names for the application logos. Previously, it was using separate logo files for each theme, like `theme1.png`, `theme1-reverse.png`, `theme2.png`, `theme2-reverse.png` (... `6`). Now, we have only two logo files: `logo-light.png` and `logo-dark.png`. So, rename your logo in the `wwwroot/images/logo/` folder for the MVC UI and `/src/assets/images/logo/` folder for the Angular UI. -* We've added the [API documentation](https://docs.abp.io/api-docs/commercial/2.9/api/index.html) for the ABP Commercial too. - -> **Also, see the [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for Angular UI**. - -## Known Issues - -* 3.0.0 version has a problem with tiered architecture. See [this issue](https://github.com/abpframework/abp/pull/4564) to fix it for your application until we release the v3.0.1. - -## About the Next Versions - -We will continue to release a new minor/feature version in every two weeks. So, the next expected release date is **2020-07-16** for the version **3.1**. - -In the next few versions, we will be focused on the **Blazor UI**, as promised on [the road map](https://docs.abp.io/en/abp/latest/Road-Map). We will continue to improve the documentation, create samples, add other new features and enhancements. Follow the [ABP Framework Twitter account](https://twitter.com/abpframework) for the latest news... - -## Bonus: Articles! - -Beside developing our products, our team are constantly writing articles/tutorials on various topics. You may want to check the latest articles: - -* [What is New in Angular 10?](https://volosoft.com/blog/what-is-new-in-angular-10) -* [Real-Time Messaging In A Distributed Architecture Using ABP, SignalR & RabbitMQ](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ) -* [How to Use Attribute Directives to Avoid Repetition in Angular Templates](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) diff --git a/docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png b/docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png deleted file mode 100644 index 3dd42f950d..0000000000 Binary files a/docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/Post.md b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/Post.md deleted file mode 100644 index 9a30c36d77..0000000000 --- a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/Post.md +++ /dev/null @@ -1,263 +0,0 @@ -# ABP Framework v3.1 RC Has Been Released - -Today, we are releasing the **ABP Framework version 3.1 Release Candidate** (RC). The development cycle for this version was **~7 weeks**. It was the longest development cycle for a feature version release ever. We have completed **~150 issues**, merged **~150 PRs** and made **~1,000 commits** only in the main [abp repository](https://github.com/abpframework/abp). See the related [milestone](https://github.com/abpframework/abp/milestone/38?closed=1) on GitHub. - -There were two main reasons of this long development cycle; - -* We've switched to **4-weeks** release cycle (was discussed in [this issue](https://github.com/abpframework/abp/issues/4692)). -* We've [re-written](https://github.com/abpframework/abp/issues/4881) the Angular service proxy generation system using the Angular schematics to make it more stable. There were some problems with the previous implementation. - -This long development cycle brings a lot of new features, improvements and bug fixes. I will highlight the fundamental features and changes in this blog post. - -## About the Preview/Stable Version Cycle - -As mentioned above, it is planned to release a new stable feature version (like 3.1, 3.2, 3.3...) in every 4-weeks. - -In addition, we are starting to deploy a **preview version** 2-weeks before the stable versions for every feature/major releases. - -Today, we've released `3.1.0-rc.1` as the first preview version. We may release more previews if it is needed until the stable 3.1.0 version. - -**The stable `3.1.0` version will be released on September 3, 2020.** Next RC version, `3.2.0-rc.1`, is planned for September 17, 2020 (2 weeks after the stable 3.1.0 and 2 weeks before the stable 3.2.0). - -We **won't add new features** to a version after publishing the preview version. We only will make **bug fixes** until the stable version. The new features being developed in this period will be available in the next version. - -> We will use `-rc.x` suffix (like `3.1.0-rc.1` and `3.1.0-rc.2`) for preview releases. However, we may also publish with `-preview.x` suffix before RC (Release Candidate) releases, especially for major versions (like 4.0, 5.0...). - -### About the Nightly Builds - -Don't confuse preview versions vs nightly builds. When we say preview, we are mentioning the preview system explained above. - -We will continue to publish **nightly builds** for all the [ABP Framework packages](https://abp.io/packages). Nightly pages are built from the development branch. You can refer to [this document](https://docs.abp.io/en/abp/latest/Nightly-Builds) to learn how to use the nightly packages. - -## Get Started with the RC Versions - -Please try the preview versions and provide feedback to us to release more stable versions. Please open an issue on the [GitHub repository](https://github.com/abpframework/abp/issues/new) if you find a bug or want to give feedback. - -### Update the ABP CLI to the 3.1.0-rc.1 - -Since this is the first preview version, you need to upgrade the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to the `3.1.0-rc.1` to be able to use the preview features: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 3.1.0-rc.1 -```` - -### New Solutions - -The [ABP.IO](https://abp.io/) platform and the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) are compatible with the RC system. You can select the "preview" option on the [download page](https://abp.io/get-started) or use the "**--preview**" parameter with the ABP CLI [new](https://docs.abp.io/en/abp/latest/CLI?_ga=2.106435654.411298747.1597771169-1910388957.1594128976#new) command: - -````bash -abp new Acme.BookStore --preview -```` - -This command will create a new project with the latest RC/Preview version. Whenever the stable version is released, you can switch to the stable version for your solution using the `abp switch-to-stable` command in the root folder of your solution. - -### Existing Solutions - -If you already have a solution and want to use/test the latest RC/Preview version, use the `abp switch-to-preview` command in the root folder of your solution. You can return back to the latest stable using the `abp switch-to-stable ` command later. - -> Note that the `abp switch-to-preview` command was being used to switch to nightly builds before the v3.1. Now, you should use the `abp switch-to-nightly` for [nightly builds](https://docs.abp.io/en/abp/latest/Nightly-Builds). - -## Breaking Changes / Special Notes - -### ABP & ABP Commercial - -* If you are using **EF Core**, you may need to **add a new migration** after upgrading the packages. Just run the standard "Add-Migration" command, check the generated migration code and execute the "Update-Database" command to apply changes to the database. -* If you have implemented **social/external logins** for your MVC / Razor Page UI application before, you may want to check [this issue](https://github.com/abpframework/abp/issues/4981). We made some improvements and changes that you may want to take action for your application. Beginning from v3.1, the users created their accounts via social login can still set a local password to login with local username/email & password. - -### ABP Commercial Only - -* We've **moved favicons** into `/wwwroot/images/favicon/` folder for the ASP.NET Core **MVC / Razor Page UI** applications. There are 10 favicon related files (including the `favicon.ico`) under this directory to better work with different browser and cases. You can create a new application to check this folder and copy the files into your own application. Then you can customize the icons for your own brand (hint: you can use a tool [like that](https://realfavicongenerator.net/) to create the favicons with various formats). -* Removed direct **Twitter & Facebook social login integrations** from the [account module](https://commercial.abp.io/modules/Volo.Account.Pro), for **MVC / Razor Pages UI**. Follow [this documentation](https://github.com/abpframework/abp/blob/dev/docs/en/Authentication/Social-External-Logins.md) to easily add social logins to your applications if you need. The account module provides all the infrastructure to handle social/external logins, you just need to configure it. - -## What's New with the ABP Framework 3.1 RC.1 - -### Angular Service Proxies - -ABP provides a system to generate Angular service proxies (with TypeScript) to consume the HTTP APIs of your application. Service proxy generation system **has been completely re-written** with the ABP Framework 3.1. The main goal was to build more stable and feature rich system that is better aligned with other ABP Framework features (like [modularity](https://docs.abp.io/en/abp/latest/Module-Development-Basics)). - -[See the documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies) to learn more about the service proxy generation for Angular applications. - -### Authorization Code Flow for the Angular UI - -We were using the **resource owner password authentication** flow for the Angular UI login page. We've implemented **Authorization Code Flow** for the Angular account module and made it **default for new projects**. With this change, the Angular application now redirects to the login page of the MVC UI which was implemented using the Identity Server 4. We also removed the client secret from the Angular side with this change. - -Old behavior remains exist. If you want to switch to the new flow (which is recommended), follow the steps below: - -1) Add `authorization_code` to the `IdentityServerClientGrantTypes` table in the database, for the client used by the Angular UI (the `ClientId` is `YourProjectName_App` by default, in the `IdentityServerClients` table). - -2) Add `http://localhost:4200` to `IdentityServerClientRedirectUris` and `IdentityServerClientPostLogoutRedirectUris` tables for the same client. - -3) Set `RequireClientSecret` to `false` in the `IdentityServerClients` table for the same client. - -> [ABP Commercial](https://commercial.abp.io/) users can make these changes on the [Identity Server Management UI](https://commercial.abp.io/modules/Volo.Identityserver.Ui). - -4) Change the `oAuthConfig` section in the `src/environments/environment.ts` file of the Angular application. - -You can take [this new configuration](https://gist.github.com/hikalkan/e7f6ae7f507b201783682dccaeadc5e3) as a reference. Main changes are; - -* Added `responseType` as `code`. -* Added `redirectUri` -* Added `offline_access` to the `scope`. -* Removed `oidc: false` option. -* Removed the client secret option. - -### Global Feature System - -The new "Global Features" system allows to **enable/disable features of an application or a module** in a central point. It is especially useful if you want to use a module but don't want to bring all its features into your application. If the module was so designed, you can enable only the features you need. - -When you disable a feature; - -* The **database tables** related to that feature should not be created in the database. -* The **HTTP APIs** related to that feature should not be exposed. They returns 404 if they are directly requested. - -So, the goal is that; when you disable a feature, it should behave like that feature doesn't exists in your system at all. - -There is **no way to enable/disable a global feature on runtime**. You should decide it in the development time (remember, even database tables are not being created for disabled global features, so you can't enable it on runtime). - -> "Global Features" system is different than [SaaS/multi-tenancy features](https://docs.abp.io/en/abp/latest/Features), where you can enable/disable features for your tenants on runtime. - -Assume that you are using the [CMS Kit module](https://github.com/abpframework/abp/tree/dev/modules/cms-kit) (this module is in a very early stage) where you only want to enable the comment feature: - -````csharp -GlobalFeatureManager.Instance.Modules.CmsKit().Comments.Enable(); -```` - -You can check if a feature was enabled: - -```csharp -GlobalFeatureManager.Instance.IsEnabled(); -``` - -Or you can add `[RequiresGlobalFeature(...)]` attribute to a controller/page to disable it if the related feature was disabled: - -```csharp -//... -[RequiresGlobalFeature(typeof(CommentsFeature))] -public class CommentController : AbpController -{ - //... -} -``` - -See the issue [#5061](https://github.com/abpframework/abp/issues/5061) until this is fully documented. - -### Social/External Logins - -Implemented the infrastructure for social/external logins in the account module. So, now you can easily configure your application to support social/external logins by [following the documentation](https://github.com/abpframework/abp/blob/dev/docs/en/Authentication/Social-External-Logins.md). Once you configure a provider, a button will appear on the login page to use this provider. - -The social logins will work as expected even if you are using the Angular UI, since the Angular UI uses the MVC login using the authorization code flow implemented with this new version (as explained above). - -### Forgot/Reset Password - -Implemented forgot password / password reset for the account module. - -You can now enter your email address to get an email containing a **password reset link**: - -![forgot-password](forgot-password.png) - -When you click to the link, you are redirected to a password reset page to determine your new password: - -![reset-password](reset-password.png) - -### External Login System - -The standard Social/External Login system (like Facebook login) works via OpenID Connect. That means the user is redirected to the login provider, logins there and redirected to your application. - -While this is pretty nice for most scenarios, sometimes you want a simpler external login mechanism: User enters username & password in your own application's login form and you check the username & password from another source, not from your own database. - -ABP v3.1 introduces an External Login System to check username & password from any source (from an external database, a REST service or from an LDAP / Active Directory server). - -You can check the [issue #4977](https://github.com/abpframework/abp/issues/4977#issuecomment-670006297) until it is fully documented. - -We've implemented LDAP authentication for the ABP Commercial, using this new login extension system (see the ABP Commercial section below). - -### User Security Logs - -The new [Security Log System](https://github.com/abpframework/abp/issues/4492) (of the Identity module) automatically logs all authentication related operations (login, logout, change password...) to a `AbpSecurityLogs` table in the database. - -### New BLOB Storage Providers - -Implemented [AWS](https://github.com/abpframework/abp/blob/dev/docs/en/Blob-Storing-Aws.md) and [Aliyun](https://github.com/abpframework/abp/blob/dev/docs/en/Blob-Storing-Aliyun.md) providers for the [BLOB storing](https://docs.abp.io/en/abp/latest/Blob-Storing) system with this version. - -### Module Entity Extensibility - -We had introduced a entity extension system that allows to add new properties to existing entities of depended modules by a simple configuration. When you add a new property, it appears on the create, edit and list views on the UI and created a new field in the related database table. We've implemented this system for the identity and tenant management modules, so you can extend entities of these modules. See [the documentation](https://github.com/abpframework/abp/blob/dev/docs/en/Module-Entity-Extensions.md). - -### Other Features / Highlights - -Here, some other highlights from this release; - -* UOW level caching system [#4796](https://github.com/abpframework/abp/issues/4796) -* Refactored the console application template to better integrate to the host builder [#5006](https://github.com/abpframework/abp/issues/5006) -* [Volo.Abp.Ldap](https://www.nuget.org/packages/Volo.Abp.Ldap) package now supports multi-tenancy. -* Introduce `BasicAggregateRoot` base class [#4808](https://github.com/abpframework/abp/issues/4808) -* Sets GUID Id in the `InsertAsync` method of the EF Core repository if it was not set by the developer [#4634](https://github.com/abpframework/abp/pull/4634) -* Added `GetPagedListAsync` methods to the repository to simplify paging [#4617](https://github.com/abpframework/abp/pull/4617) -* Configured [Prettier](https://prettier.io/) for the startup template [#4318](https://github.com/abpframework/abp/issues/4318) -* Defined new layout hooks for the MVC UI: before page content & after page content [#4008](https://github.com/abpframework/abp/issues/4008) -* Allow to put static resources (js, css... files) under the Components folder for ASP.NET Core MVC UI. -* Upgraded to AutoMapper 10 and Quartz 3.1 for the related integration packages. - -## What's New with the ABP Commercial v3.1 RC.1 - -### Security Logs UI - -We've created a UI to report user security logs for authentication related operations, under the Identity Management menu: - -![security-logs-ui](security-logs-ui.png) - -Also, every user can see his/her own security logs by selecting the "My security logs" under the user menu: - -![my-security-logs](my-security-logs.png) - - - -### LDAP Authentication - -We've implemented LDAP authentication using the new external login system explained above. Also, created a UI to configure the server settings: - -![ldap-settings-ui](ldap-settings-ui.png) - -In this way, you can simply check passwords of the users from LDAP in the login page. If given username / password doesn't exists on LDAP, then it fallbacks to the local database, just like before. - -Since it supports **multi-tenancy**, you can enable, disable and configure it for your tenants. - -### Email / Phone Number Verification - -User profile management page now supports to Email & Phone Number verification flow: - -![email-phone-verification](email-phone-verification.png) - -When user clicks to the **verify** button, a verification email/SMS (that has a verification code) sent to the user and the UI waits to submit this code. - -### User Lock - -Implemented to **lock a user** for a given period of time. Locked users can not login to the application for the given period of time: - -![user-lock](user-lock.png) - -### ABP Suite: Angular UI Code Generation Revisited - -Angular UI code generation has been re-written using the Angular Schematics for the ABP Suite. It is now more stable and produces a better application code. - -ABP Suite also supports code generation on module development. - -### Others - -* **Social logins** and **authorization code flow** are also implemented for the ABP Commercial, just as described above. -* Added breadcrumb and file icons for the **file management module**. - -## The ABP Community - -We've lunched the [community.abp.io](https://community.abp.io/) ~two weeks ago with its initial version. It only has "Article submission" system for now. We are developing new exciting features. There will be an update in a few days and we'll publish a new blog post for it. - -## Conclusion - -The main goals of the 3.1 version were; - -* Complete the missing **authentication features** (like social logins, LDAP authentication, authorization code flow for the Angular UI...) for the ABP Framework & ABP Commercial. -* Re-write a stable and feature complete **Angular service proxy generation** system for the ABP Framework and CRUD UI generation system for the ABP Commercial. -* Develop a system to lunch **preview versions** of the platform. `3.1.0-rc.1` is the first preview version that has been published with this new system. -* Complete the fundamental **documentation & tutorials** (we've even created a [video tutorial series](https://www.youtube.com/watch?v=cJzyIFfAlp8&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ)). - -ABP.IO platform will be more mature & stable with the v3.1. Enjoy Coding! diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/email-phone-verification.png b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/email-phone-verification.png deleted file mode 100644 index 2b4c5ac335..0000000000 Binary files a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/email-phone-verification.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/forgot-password.png b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/forgot-password.png deleted file mode 100644 index c4bc8bff1d..0000000000 Binary files a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/forgot-password.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/ldap-settings-ui.png b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/ldap-settings-ui.png deleted file mode 100644 index da77f3bfeb..0000000000 Binary files a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/ldap-settings-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/my-security-logs.png b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/my-security-logs.png deleted file mode 100644 index 10ac509e14..0000000000 Binary files a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/my-security-logs.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/reset-password.png b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/reset-password.png deleted file mode 100644 index 0dd9c94880..0000000000 Binary files a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/reset-password.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/security-logs-ui.png b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/security-logs-ui.png deleted file mode 100644 index 65c7380a86..0000000000 Binary files a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/security-logs-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/user-lock.png b/docs/en/Blog-Posts/2020-08-20 v3_1_Release/user-lock.png deleted file mode 100644 index f176bf067a..0000000000 Binary files a/docs/en/Blog-Posts/2020-08-20 v3_1_Release/user-lock.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-03 v3_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2020-09-03 v3_1_Release_Stable/POST.md deleted file mode 100644 index fdbeb08c0d..0000000000 --- a/docs/en/Blog-Posts/2020-09-03 v3_1_Release_Stable/POST.md +++ /dev/null @@ -1,62 +0,0 @@ -# ABP Framework 3.1 Final Has Been Released - -It is exciting for us to announce that we've released the ABP Framework & ABP Commercial 3.1 today. - -Since all the new features are already explained in details with the [3.1 RC Announcement Post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released), I will not repeat all the details here. Please read [the RC post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) for **new feature and changes** you may need to do for your solution while upgrading to the version 3.1. - -## Creating New Solutions - -You can create a new solution with the ABP Framework version 3.1 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -````bash -abp update -```` - -After the update command, check [the RC blog post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) to learn if you need to make any changes in your solution. - -> You may want to see the new [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading). - -## About the version 3.2 - -The planned schedule for the version 3.2 is like that; - -* **September 17, 2020**: 3.2.0-rc.1 (release candidate) -* **October 1, 2020**: 3.2.0 final (stable) - -You can check [the GitHub milestone](https://github.com/abpframework/abp/milestone/39) to see the features/issues we are working on. - -## ABP Community & Articles - -We had lunched the [ABP Community web site](https://community.abp.io/) a few weeks before. The core ABP team and the ABP community have started to create content for the community. - -Here, the last three articles from the ABP Community: - -* [ABP Suite: How to Add the User Entity as a Navigation Property of Another Entity](https://community.abp.io/articles/abp-suite-how-to-add-the-user-entity-as-a-navigation-property-of-another-entity-furp75ex) by [@ebicoglu](https://github.com/ebicoglu) -* [Reuse ABP vNext Modules to Quickly Implement Application Features](https://community.abp.io/articles/reuse-abp-vnext-modules-to-quickly-implement-application-features-tdtmwd9w) by [@gdlcf88](https://github.com/gdlcf88) -* [Using DevExtreme Components With the ABP Framework](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) by [@cotur](https://github.com/cotur). - -We are looking for your contributions; You can [submit your article](https://community.abp.io/articles/submit)! We will promote your article to the community. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md b/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md deleted file mode 100644 index 09df3a2119..0000000000 --- a/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md +++ /dev/null @@ -1,431 +0,0 @@ -# Introducing the Angular Service Proxy Generation - -ABP Angular Service Proxy System **generates TypeScript services and models** to consume your backend HTTP APIs developed using the ABP Framework. So, you **don't manually create** models for your server side DTOs and perform raw HTTP calls to the server. - -ABP Framework has introduced the **new** Angular Service Proxy Generation system with the **version 3.1**. While this feature was available since the [v2.3](https://blog.abp.io/abp/ABP-Framework-v2_3_0-Has-Been-Released), it was not well covering some scenarios, like inheritance and generic types and had some known problems. **With the v3.1, we've re-written** it using the [Angular Schematics](https://angular.io/guide/schematics) system. Now, it is much more stable and feature rich. - -This post introduces the service proxy generation system and highlights some important features. - -## Installation - -### ABP CLI - -You need to have the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to use the system. So, install it if you haven't installed before: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -If you already have installed it before, you can update to the latest version: - -````shell -dotnet tool update -g Volo.Abp.Cli -```` - -### Project Configuration - -> If you've created your project with version 3.1 or later, you can skip this part since it will be already installed in your solution. - -For a solution that was created before v3.1, follow the steps below to configure the angular application: - -* Add `@abp/ng.schematics` package to the `devDependencies` of the Angular project. Run the following command in the root folder of the angular application: - -````bash -npm install @abp/ng.schematics --save-dev -```` - -- Add `rootNamespace` entry into the `apis/default` section in the `/src/environments/environment.ts`, as shown below: - -```json -apis: { - default: { - ... - rootNamespace: 'Acme.BookStore' - }, -} -``` - -`Acme.BookStore` should be replaced by the root namespace of your .NET project. This ensures to not create unnecessary nested folders while creating the service proxy code. This value is `AngularProxyDemo` for the example solution explained below. - -* Finally, add the following paths to the `tsconfig.base.json` to have a shortcut while importing proxies: - -```json -"paths": { - "@proxy": ["src/app/proxy/index.ts"], - "@proxy/*": ["src/app/proxy/*"] -} -``` - -## Basic Usage - -### Project Creation - -> If you already have a solution, you can skip this section. - -You need to [create](https://abp.io/get-started) your solution with the Angular UI. You can use the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to create a new solution: - -````bash -abp new AngularProxyDemo -u angular -```` - -#### Run the Application - -The backend application must be up and running to be able to use the service proxy code generation system. - -> See the [getting started](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No) guide if you don't know details of creating and running the solution. - -### Backend - -Assume that we have an `IBookAppService` interface: - -````csharp -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace AngularProxyDemo.Books -{ - public interface IBookAppService : IApplicationService - { - public Task> GetListAsync(); - } -} -```` - -That uses a `BookDto` defined as shown: - -```csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace AngularProxyDemo.Books -{ - public class BookDto : EntityDto - { - public string Name { get; set; } - - public DateTime PublishDate { get; set; } - } -} -``` - -And implemented as the following: - -```csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace AngularProxyDemo.Books -{ - public class BookAppService : ApplicationService, IBookAppService - { - public async Task> GetListAsync() - { - //TODO: get books from a database... - } - } -} -``` - -It simply returns a list of books. You probably want to get the books from a database, but it doesn't matter for this article. - -### HTTP API - -Thanks to the [auto API controllers](https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers) system of the ABP Framework, we don't have to develop API controllers manually. Just **run the backend (*HttpApi.Host*) application** that shows the [Swagger UI](https://swagger.io/tools/swagger-ui/) by default. You will see the **GET** API for the books: - -![swagger-book-list](swagger-book-list.png) - -### Service Proxy Generation - -Open a **command line** in the **root folder of the Angular application** and execute the following command: - -````bash -abp generate-proxy -```` - -It should produce an output like the following: - -````bash -... -CREATE src/app/proxy/books/book.service.ts (446 bytes) -CREATE src/app/proxy/books/models.ts (148 bytes) -CREATE src/app/proxy/books/index.ts (57 bytes) -CREATE src/app/proxy/index.ts (33 bytes) -```` - -> `generate-proxy` command can take some some optional parameters for advanced scenarios (like [modular development](https://docs.abp.io/en/abp/latest/Module-Development-Basics)). You can take a look at the [documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies). - -#### The Generated Code - -`src/app/proxy/books/book.service.ts`: This is the service that can be injected and used to get the list of books; - -````js -import type { BookDto } from './models'; -import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root', -}) -export class BookService { - apiName = 'Default'; - - getList = () => - this.restService.request({ - method: 'GET', - url: `/api/app/book`, - }, - { apiName: this.apiName }); - - constructor(private restService: RestService) {} -} -```` - -`src/app/proxy/books/models.ts`: This file contains the modal classes corresponding to the DTOs defined in the server side; - -````js -import type { EntityDto } from '@abp/ng.core'; - -export interface BookDto extends EntityDto { - name: string; - publishDate: string; -} -```` - -> There are a few more files have been generated to help you import the types easier. - -#### How to Import - -You can now import the `BookService` into any Angular component and use the `getList()` method to get the list of books. - -````js -import { BookService, BookDto } from '../proxy/books'; -```` - -You can also use the `@proxy` as a shortcut of the proxy folder: - -````js -import { BookService, BookDto } from '@proxy/books'; -```` - -### About the Generated Code - -The generated code is; - -* **Simple**: It is almost identical to the code if you've written it yourself. -* **Splitted**: Instead of a single, large file; - * It creates a separate `.ts` file for every backend **service**. **Model** (DTO) classes are also grouped per service. - * It understands the [modularity](https://docs.abp.io/en/abp/latest/Module-Development-Basics), so creates the services for your own **module** (or the module you've specified). -* **Object oriented**; - * Supports **inheritance** of server side DTOs and generates the code respecting to the inheritance structure. - * Supports **generic types**. - * Supports **re-using type definitions** across services and doesn't generate the same DTO multiple times. -* **Well-aligned to the backend**; - * Service **method signatures** match exactly with the services on the backend services. This is achieved by a special endpoint exposed by the ABP Framework that well defines the backend contracts. - * **Namespaces** are exactly matches to the backend services and DTOs. -* **Well-aligned with the ABP Framework**; - * Recognizes the **standard ABP Framework DTO types** (like `EntityDto`, `ListResultDto`... etc) and doesn't repeat these classes in the application code, but uses from the `@abp/ng.core` package. - * Uses the `RestService` defined by the `@abp/ng.core` package which simplifies the generated code, keeps it short and re-uses all the logics implemented by the `RestService` (including error handling, authorization token injection, using multiple server endpoints... etc). - -These are the main motivations behind the decision of creating a service proxy generation system, instead of using a pre-built tool like [NSWAG](https://github.com/RicoSuter/NSwag). - -## Other Examples - -Let me show you a few more examples. - -### Updating an Entity - -Assume that you added a new method to the server side application service, to update a book: - -```csharp -public Task UpdateAsync(Guid id, BookUpdateDto input); -``` - -`BookUpdateDto` is a simple class defined shown below: - -```csharp -using System; - -namespace AngularProxyDemo.Books -{ - public class BookUpdateDto - { - public string Name { get; set; } - - public DateTime PublishDate { get; set; } - } -} -``` - -Let's re-run the `generate-proxy` command: - -````bash -abp generate-proxy -```` - -This command will re-generate the proxies by updating some files. Let's see some of the changes; - -**book.service.ts** - -````js -import type { BookDto, BookUpdateDto } from './models'; -import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root', -}) -export class BookService { - apiName = 'Default'; - - getList = () => - this.restService.request({ - method: 'GET', - url: `/api/app/book`, - }, - { apiName: this.apiName }); - - update = (id: string, input: BookUpdateDto) => - this.restService.request({ - method: 'PUT', - url: `/api/app/book/${id}`, - body: input, - }, - { apiName: this.apiName }); - - constructor(private restService: RestService) {} -} -```` - -`update` function has been added to the `BookService` that gets an `id` and a `BookUpdateDto` as the parameters. - -**models.ts** - -````typescript -import type { EntityDto } from '@abp/ng.core'; - -export interface BookDto extends EntityDto { - name: string; - publishDate: string; -} - -export interface BookUpdateDto { - name: string; - publishDate: string; -} -```` - -Added a new DTO class: `BookUpdateDto`. - -### Advanced Example - -In this example, I want to show a DTO structure using inheritance, generics, arrays and dictionaries. - -I've created an `IOrderAppService` as shown below: - -````csharp -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace AngularProxyDemo.Orders -{ - public interface IOrderAppService : IApplicationService - { - public Task CreateAsync(OrderCreateDto input); - } -} -```` - -`OrderCreateDto` and the related DTOs are as the followings; - -````csharp -using System; -using System.Collections.Generic; -using Volo.Abp.Data; - -namespace AngularProxyDemo.Orders -{ - public class OrderCreateDto : IHasExtraProperties - { - public Guid CustomerId { get; set; } - - public DateTime CreationTime { get; set; } - - //ARRAY of DTOs - public OrderDetailDto[] Details { get; set; } - - //DICTIONARY - public Dictionary ExtraProperties { get; set; } - } - - public class OrderDetailDto : GenericDetailDto //INHERIT from GENERIC - { - public string Note { get; set; } - } - - //GENERIC class - public abstract class GenericDetailDto - { - public Guid ProductId { get; set; } - - public TCount Count { get; set; } - } -} -```` - -When I run the `abp generate-proxy` command again, I see there are some created and updated files. Let's see some important ones; - -`src/app/proxy/orders/order.service.ts` - -````js -import type { OrderCreateDto } from './models'; -import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root', -}) -export class OrderService { - apiName = 'Default'; - - create = (input: OrderCreateDto) => - this.restService.request({ - method: 'POST', - url: `/api/app/order`, - body: input, - }, - { apiName: this.apiName }); - - constructor(private restService: RestService) {} -} -```` - -`src/app/proxy/orders/models.ts` - -````typescript -export interface GenericDetailDto { - productId: string; - count: TCount; -} - -export interface OrderCreateDto { - customerId: string; - creationTime: string; - details: OrderDetailDto[]; - extraProperties: Record; -} - -export interface OrderDetailDto extends GenericDetailDto { - note: string; -} -```` - -## Conclusion - -`abp generate-proxy` is a very handy command that creates all the necessary code to consume your ABP based backend HTTP APIs. It generates a clean code that is well aligned to the backend services and benefits from the power of TypeScript (by using generics, inheritance...). - -## The Documentation - -See [the documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies) for details of the Angular Service Proxy Generation. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/swagger-book-list.png b/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/swagger-book-list.png deleted file mode 100644 index 1cd24d3ec2..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/swagger-book-list.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md deleted file mode 100644 index d789e22977..0000000000 --- a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md +++ /dev/null @@ -1,262 +0,0 @@ -# ABP Framework & ABP Commercial 3.2 RC With The New Blazor UI - -We are extremely excited today to release the [ABP Framework](https://abp.io/) Release Candidate (and the [ABP Commercial](https://commercial.abp.io/), as always). This release includes an early preview version of the **Blazor UI** for the ABP.IO Platform. - -## The Blazor UI - -While the Blazor UI **should be considered as experimental** for now, it is possible to start to develop your application today. - -### Fundamental Services - -Currently, implemented some important framework features; - -* **Authentication** through the MVC backend using the OpenId Connect authorization code flow. So, all the current login options (login, register, forgot password, external/social logins...) are supported. -* **Authorization**, using the ABP Framework **permissions** as well as the standard authorization system. -* **Localization** just works like the MVC UI. -* **Basic Theme** with top main menu. -* **Dynamic C# HTTP API proxies**, so you can directly consume your backend API by injecting the application service interfaces. -* Some other **fundamental services** like `ISettingProvider`, `IFeatureChecker`, `ICurrentUser`... - -Also, the standard .net services are already available, like caching, logging, validation and much more. Since the ABP Framework is layered itself, all the non MVC UI related features are already usable for the Blazor UI. - -### Pre-Built Modules - -Some modules have been implemented; - -* **Identity** module is pre-installed and provides **user, role and permission management**. -* **Profile management** page is implemented to allow to change password and personal settings. - -### About the Blazorise Library - -We've selected the [Blazorise](https://blazorise.com/) as a fundamental UI library for the Blazor UI. It already supports different HTML/CSS frameworks (like Bootstrap, Bulma, Ant Design...) and significantly increases the developer productivity. - -![blazorise-github](blazorise-github.png) - -We also have a good news: **[Mladen Macanović](https://github.com/stsrki)**, the creator of the Blazorise, is **joining to the core ABP Framework team** in the next weeks. We are excited to work with him to bring the power of these two successful projects together. - -### The Tutorial - -We've **updated** the [web application development tutorial](https://docs.abp.io/en/abp/3.2/Tutorials/Part-1?UI=Blazor) for the **Blazor UI**. You can start to develop applications today! The **source code** of the BookStore application developed with this tutorial is [here](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore). - -### Get started with the Blazor UI - -If you want to try the Blazor UI today, follow the instructions below. - -#### Upgrade the ABP CLI - -Install the latest [ABP CLI](https://docs.abp.io/en/abp/3.2/CLI) preview version: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.2 -```` - -#### Create a new Solution - -Then you can create a new solution using the *abp new* command: - -````bash -abp new AbpBlazorDemo -u blazor --preview -```` - -Also specify the `-t app-pro` parameter if you are an ABP Commercial user. - -> See the ABP CLI documentation for the additional options, like MongoDB database or separated authentication server. - -#### Open the Solution - -Open the generated solution using the latest Visual Studio 2019. You will see a solution structure like the picture below: - -![visual-studio-solution-with-blazor](visual-studio-solution-with-blazor.png) - -#### Run the Application - -* Run the `DbMigrator` project to create the database and seed the initial data. -* Run the `HttpApi.Host` project for the server side. -* Run the `Blazor` project to start the Blazor UI. - -Use `admin` as the username and `1q2w3E*` as the password to login to the application. - -Here, a screenshot from the role management page of the Blazor UI: - -![blazor-role-management](blazor-role-management.png) - -## What's New with the ABP Framework 3.2 - -Beside the Blazor UI, there are a lot of issues have been closed with [the milestone 3.2](https://github.com/abpframework/abp/milestone/39?closed=1). I will highlight some of the major features and changes released with this version. - -### MongoDB ACID Transactions - -[MongoDB integration](https://docs.abp.io/en/abp/3.2/MongoDB) now supports multi-document transactions that comes with the MongoDB 4.x. - -We've **disabled transactions** for solutions use the MongoDB, inside the `YourProjectMongoDbModule.cs` file in the MongoDB project. If your MongoDB server **supports transactions**, you should manually enable it in this class: - -```csharp -Configure(options => -{ - options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto; -}); -``` - -> Or you can delete this code since this is already the default behavior. - -#### Upgrade Notes - -If you are upgrading an existing solution and your MongoDB server doesn't support transactions, please disable it: - -```csharp -Configure(options => -{ - options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; -}); -``` - -See the [Unit Of Work document](https://docs.abp.io/en/abp/3.2/Unit-Of-Work) to learn more about UOW and transactions. - -Also, add [this file](https://github.com/abpframework/abp/blob/rel-3.2/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MongoDb/MongoDbMyProjectNameDbSchemaMigrator.cs) into your MongoDB project (remember to change `MongoDbMyProjectNameDbSchemaMigrator` and `IMyProjectNameDbSchemaMigrator` with your own project name). - -#### Integration Tests - -> Transactions are also **disabled for automated integration tests** coming with the application startup template, since the [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) library (we use in the test projects) has a problem with the transactions. We've sent a [Pull Request](https://github.com/Mongo2Go/Mongo2Go/pull/101) to fix it and will enable the transactions again when they merge & release it. -> -> If you are upgrading an existing solution and using MongoDB, please disable transactions for the test projects just as described above. - -### Kafka Integration for the Distributed Event Bus - -ABP Framework's [distributed event system](https://docs.abp.io/en/abp/3.2/Distributed-Event-Bus) has been [integrated to RabbitMQ](https://docs.abp.io/en/abp/3.2/Distributed-Event-Bus-RabbitMQ-Integration) before. By the version 3.2, it has a Kafka integration package, named [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka). - -See the [Kafka integration documentation](https://docs.abp.io/en/abp/3.2/Distributed-Event-Bus-Kafka-Integration) to learn how to install and configure it. - -### Host Features - -[ABP Feature System](https://docs.abp.io/en/abp/3.2/Features) allows you to define features in your application. Then you can enable/disable a feature dynamically on the runtime. It is generally used in a [multi-tenant](https://docs.abp.io/en/abp/3.2/Multi-Tenancy) system to restrict features for tenants, so you can charge extra money for some features in a SaaS application. - -In some cases, you may want to use the same features in the host side (host is you as you are managing the tenants). For this case, we've added a "**Manage Host Features**" button to the Tenant Management page so you can open a modal dialog to select the features for the host side. - -![host-features](host-features.png) - -### AbpHttpClientBuilderOptions - -ABP Framework provides a system to dynamically create C# proxies to consume HTTP APIs from your client applications. `AbpHttpClientBuilderOptions` is a new option class to configure the `HttpClient`s used by the proxy system. - -**Example: Use the [Polly](https://github.com/App-vNext/Polly) library to retry up to 3 times for a failed HTTP request** - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => - { - clientBuilder.AddTransientHttpErrorPolicy(policyBuilder => - policyBuilder.WaitAndRetryAsync( - 3, - i => TimeSpan.FromSeconds(Math.Pow(2, i)) - ) - ); - }); - }); -} -```` - -See the issue [#5304](https://github.com/abpframework/abp/issues/5304) for the details. - -### ABP Build Command - -We are using **mono repository** approach and the [abp repository](https://github.com/abpframework/abp) has tens of solutions and hundreds of projects (the framework, modules, tooling, templates...) with all of them are referencing to each other. - -It gets a significant time to build the whole repository for every Git push. To **optimize** this process, we've created the **abp build** command in the [ABP CLI](https://docs.abp.io/en/abp/3.2/CLI): - -````bash -abp build -```` - -We will use this command to build the abp repository or a solution inside it. However it is available to everyone in case of need. - -> **Most of the people will not need it**. If you need it, see the [ABP CLI](https://docs.abp.io/en/abp/3.2/CLI) document to learn all the details and options. - -### Other Features, Improvements and Changes - -* Introduced the `DynamicRangeAttribute` that can be used to determine the range values on runtime, just like the `DynamicStringLengthAttribute` was introduced before. -* Improved the feature management modal for multi-tenant applications to group features on the UI and show hierarchically. -* Added `--skip-cli-version-check` option to ABP CLI to improve the performance by bypassing the online version check. -* Angular UI now redirect to MVC UI (the authentication server side) for profile management page, if the authorization code flow is used (which is the default). -* Account module profile management page is now extensible. You can implement the `IProfileManagementPageContributor` interface and register it using the `ProfileManagementPageOptions` class. -* Improvements and optimizations for the [Angular service proxy generation](https://blog.abp.io/abp/Introducing-the-Angular-Service-Proxy-Generation). - -And a lot of minor improvements and bug fixes. You can see [the milestone 3.2](https://github.com/abpframework/abp/milestone/39?closed=1) for all issues & PRs closed with this version. - -## What's New with the ABP Commercial 3.2 - -### Breaking Changes - -The new *profile picture management* feature uses the [BLOB storing](https://docs.abp.io/en/abp/3.2/Blob-Storing) system, so it needs a Storage Provider. The new **startup template comes with the [Database BLOB Provider](https://docs.abp.io/en/abp/3.2/Blob-Storing-Database) pre-installed**. You can change it if you want to use another BLOB provider (like Azure, AWS or a simple file system). - -**Existing solutions must configure a BLOB provider** after upgrading to the version 3.2. Follow the [BLOB Storing document](https://docs.abp.io/en/abp/3.2/Blob-Storing#blob-storage-providers) to configure the provider yourself. - -### The Blazor UI - -The **experimental** Blazor UI is also available for the ABP Commercial. The [Lepton Theme](https://commercial.abp.io/themes) hasn't been implemented with this initial preview, however we are working on it with the highest priority. - -You can use the [ABP Suite](https://docs.abp.io/en/commercial/latest/abp-suite/index) or the following ABP CLI command to create a new solution with the Blazor UI: - -````bash -abp new AbpBlazorDemo -u blazor -t app-pro --preview -```` - -Please try it and provide feedback to us. Thanks in advance. - -> See the instructions in the *Get started with the Blazor UI* section above to properly create and run your application. - -### File Management Angular UI - -Angular UI for the [File Management](https://commercial.abp.io/modules/Volo.FileManagement) module is available with the version 3.2. You can add it to your solution using the ABP Suite. - -![file-management-module-angular](file-management-module-angular.png) - -### Profile Picture Management - -We've added profile picture management for the account module, so a user can select one of the options below for her profile picture; - -* Use the default placeholder as the avatar. -* Use [Gravatar](https://gravatar.com/) service to get the picture matching the email address of the user. -* Upload a file as the profile picture. - -![account-profile-picture](account-profile-picture.jpg) - -### Two Factor Authentication Features - -Created [features](https://docs.abp.io/en/abp/3.2/Features) and [settings](https://docs.abp.io/en/abp/3.2/Settings) to disable, enable or force to use 2FA on login for the tenants and users. - -### Upgrading the ABP Suite - -You can use the following command to upgrade the ABP Suite to the latest preview version: - -```` -abp suite update --preview -```` - -## Other News - -### The ABP Community - -**ABP Community** web site is constantly being improved and new articles are added. We will add "**commenting**" and "**rating**" features to the articles soon to increase the interactivity between the people. - -![abp-community-20200917](abp-community-20200917.png) - -If you have something to share with the ABP community or want to follow the project progress, please check the **[community.abp.io](https://community.abp.io/)**! - -### CMS Kit Project - -We are silently working on a project, named [CMS Kit](https://github.com/abpframework/abp/tree/dev/modules/cms-kit), for a few months. CMS Kit is a set of reusable CMS (Content Management System) components based on the ABP Framework. Some of the components currently being developed: - -* **Comments**; Allows users to comment under something (a blog post, a document, an image... etc). -* **Reactions**; Allows users to give reactions to something (a comment, a picture... etc.) using simple emoji icons. -* **Rating**; Allows users to rate some content from 1 to 5. -* **Newsletter**; Allows you to put a newsletter box to your web site to collect emails from users. -* **Contact**; Put a form to get message from the web site visitors. - -There are more planned components like articles, tags, votes, favorites, portfolio, image gallery, FAQ... etc. We will document and deploy these components when they get matured and ready to use. Some of them will be open source & free while some of them are paid (included in the [ABP Commercial](https://commercial.abp.io/) license). - -## Feedback - -Please try the ABP Framework 3.2.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. The planned release date for the [3.2.0 final](https://github.com/abpframework/abp/milestone/43) version is October 01. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/abp-community-20200917.png b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/abp-community-20200917.png deleted file mode 100644 index 1a3997a6ab..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/abp-community-20200917.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/account-profile-picture.jpg b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/account-profile-picture.jpg deleted file mode 100644 index 1b57ce4025..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/account-profile-picture.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazor-role-management.png b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazor-role-management.png deleted file mode 100644 index 520ba18b0c..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazor-role-management.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazorise-github.png b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazorise-github.png deleted file mode 100644 index b5ded34da0..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazorise-github.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/file-management-module-angular.png b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/file-management-module-angular.png deleted file mode 100644 index e424056917..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/file-management-module-angular.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/host-features.png b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/host-features.png deleted file mode 100644 index dbfed05104..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/host-features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/visual-studio-solution-with-blazor.png b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/visual-studio-solution-with-blazor.png deleted file mode 100644 index 097f726bf0..0000000000 Binary files a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/visual-studio-solution-with-blazor.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-10-01 v3_2_Release_Stable/POST.md b/docs/en/Blog-Posts/2020-10-01 v3_2_Release_Stable/POST.md deleted file mode 100644 index 75ea442f0d..0000000000 --- a/docs/en/Blog-Posts/2020-10-01 v3_2_Release_Stable/POST.md +++ /dev/null @@ -1,47 +0,0 @@ -# ABP Framework 3.2 Final Has Been Released - -ABP Framework & ABP Commercial 3.2 have been released today. - -Since all the new features are already explained in details with the [3.2 RC Announcement Post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI), I will not repeat all the details again. Please read [the RC post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI) for **new feature and changes** you may need to do for your solution while upgrading to the version 3.2. - -## Creating New Solutions - -You can create a new solution with the ABP Framework version 3.2 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -````bash -abp update -```` - -After the update command, check [the RC blog post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI) to learn if you need to make any changes in your solution. - -> You may want to see the new [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading). - -## About the Next Versions - -The next two versions (3.3 & 4.0) will be mostly related to completing the Blazor UI features and upgrading the ABP Framework & ecosystem to the .NET 5.0. - -The ultimate goal is to complete the version 4.0 with a stable Blazor UI with the fundamental features implemented and publish it just after the Microsoft lunches .NET 5 in this November. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/POST.md b/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/POST.md deleted file mode 100644 index 886e8a145d..0000000000 --- a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/POST.md +++ /dev/null @@ -1,271 +0,0 @@ -# ABP Framework 3.3 RC Has Been Published - -We have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) `3.3.0-rc.1` today. This blog post introduces the new features and important changes in the new version. - -## Get Started with the 3.3 RC.1 - -If you want to try the version `3.3.0-rc.1` today, follow the steps below; - -1) **Upgrade** the ABP CLI to the version `3.3.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 3.3.0-rc.1 -```` - -**or install** if you haven't installed before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 3.3.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/3.3/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the Preview checkbox. - -## What's new with the ABP Framework 3.3 - -### The Blazor UI - -We had released an experimental early preview version of the Blazor UI with the [previous version](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI). In this version, we've completed most of the fundamental infrastructure features and the application modules (like identity and tenant management). - -It currently has almost the same functionalities as the other UI types (Angular & MVC / Razor Pages). - -**Example screenshot**: User management page of the Blazor UI - -![abp-blazor-ui](abp-blazor-ui.png) - -> We've adapted the [Lepton Theme](https://commercial.abp.io/themes) for the ABP Commercial, see the related section below. - -We are still working on the fundamentals. So, the next version may introduce breaking changes of the Blazor UI. We will work hard to keep them with the minimal effect on your application code. - -#### Breaking Changes on the Blazor UI - -There are some breaking changes with the Blazor UI. If you've built an application and upgrade it, your application might not properly work. See [the migration guide](https://docs.abp.io/en/abp/3.3/Migration-Guides/BlazorUI-3_3) for the changes you need to do after upgrading your application. - -### Automatic Validation for AntiForgery Token for HTTP APIs - -Starting with the version 3.3, all your HTTP API endpoints are **automatically protected** against CSRF attacks, unless you disable it for your application. So, no configuration needed, just upgrade the ABP Framework. - -[See the documentation](https://docs.abp.io/en/abp/3.3/CSRF-Anti-Forgery) to if you want to understand why you need it and how ABP Framework solves the problem. - -### Rebus Integration Package for the Distributed Event Bus - -[Rebus](https://github.com/rebus-org/Rebus) describes itself as "Simple and lean service bus implementation for .NET". There are a lot of integration packages like RabbitMQ and Azure Service Bus for the Rebus. The new [Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus) package allows you to use the Rebus as the [distributed event bus](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) for the ABP Framework. - -See [the documentation](https://docs.abp.io/en/abp/3.3/Distributed-Event-Bus-Rebus-Integration) to learn how to use Rebus with the ABP Framework. - -### Async Repository LINQ Extension Methods - -You have a problem when you want to use **Async LINQ Extension Methods** (e.g. `FirstOrDefaultAsync(...)`) in your **domain** and **application** layers. These async methods are **not included in the standard LINQ extension methods**. Those are defined by the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) NuGet package (see [the code](https://github.com/dotnet/efcore/blob/main/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs)). To be able to use these `async` methods, you need to reference to the `Microsoft.EntityFrameworkCore` package. - -If you don't want to depend on the EF Core in your business layer, then ABP Framework provides the `IAsyncQueryableExecuter` service to execute your queries asynchronously without depending on the EF Core package. You can see [the documentation](https://docs.abp.io/en/abp/latest/Repositories#option-3-iasyncqueryableexecuter) to get more information about this service. - -ABP Framework version 3.3 takes this one step further and allows you to directly execute the async LINQ extension methods on the `IRepository` interface. - -**Example: Use `CountAsync` and `FirstOrDefaultAsync` methods on the repositories** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace MyCompanyName.MyProjectName -{ - public class BookAppService : ApplicationService, IBookAppService - { - private readonly IRepository _bookRepository; - - public BookAppService(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task DemoAsync() - { - var countAll = await _bookRepository - .CountAsync(); - - var count = await _bookRepository - .CountAsync(x => x.Name.Contains("A")); - - var book1984 = await _bookRepository - .FirstOrDefaultAsync(x => x.Name == "1984"); - } - } -} -```` - -All the standard LINQ methods are supported: *AllAsync, AnyAsync, AverageAsync, ContainsAsync, CountAsync, FirstAsync, FirstOrDefaultAsync, LastAsync, LastOrDefaultAsync, LongCountAsync, MaxAsync, MinAsync, SingleAsync, SingleOrDefaultAsync, SumAsync, ToArrayAsync, ToListAsync*. - -This approach still has a limitation. You need to execute the extension method directly on the repository object. For example, the below usage is **not supported**: - -````csharp -var count = await _bookRepository.Where(x => x.Name.Contains("A")).CountAsync(); -```` - -This is because the object returned from the `Where` method is not a repository object, it is a standard `IQueryable`. In such cases, you can still use the `IAsyncQueryableExecuter`: - -````csharp -var count = await AsyncExecuter.CountAsync( - _bookRepository.Where(x => x.Name.Contains("A")) -); -```` - -`AsyncExecuter` has all the standard extension methods, so you don't have any restriction here. See [the repository documentation](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations) for all the options you have. - -> ABP Framework does its best to not depend on the EF Core and still be able to use the async LINQ extension methods. However, there is no problem to depend on the EF Core for your application, you can add the `Microsoft.EntityFrameworkCore` NuGet package and use the native methods. - -### Stream Support for the Application Service Methods - -[Application services](https://docs.abp.io/en/abp/latest/Application-Services) are consumed by clients and the parameters and return values (typically [Data Transfer Objects](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)). In case of the client is a remote application, then these objects should be serialized & deserialized. - -Until the version 3.3, we hadn't suggest to use the `Stream` in the application service contracts, since it is not serializable/deserializable. However, with the version 3.3, ABP Framework properly supports this scenario by introducing the new `IRemoteStreamContent` interface. - -Example: An application service that can get or return streams - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using Volo.Abp.Content; - -namespace MyProject.Test -{ - public interface ITestAppService : IApplicationService - { - Task Upload(Guid id, IRemoteStreamContent streamContent); - Task Download(Guid id); - } -} -```` - -The implementation can be as shown below: - -````csharp -using System; -using System.IO; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Application.Services; -using Volo.Abp.Content; - -namespace MyProject.Test -{ - public class TestAppService : ApplicationService, ITestAppService - { - public Task Download(Guid id) - { - var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.OpenOrCreate); - return Task.FromResult( - (IRemoteStreamContent) new RemoteStreamContent(fs) { - ContentType = "application/octet-stream" - } - ); - } - - public async Task Upload(Guid id, IRemoteStreamContent streamContent) - { - using (var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.Create)) - { - await streamContent.GetStream().CopyToAsync(fs); - await fs.FlushAsync(); - } - } - } -} -```` - -> This is just a demo code. Do it better in your production code :) - -Thanks to [@alexandru-bagu](https://github.com/alexandru-bagu) for the great contribution! - -### Other Changes - -* Upgraded all the .NET Core / ASP.NET Core related packages to the version 3.1.8. If you have additional dependencies to the .NET Core / ASP.NET Core related packages, we suggest you to updates your packages to the version 3.1.8 to have the latest bug and security fixes published by Microsoft. -* The blogging module now uses the [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing) system to store images & files of the blog posts. If you are using this module, then you need to manually migrate the local files to the BLOB Storing system after the upgrade. -* The Angular UI is now redirecting to the profile management page of the MVC UI instead of using its own UI, if you've configured the authorization code flow (which is default since the version 3.2.0). - -## What's new with the ABP Commercial 3.3 - -### The Blazor UI - -We have good news for the ABP Commercial Blazor UI too. We have implemented the [Lepton Theme](https://commercial.abp.io/themes) integration, so it is now available with the Blazor UI. Also, implemented most of the fundamental [modules](https://commercial.abp.io/modules). - -**A screenshot from the ABP Commercial startup template with the Blazor UI** - -![abp-commercial-blazor-ui](abp-commercial-blazor-ui.png) - -There are still missing features and modules. However, we are working on it to have a more complete version in the next release. - -#### Breaking Changes on the Blazor UI - -There are some breaking changes with the Blazor UI. If you've built an application and upgrade it, your application might not properly work. See the [ABP Commercial Blazor UI v 3.3 Migration Guide](https://docs.abp.io/en/commercial/3.3/migration-guides/blazor-ui-3_3) for the changes you need to do after upgrading your application. - -#### Known Issues - -When you create a new project, profile management doesn't work, you get an exception because it can't find the `/libs/cropperjs/css/cropper.min.css` file. To fix the issue; - -* Add `"@volo/account": "^3.3.0-rc.1"` to the `package.json` in the `.Host` project. -* Run `yarn` (or `npm install`), then `abp install-libs` on a command line terminal in the root folder of the `.Host` project. - -### Multi-Tenant Social Logins - -[Account module](https://commercial.abp.io/modules/Volo.Account.Pro) now supports to manage the social/external logins in the UI. You can **enable/disable** and **set options** in the settings page. It also supports to use **different credentials for the tenants** and it is also **configured on the runtime**. - -![abp-commercial-setting-account-external-logins](abp-commercial-setting-account-external-logins.png) - -### Linked Accounts - -Linked user system allows you to link other accounts (including account in a different tenant) with your account, so you can switch between different accounts with a single-click. It is practical since you no longer need to logout and login again with entering the credentials of the target account. - -To manage the linked accounts, go to the profile management page from the user menu; - -![abp-commercial-linked-users](abp-commercial-linked-users.png) - -### Paypal & Stripe Integrations - -The [Payment Module](https://commercial.abp.io/modules/Volo.Payment) was supporting PayU and 2Checkout providers until the version 3.3. It's now integrated to PayPal and Stripe. See the [technical documentation](https://docs.abp.io/en/commercial/latest/modules/payment) to learn how to use it. - -### ABP Suite Improvements - -We've done a lot of small improvements for the [ABP Suite](https://commercial.abp.io/tools/suite). Some of the enhancements are; - -* Show the previously installed modules as *installed* on the module list. -* Switch between the latest stable, the latest [preview](https://docs.abp.io/en/abp/latest/Previews) and the latest [nightly build](https://docs.abp.io/en/abp/latest/Nightly-Builds) versions of the ABP related packages. -* Moved the file that stores the *previously created entities* into the solution folder to allow you to store it in your source control system. - -### Others - -* Added an option to the Account Module to show reCAPTCHA on the login & the registration forms. - -Besides the new features introduced in this post, we've done a lot of small other enhancements and bug fixes to provide a better development experience and increase the developer productivity. - -## New Articles - -The core ABP Framework team & the community continue to publish new articles on the [ABP Community](https://community.abp.io/) web site. The recently published articles are; - -* [Replacing Email Templates and Sending Emails](https://community.abp.io/articles/replacing-email-templates-and-sending-emails-jkeb8zzh) (by [@EngincanV](https://community.abp.io/members/EngincanV)) -* [How to Add Custom Properties to the User Entity](https://community.abp.io/articles/how-to-add-custom-property-to-the-user-entity-6ggxiddr) (by [@berkansasmaz](https://community.abp.io/members/berkansasmaz)) -* [Using the AdminLTE Theme with the ABP Framework MVC / Razor Pages UI](https://community.abp.io/articles/using-the-adminlte-theme-with-the-abp-framework-mvc-razor-pages-ui-gssbhb7m) (by [@mucahiddanis](https://community.abp.io/members/mucahiddanis)) -* [Using DevExtreme Angular Components With the ABP Framework](https://community.abp.io/articles/using-devextreme-angular-components-with-the-abp-framework-x5nyvj3i) (by [@bunyamin](https://community.abp.io/members/bunyamin)) - -It is appreciated if you want to [submit an article](https://community.abp.io/articles/submit) related to the ABP Framework. - -## About the Next Release - -The next version will be `4.0.0`. We are releasing a major version, since we will move the ABP Framework to .NET 5.0. We see that for most of the applications this will not be a breaking change and we hope you easily upgrade to it. - -The planned 4.0.0-rc.1 (Release Candidate) version date is **November 11**, just after the Microsoft releases the .NET 5.0 final. The planned 4.0.0 final release date is **November 26**. - -Follow the [GitHub milestones](https://github.com/abpframework/abp/milestones) for all the planned ABP Framework version release dates. - -## Feedback - -Please check out the ABP Framework 3.3.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. The planned release date for the [3.3.0 final](https://github.com/abpframework/abp/milestone/44) version is October 27th. diff --git a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-blazor-ui.png b/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-blazor-ui.png deleted file mode 100644 index cfe00ac859..0000000000 Binary files a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-blazor-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-blazor-ui.png b/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-blazor-ui.png deleted file mode 100644 index 920585fbd4..0000000000 Binary files a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-blazor-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-linked-users.png b/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-linked-users.png deleted file mode 100644 index 0aaeffc6bc..0000000000 Binary files a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-linked-users.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-setting-account-external-logins.png b/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-setting-account-external-logins.png deleted file mode 100644 index c40b961beb..0000000000 Binary files a/docs/en/Blog-Posts/2020-10-15 v3_3_Preview/abp-commercial-setting-account-external-logins.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-10-27 v3_3_Release_Stable/POST.md b/docs/en/Blog-Posts/2020-10-27 v3_3_Release_Stable/POST.md deleted file mode 100644 index 4f7507cbfd..0000000000 --- a/docs/en/Blog-Posts/2020-10-27 v3_3_Release_Stable/POST.md +++ /dev/null @@ -1,47 +0,0 @@ -# ABP Framework & ABP Commercial 3.3 Final Have Been Released - -ABP Framework & ABP Commercial 3.3.0 have been released today. - -Since all the new features are already explained in details with the [3.3 RC Announcement Post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-v3.3-RC-Have-Been-Released), I will not repeat all the details again. Please read [the RC post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-v3.3-RC-Have-Been-Released) for **new feature and changes** you may need to do for your solution while upgrading to the version 3.3. - -## Creating New Solutions - -You can create a new solution with the ABP Framework version 3.3 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -````bash -abp update -```` - -Run this command in the root folder of your solution. After the update command, check [the RC blog post](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-v3.3-RC-Have-Been-Released) to learn if you need to make any changes in your solution. - -> You may want to see the new [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading). - -## About the Next Version: 4.0 - -The next version will be 4.0 and it will be mostly related to completing the Blazor UI features and upgrading the ABP Framework & ecosystem to the .NET 5.0. - -The goal is to complete the version 4.0 with a stable Blazor UI with the fundamental features implemented and publish it just after the Microsoft lunches .NET 5 in this November. The planned 4.0 preview release date is November 11th. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md b/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md deleted file mode 100644 index 03e3d31f97..0000000000 --- a/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/POST.md +++ /dev/null @@ -1,143 +0,0 @@ -# ABP Framework 4.0 RC Has Been Published based on .NET 5.0! - -Today, we have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) 4.0.0 RC that is based on the **.NET 5.0**. This blog post introduces the new features and important changes in the new version. - -> **The planned release date for the [4.0.0 final](https://github.com/abpframework/abp/milestone/45) version is November 26, 2020**. - -## Get Started with the 4.0 RC - -If you want to try the version `4.0.0` today, follow the steps below; - -1) **Upgrade** the ABP CLI to the version `4.0.0-rc.3` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 4.0.0-rc.3 -```` - -**or install** if you haven't installed before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 4.0.0-rc.3 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/3.3/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -## Migrating From 3.x to 4.0 - -The version 4.0 comes with some major changes including the **migration from .NET Core 3.1 to .NET 5.0**. - -We've prepared a **detailed [migration document](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0)** to explain all the changes and the actions you need to take while upgrading your existing solutions. - -## What's new with the ABP Framework 4.0 - -### The Blazor UI - -The Blazor UI is now stable and officially supported. The [web application development tutorial](https://docs.abp.io/en/abp/4.0/Tutorials/Part-1?UI=Blazor) has been updated based on the version 4.0. - -#### abp bundle command - -Introducing the `abp bundle` CLI command to manage static JavaScript & CSS file dependencies of a Blazor application. This command is currently used to add the dependencies to the `index.html` file in the dependency order by respecting to modularity. In the next version it will automatically unify & minify the files. The documentation is being prepared. - -#### Removed the JQuery & Bootstrap JavaScript - -Removed JQuery & Bootstrap JavaScript dependencies for the Blazor UI. - ->There are some other changes in the startup template and some public APIs. Follow the [Migration Guide](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0) to apply changes for existing solutions that you're upgrading from the version 3.3. While we will continue to make improvements add new features, we no longer make breaking changes on the existing APIs until the version 5.0. - -#### Others - -A lot of minor and major improvements have been done for the Blazor UI. Some of them are listed below: - -* Implemented `IComponentActivator` to resolve the component from the `IServiceProvider`. So, you can now inject dependencies into the constructor of your razor component. -* Introduced the `AbpComponentBase` base class that you derive your components from. It has useful base properties that you can use in your pages/components. -* Introduced `IUiNotificationService` service to show toast notifications on the UI. -* Improved the `IUiMessageService` to show message & confirmation dialogs. - -### System.Text.Json - -ABP Framework 4.0 uses the System.Text.Json by default as the JSON serialization library. It, actually, using a hybrid approach: Continues to use the Newtonsoft.Json when it needs to use the features not supported by the System.Text.Json. - -Follow the [Migration Guide](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0) to learn how to configure to use the Newtonsoft.Json for some specific types or switch back to the Newtonsoft.Json as the default JSON serializer. - -### Identity Server 4 Upgrade - -ABP Framework upgrades the [IdentityServer4](https://www.nuget.org/packages/IdentityServer4) library from 3.x to 4.1.1 with the ABP Framework version 4.0. IdentityServer 4.x has a lot of changes. Some of them are **breaking changes in the data structure**. - -Follow the [Migration Guide](https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0) to upgrade existing solutions. - -### Creating a New Module Inside the Application - -ABP CLI has now a command to create a new module and add it to an existing solution. In this way, you can create modular applications easier than before. - -Example: Create a *ProductManagement* module into your solution. - -````bash -abp add-module ProductManagement --new --add-to-solution-file -```` - -Execute this command in a terminal in the root folder of your solution. If you don't specify the `--add-to-solution-file` option, then the module projects will not be added to the main solution, but the project references still be added. In this case, you need to open the module's solution to develop the module. - -See the [CLI document](https://docs.abp.io/en/abp/4.0/CLI) for other options. - -### WPF Startup Template - -Introducing the WPF startup template for the ABP Framework. Use the ABP CLI new command to create a new WPF application: - -````bash -abp new MyWpfApp -t wpf -```` - -This is a minimalist, empty project template that is integrated to the ABP Framework. - -### New Languages - -**Thanks to the contributors** from the ABP Community, the framework modules and the startup template have been localized to **German** language by [Alexander Pilhar](https://github.com/alexanderpilhar) & [Nico Lachmuth](https://github.com/tntwist). - -### Other Notes - -* Upgraded to Angular 11. -* Since [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) library not supports transactions, you can use transactions in unit tests for MongoDB. - -## What's new with the ABP Commercial 4.0 - -### The Blazor UI - -The Blazor UI for the ABP Commercial is also becomes stable and feature rich with the version 4.0; - -* [ABP Suite](https://commercial.abp.io/tools/suite) now supports to generate CRUD pages for the Blazor UI. -* Completed the [Lepton Theme](https://commercial.abp.io/themes) for the Blazor UI. -* Implemented the [File Management](https://commercial.abp.io/modules/Volo.FileManagement) module for the Blazor UI. - -### The ABP Suite - -While creating create/edit modals with a navigation property, we had two options: A dropdown to select the target entity and a modal to select the entity by searching with a data table. - -Dropdown option now supports **lazy load, search and auto-complete**. In this way, selecting a navigation property becomes much easier and supports large data sets on the dropdown. - -**Example: Select an author while creating a new book** - -![abp-suite-auto-complete-dropdown](abp-suite-auto-complete-dropdown.png) - -With the new version, you can **disable backend code generation** on CRUD page generation. This is especially useful if you want to regenerate the page with a different UI framework, but don't want to regenerate the server side code. - -### Identity Server Management UI Revised - -Completely revised the Identity Server Management UI based on the IDS 4.x changes. - -## About the Next Release - -The next feature version, `4.1.0`, will mostly focus on completing the missing documents, fixing bugs, performance optimizations and improving the Blazor UI features. The planned preview release date for the version `4.1.0` is December 10 and the final (stable) version release date is December 24. - -Follow the [GitHub milestones](https://github.com/abpframework/abp/milestones) for all the planned ABP Framework version release dates. - -## Feedback - -Please check out the ABP Framework 4.0.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. **The planned release date for the [4.0.0 final](https://github.com/abpframework/abp/milestone/45) version is November 26**. diff --git a/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/abp-suite-auto-complete-dropdown.png b/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/abp-suite-auto-complete-dropdown.png deleted file mode 100644 index d349bf0375..0000000000 Binary files a/docs/en/Blog-Posts/2020-11-12 v4_0_Preview/abp-suite-auto-complete-dropdown.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/POST.md b/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/POST.md deleted file mode 100644 index 840b5585fd..0000000000 --- a/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/POST.md +++ /dev/null @@ -1,87 +0,0 @@ -# ABP.IO Platform 4.0 with .NET 5.0 in the 4th Year! - -Today, we are extremely happy to release ABP Framework 4.0 with **.NET 5.0 support**! - -## 4 Years of Work - -As a nice coincidence, today is the **4th year** since the first commit made in the [abp repository](https://github.com/abpframework/abp)! So, we can say "*Happy Birthday ABP Framework!*". - -![abp-contribution-graph-4-years](abp-contribution-graph-4-years.png) - -### Some Statistics - -ABP.IO Platform and the ABP Community is growing. Here, a summary of these 4 years. - -From GitHub, only from the main [abp repository](https://github.com/abpframework/abp); - -* **15,297 commits** done. -* **3,764 issues** are closed. -* **2,133 pull requests** are merged. -* **158 contributors**. -* **88 releases** published. -* **5.2K stars** on GitHub. - -From NuGet & NPM; - -* **220 NuGet** packages & **52 NPM** packages. -* **1,000,000 downloads** only for the core NuGet package. - -From Website; - -* **200,000 visitors**. -* **1,000,000+ sessions**. - -## What's New With 4.0? - -Since all the new features are already explained in details with the [4.0 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-v4.0-RC-Has-Been-Released-based-on-.NET-5.0), I will not repeat all the details again. Please read [the RC post](https://blog.abp.io/abp/ABP.IO-Platform-v4.0-RC-Has-Been-Released-based-on-.NET-5.0) for **new feature and changes** you may need to do for your solution while upgrading to the version 4.0. - -Here, a brief list of major features and changes; - -* Migrated to **.NET 5.0**. -* Stable **Blazor** UI. -* Moved to **System.Text.Json**. -* Upgraded to **IdentityServer** version 4.0. -* **WPF** startup template. - -## Creating New Solutions - -You can create a new solution with the ABP Framework version 4.0 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. - -## How to Upgrade an Existing Solution - -This is a **major version** and requires some **manual work**, especially related to **.NET 5.0** and **IdentityServer** 4.0 upgrades. - -* See the [MIGRATION GUIDE](https://docs.abp.io/en/abp/latest/Migration-Guides/Abp-4_0) that covers all the details about the upgrade progress. - -* You can also see the [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading). - -## New Guides / Documents - -We are constantly improving the documentation. Our purpose is not only document the ABP Framework, but also write architectural and practical guides for developers. - -### Implementing Domain Driven Design - -[Implementing Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design-Implementation-Guide) is a practical guide for they want to implement the DDD principles in their solutions. While the implementation details rely on the ABP Framework infrastructure, core concepts, principles and patterns are applicable in any kind of solution, even if it is not a .NET solution. - -![ddd-implementation-guide-sample](ddd-implementation-guide-sample.png) - -### Testing - -The new [Testing document](https://docs.abp.io/en/abp/latest/Testing) discusses different kind of automated tests and explains how you can write tests for your ABP based solutions. - -### UI Documents - -We've created a lot of documents for the [MVC](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Overall), [Blazor](https://docs.abp.io/en/abp/latest/UI/Blazor/Overall) and the [Angular](https://docs.abp.io/en/abp/latest/UI/Angular/Quick-Start) UI. - -## About the Next Version - -The next versions 4.1 will mostly focus on; - -* Improving current features. -* Complete module features for the Blazor UI. -* Improve developer experience and productivity. -* More documentation and examples. - -Planned preview date for the version **4.1 is December 17, 2020**. See the [Road Map](https://docs.abp.io/en/abp/latest/Road-Map) document and [GitHub Milestones](https://github.com/abpframework/abp/milestones) to learn what's planned for the next versions. We are trying to be clear about the coming features and the next release dates. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/abp-contribution-graph-4-years.png b/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/abp-contribution-graph-4-years.png deleted file mode 100644 index 5c92fb4f5e..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/abp-contribution-graph-4-years.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/ddd-implementation-guide-sample.png b/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/ddd-implementation-guide-sample.png deleted file mode 100644 index 050ed1249e..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-04 v4_0_Release_Stable/ddd-implementation-guide-sample.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/POST.md b/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/POST.md deleted file mode 100644 index 4154bf7241..0000000000 --- a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/POST.md +++ /dev/null @@ -1,202 +0,0 @@ -# ABP Framework 4.1 RC Has Been Published - -Today, we have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) 4.1.0 RC. This blog post introduces the new features and important changes in this new version. - -> **The planned release date for the [4.1.0 final](https://github.com/abpframework/abp/milestone/47) version is January 4, 2021**. - -## Get Started with the 4.1 RC - -If you want to try the version `4.1.0` today, follow the steps below; - -1) **Upgrade** the ABP CLI to the version `4.1.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 4.1.0-rc.1 -```` - -**or install** if you haven't installed before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 4.1.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -## Breaking Changes - -This version has a minor breaking change if you'd injected a repository by class. This is not a problem for 99% of the applications. However, see [#6677](https://github.com/abpframework/abp/issues/6677) for the solution if that's a breaking change for you. - -## What's new with the ABP Framework 4.1 - -### Module Entity Extensions - -Module Entity Extension system provides a simple way of adding new properties to an existing entity defined by a module that is used by your application. This feature is now available also for the open source modules (identity and tenant-management). [The documentation](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) has been moved into the ABP Framework's documentation. - -**Example: Add "SocialSecurityNumber" property to the `IdentityUser` entity** - -````csharp -ObjectExtensionManager.Instance.Modules() - .ConfigureIdentity(identity => - { - identity.ConfigureUser(user => - { - user.AddOrUpdateProperty( //property type: string - "SocialSecurityNumber", //property name - property => - { - //validation rules - property.Attributes.Add(new RequiredAttribute()); - property.Attributes.Add( - new StringLengthAttribute(64) { - MinimumLength = 4 - } - ); - - //...other configurations for this property - } - ); - }); - }); -```` - -The new property becomes available on the UI, API and the database. You can even define navigation properties. This provides an easy way to extend existing modules while using them as NuGet packages. See [the document](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) for details. - -### Blazor UI Improvements - -Since the Blazor UI is relatively new in the ABP Framework, we continue to add features and make enhancements to fill the gap between other supported UI types. - -#### Bundling & Minification - -In the version 4.1, we had introduced the `abp bundle` command for the Blazor UI to add global script and style files of the depended modules into the `index.html`. It was a preparation for a real bundling & minification system. With the version 4.2, this command has been completed. - -Whenever you add a new module to your Blazor application, just type the `abp bundle` command in a command line terminal; - -* It finds all the global script/style files in your application and the modules your application directly or indirectly depends on, ordered by the module dependencies. -* Bundles all the scripts into a single file and minified the file (same for the styles). -* Add the single bundle file to the `index.html` file. - -Added a configuration into the `appsettings.json` file in the Blazor application in the application startup template to control the bundling mode: - -````js -{ - "AbpCli": { - "Bundle": { - "Mode": "BundleAndMinify" - } - } -} -```` - -Possible values are; - -* `BundleAndMinify`: Bundle all the files into a single file and minify the content. -* `Bundle`: Bundle all files into a single file, but not minify. -* `None`: Add files individually, do not bundle. - -See the [Global Scripts & Styles](https://docs.abp.io/en/abp/4.1/UI/Blazor/Global-Scripts-Styles) document for details. - -#### SubmitButton - -`SubmitButton` is a new component that simplifies to save a form: - -````html - -```` - -The main advantages of using this component instead of a standard `Button` with submit type is; It automatically blocks the submit button until the save operation has fully completed. This prevents multiple clicks by user. And it is shorter than doing all manually. See the [document](https://docs.abp.io/en/abp/4.1/UI/Blazor/SubmitButton). - -#### Other Blazor UI highlights - -* Implemented some **animations** (like opening/closing modals and dropdowns). -* Automatically **focus** to the first input when you open a modal form. - -Module extensibility system (mentioned above) for the Blazor UI is under development and not available yet. - -## What's new with the ABP Commercial 4.1 - -### Blazor UI Improvements - -We continue to complete missing modules and functionalities for the Blazor UI. - -#### Organization Unit Management - -Organization Management UI has been implemented for the Blazor UI. Example screenshot: - -![blazor-organization-units](blazor-organization-units.png) - -#### IdentityServer UI - -IdentityServer Management UI is also available for the Blazor UI now: - -![blazor-identityserver-ui](blazor-identityserver-ui.png) - -### Suite: Navigation Property Selection with Typeahead - -We had introduced auto-complete select style navigation property selection. With this release, it is fully supported by all the UI options. So, when you create an CRUD page with ABP Suite for entity that has 1 to Many relation to another entity, you can simply select the target entity with a typeahead style select component. Example screenshot: - -![type-ahead](type-ahead.png) - -### Spanish Language Translation - -We continue to add new language supports for the UI. In this version, translated the UI to **Spanish** language. - -![spanish-commercial-translation](spanish-commercial-translation.png) - -### Coming: Public Website with Integrated CMS Features - -In the next version, the application startup template will come with a public website application option. CMS Kit module will be installed in the website by default, that means newsletter, contact form, comments and some other new features will be directly usable in your applications. - -An early screenshot from the public website application home page: - -![abp-commercial-public-website](abp-commercial-public-website.png) - -## Other News - -### ABP Community Contents - -A lot of new contents have been published in the ABP Community Web Site in the last two weeks: - -* [How to Integrate the Telerik Blazor Components to the ABP Blazor UI](https://community.abp.io/articles/how-to-integrate-the-telerik-blazor-components-to-the-abp-blazor-ui-q8g31abb) by [EngincanV](https://github.com/EngincanV) -* [Using DevExpress Blazor UI Components With the ABP Framework](https://community.abp.io/articles/using-devexpress-blazor-ui-components-with-the-abp-framework-wrpoa8rw) by [@berkansasmaz](https://github.com/berkansasmaz) -* [Creating a new UI theme by copying the Basic Theme (for MVC UI)](https://community.abp.io/articles/creating-a-new-ui-theme-by-copying-the-basic-theme-for-mvc-ui-yt9b18io) by [@ebubekirdinc](https://github.com/ebubekirdinc) -* [Using Angular Material Components With the ABP Framework](https://community.abp.io/members/muhammedaltug) by [@muhammedaltug](https://github.com/muhammedaltug) -* [How to export Excel files from the ABP framework](https://community.abp.io/articles/how-to-export-excel-files-from-the-abp-framework-wm7nnw3n) by [bartvanhoey](https://github.com/bartvanhoey) -* [Creating an Event Organizer Application with the ABP Framework & Blazor UI](https://community.abp.io/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z) by [@hikalkan](https://github.com/hikalkan) - -Thanks to all of the contributors. We are waiting for your contributions too. If you want to create content for the ABP Community, please visit [community.abp.io](https://community.abp.io/) website and submit your article. - -#### Be a Superhero on Day 1 with ABP.IO - -Thanks to [@lprichar](http://github.com/lprichar) prepared an awesome introduction video for the ABP.IO Platform: "[Be a Superhero on Day 1 with ABP.IO](https://www.youtube.com/watch?v=ea0Zx9DLcGA)". - -#### New Sample Application: Event Organizer - -This is a new example application developed using the ABP Framework and the Blazor UI. See [this article](https://community.abp.io/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z) for a step by step implementation guide. - -![event-list-ui](event-list-ui.png) - -### Github Discussions - -We enabled the [GitHub Discussions for the abp repository](https://github.com/abpframework/abp/discussions) as another place to discuss ideas or get help for the ABP Framework. The ABP core team is spending time participating in discussions and answering to questions as much as possible. - -## About the Next Release(s) - -Beginning from the next version (4.2.0), we are starting to spend more effort on the **CMS Kit module**. The purpose of this module is to provide CMS primitives (e.g. **comments, tags, reactions, contents**...) and features (e.g. **blog, pages, surveys**) as pre-built and reusable components. Current blog module will be a part of the CMS Kit module. - -We will continue to prepare documents, guides, tutorials and examples. And surely, we will continue to make enhancements and optimizations on the current features. - -> The planned preview release date for the version 4.2.0 is January 14, 2021 and the final (stable) version release date is January 28, 2021. - -Follow the [GitHub milestones](https://github.com/abpframework/abp/milestones) for all the planned ABP Framework version release dates. - -## Feedback - -Please check out the ABP Framework 4.1.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. **The planned release date for the [4.1.0 final](https://github.com/abpframework/abp/milestone/45) version is January 4, 2021**. diff --git a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/abp-commercial-public-website.png b/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/abp-commercial-public-website.png deleted file mode 100644 index 478c8f4f33..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/abp-commercial-public-website.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-identityserver-ui.png b/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-identityserver-ui.png deleted file mode 100644 index 947e4720af..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-identityserver-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-organization-units.png b/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-organization-units.png deleted file mode 100644 index 45bd98ecd1..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/blazor-organization-units.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/event-list-ui.png b/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/event-list-ui.png deleted file mode 100644 index 4f049c2339..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/event-list-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/spanish-commercial-translation.png b/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/spanish-commercial-translation.png deleted file mode 100644 index d324f29f11..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/spanish-commercial-translation.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/type-ahead.png b/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/type-ahead.png deleted file mode 100644 index 35bca6a495..0000000000 Binary files a/docs/en/Blog-Posts/2020-12-18 v4_1_Preview/type-ahead.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-01-07 v4_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2021-01-07 v4_1_Release_Stable/POST.md deleted file mode 100644 index 926f1193d4..0000000000 --- a/docs/en/Blog-Posts/2021-01-07 v4_1_Release_Stable/POST.md +++ /dev/null @@ -1,51 +0,0 @@ -# ABP.IO Platform 4.1 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 4.1 versions have been released today. - -## What's New With 4.1? - -Since all the new features are already explained in details with the [4.1 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-v4.1-RC-Has-Been-Released), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP.IO-Platform-v4.1-RC-Has-Been-Released) for all the features and enhancements. - -## Creating New Solutions - -You can create a new solution with the ABP Framework version 4.1 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## ABP Community - -We started to get more contributions by the community for the [ABP Community](https://community.abp.io/) contents. Thank you all! - -We will be adding **Video Content** sharing system in a short time. We are planning to create short video contents, especially to explore the new features in every release. Again, we will be waiting video contributions by the community :) - -## About the Next Versions - -Planned preview date for the version **4.2 is January 14, 2021**. See the [Road Map](https://docs.abp.io/en/abp/latest/Road-Map) document and [GitHub Milestones](https://github.com/abpframework/abp/milestones) to learn what's planned for the next versions. We are trying to be clear about the coming features and the next release dates. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/POST.md b/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/POST.md deleted file mode 100644 index d16afb4244..0000000000 --- a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/POST.md +++ /dev/null @@ -1,241 +0,0 @@ -# ABP Framework 4.2 RC Has Been Published - -Today, we have released the [ABP Framework](https://abp.io/) and the [ABP Commercial](https://commercial.abp.io/) 4.2.0 RC (Release Candidate). This blog post introduces the new features and important changes in this new version. - -> **The planned release date for the [4.2.0 final](https://github.com/abpframework/abp/milestone/48) version is January 28, 2021**. - -## Get Started with the 4.2 RC - -If you want to try the version `4.2.0` today, follow the steps below; - -1) **Upgrade** the ABP CLI to the version `4.2.0-rc.2` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 4.2.0-rc.2 -```` - -**or install** if you haven't installed before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 4.2.0-rc.2 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -## What's new with the ABP Framework 4.2 - -## IRepository.GetQueryableAsync() - -> **This version comes with an important change about using `IQueryable` features over the [repositories](https://docs.abp.io/en/abp/4.2/Repositories). It is suggested to read this section carefully and apply in your applications.** - -`IRepository` interface inherits `IQueryable`, so you can directly use the standard LINQ extension methods, like `Where`, `OrderBy`, `First`, `Sum`... etc. - -**Example: Using LINQ directly over the repository object** - -````csharp -public class BookAppService : ApplicationService, IBookAppService -{ - private readonly IRepository _bookRepository; - - public BookAppService(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task DoItInOldWayAsync() - { - //Apply any standard LINQ extension method - var query = _bookRepository - .Where(x => x.Price > 10) - .OrderBy(x => x.Name); - - //Execute the query asynchronously - var books = await AsyncExecuter.ToListAsync(query); - } -} -```` - -*See [the documentation](https://docs.abp.io/en/abp/4.2/Repositories#iqueryable-async-operations) if you wonder what is the `AsyncExecuter`.* - -Beginning from the version 4.2, the recommended way is using `IRepository.GetQueryableAsync()` to obtain an `IQueryable`, then use the LINQ extension methods over it. - -**Example: Using the new GetQueryableAsync method** - -````csharp -public async Task DoItInNewWayAsync() -{ - //Use GetQueryableAsync to obtain the IQueryable first - var queryable = await _bookRepository.GetQueryableAsync(); - - //Then apply any standard LINQ extension method - var query = queryable - .Where(x => x.Price > 10) - .OrderBy(x => x.Name); - - //Finally, execute the query asynchronously - var books = await AsyncExecuter.ToListAsync(query); -} -```` - -ABP may start a database transaction when you get an `IQueryable` (If current [Unit Of Work](https://docs.abp.io/en/abp/latest/Unit-Of-Work) is transactional). In this new way, it is possible to **start the database transaction in an asynchronous way**. Previously, we could not get the advantage of asynchronous while starting the transactions. - -> **The new way has a significant performance and scalability gain. The old usage (directly using LINQ over the repositories) will be removed in the next major version.** You have a lot of time for the change, but we recommend to immediately take the action since the old usage has a big scalability problem. - -#### About IRepository Async Extension Methods - -Using IRepository Async Extension Methods has no such a problem. The examples below are pretty fine: - -````csharp -var countAll = await _personRepository - .CountAsync(); - -var count = await _personRepository - .CountAsync(x => x.Name.StartsWith("A")); - -var book1984 = await _bookRepository - .FirstOrDefaultAsync(x => x.Name == "John"); -```` - -See the [repository documentation](https://docs.abp.io/en/abp/4.2/Repositories#iqueryable-async-operations) to understand the relation between `IQueryable` and asynchronous operations. - -### Repository Bulk Operations - -This version adds the following methods to the repositories: - -* `InsertManyAsync` -* `UpdateManyAsync` -* `DeleteManyAsync` - -The purpose of these methods to insert, update or delete many entities in one call with a better performance. - -Currently, **MongoDB** provider implements these methods as a single bulk operation since MongoDB API natively supports. But current **Entity Framework Core** implementation is not a real bulk operation. Instead, it does its best with the native API of the EF Core. If you want to implement in a more performant way, you can [customize the bulk operations](https://docs.abp.io/en/abp/4.2/Entity-Framework-Core#customize-bulk-operations) with your own implementation or by using a library. We could find a good open source library for EF Core 5.0 to implement it. - -### Selecting DBMS on Template Creation - -[ABP CLI](https://docs.abp.io/en/abp/4.2/CLI#new) now has an option to specify the DBMS when you use EF Core as the database provider. - -**Example: Select MySQL as the DBMS** - -````bash -abp new BookStore -dbms mysql --preview -```` - -Available options: `SqlServer` (default), `MySQL`, `SQLite`, `Oracle-Devart`, `PostgreSQL`. See the [documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS) to use any other DBMS or switch the DBMS later. - -One change related to this feature is that: Now, the startup template doesn't come with an **initial migration** file. This is because the database migrations are different based on your DBMS preference and should be re-created. However, when you first run the `.DbMigrator` application, it will create the initial migration and create the database just like before. - -> See The Initial Migration section in the [Getting Started](https://docs.abp.io/en/abp/4.2/Getting-Started-Running-Solution?DB=EF#database-migrations) document if you have problems on running the `.DbMigrator` application first time. - -### Swagger UI Login / Authorization - -Testing the swagger UI was requiring some additional work, especially your authentication server is separated from the application that hosts the Swagger UI. - -With the version 4.2, the startup templates come with the authorization pre-configured for you. An Authorize button is available when you open the Swagger UI: - -![swagger-authorize](swagger-authorize.png) - -When you click, it opens a modal to authorize: - -![swagger-authorize](swagger-authorize-modal.png) - -When you click to the Authorize button here, you are redirected to the login page to login with your username and password (default username is `admin` and password is `1q2w3E*`). - -> Remember to select the Scopes (typically **select all**) you want to use before clicking to the Authorize button. - -### Angular Unit Testing - -We've improved the modules and the startup template to setup and write unit tests easier with the Angular UI. See the [Angular Unit Testing document](https://docs.abp.io/en/abp/4.2/UI/Angular/Testing) for details. - -### Other News - -* Improved HTTP **request-response performance** by resolving dependencies in a deferred way in the action/page filters, interceptors and some other services. -* Removed `MultipleActiveResultSets` from connection strings for new templates for SQL Server, since the new EF Core gives a warning when using it. If you want to use it, you need to change the connection string yourself. -* Added `HardDeleteAsync` extension method that takes a predicate to delete multiple entities. This extension method is available if the entity [Soft Delete](https://docs.abp.io/en/abp/latest/Data-Filtering). -* Implemented the [Page Alerts](https://docs.abp.io/en/abp/4.2/UI/Angular/Page-Alerts) for the **Angular UI**. -* Implemented [Page Progress](https://docs.abp.io/en/abp/4.2/UI/Blazor/Page-Progress) for the **Blazor UI**. It automatically shows an undetermined progress bar on top of the page while performing an AJAX request. It also proves an API to you if you need to show/hide the progress bar in your code. - -## What's new with the ABP Commercial 4.2 - -### Microservice Startup Template - -The new [Microservice Startup Template](https://docs.abp.io/en/commercial/4.2/startup-templates/microservice/index) is a generic solution to start a new microservice solution. - -While we accept that every microservice solution will be different and every system has its own design requirements and trade-offs, we believe such a startup solution is a very useful starting point for most of the solutions, and a useful example for others. - -![microservice-template-diagram](microservice-template-diagram.png) - -*Figure: A simplified overall diagram of the microservice solution.* - -You can [follow the documentation](https://docs.abp.io/en/commercial/4.2/startup-templates/microservice/index) to get started with this startup template. **This template should be considered as an early release**. We will improve it and write a lot of guides. - -If you want to use the ABP Suite to create your solution, then you need to first upgrade it: - -````bash -abp suite update -```` - -If you want, you can directly create a new solution from the command line: - -````bash -abp new Volosoft.MyMicroserviceSystem -t microservice-pro --preview -```` - -Company Name is optional. Solution name could be *MyMicroserviceSystem* for this example. - -### Public Website in the Startup Templates - -As mentioned in the previous release post, we've added a *Public Website* application to the startup templates. It is configured to authenticate through the IdentityServer with a single sign-on system. - -You can use this application to create a landing page for your actual application or a corporate website for your business. An example screenshot: - -![public-website](public-website.jpg) - -It uses the same *Lepton Theme*, so you can apply [all the styles](https://commercial.abp.io/themes). The Public Website has a different layout and also has a different setting for the styling (that can be configured in the *Settings / Lepton Theme* page of the main web application). - -> *Public Website* is optional and you need to select the "Public Website" option while creating a new solution using the ABP Suite, or use the `--with-public-website` option while using the `abp new` CLI command. - -### Easy CRM Blazor UI - -[Easy CRM](https://docs.abp.io/en/commercial/latest/samples/easy-crm) is an example application built with the ABP Commercial. MVC (Razor Pages) and Angular UI implementations were already provided. With the version 4.2, we are providing the Blazor UI implementation for this application. - -![easy-crm](easy-crm.png) - -### Other News - -* Implemented Iyzico as a payment gateway provider for the [payment module](https://commercial.abp.io/modules/Volo.Payment) in addition to Paypal, Stripe, 2Checkout and Payu providers. -* ABP Suite supports the new microservice template creation, public website and DBMS selection options. -* Swagger authorization and other features mentioned in the ABP Framework section are already implemented for the ABP Commercial too. - -## ABP Community News - -### Sharing Video Contents - -[community.abp.io](https://community.abp.io/) is a place to share ABP related contents. It started with publishing articles. Now, it supports to publish video contents. [See this example](https://community.abp.io/articles/be-a-superhero-on-day-1-with-abp.io-wvifcy9s). All you need to do is to create a video and upload to YouTube. Then you can [submit](https://community.abp.io/articles/submit) the YouTube link to the ABP Community website. - -### Multi-language support - -We planned ABP Community to publish English-only contents. However, we see that people want to share contents in other languages too. Now, **it is possible to submit a content in any language**. Just select the Language option while submitting your content. - -**When you submit a non-English content, it is not visible to all the visitors by default**. Visitors can see a non-English content only if their browser language or the selected language matches to the content language (there is a language selection at the end of the website). - -### External Contents - -If you want to publish your content anywhere else, but want to post a link of your content, you can select *External Content* option while submitting the post. For example, [this article](https://community.abp.io/articles/aspnet-boilerplate-to-abp-framework-xml-to-json-localization-conversion-0mxyjrzj) is an external article and also written in Chinese language. - -## About the Next Release - -The next feature version will be 4.3.0. It is planned to release the 4.3 RC (Release Candidate) on March 11 and the final version on March 25, 2021. - -We decided to slow down the feature development for the [next milestone](https://github.com/abpframework/abp/milestone/49). We will continue to improve the existing features and introduce new ones, sure, but wanted to have more time for the planning, documentation, creating guides and improving the development experience. - -## Feedback - -Please check out the ABP Framework 4.2.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. **The planned release date for the [4.2.0 final](https://github.com/abpframework/abp/milestone/48) version is January 28, 2021**. diff --git a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/easy-crm.png b/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/easy-crm.png deleted file mode 100644 index 72413c382c..0000000000 Binary files a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/easy-crm.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/microservice-template-diagram.png b/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/microservice-template-diagram.png deleted file mode 100644 index f9e3a4f861..0000000000 Binary files a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/microservice-template-diagram.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/public-website.jpg b/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/public-website.jpg deleted file mode 100644 index b5784519bd..0000000000 Binary files a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/public-website.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/swagger-authorize-modal.png b/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/swagger-authorize-modal.png deleted file mode 100644 index 583bebb4d1..0000000000 Binary files a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/swagger-authorize-modal.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/swagger-authorize.png b/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/swagger-authorize.png deleted file mode 100644 index 20d5065b20..0000000000 Binary files a/docs/en/Blog-Posts/2021-01-14 v4_2_Preview/swagger-authorize.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-01-28 v4_2_Release_Stable/POST.md b/docs/en/Blog-Posts/2021-01-28 v4_2_Release_Stable/POST.md deleted file mode 100644 index fac8484411..0000000000 --- a/docs/en/Blog-Posts/2021-01-28 v4_2_Release_Stable/POST.md +++ /dev/null @@ -1,53 +0,0 @@ -# ABP.IO Platform 4.2 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 4.2 versions have been released today. - -## What's New With 4.2? - -Since all the new features are already explained in details with the [4.2 RC Announcement Post](https://blog.abp.io/abp/ABP-IO-Platform-v4-2-RC-Has-Been-Released), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP-IO-Platform-v4-2-RC-Has-Been-Released) for all the features and enhancements. - -## Creating New Solutions - -You can create a new solution with the ABP Framework version 4.2 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guide - -Check [the migration guide](https://docs.abp.io/en/abp/latest/Migration-Guides/Abp-4_2) for the applications with the version 4.x upgrading to the version 4.2. - -> It is strongly recommended to check the migration guide for this version. Especially, the new `IRepository.GetQueryableAsync()` method is a core change should be considered after upgrading the solution. - -## About the Next Version - -The next feature version will be 4.3. It is planned to release the 4.3 RC (Release Candidate) on March 11 and the final version on March 25, 2021. - -We decided to slow down the feature development for the [next milestone](https://github.com/abpframework/abp/milestone/49). We will continue to improve the existing features and introduce new ones, sure, but wanted to have more time for the planning, documentation, creating guides and improving the development experience. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/POST.md b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/POST.md deleted file mode 100644 index 31946691ab..0000000000 --- a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/POST.md +++ /dev/null @@ -1,139 +0,0 @@ -# ABP Commercial 4.3 RC Has Been Published - -ABP Commercial version 4.3 RC (Release Candidate) has been published alongside [ABP Framework 4.3. RC](https://blog.abp.io/abp/ABP-Framework-4.3-RC-Has-Been-Published). I will introduce the new features in this blog post. Here, a list of highlights for this release; - -* The **microservice starter template** is getting more mature. We've also added a **service template** to add new microservices to the solution. -* New option for the application starter template to have a **separate database schema for tenant databases**. -* New **Forms** module to create surveys -* **Enable/disable modules** per edition/tenant. -* **Lepton theme** and **Account module**'s source codes are available with the Team License. - -Here, some other features already covered in the ABP Framework announcement, but worth mentioning here since they are also implemented for the ABP Commercial; - -* **Blazor UI server-side** support - -* **Email setting** management UI -* **Module extensibility** system is now available for the **Blazor UI** too. - -> This post doesn't cover the features and changes done on the ABP Framework side. Please also see the **[ABP Framework 4.3. RC blog post](https://blog.abp.io/abp/ABP-Framework-4.3-RC-Has-Been-Published)**. - -## The Migration Guide - -**This upgrade requires some manual work documented in [the migration guide](https://docs.abp.io/en/commercial/4.3/migration-guides/v4_3).** Please read the guide carefully. Even if your application doesn't break on upgrade, you should apply the changes to avoid future release problems. - -## What's New With The ABP Commercial 4.3 - -### The Microservice Starter Template - -We'd introduced an initial version of the [microservice starter template](https://docs.abp.io/en/commercial/4.3/startup-templates/microservice/index) in the [previous version](https://blog.abp.io/abp/ABP-IO-Platform-v4-2-RC-Has-Been-Released). It is getting more mature with this release. We've made a lot of improvements and changes, including; - -* New **"service" template** to add new microservices for the solution. It still requires some manual work to integrate to other services and gateways; however, it makes progress very easy and straightforward. -* Added [Tye](https://github.com/dotnet/tye) configuration to develop and test the solution easier. -* Added [Prometheus](https://prometheus.io/), [Grafana](https://grafana.com/) integrations for monitoring the solution. -* **Automatic database migrations**. Every microservice automatically checks and migrates/seeds its database on startup (concurrency issues are resolved for multiple instances). For multi-tenant systems, tenant databases are also upgraded by the queue. -* For multi-tenant systems, **databases are being created on the fly** for new tenants with separate connection strings. -* Created **separate solution (`.sln`) file** for each microservice, gateway, and application. In this way, you can focus on what you are working on. The main (roof) solution file only includes the executable projects in these solutions. -* All microservices are converted to the standard **layered module structure**, making it easier to align with ABP application development practices. - -After this release, **we will be preparing microservice development guides** based on this startup solution. - -### Separate Tenant Schema - -ABP's multi-tenancy system allows to the creation of dedicated databases for tenants. However, the application startup solution comes with a single database migration path; hence it has a single database schema. As a result, tenant databases have some host-related tables. These tables are not used for tenants, and they are always empty. However, their existence may disturb us as a clean developer. - -With this release, the application startup template provides an option to address this problem. So, if you want, you can have a separate migration path for tenant databases. Of course, this has a cost; You will have two DbContexts for migration purposes, bringing additional complexity to your solution. We've done our best to reduce this complexity and added a README file into the migration assembly. If you prefer this approach, please check that README file. - -You can specify the new `--separate-tenant-schema` parameter while you are creating a new solution using the [ABP CLI](https://docs.abp.io/en/abp/4.3/CLI): - -````bash -abp new Acme.BookStore --separate-tenant-schema -```` - -If you prefer the [ABP Suite](https://docs.abp.io/en/commercial/latest/abp-suite/create-solution) to create solutions, you can check the *Separated tenant schema* option. - -![abp-suite-separate-tenant-schema](abp-suite-separate-tenant-schema.png) - -### Creating Tenant Databases On The Fly - -With this release, the separate tenant database feature becomes more mature. When you create a new tenant with specifying a connection string, the **new database is automatically created** with all the tables and the initial seed data if available. So, tenants can immediately start to use the new database. With this change, tenant connection string textboxes come in the tenant creation modal: - -![new-tenant-modal](new-tenant-modal.png) - -Besides, we've added an "**Apply database migrations**" action to the tenant management UI to manually trigger the database creation & migration in case you have a problem with automatic migration: - -![tenant-db-migrate](tenant-db-migrate.png) - -Automatic migration only tries one time. If it fails, it writes the exception log and discards this request. For example, this can happen if the connection string is wrong or the database server is not available. In this case, you can manually retry with this action. - -> Note that this feature requires to **make changes in your solution**, if you upgrade from an older version. Because the tenant database creation and migration code are located in the application startup template. See the [version 4.3 migration guide](https://docs.abp.io/en/commercial/4.3/migration-guides/v4_3) for details. - -### New Module: CMS Kit - -CMS Kit module initial version has been released with this version. As stated in the [ABP Framework 4.3 announcement post](https://blog.abp.io/abp/ABP-Framework-4.3-RC-Has-Been-Published), it should be considered premature for now. - -For ABP Commercial application startup template, we are providing an option to include the CMS Kit into the solution while creating new solutions: - -![cms-kit-selection](cms-kit-selection.png) - -It is available only if you select the *Public web site* option. Once you include CMS Kit, a *Cms* item is shown on the menu: - -![cms-kit-menu](cms-kit-menu.png) - -Each CMS Kit feature can be individually enabled/disabled, using the global feature system. Once you disable a feature, it becomes completely invisible; even the related tables are not included in your database. - -CMS Kit features are separated into two categories: Open source (free) features and pro (commercial) features. For now, only newsletter and contact form features are commercial. By the time, we will add more free and commercial features. - -> We will create a separate blog post for the CMS Kit module, so I keep it short. - -### New Module: Forms - -*Forms* is a new module that is being introduced with this version. It looks like the Google Forms application; You dynamically create forms on the UI and send them to people to answer. Then you can get statistics/report and export answers to a CSV file. - -Forms module currently supports the following question types; - -* **Free text** -* Selecting a **single option** from a **dropdown** list or a **radio button** list -* **Multiple choice**: Selecting multiple options from a checkbox list - -**Screenshot: editing form and questions - view responses** - -![forms-edit-report](forms-edit-report.png) - -**Screenshot: answering to the form** - -![forms-answer](forms-answer.png) - - - -### Team License Source Code for Modules - -Team License users can't access the source code of modules and themes as a license restriction. You have to buy a Business or Enterprise license to download any module/theme's full source code. However, we got a lot of feedback from the Team License owners on the source code of the account module and the lepton theme. We see that customization of these two modules is highly necessary for most of our customers. - -With this version, we decided to allow Team License holders to download the source code of the **Account Module** and the **Lepton Theme** to freely customize them based on their requirements. - -You can **Replace these modules with their source code** using the ABP Suite: - -![account-lepton-source](account-lepton-source.png) - -Remember that; when you include the source code in your solution, it is your responsibility to upgrade them when we release new versions (while you don't have to upgrade them). - -### Lepton Theme Public Website Layout - -We'd added a public website application in the application starter template in the previous versions. It was using the public website layout of the Lepton Theme. We realized that the layout of this application is customized or completely changed in most of the solutions. So, with this version, the layout is included inside the application in the downloaded solution. You can freely change it. Before, you had to download it separately and include it in your solution manually. - -### Enable/Disable Modules - -With this release, all modules can be enabled/disabled per edition/tenant. You can allow/disallow modules when you click *Features* action for an edition or tenant: - -![enable-disable-features](enable-disable-features.png) - -### Other Features/Changes - -* ABP Suite now supports defining *required* navigation properties on code generation. -* **Blazor server-side** (with tiered option) is added for the application and microservice starter templates. -* An **"Email"** tab has been added to the Settings page to configure the email settings. - -## Feedback - -Please check out the ABP Commercial 4.3 RC to help us to release a more stable version. **The planned release date for the 4.3.0 final version is April 15, 2021**. - diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/abp-suite-separate-tenant-schema.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/abp-suite-separate-tenant-schema.png deleted file mode 100644 index 4d5d22e1e9..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/abp-suite-separate-tenant-schema.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/account-lepton-source.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/account-lepton-source.png deleted file mode 100644 index 0547e418a9..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/account-lepton-source.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/cms-kit-menu.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/cms-kit-menu.png deleted file mode 100644 index 034782e3f9..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/cms-kit-menu.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/cms-kit-selection.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/cms-kit-selection.png deleted file mode 100644 index 79d8e3d321..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/cms-kit-selection.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/enable-disable-features.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/enable-disable-features.png deleted file mode 100644 index f6077bb4cc..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/enable-disable-features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/forms-answer.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/forms-answer.png deleted file mode 100644 index c6e1679151..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/forms-answer.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/forms-edit-report.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/forms-edit-report.png deleted file mode 100644 index ab8a21eb34..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/forms-edit-report.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/new-tenant-modal.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/new-tenant-modal.png deleted file mode 100644 index 96081e817d..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/new-tenant-modal.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/tenant-db-migrate.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/tenant-db-migrate.png deleted file mode 100644 index a94cde1266..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Commercial Preview/tenant-db-migrate.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Preview/POST.md b/docs/en/Blog-Posts/2021-03-31 v4_3 Preview/POST.md deleted file mode 100644 index e1e04e7a90..0000000000 --- a/docs/en/Blog-Posts/2021-03-31 v4_3 Preview/POST.md +++ /dev/null @@ -1,189 +0,0 @@ -# ABP Framework 4.3 RC Has Been Published - -We are super excited to announce the ABP Framework 4.3 RC (Release Candidate). Here, a list of highlights of this release; - -* **CMS Kit** module initial release. -* **Blazor UI server-side** support. -* **Module extensibility** system for the Blazor UI. -* Angular UI **resource owner password** flow comes back. -* **Volo.Abp.EntityFrameworkCore.Oracle** package is now compatible with .NET 5. -* CLI support to easily add the **Basic Theme** into the solution. -* New **IInitLogger** service to write logs before dependency injection phase completed. - -Besides the new features above, we've done many performance improvements, enhancements and bug fixes on the current features. See the [4.3 milestone](https://github.com/abpframework/abp/milestone/49?closed=1) on GitHub for all changes made on this version. - -This version was a big development journey for us; [~160 issues](https://github.com/abpframework/abp/issues?q=is%3Aissue+milestone%3A4.3-preview+is%3Aclosed) resolved, [~300 PRs](https://github.com/abpframework/abp/issues?q=is%3Apr+milestone%3A4.3-preview+is%3Aclosed) merged and **~1,700 commits** done only in the [main framework repository](https://github.com/abpframework/abp). **Thanks to the ABP Framework team and all the contributors.** - -> ABP Commercial 4.3 RC has also been published. Check out [the commercial blog post](https://blog.abp.io/abp/ABP-Commercial-4.3-RC-Has-Been-Published). - -## The Migration Guide - -We normally don't make breaking changes in feature versions. However, this version has some small **breaking changes** mostly related to Blazor UI WebAssembly & Server separation. **Please check the [migration guide](https://docs.abp.io/en/abp/4.3/Migration-Guides/Abp-4_3) while upgrading to version 4.3**. - -## Known Issues - -Some minor issues will be fixed in the stable release. You can see the known issues [here](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A4.3-final). - -## Get Started With The 4.3 RC - -If you want to try version 4.3 today, follow the steps below; - -1) **Upgrade** the ABP CLI to the version `4.3.0-rc.1` using a command-line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 4.3.0-rc.1 -```` - -**or install** if you haven't installed before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 4.3.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/4.3/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -## What's New With The ABP Framework 4.3 - -### CMS Kit - -CMS (Content Management System) Kit was a module we worked on for the last couple of months. It is usable now, and we are releasing the initial version with this release. We are considering this module as pre-mature. It will be improved in the next versions. The goal to provide a flexible and extensible CMS infrastructure to .NET community. It currently has the following features; - -* **Pages**: Used to create UI pages with a Markdown + WYSIWYG editor. Once you create a page, it becomes available via URL like `/pages/my-page-url`. -* **Blog**: A built-in blog system that supports multiple blogs with blog posts. -* **Comments**: Allows users to write comments under contents. It is used for blog posts. -* **Tags**: To add tag feature to any content/entity. It is used for blog posts. -* **Reactions**: Allows users to react to content via emojis, like a smile, upvote, downvote, etc. -* **Rating**: This component is used to rate content by users. - -All features are separately usable. For example, you can create an image gallery and reuse the Comments and Tags features for the images. You can enable/disable features individually using the [Global Features System](https://docs.abp.io/en/abp/4.3/Global-Features). - -> We will create a separate blog post for the CMS Kit module, so I keep it short. - -### Blazor Server Side - -We'd implemented Blazor WebAssembly before. With version 4.3, we have the Blazor Server-Side option too. All the current functionalities are available to the Blazor Server. - -You can select Blazor Server as the UI type while creating a new solution. - -**Example:** - -````bash -abp new Acme.BookStore -u blazor-server -```` - -If you write `blazor` as the UI type, it will create Blazor WebAssembly just as before. - -> You can also select the Blazor Server on the [get started](https://abp.io/get-started) page. - -Blazor Server applications are mixed applications; You can mix the server-side MVC / Razor Pages with the Blazor SPA. This brings an interesting opportunity: MVC / Razor Pages modules can work seamlessly in the Blazor Server applications. For example, the CMS Kit module has no Blazor UI yet, but you can use its MVC UI inside your Blazor Server application. - -> Blazor Server UI has a `--tiered` option just [like](https://docs.abp.io/en/abp/latest/Startup-Templates/Application#tiered-structure) the MVC / Razor Pages UI. This can be used to separate the HTTP API server from the UI server (UI application doesn't directly connect to the database). - -### Blazor UI Module Extensibility - -Module Entity Extensions and some other extensibility features was not supported by the Blazor UI. With this version, we've implemented that system for Blazor UI. - -For anyone wondering what the module entity extensions is, please check [the document](https://docs.abp.io/en/abp/4.3/Module-Entity-Extensions) or [this community video](https://community.abp.io/articles/overview-of-abp-framework-4.1-module-extensions-part-1-n04f7bhf). - -### Email Setting Management UI - -With this release, a new item is added to the main menu to navigate to the setting management page. This page contains the email setting management UI, as shown below: - -![email-settings-page](email-settings-page.png) - -The setting page is provided by the [setting management module](https://docs.abp.io/en/abp/4.3/Modules/Setting-Management), and it is extensible; You can add your tabs to this page for your application settings. - -### Angular UI Resource Owner Password Flow - -The login page was removed from the Angular UI in previous versions because Authorization Code flow is the recommended approach for SPAs. However, it requires redirecting the user to the authentication server, logging there, and returning to the application. We got a lot of feedback because this brings overhead for simple applications. - -With version 4.3, Angular UI can use its login page with resource owner password flow. Please refer to [the documentation](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Account-Module.md) to learn how to make it work. - -### Volo.Abp.EntityFrameworkCore.Oracle Package - -We couldn't update the [Oracle.EntityFrameworkCore](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/) package on .NET 5.0 upgrade since it was not supporting .NET 5.0 at that time. Now, it supports .NET 5.0 and we've upgraded the package. - -See [the documentation](https://docs.abp.io/en/abp/4.3/Entity-Framework-Core-Oracle-Official) to learn how to switch to this package for the Oracle database. - -### Add Basic Theme Into Your Solution - -ABP Framework provides a strong theming system. However, the default theme, named the Basic Theme, has a non-styled, base Bootstrap UI. It is expected that you override the styles and UI components of that theme in a serious application. - -There are some articles (see for [mvc](https://community.abp.io/articles/creating-a-new-ui-theme-by-copying-the-basic-theme-for-mvc-ui-yt9b18io) & [blazor](https://community.abp.io/articles/creating-a-new-ui-theme-by-copying-the-basic-theme-for-blazor-ui-qaf5ho1b)) to explain how to include the Basic Theme's source code into your solution to modify it fully. However, it still requires some manual work. - -With this version, ABP CLI providing a command to add the Basic Theme's source code into your solution. Run the following command in a command-line terminal inside the root directory of your solution: - -**MVC UI** - -````bash -abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic --with-source-code --add-to-solution -```` - -**Blazor Web Assembly UI** - -````bash -abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme --with-source-code --add-to-solution -abp add-package Volo.Abp.AspNetCore.Components.Web.BasicTheme --with-source-code --add-to-solution -```` - -**Blazor Server UI** - -````bash -abp add-package Volo.Abp.AspNetCore.Components.Server.BasicTheme --with-source-code --add-to-solution -abp add-package Volo.Abp.AspNetCore.Components.Web.BasicTheme --with-source-code --add-to-solution -```` - -As you see, Blazor UI developers should add two packages. The Basic Theme consists of two packages for the Blazor UI: one for wasm/server and one shared. - -**Angular UI** - -Execute the following command in a terminal inside the `angular` folder of your solution: - -````bash -abp add-package @abp/ng.theme.basic --with-source-code -```` - -### IInitLogger - -In ASP.NET Core, logging is not possible before the dependency injection phase is completed. For example, you can't write log in `ConfigureServices` method. However, we sometimes need to write logs in this stage. - -We are introducing the `IInitLogger` service, which allows writing logs inside the `ConfigureServices` method. - -**Example:** - -````csharp -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - var logger = context.Services.GetInitLogger(); - logger.LogInformation("Some log..."); - } -} -```` - -Logs are written once the service registration phase is completed. It stores the written logs in memory and then writes logs to the actual `ILogger` when ready. - -> Notice: Startup templates come with [Serilog](https://serilog.net/) pre-installed. So, you can write logs everywhere by directly using its static API (ex: `Log.Information("...");`). The `InitLogger` is a way to write pre-initialization logs without depending on a particular logging library. So, it makes it very handy to write logs inside reusable modules. - -### Other Features/Changes - -* [#7423](https://github.com/abpframework/abp/issues/7423) MongoDB repository base aggregation API. -* [#8163](https://github.com/abpframework/abp/issues/8163) Ignoring given files on minification for MVC UI. -* [#7799](https://github.com/abpframework/abp/pull/7799) Added `RequiredPermissionName` to `ApplicationMenuItem` for MVC & Blazor UI to easily show/hide menu items based on user permissions. Also added `RequiredPermissionName` to `ToolbarItem` for the MVC UI for the same purpose. -* [#7523](https://github.com/abpframework/abp/pull/7523) Add more bundle methods to the distributed cache. -* [#8013](https://github.com/abpframework/abp/pull/8013) Handle `JsonProperty` attribute on Angular proxy generation. - -See the [4.3 milestone](https://github.com/abpframework/abp/milestone/49) on GitHub for all changes made on this version. - -## Feedback - -Please check out the ABP Framework 4.3 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us release a more stable version. **The planned release date for the [4.3.0 final](https://github.com/abpframework/abp/milestone/50) version is April 15, 2021**. diff --git a/docs/en/Blog-Posts/2021-03-31 v4_3 Preview/email-settings-page.png b/docs/en/Blog-Posts/2021-03-31 v4_3 Preview/email-settings-page.png deleted file mode 100644 index 4e5d2677ca..0000000000 Binary files a/docs/en/Blog-Posts/2021-03-31 v4_3 Preview/email-settings-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-04-05 CmsKit/POST.md b/docs/en/Blog-Posts/2021-04-05 CmsKit/POST.md deleted file mode 100644 index a07928a116..0000000000 --- a/docs/en/Blog-Posts/2021-04-05 CmsKit/POST.md +++ /dev/null @@ -1,85 +0,0 @@ -# Introducing The CMS-Kit Module - -One of the most exciting announcements of the ABP Framework 4.3 release was releasing the initial version of the CMS Kit module. The team had been working hard to release the initial version for months. For those who didn't read the [ABP Framework 4.3 release post](https://blog.abp.io/abp/ABP-Framework-4.3-RC-Has-Been-Published), the CMS Kit module adds CMS(Content Management System) capabilities to your application. Some of the CMS Kit features are open source and free, while others are only included in the Pro version included in the ABP Commercial. - -List of free and pro features in the CMS Kit module; - -- Free - - Page System: Provides a **page** management system to manage dynamic pages. - - Blog System: Provides a **blog** system to create blogs and publish posts. - - Tag System: Provides a **tag** system to tag any kind of resources, such as blog posts. - - Comment System: Provides a **comment** system to add comments feature to any type of resource, such as blog posts, products, etc. - - Reaction System: Provides a **reaction** system to add reactions to any type of resource, such as blog posts or comments, etc. - - Rating System: Provides a **rating** system to add ratings feature to any type of resource, such as comments, products, etc. -- Pro - - Newsletter System: Provides a **newsletter** system to allow users to subscribe to newsletters. - - Contact Form System: Provides a **contact form** system to allow users to write a message to you on public websites. - -## Installation - -CMS Kit module comes installed with commercial templates when you create the solution with the public website option. You can run the following command and create a solution with a public website. - -```bash -abp new Acme --with-public-website -``` - -If you're using the open-source version or adding the module to an existing project, the ABP CLI allows adding a module to a solution using `add-module` command. - -You can run the following command to add the module to an open-source solution. - -```bash -abp add-module Volo.CmsKit --skip-db-migrations true -``` - -If you're a commercial user, run the following command to install the pro version of the module. - -```bash -abp add-module Volo.CmsKit.Pro --skip-db-migrations true -``` - -After adding the module to the solution, you need to configure features. The CMS Kit module uses the [global feature](https://docs.abp.io/en/abp/latest/Global-Features) system for all features. To enable the features in the CMS Kit module, open the `GlobalFeatureConfigurator` class in the `Domain.Shared` project and place the following code to the `Configure` method to enable all features in the CMS kit module. - - ```csharp -GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit => -{ - cmsKit.EnableAll(); -}); - ``` - -If you're a commercial user, you need to enable CMS Kit Pro features as well. - -```csharp -GlobalFeatureManager.Instance.Modules.CmsKitPro(cmsKitPro => -{ - cmsKitPro.EnableAll(); -}); -``` - -> If you are using Entity Framework Core, do not forget to add a new migration and update your database after configuring the features. - -We've completed the installation step. Run the project, and you will see the CMS menu item in the menu. - -![cms-kit-menu](cms-kit-menu.png) - -> CMS Kit's initial release only supports the MVC UI. We're planning to add Angular and Blazor support in upcoming releases. - -## Package Structures - -CMS Kit is designed for various usage scenarios. When you visit [nuget.org](https://www.nuget.org/packages?q=Volo.CmsKit) or [abp.io](https://abp.io/packages?moduleName=Volo.CmsKit.Pro) to see the available CMS Kit packages, you will find out that packages have admin, public and unified versions. - -For example, - - - `Volo.CmsKit.Admin.Application`: Contains functionality required by admin websites. - - `Volo.CmsKit.Public.Application`: Contains functionality required by public websites. - - `Volo.CmsKit.Application` : Unified package dependent on both public and admin packages. - - If you want to separate admin and public website codes, you can use the admin and public packages. However, if you want to keep admin and public website codes in a shared project, you can use the unified packages to include admin and public packages. - -> It is recommended to use the unified packages instead of adding both admin and public packages. - - - -We've covered the initial features, installation and configuration steps in this post. You can read the [open-source](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Index) and [commercial](https://docs.abp.io/en/commercial/latest/modules/CMS-Kit) documentation to get further information about features and the CMS Kit module. CMS Kit's initial version contains lots of features that you can easily integrate and use in your applications. We're planning to improve the existing features, fixing bugs and adding new features in upcoming releases. If you want to give some feedback or have a feature request, please reach out to us from [GitHub](https://github.com/abpframework/abp) or [support.abp.io](https://support.abp.io). We will be happy to plan the CMS Kit module's future together. - -Thank you! - diff --git a/docs/en/Blog-Posts/2021-04-05 CmsKit/cms-kit-menu.png b/docs/en/Blog-Posts/2021-04-05 CmsKit/cms-kit-menu.png deleted file mode 100644 index 24b97e46c6..0000000000 Binary files a/docs/en/Blog-Posts/2021-04-05 CmsKit/cms-kit-menu.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-04-26 v4_3_Release_Stable/POST.md b/docs/en/Blog-Posts/2021-04-26 v4_3_Release_Stable/POST.md deleted file mode 100644 index d06e2370e0..0000000000 --- a/docs/en/Blog-Posts/2021-04-26 v4_3_Release_Stable/POST.md +++ /dev/null @@ -1,53 +0,0 @@ -# ABP.IO Platform 4.3 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 4.3 versions have been released today. - -## What's New With 4.3? - -Since all the new features are already explained in details with the 4.3 RC announcement posts, I will not repeat all the details again. See the related blog posts for all the features and enhancements; - -* [What's New with the ABP Framework 4.3](https://blog.abp.io/abp/ABP-Framework-4.3-RC-Has-Been-Published) -* [What's new with the ABP Commercial 4.3](https://blog.abp.io/abp/ABP-Commercial-4.3-RC-Has-Been-Published) - -## The Quick Start Tutorial - -With this version, we've created a minimalist [quick start tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Todo/Index) for developers who are new to the ABP Framework. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -**Check the migration guides ([for ABP Framework](https://docs.abp.io/en/abp/latest/Migration-Guides/Abp-4_3) & for [ABP Commercial](https://docs.abp.io/en/commercial/latest/migration-guides/v4_3)) for the applications with the version 4.2.x upgrading to the version 4.3.0.** - -## The Road Map - -The next feature version will be 4.4. It is planned to release the 4.4 RC (Release Candidate) at the end of Quarter 2, 2021. See the updated road maps; - -* [ABP Framework Road Map](https://docs.abp.io/en/abp/latest/Road-Map) -* [ABP Commercial Road Map](https://docs.abp.io/en/commercial/latest/road-map) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/POST.md b/docs/en/Blog-Posts/2021-05-24-LeptonX/POST.md deleted file mode 100644 index c74a2cb4bd..0000000000 --- a/docs/en/Blog-Posts/2021-05-24-LeptonX/POST.md +++ /dev/null @@ -1,69 +0,0 @@ -# Introducing the Lepton Theme Next Generation! - -We are delighted and proud to announce that the next generation of [Lepton Theme](https://leptontheme.com/) which will be referred to as LeptonX is on the way. - -At Volosoft, we care about developer experience as much as user experience. Thus, LeptonX is following the latest trends for a better user experience and provides highly customizable features that will be easy to use. Our APIs follow a strict guideline to be easy-to-use, intuitive, extendable, and replaceable. In short, we'd like LeptonX to grow into such a platform that developers (not just ABP developers) love to build their applications on top of, and end-users love to interact with. - -The initial version has been built upon Bootstrap v4. Bootstrap v5 has been recently published and the ecosystem has not caught up yet. In addition, ABP packages are already dependent on Bootstrap v4, it will be easy to switch from the [Basic Theme](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Basic-Theme) or from the [Lepton Theme](https://commercial.abp.io/themes) to the LeptonX. - -We believe that the bigger the community is, the better the framework turns out to be. As we have done in our other products, we will release a free-to-use version of LeptonX, a.k.a LeptonX-lite which will contain most of the basic features. With LeptonX-lite, free ABP [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application)s will come with a production-ready theme, and existing applications will be able to switch from the Basic Theme to the LeptonX-lite with ease. - -## Highlights - -In this section, I want to highlight some of the LeptonX features. - -### Better Mobile UX - -When people talk about Responsive Web Applications, they usually refer to a side menu or top menu that shrinks down into a hamburger menu which does not provide a good user experience for mobile users. - -On the other hand, widely used native mobile applications usually employ a tab menu stick to the bottom of the screen. People have become acquainted with this type of experience. With this UX in mind, menus in the `LeptonX` theme switches to the tab menu on the mobile resolution as opposed to the hamburger menu. - -![Three iPhones showing LeptonX theme for mobile resolution in dark, dim, and light themes](./mobiles.png) -![Three iPads showing LeptonX theme for tablet resolution in dark, dim and light themes](./tablets.png) - -### Theme Build System - -Following the latest trends, LeptonX comes with three built-in theme styles; `dark`, `dim`, `light`. -A theme builder system (written in SASS) is provided with the LeptonX which you can run with your brand colors and create your own theme style as well. - -LeptonX is built on top of CSS variables that enable runtime theme building. In the future, we may release a color picker that can create custom themes. Each user can create their own color palettes. - -### Multiple Layouts - -LeptonX is being developed with such an architecture that it will be easy to replace the layout. You will be given many options to choose any layout you prefer. - -Take a look at the following layouts: - -![An image showing default layout for LeptonX lite and pro packages](./default-layout.png) -![An image showing top menu layout](./top-menu-layout.png) - -## The LeptonX Ecosystem - -The previous version of Lepton Theme has been only available to Commercial ABP customers and it has never been as a standalone (ABP independent) template. With LeptonX, we aim at a broader audience. With this goal in mind, LeptonX can be integrated into any tech stack you'd prefer as well as the ABP Framework. That's why you'll be offered a variety of packages as follows: - -- A template with HTML, VanillaJS, CSS (can be used as ABP independent) - - You can just download this like any other template and start developing your application. -- An angular library (can be used as ABP independent) - - An upgradeable angular package contains layouts, components, directives, and services for you to build any application you'd like. If you choose this option, you'll be able to stay up-to-date with LeptonX as we will continue to introduce new features. -- A standalone Angular template that contains code of the npm package above (can be used as ABP independent) - - You can also download an angular template that contains the library above. You can edit it as you see fit, however, it will be harder to integrate new features once they become available. -- Free ABP packages for all of the clients available, MVC, Blazor, and Angular that utilizes the lite packages - - Client-specific packages that employ LeptonX-lite for open-source ABP users. -- Commercial ABP packages for all of the clients available, MVC, Blazor, and Angular that utilizes Pro (full version) packages - - Client-specific packages that employ, provide and extend all of the features that LeptonX-Pro packages have. - -As is seen above, the LeptonX ecosystem contains multiple projects that are designed for different kinds of users. - -## The LeptonX-lite - -As mentioned above, there will be a free-to-use version of LeptonX. It will contain most of the basic features and components that the pro version has. It will be also a one-line change to upgrade from the lite version to the pro (full) version. Here, some of the differences between the lite and pro versions: - -- The mobile tab navigation bar shown above will be only available to pro. In the lite version, a hamburger menu will take its place. -- The lite version will be released just with the side menu layout although we may release more in the future as our infrastructure makes it easy to develop and replace layouts. -- Different color options and the ability to change colors during runtime are pro-only features. CSS of the lite version is built with the `dim` colors. - -## Alpha Version - -As we have mentioned above, both user and developer experience means a great deal to us. Therefore, we will release an alpha version of the LeptonX-lite package and the free ABP-based Angular package soon to start collecting feedback from our users. Once the packages are released, we would like you to try them out and give us feedback. It will help us improve the LeptonX greatly. We plan to release a stable version later this year. - -To stay up-to-date with LeptonX and be notified once the alpha version is ready, follow our [Twitter account](https://twitter.com/volosoftcompany). \ No newline at end of file diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/default-layout.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/default-layout.png deleted file mode 100644 index 84ef243e52..0000000000 Binary files a/docs/en/Blog-Posts/2021-05-24-LeptonX/default-layout.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/leptonx-intro-cover-image.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/leptonx-intro-cover-image.png deleted file mode 100644 index a7649c1599..0000000000 Binary files a/docs/en/Blog-Posts/2021-05-24-LeptonX/leptonx-intro-cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/mobiles.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/mobiles.png deleted file mode 100644 index 5c90a36376..0000000000 Binary files a/docs/en/Blog-Posts/2021-05-24-LeptonX/mobiles.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/tablets.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/tablets.png deleted file mode 100644 index 79dd826a3a..0000000000 Binary files a/docs/en/Blog-Posts/2021-05-24-LeptonX/tablets.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/top-menu-layout.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/top-menu-layout.png deleted file mode 100644 index 751504bf10..0000000000 Binary files a/docs/en/Blog-Posts/2021-05-24-LeptonX/top-menu-layout.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/POST.md b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/POST.md deleted file mode 100644 index dff6d1af77..0000000000 --- a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/POST.md +++ /dev/null @@ -1,174 +0,0 @@ -# ABP Platform 4.4 RC Has Been Released - -Today, we have released the [ABP Framework](https://abp.io/) and the [ABP Commercial](https://commercial.abp.io/) version 4.4 RC (Release Candidate). This blog post introduces the new features and important changes in this new version. - -> **The planned release date for the [4.4.0 final](https://github.com/abpframework/abp/milestone/53) version is July 13, 2021**. - -## Get Started with the 4.4 RC - -If you want to try the version 4.4.0 today, follow the steps below; - -1) **Upgrade** the ABP CLI to the version `4.4.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 4.4.0-rc.1 -```` - -**or install** if you haven't installed before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 4.4.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -### Migration Notes - -There is **no breaking change** with this version. However, if you are using Entity Framework Core, you will need to run the `Add-Migration` command to add a new **database migration** since some changes done in the module database mappings. - -## What's new with the ABP Framework 4.4 - -### Removed EntityFrameworkCore.DbMigrations Project - -With this version, we are doing an important change in the application startup solution template. The startup solution was containing an `EntityFrameworkCore.DbMigrations` project that contains a separate `DbContext` class which was responsible to unify the module database mappings and maintain the code-first database migrations. With the v4.4, we've removed that project from the solution. In the new structure, the `EntityFrameworkCore` integration project will be used for database migrations as well as on runtime. - -We'd published [a community article](https://community.abp.io/articles/unifying-dbcontexts-for-ef-core-removing-the-ef-core-migrations-project-nsyhrtna) about that change. Please see the article to understand the motivation behind the change. - -Beside the `DbContext` unification, we've also used the new `ReplaceDbContext` attribute and [replaced](https://github.com/abpframework/abp/blob/ea2205f0855f52015152ae066a5c239af4b8511f/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameDbContext.cs#L18-L19) the `IIdentityDbContext` and `ITenantManagementDbContext` interfaces to make it possible to perform join queries over repositories for these modules easily. In the next days, we will publish another community article to explain the problem and the solution. However, most of times, you don't need to know these details. - -### Dynamic Menu Management for the CMS Kit Module - -CMS Kit is a set of reusable *Content Management System* features packaged as an ABP application module. We had published the first usable version with the previous release. With this release, we are adding another feature to the CMS Kit module: You can now dynamically arrange the main menu on the UI, which is an essential feature for any kind of content management system. In this way, you can add pages or any kind of arbitrary URLs to the main menu from the UI. - -A screenshot from the menu management page (from the CMS Kit admin side): - -![menu-items-admin](menu-items-admin.png) - -And the items rendered in a public website: - -![menu-items-public](menu-items-public.png) - -Note that this feature is also available with the open source [CMS Kit module](https://docs.abp.io/en/abp/4.4/Modules/Cms-Kit/Index) (while the screenshots have been taken from the ABP Commercial). - -### Razor Engine Support for Text Templating - -[Text Templating](https://docs.abp.io/en/abp/4.4/Text-Templating) is a system to generate content on runtime by using a model (data) and a template. It was running on the [Scriban](https://github.com/scriban/scriban) templating engine. Beginning from this version, we have a second option: We can use the familiar **razor syntax** to build and render the templates. See the text templating [razor integration document](https://docs.abp.io/en/abp/4.4/Text-Templating-Razor) to get started with the new engine! - -### New Customization Points for DbContext/Entities - -Two new extension methods are added to `ObjectExtensionManager.Instance` to override EF Core mappings of [pre-built application modules](https://docs.abp.io/en/abp/latest/Modules/Index). - -**Example: Change mappings for the `IdentityDbContext` to override mappings for the `IdentityUser` entity** - -````csharp -ObjectExtensionManager.Instance.MapEfCoreDbContext(modelBuilder => -{ - modelBuilder.Entity(b => - { - b.ToTable("MyUsers"); - b.Property(x => x.Email).HasMaxLength(300); - }); -}); -```` - -The startup template contains a class, like `YourProjectNameEfCoreEntityExtensionMappings`, that can be used to place that code. - -### New ABP CLI Commands - -There are new [ABP CLI](https://docs.abp.io/en/abp/4.4/CLI) commands introduced with the v4.4: - -* `abp install-libs` command is used for MVC / Razor Pages and Blazor Server applications to restore the `wwwroot/libs` folder. Previously we were running the `yarn` and `gulp` commands to restore that folder. While the `install-libs` command still uses yarn (if available), it is no longer needed to use `gulp`. -* `abp prompt` command can be used to open a prompt for the ABP CLI and run multiple commands without needing to specify the `abp` command every time. For example, if you run `abp prompt`, then you can directly run `install-libs` instead of `abp install-libs`. Use `exit` to quit from the ABP prompt. -* `abp batch` command can be used to run multiple ABP commands with one command. Prepare a text file, write each command as a line (without the `abp` command prefix), then execute `abp batch ` (ex: `abp batch your_commands.txt`) command to execute all the commands in that file. - -### appsettings.secrets.json - -Added `appsettings.secrets.json` to the startup template that can be used to set your sensitive/secret configuration values. You can ignore this file from source control (by adding to `.gitignore` if you're using git) and keep it only in developer/production machines. - -### Other ABP Framework Improvements - -* [#9350](https://github.com/abpframework/abp/pull/9350) Extracted `IRemoteServiceConfigurationProvider` to get remote service configurations. You can replace this service to get the configuration from any source. -* [#8829](https://github.com/abpframework/abp/pull/8829) Implemented error handler and retry for distributed event bus. -* [#9288](https://github.com/abpframework/abp/issues/9288) Use default CORS policy instead of a named one in the startup template. It is suggested to update your own solutions to make it simpler. -* Translated the framework and module localization strings to Hindi, Italian, Finnish and French languages. - -Beside these, there are a lot of enhancements and bug fixes. See the [4.4-preview milestone](https://github.com/abpframework/abp/milestone/52?closed=1) for all issues and pull requests closed with this version. - -## What's new with the ABP Commercial 4.4 - -### New Features for the SaaS Module - -We've implemented some important features to the [SaaS module](https://commercial.abp.io/modules/Volo.Saas): - -* Integrated to the [Payment module](https://commercial.abp.io/modules/Volo.Payment) and implemented **subscription system** for the SaaS module. -* Allow to make a **tenant active/passive**. In this way, you can take a tenant to passive to prevent the users of that tenant from using the system. In addition, you can set a date to automatically make a tenant passive when the date comes. -* Allow to **limit user count** for a tenant. -* Allow to set **different connection strings** for a tenant for each database/module, which makes possible to create different databases for a tenant for each microservice in a microservice solution. - -### New ABP Suite Code Generation Features - -There are many improvements done for for [ABP Suite](https://commercial.abp.io/tools/suite), including CRUD page generation for the **[microservice solution](https://docs.abp.io/en/commercial/latest/startup-templates/microservice/index) template**. - -### Angular UI: Two Factor Authentication for the Resource Owner Password Flow - -In the previous version, we had implemented the resource owner password authentication flow for the Angular UI, which makes the login process easier for simpler applications. With this release, we've implemented two-factor authentication for that flow. Authorization code flow already supports 2FA. - -### Other ABP Commercial Improvements - -* Added web layers to microservices in the microservice solution. You can use them to create modular UI or override existing pages/components of pre-built modules (e.g. Identity and SaaS). -* ABP Commercial license code has been moved to `appsettings.secrets.json` in the new startup templates. -* Added new language options: Hindi, Italian, Arabic, Finnish, French. - -Beside these, there are many minor improvements and fixes done in the modules and themes. - -## Other News - -In this section, I will share some news that you may be interested in. - -### New Article: Using Elsa Workflow with ABP Framework - -We have been frequently asked how to use [Elsa Workflows](https://elsa-workflows.github.io/elsa-core/) with the ABP Framework. Finally, we have [created an article](https://community.abp.io/articles/using-elsa-workflow-with-the-abp-framework-773siqi9) to demonstrate it. - -![elsa-overview](elsa-overview.gif) - -You can [check it](https://community.abp.io/articles/using-elsa-workflow-with-the-abp-framework-773siqi9) to see how to integrate Elsa into an ABP based solution easily. - -### Free E-Book: Implementing Domain Driven Design - -We've published a free e-book for the ABP Community in the beginning of June. This is a practical guide for implementing Domain Driven Design (DDD). While the implementation details are based on the ABP Framework infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution. - -![ddd-book](ddd-book.png) - -Thousands of copies are already downloaded. If you haven't seen it yet, [click here to get a free copy of that e-book](https://abp.io/books/implementing-domain-driven-design). - -### The LeptonX Theme - -We have been working on a new ABP theme, named the *LeptonX*, for a long time. The theme will be available for ABP Framework (free - lite version) and ABP Commercial (pro version). It is being finalized in the next weeks and we will release the first version in a short time. - -![leptonx](leptonx.png) - -See [this blog post](https://volosoft.com/blog/introducing-the-lepton-theme-next-generation) to learn more about that project. - -### Volosoft & .NET Foundation - -[Volosoft](https://volosoft.com/), the company leads the ABP Framework project, has been a corporate sponsor of the [.NET Foundation](https://dotnetfoundation.org/). We are happy by taking our place among other great sponsors! - -![dotnetfoundation-sponsor-volosoft](dotnetfoundation-sponsor-volosoft.png) - -We will continue to contribute to and support open source! See this [blog post for the announcement](https://volosoft.com/blog/Volosoft-Announces-the-NET-Foundation-Sponsorship). - -### Looking for Developer Advocate(s) - -We are actively looking for professional developer advocates for the ABP.IO platform. If you want to create content and touch to the ABP community, please check our [job post](https://github.com/volosoft/vs-home/issues/13). - -## About the Next Version - -The next version will be a major version: 5.0, which will be based on .NET 6.0. We are planning to release it in the end of 2021, short time after the .NET 6.0 release. We will release multiple preview/beta versions before the RC version. See the [road map](https://docs.abp.io/en/abp/latest/Road-Map) for details of the planned works for the version 5.0. diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/abp-44-cover.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/abp-44-cover.png deleted file mode 100644 index aeaeefa19f..0000000000 Binary files a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/abp-44-cover.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/ddd-book.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/ddd-book.png deleted file mode 100644 index 747fcc1d13..0000000000 Binary files a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/ddd-book.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/dotnetfoundation-sponsor-volosoft.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/dotnetfoundation-sponsor-volosoft.png deleted file mode 100644 index 87fd79109a..0000000000 Binary files a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/dotnetfoundation-sponsor-volosoft.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/elsa-overview.gif b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/elsa-overview.gif deleted file mode 100644 index ba589cabfc..0000000000 Binary files a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/elsa-overview.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/leptonx.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/leptonx.png deleted file mode 100644 index a7649c1599..0000000000 Binary files a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/leptonx.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-admin.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-admin.png deleted file mode 100644 index d344f432bc..0000000000 Binary files a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-admin.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-public.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-public.png deleted file mode 100644 index 42c12c6801..0000000000 Binary files a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-public.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/POST.md b/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/POST.md deleted file mode 100644 index efba693145..0000000000 --- a/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/POST.md +++ /dev/null @@ -1,52 +0,0 @@ -# Introducing the eShopOnAbp Project - -We are happy to introduce the **eShopOnAbp** project as an example microservice solution built with the ABP Framework by the core ABP team. This solution demonstrates the strength of ABP Framework and using it in a real-life case. The goal of the project is to create a full-featured cloud-native microservices reference application. The project is inspired by the [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers) project and shows how it can be implemented with the ABP Framework. - -> **Project Status**: Currently, the project doesn't have any business logic. We've just brought ABP's pre-built modules (Identity, Tenant Management, IdentityServer, etc) together as a base solution. However, it is fully working and you can now take it as a base solution for your microservice project. From now on, we will build the example application functionalities / business logic on top of it. - -## Source Code - -The source code is available on [abpframework/eShopOnAbp](https://github.com/abpframework/eShopOnAbp) repository. - -## The Big Picture - -The project follows micro-service architecture and overall structure is presented below. - -![eShopOnAbp Overall Solution](overall-solution.png) - -## How to Run? - -You can either run in Visual Studio, or using [Microsoft Tye](https://github.com/dotnet/tye). Tye is a developer tool that makes developing, testing, and deploying micro-services and distributed applications easier. - - ### Requirements - -- .NET 5.0+ -- Docker -- Yarn - -### Instructions - -- Clone the repository ( [eShopOnAbp](https://github.com/abpframework/eShopOnAbp) ) - -- Install Tye (*follow [these steps](https://github.com/dotnet/tye/blob/main/docs/getting_started.md#installing-tye)*) - -- Execute `run-tye.ps1` - -- Wait until all applications are up! - - - You can check running application from tye dashboard ([localhost:8000](http://127.0.0.1:8000/)) - -- After all your backend services are up, start the angular application: - - ```bash - cd apps/angular - yarn start - ``` - -## What's Next? - -We'll work on deployment & CI-CD processes as a next step and build the business logic on. First goal is deploying the entire application on [Kubernetes](https://kubernetes.io/). - -## Feedback - -Your comments and suggestions is important for us. You can ask your questions or post your feedback under [this discussion entry](https://github.com/abpframework/abp/discussions/9536). diff --git a/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/overall-solution.png b/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/overall-solution.png deleted file mode 100644 index 738194b19c..0000000000 Binary files a/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/overall-solution.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-07-28-lepton-x-release/POST.md b/docs/en/Blog-Posts/2021-07-28-lepton-x-release/POST.md deleted file mode 100644 index bc586fdeed..0000000000 --- a/docs/en/Blog-Posts/2021-07-28-lepton-x-release/POST.md +++ /dev/null @@ -1,216 +0,0 @@ -# LeptonX Alpha Release - -We are excited to announce that the **alpha version** of the LeptonX theme has been released! As stated in [this blog post](https://volosoft.com/blog/introducing-the-lepton-theme-next-generation), LeptonX comes in different shapes. For this release, we introduce only ABP based projects with the Angular UI. So, if you are already using the ABP Framework and Angular as the frontend choice, you can integrate these packages into your project today. - -The theme has been deployed with two versions: LeptonX-lite (free) and LeptonX (commercial). - -> **Note that this theme currently only works for the *Angular UI*. Please keep waiting for other UI options.** - -## Open-Source - -This section shows how to replace the basic theme (that comes with open source ABP Framework startup template) with the new LeptonX-lite theme. - -To add `LeptonX-lite` into your project, - -* Install `@abp/ng.theme.lepton-x` NPM package - -`npm install @abp/ng.theme.lepton-x@preview` or - -`yarn add @abp/ng.theme.lepton-x@preview` - -* Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one. - -Replace the following style - -```JSON -{ - "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", - "inject": true, - "bundleName": "bootstrap-ltr.min" -}, -``` - -with - -```json -"node_modules/@volo/ngx-lepton-x.lite/styles/sidemenu-layout.min.css", -"node_modules/bootstrap-icons/font/bootstrap-icons.css", -``` - -* Finally, remove `ThemeBasicModule` from `app.module.ts` and `shared.module.ts`, and import the related modules in `app.module.ts` - -```js -import { ThemeLeptonXModule } from '@abp/ng.theme.lepton-x'; -import { SideMenuLayoutModule } from '@abp/ng.theme.lepton-x/layouts'; - -@NgModule({ - imports: [ - // ... - ThemeLeptonXModule.forRoot(), - SideMenuLayoutModule.forRoot(), - ], - // ... -}) -export class AppModule {} -``` - -Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well: - -```js -import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account'; - -@NgModule({ - // ... - imports: [ - // ... - AccountLayoutModule.forRoot(), - // ... - ], - // ... -}) -export class AppModule {} -``` - -To change the logos and brand color of the `LeptonX`, simply add the following CSS to the `styles.scss` - -```css -:root { - --lpx-logo: url('/assets/images/logo.png'); - --lpx-logo-icon: url('/assets/images/logo-icon.png'); - --lpx-brand: #edae53; -} -``` - -- `--lpx-logo` is used to place the logo in the menu. -- `--lpx-logo-icon` is a square icon used when the menu is collapsed. -- `--lpx-brand` is a color used throughout the application, especially on active elements. - -![LeptonX-lite dashboard](./lepton-x-lite-dashboard.png) - -![LeptonX-lite menu collapsed](./lepton-x-lite-menu-collapsed.png) - -## ABP Commercial - -This section shows how to replace the lepton theme (that comes with the ABP Commercial startup template) with the new LeptonX theme. - -To add `LeptonX` into your existing projects, - -* Firstly, install `@volosoft/abp.ng.theme.lepton-x` NPM package - -`npm install @volosoft/abp.ng.theme.lepton-x@preview` or - -`yarn add @volosoft/abp.ng.theme.lepton-x@preview` - -* Then, edit `angular.json` as follows: - -Remove the following config from the `styles` array since LeptonX provides bootstrap as embedded in its CSS. - -```JSON -{ - "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", - "inject": true, - "bundleName": "bootstrap-ltr.min" -}, -``` - -Add the following ones into the `styles` array - -```JSON -{ - "input": "node_modules/@volosoft/ngx-lepton-x/styles/themes/dark.css", - "inject": false, - "bundleName": "lepton-x.dark" -}, -{ - "input": "node_modules/@volosoft/ngx-lepton-x/styles/themes/dim.css", - "inject": false, - "bundleName": "lepton-x.dim" -}, -{ - "input": "node_modules/@volosoft/ngx-lepton-x/styles/themes/light.css", - "inject": false, - "bundleName": "lepton-x.light" -}, -"node_modules/@volosoft/ngx-lepton-x/styles/css/sidemenu-layout.min.css", -"node_modules/bootstrap-icons/font/bootstrap-icons.css", -``` - -Three of them are related to the theming and will be loaded during runtime. That's why they are not injected into the `head` as a style. Hence, the `"inject": false` - -The fourth one depends on which layout you want to use. For now, there is only `sidemenu-layout` available. In the future, there will be many layouts to choose from. - -The last one is `bootstrap-icons` which are being used throughout the components. - -* At last, remove `ThemeLeptonModule` from `app.module.ts` and `shared.module.ts`, and import the following modules in `app.module.ts` - -```js -import { ThemeLeptonXModule } from '@volosoft/abp.ng.theme.lepton-x'; -import { AbpSideMenuLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/layouts'; - -@NgModule({ - // ... - imports: [ - // ... - ThemeLeptonXModule.forRoot(), - AbpSideMenuLayoutModule.forRoot(), // depends on which layout you choose - // ... - ], - // ... -}) -export class AppModule {} -``` - -Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well: - -```js -import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account'; - -@NgModule({ - // ... - imports: [ - // ... - AccountLayoutModule.forRoot({ - layout: { - authLayoutImg: '/assets/images/login-bg.jpg', - }, - }), - // ... - ], - // ... -}) -export class AppModule {} -``` - -`authLayoutImg`: (Optional) If not given, a default image will be placed on the authentication pages. - - -* At this point, `LeptonX` theme should be up and running within your application. However, you may need to overwrite some css variables based your needs for every theme available as follows: -```scss -:root { - .lpx-theme-dark { - --lpx-logo: url('/assets/images/logo/logo-light.svg'); - --lpx-logo-icon: url('/assets/images/logo/logo-light-icon.svg'); - --lpx-brand: #edae53; - } - - .lpx-theme-dim { - --lpx-logo: url('/assets/images/logo/logo-light.svg'); - --lpx-logo-icon: url('/assets/images/logo/logo-light-icon.svg'); - --lpx-brand: #f15835; - } - - .lpx-theme-light { - --lpx-logo: url('/assets/images/logo/logo-dark.svg'); - --lpx-logo-icon: url('/assets/images/logo/logo-dark-icon.svg'); - --lpx-brand: #69aada; - } -} -``` - -When the user selects a theme, the corresponding CSS class is added to the `body`, so you can write specific CSS rules to each theme. - -## Conclusion - -In this blog post, I've explained how to use the alpha version of the new LeptonX theme for ABP-based solutions. Please, keep in mind that this is an alpha version, and we will continue to work on the LeptonX theme. The APIs are bound to change and breaking changes may be introduced in future versions. - -We would like you to try it out with the latest version of the ABP Framework and give us feedback at lepton@volosoft.com or open an issue on this repository: https://github.com/volosoft/lepton-theme diff --git a/docs/en/Blog-Posts/2021-07-28-lepton-x-release/lepton-x-lite-dashboard.png b/docs/en/Blog-Posts/2021-07-28-lepton-x-release/lepton-x-lite-dashboard.png deleted file mode 100644 index 03547aed6b..0000000000 Binary files a/docs/en/Blog-Posts/2021-07-28-lepton-x-release/lepton-x-lite-dashboard.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-07-28-lepton-x-release/lepton-x-lite-menu-collapsed.png b/docs/en/Blog-Posts/2021-07-28-lepton-x-release/lepton-x-lite-menu-collapsed.png deleted file mode 100644 index d45a41008e..0000000000 Binary files a/docs/en/Blog-Posts/2021-07-28-lepton-x-release/lepton-x-lite-menu-collapsed.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-08-02 v4_4_Release_Stable/POST.md b/docs/en/Blog-Posts/2021-08-02 v4_4_Release_Stable/POST.md deleted file mode 100644 index a2c812bb74..0000000000 --- a/docs/en/Blog-Posts/2021-08-02 v4_4_Release_Stable/POST.md +++ /dev/null @@ -1,42 +0,0 @@ -# ABP.IO Platform 4.4 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 4.4 versions have been released today. - -## What's New With 4.4? - -Since all the new features are already explained in details with the 4.4 RC announcement posts, I will not repeat all the details again. See [the related blog post](https://blog.abp.io/abp/ABP-Platform-4-4-RC-Has-Been-Released) for all the features and enhancements. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## The Road Map - -The next feature version will be 5.0. It is planned to release the 5.0 RC (Release Candidate) in November 2021. See the updated road maps; - -* [ABP Framework Road Map](https://docs.abp.io/en/abp/latest/Road-Map) -* [ABP Commercial Road Map](https://docs.abp.io/en/commercial/latest/road-map) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/POST.md b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/POST.md deleted file mode 100644 index 32ab4684f2..0000000000 --- a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/POST.md +++ /dev/null @@ -1,319 +0,0 @@ -# ABP.IO Platform 5.0 RC.1 Has Been Released - -Today, we are excited to release the [ABP Framework](https://abp.io/) and the [ABP Commercial](https://commercial.abp.io/) version **5.0 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -> **The planned release date for the [5.0.0 Stable](https://github.com/abpframework/abp/milestone/57) version is December 14, 2021**. - -Please try this version and provide feedback for a more stable ABP version 5.0! Thank you all. - -## Get Started with the 5.0 RC - -follow the steps below to try the version 5.0.0 RC today; - -1) **Upgrade** the ABP CLI to the version `5.0.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 5.0.0-rc.1 -```` - -**or install** if you haven't installed before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 5.0.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -You can use any IDE that supports .NET 6.0, like **[Visual Studio 2022](https://visualstudio.microsoft.com/downloads/)**. - -### Migration Notes & Breaking Changes - -This is a major version and there are some breaking changes and upgrade steps. Here, a list of important breaking changes in this version: - -* Upgraded to .NET 6.0, so you need to move your solution to .NET 6.0 if you want to use the ABP 5.0. -* Upgraded to Bootstrap 5. This is the most important breaking change in ABP 5.0 and you should take care of it. -* `IRepository` doesn't inherit from `IQueryable` anymore. It was already made obsolete in 4.2. -* Removed NGXS and states from the Angular UI. -* Removed gulp dependency from the MVC / Razor Pages UI in favor of `abp install-libs` command of the ABP CLI. -* Deprecated `EntityCreatingEventData`, `EntityUpdatingEventData`, `EntityDeletingEventData` and `EntityChangingEventData` classes. See [#9897](https://github.com/abpframework/abp/issues/9897). - -Please see the [migration document](https://docs.abp.io/en/abp/5.0/Migration-Guides/Abp-5_0) for all the details. You can also see all [the closed issues and pull requests](https://github.com/abpframework/abp/milestone/51). - -## What's new with 5.0? - -In this section, I will introduce some major features released with this version. - -### Static (Generated) Client Proxies for C# and JavaScript - -Dynamic C# ([see](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients)) and JavaScript ([see](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Dynamic-JavaScript-Proxies)) client proxy system is one of the most loved features of the ABP Framework. It generates the proxy code on runtime and makes client-to-server calls a breeze. With ABP 5.0, we are providing an alternative approach: You can generate the client proxy code on development-time. - -Development-time client proxy generation has a performance advantage since it doesn't need to obtain the HTTP API definition on runtime. It also makes it easier to consume a (micro)service behind an API Gateway. With dynamic proxies, we should return a combined HTTP API definition from the API Gateway and need to add HTTP API layers of the microservices to the gateway. Static proxies removes this requirement. One disadvantage is that you should re-generate the client proxy code whenever you change your API endpoint definition. Yes, software development always has tradeoffs :) - -Working with static client proxy generation is simple with the ABP CLI. You first need to run the server application, open a command-line terminal, locate to the root folder of your client application, then run the `generate-proxy` command of the ABP CLI. - -#### Creating C# client proxies - -C# client proxies are useful to consume APIs from Blazor WebAssembly applications. It is also useful for microservice to microservice HTTP API calls. Notice that the client application need to have a reference to the application service contracts (typically, the `.Application.Contracts` project in your solution) in order to consume the services. - -**Example usage:** - -````bash -abp generate-proxy -t csharp -u https://localhost:44305 -```` - -`-t` indicates the client type, C# here. `-u` is the URL of the server application. It creates the proxies for the application (the app module) by default. You can specify the module name as `-m ` if you are building a modular system. The following figure shows the generated files: - -![csharp-proxies](csharp-proxies.png) - -The next step is to enable the static proxy system for your application (or module) using the `AddStaticHttpClientProxies` extension method: - -````csharp -context.Services.AddStaticHttpClientProxies( - typeof(MyApplicationContractsModule).Assembly -); -```` - -You can then inject and use the application service interfaces of these proxies. The usage is same of the [dynamic C# client proxies](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients). - -When you use static proxies for a module / application, you don't need to dynamic proxies. Find and remove the `context.Services.AddHttpClientProxies(...)` in your solution (typically in the `*.HttpApi.Client` project). - -#### Creating JavaScript client proxies - -JavaScript proxies are useful to consume APIs from MVC / Razor Pages UI. It works on JQuery AJAX API, just like the [dynamic JavaScript proxies](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Dynamic-JavaScript-Proxies). - -**Example usage:** - -````bash -abp generate-proxy -t js -u https://localhost:44305 -```` - -The following figure shows the generated file: - -![js-proxies](js-proxies.png) - -Then you can consume the server-side APIs from your JavaScript code just like the [dynamic JavaScript proxies](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Dynamic-JavaScript-Proxies). - -#### Creating Angular client proxies - -Angular developers knows that the generate-proxy command was already available in ABP's previous versions to create client-side proxy services in the Angular UI. The same functionality continues to be available and already [documented here](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies). - -**Example usage:** - -````bash -abp generate-proxy -t ng -u https://localhost:44305 -```` - -### Transactional Outbox & Inbox for the Distributed Event Bus - -This was one of the most awaited features by distributed software developers. - -The [transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html) is used to publishing distributed events within the same transaction that manipulates the application database. Distributed events are saved into the database inside the same transaction with your data changes, then sent to the message broker (like RabbitMQ or Kafka) by a separate background worker with a re-try system. In this way, it ensures the consistency between your database state and the published events. - -The transactional inbox pattern, on the other hand, saves incoming events into database first, then executes the event handler in a transactional manner and removes the event from the inbox queue in the same transaction. It also ensures that the event is only executed one time by keeping the processed messages for a while and discarding the duplicate events received from the message broker. - -Enabling the inbox and outbox patterns requires a few manual steps for your application. We've created a simple [console application example](https://github.com/abpframework/abp/tree/dev/test/DistEvents), but I will also explain all the steps here. - -#### Pre-requirements - -First of all, you need to have EF Core or MongoDB installed into your solution. It should be already installed if you'd created a solution from the ABP startup template. - -#### Install the packages - -For the outbox & inbox functionality, ABP depends on [DistributedLock.Core](https://www.nuget.org/packages/DistributedLock.Core) library which provides a distributed locking system for concurrency control in a distributed environment. There are [many distributed lock providers](https://github.com/madelson/DistributedLock#implementations), including Redis, SqlServer and ZooKeeper. You can use the one you like. Here, I will show the Redis provider. - -Add [DistributedLock.Redis](https://www.nuget.org/packages/DistributedLock.Redis) NuGet package to your project, then add the following code into the ConfigureService method of your ABP module class: - -````csharp -context.Services.AddSingleton(sp => -{ - var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); - return new RedisDistributedSynchronizationProvider(connection.GetDatabase()); -}); -```` - -We are getting the Redis configuration from the `appsettings.json` file, so add the following lines to your `appsettings.json` file: - -````json -"Redis": { - "Configuration": "127.0.0.1" -} -```` - -You can change the IP or customize the configuration based on your environment. - -#### Configure the DbContext - -Open your `DbContext` class, implement the `IHasEventInbox` and the `IHasEventOutbox` interfaces. You should end up by adding two `DbSet` properties into your `DbContext` class: - -````csharp -public DbSet IncomingEvents { get; set; } -public DbSet OutgoingEvents { get; set; } -```` - -Add the following lines inside the `OnModelCreating` method of your `DbContext` class: - -````csharp -builder.ConfigureEventInbox(); -builder.ConfigureEventOutbox(); -```` - -It is similar for MongoDB; implement the `IHasEventInbox` and the `IHasEventOutbox` interfaces. There is no `Configure...` method for MongoDB. - -Now, we've added inbox/outbox related tables to our database schema. Now, for EF Core, use the standard `Add-Migration` and `Update-Database` commands to apply changes into your database (you can skip this step for MongoDB). If you want to use the command-line terminal, run the following commands in the root directory of the database integration project: - -````bash -dotnet ef migrations add "Added_Event_Boxes" -dotnet ef database update -```` - -#### Configure the distributed event bus options - -As the last step, write the following configuration code inside the `ConfigureServices` method of your module class: - -````csharp -Configure(options => -{ - options.Outboxes.Configure(config => - { - config.UseDbContext(); - }); - - options.Inboxes.Configure(config => - { - config.UseDbContext(); - }); -}); -```` - -Replace `YourDbContext` with your `DbContext` class. - -That's all. You can continue to publishing and consuming events just as before. See the [distributed event bus documentation](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) if you don't know how to use it. - -### Publishing events in transaction - -The previous feature (outbox & inbox) solves the transactional event publishing problem for distributed systems. This feature, publishing events in transaction, solves the problem of executing event handlers in the same transaction that the events are published in for non-distributed applications. With 5.0, all events (local or distributed) are handled in the same transaction. If any handler fails, the transaction is rolled back. If you don't want that, you should use try/catch and ignore the exception inside your event handler. - -Remember that if you don't install a real distributed event provider (like [RabbitMQ](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus-RabbitMQ-Integration) or [Kafka](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus-Kafka-Integration)), the [distributed events](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) are actually executed in-process, just like the [local events](https://docs.abp.io/en/abp/latest/Local-Event-Bus). So, with this development, all the events become transactional, even if your system is distributed or not. - -No action needed to take. It will just work by default. There is a [deprecation note](https://github.com/abpframework/abp/issues/9897) related to this change (some pre-defined events [will be removed](https://github.com/abpframework/abp/issues/9908) in the next major version since they are not needed anymore) - -### Inactivating a user - -The [Identity module](https://docs.abp.io/en/abp/latest/Modules/Identity) has a new feature to make a user active or inactive. Inactive users can not login to the system. In this way, you can disable a user account without deleting it. An *Active* checkbox is added to the user create/edit dialog to control it on the UI: - -![user-active](user-active.png) - -EF Core developers should add a new database migration since this brings a new field to the `AbpUsers` table. - -### Overriding email settings per tenant - -If you're building a multi-tenant application, it is now possible to override email sending settings per tenant. To make this possible, you should first enable that [feature](https://docs.abp.io/en/abp/latest/Features) to the tenant you want, by clicking the *Actions -> Features* for the tenant. - -![tenant-features](tenant-features.png) - -Enable the feature using the checkbox as shown in the following modal: - -![enable-email-tenant](enable-email-tenant.png) - -Then the tenant admin can open the email settings page under the Administration -> Settings menu (on development environment, logout, switch to the tenant and re-login with the tenant admin): - -![email-settings](email-settings.png) - -### Hangfire dashboard permission - -ABP allows to use Hangfire as the background job manager when you install the integration package [as documented](https://docs.abp.io/en/abp/5.0/Background-Jobs-Hangfire). Hangfire's dashboard is used to monitor and control the background job queues. Here, a screenshot from the dashboard: - -![hangfire-dashboard](hangfire-dashboard.png) - -Hangfire's dashboard is not authorized by default, so any user can navigate to the `/hangfire` URL and see/control the jobs. With the ABP version 5.0, we've added a built-in authorization implementation for the Hangfire dashboard. Instead of `app.UseHangfireDashboard();`, you can use the following middleware configuration: - -````csharp -app.UseHangfireDashboard("/hangfire", new DashboardOptions -{ - AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() } -}); -```` - -In this way, only the authenticated users can see the dashboard. However, we suggest to set a permission name, so only the users with that permission can see the dashboard: - -````csharp -app.UseHangfireDashboard("/hangfire", new DashboardOptions -{ - AsyncAuthorization = new[] { - new AbpHangfireAuthorizationFilter("MyPermissionName") - } -}); -```` - -You can define the permission (`MyPermissionName` in this example) using the standard [permission system](https://docs.abp.io/en/abp/5.0/Authorization#permission-system). - -### Introducing AbpControllerBase - -ABP provides an `AbpController` class that you can inherit your MVC controllers from. It provides some pre-injected services to simplify your controllers. With v5.0, we are adding a second base controller class, `AbpControllerBase`, which is more proper to create API controllers (without view features). It is suggested to inherit from the `AbpControllerBase` class to create API controllers, instead of the `AbpController` class. - -**Example:** - -````csharp -[Route("api/products")] -public class ProductController : AbpControllerBase -{ - // TODO: ... -} -```` - -### Automatically setting the TenantId for new entities - -Beginning from the version 5.0, ABP automatically sets the `TenantId` for you when you create a new entity object (that implements the `IMultiTenant` interface). It is done in the constructor of the base `Entity` class (all other base entity and aggregate root classes are derived from the `Entity` class). The `TenantId` is set from the current value of the `ICurrentTenant.Id` property. - -Previously, it was a responsibility of the developer to set `TenantId` for new entities. Now, ABP takes care of it. You only may need to set it if you want to set id of a tenant other than the current tenant. - -This can be a breaking change in rare cases (for example, if you create host side entities from a tenant context and do not explicitly set host entity's `TenantId` to `null`). - -## Community News - -### ABP Community Talks 2021.12 - -![community-talks](community-talks.png) - -As the core ABP development team, we've decided to organize monthly live meetings with the ABP community. The first live meeting will be at **December 16, 2021, 17:00 (UTC)** on YouTube. ABP core team members will present some of the new features coming with ABP 5.0. - -**Join this event on the Kommunity platform: https://kommunity.com/volosoft/events/abp-community-talks-4afca9c9** - -You can also [subscribe to the Volosoft channel](https://www.youtube.com/channel/UCO3XKlpvq8CA5MQNVS6b3dQ) for reminders for further ABP events and videos. - -### ABP was on ASP.NET Community Startup! - -It was great for us to be invited to Microsoft's [ASP.NET Community Weekly Standup](https://dotnet.microsoft.com/live/community-standup) show, at September 28. There was a very high attention and that made us very happy. Thanks to the ABP Community and all the watchers :) If you've missed the talk, [you can watch it here](https://www.youtube.com/watch?v=vMWM-_ihjwM). - -### Upcoming ABP Book! - -I am currently authoring the first official book for the ABP Framework and it is on [pre-sale on Amazon](https://www.amazon.com/Mastering-ABP-Framework-maintainable-implementing-dp-1801079242/dp/1801079242) now. - -![abp-book-cover](abp-book-cover.png) - -This books is a complete guide to start working with the ABP Framework, explore the ABP features and concepts. It also contains chapters for DDD, modular application development and multi-tenancy to learn and practically work with the ABP architecture to build maintainable software solutions and create SaaS applications. The book will be based on ABP 5.0 and published in the beginning of 2022. You can [pre-order now](https://www.amazon.com/Mastering-ABP-Framework-maintainable-implementing-dp-1801079242/dp/1801079242)! - -### New ABP Community posts - -Here, some of the latest posts added to the [ABP community](https://community.abp.io/): - -* [Many to many relationship with ABP and EF Core](https://community.abp.io/articles/many-to-many-relationship-with-abp-and-ef-core-g7rm2qut) -* [Add a new module to your ABP application](https://community.abp.io/articles/abp-framework-add-a-new-module-to-your-abp-application-eogrfm88) -* [Changing UI Theme for ABP MVC / Razor Pages UI](https://community.abp.io/articles/changing-ui-theme-for-abp-mvc-razor-pages-ui-ravx6a0o) -* [Create a Windows Service with the ABP Framework](https://community.abp.io/articles/create-a-windows-service-with-abp-framework-hop4dtra) -* [Deploy ABP Framework .NET Core tiered application to docker swarm](https://community.abp.io/articles/deploy-abp-framework-dotnet-core-tiered-app-to-docker-swarm-kcrjbjec) -* [Centralized logging for .NET Core ABP microservices application using Seq](https://community.abp.io/articles/centralized-logging-for-.net-core-abp-microservices-app-using-seq-g1xe7e7y) - -Thanks to the ABP Community for all the contents they have published. You can also post your ABP and .NET related (text or video) contents to the ABP Community. - -## Conclusion - -The ABP version 5.0 is coming with important changes (like .NET 6 and Bootstrap 5) and features. In this blog post, I summarized the news about that new version. Please try it and provide feedback by opening issues on [the GitHub repository](https://github.com/abpframework/abp). Thank you all! diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/abp-book-cover.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/abp-book-cover.png deleted file mode 100644 index bfd2c54599..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/abp-book-cover.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/community-talks.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/community-talks.png deleted file mode 100644 index 545cd61071..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/community-talks.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/cover-50.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/cover-50.png deleted file mode 100644 index 36862e2a2f..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/cover-50.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/csharp-proxies.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/csharp-proxies.png deleted file mode 100644 index 5bc167a388..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/csharp-proxies.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/email-settings.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/email-settings.png deleted file mode 100644 index 74574fbd2d..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/email-settings.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/enable-email-tenant.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/enable-email-tenant.png deleted file mode 100644 index d35cda4713..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/enable-email-tenant.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/hangfire-dashboard.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/hangfire-dashboard.png deleted file mode 100644 index 671a018dda..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/hangfire-dashboard.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/js-proxies.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/js-proxies.png deleted file mode 100644 index 5494ad125b..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/js-proxies.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/tenant-features.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/tenant-features.png deleted file mode 100644 index b851173ab1..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/tenant-features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/use-preview-visual-studio.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/use-preview-visual-studio.png deleted file mode 100644 index 8bcb525e59..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/use-preview-visual-studio.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/user-active.png b/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/user-active.png deleted file mode 100644 index 9df561568c..0000000000 Binary files a/docs/en/Blog-Posts/2021-11-18 v5_0_Preview/user-active.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2021-12-15 v5_0_Release_Stable/POST.md b/docs/en/Blog-Posts/2021-12-15 v5_0_Release_Stable/POST.md deleted file mode 100644 index 329db94af3..0000000000 --- a/docs/en/Blog-Posts/2021-12-15 v5_0_Release_Stable/POST.md +++ /dev/null @@ -1,47 +0,0 @@ -# ABP.IO Platform 5.0 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 5.0 versions have been released today. - -## What's new with 5.0? - -Since all the new features are already explained in details with the [5.0 RC.1 Announcement Post](https://blog.abp.io/abp/ABP-IO-Platform-5.0-RC-1-Has-Been-Released), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP-IO-Platform-5.0-RC-1-Has-Been-Released) for all the features and enhancements. - -## Getting started with 5.0 - -### Creating new solutions - -You can create a new solution with the ABP Framework version 5.0 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -Type the following command in a command-line terminal to install the ABP CLI version 5.0: - -````bash -dotnet tool install -g Volo.Abp.Cli --version 5.0.0 -```` - -To upgrade your existing ABP CLI installation: - -````bash -dotnet tool update -g Volo.Abp.Cli --version 5.0.0 -```` - -Then you can create a new solution using the `abp new` command: - -````bash -abp new Acme.BookStore -```` - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. - -### Upgrading existing solutions - -Check [the migration guide](https://docs.abp.io/en/abp/latest/Migration-Guides/Abp-5_0) for the applications with the version 4.x upgrading to the version 5.0. Also see [the upgrading guide](https://docs.abp.io/en/abp/latest/Upgrading) to understand how to update existing solutions. - -## ABP Community Talks 2021.12 - -![community-talks](community-talks.png) - -As the core ABP development team, we've decided to organize monthly live meetings with the ABP community. The first live meeting will be at **December 16, 2021, 17:00 (UTC)** on YouTube. ABP core team members will present some of the new features coming with ABP 5.0. - -**Join this event on the Kommunity platform: https://kommunity.com/volosoft/events/abp-community-talks-4afca9c9** - -See you in the event! diff --git a/docs/en/Blog-Posts/2021-12-15 v5_0_Release_Stable/community-talks.png b/docs/en/Blog-Posts/2021-12-15 v5_0_Release_Stable/community-talks.png deleted file mode 100644 index 545cd61071..0000000000 Binary files a/docs/en/Blog-Posts/2021-12-15 v5_0_Release_Stable/community-talks.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/POST.md deleted file mode 100644 index 8620b4f7d2..0000000000 --- a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/POST.md +++ /dev/null @@ -1,155 +0,0 @@ -# ABP.IO Platform 5.1 Has Been Released - -Today, we are releasing the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version 5.1 (with a version number `5.1.1`). This blog post introduces the new features and important changes in this new version. - -> **Warning** -> -> For a long time we've been releasing RC (Release Candidate) versions a few weeks prior to every minor and major release. **This version has been released without a preview version.** This is because we've accidently released all the packages with a stable version number, without a `-rc.1` suffix and there is no clear way to unpublish all the NuGet and NPM packages. We're sorry about that. However, it doesn't mean that this release is buggy. We've already resolved the known problems. We will publish one or more patch releases if needed. You can follow [this milestone](https://github.com/abpframework/abp/milestone/64?closed=1) for the common problems or submit your own bug report. If you are worried about its stability, you can wait for the next patch release. - -## Get Started with 5.1 - -Follow the steps below to try the 5.1 version today: - -1) **Upgrade** the ABP CLI to the latest version using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g -```` - -**or install** if you haven't installed it before: - -````bash -dotnet tool install Volo.Abp.Cli -g -```` - -2) Create a **new application**: - -````bash -abp new BookStore -```` - -Check out the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page. - -You can use any IDE that supports .NET 6.x development (e.g. [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/)). - -### Migration Notes & Breaking Changes - -This is a minor feature release, mostly with enhancements and improvements based on [version 5.0](https://blog.abp.io/abp/ABP-IO-Platform-5-0-Final-Has-Been-Released). There aren't any breaking changes except for the Angular UI upgrade. ABP 5.1 startup templates use **Angular 13**. - -### Angular UI - -**If you want to upgrade the ABP Framework but want to continue with Angular 12**, add the following section to the `package.json` file of the Angular project: - -````json -"resolutions": { - "ng-zorro-antd": "^12.1.1", - "@ng-bootstrap/ng-bootstrap": "11.0.0-beta.2" - } -```` - -### Blazor UI - -If you are using Blazor WebAssembly, open the `Program` class and replace `InitializeAsync` with `InitializeApplicationAsync`. - -## What's New with the ABP Framework 5.1? - -In this section, I will introduce some major features released with this version. - -### The New Hosting Model - -The ABP startup application template now uses the new ASP.NET Core hosting APIs ([check out the Microsoft's minimal APIs document](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0)) on application startup ([check out the exact place in the ABP startup template](https://github.com/abpframework/abp/blob/46cdfbe7b06c93690181633be4e96bf62e7f34e2/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs#L33-L40)). So, the `Startup.cs` file has been removed. - -Old-style hosting logic will continue to work as long as ASP.NET Core supports it. It is recommended to switch to the new model if it's possible for your solution. Check out [this guide](https://docs.abp.io/en/abp/latest/Migration-Guides/Upgrading-Startup-Template) if you need to know how to do that. - -### Asynchronous Startup Lifecycle Methods - -The new hosting model allows us to execute asynchronous code on application initialization in the [ABP module](https://docs.abp.io/en/abp/latest/Module-Development-Basics) classes. If you are using the new hosting model (which is default with 5.1 startup templates), you can override the `Async` versions of the module lifecycle methods. - -For example, you can now override the `ConfigureServicesAsync` (instead of `ConfigureServices`) or `OnApplicationInitializationAsync` (instead of `OnApplicationInitialization`) as shown in the following code block: - -````csharp -public class MyModule : AbpModule -{ - public override async Task ConfigureServicesAsync(ServiceConfigurationContext context) - { - /* You can use await here and safely execute other async methods */ - } - - public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) - { - /* You can use await here and safely execute other async methods */ - } -} -```` - -If you override both the asynchronous and synchronous versions of the same method, only the asynchronous one will be executed. So, override only one of them based on your needs. - -### eShopOnABP Is Getting More Stable - -Our team is working to finalize the [eShopOnAbp](https://github.com/abpframework/eShopOnAbp) example solution, which is a reference **microservice solution** built with the ABP Framework. They will explain the project status and show what's done in the next **ABP Community Talks** meeting (Check out the *ABP Community Talks 2021.1* section at the bottom of this post). - -### The New ABP.IO Design! - -We've been working on a new design for the [abp.io](https://abp.io/) websites for a while. We are adding the final touches; the new design will be live very soon. Here's a screenshot from the design work: - -![new-abp-io-design](new-abp-io-design.png) - -The [ABP Commercial](https://commercial.abp.io/) and [ABP Community](https://community.abp.io/) websites will also have new designs as part of this update. - -### Other Changes - -Here are some other notable changes that come with this release: - -* Support markdown in [CMS-Kit comments ](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments)feature ([#10792](https://github.com/abpframework/abp/pull/10792)) -* Used file scoped namespaces for all the ABP Framework source code :) ([#10552](https://github.com/abpframework/abp/pull/10696)) -* Developers should control `EnableLegacyTimestampBehavior` when using PostgreSQL. [#11371](https://github.com/abpframework/abp/pull/11371) [#65](https://github.com/abpframework/eShopOnAbp/pull/65) - - -All issues & PRs in [5.1 milestone](https://github.com/abpframework/abp/milestone/60?closed=1). - -### About ABP Commercial - -The core team is also working on ABP Commercial (which provides pre-built modules, themes, tooling and support on top of the ABP Framework). We've done a lot of minor improvements and fixes to the modules and tooling. - -There's some exciting news about the **LeptonX theme**; We are working on making it available in the **MVC (Razor Pages)** and **Blazor** UI options too (in addition to Angular UI). We are also adding more components, layout options, demo pages, etc... We are planning to release a beta version in the next weeks. Here's an animated GIF from the dashboard we've prepared as a demonstration: - -![leptonx-dash](leptonx-dash.gif) - -If you are wondering what is the LeptonX project, please check out this [blog post](https://blog.abp.io/abp/LeptonX-Theme-for-ABP-Framework-Alpha-Release). - -As another visible functionality, we've added a new feature to the [CMS Kit Pro](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index) module that is used to forward a URL to another URL. This is a screenshot from the management UI: - -![url-forward](url-forward.png) - -This feature can be used to create short URLs in your application (like URL shortening services providers) or forward old pages to their new URLs. - -In addition to the new features shipped in every minor version, we are working on long-term projects for ABP.IO Platform and ABP Commercial (a little secret for now :). We will have announcements once these projects get more stable. - -## Community News - -### ABP Community Talks 2021.1 - -![abp-community-talks-2022-1](abp-community-talks-2022-1.png) - -This is the second episode of the ABP Community Talks and we are talking about microservice development with the ABP Framework, based on the [eShopOnAbp](https://github.com/abpframework/eShopOnAbp) reference solution. We will also briefly talk about the changes that come with ABP version 5.1. This **live meeting** will be on **January 20, 2022, 17:00 (UTC)** on YouTube. - -**Join this event on the Kommunity platform: https://kommunity.com/volosoft/events/abp-community-talks-20221-microservice-development-acd0f44b** - -You can also [subscribe to Volosoft YouTube channel](https://www.youtube.com/channel/UCO3XKlpvq8CA5MQNVS6b3dQ) for reminders of further ABP events and videos. - -### New ABP Community Posts - -Here are some of the recent posts added to the [ABP community](https://community.abp.io/): - -* [Minimal API development with the ABP Framework](https://community.abp.io/articles/minimal-api-with-abp-hello-world-part-1-sg5i44p8) by [@antosubash](https://github.com/antosubash) (three parts, video tutorial). -* [Integrating the Syncfusion MVC Components to the ABP MVC UI](https://community.abp.io/articles/integrating-the-syncfusion-mvc-components-to-the-abp-mvc-ui-0gpkr1if) by [@EngincanV](https://github.com/EngincanV). -* [Add Tailwind CSS to your ABP Blazor UI](https://community.abp.io/articles/add-tailwindcss-to-your-abp-blazor-ui-vidiwzcy) by [@antosubash](https://github.com/antosubash) (video tutorial). -* [Import external users into the users Table from an ABP Framework application](https://community.abp.io/articles/import-external-users-into-the-users-table-from-an-abp-framework-application-7lnyw415) by [@bartvanhoey](https://github.com/bartvanhoey). - -Thanks to the ABP Community for all the contents they have published. You can also [post your ABP and .NET related (text or video) contents](https://community.abp.io/articles/submit) to the ABP Community. - -## Conclusion - -In this blog post, I summarized the news about the new version and the ABP Community. Please try it and provide feedback by opening issues on the [GitHub repository](https://github.com/abpframework/abp). Thank you all! diff --git a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/abp-community-talks-2022-1.png b/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/abp-community-talks-2022-1.png deleted file mode 100644 index 75e93a6fdd..0000000000 Binary files a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/abp-community-talks-2022-1.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/leptonx-dash.gif b/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/leptonx-dash.gif deleted file mode 100644 index 912de644aa..0000000000 Binary files a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/leptonx-dash.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/new-abp-io-design.png b/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/new-abp-io-design.png deleted file mode 100644 index 98057eb97c..0000000000 Binary files a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/new-abp-io-design.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/url-forward.png b/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/url-forward.png deleted file mode 100644 index 4c489854f0..0000000000 Binary files a/docs/en/Blog-Posts/2022-01-11 v5_1_Release_Stable/url-forward.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md b/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md deleted file mode 100644 index b78e3235a2..0000000000 --- a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md +++ /dev/null @@ -1,196 +0,0 @@ -# ABP.IO Platform 5.2 RC.1 Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **5.2 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -> **The planned release date for the [5.2.0 Stable](https://github.com/abpframework/abp/milestone/66) version is April 05, 2022**. - -Please try this version and provide feedback for a more stable ABP version 5.2! Thank you all. - -## Get Started with the 5.2 RC - -Follow the steps below to try the version 5.2.0 RC today: - -1) **Upgrade** the ABP CLI to version `5.2.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 5.2.0-rc.1 -```` - -​ **or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 5.2.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -You can use any IDE that supports .NET 6.x, like **[Visual Studio 2022](https://visualstudio.microsoft.com/downloads/)**. - -## Migration Guides - -Please see the migration guides if you are upgrading from 5.x versions: - -* [ABP Framework 5.x to 5.2 migration guide](https://docs.abp.io/en/abp/5.2/Migration-Guides/Abp-5_2) -* [ABP Commercial 5.x to 5.2 migration guide](https://docs.abp.io/en/commercial/5.2/migration-guides/index) - -## What's New with ABP Framework 5.2? - -In this section, I will introduce some major features released with this version. Here, a brief list of titles explained in the next sections: - -* Single-layer Solution Template -* API Versioning -* libs Folder Has been Removed from Source Control -* Hiding the Default ABP Endpoints from Swagger UI -* Custom Global CSS and JavaScript for the CMS Kit module -* Other news - -Let's begin with the first section. - -### Single-layer Solution Template - -ABP's [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) is a well-organized and layered solution to create maintainable business applications. However, some developers find it a little bit complex for simple and short-term applications. For such applications, we've created a new startup template that has no layering and built as simple as possible. It has the same functionality, features and modules on runtime, but the development model is minimal and everything is in the single project (`csproj`), as shown in the following figure: - -![single-layer-ABP-Dotnet-solution](single-layer-solution.png) - -Use the `app-nolayers` as the template name while creating your solution: - -````bash -abp new BookStore -t app-nolayers --preview -```` - -[ABP Commercial](https://commercial.abp.io/) developers can use the `app-nolayers-pro` as the startup template: - -````csharp -abp new BookStore -t app-nolayers-pro --preview -```` - -> There is a bug for the `app-nolayers-pro` related to the licensing system, which will be fixed with 5.2.0-rc.2: `appsettings.secrets.json` is missing in the project folder (should be near to `appsettings.json` and contains the license code normally). As a workaround, create a new solution with the layered startup template, find and copy that file to the single-layer solution. - -One note about the single-layer project is it doesn't have Blazor WebAssembly UI, because it requires 3 projects at least (one server-side, one UI and one shared library project between these two). We will consider to add Blazor UI support based on your feedback. You can continue to develop Blazor WASM projects using the standard layered solution template. - -#### Database Migrations for EF Core - -After creating your solution, you need to create the database before running the application. We've added a parameter to the application that can be specified to migrate the database and seed the initial data. Open the project's directory (that contains the `csproj` file) in a command-line terminal and type the following command: - -````bash -dotnet run --migrate-database -```` - -It will run, migrate the database and exit. You can then run the application as normal. - -You could use the standard `dotnet ef database update` command (or `Update-Database` command in Visual Studio's Package Manager Console). It can successfully create the database tables. However, it doesn't seed the initial data that is necessary to run the application. - -To keep the solution simple, we haven't added an external application (like the `DbMigrator` in the layered application startup template) to migrate the database. - -Using the same application to migrate the database is simple and useful for development environment, and it can also be used in production environment. However, there are other ways of migrating a database. Please read more on [Microsoft's documentation](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). - -#### Other UI and Database Options - -The new single-layer solution template also supports Angular and Blazor UI, and MongoDB for the database side. For the UI, you can use `mvc` (default), `angular` or `blazor-server` with the `-u` (or `--ui`) parameter. For the database provider, you can use `ef` (default) or `mongodb` with the `-d` (or `--database-provider`) parameter. Example: - -````bash -abp new BookStore -t app-nolayers -u angular -d mongodb --preview -```` - -This will create a single layer template with Angular as the UI framework and MongoDB as the database provider. - -#### Single-Layer Tutorial - -[Hamza Albreem](https://twitter.com/st_braim) has created a video tutorial to explain how to develop a simple application with this startup template. - -![video-single-layer-tutorial](video-single-layer-tutorial.png) - -You can [watch it here](https://community.abp.io/posts/developing-a-minimalist-application-with-the-abp-framework-mvad01ca). - -### API Versioning - -API versioning has always been possible with the ABP Framework, but we haven't had a documentation for it. With version 5.2, we've created a document to explain how to implement API versioning for your applications and add versioning support to your standard application service classes. See the [documentation here](https://docs.abp.io/en/abp/5.2/API/API-Versioning). - -### libs Folder Has been Removed from Source Control - -> **NOTICE: This can be a breaking change for your development environment and CI/CD pipelines. So, please read it carefully and take the necessary actions.** - -When you create solutions with MVC (Razor Pages) or Blazor Server UI, your application will have a `wwwroot/libs` folder in the UI project as shown below: - -![libs-folder](libs-folder.png) - -The `libs` folder contains all the client-side (mostly JavaScript and CSS) library dependencies. For example, in the figure above, the `bootstap` folder contains the necessary files for the Bootstrap library. The folder's content is copied from the `node_modules` folder (it only copies the minimum files to make the library work, not the whole folder) with ABP CLI's `abp install-libs` command. - -Before version 5.2, the `libs` folder was coming with the startup template and committed into your source control system (e.g. Git). With version 5.2, this folder is excluded from the source control by default, so every developer getting the solution must run `abp install-libs` in the UI project's root directory to install these libraries. This approach saves a huge amount of size of the solution. For example, the initial size of an MVC UI application reduces from `9.83MB` to `0.23MB` (you read it right!). - -When you create a new solution with ABP CLI, `abp install-libs` command is automatically executed, so your application directly works. However, if your teammates (or CI/CD system) get the solution from the source control, they should run the `abp install-libs` before running the solution. If you don't want that, you can simply remove the `**/wwwroot/libs/*` line from the `.gitignore` file in the root folder of your solution, then the `libs` folder is added to your source control again (if you are using a source control system other than Git, you should apply your system's rules to include/exclude that folder). - -### Hiding the Default ABP Endpoints from Swagger UI - -[Engincan Veske](https://twitter.com/EngincanVeske) had written [an article](https://community.abp.io/posts/how-to-hide-abp-related-endpoints-on-swagger-ui-mb2w01fe) to explain how to hide ABP's default endpoints from Swagger UI. Then We thought that could be a good built-in option in the ABP Framework and added a `HideAbpEndpoints` method to the `AddAbpSwaggerGen` method, which can be used as in the following code example: - -````csharp -services.AddAbpSwaggerGen( - options => - { - //... other options - - //Hides ABP Related endpoints on Swagger UI - options.HideAbpEndpoints(); - } -) -```` - -After that, ABP's default endpoints will still be functional, but will be hidden in Swagger UI. - -### Custom Global CSS and JavaScript for the CMS Kit module - -We are improving the [CMS Kit module](https://docs.abp.io/en/abp/5.2/Modules/Cms-Kit/Index) and adding new features constantly. A new feature with version 5.2 is a global resources system, where you can write custom JavaScript or CSS code on the application's UI (added a new page for it), which will be immediately available in all your application pages: - -![cms-kit-global-resources](cms-kit-global-resources.png) - -In this way, you can customize your application's look and behavior on runtime. See [the documentation](https://docs.abp.io/en/abp/5.2/Modules/Cms-Kit/Global-Resources) for more information. - -> Note that the [pages](https://docs.abp.io/en/abp/5.2/Modules/Cms-Kit/Pages) feature already have a page-basis script/style editor. But this new feature allows you to write script/style that is applied to all pages of your application. - -### Other news - -* Upgraded the [Blazorise](https://blazorise.com/) library to v1.0 for Blazor UI. After the upgrade, ensure that all Blazorise-related packages are using v1.0 in your application. - -If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/5.2.0-rc.1), that contains a list of all issues and pull requests closed with this version. - -## What's New with ABP Commercial 5.2? - -We've also made many enhancements for [ABP Commercial](https://commercial.abp.io/), and also made the necessary changes and improvements to align with the v5.2 release of the ABP Framework. For example, the **single-layer solution template** is also available for ABP Commercial as explained above. - -### Many to Many Relations on Code Generation - -One exciting new feature with ABP Commercial v5.2 is supporting many-to-many entity relations while generating CRUD code with [ABP Suite](https://commercial.abp.io/tools/suite). - -When you open ABP Suite v5.2, you will see an "Add navigation collection" button in the Navigation tab. Here, you can click that button, select the target entity and other options: - -![suite-many-to-many-entity-relation](suite-many-to-many-entity-relation.png) - -With this new feature, you can automatically generate advanced user interfaces without touching the code, then customize the generated code to implement your business logic. - -## Community News - -We organized the 3rd live [ABP Community Talks](https://community.abp.io/events) event on February 23rd. ABP community has a good interest in these events and we will continue to organize such a live event in every month. March's event will be announced in a few days. [Follow us on twitter](https://twitter.com/abpframework). - -[ABP Community](https://community.abp.io/) website is being a huge resource of articles and video tutorials on the ABP Framework and .NET. There have been 93 articles/tutorials submitted so far. Here's a list of a few contents posted in the last weeks: - -* [Ahmet Urel](https://twitter.com/YellowDraqon) submitted a series of articles to demonstrate the usage of [MudBlazor](https://www.mudblazor.com/) library with the ABP Framework Blazor UI: [Part 1](https://community.abp.io/posts/mudblazor-theme-in-abp-blazor-webassembly-ae23zz17), [Part 2](https://community.abp.io/posts/mudblazor-theme-in-abp-blazor-webassembly-part-2-tkvrvyvm) and [Part 3](https://community.abp.io/posts/mudblazor-theme-in-abp-blazor-webassembly-part-3-c8hwx00l). -* [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) has created two articles for ABP & **.NET MAUI** integration: See [Part 1](https://community.abp.io/posts/integrating-maui-client-via-using-openid-connect-aqjjwsdf) and [Part 2](https://community.abp.io/posts/using-abp-client-proxies-in-maui-with-openid-connect-em7x1s8k). [Bart Van Hoey](https://twitter.com/bartvanhoey) also created [an article](https://community.abp.io/posts/abp-framework-consumed-by-a-.net-maui-app-e74fmblw) for .NET MAUI and the ABP Framework. -* [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) has also created [an article](https://community.abp.io/posts/using-autofilterer-with-abp-framework-uuqv81jm) to demonstrate how to use his own open source [AutoFilterer](https://github.com/enisn/AutoFilterer) library with the ABP Framework. -* [Jonathan Potts](https://github.com/jonathanpotts) has created his first ABP Community article that shows how to use Bootswatch themes with the ABP Framework. [See it here](https://community.abp.io/posts/customizing-the-abp-basic-theme-with-bootswatch-4luoqzr0). - -Thanks to all the contributors. It is appreciated if you want to submit your post and share your knowledge with the ABP community: https://community.abp.io/posts/submit - -## Conclusion - -This version was mostly about minor features and enhancements for existing features, and getting the framework, architecture and solution structure more mature. Currently, we are working on middle and long term features, changes and goals. You can find most of them on the [road map here](https://docs.abp.io/en/abp/5.2/Road-Map). - -The planned release date for the [5.2.0 Stable](https://github.com/abpframework/abp/milestone/66) version is April 05, 2022. Please try the ABP v5.2 RC and provide feedback to have a more stable release. diff --git a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/cms-kit-global-resources.png b/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/cms-kit-global-resources.png deleted file mode 100644 index ffd64b4ec0..0000000000 Binary files a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/cms-kit-global-resources.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/libs-folder.png b/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/libs-folder.png deleted file mode 100644 index 61f3f3edf5..0000000000 Binary files a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/libs-folder.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/single-layer-solution.png b/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/single-layer-solution.png deleted file mode 100644 index 6005edde7f..0000000000 Binary files a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/single-layer-solution.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/suite-many-to-many-entity-relation.png b/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/suite-many-to-many-entity-relation.png deleted file mode 100644 index a0cf54ee9a..0000000000 Binary files a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/suite-many-to-many-entity-relation.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/video-single-layer-tutorial.png b/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/video-single-layer-tutorial.png deleted file mode 100644 index d2cd8b67bb..0000000000 Binary files a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/video-single-layer-tutorial.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-04-05 v5_2_Release_Stable/POST.md b/docs/en/Blog-Posts/2022-04-05 v5_2_Release_Stable/POST.md deleted file mode 100644 index ed83f36e19..0000000000 --- a/docs/en/Blog-Posts/2022-04-05 v5_2_Release_Stable/POST.md +++ /dev/null @@ -1,51 +0,0 @@ -# ABP.IO Platform 5.2 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 5.2 versions have been released today. - -## What's New With 5.2? - -Since all the new features are already explained in details with the [5.2 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-5-2-RC-Has-Been-Published), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP.IO-Platform-5-2-RC-Has-Been-Published) for all the features and enhancements. - -## Creating New Solutions - -You can create a new solution with the ABP Framework version 5.2 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -## How to Upgrade an Existing Solution - -### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -### ABP UPDATE Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guide - -Check [the migration guide](https://docs.abp.io/en/abp/5.2/Migration-Guides/Abp-5_2) for the applications with the version 5.x upgrading to the version 5.2. - -## About the Next Version - -The next feature version will be 5.3. It is planned to release the 5.3 RC (Release Candidate) on May 03 and the final version on May 31, 2022. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). - -Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problem with this version. diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md deleted file mode 100644 index b23dab0cd1..0000000000 --- a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md +++ /dev/null @@ -1,258 +0,0 @@ -# ABP.IO Platform 5.3 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **5.3 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -> **The planned release date for the [5.3.0 Stable](https://github.com/abpframework/abp/milestone/69) version is May 31, 2022**. - -Please try this version and provide feedback for a more stable ABP version 5.3! Thank you all. - -## Get Started with the 5.3 RC - -Follow the steps below to try version 5.3.0 RC today: - -1) **Upgrade** the ABP CLI to version `5.3.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 5.3.0-rc.1 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 5.3.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -You can use any IDE that supports .NET 6.x, like **[Visual Studio 2022](https://visualstudio.microsoft.com/downloads/)**. - -## Migration Notes - -There is a change in this version that may effect your applications: - -* Upgraded the [AutoMapper](https://github.com/AutoMapper/AutoMapper) library to **v11.0.1**. So, you need to change your project's target SDK that use the **AutoMapper** library (typically your `*.Application` project). You can change it from `netstandard2.0` to `netstandard2.1` or `net6` if needed. You can write to [#12189](https://github.com/abpframework/abp/pull/12189) if you need any help. - -## What's New with ABP Framework 5.3? - -In this section, I will introduce some major features released with this version. Here is a brief list of titles explained in the next sections: - -* Single-layer option added to the [*Get Started*](https://abp.io/get-started) page -* PWA Support for Startup Templates -* Introduced the `Volo.Abp.Gdpr.Abstractions` package -* Batch Publish Events from Outbox to the Event Bus -* Improvements on **eShopOnAbp** Project & E-Book Announcement -* LeptonX Lite Documentations & Project Status & Roadmap -* OpenIddict Module & Keycloack Integration -* Deployment Documentations -* Other News - -### Single-layer Option on *Get Started* Page - -We've created a new startup template named `app-nolayers` and [announced](https://blog.abp.io/abp/ABP.IO-Platform-5-2-RC-Has-Been-Published) it in the previous version. In this version, we've also added this startup template option to the *Get Started* page. - -*You can examine the screenshot below to see how to create an `app-nolayers` template from the ["Get Started"](https://abp.io/get-started) page:* - -![](./app-nolayers-get-started.png) - -### PWA Support for Startup Templates - -ABP v5.3 application startup template now supports PWA for Blazor WASM & Angular UIs. To create a startup template with the PWA support, you can use the `--pwa` parameter. - -Example: - -```bash -abp new MyProgressiveWebApp -t app -u blazor --pwa -``` - -### Introducing the `Volo.Abp.Gdpr.Abstractions` Package - -A new `Volo.Abp.Gdpr.Abstractions` package has been added to the framework. This is an abstraction package, so doesn't contain any actual GDPR implementation. It defines some classes and interfaces to put a standard for who want to implement a GDPR module that can run in a modular or microservice system. - -At that point, we are introducing the **GDPR Module** for the ABP Commercial customers and this module does the GDPR-related operations on behalf of you, such as *"Download/Delete Personal Data"*. I'll describe the **GDPR Module** later in this blog post. - -> Please see the **GDPR Module** section below to learn more about this module. - -### Batch Publish Events from Outbox to the Event Bus - -We introduced the "Transactional Outbox & Inbox Patterns" in [**ABP v5.0**](https://blog.abp.io/abp/ABP-IO-Platform-5.0-RC-1-Has-Been-Released), it was one of the most awaited features by several software developers. - -We've made some optimizations for the **Batch Event Publishing** in this version, you can examine the related development from [here](https://github.com/abpframework/abp/pull/11243). After the optimization, the results are impressive. It is enabled by default (if you have configured [event outbox](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#outbox-inbox-for-transactional-events)), so you don't need to any manual configuration. - -### Improvements on eShopOnAbp Project & E-Book Announcement - -There are some developments on the [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) made in this version. You can see the brief descriptions of some of the improvements below: - -* Local certificates have been created to use while working in Kubernetes and also Helm Charts have been updated. See [#107](https://github.com/abpframework/eShopOnAbp/pull/107). -* The Order Management page has been created. See [#92](https://github.com/abpframework/eShopOnAbp/pull/92). -* Database migration event handlers have been removed and "Distributed Locking" is now used for database migrations. See [#85](https://github.com/abpframework/eShopOnAbp/pull/85) and [#102](https://github.com/abpframework/eShopOnAbp/pull/102). -* Switched from Ocelot to YARP as the gateway. See [#97](https://github.com/abpframework/eShopOnAbp/pull/97). - -We have exciting news to share with the community, we're working on an "ABP Microservice Development" e-book. In this book, we're using the eShopOnAbp project as a reference microservice solution and we're trying to explain our experiences during the microservice application development process through this project. - -We're planning to create this book in nine chapters and make it available after the third chapter is written. After that, you will be able to download this free e-book from the [abp.io](https://abp.io/) website. - -### LeptonX Lite Documentations & Project Status & Roadmap - -It is finally here, we've released the **1.0.0-beta.1** version for the **LeptonX Lite**. - -![](./leptonx-lite-documentations.png) - -Lepton X Lite documents have been written for the three UI types within this version. You can see the related documentation from the screenshot above. You can follow these documents and try the new **LeptonX Lite Theme**. - -We don't suggest using the **beta.1** version on production, we highly demand you to test **LeptonX Lite** and provide feedback to us. It's really important for us to be able to release a more stable version. Thanks in advance. - -For the following versions (beta.2 and RC versions), we will focus on: - -* Fixing the reported bugs from the community -* Providing documentations as much as possible -* Adding new custom pages to the demo - -### OpenIddict Module & Keycloack Integration - -We have [announced the plan of replacing the IdentityServer](https://github.com/abpframework/abp/issues/11989). ABP currently uses **IdentityServer4** to add **OAuth** features as built-in on the server-side. However, since *IdentityServer4's support ends at the end of the year 2022*. Its replacement is Duende IdentityServer, which is not a free software anymore. (see [more](https://blog.duendesoftware.com/posts/20220111_fair_trade/)) - -Therefore, we've decided to completely drop the **IdentityServer4** from the ABP platform and implement the [OpenIddict](https://github.com/openiddict/openiddict-core) and install onto the startup templates. - -We've implemented both open source and commercial OpenIddict modules, we plan to remove Identity Server and replace it with OpenIddict for template projects in **ABP v5.4**. Please check [#12084](https://github.com/abpframework/abp/pull/12084) to see the development made on the open-source side. - -We're creating the documentation for the OpenIddict Module, if you want to have general knowledge about this module, you can check the documentation from [here](https://github.com/abpframework/abp/blob/dev/docs/en/Modules/OpenIddict.md). Currently, this is a draft documentation but it gives overall knowledge about the OpenIddict Module, we'll complete this documentation in ABP v5.4 and you'll be able to read it completely. - -Currently, we are also working on Keycloak integration possibilities in parallel to the OpenIddict integration research and we've prepared some samples that you can examine. You can see [#154](https://github.com/abpframework/abp-samples/pull/154) and [#158](https://github.com/abpframework/abp-samples/pull/158). - -### Deployment Documentations - -Deploying an ABP-based application is not so different than deploying any .NET or ASP.NET Core application. You can deploy it to a cloud provider (e.g. Azure, AWS, Google Could) or on-premise server, IIS or any other web server. However, we wanted to prepare a "Deployment Guide" to mention the important points and considerations. - -![](./deployment-documentation.png) - -In the [Deploying to a Clustered Environment](https://docs.abp.io/en/abp/5.3/Deployment/Clustered-Environment) documentation, we've documented the topics that you should consider when you are developing your application to a clustered environment and explained how you can deal with these topics in your ABP-based application. - -### Other News - -* Global Features were only accessible from the C# code. From this version and on, Global Features can be also provided from application configurations. See [#12043](https://github.com/abpframework/abp/pull/12043). -* Getting the user's detailed information (name, surname and phone number) from external login. See [#12085](https://github.com/abpframework/abp/pull/12085). -* Date Pipes for Angular. See [#11909](https://github.com/abpframework/abp/issues/11909). - -If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/5.3.0-rc.1), which contains a list of all the issues and pull requests closed with this version. - -## What's New with ABP Commercial 5.3? - -### GDPR Module - -> **GDPR (General Data Protection Regulation)** is a regulation in EU law on data protection and known as the toughest privacy and security law in the world. GDPR applies to any organization operating within the EU, as well as any organizations outside of the EU which offer goods or services to customers or businesses in the EU. - -With this version, we are introducing the new **GDPR Module**. This was one of the most awaited features, so we've prioritized it and implemented it in this version. - -The GDPR Module is pre-installed in the [startup templates](https://docs.abp.io/en/commercial/5.3/startup-templates/index) for MVC. So, no need to manually install it. When you create a new startup template, you can directly use this module. We'll also implement this module for the other UI types as soon as possible and also add extra functionality such as "Cookie Consent" and more. - -Currently, there are two main functions of this module and they are "Download Personal Data" and "Delete Personal Data". - -![](./gdpr-user-menu.png) - -There is a "Personal Data" section in the user menu as in the screenshot above and when you click on this section, you'll be redirected to the "Personal Data" page. On that page, you can either request to "Download Personal Data" or "Delete Personal Data". - -![](./gdpr-personal-data-page.png) - -After you've requested to download "Personal Data", you need to wait for 1 hour by default (you can configure the related option). Because the GDPR module is developed by considering the distributed systems and therefore a specific time should be passed to ensure all the published events are handled and all personal data is collected. - -### CMS Kit Pro - Polling Feature - -We've added a **Polling** feature to the **CMS Kit Pro** module. This feature allows you to use a questionnaire/voting system in your application easily. You can create a question, define some options for it and the poll will be created for you. You can see the example poll in the screenshot below: - -![](./poll-question-example.png) - -Also, there is an admin side of the Polling Feature. You can easily manage your polls in your admin (back-office) project. You can create, update, delete and show the results of the poll on the Polls page: - -![](./poll-questions.png) - -### OAuth Resource Owner Password as External Login Provider - -> The Resource Owner Password flow allows for the exchanging of the username and password of a user for an access token. When using the resource owner password credentials grant, the user provides the credentials (username and password) directly to the application. - -Now, you can login by entering a username and password from an OAuth server. - -Example: Use OAuth external login provider with Keycloak: - -![](./oauth-external-provider.gif) - -### Suite New Features & Enhancements - -In this version, there are some enhancements and new features in **Suite** and they are listed briefly below: - -* It's now possible to create an **app-nolayers (Application - single layer)** template via Suite and also code-generation is supported for the **app-nolayers** template with this version. -* Suite now allows users to see and download its logs. -* Suite now allows generating code via CLI. If you have a JSON file that contains code blocks, like entity configurations, you can use the `abp suite generate` command to generate CRUD pages based on it. - -Example: - -```bash -abp suite generate -e C:\Users\.suite\entities\Country.json -s C:\Users\my-proj\SuiteProj\SuiteProj.sln -``` - -### Suite Webinar: Take a closer look at the code generation - -![](./webinar.png) - -We've organized a webinar for Suite and in this webinar, we've talked about ABP Suite's capabilities, important features and more... - -You can watch the event from [here](https://www.youtube.com/watch?v=RFArBh60RSA&t=3s), if you haven't watched it yet. - -### Docker Compose Configurations for Single Layer Startup Template - -Dockerfiles, docker-compose files and build script files have been added to the Single Layer Startup Template (app-nolayers) with this version. - -And this way, applications created with this template now can be deployed more easily. - -### Microservice Solution Enhancements - -There are some enhancements made in the Microservice Solution. You can see the list of these enhancements: - -* Initial migration on the template has been updated with the small improvement that was made in the **Language Management** module. -* Database migration event handlers have been removed and "Distributed Locking" is now used for the database migrations. - -### PWA Support for the Application Pro Template - -Application Pro template also supports the PWA for Blazor WASM & Angular UIS. To create a startup template with the PWA support, you can use the `--pwa` parameter. -Example: - -```bash -abp new MyProgressiveWebApp -t app-pro -u blazor --pwa -``` - -## Community News - -### New ABP Community Posts - -* [Anto Subash](https://twitter.com/antosubash) created a series named ["Microservice with ABP"](https://blog.antosubash.com/posts/abp-microservice-series) and shared a couple of video posts about the ABP Microservice solution. -* [Francisco Kadzi](https://github.com/CiscoNinja) has created his first ABP Community article that shows how to ["Customize ABP Angular Application UI with AdminLTE"](https://community.abp.io/posts/customize-abp-angular-application-ui-with-adminlte.-7qu1m67s). -* [Jack Fistelmann](https://github.com/nebula2) has created an article to introduce a helpful project extension to speed up development on Visual Studio. You can read the article [here](https://community.abp.io/posts/using-switch-startup-project-extension-for-visual-studio-52yyw27v). -* [Jack Fistelmann](https://github.com/nebula2) has also created an article to show how you can generate PDF files with the `Sycyber.Core` package in ABP-based applications. You can read it [here](https://community.abp.io/posts/generate-pdfs-in-an-abp-framework-project-using-scryber.core-x9yh1vfa). -* [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) has created an article to show ["Dealing with Multiple Implementations of a Service in ASP.NET Core & ABP Dependency Injection"](https://community.abp.io/posts/dealing-with-multiple-implementations-of-a-service-in-asp.net-core-abp-dependency-injection-ysfp4ho2) with examples. -* [Manoj Kumar](https://community.abp.io/members/manojkumar.t@shloklabs.com) submitted a new article about how to use "ABP authentication in a Flutter application". It was a frequently asked topic, which you can read [here](https://community.abp.io/posts/flutter-web-authentication-from-abp-mp6l2ehx). -* [Engincan Veske](https://twitter.com/EngincanVeske) created a new Community Article to show "Concurrency Check/Control in ABP". You can read it [here](https://community.abp.io/posts/handle-concurrency-with-ef-core-in-an-abp-framework-project-with-asp.net-core-mvc-jlkc3w8f). - -### ABP Community Talks 2022.4: How can you contribute to the open source ABP Framework? (May 10, 2022 - 17:00 UTC) - -![](./community-talks-2022.4.png) - -We've [asked you to pick the topic of the next Community Talks](https://twitter.com/abpframework/status/1514567683072745474?s=20&t=rJfHrB3DYDNsk2EXS8zBBQ) and you've chosen the "How to contribute to open source ABP Framework?" for the next talk topic. So, in this Community Talk, we will be talking about "How to contribute to ABP Framework" with one of the top contributors of the ABP Framework, [Ismail Yılmaz](https://github.com/iyilm4z). The event will be on **May 10, 2022, at 17:00 (UTC)** on YouTube. - -> You can register for the event from [here](https://kommunity.com/volosoft/events/abp-community-talks-20224-how-to-contribute-to-the-open-source-abp-framework-d9b50664), if you haven't registered yet. - -You can also [subscribe to the Volosoft channel](https://www.youtube.com/channel/UCO3XKlpvq8CA5MQNVS6b3dQ) to be informed about future ABP events and videos. - -### Discord Server - -We've created an official ABP Discord server so the ABP Community can interact with each other and created a blog-post to introduce it. You can read the [ABP Discord Server announcement post](https://blog.abp.io/abp/Official-ABP-Discord-Server-is-Here) to learn more about the ABP Discord Server. - -Thanks to the ABP Community, **700+** people joined our Discord Server so far and it grows every day. - -You can join our Discord Server from [here](https://discord.gg/abp), if you haven't yet. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/app-nolayers-get-started.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/app-nolayers-get-started.png deleted file mode 100644 index 736d3b7a88..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/app-nolayers-get-started.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/community-talks-2022.4.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/community-talks-2022.4.png deleted file mode 100644 index ee5f374ff4..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/community-talks-2022.4.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/cover-53.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/cover-53.png deleted file mode 100644 index 3efb3f5a42..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/cover-53.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/deployment-documentation.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/deployment-documentation.png deleted file mode 100644 index 1015f7ce18..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/deployment-documentation.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-personal-data-page.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-personal-data-page.png deleted file mode 100644 index 5beb5551ac..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-personal-data-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-user-menu.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-user-menu.png deleted file mode 100644 index c617f9348a..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-user-menu.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/leptonx-lite-documentations.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/leptonx-lite-documentations.png deleted file mode 100644 index 124aa35ecf..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/leptonx-lite-documentations.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/oauth-external-provider.gif b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/oauth-external-provider.gif deleted file mode 100644 index 8f301559ee..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/oauth-external-provider.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-new-question.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-new-question.png deleted file mode 100644 index 73cadda6c1..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-new-question.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-question-example.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-question-example.png deleted file mode 100644 index 1f048819b3..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-question-example.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-questions.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-questions.png deleted file mode 100644 index bee6caac75..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-questions.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/webinar.png b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/webinar.png deleted file mode 100644 index 9126215031..0000000000 Binary files a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/webinar.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-06-15 v5_3_Release_Stable/POST.md b/docs/en/Blog-Posts/2022-06-15 v5_3_Release_Stable/POST.md deleted file mode 100644 index 1f52772389..0000000000 --- a/docs/en/Blog-Posts/2022-06-15 v5_3_Release_Stable/POST.md +++ /dev/null @@ -1,69 +0,0 @@ -# ABP.IO Platform 5.3 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 5.3 versions have been released today. - -## What's New With 5.3? - -Since all the new features are already explained in details with the [5.3 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-5.3-RC-Has-Been-Published), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP.IO-Platform-5.3-RC-Has-Been-Published) for all the features and enhancements. - -## Getting Started with 5.3 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 5.3 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -Check the following migration guides for the applications with version 5.2 that are upgrading to version 5.3. - -* [ABP Framework 5.2 to 5.3 migration guide](https://docs.abp.io/en/abp/5.3/Migration-Guides/Abp-5_3) -* [ABP Commercial 5.2 to 5.3 migration guide](https://docs.abp.io/en/commercial/5.3/migration-guides/v5_3) - -## Community News - -### New ABP Community Posts - -Here are some of the recent posts added to the [ABP Community](https://community.abp.io/): - -* [Integrating Elsa .NET Workflows with ABP Commercial](https://community.abp.io/posts/integrating-elsa-.net-workflows-with-abp-commercial-io32k420) by [kirtik](https://community.abp.io/members/kirtik). -* [ABP's Conventional Dependency Injection](https://community.abp.io/posts/abps-conventional-dependency-injection-948toiqy) by [iyilm4z](https://github.com/iyilm4z). -* [How to implement Single Sign-On with ABP commercial application](https://community.abp.io/posts/how-to-implement-single-signon-with-abp-commercial-application-m5ek38y9) by [kirtik](https://community.abp.io/members/kirtik). -* [Introduce DTM for Multi-Tenant Multi-Database Scene](https://community.abp.io/posts/introduce-dtm-for-multitenant-multidatabase-scene-23ziikbe) by [gdlcf88](https://github.com/gdlcf88). - -Thanks to the ABP Community for all the contents they have published. You can also [post your ABP related (text or video) contents](https://community.abp.io/articles/submit) to the ABP Community. - -## About the Next Version - -The next feature version will be 6.0. It is planned to release the 6.0 RC (Release Candidate) on July 12 and the final version on August 16, 2022. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). - -Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md deleted file mode 100644 index e545681d71..0000000000 --- a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md +++ /dev/null @@ -1,380 +0,0 @@ -# ABP.IO Platform 6.0 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **6.0 RC** (release candidate). This blog post introduces the new features and important changes in this new version. - -> **The planned release date for the [6.0.0 Stable](https://github.com/abpframework/abp/milestone/71) version is September 06, 2022**. - -Try this version and provide feedback for the stable ABP v6.0! Thank you to all. - -## Get Started with the 6.0 RC - -Follow the steps below to try version 6.0.0 RC today: - -1) **Upgrade** the ABP CLI to version `6.0.0-rc.5` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 6.0.0-rc.5 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 6.0.0-rc.5 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. - -You can use any IDE that supports .NET 6.x, like **[Visual Studio 2022](https://visualstudio.microsoft.com/downloads/)**. - -## Migration Guides - -There are breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v5.3.0: - -* [ABP Framework 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/abp/6.0/Migration-Guides/Abp-6_0) -* [ABP Commercial 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/commercial/6.0/migration-guides/v6_0) - -## What's New with ABP Framework 6.0? - -In this section, I will introduce some major features released in this version. Here is a brief list of titles explained in the next sections: - -* **LeptonX Lite** is now the **default theme** for startup templates. -* Optional PWA support is added to [*Get Started*](https://abp.io/get-started) page. -* Introducing the **OpenIddict Module** and switching to OpenIddict for the startup templates. -* New **.NET MAUI** Startup Template. -* Introducing the `ITransientCachedServiceProvider` interface. -* Introducing the dynamic components for Blazor UI. -* Improvements on ABP CLI. -* Introducing the `Volo.Abp.RemoteServices` package. -* Create/Update user accounts for external logins. -* Sending test email in the setting page for MVC and Blazor user interfaces. -* Improvements on the **eShopOnAbp** project. -* Other news... - -### LeptonX Lite Theme on Startup Templates - -![](leptonx-lite-theme.png) - -With this version, startup templates (`app` and `app-nolayers` templates) use the **LeptonX Lite** as the default theme. However, it's still possible to create a project with **Basic Theme** either using the **ABP CLI** or downloading the project via [*Get Started*](https://abp.io/get-started) page on the [abp.io](https://abp.io/) website. - -#### via ABP CLI - -To create a new project with **Basic Theme**, you can use the `--theme` option as below: - -```bash -abp new Acme.BookStore --theme basic --preview -``` - -#### via Get Started page - -Also, you can create a new project with **LeptonX Lite** or **Basic Theme** on *Get Started* page. - -![](get-started-page.png) - -> The "Preview" checkbox should be checked to be able to see the theme selection section on the *Get Started* page. - - - -### Optional PWA Support is Added to the Get Started Page - -We've introduced the PWA (Progressive Web Application) support for the startup templates for Angular & Blazor WASM UIs in **v5.3**. In this version, we also added this PWA support to the [*Get Started*](https://abp.io/get-started) page on the [abp.io](https://abp.io/) website. - -![](pwa-support-get-started-page.png) - -If you check the "Progressive Web Application" checkbox while creating an application, the all required configurations will be done for you and you will get the benefit of PWA features in your application. - - - -### Introducing the **OpenIddict Module** and Switching to OpenIddict in the Startup Templates - -We already [announced the plan of replacing the IdentityServer with OpenIddict](https://github.com/abpframework/abp/issues/11989). - -Therefore, we have created the `OpenIddict` module in this version and switched to **OpenIddict** in the startup templates. The ABP Framework uses this module to add **OAuth** features to the applications. We created documentation for the **OpenIddict Module**. - -- You can see the following document to **learn about the OpenIddict Module**: - [https://docs.abp.io/en/abp/6.0/Modules/OpenIddict](https://docs.abp.io/en/abp/6.0/Modules/OpenIddict) -- You can check out the following migration guide to learn **how to migrate to OpenIddict**: - [https://docs.abp.io/en/abp/6.0/Migration-Guides/IdentityServer_To_OpenIddict](https://docs.abp.io/en/abp/6.0/Migration-Guides/IdentityServer_To_OpenIddict) - - - -> We will continue to ship Identity Server packages for a while but in the long term, you may need to replace it, because Identity Server support ends at the end of 2022. Please see the [announcement]((https://github.com/abpframework/abp/issues/11989)) for more info. - - - -### New .NET MAUI Startup Template - -![](maui-template.png) - -ABP Framework provides .NET MAUI startup templates with **v6.0.0**. You can create a new .NET MAUI project with the command below: - -```bash -abp new Acme.BookStore -t maui -``` - - - -### Introducing the `ITransientCachedServiceProvider` - -`ICachedServiceProvider` interface is used to resolve the cached services within a new scope. We created a new interface to resolve cached services **without creating scopes**. It's called `ITransientCachedServiceProvider`. The difference between `ICachedServiceProvider` and `ITransientCachedServiceProvider` is; `ITransientCachedServiceProvider` is transient. Check out [this issue](https://github.com/abpframework/abp/issues/12918) for more information. - - - -### Introducing the dynamic layout components for Blazor UI - -ABP Framework provides different ways of customizing the UI and one of them is to use [Layout Hooks](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Layout-Hooks) in MVC. The **Layout Hook System** allows you to add code to some specific parts of the layout and all layouts of the themes provided by the ABP Framework implement these hooks. - -However, Blazor UI doesn't have such a system yet and we are planning to implement [Layout Hooks for the Blazor UI](https://github.com/abpframework/abp/issues/6261) in version 7.0. - -We are introducing the dynamic layout components for the Blazor UI to be able to add components to the Blazor layouts. - -You can configure the `AbpDynamicLayoutComponentOptions` to render your components in the layout, as below: - -```csharp -Configure(options => -{ - options.Components.Add(typeof(MyBlazorComponent), null); -}); -``` - - - -### Improvements in ABP CLI - -There are some enhancements in [ABP CLI](https://docs.abp.io/en/abp/6.0/CLI). You can see the brief list of some of these improvements below: - -* You can list all available templates by using the `abp list-templates` command with v6.0. See [#13083](https://github.com/abpframework/abp/pull/13083). -* You can select the theme when creating a new project by specifying the `--theme` option. You can see the *LeptonX Lite Theme on the Startup Templates* section above for an example. -* `abp update` command has been updating the version of the main application until now. With v6.0.0, this command updates all package versions **inside all solutions in the sub-folders**. Checkout the issue [#12735](https://github.com/abpframework/abp/pull/12738) for more information. - - - -### Introducing the `Volo.Abp.RemoteService` Package - -A new `Volo.Abp.RemoteService` package has been added to the framework. Some of the classes that are related to the remote service configurations such as `AbpRemoteServiceOptions` class moved from `Volo.Abp.Http.Client` to this package. In this way, it became more reusable for further usages. - - - -### Create/Update User Accounts For External Logins - -If a user authenticates from an external provider like `Keycloak`, the user is being redirected to this external provider, and comes back to the main application. In this process, the user's data is not being saved in the main application's database. With this version, ABP saves the user information and lists in the users page. And this fixes permission management, user information mismatches and other issues. For more info, see [the related issue](https://github.com/abpframework/abp/issues/12203). - - - -### Sending test email in the setting page for MVC and Blazor UIs - -"Sending Test Email" feature is added to the [Setting Management](https://docs.abp.io/en/abp/6.0/Modules/Setting-Management) module, which allows checking the email settings are configured properly and sending emails successfully to the target email address. - -![](setting-management-emailing.png) - -After configuring the email settings such as the target email address, you can click the "Send" button to send a test email to see if everything went well. - -> Note that this feature will be implemented for the Angular UI in the stable v6.0. - - - -### Improvements on eShopOnAbp Project - -The following improvements have been made on [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) with this version: - -* Some improvements have been made on the Admin Application for Order Management for Angular UI. See [#110](https://github.com/abpframework/eShopOnAbp/pull/110). -* `SignalR` error on Kubernetes & Docker Compose has been fixed. See [#113](https://github.com/abpframework/eShopOnAbp/pull/113). -* eShopOnAbp project has been deployed to Azure Kubernetes Service. See [#114](https://github.com/abpframework/eShopOnAbp/pull/114). The live demo can be seen from [eshoponabp.com](https://eshoponabp.com/). -* Configurations have been made for some services on the `docker-compose.yml` file. See [#112](https://github.com/abpframework/eShopOnAbp/pull/112). -* Gateway Redirect Loop problem on Kubernetes has been fixed. See [the commit](https://github.com/abpframework/eShopOnAbp/commit/6413ef15c91cd8a5309050b63bb4dbca23587607). - - - -### Other News - -* Autofac library has been upgraded to **v6.4.0**. Please see [#12816](https://github.com/abpframework/abp/pull/12816) for more info. -* Performance Improvements have been made in the **Settings Module** and tabs on the *Settings* page are lazy loading now. -* Some improvements have been made in the CMS Kit Module. You can see the improvements from [here](https://github.com/abpframework/abp/issues/11965). - -If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/6.0.0-rc.5), which contains a list of all the issues and pull requests closed in this version. - - - -## What's New with ABP Commercial 6.0? - - - -### LeptonX Theme is the Default Theme - -With this version, the startup templates (`app-pro`, `app-nolayers-pro` and `microservice-pro` templates) use the **LeptonX Theme** as the default theme. However, it's still possible to create a new project with **Lepton Theme** or **Basic Theme**, either using the **ABP CLI** or **ABP Suite**. - -#### via ABP CLI - -To create a new project with **Lepton Theme** or **Basic Theme**, you can use the `--theme` option as below. For "Basic Theme" specify the theme name as `--theme basic`. - -```bash -abp new Acme.BookStore --theme lepton --preview -``` - - - -#### via ABP Suite - -Also, you can create a new project with **Lepton Theme** or **Basic Theme** from ABP Suite. - -![](suite-create-new-solution.png) - -### Switching to OpenIddict in the Startup Templates - -We have also switched to the **OpenIddict** for the startup templates for ABP Commercial as explained above. - - - -### New .NET MAUI Mobile - -![](maui-mobile-option.gif) - -ABP Commercial has been providing a [React Native](https://docs.abp.io/en/commercial/latest/getting-started-react-native) mobile app since with the very early versions. Alternative to this application, we created a new .NET MAUI mobile app. To create a new `app-pro` ABP project with the .NET MAUI mobile app, you can use the command below: - -```bash -abp new Acme.BookStore -t app-pro --mobile maui -``` - -> Note that, when Microsoft supports `WebAuthenticator` on Windows, we'll also support it to work on Windows OS. - - - -### GDPR: Cookie Consent - -![](cookie-banner.png) - -With this version, the **Cookie Consent** feature has been added to the **GDPR** module. It's enabled by default for the new startup templates. There are two pages in the templates: "Cookie Policy" page and "Privacy Policy" page. - -If you want to disable/hide the "Cookie Consent", you can simply open the startup project module class and set the `IsEnabled` property as **false** for the **AddAbpCookieConsent** method as below: - -```csharp -context.Services.AddAbpCookieConsent(options => -{ - options.IsEnabled = false; //disabled - options.CookiePolicyUrl = "/CookiePolicy"; - options.PrivacyPolicyUrl = "/PrivacyPolicy"; -}); -``` - -> These pages are used to build up the cookie consent text and you can change the content or url of these pages by your needs. - -If you want to use the Cookie Consent feature of the GDPR module in your existing project, please see the [GDPR Module](https://docs.abp.io/en/commercial/6.0/modules/gdpr) documentation for configurations. - -### Improvements/Developments on CMS Kit Poll - -Some improvements have been made on the Poll System of CMS Kit module as listed below: - -* The Widget rendering and Admin side for the Blazor UI improvements. -* A Widget can be picked from the editor as seen in the image below. - -![](poll-add-widget.png) - - - -### Blazor UI for the Chat Module - -Chat Module is now also available for the Blazor UI after the MVC and Angular UIs. You can read the [Chat Module](https://docs.abp.io/en/commercial/6.0/modules/chat) documentation to get the overall knowledge about the module and add to your application. - -![](blazor-chat-module-1.png) -![](blazor-chat-module-2.png) - - - -### Blazor Admin UI for CMS Kit Module - -All admin side **CMS Kit** and **CMS Kit Pro** features have been implemented for the Blazor UI. Blazor UI will only be available to ABP Commercial customers. - -![](cms-blog-blazor.png) - -![](cms-blog-post-blazor.png) - - -### Suite: Excel Export - -With v6.0, now it's possible to export the records as Excel for Blazor & MVC UIs. Angular UI is still in-progress, and we will implement it with the stable v6.0 release. Check the "Excel export" checkbox to add this feature. - -![](excel-export.png) - - - -A new Excel Export button is being located at the top of the generated page as seen below: - -![](export-excel-page.png) - -Then, you can download the records as `.xlsx` format by clicking the "Excel Export" button. Note that the exported Excel list is the filtered list. - - - -### ABP Suite: Optional PWA Support - -With this version, it's possible to add the [PWA (Progressive Web App)](https://web.dev/progressive-web-apps/?gclid=Cj0KCQjwxIOXBhCrARIsAL1QFCY0IB-W5k-lsXmRCbm00sl4nyBIYynAX3IdJkjyizyNUjuCE8zeu24aApxtEALw_wcB) support for Blazor & Angular UIs while creating the application via Suite. - -![](suite-pwa-support.png) - -You just need to check the "Progressive web application" checkbox, when creating a new application. Then, ABP Suite will add the PWA support to your application. When you publish your application, you get the full benefits of PWA features such as offline support. - - - -### Other News - -#### Explainer Videos - -We are creating explainer videos for the ABP Commercial Modules to provide an overview. Within this milestone, we've created four new explainer videos: - -* [Audit Logging Module](https://www.youtube.com/watch?v=NzSuFBpqfsc) -* [Identity Module](https://www.youtube.com/watch?v=W87jA_GBE54) -* [SaaS Module](https://www.youtube.com/watch?v=xXlaaXP6qqQ) -* [Forms Module](https://www.youtube.com/watch?v=MousWEPfrA8) - -You can subscribe to [Volosoft's YouTube channel](https://www.youtube.com/channel/UCO3XKlpvq8CA5MQNVS6b3dQ) to be informed about the future ABP events and videos. - - - -### Trial License is now available! - -![](pricing-page.png) - -If you are considering purchasing a new ABP Commercial license, and you want to see ABP in action then, check out https://commercial.abp.io/pricing and click FREE TRIAL button. - - - -## Community News - -### New ABP Community Posts - -* [Alper Ebicoglu](https://twitter.com/alperebicoglu) has created a new community article to give a full overview of .NET MAUI. You can read it [here](https://community.abp.io/posts/all-about-.net-maui-gb4gkdg5). -* [Anto Subash](https://twitter.com/antosubash) has created a new video content to show "State Management in Blazor with Fluxor". You can read it [here](https://community.abp.io/posts/blazor-state-management-with-fluxor-raskpv19). -* [Learn ABP Framework](https://community.abp.io/members/learnabp) has also created a new video content to show "How to install LeptonX Lite Theme for ABP Framework 5.3 MVC UI". You can read it [here](https://community.abp.io/posts/how-to-install-leptonx-lite-theme-on-abp-framework-5.3-mvc-ui-epzng137). -* [Kirti Kulkarni](https://twitter.com/kirtimkulkarni) has created three new community articles. You can use the links below to read the articles: - * [Integrating the file management module with ABP Commercial application](https://community.abp.io/posts/integrating-the-file-management-module-with-abp-commercial-application-qd6v4dsr) - * [Work with PDF's in ABP Commercial Project using PDFTron](https://community.abp.io/posts/work-with-pdfs-in-abp-commercial-project-using-pdftron-tjw0hlgu) - * [Create a custom login page in ABP Commercial Angular app](https://community.abp.io/posts/create-a-custom-login-page-in-abp-commercial-angular-app-r2huidx7) -* [Don Boutwell](https://community.abp.io/members/dboutwell) has created his first ABP Community article. You can read it from [here](https://community.abp.io/posts/password-required-redis-with-abp-framework-and-docker-94old5rm). - - - -### Volosoft Has Attended the DNF Summit 2022 - -![](dnf-summit.png) - -Core team members of ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) and [Alper Ebicoglu](https://twitter.com/alperebicoglu) have attended the [DNF Summit](https://t.co/ngWnBLiAn5) on the 20th of July. Halil Ibrahim Kalkan talked about the creation of the ABP Framework and Alper Ebicoglu showed how easy to create a project with ABP Framework within 15 minutes. - -Watch DNF Summit session 👉 https://www.youtube.com/embed/VL0ewZ-0ruo - -![](dnf-summit-attendees.jpg) - - - -## Conclusion - -This version comes with some features and enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/6.0/Road-Map) documentation to learn about the release schedule and planned features for the next releases. The planned release date for the [6.0.0 Stable](https://github.com/abpframework/abp/milestone/71) version is September 06, 2022. Please try the ABP v6.0 RC and provide feedback to us. - -Thanks for being a part of this community! \ No newline at end of file diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/blazor-chat-module-1.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/blazor-chat-module-1.png deleted file mode 100644 index 00ba2882d2..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/blazor-chat-module-1.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/blazor-chat-module-2.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/blazor-chat-module-2.png deleted file mode 100644 index 0fbc244b04..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/blazor-chat-module-2.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cms-blog-blazor.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cms-blog-blazor.png deleted file mode 100644 index 37c428b75e..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cms-blog-blazor.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cms-blog-post-blazor.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cms-blog-post-blazor.png deleted file mode 100644 index badedb60d5..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cms-blog-post-blazor.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cookie-banner.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cookie-banner.png deleted file mode 100644 index 44e5195e1f..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cookie-banner.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cover-image.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cover-image.png deleted file mode 100644 index 56f833edcb..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/dnf-summit-attendees.jpg b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/dnf-summit-attendees.jpg deleted file mode 100644 index 314c44d58e..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/dnf-summit-attendees.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/dnf-summit.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/dnf-summit.png deleted file mode 100644 index 97e6160211..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/dnf-summit.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/excel-export.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/excel-export.png deleted file mode 100644 index 7a510943ab..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/excel-export.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/export-excel-page.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/export-excel-page.png deleted file mode 100644 index b635ffbd5e..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/export-excel-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/get-started-page.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/get-started-page.png deleted file mode 100644 index 3b90b515c1..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/get-started-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/leptonx-lite-theme.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/leptonx-lite-theme.png deleted file mode 100644 index 703f80f07d..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/leptonx-lite-theme.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/maui-mobile-option.gif b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/maui-mobile-option.gif deleted file mode 100644 index 645346abc6..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/maui-mobile-option.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/maui-template.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/maui-template.png deleted file mode 100644 index c742d208aa..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/maui-template.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/poll-add-widget.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/poll-add-widget.png deleted file mode 100644 index 46d4ce47e1..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/poll-add-widget.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/pricing-page.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/pricing-page.png deleted file mode 100644 index 5b1ca8baf8..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/pricing-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/pwa-support-get-started-page.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/pwa-support-get-started-page.png deleted file mode 100644 index 9d38d48772..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/pwa-support-get-started-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/setting-management-emailing.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/setting-management-emailing.png deleted file mode 100644 index ffb20ca798..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/setting-management-emailing.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/suite-create-new-solution.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/suite-create-new-solution.png deleted file mode 100644 index 109a07be2b..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/suite-create-new-solution.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/suite-pwa-support.png b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/suite-pwa-support.png deleted file mode 100644 index 271aeac8fd..0000000000 Binary files a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/suite-pwa-support.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-09-21 v6_0_Release_Stable/POST.md b/docs/en/Blog-Posts/2022-09-21 v6_0_Release_Stable/POST.md deleted file mode 100644 index 2049c24352..0000000000 --- a/docs/en/Blog-Posts/2022-09-21 v6_0_Release_Stable/POST.md +++ /dev/null @@ -1,76 +0,0 @@ -# ABP.IO Platform 6.0 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 6.0 versions have been released today. - -## What's New With 6.0? - -Since all the new features are already explained in details with the [6.0 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published) for all the features and enhancements. - -## Getting Started with 6.0 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 6.0 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -Check the following migration guides for the applications with version 5.3 that are upgrading to version 6.0. - -* [ABP Framework 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/abp/6.0/Migration-Guides/Abp-6_0) -* [ABP Commercial 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/commercial/6.0/migration-guides/v6_0) - -## Community News - -### New ABP Community Posts - -Here are some of the recent posts added to the [ABP Community](https://community.abp.io/): - -* [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) has created two new community articles: - * [Consuming gRPC Services from Blazor WebAssembly Application Using gRPC-Web](https://community.abp.io/posts/consuming-grpc-services-from-blazor-webassembly-application-using-grpcweb-dqjry3rv) - * [Using gRPC with the ABP Framework](https://community.abp.io/posts/using-grpc-with-the-abp-framework-2dgaxzw3) -* [Malik Masis](https://twitter.com/malikmasis) also has created two new community articles: - * [Consuming HTTP APIs from a .NET Client Using ABP's Client Proxy System](https://community.abp.io/posts/consuming-http-apis-from-a-.net-client-using-abps-client-proxy-system-xriqarrm) - * [Using MassTransit via eShopOnAbp](https://community.abp.io/posts/using-masstransit-via-eshoponabp-8amok6h8) -* [Xeevis](https://community.abp.io/members/Xeevis) has created her/his first community article, that shows [Prerendering in Blazor WASM applications](https://community.abp.io/posts/prerendering-blazor-wasm-application-with-abp-6.x-2v8590g3). -* [Don Boutwell](https://community.abp.io/members/dboutwell) has created two new community articles: - * [Logging to Datadog from ABP framework](https://community.abp.io/posts/logging-to-datadog-from-abp-framework-fm4ozds4) - * [Configuring Multiple DbContexts in an ABP Framework Project](https://community.abp.io/posts/configuring-multiple-dbcontexts-in-an-abp-framework-project-uoz5is3o) -* [Kirti Kulkarni](https://twitter.com/kirtimkulkarni) has created a new community article: [Deploying ABP angular application to Azure and App Insights integration](https://community.abp.io/posts/deploying-abp-angular-application-to-azure-and-app-insights-integration-4jrhtp01) - -Thanks to the ABP Community for all the contents they have published. You can also [post your ABP related (text or video) contents](https://community.abp.io/articles/submit) to the ABP Community. - -## About the Next Version - -The next feature version will be 7.0. It is planned to release the 7.0 RC (Release Candidate) on November 15 and the final version on December 13, 2022. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). - -Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/POST.md b/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/POST.md deleted file mode 100644 index 5fbae02601..0000000000 --- a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/POST.md +++ /dev/null @@ -1,310 +0,0 @@ -# ABP.IO Platform 7.0 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.0 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -Try this version and provide feedback for a more stable version of ABP v7.0! Thanks to all of you. - -## Get Started with the 7.0 RC - -Follow the steps below to try version 7.0.0 RC today: - -1) **Upgrade** the ABP CLI to version `7.0.0-rc.2` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 7.0.0-rc.2 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 7.0.0-rc.2 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command for creating a new application. - -You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). - -## Migration Guides - -There are breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v6.x: - -* [ABP Framework 6.x to 7.0 Migration Guide](https://docs.abp.io/en/abp/7.0/Migration-Guides/Abp-7_0) -* [ABP Commercial 6.x to 7.0 Migration Guide](https://docs.abp.io/en/commercial/7.0/migration-guides/v7_0) - -## What's New with ABP Framework 7.0? - -In this section, I will introduce some major features released in this version. Here is a brief list of titles explained in the next sections: - -* Upgraded to .NET 7.0 -* Upgraded to OpenIddict 4.0 -* Dapr Integration -* Integration Services -* Dynamic Permissions and Features -* External Localization Infrastructure -* Distributed Entity Cache Service -* Layout Hooks for the Blazor UI -* Improvements on the eShopOnAbp project - -### Upgraded to .NET 7.0 - -We've upgraded the ABP Framework to .NET 7.0, so you need to move your solutions to .NET 7.0 if you want to use ABP 7.0. - -> You can check the [Migrate from ASP.NET Core 6.0 to 7.0](https://learn.microsoft.com/en-us/aspnet/core/migration/60-70?view=aspnetcore-7.0) documentation. Also, there is an ABP Community article that shows how to upgrade an existing project to .NET 7.0. You can check it from 👉 [here](https://community.abp.io/posts/upgrade-your-existing-projects-to-.net7-nmx6vm9m). - -### Upgraded to OpenIddict 4.0 - -OpenIddict 4.0 preview has been released on June 22. So, we decided to upgrade the OpenIddict packages to 4.0-preview in ABP 7.0. - -Once the final release of OpenIddict 4.0 is published, we will immediately upgrade it to the stable version and we plan to make ABP 7.0 final use the stable version of OpenIddict 4.0. - -> You can read the "[OpenIddict 4.0 preview1 is out](https://kevinchalet.com/2022/06/22/openiddict-4-0-preview1-is-out/)" post to learn what's new with OpenIddict 4.0. - -### Dapr Integration - -[Dapr (Distributed Application Runtime)](https://dapr.io/) provides APIs that simplify microservice connectivity. - -ABP and Dapr have some intersecting features like service-to-service communication, distributed message bus and distributed locking. However, the purposes of ABP and Dapr are different. ABP's goal is to provide an end-to-end developer experience with an opinionated architecture. On the other hand, Dapr's purpose is to provide a runtime to decouple common microservice communication patterns from your application logic. - -ABP 7.0 offers some packages to provide better integration with Dapper. I will cover some important integration notes below but if you want to get a full overview of ABP Dapr Integration please see the [ABP Dapr Integration documentation](https://docs.abp.io/en/abp/7.0/Dapr/Index). - -#### Distributed Event Bus Integration - -ABP's [Distributed Event Bus System](https://docs.abp.io/en/abp/7.0/Distributed-Event-Bus) provides a convenient abstraction to allow applications to communicate asynchronously via events. - -The new [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) and [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) packages make it possible to use the Dapr infrastructure with the ABP's standard distributed event bus abstractions. The **Volo.Abp.EventBus.Dapr** package is used to publish events and the **Volo.Abp.AspNetCore.Mvc.Dapr.EventBus** package is used to subscribe to events. - -> See [the documentation](https://docs.abp.io/en/abp/7.0/Dapr/Index#distributed-event-bus-integration) to learn more. - -#### C# API Client Proxies Integration - -ABP can [dynamically](https://docs.abp.io/en/abp/7.0/API/Dynamic-CSharp-API-Clients) or [statically](https://docs.abp.io/en/abp/7.0/API/Static-CSharp-API-Clients) generate proxy classes to invoke your HTTP APIs from a Dotnet client application. - -The [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) package configures the client-side proxy system, so it uses Dapr's service invocation building block for the communication between your applications. - -> See [the documentation](https://docs.abp.io/en/abp/7.0/Dapr/Index#c-api-client-proxies-integration) to learn more. - -#### Distributed Lock - -ABP provides a [Distributed Locking](https://docs.abp.io/en/abp/7.0/Distributed-Locking) abstraction to control access to a resource that's shared by multiple applications. Dapr also has a [distributed lock building block](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/). - -The [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) package makes ABP use Dapr's distributed locking system. - -> See [the documentation](https://docs.abp.io/en/abp/7.0/Dapr/Index#distributed-lock) to learn more. - -### Integration Services - -Integration services [was an idea](https://github.com/abpframework/abp/issues/12470) to distinguish the application services that are built for inter-module (or inter-microservice) communication from the application services that are intended to be consumed from a user interface or a client application. - -With ABP 7.0, now it is possible to mark an application service as an integration service using the `[IntegrationService]` attribute (that is defined in the `Volo.Abp.Application.Services` namespace). Example: - -````csharp -[IntegrationService] -public class ProductAppService : ApplicationService, IProductAppService -{ - // ... -} -```` - -If your application service has an interface, like `IProductService`, you can use it on the service interface: - -````csharp -[IntegrationService] -public interface IProductAppService : IApplicationService -{ - // ... -} -```` - -When you do that ABP takes the following actions by conventions: - -* If you use the [Auto API Controllers](https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers) feature, the URL prefix will be `/integration-api/` instead of `/api/`. In this way, for example, you can prevent REST API calls to your integration services out of your API Gateway, in a microservice system, and don't authorize these services. You can also filter integration services (or non-integration services) while creating Auto API Controllers, using the `ApplicationServiceTypes` option of the `ConventionalControllerSetting` object. -* Calls made to integration services are not audit logged by default, because they are intended to be used by other services. You can set `IsEnabledForIntegrationServices` to `true` in `AbpAuditingOptions` [options class](https://docs.abp.io/en/abp/latest/Options) to enable audit logging for the integration services too. - -### Dynamic Permissions and Features - -In ABP Framework, [permissions](https://docs.abp.io/en/abp/latest/Authorization) and [features](https://docs.abp.io/en/abp/latest/Features) are defined in the codebase of your application. Because of that design, it was hard to define permissions (and features) in different microservices and centrally manage all the permissions (and features) in a single admin application. To make that possible, we were adding project references for all the microservices' service contract packages from a single microservice, so it can know all the permissions (and features) and manage them. As a result, that permission manager microservice needs to be re-deployed whenever a microservice's permissions change. - -With ABP 7.0, we've introduced the [dynamic permissions](https://github.com/abpframework/abp/pull/13644) and [dynamic features](https://github.com/abpframework/abp/pull/13881) systems. See the following figure: - -![dynamic-permissions](dynamic-permissions.png) - -Here, Microservice 1 defines the permissions A and B, Microservice 2 defines the permissions C, D, E. The Permission Management microservice is used by the permission management UI and manages all the permissions of a user in the application. - -Basically, in the solution with ABP 7.0, all microservices serialize their own permission definitions and write them into a shared database on their application startup (with a highly optimized algorithm). On the other hand, the permission management service can dynamically get these permission definitions from the database (it is also highly optimized to reduce database usage) and allow the UI to show and manage them for a user or role. - -We will update the authorization and features documentation in next days to state the configuration, while it mostly works automatically. - -> If you want to know why we made all these decisions and what problems we've solved, you can watch Halil İbrahim Kalkan's "[Authorization in a Distributed / Microservice System](https://www.youtube.com/watch?v=DVqvRZ0w-7g)" talk in .NET Conf 2022. - -### External Localization Infrastructure - -Localization was another problem in a microservice system, when each microservice try to define its own localization texts and you build a unified UI application. - -The PR [#13845](https://github.com/abpframework/abp/pull/13845) described what's done in details. Basically, you need to implement `IExternalLocalizationStore ` to get localizations of other services. However, since the open-source ABP Framework doesn't provide a module for dynamic localization, we haven't implemented that out of the box. We may implement it for open-source if we get a considerable request from the community (you can upvote [#13953](https://github.com/abpframework/abp/issues/13953)). - -We've implemented the external localization system in ABP Commercial's [Language Management module](https://commercial.abp.io/modules/Volo.LanguageManagement) and also applied it in the [microservice startup template](https://commercial.abp.io/startup-templates/microservice). See the ABP Commercial part of this blog post to know more. - -### Distributed Entity Cache Service - -ABP introduces a distributed entity cache service with v7.0. - -Assume that you have a `Product` entity (an [aggregate root](https://docs.abp.io/en/abp/latest/Entities) actually): - -````csharp -public class Product : AggregateRoot -{ - public string Name { get; set; } - public string Description { get; set; } - public float Price { get; set; } - public int StockCount { get; set; } -} -```` - -And you want to use caching for faster access to the products. You first should configure the [dependency injection](https://docs.abp.io/en/abp/latest/Dependency-Injection) to register the `IEntityCache` service, in the `ConfigureServices` method of your [module class](https://docs.abp.io/en/abp/latest/Module-Development-Basics): - -````csharp -context.Services.AddEntityCache(); -```` - -Now, you can inject the `IEntityCache` service whenever you need: - -````csharp -public class ProductAppService : ApplicationService -{ - private readonly IEntityCache _productCache; - - public ProductAppService(IEntityCache productCache) - { - _productCache = productCache; - } - - public async Task GetAsync(Guid id) - { - var product = await _productCache.GetAsync(id); - return ObjectMapper.Map(product); - } -} -```` - -*In this example, I assume that the [object mapping](https://docs.abp.io/en/abp/latest/Object-To-Object-Mapping) is configured to map from `Product` to `ProductDto`.* - -Here, We've directly cached the `Product` objects. In that case, the `Product` class must be serializable (because it is serialized to JSON when saving in the [distributed cache](https://docs.abp.io/en/abp/latest/Caching)). That may not be possible in some scenarios and you may want to use another class to store the cache data. For example, we may want to use the `ProductDto` class instead of the `Product` class for the cache object. In this case, change the dependency injection configuration as below: - -````csharp -context.Services.AddEntityCache(); -```` - -Then inject the `IEntityCache` service instead of the `IEntityCache` service. - -You can configure the cache duration by passing a `DistributedCacheEntryOptions` object to the `AddEntityCache` method: - -````csharp -context.Services.AddEntityCache( - new DistributedCacheEntryOptions - { - SlidingExpiration = TimeSpan.FromMinutes(30) - } -); -```` - -Default cache duration is 2 minutes with the `AbsoluteExpirationRelativeToNow` configuration. - -> Check [this PR](https://github.com/abpframework/abp/pull/14055) to see the implementation and additional notes. - -### Layout Hooks for Blazor UI - -The **Layout Hook System** allows you to add code to some specific parts of the layout and all layouts of the themes provided by the ABP Framework implement these hooks. - -This system was already implemented for MVC UI but not for Blazor UI. We announced in the previous blog post ([ABP 6.0 Release Candidate blog post](https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published)) that we were planning to implement it in version 7.0. And now, we are introducing the Layout Hook System for Blazor UI as planned within this version. - -> You can read the [Blazor UI: Layout Hooks](https://docs.abp.io/en/abp/7.0/UI/Blazor/Layout-Hooks) documentation if you want to use the Layout Hooks in your Blazor application and see the required configurations. - -### Improvements on eShopOnAbp - -The following improvements have been made on the [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) within this version: - -* We've integrated [Keycloak](https://www.keycloak.org/) (an open-source identity and access management system) as the authentication server instead of the built-in authentication server (that was based on IdentityServer). See [#12021](https://github.com/abpframework/abp/issues/12021) for more information. -* The product detail page now uses CMS Kit's [Rating](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Ratings) and [Comment](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments) features. See [#11429](https://github.com/abpframework/abp/issues/11429) for more info. - -### Other News - -* ABP 7.0 introduces the `AbpDistributedLockOptions` for the main options class to configure the distributed locking. You can specify any name as the lock prefix by configuring the `AbpDistributedLockOptions`. See the [documentation](https://docs.abp.io/en/abp/7.0/Distributed-Locking#abpdistributedlockoptions) for more. - -## What's New with ABP Commercial 7.0? - -We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.0. - -### Microservice Solution Architectural Improvements - -We've worked on the [microservice startup solution](https://commercial.abp.io/startup-templates/microservice) to make it proper for more advanced scenarios and better service independencies. As a result, all the services are made independently deployable and flexible to define its own permissions, features and localization texts. - -For the permissions and features part, we've applied ABP's new dynamic permission and feature systems that are explained above. For the localization texts, we'd implemented ABP's new external localization infrastructure (that was also explained above) in the [Language Management Module](https://commercial.abp.io/modules/Volo.LanguageManagement). - -If you want to build a new microservice solution with ABP 7.0, all these are pre-configured for you. Just create a new solution and focus on your own business code! You can also migrate your existing microservice solutions to take advantage of these new enhancements. You can follow [this guide](https://docs.abp.io/en/abp/latest/Migration-Guides/Upgrading-Startup-Template) as a good way to see the changes you need to apply in your solutions. - -### Set the Tenant Admin's Password from Host - -![](tenant-admin-password.gif) - -ABP Commercial's [SaaS module](https://commercial.abp.io/modules/Volo.Saas) now allows setting the tenant admin's password from the host side. You can set a new password to any tenant admin's password from the Tenants page if you are a host user of the system. - -### WeChat and Alipay Integrations for the Payment Module - -![](payment-gateway-1.png) - -In this version, WeChat Pay and Alipay gateways have been added to the payment module. You can read the [Payment Module documentation](https://docs.abp.io/en/commercial/7.0/modules/payment#alipayoptions) for configurations and more information. - -### Others - -* [CMS Kit (Pro) Module](https://commercial.abp.io/modules/Volo.CmsKit.Pro): Contact Feature allows multiple (named) contact forms with this version. Now, you can add different contact forms on different pages (with different settings). -* [Saas Module](https://commercial.abp.io/modules/Volo.Saas): Allows host users to test the connection string of a tenant database on the UI. -* [Chat Module](https://commercial.abp.io/modules/Volo.Chat): Introduces permission for searching other users. - - -## Community News - -### New ABP Community Posts - -* [gdlcf88](https://github.com/gdlcf88) has created two new community articles: - * [Use Stepping To Perform Atomic Multi-Step Operations](https://community.abp.io/posts/use-stepping-to-perform-atomic-multistep-operations-4kqu8ewp) - * [Notice and Solve ABP Distributed Events Disordering](https://community.abp.io/posts/notice-and-solve-abp-distributed-events-disordering-yi9vq3p4) -* [GDUnit](https://community.abp.io/members/GDUnit) has created his first ABP community article that shows multi-tenant subdomain resolution in Blazor applications. You can read it 👉 [here](https://community.abp.io/posts/abp-blazor-multitenant-subdomain-resolution-c1x4un8x). -* [EngincanV](https://twitter.com/EngincanVeske) has created two new community articles: - * [Testing in ABP Framework (with examples)](https://community.abp.io/posts/testing-in-abp-framework-with-examples-3w29v6ce) - * [What's new with .NET 7?](https://community.abp.io/posts/whats-new-with-.net-7-tlq2g43w) -* [Alper Ebicoglu](https://twitter.com/alperebicoglu) has created a new community article to show "How to upgrade an existing project to .NET7". You can read it 👉 [here](https://community.abp.io/posts/upgrade-your-existing-projects-to-.net7-nmx6vm9m). -* [Kirti Kulkarni](https://community.abp.io/members/kirtik) has created a new community article to show "How to integrate and enable the Chat Module in an ABP Commercial application". You can read it 👉 [here](https://community.abp.io/posts/integrating-and-enabling-the-chat-module-in-abp-commercial-vsci3ov2). -* [maliming](https://github.com/maliming) has created a new community article to show "how to add custom grant type in OpenIddict". You can read it 👉 [here](https://community.abp.io/posts/how-to-add-a-custom-grant-type-in-openiddict.-6v0df94z). - -We thank you all. We thank all the authors for contributing to the [ABP Community platform](https://community.abp.io/). - -### We were in the .NET Conf 2022 - -![](dotnef-conf-2022.jpg) - -Microsoft has released .NET 7.0 and celebrated it with a 3-days international online conference. Halil İbrahim Kalkan, the lead developer of ABP Framework attended [.NET Conf 2022](https://www.dotnetconf.net/) on November 10, 2022. His topic was "Authorization in a Distributed / Microservice System". In this talk, he talked about permission-based authorization systems and their challenges in a distributed system. Then, gave solutions that are implemented in the open source ABP Framework. - -You can watch his speech from 👉 [here](https://www.youtube.com/watch?v=DVqvRZ0w-7g). - -### Community Talks 2022.9: .NET 7.0 & ABP 7.0 - -![](community-talks-cover-image.png) - -In this episode of ABP Community Talks, 2022.9; we'll talk about .NET 7.0 and ABP 7.0 with the ABP Core Team. We will dive into the features that came with .NET 7.0, how they are implemented in ABP 7.0, and the highlights in the .NET Conf 2022 with [Halil İbrahim Kalkan](https://github.com/hikalkan), [Alper Ebicoglu](https://github.com/ebicoglu), [Engincan Veske](https://github.com/EngincanV), [Hamza Albreem](https://github.com/braim23) and [Bige Besikci Yaman](https://github.com/bigebesikci). - -> Register to listen and ask your questions now 👉 https://kommunity.com/volosoft/events/abp-community-20229-net-70-abp-70-f9e8fb72 . - -## Conclusion - -This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.0/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try the ABP v7.0 RC and provide feedback to help us release a more stable version. - -Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/community-talks-cover-image.png b/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/community-talks-cover-image.png deleted file mode 100644 index 30f73d28f8..0000000000 Binary files a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/community-talks-cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/cover-image.png b/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/cover-image.png deleted file mode 100644 index 024f1cf278..0000000000 Binary files a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/dotnef-conf-2022.jpg b/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/dotnef-conf-2022.jpg deleted file mode 100644 index 82179e3362..0000000000 Binary files a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/dotnef-conf-2022.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/dynamic-permissions.png b/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/dynamic-permissions.png deleted file mode 100644 index 49ab9a97c1..0000000000 Binary files a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/dynamic-permissions.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/payment-gateway-1.png b/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/payment-gateway-1.png deleted file mode 100644 index c6c3134934..0000000000 Binary files a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/payment-gateway-1.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/tenant-admin-password.gif b/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/tenant-admin-password.gif deleted file mode 100644 index 479b83ce2b..0000000000 Binary files a/docs/en/Blog-Posts/2022-11-14 v7_0_Preview/tenant-admin-password.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-01-04 v7_0_Release_Stable/POST.md b/docs/en/Blog-Posts/2023-01-04 v7_0_Release_Stable/POST.md deleted file mode 100644 index 85350d5efe..0000000000 --- a/docs/en/Blog-Posts/2023-01-04 v7_0_Release_Stable/POST.md +++ /dev/null @@ -1,75 +0,0 @@ -# ABP.IO Platform 7.0 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.0 versions have been released today. - -## What's New With 7.0? - -Since all the new features are already explained in detail in the [7.0 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7.0-RC-Has-Been-Published), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP.IO-Platform-7.0-RC-Has-Been-Published) for all the features and enhancements. - -## Getting Started with 7.0 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 7.0 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update an existing installation: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -There are breaking changes in this version that may affect your application. Please see the following migration documents, if you are upgrading from v6.x: - -* [ABP Framework 6.x to 7.0 Migration Guide](https://docs.abp.io/en/abp/7.0/Migration-Guides/Abp-7_0) -* [ABP Commercial 6.x to 7.0 Migration Guide](https://docs.abp.io/en/commercial/7.0/migration-guides/v7_0) - -## Community News - -### Highlights from .NET 7.0? - -Our team has closely followed the ASP.NET Core and Entity Framework Core 7.0 releases, read Microsoft's guides and documentation and adapt the changes to our ABP.IO Platform. We are proud to say that we've shipped the ABP 7.0 RC.1 based on .NET 7.0 just after Microsoft's .NET 7.0 release. - -In addition to the ABP's .NET 7.0 upgrade, our team has created 13 great articles to highlight the important features coming with ASP.NET Core 7.0 and Entity Framework Core 7.0. - -You can read [this post](https://volosoft.com/Blog/Highlights-for-ASP.NET-Entity-Framework-Core-NET-7.0) to see the list of all articles. - -### New ABP Community Posts - -In addition to [the 13 articles to highlight .NET 7.0 features written by our team]((https://volosoft.com/Blog/Highlights-for-ASP.NET-Entity-Framework-Core-NET-7.0)), here are some of the recent posts added to the [ABP Community](https://community.abp.io/): - -* [liangshiwei](https://github.com/realLiangshiwei) has created a new community article, that shows [How to Use the Weixin Authentication for MVC / Razor Page Applications](https://community.abp.io/posts/how-to-use-the-weixin-authentication-for-mvc-razor-page-applications-a33e0wti). -* [Jasen Fici](https://community.abp.io/posts/deploying-abp.io-to-an-azure-appservice-ma8kukdp) has created a new article: [Deploying abp.io to an Azure AppService](https://community.abp.io/posts/deploying-abp.io-to-an-azure-appservice-ma8kukdp). - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -## About the Next Version - -The next feature version will be 7.1. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). - -Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/POST.md b/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/POST.md deleted file mode 100644 index 71494f09a6..0000000000 --- a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/POST.md +++ /dev/null @@ -1,207 +0,0 @@ -# ABP.IO Platform 7.1 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.1 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -Try this version and provide feedback for a more stable version of ABP v7.1! Thanks to all of you. - -## Get Started with the 7.1 RC - -Follow the steps below to try version 7.1.0 RC today: - -1) **Upgrade** the ABP CLI to version `7.1.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 7.1.0-rc.1 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 7.1.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. - -You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). - -## Migrating to 7.1 - -This version doesn't introduce any breaking changes. However, Entity Framework developers may need to add a new code-first database migration to their projects since we made some improvements to the existing entities of some application modules. - -## What's New with ABP Framework 7.1? - -In this section, I will introduce some major features released in this version. In addition to these features, so many enhancements have been made in this version too. - -Here is a brief list of the titles explained in the next sections: - -* Blazor WASM option added to Application Single Layer Startup Template -* Introducing the `IHasEntityVersion` interface and `EntitySynchronizer` base class -* Introducing the `DeleteDirectAsync` method for the `IRepository` interface -* Introducing the `IAbpHostEnvironment` interface -* Improvements on the eShopOnAbp project -* Others - -### Blazor WASM option added to Application Single Layer Startup Template - -We've created the [Application (Single Layer) Startup Template](https://docs.abp.io/en/abp/7.1/Startup-Templates/Application-Single-Layer) in v5.2 with three UI types: Angular, Blazor Server and MVC. At the moment, we didn't provide UI option for Blazor, because it required 3 projects at least (server-side, client-side and shared library between these two projects). - -In this version, we've added the Blazor WASM option to the **Application (Single Layer) Startup Template**. It still contains three projects (`blazor`, `host`, and `contracts`) but hosted by a single `host` project. - -You can use the following CLI command to create an `app-nolayers` template with the Blazor UI as the UI option: - -```bash -abp new TodoApp -t app-nolayers -u blazor --version 7.1.0-rc.1 -``` - -> You can check the [Quick Start documentation](https://docs.abp.io/en/abp/7.1/Tutorials/Todo/Single-Layer/Index?UI=Blazor&DB=EF) for a quick start with this template. - -### Introducing the `IHasEntityVersion` interface and `EntitySynchronizer` base class - -Entity synchronization is an important concept, especially in distributed applications and module development. If we have an entity that is related to other modules, we need to align/sync their data once the entity changes and versioning entity changes can also be good, so we can know whether they're synced or not. - -In this version, [@gdlcf88](https://github.com/gdlcf88) made a great contribution to the ABP Framework and introduced the `IHasEntityVersion` interface which adds **auto-versioning** to entity classes and `EntitySynchronizer` base class to **automatically sync an entity's properties from a source entity**. - -You can check the issue and documentation from the following links for more info: - -- [Issue: Entity synchronizers and a new EntityVersion audit property](https://github.com/abpframework/abp/issues/14196) -- [Versioning Entities](https://docs.abp.io/en/abp/7.1/Entities#versioning-entities) -- [Distributed Event Bus - Entity Synchronizer](https://docs.abp.io/en/abp/7.1/Distributed-Event-Bus#entity-synchronizer) - -> Note: The entities of some modules from the ABP Framework have implemented the `IHasEntityVersion` interface. Therefore, if you are upgrading your application from an earlier version, you need to create a new migration and apply it to your database. - -### Introducing the `DeleteDirectAsync` method for the `IRepository` interface - -EF 7 introduced a new [`ExecuteDeleteAsync`](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) method that deletes entities without involving the change tracker into the process. Therefore, it's much faster. - -We've added the `DeleteDirectAsync` method to the `IRepository<>` interface to take the full power of EF 7. It deletes all entities that fit the given predicate. It directly deletes entities from the database, without fetching them. Therefore, some features (like **soft-delete**, **multi-tenancy**, and **audit logging)** won't work, so use this method carefully when you need it. And use the `DeleteAsync` method if you need those features. - -### Introducing the `IAbpHostEnvironment` interface - -Sometimes, while creating an application, we need to get the current hosting environment and take actions according to that. In such cases, we can use some services such as [IWebHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.iwebhostenvironment?view=aspnetcore-7.0) or [IWebAssemblyHostEnvironment](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.iwebassemblyhostenvironment) provided by .NET, in the final application. - -However, we can not use these services in a class library, which is used by the final application. ABP Framework provides the `IAbpHostEnvironment` service, which allows you to get the current environment name whenever you want. `IAbpHostEnvironment` is used by the ABP Framework in several places to perform specific actions by the environment. For example, ABP Framework reduces the cache duration on the **Development** environment for some services. - -**Usage:** - -```csharp -public class MyService -{ - private readonly IAbpHostEnvironment _abpHostEnvironment; - - public MyService(IAbpHostEnvironment abpHostEnvironment) - { - _abpHostEnvironment = abpHostEnvironment; - } - - public void MyMethod() - { - //getting the current environment name - var environmentName = _abpHostEnvironment.EnvironmentName; - - //check for the current environment - if (_abpHostEnvironment.IsDevelopment()) { /* ... */ } - } -} -``` - -You can inject the `IAbpHostEnvironment` into your service and get the current environment by using its `EnvironmentName` property. You can also check the current environment by using its extension methods such as `IsDevelopment()`. - -> Check the [ABP Application Startup](https://docs.abp.io/en/abp/7.1/Application-Startup) documentation for more information. - -### Improvements on the eShopOnAbp project - -K8s and Docker configurations have been made within this version (Dockerfiles and helm-charts have been added and image build scripts have been updated). See [#14083](https://github.com/abpframework/abp/issues/14083) for more information. - -### Others - -* Referral Links have been added to the CMS Kit Comment Feature (optional). You can specify common referral links (such as "nofollow" and "noreferrer") for links in the comments. See [#15458](https://github.com/abpframework/abp/issues/15458) for more information. -* ReCaptcha verification has been added to the CMS Kit Comment Feature (optional). You can enable ReCaptcha support to enable protection against bots. See the [documentation](https://docs.abp.io/en/abp/7.1/Modules/Cms-Kit/Comments) for more information. -* In the development environment, it is a must to reduce cache durations for some points. We typically don't have to invalidate the cache manually or wait on it for a certain time to be invalidated. For that purpose, we have reduced the cache durations for some points on the development environment. See [#14842](https://github.com/abpframework/abp/pull/14842) for more information. - -## What's New with ABP Commercial 7.1? - -We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the new features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.1. - -### Blazor WASM option added to Application Single Layer Pro Startup Template - -The [**Application (Single Layer) Startup Template**](https://docs.abp.io/en/commercial/latest/startup-templates/application-single-layer/index) with Blazor UI is also available for ABP Commercial customers with this version as explained above. - -You can use the following CLI command to create an `app-nolayers-pro` template with Blazor UI as the UI option: - -```bash -abp new TodoApp -t app-nolayers-pro -u blazor --version 7.1.0-rc.1 -``` - -You can also create an `app-nolayers-pro` template with Blazor UI via ABP Suite: - -![](suite-blazor-wasm-nolayers.png) - -### Suite - MAUI Blazor Code Generation - -We provided a new UI option "MAUI Blazor" for the `app-pro` template in the previous version and it's possible to create a `maui-blazor` application with both ABP CLI and ABP Suite. - -You can create an `app-pro` template with the MAUI Blazor as the UI option with the following ABP CLI command: - -```bash -abp new Acme.BookStore -t app-pro -u maui-blazor -``` - -In this version, we implemented the code generation for MAUI Blazor. You can create and generate CRUD pages for this new UI option as you do in other UI types. - -> Note: MAUI Blazor is currently only available with the `app-pro` template. - -### SaaS Module - Allowing entering a username while impersonating the tenant - -In the previous versions, we were able to impersonate a tenant from the [SaaS Module's Tenant Management UI](https://docs.abp.io/en/commercial/7.1/modules/saas#tenant-management). There was a constraint in this approach, which forced us to only impersonate the "admin" user. However, the tenant might change the admin user's username, or we may want to impersonate another user of the tenant. - -Thus, with this version, we decided to allow the impersonation of the tenant by the specified username. - -*You can click on the "Login with this tenant" action button:* - -![](saas-impersonation-1.png) - -*Then, Specify the admin name of the tenant:* - -![](saas-impersonation-2.png) - -## Community News - -### New ABP Community Posts - -* [Sergei Gorlovetsky](https://community.abp.io/members/Sergei.Gorlovetsky) has created two new community articles: - * [Why ABP Framework is one of the best tools for migration from legacy MS Access systems to latest Web app](https://community.abp.io/posts/why-abp-framework-is-one-of-the-best-tools-for-migration-from-legacy-ms-access-systems-to-latest-web-app-7l39eof0) - * [ABP Framework — 5 steps Go No Go Decision Tree](https://community.abp.io/posts/abp-framework-5-steps-go-no-go-decision-tree-2sy6r2st) -* [Onur Pıçakcı](https://github.com/onurpicakci) has created his first ABP community article that explains how to contribute to ABP Framework. You can read it 👉 [here](https://community.abp.io/posts/how-to-contribute-to-abp-framework-46dvzzvj). -* [Maliming](https://github.com/maliming) has created a new community article to show how to convert create/edit modals to a page. You can read it 👉 [here](https://community.abp.io/posts/converting-createedit-modal-to-page-4ps5v60m). - -We thank you all. We thank all the authors for contributing to the [ABP Community platform](https://community.abp.io/). - -### Volosoft Attended NDC London 2023 - -![](ndc-london.png) - -Core team members of the ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) and [Alper Ebicoglu](https://twitter.com/alperebicoglu) attended [NDC London 2023](https://ndclondon.com/) from the 23rd to the 27th of January. - -### Community Talks 2023.1: LeptonX Customization - -![](community-talks-conver-image.png) - -In this episode of ABP Community Talks, 2023.1; we'll talk about **LeptonX Customization**. We will dive into the details and show you how to customize the [LeptonX Theme](https://leptontheme.com/) with examples. - -The event will be live on Thursday, February 16, 2023 (20:00 - 21:00 UTC). - -> Register to listen and ask your questions now 👉 https://kommunity.com/volosoft/events/abp-community-talks-20231-leptonx-customization-03f9fd8c. - -## Conclusion - -This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.1/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try the ABP v7.1 RC and provide feedback to help us release a more stable version. - -Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/community-talks-conver-image.png b/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/community-talks-conver-image.png deleted file mode 100644 index 6d7ccb5b4c..0000000000 Binary files a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/community-talks-conver-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/cover-image.png b/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/cover-image.png deleted file mode 100644 index 879ecbe747..0000000000 Binary files a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/ndc-london.png b/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/ndc-london.png deleted file mode 100644 index cddf3d01b8..0000000000 Binary files a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/ndc-london.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-1.png b/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-1.png deleted file mode 100644 index 30a9d70cea..0000000000 Binary files a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-1.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-2.png b/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-2.png deleted file mode 100644 index 3dfd62fb94..0000000000 Binary files a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/saas-impersonation-2.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/suite-blazor-wasm-nolayers.png b/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/suite-blazor-wasm-nolayers.png deleted file mode 100644 index 7ea566207b..0000000000 Binary files a/docs/en/Blog-Posts/2023-02-08 v7_1_Preview/suite-blazor-wasm-nolayers.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/POST.md b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/POST.md deleted file mode 100644 index 14571157b6..0000000000 --- a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/POST.md +++ /dev/null @@ -1,37 +0,0 @@ -# Migrating from MS-SQL to Postgresql - -![sql-server-to-postgres](images/sql-server-to-postgres.jpg) - -## Introduction - -Database migration is a common practice for organizations that want to move from one database system to another. This can be for a variety of reasons, including cost, performance, and features. In this article, we will discuss the process of migrating a database from MS-SQL to PostgreSQL, the challenges that may arise during the migration, and how to overcome them. And we recently moved the main database of https://abp.io platform from MS-SQL to PostgreSQL. - -In our case, we decided to switch our database from Microsoft SQL Server (MS-SQL) to PostgreSQL because we wanted to move our on-premise platform to Azure. We’ve also found out that the cost of the license for MS-SQL on Azure was significantly higher than PostgreSQL. After conducting a cost-benefit analysis, we decided to migrate our database to PostgreSQL to save costs. - -Before migrating to Azure, we decided to switch our database from MS-SQL to PostgreSQL on-premise first. This gave us the opportunity to test and fine-tune the migration process before making the final switch to Azure. - -## Challenges - -Despite using a third-party tool(DBConvert for MySQL & PostgreSQL) for the migration, we faced three main problems when exporting data to PostgreSQL. Firstly, some tables with plain text had utf-8 encoding problems. We overcame this problem by dump-restoring these tables. - -![db-converter](images/db-converter.jpg) - - -Secondly, our database had to be case-insensitive, but PostgreSQL does not have this as a default configuration. We handled it using `citext` with the ABP migration service. - -![citext-1](images/citext-1.jpg) -![citext-2](images/citext-2.jpg) -![citext-3](images/citext-3.jpg) - - -While everything was proceeding very smooth, we faced one last problem: importing binary data, such as the content of the NuGet packages. It was hard to understand that the binaries of the NuGet packages were different. Our paid commercial NuGet packages are being stored as binary data in the database. Therefore, it was the most compelling part of this migration to transfer the NuGet packages. Fortunately, we overcame the binary error. And we decided to write a custom .NET tool to move only the binary data from MS-SQL to PostgreSQL, thanks to the ABP Core team! - - -## Conclusion - -One of the benefits of using PostgreSQL is the low license costs of the Azure platform. As the main contributors of ABP, we also use ABP Framework under the hood of our abp.io websites, we could easily switch to PostgreSQL. For those who want to switch their ABP project to PostgreSQL, check out [docs.abp.io/en/abp/latest/Entity-Framework-Core-PostgreSQL](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-PostgreSQL). We had not used any MS-SQL specific function, therefore there was no need to make any changes in the repository classes. This means that the applications that were previously using MS-SQL can seamlessly switch to PostgreSQL without any modifications. - -Thanks to the flexibility of ABP, it has [PostgreSQL package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.PostgreSql) which is 100% compatible with PostgreSQL. This helped us to make this migration very smooth and seamless. - -In conclusion, migrating a database from MS-SQL to PostgreSQL can be challenging, but it can bring significant cost savings in the long run. By testing and fine-tuning the migration process before making the final switch, we were able to overcome the challenges we’d faced during the migration process. Thanks to the flexibility of ABP, we were able to make the transition with minimal code changes. Also we didn't see any big performance differences between MS-SQL and PostgreSQL. - diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg deleted file mode 100644 index f0005d378d..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-1.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg deleted file mode 100644 index 439e529ee9..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-2.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg deleted file mode 100644 index 309a7a142f..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/citext-3.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg deleted file mode 100644 index b2d0a78ee4..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql-postges.png b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql-postges.png deleted file mode 100644 index c1766bc713..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql-postges.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql.png b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql.png deleted file mode 100644 index 2333842ea6..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/mssql.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg deleted file mode 100644 index 645de45341..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/sql-server-to-postgres.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/POST.md b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/POST.md deleted file mode 100644 index 82384b3f67..0000000000 --- a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/POST.md +++ /dev/null @@ -1,49 +0,0 @@ - -# On-Prem to Azure: Migration of abp.io Platform to Azure - - -![abpio-azure](images/abpio-azure.png) - -Migrating a Kubernetes platform with a database from our own dedicated servers to Azure can be a compelling task, but it can be a necessary one to take advantage of the benefits that the cloud service offers. In this post, we will discuss the reasons for migrating from a on-premise platform to Azure, the steps taken to create and configure the [abp.io platform](https://abp.io) on Azure, and the benefits gained from the migration. - -### On-Premise Server: The old platform - -There were several reasons for migrating from the old on-premise platform to Azure. First, the Kubernetes cluster and the database were on the same Windows server. Additionally, the Linux virtual machines in Kubernetes were on the Windows server and had limited resources. Furthermore, the Kubernetes maintenance was quite challenging, which was another reason for the migration. - -### Reasons for Moving to Azure: The new platform - -The decision to move to the cloud was made to eliminate the disadvantages of the old platform. The migration to Azure meant that the resources would be independent of each other but faster in terms of communicating with each other. The platform would also take advantage of cloud security and availability. The managed Kubernetes service that Azure offers comes with autoscaling and loadbalancing, which makes the platform more reliable. Finally, the migration to Azure would provide a better quality service to global customers and the community. - -### Before Moving to Azure - -Before migrating to Azure, we decided to switch our database from MS-SQL to PostgreSQL on-premise first. This gave us the opportunity to test and fine-tune the migration process before making the final switch to Azure.You can check the details of database migration from this article [Migrating from MS-SQL to Postgresql](https://community.abp.io/posts/migrating-from-mssql-to-postgresql-lbi5anlv). - -The platform was tested in a staging environment created on Azure with the same resources as the production environment. The staging environment was used to test and optimize the migration process, including the migration of data from the old platform to Azure, which was tested multiple times to ensure success. - -### Creating and Configuring the abp.io Platform on Azure - -Several steps were taken to create and configure the abp.io platform on Azure. [Terraform](https://www.terraform.io/) was used to create the infrastructure (VM, Private Network, AKS, Postgresql Flexible Server...), while [Ansible](https://www.ansible.com/) was used to configure the Azure resources like Terraform, Helm, Kubectl, Docker, VPN, Redis, Prometheus, Grafana, ElasticSearch, Kibana and so on.... Azure DevOps pipelines and release were used for AKS deployment. The most time-consuming part in this process was to prepare, test and optimize the terraform and ansible settings files. - -To access our platform, which is configured on a private network in Azure, we require a VPN connection. To enable this, we have installed [Wireguard](https://www.wireguard.com/) - an open source VPN service - by creating a virtual machine using Terraform and configuring it with Ansible in Azure. This approach has made the process efficient and streamlined. - -![terra-wire](images/terra-wire.png) - -The most important step was to transfer the data in both the database and Kubernetes of the volumes. rsync (remote sync commands) were used to transfer the data from Kubernetes volumes to Azure NFS through the VPN machine. Additionally, `pg_dump` and `pg_restore` were used to transfer the PostgreSQL database through the machine with VPN. - -Before the production environment, the data migration was tested many times for the staging environment. We estimated this migration to take max 1.5 hours. The ABP community was informed that there may be interruptions during the hours designated for the transition to Azure. To inform our customers and community, we created a status page before this migration. The new status page is [status.abp.io](https://status.abp.io). From now on we wil make all the infrastructural announcements on [status.abp.io](https://status.abp.io). We used [Upptime](https://upptime.js.org) which is an open-source uptime monitor and status page provider. During the migration of the production environment, the websites and databases were still up and running. After the data transfer, the only remaining step was to direct the traffic of the already standing abp.io sites to the Azure Kubernetes service via Cloudflare. - -We would like to happily state that **we were offline for only 4 minutes** during this transition. - -![az-infra](images/az-infra.png) - - -### Benefits of Moving to Azure - -The migration to Azure resulted in several benefits. The platform is now more reliable, scalable, secure, solid with built-in one-click backup and recovery capabilities for abp.io. Additionally, the critical resources are in a private network, making them more secure than the old environment. When we initially compared the speed of our abp.io sites before and after migrating to Azure, we were pleasantly surprised by the significant improvement in performance. To be honest, we did not expect such a speed increase. - -![speed](images/speed.png) - -In conclusion, migrating a Kubernetes platform with a database from on-premise to Azure is a complex process that requires careful planning and execution. However, the benefits gained from the migration make the process worthwhile. By moving to Azure, the abp.io platform now has a more reliable and available infrastructure that is more secure than the old environment. The migration also resulted in significant improvements in connection speeds, which ultimately provides a better service to global customers and the community. - - - diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/abpio-azure.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/abpio-azure.png deleted file mode 100644 index 34caf81dd3..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/abpio-azure.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/az-infra.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/az-infra.png deleted file mode 100644 index 7602764591..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/az-infra.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/speed.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/speed.png deleted file mode 100644 index 186615468b..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/speed.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/terra-wire.png b/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/terra-wire.png deleted file mode 100644 index a2a032a1de..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-16-Migration-Abp.io-Azure/images/terra-wire.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/POST.md deleted file mode 100644 index 0cecf0fb72..0000000000 --- a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/POST.md +++ /dev/null @@ -1,82 +0,0 @@ -# ABP.IO Platform 7.1 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.1 versions have been released today. - -## What's New With Version 7.1? - -All the new features were already explained in detail in the [7.1 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7.1-RC-Has-Been-Published), so no need to go over them again. Check it out for more details. - -## Getting Started with 7.1 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 7.1 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade it to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update the existing CLI: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -This version includes a very minor breaking change and it doesn't affect most of the applications. Check [the migration guide](https://docs.abp.io/en/abp/7.1/Migration-Guides/Abp-7_1) for the details. - -## Community News - -## ABP Community Talks 2023.2 - -The next ABP Community Talks will take place on March 30, at 18:00 (UTC). - -![abp-comm-talks-2023-2](abp-comm-talks-2023-2.png) - -In this episode, core ABP Framework developers will discuss the benefits of using the ABP Framework as a .NET developer instead of creating your own solution from scratch. They will answer most of the common doubts about using ABP and application frameworks in general. You will better understand how ABP makes a developer’s life easier and more enjoyable while cutting production costs. We will also have a question - answer session after the talk, as always. I think this talk will be useful for every .NET developer whether they use ABP or not. - -**[CLICK HERE to register for the event and join us](https://kommunity.com/volosoft/events/abp-community-talks-20232-why-use-abp-framework-as-a-net-developer-e3254183)**. - -## Introducing the first ABP .NET Conference! - -As the ABP team, we've executed more than 10 [online events](https://community.abp.io/events) and gained a good experience of software talks. In May, we are organizing a full-featured software conference, named **ABP Dotnet Conference 2023**! - -![abp-conf-2023](abp-conf-2023.png) - -We are still organizing the speakers, talks and schedule. There will be 12 sessions about software development and .NET. These will also include a few ABP-related talks. You can **follow https://abp.io/conference website** and buy early bird tickets from now. - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [Creating Dockerfile for ABP Applications](https://community.abp.io/posts/creating-dockerfile-for-abp-applications-caj4fkxa) by [Anto Subash](https://community.abp.io/members/antosubash) -* [IdentityUser Relationship and Extending it](https://community.abp.io/posts/identityuser-relationship-and-extending-it-xtv79mpx) by [Onur Pıçakçı](https://community.abp.io/members/onurpicakci) -* [Streamline Localization in Your ABP Project](https://community.abp.io/posts/streamline-localization-in-your-abp-project-1t12rmjc) by [Salih Özkara](https://community.abp.io/members/salih) -* [.Net Microservice template with ABP](https://community.abp.io/posts/.net-microservice-template-with-abp-53r52ryy) by [Anto Subash](https://community.abp.io/members/antosubash) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -## About the Next Version - -The next feature version will be 7.2. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-comm-talks-2023-2.png b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-comm-talks-2023-2.png deleted file mode 100644 index 730e48dc0b..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-comm-talks-2023-2.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-conf-2023.png b/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-conf-2023.png deleted file mode 100644 index b562de6dae..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-23 v7_1_Release_Stable/abp-conf-2023.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/POST.md b/docs/en/Blog-Posts/2023-03-24-status.abp.io/POST.md deleted file mode 100644 index 42fd131f3b..0000000000 --- a/docs/en/Blog-Posts/2023-03-24-status.abp.io/POST.md +++ /dev/null @@ -1,114 +0,0 @@ -# Creating a Custom Status Page for abp.io with Upptime - -## Introduction -In today's digital world, providing reliable and transparent information about your platform's availability is essential to maintaining trust among your community and customers. With the growing number of abp.io users, we needed a dedicated status page [status.abp.io](https://status.abp.io/) to keep everyone informed about our platform's health. To achieve this, we utilized the open-source project [Upptime](https://upptime.js.org/) and built a custom status page on [GitHub Pages](https://pages.github.com/). In this article, we'll guide you through the process of creating our own status page and customizing it to suit our needs. - -![status-abpio](./images/status-abpio.png) - -## Why we chose Upptime -[Upptime](https://github.com/upptime/upptime) is an open-source, easy-to-use, and cost-effective solution for monitoring websites and APIs. It offers essential features, such as downtime alerts, response time monitoring, and status history. We decided to use Upptime because of its compatibility with GitHub Pages, ease of customization, comprehensive [documentation ](https://upptime.js.org/docs/) and discord notifications. - -![gh-status](./images/gh-status.png) - -#### Advantages of Upptime -* Open-source: Allows easy customization and community support. - -* GitHub Pages compatibility: Seamless integration with GitHub Pages for hosting. - -* Cost-effective: Utilizes GitHub Actions, which provides free monitoring within the GitHub Actions usage limits. - -* Comprehensive documentation: Easy-to-follow instructions for setting up and customizing the status page. - -#### Disadvantages of Upptime -* Limited monitoring capabilities: Upptime offers basic monitoring features but lacks advanced capabilities found in dedicated monitoring tools. - -* Dependence on GitHub Actions: Upptime relies on GitHub Actions, which may pose limitations for users unfamiliar with GitHub's ecosystem or those with large-scale projects. - -* No built-in alerting system: Users must rely on third-party integrations or custom solutions for notifications, requiring additional configuration. - -* Limited customization options: Upptime allows for some customization, but options are limited compared to comprehensive monitoring platforms. - -* Self-hosted limitations: As a self-hosted solution, users are responsible for maintaining and managing their own infrastructure, which may not be ideal for those who prefer a fully managed monitoring solution. - - -## How to set up the status page on GitHub Pages -To get started with our custom status page, we followed the instructions in the [Upptime documentation](https://upptime.js.org/docs/). Here's a summary of the steps we took: - -* Fork the Upptime [template repository](https://github.com/upptime/upptime) to our own GitHub account as [abpio-status](https://github.com/abpframework/abpio-status). - -* Configure the GitHub Actions workflow. We configured the GitHub Actions workflow by adding the following lines to the [`.github/workflows/uptime.yml`](https://github.com/abpframework/abpio-status/blob/master/.github/workflows/uptime.yml) - -* Add the monitored endpoints. We added the monitored endpoints (our abp.io websites) to the [.upptimerc.yml](https://github.com/abpframework/abpio-status/blob/master/.upptimerc.yml) file. This file is located in the root of the repository and contains a list of URLs that Upptime monitors. - -```yaml -sites: - - name: abp.io - url: https://abp.io/health-status - - name: community.abp.io - url: https://community.abp.io/health-status - - name: commercial.abp.io - url: https://commercial.abp.io/health-status - - name: nuget.abp.io - url: https://nuget.abp.io/health-status - - name: docs.abp.io - url: https://docs.abp.io/health-status - - name: support.abp.io - url: https://support.abp.io/health-status - - name: blog.abp.io - url: https://blog.abp.io/health-status - - name: commercial-demo.abp.io - url: https://commercial-demo.abp.io/health-status -``` -* Enable GitHub Pages. Finally, we enabled GitHub Pages for our forked repository by going to the repository's settings and selecting the gh-pages branch as the source. This made our status page accessible at [status.abp.io](https://status.abp.io/). - -## Customizing the status page - -After setting up the default Upptime status page, we focused on customizing it to align with our brand and provide a consistent experience for our community and customers. We made the following changes: - -* Updating the logo and favicon. We replaced the default logo and favicon with our own abp.io branded assets. This involved adding the new image files to the repository and updating the references in the `.upptimerc.yml` file: - -* Customizing the color scheme and typography. We customized the color scheme and typography to match our corporate identity by editing the `.upptimerc.yml` file: - -```yaml -status-website: - theme: dark - # Add your custom domain name, or remove the `cname` line if you don't have a domain - # Uncomment the `baseUrl` line if you don't have a custom domain and add your repo name there - cname: status.abp.io - # baseUrl: /abpio-status - logoUrl: https://commercial.abp.io/assets/svg/abp-logo-light.svg - favicon: https://raw.githubusercontent.com/abpframework/abpio-status/master/assets/abp-logo-without-text.svg - faviconSvg: https://raw.githubusercontent.com/abpframework/abpio-status/master/assets/abp-logo-without-text.svg -``` -## Creating GitHub Issues for Maintenance Information on status.abp.io - -To provide maintenance information for your status page, you can create GitHub issues in your repository. This allows you to inform your users about planned downtime or ongoing maintenance work. - -![issue](./images/issue.png) - -## Discord Notifications - -### Create a Discord Webhook - -To set up Discord notifications for your status.abp.io status page using [Upptime documentation](https://upptime.js.org/docs/notifications#discord), follow these steps: - -* In Discord, go to "Server Settings" > "Integrations" > "Create Webhook." -* Customize the Webhook name, choose a channel, and copy the Webhook URL. - -### Configure GitHub Actions -* In your Upptime repository, go to the "Settings" tab. -* Click on "Secrets" and then "New repository secret." -* Add secret: Name it DISCORD_WEBHOOK_URL and paste the Webhook URL as the value. -* Add environment variables NOTIFICATION_DISCORD_WEBHOOK and NOTIFICATION_DISCORD set it to true. - -Your status page will now send notifications to your Discord channel whenever there's a change in your platform's status. - -![discord](./images/discord.png) - - -## Conclusion -If your primary goal is to create a simple, cost-effective status page with basic monitoring features, Upptime is an excellent choice. Its open-source nature, seamless integration with GitHub Pages, and comprehensive documentation make it a user-friendly option. - -If you require advanced monitoring capabilities or prefer a fully managed monitoring solution, you may want to explore dedicated monitoring tools, such as Pingdom, Uptime Robot, or Datadog. These tools typically offer more robust monitoring features, built-in alerting systems, and customizable dashboards. - -Creating a custom status page for abp.io using Upptime and GitHub Pages proved to be an efficient and cost-effective solution. By following the documentation and customizing the template, we were able to provide our community and customers with a reliable source of information about our platform's availability. With this new status page [status.abp.io](https://status.abp.io/), we can continue to build trust and transparency as our platform grows and evolves. diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/discord.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/discord.png deleted file mode 100644 index 0e773729e4..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/discord.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/gh-status.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/gh-status.png deleted file mode 100644 index 0490eeb6f6..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/gh-status.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/issue.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/issue.png deleted file mode 100644 index 7f21404e8f..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/issue.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/status-abpio.png b/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/status-abpio.png deleted file mode 100644 index 359df07a92..0000000000 Binary files a/docs/en/Blog-Posts/2023-03-24-status.abp.io/images/status-abpio.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/abp-framework.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/abp-framework.png deleted file mode 100644 index b1157793bf..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/abp-framework.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/abp-suite.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/abp-suite.png deleted file mode 100644 index e4ad6d6014..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/abp-suite.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/architecture-layers.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/architecture-layers.png deleted file mode 100644 index 081376668b..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/architecture-layers.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/conclusion.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/conclusion.png deleted file mode 100644 index cecdd739b4..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/conclusion.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/cover.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/cover.png deleted file mode 100644 index a926bcfca6..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/cover.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/csharp-microservice-framework.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/csharp-microservice-framework.png deleted file mode 100644 index 2550a9209d..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/csharp-microservice-framework.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/ddd.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/ddd.png deleted file mode 100644 index 45f6261a57..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/ddd.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/essential-features.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/essential-features.png deleted file mode 100644 index e709f00327..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/essential-features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/essentials.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/essentials.png deleted file mode 100644 index dee5928bbd..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/essentials.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/modular.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/modular.png deleted file mode 100644 index 96943ef81f..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/modular.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/pre-built-modules.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/pre-built-modules.png deleted file mode 100644 index 0d5f5a7e9a..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/pre-built-modules.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/the-abp-platform.png b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/the-abp-platform.png deleted file mode 100644 index bae6f08238..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/images/the-abp-platform.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/post.md b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/post.md deleted file mode 100644 index b844fb5540..0000000000 --- a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/post.md +++ /dev/null @@ -1,118 +0,0 @@ -# ABP Framework: An Open Source Web Application Development Framework - - - -## What is ABP Framework? - -![ABP Framework](images/abp-framework.png) - -ABP Framework is an open-source web application development framework that provides developers with a set of tools to build modern, scalable, and maintainable web applications. ABP Framework is also a C# web framework that is based on the ASP.NET web framework. It is one of the [most popular repository](https://github.com/abpframework/abp) for open source application framework. - -ABP Framework is a modular and extensible framework that uses the clean architecture principles and is built on top of the latest .NET technologies. The framework comes with a set of pre-built modules, including user management, role management, permission management, content management system (CMS) modules, which makes it easier for developers to create line of business applications. - - - -## Clean Architecture - -When you want to start a new scratch project, you first google for Dotnet Framework Architecture. There are some boilerplate dotnet startup templates but these are only an orchestration of some popular tools. ABP is not a template but it's a full stack open source application development framework. - -When you say "Clean Architecture ASP.NET Core", the first web development framework that comes to mind is undoubtedly the ABP Framework. It is built using clean architecture principles, which help developers build scalable and maintainable applications. Clean architecture separates the application into distinct layers, each with a clear responsibility. The dotnet architecture layers include the presentation layer, application layer, domain layer, and infrastructure layer. Each layer has a clear responsibility, which helps in separating concerns and keeping the code organized. This makes ABP Framework one of the best asp net frameworks. - -![Clean architecture - ABP Layers](images/architecture-layers.png) - -## C# Web Framework for Web Development - -ABP Framework is built using C#, which is a modern programming language that is widely used in the development of web applications. C# provides developers with a set of features that make it easy to write clean and maintainable code. ABP Framework is a web framework that is designed to work with C# and provides developers with a set of tools that makes it easy to build modern web applications. If you are looking for an ASP NET Core shared framework download, then go to https://abp.io/get-started and create your project. - -![ABP Framework Essentials](images/essentials.png) - -## Yet another ASP.NET Web Framework - -There are a few full stack AspNet Core frameworks around. Many of them are one developer projects which can be risky for you to start a long running project. ABP Framework is built on top of the latest ASP NET Core Framework, which provides developers with a set of features that makes it easy to build modern web applications. And most important part is, ABP is backed with a large group of developers and it has almost 10K stars on GitHub. ASP.NET provides developers with a set of tools that makes it easy to build web applications using a model-view-controller (MVC) architecture. - -![ABP.io Platform](images/the-abp-platform.png) - - - -## Implementing Domain Driven Design with C# - -ABP Framework provides developers with a set of tools that make it easy to implement domain-driven design principles. The framework comes with a set of pre-built modules, including user management, role management, permission management, and content management system (CMS) modules, which makes it easier for developers to create complex applications. - -![Implementing Domain Driven Design with C#](images/ddd.png) - - - -## Open Source Web Application - -ABP Framework is an open-source web application development framework that is free to use and distribute. The framework is licensed under the MIT license, which means that developers can use it for commercial and non-commercial purposes without any restrictions. - - - -## .NET Application Framework with Pre-Built Modules - -ABP Framework is built using the latest .NET technologies and provides developers with a set of tools that makes it easy to build modern web applications. ABP contains several important modules of a line of business applications. - -![ABP Pre-built Modules](images/pre-built-modules.png) - - - -## C# Microservice Framework - -ABP Framework is a C# microservices framework that is designed to help developers build scalable and maintainable microservices. The framework is also known as .NET .net microservices framework. It provides developers with a set of tools that makes it easy to build microservices using clean architecture principles. - -![C# Microservice Framework](images/csharp-microservice-framework.png) - - - -## CRUD Tool Dotnet - -ABP Framework has a commercial version as well. The paid version comes with a premium support, rich themes and there's a very handy tool called [ABP Suite](https://commercial.abp.io/tools/suite) for "CRUD page generation ASP.NET". While ABP is not a low-code or no-code platform, ABP Suite provides ASP NET rapid application development. There are many ASP.NET rapid development tools but these are only tools without a framework support. If you are looking for web based rapid application development tools (also open-source), ABP is the way to go! - -![ABP Suite](images/abp-suite.png) - -## Modular Development - -One of the best sides of the ABP Framework is the modular development side. It's born as a modular system. There are several open source web frameworks around but many of them lack of modularity. - -![Modular Development](images/modular.png) - -Let's see the key features of the ABP Framework: - -- Multi-tenancy support -- Cross-cutting concerns implemented -- Full-stack microservice solution -- SaaS framework -- ASP.NET modular monolith -- Has an ASP.NET user management module -- Distributed events -- Layered architecture -- Free framework for website -- Several framework templates - - - -## The Essential Features of ABP - -ABP Framework is an open source SaaS framework. This crucial feature distinguishes it from other open source web development frameworks. - -![Essential Features](images/essential-features.png) - -The following essential ASP.NET features are available in the ABP Framework: - -ASP.NET modularity, ASP.NET modular development, ASP.NET localization, ASP.NET multi-tenancy, ASP.NET SaaS, ASP.NET SaaS framework, ASP.NET distributed events, ASP.NET distributed event bus, ASP.NET cross-cutting concerns, ASP.NET blob storing, ASP.NET audit logging, ASP.NET microservice, ASP.NET microservice solution, ASP.NET microservice example, ASP.NET API gateway, ASP.NET domain driven design, ASP.NET layered architecture, ASP.NET layering, ASP.NET clean architecture, ASP.NET authentication, ASP.NET authorization, ASP.NET identity, ASP.NET identity server, ASP.NET IdentityServer, ASP.NET payment module, ASP.NET best practices, ASP.NET design patterns, ASP.NET background jobs, ASP.NET exception handling, ASP.NET background workers, ASP.NET repository, ASP.NET repository pattern, ASP.NET unit of work, ASP.NET domain services, ASP.NET swagger, ASP.NET content management system, ASP.NET user management, ASP.NET role management, ASP.NET permission management - - - -## Microservice Example: eShopOnAbp - -[eShopOnAbp](https://www.eshoponabp.com/) is a microservice example built on top ABP. It is a sample net application, similar to the Microsoft's [eShopOnContainer](https://github.com/dotnet-architecture/eShopOnContainers) project. It is a reference microservice solution built with the ABP Framework and .NET, runs on Kubernetes with Helm configuration, includes API Gateways, Angular and ASP.NET Core MVC applications with PostgreSQL and MongoDB databases. For more information, check out https://github.com/abpframework/eShopOnAbp. - - - -## Conclusion - -In conclusion, ABP Framework is an open-source web development framework that offers many features and benefits for building modern and scalable web applications. Its modular and extensible architecture, implementation of Domain-Driven Design, and compatibility with various platforms make it a popular choice for developers. Whether you're building a web application, microservices, or modular monoliths, ABP Framework has everything you need to get started. - -![conclusion](images/conclusion.png) - -Whether you're building a dot net website, a web app infrastructure, or a webpage framework, open source web application frameworks are a cost-effective and flexible option for web development. If you are looking for an open source web application builder which contains project framework template with web application development tools (open source), [ABP Framework](https://abp.io/) is the right choice. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/abp-essential-features.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/abp-essential-features.png deleted file mode 100644 index 287d130cb8..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/abp-essential-features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/abp-try-now.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/abp-try-now.png deleted file mode 100644 index c3a5ffdaec..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/abp-try-now.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/cover.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/cover.png deleted file mode 100644 index fb6ee89af3..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/cover.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/cross-platform.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/cross-platform.png deleted file mode 100644 index 5889357a42..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/cross-platform.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/ddd-book.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/ddd-book.png deleted file mode 100644 index b18c3b716a..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/ddd-book.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/developer-focused.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/developer-focused.png deleted file mode 100644 index 0bd8d212b8..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/developer-focused.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/features.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/features.png deleted file mode 100644 index 2f68e23137..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/key-features.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/key-features.png deleted file mode 100644 index 4c4b714694..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/key-features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/module-layers-and-packages.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/module-layers-and-packages.png deleted file mode 100644 index 50d8d09f9b..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/module-layers-and-packages.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/open-source.png b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/open-source.png deleted file mode 100644 index 6c166ac7eb..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/images/open-source.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/post.md b/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/post.md deleted file mode 100644 index 8c6bef4b8a..0000000000 --- a/docs/en/Blog-Posts/2023-04-07_Ultimate_NET_Framework_ABP/post.md +++ /dev/null @@ -1,85 +0,0 @@ -Building robust web applications has become more important than ever as the world is becoming more web-focused. With the rise of new tools and frameworks, choosing the right web development framework can be challenging. Especially if you are a .NET developer, there not so many popular ASPNET Framework around. However, the ABP Framework has become popular for many developers due to its flexibility, scalability, feature set and performance. Let's mention what's ABP Framework and what it promises to .NET developers. - -## ASP.NET Core Architecture Best Practices - -![ABP Key Features](images/key-features.png) - -ABP Framework is an application design framework that provides developers with a powerful set of tools to build web applications quickly and efficiently. It is an open-source, cross-platform framework supporting monolithic and microservices architectures. ABP Framework is built on top of the ASP.NET Core architecture and incorporates best practices for developing web applications. - -## ASP.NET Platform - -The framework includes a wide range of features, such as an angular code generator with the help of [ABP Suite](https://commercial.abp.io/tools/suite), project templates, and web application themes. These features enable developers to create web applications that are both functional and visually appealing without spending much time on coding. Moreover, ABP Framework provides a common application framework that can be used for different applications, including SAAS, e-commerce, and social media platforms. - -ABP Framework also supports the domain-driven design, which means that the framework is designed to be flexible and adaptable to different business requirements. This approach allows developers to build applications aligned with business needs, ensuring that they are efficient and effective. - -## Open Source Dot Net Framework - -One of the major advantages of ABP Framework is its open-source nature. Many developers continuously improve and update the framework, making it more reliable and secure. Moreover, the ABP Framework is compatible with multiple .NET frameworks, including ASP.NET and .NET Core. When starting your project on top of a solid Microsoft web framework, the ABP Framework is one of the best choices. - -![ABP is cross platform](images\cross-platform.png) - -Another advantage of ABP Framework is that it provides rapid web application development tools that are easy to use. The framework includes project templates that developers can use as a starting point for their web applications, which can significantly reduce the development time. ABP Framework also provides a web app builder that developers can use to create web applications quickly and efficiently. - -## ASPNET Core Architecture - -ABP Framework is also compatible with Microsoft's Clean Architecture, a software design pattern that promotes separation of concerns and maintainability. This integration ensures that the applications developed using ABP Framework are well-structured, easy to maintain, and scalable. Many application marketplace reviewers commented about ABP as the best web application framework. While ABP supports multiple UI choices like MVC, Angular, Blazor Web Assembly and Blazor Server, the most downloaded one is MVC microservice architecture. If you are here to find a NET application framework for your next NET Core application, you are in the right place! - -![ABP Framework Project Hierarchy](images/module-layers-and-packages.png) - -Clean Architecture is a design pattern that emphasizes the separation of concerns, ensuring that the code is organized in independent layers. This helps developers to write code that is easy to maintain, test and refactor. ABP Framework uses this pattern to structure its application code and ensure that it is easy to manage. - -## Open Source Web Framework - -ABP Framework is an open-source web framework that is free to use and distribute. The framework is licensed under the MIT license, meaning developers can use it for commercial and non-commercial purposes without any restrictions. - -ABP Framework provides different templates, which are ASPNET Core web app compatible with various platforms, including Windows, Linux, and macOS. - -## Domain Driven Design DotNet - -The ABP Framework also implements Domain-Driven Design (DDD), a software design methodology that emphasizes the importance of the domain model. The framework provides a guide for implementing DDD through an implementing domain-driven design. It helps developers to create a domain model that is easy to understand, test, and maintain. As ABP is a C# framework, it supports most of the common application framework features for a core framework. - -![ABP Framework - Domain Driven Design e-book](images/ddd-book.png) - -There is also a free PDF e-book for Dotnet developers that explains the Domain Driven Design principle with real-world examples. You can download this e-book at https://abp.io/books/implementing-domain-driven-design - -## It's a Dotnet Web Framework - -ABP Framework is a dotnet web framework that is designed with C# and provides developers with a set of tools that makes it easy to build modern web applications. Whether you want to start a new dotnet monolithic solution or dotnet microservice solution, you can start with ABP. Creating your own dotnet framework architecture may be hard if you don't have many years of experience. The brain team of the ABP Framework specializes in ASP.NET framework architecture and ASP.NET application frameworks. - -![Developer Focused](images/developer-focused.png) - -## Essential Features of the ABP Framework: - -ASP.NET Core modularity, ASP.NET Core modular development, ASP.NET Core localization, ASP.NET Core SaaS framework, ASP.NET Core distributed, event, bus, ASP.NET Core cross-cutting concerns, ASP.NET Core blob storing, ASP.NET Core audit logging, ASP.NET Core microservice, ASP.NET Core microservice solution, ASP.NET Core microservice example, ASP.NET Core API gateway, ASP.NET Core domain, driven, design, ASP.NET Core layered architecture, ASP.NET Core layering, ASP.NET Core clean architecture, ASP.NET Core authentication, ASP.NET Core authorization, ASP.NET Core UI theme, ASP.NET Core tag helpers, ASP.NET Core identity, ASP.NET Core, identity, server, ASP.NET Core IdentityServer, ASP.NET Core payment module, ASP.NET Core best practices, ASP.NET Core design patterns, ASP.NET Core background jobs, ASP.NET Core exception handling, ASP.NET Core, background, workers, ASP.NET Core repository, ASP.NET Core repository pattern, ASP.NET Core unit of work, ASP.NET Core domain services, ASP.NET Core Swagger, ASP.NET Core content management system, ASP.NET Core CMS module, ASP.NET Core user management, ASP.NET Core Role management, ASP.NET Core permission management - -![ABP Essential Features](images/abp-essential-features.png) - -## Open Source Web Application Framework - -ABP Framework is an open-source web application development framework that is free to use and distribute. The framework is licensed under the MIT license, meaning developers can use it for commercial and non-commercial purposes without any restrictions. - -![ABP Framework is open-source](images\open-source.png) - -## C# Web Application Framework - -ABP Framework is built using C#, which is a modern programming language that is widely used in the development of web applications. C# provides developers with features that make it easy to write clean and maintainable code. ABP Framework is a web framework designed to work with C# and provides developers with tools that make it easy to build modern web applications. - -## Key Features - -The following .NET features are available in the ABP Framework: - -.NET modular development, .NET localization, .NET multi-tenancy, .NET SaaS framework, .NET distributed event bus, .NET cross-cutting concerns, .NET,microservice, .NET microservice solution, .NET microservice example, .NET domain driven design, .NET clean architecture, .NET authentication, .NET authorization, .NET best practices, .NET design,patterns, .NET exception handling, .NET background workers, .NET unit of work, .NET domain services, .NET user management, .NET role management, .NET permission management - -![ABP Framework Features](images/features.png) - -The following keywords best describe the ABP Framework; - -Open source backend framework, open source development framework, open source web app builder, open source web applications, open source web development, web application development framework, web application framework, web application framework software, web application infrastructure, web application open source, asp net framework, asp net open source, ASP.NET application, ASP.NET software, ASP.NET web app, ASP.NET web development, web app builder open source, web app framework, Dotnet framework, Dotnet UI framework, Dotnet web application themes. - - - -## Conclusion - -ABP Framework is a powerful and flexible web application framework that provides developers with the tools to build high-quality web applications quickly and efficiently. It is an open-source, cross-platform framework that supports multiple .NET frameworks, including ASP.NET and .NET Core. ABP Framework provides rapid web application development tools, project templates, and web application themes that enable developers to create visually appealing and functional applications in no time. - -![Try ABP now](images\abp-try-now.png) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/Top 10 .NET Core Libraries Every Developer Should Know.md b/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/Top 10 .NET Core Libraries Every Developer Should Know.md deleted file mode 100644 index be037f30c5..0000000000 --- a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/Top 10 .NET Core Libraries Every Developer Should Know.md +++ /dev/null @@ -1,79 +0,0 @@ -# Top 10 .NET Core Libraries Every Developer Should Know - -> *Brief Summary*: -> -> This article is intended for .NET Core developers who wish to create a robust and useful.NET core application. It is a list of the most popular and widely used .NET Core libraries that have been carefully vetted. Go up the GitHub reference link for the DotNet libraries and see how many stars the .NET community has rewarded. - - - -## Best .NET libraries — Top useful libraries every .NET developers use - -.NET Core has become one of the most popular frameworks for developing modern applications. One of the reasons for its popularity is the wide range of libraries available to developers. .NET Core got new updates in its features with lesser coding, deploying high-accomplishment, and extremely scalable applications. Making the underlying architecture functions more effective and efficient without having to reinvent the wheel will free up your time to focus on more crucial tasks, including making your application stand out from the competition. - -In this article, we'll take a closer look at **The Most Popular .NET Libraries Every Developer Should Know**. As a software developer, you're likely familiar with the .NET framework and the many libraries it offers. With so many options available, it can be overwhelming to know which ones to choose for your project. This is the main reason I have compiled a list of the **Top 10 .NET Libraries That Developers Should Use** to make their development process more efficient and effective. A list of Top 10 .NET Core Libraries will let developers understand these so that they can pick appropriate libraries for their projects. - -If you're a .NET Core developer, there are **10 important .NET Core libraries** that you should be familiar with. While creating these **Essential 10 .NET Libraries Every Developer Must Know**, I used NuGet and GitHub.com popular repositories. And all the libraries listed here are also open-source. The list is filtered with only to .NET Core related libraries. Also I excluded the Microsoft .NET Core Framework libraries from this **Top 10 Unique .NET Core Libraries Developers Must Utilize**. So, without further ado, let’s get right into it: - ------- - - - -## Top 10 best libraries for .NET developers - -1. **Newtonsoft.Json:** This library is widely used for working with JSON data in .NET applications. It provides high performance and ease of use, making it a go-to solution for serialization and deserialization of JSON data. -2. **Dapper:** It is a simple and efficient ORM that offers high performance and flexibility when working with relational databases. It is easy to use and offers a fast and efficient way to interact with databases. -3. **Polly:** Polly is a library that helps handle transient errors and faults in .NET applications. It offers an easy-to-use policy-based approach to handling retries, timeouts, and circuit breakers, making it a valuable tool for building reliable applications. -4. **AutoMapper**: This .NET Core library simplifies object-to-object mapping by automatically mapping properties from one object to another. This library is especially useful in larger projects where mapping can become time-consuming and tedious. -5. **FluentValidation:** It is a library that provides a fluent API for building validation rules. It makes it easy to create complex validation logic and supports a wide range of validation scenarios, making it a valuable tool for ensuring data integrity in your applications. -6. **Serilog**: This library is a structured logging library that makes it easy to collect and analyze logs from your application. It offers flexibility and extensibility, and supports a variety of sinks for storing logs, including Elasticsearch, SQL Server, and more. -7. **Swashbuckle.AspNetCore.Swagger:** This library generates OpenAPI documentation for your ASP.NET Core Web API. It makes it easy to understand the functionality of your API and allows you to easily generate client code for your API. -8. **NLog**: It is is a free logging platform for .NET with rich log routing and management capabilities. It makes it easy to produce and manage high-quality logs for your application regardless of its size or complexity. -9. **Moq4**: It is is a popular mocking framework for .NET applications. It makes it easy to create mock objects for unit testing, reducing the need for expensive and time-consuming integration testing. -10. **StackExchange.Redis**: This is a library for working with Redis databases in .NET applications. It provides a simple and efficient way to interact with Redis, and offers high performance and scalability. - ------- - -![10 important .NET Core Libraries](essential-10-net-libraries-every-developer-must-know.png) - - - -## Top 10 .NET Core Libraries List That Every Developer Must Know - -Here you can see them in table with the GitHub stars, GitHub release counts, recent release frequency, NuGet download counts and per day NuGet download counts: - -| GitHub URL | NuGet URL | Stars | Releases | Last release | Downloads | Download Per Day | -| ------------------------------------------------------------ | ------------------------------------------------------------ | ----- | -------- | ------------ | --------- | ---------------- | -| [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) | [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json) | 10K | 65 | 1 month ago | 3B | 680K | -| [Dapper](https://github.com/DapperLib/Dapper) | [Dapper](https://www.nuget.org/packages/Dapper) | 16K | 70 | 2 years ago | 216M | 50K | -| [Polly](https://github.com/App-vNext/Polly) | [Polly](https://www.nuget.org/packages/polly) | 12K | 26 | 1 year ago | 335M | 92K | -| [AutoMapper](https://github.com/AutoMapper/AutoMapper) | [AutoMapper](https://www.nuget.org/packages/AutoMapper) | 9K | 41 | 6 months ago | 400M | 90K | -| [FluentValidation](https://github.com/FluentValidation/FluentValidation) | [FluentValidation](https://www.nuget.org/packages/FluentValidation) | 8K | 68 | 3 days ago | 250M | 56K | -| [Serilog](https://github.com/serilog/serilog) | [Serilog](https://www.nuget.org/packages/Serilog) | 6K | 15 | 1 month ago | 722M | 197K | -| [Swashbuckle.AspNetCore.Swagger](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) | [Swashbuckle.AspNetCore.Swagger](https://www.nuget.org/packages/Swashbuckle.AspNetCore.Swagger) | 5K | 28 | 4 months ago | 386M | 168K | -| [NLog](https://github.com/NLog/NLog) | [NLog](https://www.nuget.org/packages/Nlog) | 6K | 125 | 1 week ago | 217M | 48K | -| [Moq](https://github.com/moq/moq4) | [Moq](https://www.nuget.org/packages/Moq) | 5K | 33 | 4 months ago | 418M | 93K | -| [StackExchange.Redis](https://github.com/StackExchange/StackExchange.Redis) | [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis) | 5K | 34 | 11 days ago | 244M | 74K | - - - -In conclusion, these 10 .NET Core libraries are essential tools for any .NET Core developer. They offer a wide range of functionality, from handling errors to mocking for unit testing and simplifying object mapping. Whether you're working on a large-scale enterprise application or a small project, these libraries can help you build more reliable, efficient, and effective applications. - - ---- - - -### What is ABP Framework? -ABP Framework offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. It provides the fundamental infrastructure, production-ready startup templates, modules, themes, tooling, guides and documentation to implement that architecture properly and automate the details and repetitive work as much as possible. - -If you are starting a new ASP.NET Core project, try [abp.io](https://abp.io) now... - - **IT IS FREE AND OPEN-SOURCE!** - ---- - -> Alper Ebicoglu 🧑🏽‍💻 ABP Framework Core Team Member\ -> Follow me for the latest news about .NET and software development:\ -> 📌 [twitter.com/alperebicoglu](https://twitter.com/alperebicoglu)\ -> 📌 [github.com/ebicoglu](https://github.com/ebicoglu)\ -> 📌 [linkedin.com/in/ebicoglu](https://www.linkedin.com/in/ebicoglu)\ -> 📌 [medium.com/@alperonline](https://medium.com/@alperonline) diff --git a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/cover1.png b/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/cover1.png deleted file mode 100644 index 2edb48ce42..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/cover1.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/cover2.png b/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/cover2.png deleted file mode 100644 index 2e2a201748..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/cover2.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/essential-10-net-libraries-every-developer-must-know.png b/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/essential-10-net-libraries-every-developer-must-know.png deleted file mode 100644 index e986bb5719..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-11-Top-10-NET-Core-Libraries-Every-Developer-Should-Know/essential-10-net-libraries-every-developer-must-know.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/POST.md b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/POST.md deleted file mode 100644 index d71628f3b8..0000000000 --- a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/POST.md +++ /dev/null @@ -1,211 +0,0 @@ -# ABP.IO Platform 7.2 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.2 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -Try this version and provide feedback for a more stable version of ABP v7.2! Thanks to all of you. - -## Get Started with the 7.2 RC - -Follow the steps below to try version 7.2.0 RC today: - -1) **Upgrade** the ABP CLI to version `7.2.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 7.2.0-rc.1 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 7.2.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. - -You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). - -## Migration Guides - -There are breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v7.1: - -* [ABP Framework 7.1 to 7.2 Migration Guide](https://docs.abp.io/en/abp/7.2/Migration-Guides/Abp-7_2) -* [ABP Commercial 7.1 to 7.2 Migration Guide](https://docs.abp.io/en/commercial/7.2/migration-guides/v7_2) - - -## What's New with ABP Framework 7.2? - -In this section, I will introduce some major features released in this version. Here is a brief list of the titles that will be explained in the next sections: - -* Grouping of Navigation Menu Items -* Introducing the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService` -* CMS Kit Comments: Don't Allow External URLs -* Angular UI New Components -* Others - -### Grouping of Navigation Menu Items - -Some applications may need to group their main menus to tidy up their menu structure. For example, you may want to group ABP's menu items, which came from modules in a group named *Admin*. - -In this version, you can allow to define groups and associate menu items with a group. Then your theme can render your menu items within the specified groups. - -**Example:** - -```csharp -private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) -{ - //Creating a new group - context.Menu.AddGroup("Dashboards", l["Dashboards"]); - - //Setting the group name for menu items - context.Menu - .AddItem(new ApplicationMenuItem("Home", l["Menu:Home"], groupName: "Dashboards") - .AddItem(new ApplicationMenuItem("Home", l["Menu:Dashboard"], groupName: "Dashboards"); -} -``` - -> **Note**: Currently, only the [LeptonX Theme](https://leptontheme.com/) renders groups for menu items. See the "LeptonX - Render Groups for Menu Items" section below for a demonstration. - -### Introducing the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService` - -In this version, we have introduced the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService` service to re-initialize application configurations. This service can be helpful, if you want to reset the application configurations after changing some configurations through your code. For example, you might have changed the values of some settings and might want to be able to get the new settings without the need to refresh the page. For this purpose, the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.ResetAsync()` method can be used to re-initialize the application configurations and cache the updated configurations for further usages. - -> For more information, please see [https://github.com/abpframework/abp/issues/15887](https://github.com/abpframework/abp/issues/15887). - -### CMS Kit Comments: Disallowing External URLs - -CMS Kit provides a [comment system](https://docs.abp.io/en/abp/7.2/Modules/Cms-Kit/Comments) to add the comment feature to any kind of resource, like blog posts for an example. The CMS Kit comment section is good for visitor comments and can improve your interaction with your application users. - -Sometimes, malicious users (or bots) can submit advertisement links into the comment sections. With this version, you can specify *allowed external URLs* for a specific comment section and disallow any other external URLs. You just need to configure the `CmsKitCommentOptions` as follows: - -```csharp -Configure(options => -{ - options.AllowedExternalUrls = new Dictionary> - { - { - "Product", - new List - { - "https://abp.io/" - } - } - }; -}); -``` - -If you don't specify any allowed external URLs for a specific comment section, all external URLs are allowed to be used in comments. For more information, please refer to the [CMS Kit: Comments documentation](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments). - -### New Components for Angular UI - -In this version, we have created some useful UI components for Angular UI, which are `abp-checkbox`, `abp-form-input`, and `abp-card`. Instead of using the related HTML elements and specifying bootstrap classes, from this version on, you can use these components. - -You can see the following examples for the usage of the `abp-card` component: - -```html - - -
...
-
-
-``` - -> See the [Card Component documentation](https://docs.abp.io/en/abp/7.2/UI/Angular/Card-Component) for more information. - -### Others - -* OpenIddict registered custom scopes have been added to the openid-configuration endpoint (`/.well-known/openid-configuration`) automatically. See [#16141](https://github.com/abpframework/abp/issues/16141) for more information. -* Two new tag-helpers have been added to MVC UI, which are `abp-date-picker` and `abp-date-range-picker`. See [#15806](https://github.com/abpframework/abp/pull/15806) for more information. -* Filtering/searching has been improved in the Docs Module and unified under a single *Search* section. See [#15787](https://github.com/abpframework/abp/issues/15787) for more information. - -## What's New with ABP Commercial 7.2? - -We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.2. - -### Authority Delegation - -Authority Delegation is a way of delegating the responsibility of the current user to a different user(s) for a limited time. Thus, a user can be switched to the delegated users' account and perform actions on their behalf. - -This version introduces support for the **Authority Delegation** in the [Account Module](https://docs.abp.io/en/commercial/latest/modules/account). You can check the following gif for a demonstration: - -![authority delegation](authority-delegation.gif) - -### Force Password Change at Next Logon - -It's a typical need to force users to change their password after their first successful login. Especially, if you as admin create a new user (*from the Users page of the Identity Pro module*, for example) with an easy initial password or a randomly generated password. The user should change his/her password with a more secure password that only they know. - -In this version, the "Forcing Password Change at Next Logon" feature has been added for this kind of purpose. Now, it's possible to force a user to change their password on the next login. - -The admin only needs to check the *Should change password on next login* option, while creating a new user: - -![force password change](force-password-change.png) - -After the first successful login, a password change page will open and force the user to change their password: - -![password change form](password-change-form.png) - -Then, the user starts using their account with a secure password that only they know. - -### Periodic Password Changes (Password Aging) - -**Password aging** is a mechanism to force users to periodically change their passwords. It allows you to specify a max number of days that a password can be used before it has to be changed. - -![password aging](password-aging.png) - -You can force this behavior in the "Password renewing settings" section of the Settings page as can be seen in the image above. Then, after the specified time has passed, users will have to renew their passwords. - -### LeptonX - Render Groups for Menu Items - -As mentioned in the *Grouping of Navigation Menu Items* section above, the [LeptonX Theme](https://leptontheme.com/) renders groups for menu items: - -![leptonx-group-menu-render](leptonx-group-render.png) - -### Suite: Show Properties on Create/Update/List Pages - -In this version, ABP Suite allows you to choose whether a property is visible/invisible on the create/update modals and list page. It also allows you to set specific properties to *readonly* on the update modals. - -![suite property](suite-property.png) - -## Community News - -### ABP - DOTNET CONF'23 - -![abp-conf](abp-conf.png) - -As the ABP team, we've organized 10+ [online events](https://community.abp.io/events) and gained a good experience with software talks. We are organizing ABP Dotnet Conference 2023, a full-featured software conference, in May. You can visit [https://abp.io/conference](https://abp.io/conference) to see speakers, talks, schedules, and other details. - -**Less than a month left until the event**! Don't forget to take your seat and buy an early bird ticket from [https://kommunity.com/volosoft/events/1st-abp-conference-96db1a54](https://kommunity.com/volosoft/events/1st-abp-conference-96db1a54)! - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [What’s New in .NET 8 🧐 ? Discover ALL .NET 8 Features](https://community.abp.io/posts/whats-new-in-.net-8-discover-all-.net-8-features-llcmrdre) by [Alper Ebicoglu](https://twitter.com/alperebicoglu). -* [Converting Create/Edit Modal to Page - Blazor](https://community.abp.io/posts/converting-createedit-modal-to-page-blazor-eexdex8y) by [Enis Necipoglu](https://twitter.com/EnisNecipoglu). -* [Using Dapper with the ABP Framework](https://community.abp.io/posts/using-dapper-with-the-abp-framework-shp74p2l) by [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan). -* [ABP React Template](https://community.abp.io/posts/abp-react-template-33pjmran) by [Anto Subash](https://twitter.com/antosubash). -* [How to Export Data to Excel Files with ASP.NET Core Minimal API](https://community.abp.io/posts/how-to-export-data-to-excel-files-with-asp.net-core-minimal-api-79o45u3s) by [Berkan Sasmaz](https://twitter.com/berkansasmazz). - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -### New ABP Blog Posts - -There are also some exciting blog posts written by the ABP team. You can see the following list for some of those articles: - -* [ABP Framework: Open Source Web Application Development Framework](https://blog.abp.io/abp/open-source-web-application-development-framework) by [Alper Ebicoglu](https://twitter.com/alperebicoglu). -* [ABP Framework: The Ultimate .NET Web Framework for Rapid Application Development](https://blog.abp.io/abp/ultimate-net-web-framework-for-rapid-application-development) by [Alper Ebicoglu](https://twitter.com/alperebicoglu). -* [Top 10 .NET Core Libraries Every Developer Should Know 🔥](https://blog.abp.io/abp/Top-10-.NET-Core-Libraries-Every-Developer-Should-Know) by [Alper Ebicoglu](https://twitter.com/alperebicoglu) - -## Conclusion - -This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.2/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.2 RC and provide feedback to help us release a more stable version. - -Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/abp-conf.png b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/abp-conf.png deleted file mode 100644 index b562de6dae..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/abp-conf.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/authority-delegation.gif b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/authority-delegation.gif deleted file mode 100644 index c09f428798..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/authority-delegation.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/cover-image.png b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/cover-image.png deleted file mode 100644 index 7cb92d2fa8..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/force-password-change.png b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/force-password-change.png deleted file mode 100644 index dec11bc1c9..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/force-password-change.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/leptonx-group-render.png b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/leptonx-group-render.png deleted file mode 100644 index 5a02c2596f..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/leptonx-group-render.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/password-aging.png b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/password-aging.png deleted file mode 100644 index 8ed43ac2a7..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/password-aging.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/password-change-form.png b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/password-change-form.png deleted file mode 100644 index 783225e175..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/password-change-form.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/suite-property.png b/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/suite-property.png deleted file mode 100644 index aa14b424a2..0000000000 Binary files a/docs/en/Blog-Posts/2023-04-12 v7_2_Preview/suite-property.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-04-17-How-to-Optimize-Your-ASP.NET-Application-for-Improved-Performance/post.md b/docs/en/Blog-Posts/2023-04-17-How-to-Optimize-Your-ASP.NET-Application-for-Improved-Performance/post.md deleted file mode 100644 index 261621933a..0000000000 --- a/docs/en/Blog-Posts/2023-04-17-How-to-Optimize-Your-ASP.NET-Application-for-Improved-Performance/post.md +++ /dev/null @@ -1,118 +0,0 @@ -# 💻 How to Optimize Your ASP.NET Application for Improved Performance 🚀 - -If you want your ASP.NET application to perform well, you need to optimize it for speed, responsiveness, and user experience. Performance optimization is critical for factors like fast page load times, improved response efficiency, and happy users. In this article, I'll provide several tips and tricks to help you optimize performance in ASP.NET Core. - -## 🚀 Use Response Compression in Your ASP.NET Application -You can use ASP.NET Core's built-in response compression middleware to compress the response data and reduce the amount of data that needs to be transferred over the network. To use response compression, add the following code to your application's Startup.cs file: - -```csharp -services.AddResponseCompression(options => -{ - options.EnableForHttps = true; -}); - -app.UseResponseCompression(); -``` - -## 🖼️ Optimize Images in Your ASP.NET Application: - -Images can be a major contributor to page bloat and slow load times. Here are some tips to optimize images: - -🖌️ Use a tool like ImageOptim or Kraken.io to compress and optimize images. - -🖼️ Specify the width and height of images in HTML so the browser can allocate space for them before they load. - -📝 Use alt attributes to provide descriptive text for images, which can improve accessibility and also help with SEO. - -📜 Use lazy loading for images that are below the fold, meaning they're not visible on the initial screen view. You can use libraries like Vanilla LazyLoad to implement lazy loading. - -📱 Use responsive images to serve different image sizes to different devices. This can improve page load times by reducing the size of images that are displayed on smaller devices. - -💻 Example: - -```html - - - - Image - -``` - -```javascript -var lazyLoadInstance = new LazyLoad(); -``` - -## 🧱 Optimize HTML in Your ASP.NET Application: - -The structure and organization of HTML can affect the page speed. Here are some tips to optimize HTML: - -📝 Use the heading tags (h1, h2, h3, etc.) in a logical and sequential order. - -🔩 Use the "defer" attribute for script tags that don't need to be executed immediately. This can improve the page load times by delaying the execution of scripts until after the page has rendered. - -🔩 Use the "async" attribute for script tags that can be executed asynchronously. This can further improve the page load times by allowing scripts to be downloaded and executed simultaneously. - -🧱 Use semantic HTML elements (like nav, section, and article) to provide additional structure and meaning to the page. - -## 🎨 Optimize CSS and JavaScript in Your ASP.NET Application: - -CSS and JavaScript files can be a major contributor to the page load times. Here are some tips to optimize CSS and JavaScript in your ASP.NET application: - -🔨 Minify and concatenate CSS and JavaScript files to reduce their size. - -🔩 Use the "defer" or "async" attributes for script tags to delay or asynchronously load scripts. - -## 🔡 Use system fonts in Your ASP.NET Application: - -Loading custom fonts can be slow and increase page load times. Using system fonts can improve page speed by allowing the browser to use fonts that are already installed on the user's device. - -## 🖼️ Use Placeholders and Progress Indicators in Your ASP.NET Application: - -To improve the perceived performance of your website, you can use placeholders and progress indicators for slow-loading sections of your page. You can use JavaScript to load these sections after the initial page load. - -💻 Example: - -```html - -
-

Loading...

-
-``` - -```javascript -const placeholder = document.querySelector('#placeholder'); - fetch(placeholder.dataset.url) - .then(response => response.text()) - .then(html => placeholder.innerHTML = html); -``` - -## 🔗 Use the Appropriate Link Text and ARIA Labels: - -When using links, use appropriate link texts that accurately describe the content of the linked page. This can improve the accessibility and also help with SEO. - -ARIA labels should also be used to provide additional context for links. This can also improve the accessibility and help with SEO. - -💻 Example: - -```html -Example -Another Example -``` - -## 🌐 Optimize the Third-party Resources in Your ASP.NET Application: - -Third-party resources like social media widgets and advertising scripts can slow down the page load times. Here are some tips to optimize third-party resources: - -🔩 Use asynchronous scripts when possible. - -🔍 Only load third-party resources that are necessary for the page. - -By following these optimization techniques, you can significantly improve the page speed of your ASP.NET Core web application. - -## What is ABP Framework? - -ABP Framework offers an opinionated architecture to build enterprise software solutions with ASP.NET Core best practices on top of the .NET and the ASP.NET Core platforms. It provides the fundamental web application infrastructure, production-ready dotnet startup templates, modules, asp.net core ui themes, tooling, guides and documentation to implement that ASP.NET core architecture properly and automate the details and repetitive work as much as possible. - -If you are starting a new ASP.NET Core project, try [abp.io](https://abp.io/) now... - -**IT IS FREE AND OPEN-SOURCE!** \ No newline at end of file diff --git a/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/POST.md b/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/POST.md deleted file mode 100644 index 1fc9a64d0c..0000000000 --- a/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/POST.md +++ /dev/null @@ -1,86 +0,0 @@ -# ABP.IO Platform 7.2 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.2 versions have been released today. - -## What's New With Version 7.2? - -All the new features were already explained in detail in the [7.2 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7.2-RC-Has-Been-Published), so no need to go over them again. Check it out for more details. - -## Getting Started with 7.2 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 7.2 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade it to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update the existing CLI: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -There are breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v7.1: - -* [ABP Framework 7.1 to 7.2 Migration Guide](https://docs.abp.io/en/abp/7.2/Migration-Guides/Abp-7_2) -* [ABP Commercial 7.1 to 7.2 Migration Guide](https://docs.abp.io/en/commercial/7.2/migration-guides/v7_2) - -## Community News - -### ABP - DOTNET CONF'23 - -![abp-conf](abp-conf.png) - -As the ABP team, we've organized 10+ [online events](https://community.abp.io/events) and gained a good experience with software talks. We are organizing ABP Dotnet Conference 2023, a full-featured software conference, on May 10. You can visit [https://abp.io/conference](https://abp.io/conference) to see the speakers, talks, schedules, and other details. - -Don't forget to take your seat and buy a ticket from [https://kommunity.com/volosoft/events/1st-abp-conference-96db1a54](https://kommunity.com/volosoft/events/1st-abp-conference-96db1a54)! - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [Converting Create/Edit Modal to Page AngularUI](https://community.abp.io/posts/converting-createedit-modal-to-page-angularui-doadhgil) by [Masum Ulu](https://twitter.com/masumulu) -* [How to Export Data to Excel Files with ASP.NET Core Minimal API](https://community.abp.io/posts/how-to-export-data-to-excel-files-with-asp.net-core-minimal-api-79o45u3s) by [Berkan Sasmaz](https://twitter.com/berkansasmazz) -* [ABP React Template](https://community.abp.io/posts/abp-react-template-33pjmran) by [Anto Subash](https://twitter.com/antosubash) -* [Using Dapper with the ABP Framework](https://community.abp.io/posts/using-dapper-with-the-abp-framework-shp74p2l) by [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) -* [Converting Create/Edit Modal to Page - Blazor](https://community.abp.io/posts/converting-createedit-modal-to-page-blazor-eexdex8y) by [Enis Necipoglu](https://twitter.com/EnisNecipoglu) -* [What’s New in .NET 8 🧐 ? Discover ALL .NET 8 Features](https://community.abp.io/posts/whats-new-in-.net-8-discover-all-.net-8-features-llcmrdre) by [Alper Ebicoglu](https://twitter.com/alperebicoglu) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -### New ABP Blog Posts - -There are also some exciting blog posts written by the ABP team. You can see the following list for some of those articles: - -* [You Are Invited to ABP .NET Conf’23! 📣](https://blog.abp.io/abp/You-Are-Invited-to-ABP-dotNET-Conf23) by [Bige Beşikçi](https://twitter.com/bigedediki) -* [💻 Speed Up Your ASP.NET Application 🚀](https://blog.abp.io/abp/Speed-Up-Your-ASP.NET-Application) by [Salih Özkara](https://twitter.com/salihozkara_) -* [C# 12 🔍 Discover the Exciting New Features & Improvements 🆕🚀](https://blog.abp.io/abp/CSharp-12-Discover-the-Exciting-New-Features-and-Improvements) by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) - -## About the Next Version - -The next feature version will be 7.3. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/abp-conf.png b/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/abp-conf.png deleted file mode 100644 index b562de6dae..0000000000 Binary files a/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/abp-conf.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/cover-image.png deleted file mode 100644 index 7cb92d2fa8..0000000000 Binary files a/docs/en/Blog-Posts/2023-05-03 v7_2_Release_Stable/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md deleted file mode 100644 index ef5a8323c4..0000000000 --- a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md +++ /dev/null @@ -1,267 +0,0 @@ -# ABP.IO Platform 7.3 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.3 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -Try this version and provide feedback for a more stable version of ABP v7.3! Thanks to all of you. - -## Get Started with the 7.3 RC - -Follow the steps below to try version 7.3.0 RC today: - -1) **Upgrade** the ABP CLI to version `7.3.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 7.3.0-rc.1 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 7.3.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. - -You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). - -## Migration Guides - -There are a few breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v7.2: - -* [ABP Framework 7.2 to 7.3 Migration Guide](https://docs.abp.io/en/abp/7.3/Migration-Guides/Abp-7_3) - -> If you are using the CMS Kit or CMS Kit Pro module, please don't forget to create a new migration and apply it to your database. - -## What's New with ABP Framework 7.3? - -In this section, I will introduce some major features released in this version. Here is a brief list of the titles that will be explained in the next sections: - -* Introducing the Volo.Abp.Imaging packages -* ABP CLI: switch-to-local command -* Monitoring Distributed Events -* Ordering of the Local Event Handlers -* Nonce attribute support for Content Security Policy (CSP) -* Other News - -### Introducing the Volo.Abp.Imaging packages - -ABP Framework provides some packages to compress and resize images. Currently, there are four official packages: - -* `Volo.Abp.Imaging.Abstractions`: Provides common services for compression and resizing purposes. -* `Volo.Abp.Imaging.AspNetCore`: Provides some attributes for controller actions that can automatically compress and/or resize uploaded files. -* `Volo.Abp.Imaging.ImageSharp`: Implements the image compression & resize operations using the [ImageSharp](https://github.com/SixLabors/ImageSharp) library. -* `Volo.Abp.Imaging.MagickNet`: Implements the image compression & resize operations using the [Magick.NET](https://github.com/dlemstra/Magick.NET) library. - -You can use one of these official providers (`ImageSharp` or `Magick.NET`) or implement your own image resizer/compressor contributor and use it in your application. - -> See the [Image Manipulation](https://docs.abp.io/en/abp/7.3/Image-Manipulation) documentation to learn more and see the required configurations. - -### ABP CLI: switch-to-local command - -In this version, ABP CLI introduces a new CLI command: **"switch-to-local"**. The `switch-to-local` command changes all NuGet package references on a solution to local project references for all the `.csproj` files in the specified folder (and all its subfolders with any depth). - -**Usage:** - -```bash -abp switch-to-local --paths "C:\Github\abp" -``` - -### Monitoring Distributed Events - -ABP Framework allows you to stay informed when your application **receives** or **sends** a distributed event. This enables you to track the event flow within your application and take appropriate actions based on the received or sent distributed events. - -You just need to subscribe to one of the `DistributedEventReceived` or `DistributedEventSent` events and take additional actions according to your cases. - -**Example: Get informed when your application sends an event to the distributed event bus** - -```csharp -public class DistributedEventSentHandler : ILocalEventHandler, ITransientDependency -{ - public async Task HandleEventAsync(DistributedEventSent eventData) - { - // TODO: IMPLEMENT YOUR LOGIC... - } -} -``` - -> See the documentation to learn more: [https://docs.abp.io/en/abp/7.3/Distributed-Event-Bus](https://docs.abp.io/en/abp/7.3/Distributed-Event-Bus) - -### Ordering of the Local Event Handlers - -In this version, ABP Framework introduces the `LocalEventHandlerOrder` attribute, which can be used to set the execution order for the event handlers. This can be helpful if you want to handle your local event handlers in a specific order. - -**Example:** - -```csharp -[LocalEventHandlerOrder(-1)] -public class MyHandler - : ILocalEventHandler, - ITransientDependency -{ - public async Task HandleEventAsync(StockCountChangedEvent eventData) - { - //TODO: your implementation - } -} -``` - -By default, all event handlers have an order value of 0. Thus, if you want to take certain event handlers to be executed before other event handlers, you can set the order value as a negative value. - -> See the documentation to learn more: [https://docs.abp.io/en/abp/7.3/Local-Event-Bus](https://docs.abp.io/en/abp/7.3/Local-Event-Bus) - -### Nonce attribute support for Content Security Policy (CSP) - -ABP Framework supports adding unique value to nonce attribute for script tags which can be used by Content Security Policy to determine whether or not a given fetch will be allowed to proceed for a given element. In other words, it provides a mechanism to execute only correct script tags with the correct nonce value. - -This feature is disabled by default. You can enable it by setting the *UseContentSecurityPolicyScriptNonce* property of the `AbpSecurityHeadersOptions` class to **true**: - -```csharp -Configure(options => -{ - //adding script-src nonce - options.UseContentSecurityPolicyScriptNonce = true; //false by default -}); -``` - -> See the [Security Headers](https://docs.abp.io/en/abp/7.3/UI/AspNetCore/Security-Headers) documentation for more information. - -### Other News - -* Upgraded the [Blazorise](https://blazorise.com/) library to v1.2.3 for Blazor UI. After the upgrade, ensure that all Blazorise-related packages are using v1.2.3 in your application. -* Module Entity Extension support has been added for the CMS Kit module. See [#16572](https://github.com/abpframework/abp/issues/16572) for more information. - -If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/5.3.0-rc.1), which contains a list of all the issues and pull requests were closed with this version. - -## What's New with ABP Commercial 7.3? - -We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.3. - -### Account Module - Using Authenticator App for Two-Factor Authentication - -In this version, ABP Commercial provides a new **Two-Factor Authentication (2FA) provider** that allows you to log in to an application by scanning a QR Code with an Authenticator App, such as Microsoft Authenticator or Google Authenticator. - -You need to apply the following actions to configure an Authenticator and then you are free to log in by using the Authenticator App: - -**Step 1 - Enable Two Factor Authentication and Scan the QR Code:** - -![](./two-factor-auth-1.png) - -**Step 2 - Verify the QR Code with an authenticator app:** - -![](./two-factor-auth-2.png) - -**Step 3 - Save the recovery codes for later use in case of not being able to login by verifying the QR code:** - -![](./two-factor-auth-3.png) - -You can disable the two-factor authentication and reset the Authenticator App anytime you want, just by disabling the two-factor authentication or resetting the authenticator: - -![](./reset-authenticator.png) - -### Upgrade Blazorise to v1.2.3 - -Upgraded the [Blazorise](https://blazorise.com/) library to v1.2.3 for Blazor UI. If you are upgrading your project to v7.3.0, please ensure that all the Blazorise-related packages are using v1.2.3 in your application. Otherwise, you might get errors due to incompatible versions. - -### CMS Kit: Module Entity Extensions - -Module entity extension system is a high-level extension system that allows you to define new properties for existing entities of the dependent modules. ABP Framework and ABP Commercial use this system to allow developers to extend entities in different modules. - -In this version, Module Entity Extension support has been added for the CMS Kit Pro module. - -You can open the `YourProjectNameModuleExtensionConfigurator` class inside the `Domain.Shared` project of your solution and change the `ConfigureExtraProperties` method as shown below to add a new property to the `Poll` entity of the [CMS Kit Pro module](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index): - -```csharp -public static void ConfigureExtraProperties() -{ - OneTimeRunner.Run(() => - { - ObjectExtensionManager.Instance.Modules() - .ConfigureCmsKitPro(cmsKitPro => - { - cmsKitPro.ConfigurePoll(poll => - { - poll.AddOrUpdateProperty( - "", - property => - { - //configuration for this property - } - ) - }); - }); - }); -} - -``` - -> See the [Module Entity Extensions documentation](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) to learn more. - -### LeptonX Account Layout - -In this version, Account Layout has been re-designed for LeptonX Theme. You can see the new account layout in the following figure: - -![](leptonx-account-layout.png) - -> To use this new account layout, ensure that your LeptonX Theme package versions are v2.3+. - -## Community News - -### ABP Community Talks 2023.4: Angular 16 and ABP v7.3 - -![](./community-talks-2023-4.png) - -In this episode, the core ABP team talked about what's new with ABP v7.3 and Angular 16. You can watch the event from [here](https://www.youtube.com/watch?v=lq6u4vQURcI). - -### ABP .NET Conference 2023 - -![](./abp-conf.png) - -We organized ABP .NET Conference 2023 on May 2023 and we are happy to share the success of the conference, which captivated overwhelmingly interested live viewers from all over the world. 13 great line up of speakers which includes .NET experts and Microsoft MVPs delivered captivating talks that resonated with the audiences. Each of the talks attracted a great amount of interest and a lot of questions, sparking curiosity in the attendees. - -Thanks to all speakers and attendees for joining our event. - -> We shared our takeaways in a blog post, which you can read at [https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up](https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up). - -### Volosoft Attendeed & Sponsored Devnot .NET Conference 2023 - -![](devnot-conference.png) - -We are thrilled to announce that the Volosoft Company proudly attended as one of the Gold Sponsors at the Devnot .NET Conference 2023! We are happy to join and be a sponsor of events and contribute to the software society, empowering developers and driving innovation with the .NET community. - -![](devnot-talk.png) - -Co-Founder of [Volosoft](https://volosoft.com/) and Lead Developer of the ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) gave a word about "Dealing with Concurrency and Multi Threading in .NET" at this event. - -> You can check [this blog post](https://volosoft.com/blog/Reflecting-on-Devnot-Dotnet-Conference-2023) if you want to learn more about the Devnot .NET Conference 2023. - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [Authority Delegation in ABP Commercial](https://community.abp.io/posts/authority-delegation-in-abp-commercial-3wtljpp0) by [Liang Shiwei](https://github.com/realLiangshiwei) -* [What's new in Angular 16? New Features and Updates](https://community.abp.io/posts/whats-new-in-angular-16-new-features-and-updates-s1izi9br) by [Masum Ulu](https://twitter.com/masumulu) -* [Kubernetes Integrated Microservice Development with ABP Studio](https://community.abp.io/videos/kubernetes-integrated-microservice-development-with-abp-studio-oix9zkp8) by [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -### New ABP Blog Posts - -There are also some exciting blog posts written by the ABP team. You can see the following list for some of those articles: - -* [ABP .NET Conference 2023 Wrap Up](https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up) by [Bige Beşikçi](https://twitter.com/bigedediki) -* [Meet Volosoft at the Devnot .NET Conference 2023!](https://volosoft.com/blog/Meet-Volosoft-at-the-Devnot-.NET-Conference-2023) by [Roo Xu](https://github.com/Roo1227) - -## Conclusion - -This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.3/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.3 RC and provide feedback to help us release a more stable version. - -Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/abp-conf.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/abp-conf.png deleted file mode 100644 index b3add3ebc0..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/abp-conf.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png deleted file mode 100644 index 7c4bef3950..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png deleted file mode 100644 index a15a8f0feb..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png deleted file mode 100644 index 9175452037..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png deleted file mode 100644 index 896663ae28..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png deleted file mode 100644 index 90ae483bec..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png deleted file mode 100644 index d627f5c783..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png deleted file mode 100644 index b248970b47..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png deleted file mode 100644 index 4ad00ddd5f..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png deleted file mode 100644 index 407d8bc6d3..0000000000 Binary files a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/POST.md b/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/POST.md deleted file mode 100644 index 89cf9e340d..0000000000 --- a/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/POST.md +++ /dev/null @@ -1,75 +0,0 @@ -# ABP.IO Platform 7.3 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.3 versions have been released today. - -## What's New With Version 7.3? - -All the new features were already explained in detail in the [7.3 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7-3-RC-Has-Been-Published), so no need to go over them again. Check it out for more details. - -## Getting Started with 7.3 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 7.3 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade it to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update the existing CLI: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -There are breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v7.2: - -* [ABP Framework 7.2 to 7.3 Migration Guide](https://docs.abp.io/en/abp/7.3/Migration-Guides/Abp-7_3) - -## Community News - -### ABP Community Talks 2023.5: Mobile Development with the ABP Framework - -![](community-talks.png) - -In this episode, we'll talk about Exploring Options for Mobile Development with the ABP Framework. - -> Join us to explore the options for Mobile Development in ABP Framework on July 27, 2023, at 17:00 UTC. You can register from [here](https://kommunity.com/volosoft/events/abp-community-talks-20235-mobile-development-with-the-abp-framework-68e64e59). - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [Image Compression and Resize with ABP Framework](https://community.abp.io/posts/image-compression-and-resize-with-abp-framework-4v2gpb7g) by [Engincan Veske](https://twitter.com/EngincanVeske) -* [Manage Quartz with SilkierQuartz](https://community.abp.io/posts/manage-quartz-with-silkierquartz-xb4ovbj9) by [Jadyn](https://community.abp.io/members/Jadyn) -* [ABP Helper Methods](https://community.abp.io/posts/abp-helper-methods-04dk74cq) by [Engincan Veske](https://twitter.com/EngincanVeske) -* [How to replace SwaggerUI with RapiDoc](https://community.abp.io/posts/how-to-replace-swaggerui-with-rapidoc-hw7pktmz) by [Jadyn](https://community.abp.io/members/Jadyn) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -## About the Next Version - -The next feature version will be 7.4. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/community-talks.png b/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/community-talks.png deleted file mode 100644 index f6fded181f..0000000000 Binary files a/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/community-talks.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/cover-image.png deleted file mode 100644 index a15a8f0feb..0000000000 Binary files a/docs/en/Blog-Posts/2023-07-12 v7_3_Release_Stable/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/POST.md b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/POST.md deleted file mode 100644 index c7d8411886..0000000000 --- a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/POST.md +++ /dev/null @@ -1,287 +0,0 @@ -# ABP.IO Platform 7.4 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.4 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -Try this version and provide feedback for a more stable version of ABP v7.4! Thanks to all of you. - -## Get Started with the 7.4 RC - -Follow the steps below to try version 7.4.0 RC today: - -1) **Upgrade** the ABP CLI to version `7.4.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 7.4.0-rc.1 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 7.4.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. - -You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). - -## Migration Guides - -There are a few breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v7.3 or earlier: - -* [ABP Framework 7.3 to 7.4 Migration Guide](https://docs.abp.io/en/abp/7.4/Migration-Guides/Abp-7_4) - -## What's New with ABP Framework 7.4? - -In this section, I will introduce some major features released in this version. Here is a brief list of the titles that will be explained in the next sections: - -* Dynamic Setting Store -* Introducing the `AdditionalAssemblyAttribute` -* `CorrelationId` Support on Distributed Events -* Database Migration System for EF Core -* Other News - -### Dynamic Setting Store - -Prior to this version, it was hard to define settings in different microservices and centrally manage all setting definitions in a single admin application. To make that possible, we used to add project references for all the microservices' service contract packages from a single microservice, so it can know all the setting definitions and manage them. - -In this version, ABP Framework introduces the Dynamic Setting Store, which is an important feature that allows you to collect and get all setting definitions from a single point and overcome the setting management problems on microservices. - -> *Note*: If you are upgrading from an earlier version and using the Setting Management module, you need to create a new migration and apply it to your database because a new database table has been added for this feature. - -### Introducing the `AdditionalAssemblyAttribute` - -In this version, we have introduced the `AdditionalAssemblyAttribute` to define additional assemblies to be part of a module. ABP Framework automatically registers all the services of your module to the [Dependency Injection System](https://docs.abp.io/en/abp/latest/Dependency-Injection). It finds the service types by scanning types in the assembly that define your module class. Typically, every assembly contains a separate module class definition and modules depend on each other using the `DependsOn` attribute. - -In some rare cases, your module may consist of multiple assemblies and only one of them defines a module class, and you want to make the other assemblies parts of your module. This is especially useful if you can't define a module class in the target assembly or you don't want to depend on that module's dependencies. - -In that case, you can use the `AdditionalAssembly` attribute as shown below: - -```csharp -[DependsOn(...)] // Your module dependencies as you normally would do -[AdditionalAssembly(typeof(IdentityServiceModule))] // A type in the target assembly (in another assembly) -public class IdentityServiceTestModule : AbpModule -{ - ... -} -``` - -With the `AdditionalAssembly` attribute definition, ABP loads the assembly containing the `IdentityServiceModule` class as a part of the identity service module. Notice that in this case, none of the module dependencies of the `IdentityServiceModule` are loaded. Because we are not depending on the `IdentityServiceModule`, instead we are just adding its assembly as a part of the `IdentityServiceTestModule`. - -> You can check the [Module Development Basics](https://docs.abp.io/en/abp/7.4/Module-Development-Basics) documentation to learn more. - -### `CorrelationId` Support on Distributed Events - -In this version, `CorrelationId` (a unique key that is used in distributed applications to trace requests across multiple services/operations) is attached to the distributed events, so you can relate events with HTTP requests and can trace all the related activities. - -ABP Framework generates a `correlationId` for the first time when an operation is started and then attaches the current `correlationId` to distributed events as an additional property. For example, if you are using the [transactional outbox or inbox pattern provided by ABP Framework](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#outbox-inbox-for-transactional-events), you can see the `correlationId` in the extra properties of the `IncomingEventInfo` or `OutgoingEventInfo` classes with the standard `X-Correlation-Id` key. - -> You can check [this issue](https://github.com/abpframework/abp/issues/16773) for more information. - -### Database Migration System for EF Core - -In this version, ABP Framework provides base classes and events to migrate the database schema and seed the database on application startup. This system works compatibly with multi-tenancy and whenever a new tenant is created or a tenant's database connection string has been updated, it checks and applies database migrations for the new tenant state. - -This system is especially useful to migrate databases for microservices. In this way, when you deploy a new version of a microservice, you don't need to manually migrate its database. - -You need to take the following actions to use the database migration system: - -* Create a class that derives from `EfCoreRuntimeDatabaseMigratorBase` class, override and implement its `SeedAsync` method. And lastly, execute the `CheckAndApplyDatabaseMigrationsAsync` method of your class in the `OnPostApplicationInitializationAsync` method of your module class. -* Create a class that derives from `DatabaseMigrationEventHandlerBase` class, override and implement its `SeedAsync` method. Then, whenever a new tenant is created or a tenant's connection string is changed then the `SeedAsync` method will be executed. - -### Other News - -* [OpenIddict](https://github.com/openiddict/openiddict-core/tree/4.7.0) library has been upgraded to **v4.7.0**. See [#17334](https://github.com/abpframework/abp/pull/17334) for more info. -* ABP v7.4 introduces the `Volo.Abp.Maui.Client` package, which is used by the MAUI mobile application in ABP Commercial. See [#17201](https://github.com/abpframework/abp/pull/17201) for more info. -* In this version, the `AbpAspNetCoreIntegratedTestBase` class gets a generic type parameter, which expects either a startup class or an ABP module class. This allows us to use configurations from an ABP module or old-style ASP.NET Core Startup class in a test application class and this simplifies the test application project. See [#17039](https://github.com/abpframework/abp/pull/17039) for more info. - -## What's New with ABP Commercial 7.4? - -We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce new features coming with ABP Commercial 7.4. - -### Dynamic Text Template Store - -Prior to this version, it was hard to create text templates in different microservices and centrally manage them in a single admin application. For example, if you would define a text template in your ordering microservice, then those text templates could not be seen on the administration microservice because the administration microservice would not have any knowledge about that text template (because it's hard-coded in the ordering microservice). - -For this reason, in this version, the Dynamic Text Template Store has been introduced to make the [Text Template Management module](https://docs.abp.io/en/commercial/latest/modules/text-template-management) compatible with microservices and distributed systems. It allows you to store and get all text templates from a single point. Thanks to that, you can centrally manage the text templates in your admin application. - -> *Note*: If you are upgrading from an earlier version and are using the Text Template Management module, you need to create a new migration and apply it to your database. - -To enable the dynamic template store, you just need to configure the `TextTemplateManagementOptions` and set the `IsDynamicTemplateStoreEnabled` as true in your module class: - -```csharp -Configure(options => -{ - options.IsDynamicTemplateStoreEnabled = true; -}); -``` - -Notice this is only needed in the microservice where you centrally manage your text template contents. So, typically you would use the configuration above in your administration microservice. Other microservices automatically save their text template contents to the central database. - -### Suite: Custom Code Support - -In this version, we have implemented the custom code support in Suite. This allows you to customize the generated code-blocks and preserve your custom code changes in the next CRUD Page Generation in Suite. ABP Suite specifies hook-points to allow adding custom code blocks. Then, the code that you wrote to these hook points will be respected and will not be overridden in the next entity generation. - -![](suite-custom-code.png) - -To enable custom code support, you should check the *Customizable code* option in the crud page generation page. When you enable the custom code support, you will be seeing some hook-points in your application. - -For example, on the C# side, you'll be seeing some abstract classes and classes that derive from them (for entities, application services, interfaces, domain services, and so on...). You can write your custom code in those classes (`*.Extended.cs`) and the next time when you need to re-generate the entity, your custom code will not be overridden (only the base abstract classes will be re-generated and your changes on Suite will be respected): - -Folder structure | Book.Extended.cs -:-------------------------:|:-------------------------: -![](suite-custom-code-backend.png) | ![](book-extended-cs.png) - -> *Note*: If you want to override the entity and add custom code, please do not touch the code between `...` placeholders, because the constructor of the entity should be always re-generated in case of a new property added. - -On the UI side, you can see the *comment placeholders* on the pages for MVC & Blazor applications. These are hook-points provided by ABP Suite and you can write your custom code between these comment sections: - -Folder structure | Books/Index.cshtml -:-------------------------:|:-------------------------: -![](suite-custom-code-ui.png) | ![](book-extended-cshtml.png) - -### MAUI & React Native UI Revisions - -In this version, we have revised MAUI & React Native mobile applications and added new pages, functionalities and made improvements on the UI side. - -![](maui.png) - -For example, in the MAUI application, we have implemented the following functionalities and changed the UI completely: - -* **User Management Page**: Management page for your application users. You can search, add, update, or delete users of your application. -* **Tenants**: Management page for your tenants. -* **Settings**: Management page for your application settings. On this page, you can change **the current language**, **the profile picture**, **the current password**, or/and **the current theme**. - -Also, we have aligned the features on both of these mobile options (MAUI & React Native) and showed them in the ["ABP Community Talks 2023.5: Exploring the Options for Mobile Development with the ABP Framework"](https://community.abp.io/events/mobile-development-with-the-abp-framework-ogtwaz5l). - -> If you have missed the event, you can watch from 👉 [here](https://www.youtube.com/watch?v=-wrdngeKgZw). - -### New LeptonX Theme Features - -In the new version of LeptonX Theme, which is v2.4.0-rc.1, there are some new features that we want to mention. - -#### Mobile Toolbars - -The [Toolbar System](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Toolbars) is used to define *toolbars* on the user interface. Modules (or your application) can add items to a toolbar, then the UI themes can render the toolbar on the layout. - -LeptonX Theme extends this system even further and introduces mobile toolbars with this version. You can create a component and add it as a mobile toolbar as below: - -```csharp -public class MyToolbarContributor : IToolbarContributor -{ - public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) - { - if (context.Toolbar.Name == LeptonXToolbars.MainMobile) - { - context.Toolbar.Items.Add(new ToolbarItem(typeof(ShoppingCardToolbarComponent))); - - //other mobile toolbars... - } - - return Task.CompletedTask; - } -} -``` - -Then, the LeptonX Theme will render these mobile toolbars like in the figure below: - -![](mobile-toolbars.png) - -> **Note**: The Angular UI hasn't been completed yet. We aim to complete it as soon as possible and include it in the next release. - -#### New Error Page Designs - -In this version, we have implemented new error pages. Encounter a fresh look during error situations with the 'New Error Page Designs,' providing informative and visually appealing error displays that enhance user experience: - -![](error-page.png) - -#### Fluid Layout - -In this version, LeptonX Theme introduces the fresh-looking **Fluid Layout**, which is a layout that lets you align elements so that they automatically adjust their alignment and proportions for different page sizes and orientations. - -![](fluid-layout.png) - -> You can visit [the live demo of LeptonX Theme](https://x.leptontheme.com/side-menu) and try the Fluid Layout now! - -### Check & Move Related Entities on Deletion/Demand - -In application modules, there are some entities that have complete relationships with each other such as role-user relations. In such cases, it's a typical requirement to check & move related entities that have a relation with the other entity that is about to be deleted. - -For example, if you need to delete an edition from your system, you would typically want to move the tenant that is associated with that edition. For this purpose, in this version, ABP Commercial allows you to move related entities on deletion/demand. - -![](editions.png) - -Currently, this feature is implemented for SaaS and Identity Pro modules and for the following relations: - -* Edition - Tenant -* Role - User -* Organization Unit - User - -Also, it's possible to move the related associated-records before deleting the record. For example, you can move all tenants from an edition as shown in the figure below: - -"Move all tenants" action | "Move all tenants" modal -:-------------------------:|:-------------------------: -![](move-all-tenants.png) | ![](move-tenants.png) - -### CMS Kit Pro: Page Feedback - -In this version, the **Page Feedback** feature has been added to the [CMS Kit Pro](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index) module. This feature allows you to get feedback from a page in your application. - -This is especially useful if you have content that needs feedback from users. For example, if you have documentation or a blog website, it's a common requirement to assess the quality of the articles and get feedback from users. In that case, you can use this feature: - -![](page-feedback.png) - -### Chat Module: Deleting Messages & Conversations - -In this version, the [Chat Module](https://docs.abp.io/en/commercial/latest/modules/chat) allows you to delete individual messages or a complete conversation. - -You can enable or disable the message/conversation deletion globally on your application: - -![](settings.png) - -> **Note**: The Angular UI hasn't been completed yet. We aim to complete it as soon as possible and include it in the next release. - -### Password Complexity Indicators - -In this version, ABP Framework introduces an innovative ["Password Complexity Indicator"](https://docs.abp.io/en/commercial/7.4/ui/angular/password-complexity-indicator-component) feature, designed to enhance security and user experience. This feature dynamically evaluates and rates the strength of user-generated passwords, providing real-time feedback to users as they create or update their passwords. By visually indicating the complexity level, users are guided toward crafting stronger passwords that meet modern security standards. - -![](password-complexity.png) - -You can check the [Password Complexity Indicator Angular documentation](https://docs.abp.io/en/commercial/7.4/ui/angular/password-complexity-indicator-component) to learn more. - -> **Note**: Currently, this feature is only available for the Angular UI, but we will be implemented for other UIs in the next version. - -## Community News - -### DevNot Developer Summit 2023 - -![](developersummit.jpg) - -We are thrilled to announce that the co-founder of [Volosoft](https://volosoft.com/) and Lead Developer of the ABP Framework, Halil Ibrahim Kalkan will give a speech about "Building a Kubernetes Integrated Local Development Environment" in the [Developer Summit 2023 event](https://summit.devnot.com/) on the 7th of October. - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [ABP Commercial - GDPR Module Overview](https://community.abp.io/posts/abp-commercial-gdpr-module-overview-kvmsm3ku) by [Engincan Veske](https://twitter.com/EngincanVeske) -* [Video: ABP Framework Data Transfer Objects](https://community.abp.io/videos/abp-framework-data-transfer-objects-qwebfqz5) by [Hamza Albreem](https://github.com/braim23) -* [Video: ABP Framework Essentials: MongoDB](https://community.abp.io/videos/abp-framework-essentials-mongodb-gwlblh5x) by [Hamza Albreem](https://github.com/braim23) -* [ABP Modules and Entity Dependencies](https://community.abp.io/posts/abp-modules-and-entity-dependencies-hn7wr093) by [Jack Fistelmann](https://github.com/nebula2) -* [How to add dark mode support to the Basic Theme in 3 steps?](https://community.abp.io/posts/how-to-add-dark-mode-support-to-the-basic-theme-in-3-steps-ge9c0f85) by [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) -* [Deploying docker image to Azure with yml and bicep through Github Actions](https://community.abp.io/posts/deploying-docker-image-to-azure-with-yml-and-bicep-through-github-actions-cjiuh55m) by [Sturla](https://community.abp.io/members/Sturla) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -## Conclusion - -This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.4/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.4 RC and provide feedback to help us release a more stable version. - -Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cs.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cs.png deleted file mode 100644 index 5efd677bb4..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cs.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cshtml.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cshtml.png deleted file mode 100644 index ddbae4a841..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cshtml.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/cover-image.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/cover-image.png deleted file mode 100644 index d3152c7f4b..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/developersummit.jpg b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/developersummit.jpg deleted file mode 100644 index 65bae90315..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/developersummit.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/editions.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/editions.png deleted file mode 100644 index ea96907e56..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/editions.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/error-page.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/error-page.png deleted file mode 100644 index 5f8c7cf24f..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/error-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/fluid-layout.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/fluid-layout.png deleted file mode 100644 index 3eff13e504..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/fluid-layout.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/maui.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/maui.png deleted file mode 100644 index 9d4e95c191..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/maui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/mobile-toolbars.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/mobile-toolbars.png deleted file mode 100644 index eb322626e3..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/mobile-toolbars.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-all-tenants.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-all-tenants.png deleted file mode 100644 index d850245181..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-all-tenants.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-tenants.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-tenants.png deleted file mode 100644 index 8801b5fb0b..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-tenants.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/page-feedback.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/page-feedback.png deleted file mode 100644 index d5afe74f10..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/page-feedback.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/password-complexity.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/password-complexity.png deleted file mode 100644 index d3b8555be1..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/password-complexity.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/settings.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/settings.png deleted file mode 100644 index 8934a9404e..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/settings.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-backend.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-backend.png deleted file mode 100644 index 9715f7ff52..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-backend.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-ui.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-ui.png deleted file mode 100644 index 82f09d6247..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-ui.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code.png deleted file mode 100644 index b059320b9d..0000000000 Binary files a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/POST.md b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/POST.md deleted file mode 100644 index 604d4c5c6c..0000000000 --- a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/POST.md +++ /dev/null @@ -1,98 +0,0 @@ -# Unleash the Power of ABP CMS Kit: A Dynamic Content Showcase - -We're excited to introduce to you [ABP](https://abp.io/)'s [CMS Kit Module](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Index) – a versatile module that empowers you to build your own dynamic content website with ease. In this introductory blog post, we'll first take a look at the **CMS Kit Module** and then we'll take you on a journey through our **CMS Kit Demo Application**, showcasing the incredible capabilities of this feature-rich module. - -## Overview of the CMS Kit Module - -At the heart of ABP's CMS Kit Module is a robust Content Management System (CMS) designed to simplify content creation and management. With the CMS Kit, you have the tools to build your own dynamic content website, complete with a range of features tailored to your specific needs. It provides core **building blocks** and fully working sub-systems to create your own website with the CMS features or use the building blocks in your websites for any purpose. - -### CMS Kit: The Building Blocks (a.k.a Features) - -The following features are currently available and ready to use: - -* **Blogging**: Create your blog and publish posts (with markdown / HTML support) -* **Dynamic Pages**: Create pages with dynamic URLs (with markdown / HTML support) -* **Dynamic Menu**: Manage your application’s main menu on the fly -* **Tagging**: Tag any kind of content, like a blog post -* **Comments**: Allow users to comment and discuss in your application -* **Reactions**: Allow users to react to your content using simple smileys -* **Rating**: Reusable component to rate other contents -* **Global Resources**: Dynamically add CSS / JavaScript to your pages or blog posts -* **Dynamic Widgets**: Build widgets and use them in dynamic content, like blog posts - -> For more information, please check the [CMS Kit Module documentation](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Index). In the documentation, you can see descriptions for each feature, learn how to install & configure the module, and much more... - -### CMS Kit Pro: The Building Blocks (a.k.a Features) - -CMS Kit Pro is a part of [ABP Commercial](https://commercial.abp.io/) and provides additional features. The following features are provided by the CMS Kit Pro Module: - -* **Contact Form**: Easily add a «contact us» form to your website -* **Newsletter**: Allow users to subscribe to your newsletter (with multiple categories) -* **URL Forwarding**: Create short URLs or redirect users to other pages (for example: [abp.io/dapr](https://abp.io/dapr)) -* **Poll**: Create quick polls for your users -* **Page Feedback**: Collect feedbacks from users for your content - -> For more information, check the [CMS Kit Pro Module documentation](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index). - -## Explore the CMS Kit Demo Application - -As the core ABP development team, we've created a sample application to showcase the incredible capabilities of the ABP's CMS Kit Module. You can explore the source code of the application on our [GitHub repository](https://github.com/abpframework/cms-kit-demo) to get a deeper understanding of how the CMS Kit works under the hood. While developing the application, we aimed to build a real-world application and use almost all of the CMS Kit Features. - -In the next sections, we will provide a detailed walkthrough of the application and highlight each CMS Kit feature that has been incorporated into it. - -### Dynamic Menu Creation with CMS Kit's Menu System - -One of the standing out features of the CMS Kit is its [Menu System](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Menus), which allows for the creation and dynamic ordering of application menu items. Say goodbye to static menus; with CMS Kit, you have the power to tailor your menu structure according to your evolving content needs. - -You can see the homepage of the application in the following figure: - -![](homepage.png) - -The application menu items in the navbar are **created & ordered dynamically** with the [CMS Kit's Menu System](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Menus): - -![](menu-admin-side.jpg) - -### Custom Implementations with Comment & Reaction Features - -Our demo application goes a step further by demonstrating custom implementations, such as an **image gallery**, seamlessly integrated with CMS Kit's [Comment](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments) & [Reaction](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Reactions) Features: - -| Gallery | Detail Page | -|------------------------ |-----------------------| -| ![](image-gallery.jpg) | ![](image-gallery-detail.jpg) | - -It's pretty easy to integrate CMS Kit Features such as Comments & Reactions into your existing pages. You can check the [source code of the application](https://github.com/abpframework/cms-kit-demo/blob/main/src/CmsKitDemo/Pages/Gallery/Detail.cshtml) and see how to integrate the features. - -### Robust Blogging Capabilities - -Blogging has never been easier! With the CMS Kit's [Blogging Feature](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Blogging), you can effortlessly manage your blog content, complete with [Ratings](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Ratings), [Comments](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments), [Tags](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Tags), and [Reactions](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Reactions) features as enabled: - -| Blog | Blog Post | -|------------------------ |-----------------------| -| ![](blogs.jpg) | ![](blog-detail.jpg) | - -You can enable/disable CMS Kit Features per blog on the admin side easily: - -![](cmskit-module-features.png) - -### Dynamic Pages with Style and Script Integration - -*Products* pages showcase the flexibility of the CMS Kit's [Pages Feature](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Pages), allowing for dynamic content creation, style customization, and script integration. Your website can now truly reflect your unique brand and content style. - -You can create pages with dynamic URLs on the admin side: - -![](pages-admin-side.jpg) - -After you have created the page, you can access it via `/{slug}` URL on the public-web side: - -![](products-abp-commercial.png) - -## What's Next? - -Please try the CMS Kit Module now and provide [feedback](https://github.com/abpframework/abp) to help us to build a more effective content management kit! - -## Resources - -* [CMS Kit Demo: Source Code](https://github.com/abpframework/cms-kit-demo) -* [cms-kit-demo.abp.io](https://cms-kit-demo.abp.io/) -* [CMS Kit Module documentation](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Index) -* [CMS Kit Pro Module documentation](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index) \ No newline at end of file diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg deleted file mode 100644 index 8ebe63afa7..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blog-detail.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg deleted file mode 100644 index 8e0d71909b..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/blogs.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png deleted file mode 100644 index 08d456b9b3..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/cmskit-module-features.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png deleted file mode 100644 index 33c0a61b6b..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/homepage.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg deleted file mode 100644 index 4179451e34..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery-detail.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg deleted file mode 100644 index 56d1b43a01..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/image-gallery.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg deleted file mode 100644 index 3a6366eb7b..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/menu-admin-side.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg deleted file mode 100644 index 9a2594c3b9..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/pages-admin-side.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png b/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png deleted file mode 100644 index 991b764ce6..0000000000 Binary files a/docs/en/Blog-Posts/2023-09-23-CMS-Kit-Demo/products-abp-commercial.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/POST.md b/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/POST.md deleted file mode 100644 index 9ce3e26967..0000000000 --- a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/POST.md +++ /dev/null @@ -1,80 +0,0 @@ -# ABP.IO Platform 7.4 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.4 versions have been released today. - -## What's New With Version 7.4? - -All the new features were already explained in detail in the [7.4 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7-4-RC-Has-Been-Published), so no need to go over them again. Check it out for more details. - -## Getting Started with 7.4 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 7.4 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First of all, install the ABP CLI or upgrade it to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update the existing CLI: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -There are breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v7.3: - -* [ABP Framework 7.3 to 7.4 Migration Guide](https://docs.abp.io/en/abp/7.4/Migration-Guides/Abp-7_4) - -## Community News - -### ABP Community Talks 2023.7: Build Your Content Management System with .NET - -We as the ABP team organized the [**ABP Community Talks 2023.7: Build Your Content Management System with .NET**](https://community.abp.io/events/build-your-own-cms-with-.net-a-first-look-at-abps-content-management-system-kit-3nfvm9ix) event to explore the depths of the CMS Kit Module and its real-world applications. The talk delved into the intricacies of the CMS Kit Module, providing valuable insights into its features and functionalities. Attendees had the opportunity to witness the module in action through live demonstrations and interactive Q&A sessions. - -For those who missed the live session, you can catch up on all the enriching discussions and demonstrations by watching the record below 👇: - - - -### BASTA! Mainz 2023 - -![](basta-mainz.png) - -BASTA! Mainz 2023 has wrapped up, and what an extraordinary journey it has been! We have shared our impressions, highlights, and the incredible impact it had on the tech community in Germany and beyond in a blog post, which you can find at [https://blog.abp.io/abp/BASTA-Mainz-2023-What-a-Blast-in-Germany](https://blog.abp.io/abp/BASTA-Mainz-2023-What-a-Blast-in-Germany). - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [Moving Background Job Execution To A Separate Application](https://community.abp.io/posts/moving-background-job-execution-to-a-separate-application-my9cgo9a) by [liangshiwei](https://github.com/RealLowis). -* [Cascading Option Loading with Extensions System in ABP Angular](https://community.abp.io/posts/cascading-option-loading-with-extensions-system-in-abp-angular-gcxgp0v9) by [Masum Ulu](https://twitter.com/masumulu). -* [How to use domain-based tenant resolver in ABP with Angular and OpenIddict](https://community.abp.io/posts/how-to-use-domainbased-tenant-resolver-in-abp-with-angular-and-openiddict-v9y8da7v) by [Mahmut Gündoğdu](https://twitter.com/MahmutGundogdu). - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -## About the Next Version - -The next feature version will be 8.0. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/abp-community-talk.png b/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/abp-community-talk.png deleted file mode 100644 index d04ce6f245..0000000000 Binary files a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/abp-community-talk.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/basta-mainz.png b/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/basta-mainz.png deleted file mode 100644 index a84abea379..0000000000 Binary files a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/basta-mainz.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/cover-image.png deleted file mode 100644 index d3152c7f4b..0000000000 Binary files a/docs/en/Blog-Posts/2023-10-09 v7_4_Release_Stable/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/POST.md b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/POST.md deleted file mode 100644 index 5fcffd1092..0000000000 --- a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/POST.md +++ /dev/null @@ -1,223 +0,0 @@ -# ABP.IO Platform 8.0 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **8.0 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -Try this version and provide feedback for a more stable version of ABP v8.0! Thanks to all of you. - -## Get Started with the 8.0 RC - -Follow the steps below to try version 8.0.0 RC today: - -1) **Upgrade** the ABP CLI to version `8.0.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 8.0.0-rc.1 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 8.0.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. - -You can use any IDE that supports .NET 8.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). - -## Migration Guides - -There are a few breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v7.x or earlier: - -* [ABP Framework 7.x to 8.0 Migration Guide](https://docs.abp.io/en/abp/8.0/Migration-Guides/Abp-8_0) -* [ABP Commercial 7.x to 8.0 Migration Guide](https://docs.abp.io/en/commercial/8.0/migration-guides/v8_0) - -## What's New with ABP Framework 8.0? - -In this section, I will introduce some major features released in this version. -Here is a brief list of titles explained in the next sections: - -* Upgraded to .NET 8.0 -* Upgraded to Angular 17 -* Dynamic Claims -* CDN Support for Bundling & Minification System -* Read-Only Repositories -* Account Module: Set Username After Social/External Login -* Other News... - -### Upgraded to .NET 8.0 - -We've upgraded the ABP Framework to .NET 8.0, so you need to move your solutions to .NET 8.0 if you want to use ABP 8.0. You can check [Microsoft’s Migrate from ASP.NET Core 7.0 to 8.0 documentation](https://learn.microsoft.com/en-us/aspnet/core/migration/70-80), to see how to update an existing ASP.NET Core 7.0 project to ASP.NET Core 8.0. - -### Upgraded to Angular 17 - -Angular 17 [was released on November 8](https://blog.angular.io/introducing-angular-v17-4d7033312e4b) and ABP Framework & ABP Commercial startup templates were immediately migrated to **Angular 17**! - -So, when you create a new solution with the Angular UI, you will take advantage of the new Angular with the new cutting-edge features and enhancements right from the start! - -### Dynamic Claims - -The **Dynamic Claims** feature is used to dynamically generate claims for the user in each request. It's used to automatically and dynamically override the configured claim values in the client's authentication token/cookie by the latest user claims. - -In the prior versions, whenever a user changed their email address or confirmed their own email address, or any other information related to the user (and if it's in the claims), he/she would need to logout and then login to refresh its claims. The new **Dynamic Claims** feature overcomes this problem and allows to **always get the latest user claims**. - -This feature is disabled by default and you can enable it easily for your existing MVC applications by following the [Dynamic Claims documentation](https://docs.abp.io/en/abp/8.0/Dynamic-Claims). For the other UI options (Angular & Blazor UIs), you don't need to enable this feature, since they obtain claims ftom a configuration endpoint. - -> **Note**: Beginning from the v8.0, all the startup templates are pre-configured and the **Dynamic Claims** feature is enabled by default. - -### CDN Support for Bundling & Minification System - -In this version, ABP Framework's [Bundling & Minification System](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification) provides CDN support for MVC / Razor Pages UI. The bundling system automatically recognizes the external/CDN files and places them as link/script tags on the page along with the bundled CSS/JSS files. - -> Read the documentation for more info: https://docs.abp.io/en/abp/8.0/UI/AspNetCore/Bundling-Minification - -### Read-Only Repositories - -ABP Framework provides read-only repository interfaces (`IReadOnlyRepository<>` or `IReadOnlyBasicRepository<>`) to explicitly indicate that your purpose is to query data, but not change it. It uses [EF Core's No-Tracking Feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries) behind the scenes, which means the entities returned from the repository will not be tracked by the EF Core's [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/) and thanks to that you get significant performance gains. - -```csharp -public class MyService -{ - private readonly IReadOnlyRepository _bookRepository; - - public async Task MyMethod() - { - var books = await _bookRepository.GetListAsync(); //change tracking not involved - - //... - } -} -``` - -> In addition to the read-only repository interfaces, ABP Framework introduces the `IRepository.DisableTracking()` and `IRepository.EnableTracking()` extension methods to allow developers to disable/enable entity tracking by these methods manually. If you don't want to use the read-only repositories, you can use these methods to enable or disable the change tracker controlled. Read the documentation to learn more: [https://docs.abp.io/en/abp/8.0/Repositories#enabling-disabling-the-change-tracking](https://docs.abp.io/en/abp/8.0/Repositories#enabling-disabling-the-change-tracking) - -### Account Module: Set Username After Social/External Login - -Prior to this version, when you registered with your social accounts for the first time, your email address was becoming your username and it was shown everywhere in the application. Therefore, you would need to update your username later on and this is not a good user experience. - -Thus, in this version, we have enhanced this flow, and now, when you register as an external user for the first time, a username and email address are shown you in a form for you to revise and update if you want, before logging into the application. Thanks to that, after the social registration you would not need to update your username and email address. This is also good at the point of GDPR regulations because your email address will not be shown as a username and will not exposed. - -![](account-module-register.png) - -### Other News - -* LDAP over SSL (LDAPS) setting has been added and recommended to establish a secure connection. See [#17865](https://github.com/abpframework/abp/pull/17865) for more information. -* Object Mapping Enhancements (supports mapping collection of objects for custom object mappers). -* Email Sending Improvements (sending attachments with `IEmailSender.QueueAsync()` method). - -## What's New with ABP Commercial 8.0? - -We've also worked on ABP Commercial to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 8.0. - -### Suite: Generating Master/Detail Relationship - -In this version, we have introduced the **Master/Detail Relationship** support in Suite. The Master-Detail (or Master-Child) relationship refers to a hierarchical connection between two entities, where one entity (the master or parent entity) influences or controls the behavior or properties of another element (the child entity) relationship. The relationship between **Order - Order Lines** can be considered as an example of a master-detail relationship. - -![](suite-master-child-datagrid.png) - -ABP Suite allows you to create a master-detail relationship with a few clicks. It generates the necessary code for the master and detail tables, including the foreign key relationship between the two tables. - -To establish a master-detail relationship, you need to apply the following two steps: - -1-) Create the master entity, -2-) Create a child entity and associate it with a master entity. - -That's it! ABP Suite will be generating the entities, making the related configurations, establishing database relations (including the foreign key relationship), generating the UI for the master entity (with child-grids for child entities), and so on... - -It’s already documented and you can read the documentation at [https://docs.abp.io/en/commercial/8.0/abp-suite/creating-master-detail-relationship](https://docs.abp.io/en/commercial/8.0/abp-suite/creating-master-detail-relationship). - -#### Known Issues - -* After you generate CRUD pages via Suite for the Angular UI, you should start the backend project and run the `abp generate-proxy -t ng` command in the root directory of the Angular application manually. It will be automatically done with the next version, so you will not need to run the command manually in further versions. - -### Get Profile Picture From Social/External Logins - -A user's profile picture would be blank when they first register for an application using a social account through an external authentication provider like Google or Facebook because it hasn't been configured yet. The user must update their profile photo after logging into the application. - - -In order to save the user from having to change their profile picture after logging in for the first time, we have improved this behavior in this version and are now attempting to retrieve the user's profile picture from external authentication providers (like Google) and set it as their profile picture. Later on, if desired, he or she might modify the profile image. - -### Switch Ocelot to YARP for the API Gateway - -Until this version, ABP Commercial was using the [Ocelot](https://github.com/ThreeMammals/Ocelot) for the API Gateway, in the [Microservice Startup Template](https://docs.abp.io/en/commercial/latest/startup-templates/microservice/index). Since the **Ocelot** library is not actively maintained, we have searched for an alternative and decided to switch from Ocelot to [YARP](https://github.com/microsoft/reverse-proxy) for the API Gateway. YARP is maintained by Microsoft and is actively being developed and seems a better alternative than Ocelot and provides the same feature stack and even more. - -You can read the [Migrating to YARP](https://docs.abp.io/en/commercial/8.0/migration-guides/migrating-to-yarp) documentation for migrating your existing microservice application's API Gateway from [Ocelot](https://github.com/ThreeMammals/Ocelot) to [YARP](https://github.com/microsoft/reverse-proxy). - -> We have made the all related changes in the Microservice Startup Template, and also updated the documentation, which you can read [here](https://docs.abp.io/en/commercial/8.0/startup-templates/microservice/gateways). - -### Password Complexity Indicators (MVC & Blazor UIs) - -In v7.4, we have introduced the [Password Complexity Indicators for Angular UI](https://docs.abp.io/en/commercial/7.4/ui/angular/password-complexity-indicator-component) and with this version, we have implemented it for the MVC & Blazor UIs as well. You can use this feature to dynamically evaluate and rate the strength of user-generated passwords, providing real-time feedback to users as they create or update their passwords. - -![](password-complexity-indicators.png) - -### Read-Only View for Users Page - -In your application, you may want to grant permission to a specific group or people to read-only view the users of your application to be able to do some actions. For example, you may want to marketing team to see the users to organize campaigns for the customers, or make controls. In this case, you can grant default permissions for these groups, however, they could not see the details of a user, because in the current design, if the edit permission is not granted you can't see the detailed info for a user. - -![](identity-users.gif) - -In this version, we have added the read-only view action to the user's page. This allows you to only grant the default view permission to the specific users and allow them to view user information as read-only and not allow them to change or modify it. - -### Export & Import Users as Excel / CSV - -With v8.0, now it's possible to import and export user records in Excel and CSV formats. You can import external users, or import users from Excel or CSV files and also you can export users to Excel or CSV files: - -![](users-page.png) - -## Community News - -### Highlights from .NET 8.0 - -Our team has closely followed the ASP.NET Core and Entity Framework Core 8.0 releases, read Microsoft's guides and documentation, and adapted the changes to our ABP.IO Platform. We are proud to say that we've shipped the ABP 8.0 RC.1 based on .NET 8.0 just after Microsoft's .NET 8.0 release. - -In addition to the ABP's .NET 8.0 upgrade, our team has created 13 great articles to highlight the important features coming with ASP.NET Core 8.0 and Entity Framework Core 8.0. - -> You can read [this post](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-8-0) to see the list of all articles. - -### New ABP Community Articles - -In addition to [the 13 articles to highlight .NET 8.0 features written by our team](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-8-0), here are some of the recent posts added to the [ABP Community](https://community.abp.io/): - -* [Upgrade Your Existing Projects to .NET 8 & ABP 8.0](https://community.abp.io/posts/upgrade-your-existing-projects-to-.net-8-abp-8.0-x0n7hiqr) by [Engincan Veske](https://github.com/EngincanV) -* [How to Upload and Download Files in the ABP Framework using Angular](https://community.abp.io/posts/how-to-upload-and-download-files-in-the-abp-framework-using-angular-que8cdr8) by [Mahmut Gündoğdu](https://github.com/mahmut-gundogdu) -* New **ABP Framework Essentials** Videos by [Hamza Albreem](https://github.com/braim23): - * [ABP Essentials - Interception](https://community.abp.io/videos/abp-essentials-interception-ath78xhw) - * [ABP Essentials - Virtual File System](https://community.abp.io/videos/abp-essentials-virtual-file-system-hpgr2j72) - * [ABP Framework Essentials - Localization](https://community.abp.io/videos/abp-framework-essentials-localization-7taieh68) - * [ABP Framework Essentials - Dependency Injection](https://community.abp.io/videos/abp-framework-essentials-dependency-injection-q241mfrf) - * See the playlist for other videos of this series: https://www.youtube.com/playlist?list=PLsNclT2aHJcNupH2wz83y7htugpLoUZ_B - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -### We were in the .NET Conf 2023 - -Microsoft has released .NET 8.0 and celebrated it with a 3-day international online conference. The core team members of ABP Framework, [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) and [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) gave speeches at the .NET Conf 2023. - -[Alper Ebiçoğlu](https://twitter.com/alperebicoglu)'s topic was "Building Multi-Tenant ASP.NET Core Applications and ABP Framework" and in this talk, he talked about what's SaaS development, what are its pros and challenges and multi-tenant development with the open-source ABP Framework: - - - -On the other hand, [Enis Necipoğlu](https://twitter.com/EnisNecipoglu)'s topic was "Reactive programming with .NET MAUI" and he talked about applying reactive programming in .NET MAUI with MVVM and ReactiveUI: - - - -### ABP Community Talks 2023.8: What’s coming with .NET 8.0 & ABP v8.0 - -![](community-talk-2023-8.png) - -In this episode of ABP Community Talks, 2023.8; [Steve Sanderson](https://twitter.com/stevensanderson) will be our guest speaker and we'll talk about .NET 8.0 and ABP 8.0 with the ABP Core Team. We will dive into the features that came with .NET 8.0, how they are implemented in ABP 8.0, and the highlights in the .NET Conf 2023 with [Halil İbrahim Kalkan](https://github.com/hikalkan), [Alper Ebicoglu](https://github.com/ebicoglu), [Engincan Veske](https://github.com/EngincanV), [Berkan Sasmaz](https://github.com/berkansasmaz) and [Bige Besikci Yaman](https://github.com/bigebesikci). - -## Conclusion - -This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/8.0/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v8.0 RC and provide feedback to help us release a more stable version. - -Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/account-module-register.png b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/account-module-register.png deleted file mode 100644 index d58c0c52a9..0000000000 Binary files a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/account-module-register.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/community-talk-2023-8.png b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/community-talk-2023-8.png deleted file mode 100644 index e8e44f42bb..0000000000 Binary files a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/community-talk-2023-8.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/cover-image.png b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/cover-image.png deleted file mode 100644 index 0bf0e0f938..0000000000 Binary files a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/identity-users.gif b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/identity-users.gif deleted file mode 100644 index 92dad96550..0000000000 Binary files a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/identity-users.gif and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/password-complexity-indicators.png b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/password-complexity-indicators.png deleted file mode 100644 index ba1d5f5a97..0000000000 Binary files a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/password-complexity-indicators.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/suite-master-child-datagrid.png b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/suite-master-child-datagrid.png deleted file mode 100644 index c774e78671..0000000000 Binary files a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/suite-master-child-datagrid.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/users-page.png b/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/users-page.png deleted file mode 100644 index d5c3b4d68a..0000000000 Binary files a/docs/en/Blog-Posts/2023-11-15 v8_0_Preview/users-page.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/POST.md b/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/POST.md deleted file mode 100644 index 2f40b61851..0000000000 --- a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/POST.md +++ /dev/null @@ -1,84 +0,0 @@ -# ABP.IO Platform 8.0 Has Been Released Based on .NET 8.0 - -Today, [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 8.0 versions have been released based on [.NET 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). - -## What's New With Version 8.0? - -All the new features were explained in detail in the [8.0 RC Announcement Post](https://blog.abp.io/abp/announcing-abp-8-0-release-candidate), so there is no need to review them again. You can check it out for more details. - -## Getting Started with 8.0 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 8.0 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First, install the ABP CLI or upgrade it to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update the existing CLI: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -There are breaking changes in this version that may affect your application. -Please see the following migration documents if you are upgrading from v7.x or earlier: - -* [ABP Framework 7.x to 8.0 Migration Guide](https://docs.abp.io/en/abp/8.0/Migration-Guides/Abp-8_0) -* [ABP Commercial 7.x to 8.0 Migration Guide](https://docs.abp.io/en/commercial/8.0/migration-guides/v8_0) - -## Community News - -### We were at China .NET Conf 2023 - -China's most influential .NET event officially kicked off on December 16, 2023. The conference has invited 30+ technical experts from various fields to share the new features of .NET 8, full-stack Blazor, AI, .NET MAUI, and more... - -![](abp-china-team.jpeg) - -As one of the community partners of .NET Conf China 2023, our ABP.IO China team was at the event. At the event, we showed developers the latest news and related updates on ABP.IO. - -Through this event, we gained a lot and felt the enthusiasm and support of the developers community for ABP.IO. If you want to learn more, we have shared our impressions and takeaways in a blog post, which you can find at [https://blog.abp.io/abp/ABP-at-China-NET-Conf-2023](https://blog.abp.io/abp/ABP-at-China-NET-Conf-2023). - -### ABP Commercial Won 5 Recognitions from Gartner - -![](awards.jpeg) - -2023 was the year for ABP.IO to strive with the community. On top of last year's [Software Advice's Front Runner of Application Development in 2022](https://blog.abp.io/abp/abpcommercial-2022-front-runner-in-app-development-category) and [GetApp's Application Development Category Leader in 2022](https://blog.abp.io/abp/abpcommercial-2022-category-leader-in-app-development-category), we won **5 awards** this year from **Gartner**! - -> If you are interested in these awards and want to learn more, you can check out our [blog post](https://blog.abp.io/abp/ABP-Commercial-Won-5-Recognitions-from-Gartner-in-2023)! - -### New ABP Community Posts - -There are exciting articles contributed by the ABP community, as always. I will highlight some of them here: - -* [Performance Optimization of .NET-based application](https://community.abp.io/posts/performance-optimization-of-.netbased-and-also-abpbased-application-pmdwhwxc) by [Leon Košak](https://github.com/leonkosak) -* [Video: ABP Framework Consuming HTTP APIs from a .NET Client](https://community.abp.io/videos/abp-framework-consuming-http-apis-from-a-.net-client-uzul9og4) by [Hamza Albreem](https://github.com/braim23) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -## About the Next Version - -The next feature version will be 8.1. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/abp-china-team.jpeg b/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/abp-china-team.jpeg deleted file mode 100644 index a24bf3cfd5..0000000000 Binary files a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/abp-china-team.jpeg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/awards.jpeg b/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/awards.jpeg deleted file mode 100644 index 42dc55846f..0000000000 Binary files a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/awards.jpeg and /dev/null differ diff --git a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/cover-image.png deleted file mode 100644 index 0bf0e0f938..0000000000 Binary files a/docs/en/Blog-Posts/2023-12-20 v8_0_Release_Stable/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/1.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/1.jpg deleted file mode 100644 index dbdf7c96b0..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/1.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/2.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/2.jpg deleted file mode 100644 index a25fabcc98..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/2.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/3.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/3.jpg deleted file mode 100644 index 660d086cbc..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/3.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/4.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/4.jpg deleted file mode 100644 index 7babc9ab5b..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/4.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/5.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/5.jpg deleted file mode 100644 index b1ab47d590..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/5.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/51.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/51.jpg deleted file mode 100644 index 27aad1a716..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/51.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/6.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/6.jpg deleted file mode 100644 index eda7ac4735..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/6.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/8.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/8.jpg deleted file mode 100644 index 76842cf0fa..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/8.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/9.jpg b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/9.jpg deleted file mode 100644 index 5be2c5d6dc..0000000000 Binary files a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/9.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/POST.md b/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/POST.md deleted file mode 100644 index 3fcd4d0da2..0000000000 --- a/docs/en/Blog-Posts/2024-01-14-china-shenzhen-dotnet-conf/POST.md +++ /dev/null @@ -1,20 +0,0 @@ -# 2024 First Community Event. - -The first .NET community event in 2024 was successfully held in Shenzhen on January 14, 2024. - -This event is co-organized by **Microsoft MVP China Team**, **Microsoft Reactor**, **China .NET Community** and **Shenzhen .NET Club**. - -**ABP.IO** continues to strongly support the community, and we have prepared exquisite gifts for participants. - -The event includes four wonderful technical lectures to reveal big data and AI's potential opportunities and innovations. It is a transfer of knowledge and a platform for communication and cooperation among technology enthusiasts. -![](1.jpg) -![](2.jpg) -![](3.jpg) -![](4.jpg) -![](51.jpg) -![](5.jpg) -![](6.jpg) -![](8.jpg) -![](9.jpg) - -**See you at the next community event!** diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md deleted file mode 100644 index 24f55474ad..0000000000 --- a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md +++ /dev/null @@ -1,239 +0,0 @@ -# ABP.IO Platform 8.1 RC Has Been Released - -Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **8.1 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. - -Try this version and provide feedback for a more stable version of ABP v8.1! Thanks to all of you. - -## Get Started with the 8.1 RC - -Follow the steps below to try version 8.1.0 RC today: - -1) **Upgrade** the ABP CLI to version `8.1.0-rc.1` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 8.1.0-rc.1 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 8.1.0-rc.1 -```` - -2) Create a **new application** with the `--preview` option: - -````bash -abp new BookStore --preview -```` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. - -> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. - -You can use any IDE that supports .NET 8.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). - -## Migration Guides - -There are a few breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v8.x or earlier: - -* [ABP Framework 8.0 to 8.1 Migration Guide](https://docs.abp.io/en/abp/8.1/Migration-Guides/Abp-8_1) -* [ABP Commercial 8.0 to 8.1 Migration Guide](https://docs.abp.io/en/commercial/8.1/migration-guides/v8_1) - -## What's New with ABP Framework 8.1? - -In this section, I will introduce some major features released in this version. -Here is a brief list of titles explained in the next sections: - -* Introducing the `ExposeKeyedServiceAttribute` -* Custom Menu Component Support for MVC UI -* Introducing the `DisableAbpFeaturesAttribute` - -### Introducing the `ExposeKeyedServiceAttribute` - -[Keyed dependency injection (DI) services](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#keyed-services) were added to the built-in DI container as a new feature with .NET 8.0. This is an important feature, which allows for registering and retrieving DI services using keys/names. - -In this version, we have introduced the `ExposeKeyedServiceAttribute` to allow you to automatically register keyed services by conventions: - -```csharp -[ExposeKeyedService("taxCalculator")] -[ExposeKeyedService("calculator")] -public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency -{ -} -``` - -In the example above, the `TaxCalculator` class exposes the `ITaxCalculator` interface with the key `taxCalculator` and the `ICalculator` interface with the key `calculator`. - -Thanks to that, then you can use the [`FromKeyedServicesAttribute`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.fromkeyedservicesattribute?view=dotnet-plat-ext-8.0) to resolve a certain keyed service in the constructor: - -```csharp -public class MyClass -{ - //... - public MyClass([FromKeyedServices("taxCalculator")] ITaxCalculator taxCalculator) - { - TaxCalculator = taxCalculator; - } -} -``` - -> Notice that the `ExposeKeyedServiceAttribute` only exposes the keyed services. So, you can not inject the `ITaxCalculator` or `ICalculator` interfaces in your application without using the `FromKeyedServicesAttribute` as shown in the example above. - -> If you want to learn more about the keyed dependency injection services, please refer to [Microsoft's documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#keyed-services) and [ABP Framework's Dependency Injection document](https://docs.abp.io/en/abp/8.1/Dependency-Injection#exposekeyedservice-attribute). - -### Custom Menu Component Support for MVC UI - -In this version, we have introduced the **custom menu component support**, which allows you to use a custom component for a certain menu item. - -You can use the `UseComponent` extension method while defining menu items to use your custom component for the related menu item: - -```csharp -context.Menu.Items.Add( - new ApplicationMenuItem("Custom.1", "My Custom Menu", "#") - .UseComponent(typeof(MyMenuComponent))); -``` - -Then, for the related menu item, your custom component will be rendered on the UI. - -### Introducing the `DisableAbpFeaturesAttribute` - -In this version, we have introduced the `DisableAbpFeaturesAttribute` to allow you to disable interceptors, middlewares, and MVC filters for a specific controller. - -For example, you may want to disable interceptors for a certain controller, but you may also don't want to disable middlewares and mvc filters, in that case, you can use the `DisableAbpFeaturesAttribute` as follows: - -```csharp -[Route("api/my-endpoint")] -[DisableAbpFeatures(DisableInterceptors = true, DisableMiddleware = false, DisableMvcFilters = false)] -public class MyController : AbpController -{ - -} -``` - -This can be useful if you have some APIs that are used frequently but you don't need all the features of ABP Framework. - -> **Note:** If you want to disable all interceptors, middlewares, and filters for a certain controller, then you can use the `[DisableAbpFeatures]` without the need to specify the parameters, they are disabled by default. - -## What's New with ABP Commercial 8.1? - -We've also worked on ABP Commercial to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 8.1. - -### Suite: New Features - -In this version, we mostly worked on ABP Suite and implemented some wanted features, such as *bulk delete*, *filterable properties*, *customizable page title*, *allowing establishing relationships with installed ABP modules' entities* and *supporting the `BasicAggregateRoot` as the base class*. - -#### Bulk Delete - -From this version on, ABP Suite allows you to perform bulk deletion of records based on specified criteria. - -![](bulk-delete.png) - -To enable *bulk delete support*, you should enable the *Bulk delete* option in the CRUD page generation page, along with the *Create user interface* option (they are enabled by default). When you enable the *bulk delete support*, you will see the checkboxes for each line in the datatable: - -![](bulk-delete-datatable.png) - -By selecting these checkboxes, you can delete all records in the current datatable (10 records by default), or delete all records in the database (by selecting *select all xx items* selection), or delete any records you want by selecting them one by one. - -> **Note:** This feature has already been implemented for MVC & Blazor UIs, but not implemented for Angular UI yet. We aim to implement it for Angular UI with *v8.1.0-rc.2*. - -#### Filterable Properties - -ABP Suite has been generating advanced filters for entities for a while and before this version, if you enabled the *create user interface* option, then all of the properties that you specified were included in the advanced filter section automatically. - -In this version, we have introduced the **filterable properties** feature to allow you to select which properties should be filterable and which are not: - -![](filterable-properties.png) - -Now, you have full control to select which properties should be included as filter inputs and shown on the advanced filters section on your generated pages. - -In other words, once, you select a property as *not filterable*, then the property will not be included in the filter inputs and in the advanced filter section. Also, if there are not any filterable properties for an entity, then the advanced filters section will not be generated. - -#### Customizable Page Title - -In this version on, ABP Suite allows you to specify the page title for the current entity. - -![](page-title.png) - -You just need to specify the *page title* on the CRUD page generation page for a specific entity and then it will be used as a localization key in the application (and also will be localized - for example, if you specify it as 'MyPageTitle', then it will be localized in English as 'My Page Title'), so you can localize it for different languages later on. - -#### Allowing Establishing Relationships with Installed ABP Modules' Entities - -In this version, ABP Suite allows you to establish one-to-many relationship with pre-installed ABP Modules. You can add any entity from pre-installed ABP modules as a navigation property, by checking the **Include entities from ABP modules** checkbox in the navigation property model and choosing the related module entity as in the following figure: - -![](suite-include-entities-from-abp-modules.png) - -In the example above, the `IdentityUser` entity is selected as the navigation property but you can also choose any entity from the installed ABP modules in the navigation property model. - -> **Note:** Ensure that your solution is built properly before establishing relationship between your own entity and a module entity because ABP Suite scans assemblies and finds which ABP modules you are using and lists their entities in the navigation property model if you have checked the **Include entities from ABP modules** checkbox. - -#### Support `BasicAggregateRoot` Base Class - -In this version on, ABP Suite allows you to choose the `BasicAggregateRoot` as the base class while generating an entity: - -![](basic-aggregate-root.png) - -> You can choose the [BasicAggregateRoot](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs) if you want to create an aggregate root without the `IHasExtraProperties` and `IHasConcurrencyStamp` interfaces implemented (extra properties & concurrency check). - -### ABP Studio v0.6.5 Has Been Released! - -We have just released v0.6.5 for ABP Studio and with this version, we fixed many minor bugs reported by you, started supporting running multiple ABP Studio instances, and added important features, such as adding a public website to the new microservice template, and so on... - -In addition to all of this, we continue to enrich ABP Studio's documentation as always and you can read them at [https://docs.abp.io/en/commercial/latest/studio/overview](https://docs.abp.io/en/commercial/latest/studio/overview). - -Please try ABP Studio v0.6.5 and [provide feedback](https://support.abp.io/QA/Questions/6416/ABP-Studio-Bugs--Issues) to help us release more stable versions. Thanks in advance! - -## Community News - -### .NET Conf China 2023 Watch Party - -![](dotnet-conf-china-2023-watch-party.jpg) - -ABP.IO was thrilled to sponsor the first .NET Community event in 2024 held in Shenzen on January 14, 2024. - -The event included four wonderful technical lectures to reveal big data and AI's potential opportunities and innovations. It was a transfer of knowledge and a platform for communication and cooperation among technology enthusiasts and we are happy to be attended. - -> If you want to learn more about the .NET Conf China 2023 Watch Party event, please check [the blog post](https://blog.abp.io/abp/2024-First-Community-Event). - -### Volosoft Attended NDC London 2024 - -![](ndc-london-2024.png) - -Core team members of the ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan), [Alper Ebicoglu](https://twitter.com/alperebicoglu), [Engincan Veske](https://twitter.com/EngincanVeske), and [Bige Beşikci Yaman](https://twitter.com/bigedediki) attended [NDC London 2024](https://ndclondon.com/) from the 31st of January to the 2nd of February. - -It was the 5th time in a row we were a proud sponsor of NDC London. It, now, basically feels like home spending 3 days in Queen Elizabeth Centre II with NDC London for the [ABP.IO](https://abp.io/) team to be there. - -These 3 days with the team were all about chatting and having fun with amazing attendees and speakers. We met with talented and passionate software developers and introduced the [open source ABP Framework](https://github.com/abpframework/abp) - web application framework built on ASP.NET Core and [ABP Commercial](https://commercial.abp.io/) - the complete web application development platform built on open source ABP Framework - to them. - -> We shared our insights and key highlights from the NDC London 2024 event, which you can find at [https://blog.abp.io/abp/NDC-London-2024-ABP.IO-Key-Highlights](https://blog.abp.io/abp/NDC-London-2024-ABP.IO-Key-Highlights). - -### New ABP Community Articles - -There are exciting articles contributed by the ABP community as always. I will highlight some of them here: - -* [Every Programmer Should Know #2: Optimistic Concurrency Control](https://community.abp.io/posts/every-programmer-should-know-2-optimistic-concurrency-control-sms9xs9n) by [Berkan Sasmaz](https://github.com/berkansasmaz) -* [Global Error Handling in Angular](https://community.abp.io/posts/global-error-handling-in-angular-gjcb2f1e) by [Sinan Öztürk](https://github.com/Sinan997) -* [ABP Framework goes Azure](https://community.abp.io/posts/abp-framework-goes-azure-ub4u5ax5) by [Bart Van Hoey](https://github.com/bartvanhoey) -* [ABP Supports .NET8](https://community.abp.io/posts/abp-supports-.net8-e4gve6ih) by [Alper Ebicoglu](https://community.abp.io/members/alper) -* [Engincan Veske](https://github.com/EngincanV) has created **three** new community articles: - * [ABP Now Supports Keyed Services!](https://community.abp.io/posts/abp-now-supports-keyed-services-6k92wz7h) - * [Mutation Testing in C# with Stryker](https://community.abp.io/posts/mutation-testing-in-c-with-stryker-tp6u599h) - * [ABP Suite: Best CRUD Page Generation Tool for .NET](https://community.abp.io/posts/abp-suite-best-crud-page-generation-tool-for-.net-cmm9xs3n) -* [Ahmed Tarek](https://github.com/AhmedTarekHasan) has created **nine** new community articles: - * [📑 Cover IO Based Apps With Unit Tests in .NET C# 🧪](https://community.abp.io/posts/-cover-io-based-apps-with-unit-tests-in-.net-c--zp6kip2r) - * [Better Enhanced Repository Pattern Implementation in .NET C#](https://community.abp.io/posts/better-enhanced-repository-pattern-implementation-in-.net-c-hpkbxr3l) - * [When Not To Use DI, IoC, and IoC Containers in .NET C#](https://community.abp.io/posts/when-not-to-use-di-ioc-and-ioc-containers-in-.net-c-n769hq8u) - * [⏰ Best Practice for Using Timers in .NET C# ⏳](https://community.abp.io/posts/-best-practice-for-using-timers-in-.net-c--3cqvew5o) - * [How to Fully Cover .NET C# Console Application With Unit Tests](https://community.abp.io/posts/how-to-fully-cover-.net-c-console-application-with-unit-tests-3h248yhe) - * [Web Scraping in .NET C#](https://community.abp.io/posts/web-scraping-in-.net-c-6pkp1abi) - * [Step by step guide to develop a Fluent API from scratch in .NET C# using the Builder Design Pattern](https://community.abp.io/posts/step-by-step-guide-to-develop-a-fluent-api-from-scratch-in-.net-c-using-the-builder-design-pattern-sbww0vky) - * [A Best Practice for Designing Interfaces in .NET C#](https://community.abp.io/posts/a-best-practice-for-designing-interfaces-in-.net-c-9xqc4h8d) - * [Invariance, Covariance, and Contravariance in .NET C#](https://community.abp.io/posts/invariance-covariance-and-contravariance-in-.net-c-9blmuhme) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/submit) to the ABP Community. - -## Conclusion - -This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/8.1/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v8.1 RC and provide feedback to help us release a more stable version. - -Thanks for being a part of this community! \ No newline at end of file diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/basic-aggregate-root.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/basic-aggregate-root.png deleted file mode 100644 index c1c8e6ca8d..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/basic-aggregate-root.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/bulk-delete-datatable.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/bulk-delete-datatable.png deleted file mode 100644 index 92b767515a..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/bulk-delete-datatable.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/bulk-delete.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/bulk-delete.png deleted file mode 100644 index 8281ecb8d6..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/bulk-delete.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/cover-image.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/cover-image.png deleted file mode 100644 index bb7094ece5..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/cover-image.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/dotnet-conf-china-2023-watch-party.jpg b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/dotnet-conf-china-2023-watch-party.jpg deleted file mode 100644 index c8a78a0f93..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/dotnet-conf-china-2023-watch-party.jpg and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/filterable-properties.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/filterable-properties.png deleted file mode 100644 index 6c45f9cb71..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/filterable-properties.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/ndc-london-2024.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/ndc-london-2024.png deleted file mode 100644 index cb03ca7462..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/ndc-london-2024.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/page-title.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/page-title.png deleted file mode 100644 index 23b1dc8513..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/page-title.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/suite-include-entities-from-abp-modules.png b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/suite-include-entities-from-abp-modules.png deleted file mode 100644 index 6bb0920800..0000000000 Binary files a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/suite-include-entities-from-abp-modules.png and /dev/null differ diff --git a/docs/en/Blog-Posts/2024-04-04 v8_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2024-04-04 v8_1_Release_Stable/POST.md deleted file mode 100644 index f7fcb90683..0000000000 --- a/docs/en/Blog-Posts/2024-04-04 v8_1_Release_Stable/POST.md +++ /dev/null @@ -1,80 +0,0 @@ -# ABP.IO Platform 8.1 Final Has Been Released! - -[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 8.1 versions have been released today. - -## What's New With Version 8.1? - -All the new features were explained in detail in the [8.1 RC Announcement Post](https://blog.abp.io/abp/announcing-abp-8-1-release-candidate), so there is no need to review them again. You can check it out for more details. - -## Getting Started with 8.1 - -### Creating New Solutions - -You can create a new solution with the ABP Framework version 8.1 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started). - -> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more. - -### How to Upgrade an Existing Solution - -#### Install/Update the ABP CLI - -First, install the ABP CLI or upgrade it to the latest version. - -If you haven't installed it yet: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -To update the existing CLI: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -#### Upgrading Existing Solutions with the ABP Update Command - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: - -```bash -abp update -``` - -Run this command in the root folder of your solution. - -## Migration Guides - -There are a few breaking changes in this version that may affect your application. -Please see the following migration documents, if you are upgrading from v8.x or earlier: - -* [ABP Framework 8.0 to 8.1 Migration Guide](https://docs.abp.io/en/abp/8.1/Migration-Guides/Abp-8_1) -* [ABP Commercial 8.0 to 8.1 Migration Guide](https://docs.abp.io/en/commercial/8.1/migration-guides/v8_1) - -## Community News - -### New ABP Community Posts - -As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: - -* [Adding a Module to an ABP project made simple](https://community.abp.io/posts/adding-a-module-to-an-abp-project-made-simple-a8zw0j2m) by [Bart Van Hoey](https://twitter.com/@bartvanhoey) -* [Getting started with Abp Vue UI](https://community.abp.io/posts/getting-started-with-abp-vue-ui-4vfiv5io) by [Sajankumar Vijayan](https://community.abp.io/members/Sajan) -* [Liming Ma](https://github.com/maliming) has created **two** new community articles: - * [How to share the cookies between subdomains](https://community.abp.io/posts/how-to-share-the-cookies-between-subdomains-jfrzggc2) - * [Using Testcontainers in ABP Unit Test](https://community.abp.io/posts/using-testcontainers-in-abp-unit-test-b67gzpxg) -* [Ahmed Tarek](https://github.com/AhmedTarekHasan) has created **three** new community articles: - * [Strategy Design Pattern In .NET C#](https://community.abp.io/posts/strategy-design-pattern-in-.net-c-vcgv11h5) - * [Mediator Design Pattern In .NET C#](https://community.abp.io/posts/mediator-design-pattern-in-.net-c-pdsjp93n) - * [SOLID: Liskov Substitution Principle Explained In .NET C#](https://community.abp.io/posts/solid-liskov-substitution-principle-explained-in-.net-c-hx2z8vo9) - -Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community. - -### New ABP Blog Posts - -There are also some exciting blog posts written by the ABP team. You can see the following list for some of those articles: - -* [Announcement: Our New ABP.IO Unified Platform](https://blog.abp.io/abp/our-new-abp.io-unified-platform) by [Alper Ebicoglu](https://twitter.com/alperebicoglu) -* [Join ABP.IO at Modern .NET Web Day](https://blog.abp.io/abp/Join-ABP.IO-at-Modern-.NET-Web-Day) by [Roo Xu](https://github.com/Roo1227) - -## About the Next Version - -The next feature version will be 8.2. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/CLI-BuildCommand.md b/docs/en/CLI-BuildCommand.md deleted file mode 100644 index d9620aa621..0000000000 --- a/docs/en/CLI-BuildCommand.md +++ /dev/null @@ -1,67 +0,0 @@ -# Build Command - -Building a .NET project is hard when the project references a project reference outside of the solution or even from a different GIT repository. This command builds a GIT repository and it's depending repositories or a single .NET solution File. In order ```build``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain one of; - -* A .NET solution file (*.sln) -* abp-build-config.json - -When the executing directory (or ```--working-directory``` parameter's directory) contains a .NET solution file, ```build``` command builds all the projects in the related solution file and all project references on building project files recursively. - -When the executing directory (or ```--working-directory``` parameter's directory) contains a ```abp-build-config.json```, ```build``` command builds all changed projects form its last build and all project references on building project files recursively. - -# Build Command Config - -```abp-build-config.json``` contains properties below; - -* ```Name```: Name of the GIT repository. This can be friendly name of your GIT repository or any other unique string for the repository. -* ```RootPath```: Root path of the repository which contains ```.git``` folder. -* ```DependingRepositories```: Depending repository list of a repository. Each depending repository item contains same fields as a repository. -* ```IgnoredDirectories```: Relative directory paths to ignore while building a GIT repository. - -A sample ```abp-build-config.json``` looks like for a Windows OS; - -````json -{ - "Name": "main-repository", - "RootPath": "D:\\GitHub\\main-repository", - "DependingRepositories": [{ - "Name": "module-repository", - "RootPath": "D:\\GitHub\\module-repository" - }], - "IgnoredDirectories": [ - "utils" - ] -} -```` - -# Build Status - -ABP CLI stores a build status file for builds using the repository friendly names and current branch names in the; - -* ```%USERPROFILE%\.abp\build\``` for Windows. -* ```$HOME/.abp/build/``` for Linux/macOS. - -and uses this file when building same repository next time and only builds affected projects and decreases the total build time. A sample build status file content looks like; - -````json -{ - "RepositoryName": "main-repository", - "BranchName": "dev", - "CommitId": "84ecde8ba275aeeb14d24a87ad46a1e941adf8ba", - "SucceedProjects": [{ - "CsProjPath": "D:\\GitHub\\main-repository\\BookStore\\BookStore.Web.csproj", - "CommitId": "84ecde8ba275aeeb14d24a87ad46a1e941adf8ba" - }], - "DependingRepositories": [{ - "RepositoryName": "module-repository", - "BranchName": "dev", - "CommitId": "0598b8e45af9507fc9ba8abf304e78fc7d434e04", - "SucceedProjects": [{ - "CsProjPath": "D:\\GitHub\\module-repository\\identity-module\Identity\\Identity.Web.csproj", - "CommitId": "0598b8e45af9507fc9ba8abf304e78fc7d434e04" - }], - "DependingRepositories": [] - }] -} -```` - diff --git a/docs/en/CLI-New-Command-Samples.md b/docs/en/CLI-New-Command-Samples.md deleted file mode 100644 index 635390c29e..0000000000 --- a/docs/en/CLI-New-Command-Samples.md +++ /dev/null @@ -1,258 +0,0 @@ -# ABP CLI - New Solution Sample Commands - -The `abp new` command creates an ABP solution or other artifacts based on an ABP template. [ABP CLI](CLI.md) has several parameters to create a new ABP solution. In this document we will show you some sample commands to create a new solution. All the project names are `Acme.BookStore`. Currently, the available mobile projects are `React Native` and `MAUI` mobile app. Available database providers are `Entity Framework Core` and `MongoDB`. All the commands starts with `abp new`. - -## Angular - -The following commands are for creating Angular UI projects: - -* **Entity Framework Core**, no mobile app, creates the project in a new folder: - - ````bash - abp new Acme.BookStore -u angular --mobile none --database-provider ef -csf - ```` - -* **Entity Framework Core**, default app template, **separate Auth Server**, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -t app -u angular -m none --separate-auth-server --database-provider ef -csf - ``` - -* **Entity Framework Core**, **custom connection string**, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -u angular -csf --connection-string Server=localhost;Database=MyDatabase;Trusted_Connection=True - ``` - -* **MongoDB**, default app template, mobile project included, creates solution in `C:\MyProjects\Acme.BookStore` - - ```bash - abp new Acme.BookStore -u angular --database-provider mongodb --output-folder C:\MyProjects\Acme.BookStore - ``` - -* **MongoDB**, default app template, no mobile app, **separate Auth Server**, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -t app -u angular -m none --separate-auth-server --database-provider mongodb -csf - ``` - -## MVC - -The following commands are for creating MVC UI projects: - -* **Entity Framework Core**, no mobile app, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef -csf - ``` - -* **Entity Framework Core**, **tier architecture** (*Web and HTTP API are separated*), no mobile app, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -u mvc --mobile none --tiered --database-provider ef -csf - ``` - -* **MongoDB**, no mobile app, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -t app -u mvc --mobile none --database-provider mongodb -csf - ``` - -* **MongoDB**, **tier architecture**, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -u mvc --tiered --database-provider mongodb -csf - ``` - -* **Public Website**, Entity Framework Core, no mobile app, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef -csf --with-public-website - ``` - - _Note that Public Website is only included in PRO templates._ - - -## Blazor WebAssembly - -The following commands are for creating Blazor WASM projects: - -* **Entity Framework Core**, no mobile app: - - ```bash - abp new Acme.BookStore -t app -u blazor --mobile none - ``` - -* **Entity Framework Core**, **separate Auth Server**, mobile app included: - - ```bash - abp new Acme.BookStore -u blazor --separate-auth-server - ``` - -* **MongoDB**, no mobile app, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -u blazor --database-provider mongodb --mobile none -csf - ``` - -## Blazor Server - -The following commands are for creating Blazor projects: - -* **Entity Framework Core**, no mobile app: - - ```bash - abp new Acme.BookStore -t app -u blazor-server --mobile none - ``` - -* **Entity Framework Core**, **separate Auth Server**, **separate API Host**, mobile app included: - - ```bash - abp new Acme.BookStore -u blazor-server --tiered - ``` - -* **MongoDB**, no mobile app, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -u blazor --database-provider mongodb --mobile none -csf - ``` - -## No UI - -In the default app template, there is always a frontend project. In this option there is no frontend project. It has a `HttpApi.Host` project to serve your HTTP WebAPIs. It's appropriate if you want to create a WebAPI service. - -* **Entity Framework Core**, separate Auth Server, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -u none --separate-auth-server -csf - ``` -* **MongoDB**, no mobile app: - - ```bash - abp new Acme.BookStore -u none --mobile none --database-provider mongodb - ``` - - - -## Console application - -It's a template of a basic .NET console application with ABP module architecture integrated. To create a console application use the following command: - -* This project consists of the following files: `Acme.BookStore.csproj`, `appsettings.json`, `BookStoreHostedService.cs`, `BookStoreModule.cs`, `HelloWorldService.cs` and `Program.cs`. - - ```bash - abp new Acme.BookStore -t console -csf - ``` - -## Module - -Module are reusable sub applications used by your main project. Using ABP Module is a best practice if you are building a microservice solution. As modules are not final applications, each module has all the frontend UI projects and database providers. The module template comes with an MVC UI to be able to develop without the final solution. But if you will develop your module under a final solution, you add `--no-ui` parameter to exclude MVC UI project. - -* Included frontends: `MVC`, `Angular`, `Blazor`. Included database providers: `Entity Framework Core`, `MongoDB`. Includes MVC startup project. - - ```bash - abp new Acme.IssueManagement -t module - ``` -* The same with the upper but doesn't include MVC startup project. - - ```bash - abp new Acme.IssueManagement -t module --no-ui - ``` - -* Creates the module and adds it to your solution - - ```bash - abp new Acme.IssueManagement -t module --add-to-solution-file - ``` - -## Create a solution from a specific version - -When you create a solution, it always creates with the latest version. To create a project from an older version, you can pass the `--version` parameter. - -* Create a solution from v3.3.0, with Angular UI and Entity Framework Core. - - ```bash - abp new Acme.BookStore -t app -u angular -m none --database-provider ef -csf --version 3.3.0 - ``` - -To get the ABP version list, checkout following link: https://www.nuget.org/packages/Volo.Abp.Core/ - -## Create from a custom template - -ABP CLI uses the default [app template](https://github.com/abpframework/abp/tree/dev/templates/app) to create your project. If you want to create a new solution from your customized template, you can use the parameter `--template-source`. - -* MVC UI, Entity Framework Core, no mobile app, using the template in `c:\MyProjects\templates\app` directory. - - ```bash - abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef --template-source "c:\MyProjects\templates\app" - ``` - -* Same with the previous one except this command retrieves the template from the URL `https://myabp.com/app-template.zip`. - - ```bash - abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef --template-source https://myabp.com/app-template.zip - ``` - -## Create a preview version - -ABP CLI always uses the latest version. In order to create a solution from a preview (RC) version add the `--preview` parameter. - -* Blazor UI, Entity Framework Core, no mobile, **preview version**, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -t app -u blazor --mobile none -csf --preview - ``` - -## Choose database management system - -The default database management system (DBMS) is `Entity Framework Core` / ` SQL Server`. You can choose a DBMS by passing `--database-management-system` parameter. [Accepted values](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseManagementSystem.cs) are `SqlServer`, `MySQL`, `SQLite`, `Oracle`, `Oracle-Devart`, `PostgreSQL`. The default value is `SqlServer`. - -* Angular UI, **PostgreSQL** database, creates the project in a new folder: - - ```bash - abp new Acme.BookStore -u angular --database-management-system PostgreSQL -csf - ``` - - - -## Use static HTTP ports - -ABP CLI always assigns random ports to the hostable projects. If you need to keep the default ports and create a solution always with the same HTTP ports, add the parameter `--no-random-port`. - -* MVC UI, Entity Framework Core, **static ports**, creates the project in a new folder: - - ```bash - abp new Acme.BookStore --no-random-port -csf - ``` - -## Use local ABP framework references - -ABP libraries are referenced from NuGet by default in the ABP solutions. Sometimes you need to reference ABP libraries locally to your solution. This is useful to debug the framework itself. Your local ABP Framework 's root directory must have the `Volo.Abp.sln` file. You can copy the content of the following directory to your file system https://github.com/abpframework/abp/tree/dev/framework - -* MVC UI, Entity Framework Core, **ABP libraries are local project references**: - -The local path must be the root directory of ABP repository. -If `C:\source\abp\framework\Volo.Abp.sln` is your framework solution path, then you must write `C:\source\abp` to the `--abp-path` paramter. - - ```bash - abp new Acme.BookStore --local-framework-ref --abp-path C:\source\abp - ``` - -**Output**: - -As seen below, ABP Framework libraries are local project references. - -```xml - - - - - - - - -``` - -## See Also - -* [ABP CLI documentation](CLI.md) diff --git a/docs/en/CLI.md b/docs/en/CLI.md deleted file mode 100644 index 58ca9f4629..0000000000 --- a/docs/en/CLI.md +++ /dev/null @@ -1,614 +0,0 @@ -# ABP CLI - -ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions. - -## Installation - -ABP CLI is a [dotnet global tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools). Install it using a command line window: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -To update an existing installation: - -````bash -dotnet tool update -g Volo.Abp.Cli -```` - -## Global Options - -While each command may have a set of options, there are some global options that can be used with any command; - -* `--skip-cli-version-check`: Skips to check the latest version of the ABP CLI. If you don't specify, it will check the latest version and shows a warning message if there is a newer version of the ABP CLI. - -## Commands - -Here, is the list of all available commands before explaining their details: - -* **`help`**: Shows help on the usage of the ABP CLI. -* **`cli`**: Update or remove ABP CLI. -* **`new`**: Generates a new solution based on the ABP [startup templates](Startup-Templates/Index.md). -* **`update`**: Automatically updates all ABP related NuGet and NPM packages in a solution. -* **`clean`**: Deletes all `BIN` and `OBJ` folders in the current folder. -* **`add-package`**: Adds an ABP package to a project. -* **`add-module`**: Adds a [multi-package application module](https://docs.abp.io/en/abp/latest/Modules/Index) to a solution. -* **`list-modules`**: Lists names of open-source application modules. -* **`list-templates`**: Lists the names of available templates to create a solution. -* **`get-source`**: Downloads the source code of a module. -* **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints. -* **`remove-proxy`**: Removes previously generated client side proxies. -* **`switch-to-preview`**: Switches to the latest preview version of the ABP Framework. -* **`switch-to-nightly`**: Switches to the latest [nightly builds](Nightly-Builds.md) of the ABP related packages on a solution. -* **`switch-to-stable`**: Switches to the latest stable versions of the ABP related packages on a solution. -* **`switch-to-local`**: Changes NuGet package references on a solution to local project references. -* **`translate`**: Simplifies to translate localization files when you have multiple JSON [localization](Localization.md) files in a source control repository. -* **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. -* **`login-info`**: Shows the current user's login information. -* **`logout`**: Logouts from your computer if you've authenticated before. -* **`bundle`**: Generates script and style references for ABP Blazor and MAUI Blazor project. -* **`install-libs`**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types. -* **`clear-download-cache`**: Clears the templates download cache. -* **`trust-version`**: Trusts the user's version and does not check if the version exists or not. If the template with the given version is found in the cache, it will be used, otherwise throws an exception. - -### help - -Shows basic usages of the ABP CLI. - -Usage: - -````bash -abp help [command-name] -```` - -Examples: - -````bash -abp help # Shows a general help. -abp help new # Shows help about the "new" command. -```` - -### cli - -Update or remove ABP CLI. - -Usage: - -````bash -abp cli [command-name] -```` - -Examples: - -````bash -abp cli update -abp cli update --preview -abp cli update --version 5.0.0 -abp cli remove -```` - -### new - -Generates a new solution based on the ABP [startup templates](Startup-Templates/Index.md). - -Usage: - -````bash -abp new [options] -```` - -Example: - -````bash -abp new Acme.BookStore -```` - -* `Acme.BookStore` is the solution name here. -* Common convention is to name a solution is like *YourCompany.YourProject*. However, you can use different naming like *YourProject* (single level namespacing) or *YourCompany.YourProduct.YourModule* (three levels namespacing). - -For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Samples.md) - -#### Options - -* `--template` or `-t`: Specifies the template name. Default template name is `app`, which generates a web application. Available templates: - * **`app`** (default): [Application template](Startup-Templates/Application.md). Additional options: - * `--ui` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: - * `mvc`: ASP.NET Core MVC. There are some additional options for this template: - * `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. - * `angular`: Angular UI. There are some additional options for this template: - * `--separate-auth-server`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. - * `--pwa`: Specifies the project as Progressive Web Application. - * `blazor-webapp`: Blazor Web App UI. There are some additional options for this template: - * `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. - * `blazor`: Blazor UI. There are some additional options for this template: - * `--separate-auth-server`The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. - * `--pwa`: Specifies the project as Progressive Web Application. - * `blazor-server`: Blazor Server UI. There are some additional options for this template: - * `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. - * `none`: Without UI. No front-end layer will be created. There are some additional options for this template: - * `--separate-auth-server`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. - * `--mobile` or `-m`: Specifies the mobile application framework. If not specified, no mobile application will be created. Available options: - * `react-native`: React Native. - * `maui`: MAUI. This mobile option is only available for ABP Commercial. - * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: - * `ef`: Entity Framework Core. - * `mongodb`: MongoDB. - * `--theme`: Specifies the theme. Default theme is `leptonx-lite`. Available themes: - * `leptonx-lite`: [LeptonX Lite Theme](Themes/LeptonXLite/AspNetCore.md). - * `basic`: [Basic Theme](UI/AspNetCore/Basic-Theme.md). - * **`module`**: [Module template](Startup-Templates/Module.md). Additional options: - * `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI). - * **`console`**: [Console template](Startup-Templates/Console.md). - * **`app-nolayers`**: [Single-layer application template](Startup-Templates/Application-Single-Layer.md). Additional options: - * `--ui` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: - * `mvc`: ASP.NET Core MVC. - * `angular`: Angular UI. - * `blazor`: Blazor UI. - * `blazor-server`: Blazor Server UI. - * `none`: Without UI. - * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: - * `ef`: Entity Framework Core. - * `mongodb`: MongoDB. - * `--theme`: Specifies the theme. Default theme is `leptonx-lite`. Available themes: - * `leptonx-lite`: [LeptonX Lite Theme](Themes/LeptonXLite/AspNetCore.md). - * `basic`: [Basic Theme](UI/AspNetCore/Basic-Theme.md). - * **`maui`**: .NET MAUI. A minimalist .NET MAUI application will be created if you specify this option. -* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. -* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. -* `--preview`: Use latest preview version. -* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`). -* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. -* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution). -* `--database-management-system` or `-dbms`: Sets the database management system. Default is **SQL Server**. Supported DBMS's: - * `SqlServer` - * `MySQL` - * `SQLite` - * `Oracle` - * `Oracle-Devart` - * `PostgreSQL` -* `--local-framework-ref --abp-path`: Uses local projects references to the ABP framework instead of using the NuGet packages. This can be useful if you download the ABP Framework source code and have a local reference to the framework from your application. -* `--no-random-port`: Uses template's default ports. -* `--skip-installing-libs` or `-sib`: Skip installing client side packages. -* `--skip-cache` or `-sc`: Always download the latest from our server and refresh their templates folder cache. -* `--with-public-website`: **Public Website** is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. - -See some [examples for the new command](CLI-New-Command-Samples.md) here. - -### update - -Updating all ABP related packages can be tedious since there are many packages of the framework and modules. This command automatically updates all ABP related NuGet and NPM packages in a solution or project to the latest versions. You can run it in the root folder of your solutions. - -Usage: - -````bash -abp update [options] -```` - -* If you run in a directory with a .csproj file, it updates all ABP related packages of the project to the latest versions. -* If you run in a directory with a .sln file, it updates all ABP related packages of the all projects of the solution to the latest versions. -* If you run in a directory that contains multiple solutions in sub-folders, it can update all the solutions, including Angular projects. - -Note that this command can upgrade your solution from a previous version, and also can upgrade it from a preview release to the stable release of the same version. - -#### Options - -* `--npm`: Only updates NPM packages. -* `--nuget`: Only updates NuGet packages. -* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default -* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default. -* `--check-all`: Check the new version of each package separately. Default is `false`. -* `--version` or `-v`: Specifies the version to use for update. If not specified, latest version is used. - -### clean - -Deletes all `BIN` and `OBJ` folders in the current folder. - -Usage: - -````bash -abp clean -```` - - -### add-package - -Adds an ABP package to a project by, - -* Adding related nuget package as a dependency to the project. -* Adding `[DependsOn(...)]` attribute to the module class in the project (see the [module development document](Module-Development-Basics.md)). - -> Notice that the added module may require additional configuration which is generally indicated in the documentation of the related package. - -Basic usage: - -````bash -abp add-package [options] -```` - -Example: - -```` -abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic -```` - -* This example adds the `Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic` package to the project. - -#### Options - -* `--project` or `-p`: Specifies the project (.csproj) file path. If not specified, CLI tries to find a .csproj file in the current directory. -* `--with-source-code`: Downloads the source code of the package to your solution folder and uses local project references instead of NuGet/NPM packages. -* `--add-to-solution-file`: Adds the downloaded package to your solution file, so you will also see the package when you open the solution on a IDE. (only available when `--with-source-code` is True) - -> Currently only the source code of the basic theme packages([MVC](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Basic-Theme) and [Blazor](https://docs.abp.io/en/abp/latest/UI/Blazor/Basic-Theme)) can be downloaded. -> - Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic -> - Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme -> - Volo.Abp.AspNetCore.Components.Web.BasicTheme -> - Volo.Abp.AspNetCore.Components.Server.BasicTheme - - -### add-module - -Adds a [multi-package application module](Modules/Index) to a solution by finding all packages of the module, finding related projects in the solution and adding each package to the corresponding project in the solution. - -It can also create a new module for your solution and add it to your solution. See `--new` option. - -> A business module generally consists of several packages (because of layering, different database provider options or other reasons). Using `add-module` command dramatically simplifies adding a module to a solution. However, each module may require some additional configurations which is generally indicated in the documentation of the related module. - -Usage: - -````bash -abp add-module [options] -```` - -Examples: - -```bash -abp add-module Volo.Blogging -``` - -* This example adds the `Volo.Blogging` module to the solution. - -```bash -abp add-module ProductManagement --new --add-to-solution-file -``` - -* This command creates a fresh new module customized for your solution (named `ProductManagement`) and adds it to your solution. - - -#### Options - -* `--solution` or `-s`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory. -* `--skip-db-migrations`: For EF Core database provider, it automatically adds a new code first migration (`Add-Migration`) and updates the database (`Update-Database`) if necessary. Specify this option to skip this operation. -* `-sp` or `--startup-project`: Relative path to the project folder of the startup project. Default value is the current folder. -* `--new`: Creates a fresh new module (customized for your solution) and adds it to your solution. -* `--with-source-code`: Downloads the source code of the module to your solution folder and uses local project references instead of NuGet/NPM packages. This options is always `True` if `--new` is used. -* `--add-to-solution-file`: Adds the downloaded/created module to your solution file, so you will also see the projects of the module when you open the solution on a IDE. (only available when `--with-source-code` is `True`.) - -### list-modules - -Lists names of open-source application modules. - -Usage: - -````bash -abp list-modules [options] -```` - -Example: - -```bash -abp list-modules -``` - -#### Options - -* `--include-pro-modules`: Includes commercial (pro) modules in the output. - -### list-templates - -Lists all available templates to create a solution. - -Usage: - -```bash -abp list-templates -``` - -### get-source - -Downloads the source code of a module to your computer. - -Usage: - -````bash -abp get-source [options] -```` - -Example: - -```bash -abp get-source Volo.Blogging - -abp get-source Volo.Blogging --local-framework-ref --abp-path D:\GitHub\abp -``` - -#### Options - -* `--output-folder` or `-o`: Specifies the directory that source code will be downloaded in. If not specified, current directory is used. -* `--version` or `-v`: Specifies the version of the source code that will be downloaded. If not specified, latest version is used. -* `--preview`: If no version option is specified, this option specifies if latest [preview version](Previews.md) will be used instead of latest stable version. -* `--local-framework-ref --abp-path`: Path of [ABP Framework GitHub repository](https://github.com/abpframework/abp) in your computer. This will be used for converting project references to your local system. If this is not specified, project references will be converted to NuGet references. - -### generate-proxy - -Generates Angular, C# or JavaScript service proxies for your HTTP APIs to make easy to consume your services from the client side. Your host (server) application must be up and running before running this command. - -Usage: - -````bash -abp generate-proxy -t [options] -```` - -Examples: - -````bash -abp generate-proxy -t ng -url https://localhost:44302/ -abp generate-proxy -t js -url https://localhost:44302/ -abp generate-proxy -t csharp -url https://localhost:44302/ -```` - -#### Options - -* `--type` or `-t`: The name of client type. Available clients: - * `csharp`: C#, work in the `*.HttpApi.Client` project directory. There are some additional options for this client: - * `--without-contracts`: Avoid generating the application service interface, class, enum and dto types. - * `--folder`: Folder name to place generated CSharp code in. Default value: `ClientProxies`. - * `ng`: Angular. There are some additional options for this client: - * `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`. - * `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`. - * `--target`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`. - * `--module`: Backend module name. Default value: `app`. - * `--entry-point`: Targets the Angular project to place the generated code. - * `--url`: Specifies api definition url. Default value is API Name's url in environment file. - * `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options). - - * `js`: JavaScript. work in the `*.Web` project directory. There are some additional options for this client: - * `--output` or `-o`: JavaScript file path or folder to place generated code in. -* `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`. -* `--working-directory` or `-wd`: Execution directory. For `csharp` and `js` client types. -* `--url` or `-u`: API definition URL from. -* `--service-type` or `-st`: Specifies the service type to generate. `application`, `integration` and `all`, Default value: `all` for C#, `application` for JavaScript / Angular. - -> See the [Angular Service Proxies document](UI/Angular/Service-Proxies.md) for more. - -### remove-proxy - -Removes previously generated proxy code from the Angular, CSharp or JavaScript application. Your host (server) application must be up and running before running this command. - -This can be especially useful when you generate proxies for multiple modules before and need to remove one of them later. - -Usage: - -````bash -abp remove-proxy -t [options] -```` - -Examples: - -````bash -abp remove-proxy -t ng -abp remove-proxy -t js -m identity -o Pages/Identity/client-proxies.js -abp remove-proxy -t csharp --folder MyProxies/InnerFolder -```` - -#### Options - -* `--type` or `-t`: The name of client type. Available clients: - * `csharp`: C#, work in the `*.HttpApi.Client` project directory. There are some additional options for this client: - * `--folder`: Folder name to place generated CSharp code in. Default value: `ClientProxies`. - * `ng`: Angular. There are some additional options for this client: - * `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`. - * `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`. - * `--target`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`. - * `--url`: Specifies api definition url. Default value is API Name's url in environment file. - * `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options). - * `js`: JavaScript. work in the `*.Web` project directory. There are some additional options for this client: - * `--output` or `-o`: JavaScript file path or folder to place generated code in. -* `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`. -* `--working-directory` or `-wd`: Execution directory. For `csharp` and `js` client types. -* `--url` or `-u`: API definition URL from. - -> See the [Angular Service Proxies document](UI/Angular/Service-Proxies.md) for more. - -### switch-to-preview - -You can use this command to switch your solution or project to latest preview version of the ABP framework. - -Usage: - -````bash -abp switch-to-preview [options] -```` - -#### Options - -* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. - - -### switch-to-nightly - -You can use this command to switch your solution or project to latest [nightly](Nightly-Builds.md) preview version of the ABP framework packages. - -Usage: - -````bash -abp switch-to-nightly [options] -```` - -#### Options - -* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. - -### switch-to-stable - -If you're using the ABP Framework preview packages (including nightly previews), you can switch back to latest stable version using this command. - -Usage: - -````bash -abp switch-to-stable [options] -```` -#### Options - -* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. - -### switch-to-local - -Changes all NuGet package references to local project references for all the .csproj files in the specified folder (and all its subfolders with any deep). It is not limited to ABP Framework or Module packages. - -Usage: - -````bash -abp switch-to-local [options] -```` -#### Options - -* `--solution` or `-s`: Specifies the solution directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. - -* `--paths` or `-p`: Specifies the local paths that the projects are inside. You can use `|` character to separate the paths. - -Example: - -````bash -abp switch-to-local --paths "D:\Github\abp|D:\Github\my-repo" -```` - -### translate - -Simplifies to translate [localization](Localization.md) files when you have multiple JSON [localization](Localization.md) files in a source control repository. - -* This command will create a unified json file based on the reference culture. -* It searches all the localization `JSON` files in the current directory and all subdirectories (recursively). Then creates a single file (named `abp-translation.json` by default) that includes all the entries need to be translated. -* Once you translate the entries in this file, you can then apply your changes to the original localization files using the `--apply` command. - -> The main purpose of this command is to translate ABP Framework localization files (since the [abp repository](https://github.com/abpframework/abp) has tens of localization files to be translated in different directories). - -#### Creating the Translation File - -First step is to create the unified translation file: - -````bash -abp translate -c [options] -```` - -Example: - -````bash -abp translate -c de -```` - -This command created the unified translation file for the `de` (German) culture. - -##### Additional Options - -* `--reference-culture` or `-r`: Default `en`. Specifies the reference culture. -* `--output` or `-o`: Output file name. Default `abp-translation.json`. -* `--all-values` or `-all`: Include all keys to translate. By default, the unified translation file only includes the missing texts for the target culture. Specify this parameter if you may need to revise the values already translated before. - -#### Applying Changes - -Once you translate the entries in the unified translation file, you can apply your changes to the original localization files using the `--apply` parameter: - -````bash -abp translate --apply # apply all changes -abp translate -a # shortcut for --apply -```` - -Then review changes on your source control system to be sure that it has changed the proper files and send a Pull Request if you've translated ABP Framework resources. Thank you in advance for your contribution. - -##### Additional Options - -* `--file` or `-f`: Default: `abp-translation.json`. The translation file (use only if you've used the `--output` option before). - -#### Online DeepL translate - -The `translate` command also supports online translation. You need to provide your [DeepL Authentication Key](https://support.deepl.com/hc/en-us/articles/360020695820-Authentication-Key). - -It will search all the `en.json(reference-culture)` files in the directory and sub-directory and then translate and generate the corresponding `zh-Hans.json(culture)` files. - -````bash -abp translate -c zh-Hans --online --deepl-auth-key -```` - -### login - -Some features of the CLI requires to be logged in to abp.io platform. To login with your username write: - -```bash -abp login # Allows you to enter your password hidden -abp login -p # Specify the password as a parameter (password is visible) -abp login --organization # If you have multiple organizations, you need set your active organization -abp login -p -o # You can enter both your password and organization in the same command -abp login --device # Use device login flow -``` - -> When using the -p parameter, be careful as your password will be visible. It's useful for CI/CD automation pipelines. - -A new login with an already active session overwrites the previous session. - -### login-info - -Shows your login information such as **Name**, **Surname**, **Username**, **Email Address** and **Organization**. - -```bash -abp login-info -``` - -### logout - -Logs you out by removing the session token from your computer. - -```bash -abp logout -``` - -### bundle - -This command generates script and style references for ABP Blazor WebAssembly and MAUI Blazor project and updates the **index.html** file. It helps developers to manage dependencies required by ABP modules easily. In order ```bundle``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain a Blazor or MAUI Blazor project file(*.csproj). - -Usage: - -````bash -abp bundle [options] -```` - -#### Options - -* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a Blazor project file. -* ```--force``` or ```-f```: Forces to build project before generating references. -* ```--project-type``` or ```-t```: Specifies the project type. Default type is `webassembly`. Available types: - * `webassembly` - * `maui-blazor` - -`bundle` command reads the `appsettings.json` file inside the Blazor and MAUI Blazor project for bundling options. For more details about managing style and script references in Blazor or MAUI Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md) - -### install-libs - -This command install NPM Packages for MVC / Razor Pages and Blazor Server UI types. Its **executing directory** or passed ```--working-directory``` parameter's directory must contain a project file(*.csproj). - -`install-libs` command reads the `abp.resourcemapping.js` file to manage package. For more details see [Client Side Package Management](UI/AspNetCore/Client-Side-Package-Management.md). - -Usage: - -````bash -abp install-libs [options] -```` - -#### Options - -* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a project file. - -## See Also - -* [Examples for the new command](CLI-New-Command-Samples.md) -* [Video tutorial](https://abp.io/video-courses/essentials/abp-cli) diff --git a/docs/en/CSRF-Anti-Forgery.md b/docs/en/CSRF-Anti-Forgery.md deleted file mode 100644 index ee684aaa52..0000000000 --- a/docs/en/CSRF-Anti-Forgery.md +++ /dev/null @@ -1,162 +0,0 @@ -# CSRF/XSRF & Anti Forgery System - -"*Cross-Site Request Forgery (CSRF) is a type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user’s web browser to perform an unwanted action on a trusted site for which the user is currently authenticated*" ([OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet)). - -**ABP Framework completely automates CSRF preventing** and works out of the box without any configuration. Read this documentation only if you want to understand it better or need to customize. - -## The Problem - -ASP.NET Core [provides infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery) to prevent CSRF attacks by providing a system to **generate** and **validate antiforgery tokens**. However, the standard implementation has a few drawbacks; - -Antiforgery token validation is only **enabled for razor pages by default** and not enabled for **HTTP APIs**. You need to enable it yourself for the Controllers. You can use the `[ValidateAntiForgeryToken]` attribute for a specific API Controller/Action or the `[AutoValidateAntiforgeryToken]` attribute to prevent attacks globally. - -Once you enable it; - -* You need to manually add an HTTP header, named `RequestVerificationToken` to every **AJAX request** made in your application. You should care about obtaining the token, saving in the client side and adding to the HTTP header on every HTTP request. -* All your clients, including **non-browser clients**, should care about obtaining and sending the antiforgery token in every request. In fact, non-browser clients has no CSRF risk and should not care about this. - -Especially, the second point is a pain for your clients and unnecessarily consumes your server resources. - -> You can read more about the ASP.NET Core antiforgery system in its own [documentation](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery). - -## The Solution - -ABP Framework provides `[AbpValidateAntiForgeryToken]` and `[AbpAutoValidateAntiforgeryToken]` attributes, just like the attributes explained above. `[AbpAutoValidateAntiforgeryToken]` is already added to the global filters, so you should do nothing to enable it for your application. - -ABP Framework also automates the following infrastructure; - -* Server side sets a **special cookie**, named `XSRF-TOKEN` by default, that is used make the antiforgery token value available to the browser. This is **done automatically** (by the [application configuration](API/Application-Configuration.md) endpoint). Nothing to do in the client side. -* In the client side, it reads the token from the cookie and sends it in the **HTTP header** (named `RequestVerificationToken` by default). This is implemented for all the supported UI types. -* Server side validates the antiforgery token **only for same and cross site requests** made by the browser. It bypasses the validation for non-browser clients. - -That's all. The systems works smoothly. - -## Configuration / Customization - -### AbpAntiForgeryOptions - -`AbpAntiForgeryOptions` is the main [options class](Options.md) to configure the ABP Antiforgery system. It has the following properties; - -* `TokenCookie`: Can be used to configure the cookie details. This cookie is used to store the antiforgery token value in the client side, so clients can read it and sends the value as the HTTP header. Default cookie name is `XSRF-TOKEN`, expiration time is 10 years (yes, ten years! It should be a value longer than the authentication cookie max life time, for the security). -* `AuthCookieSchemaName`: The name of the authentication cookie used by your application. Default value is `Identity.Application` (which becomes `AspNetCore.Identity.Application` on runtime). The default value properly works with the ABP startup templates. **If you change the authentication cookie name, you also must change this.** -* `AutoValidate`: The single point to enable/disable the ABP automatic antiforgery validation system. Default value is `true`. -* `AutoValidateFilter`: A predicate that gets a type and returns a boolean. ABP uses this predicate to check a controller type. If it returns false for a controller type, the controller is excluded from the automatic antiforgery token validation. -* `AutoValidateIgnoredHttpMethods`: A list of HTTP Methods to ignore on automatic antiforgery validation. Default value: "GET", "HEAD", "TRACE", "OPTIONS". These HTTP Methods are safe to skip antiforgery validation since they don't change the application state. - -If you need to change these options, do it in the `ConfigureServices` method of your [module](Module-Development-Basics.md). - -**Example: Configuring the AbpAntiForgeryOptions** - -```csharp -Configure(options => -{ - options.TokenCookie.Expiration = TimeSpan.FromDays(365); - options.AutoValidateIgnoredHttpMethods.Remove("GET"); - options.AutoValidateFilter = - type => !type.Namespace.StartsWith("MyProject.MyIgnoredNamespace"); -}); -``` - -This configuration; - -* Sets the antiforgery token expiration time to ~1 year. -* Enables antiforgery token validation for GET requests too. -* Ignores the controller types in the specified namespace. - -### AntiforgeryOptions - -`AntiforgeryOptions` is the standard [options class](Options.md) of the ASP.NET Core. **You can find all the information about this class in its [own documentation](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery)**. - -`HeaderName` option is especially important for the ABP Framework point of view. Default value of this value is `RequestVerificationToken` and the clients uses this name while sending the token value in the header. So, if you change this option, you should also arrange your clients to align the change. If you don't have a good reason, leave it as default. - -### AbpValidateAntiForgeryToken Attribute - -If you disable the automatic validation or want to perform the validation for an endpoint that is not validated by default (for example, an endpoint with HTTP GET Method), you can use the `[AbpValidateAntiForgeryToken]` attribute for a **controller type or method** (action). - -**Example: Add `[AbpValidateAntiForgeryToken]` to a HTTP GET method** - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.AntiForgery; - -namespace MyCompanyName.MyProjectName.Controllers -{ - [Route("api/products")] - public class ProductController : AbpController - { - [HttpGet] - [AbpValidateAntiForgeryToken] - public async Task GetAsync() - { - //TODO: ... - } - } -} -``` - -### Angular UI - -Angular supports CSRF Token out of box, but the token header name is `X-XSRF-TOKEN`. Since ABP Framework follows the ASP.NET Core conventions, it changes this value to `RequestVerificationToken` in the core package. - -You don't need to make anything unless you need to change the `AntiforgeryOptions.HeaderName` as explained before. If you change it, remember to change the header name for the Angular application too. To do that, add an import declaration for the `HttpClientXsrfModule` into your root module. - -**Example: Change the header name to *MyCustomHeaderName*** - -```typescript -@NgModule({ - // ... - imports: [ - //... - HttpClientXsrfModule.withOptions({ - cookieName: 'XSRF-TOKEN', - headerName: 'MyCustomHeaderName' - }) - ], -}) -export class AppModule {} -``` - -**Note:** XSRF-TOKEN is only valid if both frontend application and APIs run on the same domain. Therefore, when you make a request, you should use a relative path. - -For example, let's say your APIs is hosted at `https://testdomain.com/ws` -and your angular application is hosted at `https://testdomain.com/admin` - -So if your API request should look like this `https://testdomain.com/ws/api/identity/users` - -your `environment.prod.ts` has to be as follows: - -```typescript -export const environment = { - production: true, - // .... - apis: { - default: { - url: '/ws', // <- just use the context root here - // ... - }, - }, -} as Config.Environment; -``` - -Let's talk about why. - -First, take a look at [Angular's code](https://github.com/angular/angular/blob/master/packages/common/http/src/xsrf.ts#L81) - -It does not intercept any request that starts with `http://` or `https://`. There is a good reason for that. Any cross-site request does not need this token for security. This verification is only valid if the request is made to the same domain from which the web page is served. So, simply put, if you serve everything from a single domain, you just use a relative path. - -If you serve your APIs from the root, i.e. no context root (https://testdomain.com/api/identity/users), leave `url` empty as follows: - -```typescript -export const environment = { - production: true, - // .... - apis: { - default: { - url: '', // <- should be empty string, not '/' - // ... - }, - }, -} as Config.Environment; -``` diff --git a/docs/en/Caching.md b/docs/en/Caching.md deleted file mode 100644 index e39e0ead24..0000000000 --- a/docs/en/Caching.md +++ /dev/null @@ -1,284 +0,0 @@ -# Distributed Caching - -ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). - -> **Default implementation of the `IDistributedCache` interface is` MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use Redis as the distributed cache server. - -## Installation - -> This package is already installed by default with the [application startup template](Startup-Templates/Application.md). So, most of the time, you don't need to install it manually. - -[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) is the main package of the caching system. You can install it a project using the add-package command of the [ABP CLI](CLI.md): - -```bash -abp add-package Volo.Abp.Caching -``` - -You need to run this command on a command line terminal in a folder containing a `csproj` file (see [other options](https://abp.io/package-detail/Volo.Abp.Caching) to install). - -## Usage - -### `IDistributedCache` Interface - -ASP.NET Core defines the `IDistributedCache` interface to get/set the cache values. But it has some difficulties: - -* It works with **byte arrays** rather than .NET objects. So, you need to **serialize/deserialize** the objects you need to cache. -* It provides a **single key pool** for all cache items, so; - * You need to care about the keys to distinguish **different type of objects**. - * You need to care about the cache items of **different tenants** in a [multi-tenant](Multi-Tenancy.md) system. - -> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**. - -See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information. - -### `IDistributedCache` Interface - -ABP framework defines the generic `IDistributedCache` interface in the [Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching/) package. `TCacheItem` is the type of the object stored in the cache. - -`IDistributedCache` solves the difficulties explained above; - -* It internally **serializes/deserializes** the cached objects. Uses **JSON** serialization by default, but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](Dependency-Injection.md) system. -* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. Default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the **`CacheName` attribute** on the cache item class to set the cache name. -* It automatically adds the **current tenant id** to the cache key to distinguish cache items for different tenants (if your application is [multi-tenant](Multi-Tenancy.md)). Define `IgnoreMultiTenancy` attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application. -* Allows to define a **global cache key prefix** per application, so different applications can use their isolated key pools in a shared distributed cache server. -* It **can tolerate errors** wherever possible and bypasses the cache. This is useful when you have temporary problems on the cache server. -* It has methods like `GetManyAsync` and `SetManyAsync` which significantly improve the performance on **batch operations**. - -**Example: Store Book names and prices in the cache** - -````csharp -namespace MyProject -{ - public class BookCacheItem - { - public string Name { get; set; } - - public float Price { get; set; } - } -} -```` - -You can inject and use the `IDistributedCache` service to get/set `BookCacheItem` objects: - -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; - -namespace MyProject -{ - public class BookService : ITransientDependency - { - private readonly IDistributedCache _cache; - - public BookService(IDistributedCache cache) - { - _cache = cache; - } - - public async Task GetAsync(Guid bookId) - { - return await _cache.GetOrAddAsync( - bookId.ToString(), //Cache key - async () => await GetBookFromDatabaseAsync(bookId), - () => new DistributedCacheEntryOptions - { - AbsoluteExpiration = DateTimeOffset.Now.AddHours(1) - } - ); - } - - private Task GetBookFromDatabaseAsync(Guid bookId) - { - //TODO: get from database - } - } -} -```` - -* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache. `GetOrAddAsync` is an additional method that was added by the ABP Framework to the standard ASP.NET Core distributed cache methods. -* If the book was not found in the cache, it calls the factory method (`GetBookFromDatabaseAsync` in this case) to retrieve the book item from the original source. -* `GetOrAddAsync` optionally gets a `DistributedCacheEntryOptions` which can be used to set the lifetime of the cached item. - -`IDistributedCache` supports the same methods of the ASP.NET Core's standard `IDistributedCache` interface, so you can refer [it's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). - -### `IDistributedCache` Interface - -`IDistributedCache` interface assumes that the type of your **cache key** is `string` (so, you need to manually convert your key to string if you need to use a different kind of cache key). While this is not a big deal, `IDistributedCache` can be used when your cache key type is not `string`. - -**Example: Store Book names and prices in the cache** - -````csharp -using Volo.Abp.Caching; - -namespace MyProject -{ - [CacheName("Books")] - public class BookCacheItem - { - public string Name { get; set; } - - public float Price { get; set; } - } -} -```` - -* This example uses the `CacheName` attribute for the `BookCacheItem` class to set the cache name. - -You can inject and use the `IDistributedCache` service to get/set `BookCacheItem` objects: - -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; - -namespace MyProject -{ - public class BookService : ITransientDependency - { - private readonly IDistributedCache _cache; - - public BookService(IDistributedCache cache) - { - _cache = cache; - } - - public async Task GetAsync(Guid bookId) - { - return await _cache.GetOrAddAsync( - bookId, //Guid type used as the cache key - async () => await GetBookFromDatabaseAsync(bookId), - () => new DistributedCacheEntryOptions - { - AbsoluteExpiration = DateTimeOffset.Now.AddHours(1) - } - ); - } - private Task GetBookFromDatabaseAsync(Guid bookId) - { - //TODO: get from database - } - } -} -```` - -* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache. -* Since cache explicitly implemented as using `Guid` as cache key, `Guid` value passed to `_cache_GetOrAddAsync()` method. - -#### Complex Types as the Cache Key - -`IDistributedCache` internally uses `ToString()` method of the key object to convert it to a string. If you need to use a complex object as the cache key, you need to override `ToString` method of your class. - -An example class that is used as a cache key: - -````csharp -public class UserInOrganizationCacheKey -{ - public Guid UserId { get; set; } - - public Guid OrganizationId { get; set; } - - //Builds the cache key - public override string ToString() - { - return $"{UserId}_{OrganizationId}"; - } -} -```` - -Example usage: - -````csharp -public class BookService : ITransientDependency -{ - private readonly IDistributedCache _cache; - - public BookService( - IDistributedCache cache) - { - _cache = cache; - } - - ... -} -```` - -## Configuration - -### AbpDistributedCacheOptions - -`AbpDistributedCacheOptions` is the main [options class](Options.md) to configure the caching. - -**Example: Set the cache key prefix for the application** - -```csharp -Configure(options => -{ - options.KeyPrefix = "MyApp1"; -}); -``` - -> Write that code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md). - -#### Available Options - -* `HideErrors` (`bool`, default: `true`): Enables/disables hiding the errors on writing/reading values from the cache server. -* `KeyPrefix` (`string`, default: `null`): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items. -* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. Default value uses the `SlidingExpiration` as 20 minutes. - -## Error Handling - -When you design a cache for your objects, you typically try to get the value from cache first. If not found in the cache, you query the object from the **original source**. It may be located in a **database** or may require to perform an HTTP call to a remote server. - -In most cases, you want to **tolerate the cache errors**; If you get error from the cache server you don't want to cancel the operation. Instead, you silently hide (and log) the error and **query from the original source**. This is what the ABP Framework does by default. - -ABP's Distributed Cache [handle](Exception-Handling.md), log and hide errors by default. There is an option to change this globally (see the options below). - -In addition, all of the `IDistributedCache` (and `IDistributedCache`) methods have an optional `hideErrors` parameter, which is `null` by default. The global value is used if this parameter left as `null`, otherwise you can decide to hide or throw the exceptions for individual method calls. - -## Batch Operations - -ABP's distributed cache interfaces provide methods to perform batch methods those improves the performance when you want to batch operation multiple cache items in a single method call. - -* `SetManyAsync` and `SetMany` methods can be used to set multiple values to the cache. -* `GetManyAsync` and `GetMany` methods can be used to retrieve multiple values from the cache. -* `GetOrAddManyAsync` and `GetOrAddMany` methods can be used to retrieve multiple values and set missing values from the cache -* `RefreshManyAsync` and `RefreshMany` methods can be used to resets the sliding expiration timeout of multiple values from the cache -* `RemoveManyAsync` and `RemoveMany` methods can be used to remove multiple values from the cache - -> These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by the [ABP Redis Cache integration package](Redis-Cache.md). If the provider doesn't support, it fallbacks to `SetAsync` and `GetAsync` ... methods (called once for each item). - -## Caching Entities - -ABP Framework provides a [Distributed Entity Cache System](Entity-Cache.md) for caching entities. It is useful if you want to use caching for quicker access to the entity rather than repeatedly querying it from the database. - -It's designed as read-only and automatically invalidates a cached entity if the entity is updated or deleted. - -> See the [Entity Cache](Entity-Cache.md) documentation for more information. - -## Advanced Topics - -### Unit Of Work Level Cache - -Distributed cache service provides an interesting feature. Assume that you've updated the price of a book in the database, then set the new price to the cache, so you can use the cached value later. What if you have an exception after setting the cache and you **rollback the transaction** that updates the price of the book? In this case, cache value will be incorrect. - -`IDistributedCache<..>` methods gets an optional parameter, named `considerUow`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](Unit-Of-Work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeed**. - -### IDistributedCacheSerializer - -`IDistributedCacheSerializer` service is used to serialize and deserialize the cache items. Default implementation is the `Utf8JsonDistributedCacheSerializer` class that uses `IJsonSerializer` service to convert objects to [JSON](Json-Serialization.md) and vice verse. Then it uses UTC8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache. - -You can [replace](Dependency-Injection.md) this service by your own implementation if you want to implement your own serialization logic. - -### IDistributedCacheKeyNormalizer - -`IDistributedCacheKeyNormalizer` is implemented by the `DistributedCacheKeyNormalizer` class by default. It adds cache name, application cache prefix and current tenant id to the cache key. If you need a more advanced key normalization, you can [replace](Dependency-Injection.md) this service by your own implementation. - -## See Also - -* [Entity Cache](Entity-Cache.md) -* [Redis Cache](Redis-Cache.md) diff --git a/docs/en/Cancellation-Token-Provider.md b/docs/en/Cancellation-Token-Provider.md deleted file mode 100644 index 7f7a78f8ad..0000000000 --- a/docs/en/Cancellation-Token-Provider.md +++ /dev/null @@ -1,71 +0,0 @@ -# Cancellation Token Provider - -A `CancellationToken` enables cooperative cancellation between threads, thread pool work items, or `Task` objects. To handle the possible cancellation of the operation, ABP Framework provides `ICancellationTokenProvider` to obtain the `CancellationToken` itself from the source. - -> To get more information about `CancellationToken`, see [Microsoft Documentation](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken). - -## ICancellationTokenProvider - -`ICancellationTokenProvider` is an abstraction to provide `CancellationToken` for different scenarios. - -Generally, you should pass the `CancellationToken` as a parameter for your method to use it. With the `ICancellationTokenProvider` you don't need to pass `CancellationToken` for every method. `ICancellationTokenProvider` can be injected with the **dependency injection** and provides the token from it's source. - -**Example:** - -```csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Threading; - -namespace MyProject -{ - public class MyService : ITransientDependency - { - private readonly ICancellationTokenProvider _cancellationTokenProvider; - - public MyService(ICancellationTokenProvider cancellationTokenProvider) - { - _cancellationTokenProvider = cancellationTokenProvider; - } - - public async Task DoItAsync() - { - while (_cancellationTokenProvider.Token.IsCancellationRequested == false) - { - // ... - } - } - } -} -``` - -## Built-in providers - -- `NullCancellationTokenProvider` - - The `NullCancellationTokenProvider` is a built in provider and it supply always `CancellationToken.None`. - -- `HttpContextCancellationTokenProvider` - - The `HttpContextCancellationTokenProvider` is a built in default provider for ABP Web applications. It simply provides a `CancellationToken` that is source of the web request from the `HttpContext`. - -## Implementing the ICancellationTokenProvider - -You can easily create your CancellationTokenProvider by creating a class that implements the `ICancellationTokenProvider` interface, as shown below: - -```csharp -using System.Threading; - -namespace AbpDemo -{ - public class MyCancellationTokenProvider : ICancellationTokenProvider - { - public CancellationToken Token { get; } - - private MyCancellationTokenProvider() - { - - } - } -} -``` diff --git a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/How-to-Design-Multi-Lingual-Entity.md b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/How-to-Design-Multi-Lingual-Entity.md deleted file mode 100644 index 2c4b860c95..0000000000 --- a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/How-to-Design-Multi-Lingual-Entity.md +++ /dev/null @@ -1,690 +0,0 @@ -# How to Design Multi-Lingual Entity - -## Introduction - -If you want to open up to the global market these days, end-to-end localization is a must. ABP provides an already established infrastructure for static texts. However, this may not be sufficient for many applications. You may need to fully customize your app for a particular language and region. - -Let's take a look at a few quotes from Christian Arno's article "[How Foreign-Language Internet Strategies Boost Sales](https://www.mediapost.com/publications/article/155250/how-foreign-language-internet-strategies-boost-sal.html)" to better understand the impact of this: - -- 82% of European consumers are less likely to buy online if the site is not in their native tongue ([Eurobarometer survey](http://europa.eu/rapid/pressReleasesAction.do?reference=IP/11/556)). -- 72.4% of global consumers are more likely to buy a product if the information is available in their own language ([Common Sense Advisory](http://www.commonsenseadvisory.com/)). -- The English language currently only accounts for 31% of all online use, and more than half of all searches are in languages other than English. -- Today, 42% of all Internet users are in Asia, while almost one-quarter are in Europe and just over 10% are in Latin America. - -- Foreign languages have experienced exponential growth in online usage in the past decade -- with Chinese now officially the [second-most-prominent-language](http://english.peopledaily.com.cn/90001/90776/90882/7438489.html) on the Web. [Arabic](http://www.internetworldstats.com/stats7.htm) has increased by a whopping 2500%, while English has only risen by 204% - -If you are looking for ways to expand your market share by fully customizing your application for a particular language and region, in this article I will explain how you can do it with ABP framework. - -### Source Code - -You can find the source code of the application at [abpframework/abp-samples](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreMultiLingual). - -### Demo of the Final Application - -At the end of this article, we will have created an application same as in the gif below. - -![data-model](./result.gif) - -## Development - -In order to keep the article short and get rid of unrelated information in the article (like defining entities etc.), we'll be using the [BookStore](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) example, which is used in the "[Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF)" documentation of ABP Framework and we will make the Book entity as multi-lingual. If you do not want to finish this tutorial, you can find the application [here](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore). - -### Determining the data model - -We need a robust, maintainable, and efficient data model to store content in multiple languages. - -> I read many articles to determine the data model correctly, and as a result, I decided to use one of the many approaches that suit us. -> However, as in everything, there is a trade-off here. If you are wondering about the advantages and disadvantages of the model we will implement compared to other models, I recommend you to read [this article](https://vertabelo.com/blog/data-modeling-for-multiple-languages-how-to-design-a-localization-ready-system/). - -![data-model](./data-model.png) - -As a result of the tutorial, we already have the `Book` and `Author` entities, as an extra, we will just add the `BookTranslation`. - -> In the article, we will make the Name property of the Book entity multi-lingual, but the article is independent of the Book entity, you can make the entity you want multi-lingual with similar codes according to your requirements. - -#### Acme.BookStore.Domain.Shared - -Create a folder named `MultiLingualObjects` and create the following interfaces in its contents. - -We will use the `IObjectTranslation` interface to mark the translation of a multi-lingual entity: - -```csharp -public interface IObjectTranslation -{ - string Language { get; set; } -} -``` - -We will use the `IMultiLingualObject` interface to mark multi-lingual entities: - -```csharp -public interface IMultiLingualObject - where TTranslation : class, IObjectTranslation -{ - ICollection Translations { get; set; } -} -``` - -#### Acme.BookStore.Domain - -In the `Books` folder, create the `BookTranslation` class as follows: - -```csharp -public class BookTranslation : Entity, IObjectTranslation -{ - public Guid BookId { get; set; } - - public string Name { get; set; } - - public string Language { get; set; } - - public override object[] GetKeys() - { - return new object[] {BookId, Language}; - } -} -``` - -`BookTranslation` contains the `Language` property, which contains a language code for translation and a reference to the multi-lingual entity. We also have the `BookId` foreign key to help us know which book is translated. - -Implement `IMultiLingualObject` in the `Book` class as follows: - -```csharp -public class Book : AuditedAggregateRoot, IMultiLingualObject -{ - public Guid AuthorId { get; set; } - - public string Name { get; set; } - - public BookType Type { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - - public ICollection Translations { get; set; } -} -``` - -Create a folder named `MultiLingualObjects` and add the following class inside of this folder: - -```csharp -public class MultiLingualObjectManager : ITransientDependency -{ - protected const int MaxCultureFallbackDepth = 5; - - public async Task FindTranslationAsync( - TMultiLingual multiLingual, - string culture = null, - bool fallbackToParentCultures = true) - where TMultiLingual : IMultiLingualObject - where TTranslation : class, IObjectTranslation - { - culture ??= CultureInfo.CurrentUICulture.Name; - - if (multiLingual.Translations.IsNullOrEmpty()) - { - return null; - } - - var translation = multiLingual.Translations.FirstOrDefault(pt => pt.Language == culture); - if (translation != null) - { - return translation; - } - - if (fallbackToParentCultures) - { - translation = GetTranslationBasedOnCulturalRecursive( - CultureInfo.CurrentUICulture.Parent, - multiLingual.Translations, - 0 - ); - - if (translation != null) - { - return translation; - } - } - - return null; - } - - protected TTranslation GetTranslationBasedOnCulturalRecursive( - CultureInfo culture, ICollection translations, int currentDepth) - where TTranslation : class, IObjectTranslation - { - if (culture == null || - culture.Name.IsNullOrWhiteSpace() || - translations.IsNullOrEmpty() || - currentDepth > MaxCultureFallbackDepth) - { - return null; - } - - var translation = translations.FirstOrDefault(pt => pt.Language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)); - return translation ?? GetTranslationBasedOnCulturalRecursive(culture.Parent, translations, currentDepth + 1); - } -} -``` - -With `MultiLingualObjectManager`'s `FindTranslationAsync` method, we get the translated version of the book according to `CurrentUICulture`. If no translation of culture is found, we return null. - -> Every thread in .NET has `CurrentCulture` and `CurrentUICulture` objects. - -#### Acme.BookStore.EntityFrameworkCore - -In the `OnModelCreating` method of the `BookStoreDbContext` class, configure the `BookTranslation` as follows: - -```csharp -builder.Entity(b => -{ - b.ToTable(BookStoreConsts.DbTablePrefix + "BookTranslations", - BookStoreConsts.DbSchema); - - b.ConfigureByConvention(); - - b.HasKey(x => new {x.BookId, x.Language}); -}); -``` - -> I haven't explicitly set up a one-to-many relationship between `Book` and `BookTranslation` here, but the entity framework will do it for us. - -After that, you can just run the following command in a command-line terminal to add a new database migration (in the directory of the `EntityFrameworkCore` project): - -```bash -dotnet ef migrations add Added_BookTranslation -``` - -This will add a new migration class to your project. You can then run the following command (or run the `.DbMigrator` application) to apply changes to the database: - -```bash -dotnet ef database update -``` - -Add the following code to the `ConfigureServices` method of the `BookStoreEntityFrameworkCoreModule`: - -```csharp - Configure(options => - { - options.Entity(bookOptions => - { - bookOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Translations); - }); -}); -``` - -Now we can use `WithDetailsAsync` without any parameters on `BookAppService` knowing that `Translations` will be included. - -#### Acme.BookStore.Application.Contracts - -Implement `IObjectTranslation` in the `BookDto` class as follows: - -```csharp -public class BookDto : AuditedEntityDto, IObjectTranslation -{ - public Guid AuthorId { get; set; } - - public string AuthorName { get; set; } - - public string Name { get; set; } - - public BookType Type { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - - public string Language { get; set; } -} -``` - -`Language` property is required to understand which language the translated book name belongs to in the UI. - -Create the `AddBookTranslationDto` class in the `Books` folder as follows: - -```csharp -public class AddBookTranslationDto : IObjectTranslation -{ - [Required] - public string Language { get; set; } - - [Required] - public string Name { get; set; } -} -``` - -Add the `AddTranslationsAsync` method to the `IBookAppService` as follows: - -```csharp -public interface IBookAppService : - ICrudAppService< - BookDto, - Guid, - PagedAndSortedResultRequestDto, - CreateUpdateBookDto> -{ - Task> GetAuthorLookupAsync(); - - Task AddTranslationsAsync(Guid id, AddBookTranslationDto input); // added this line -} -``` - -#### Acme.BookStore.Application - -Now, we need to implement the `AddTranslationsAsync` method in `BookAppService` and include `Translations` in the `Book` entity, for this you can change the `BookAppService` as follows: - -```csharp -[Authorize(BookStorePermissions.Books.Default)] -public class BookAppService : - CrudAppService< - Book, //The Book entity - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting - CreateUpdateBookDto>, //Used to create/update a book - IBookAppService //implement the IBookAppService -{ - private readonly IAuthorRepository _authorRepository; - - public BookAppService( - IRepository repository, - IAuthorRepository authorRepository) - : base(repository) - { - _authorRepository = authorRepository; - GetPolicyName = BookStorePermissions.Books.Default; - GetListPolicyName = BookStorePermissions.Books.Default; - CreatePolicyName = BookStorePermissions.Books.Create; - UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Create; - } - - public override async Task GetAsync(Guid id) - { - //Get the IQueryable from the repository - var queryable = await Repository.WithDetailsAsync(); // this line changed - - //Prepare a query to join books and authors - var query = from book in queryable - join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id - where book.Id == id - select new { book, author }; - - //Execute the query and get the book with author - var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query); - if (queryResult == null) - { - throw new EntityNotFoundException(typeof(Book), id); - } - - var bookDto = ObjectMapper.Map(queryResult.book); - bookDto.AuthorName = queryResult.author.Name; - return bookDto; - } - - public override async Task> GetListAsync(PagedAndSortedResultRequestDto input) - { - //Get the IQueryable from the repository - var queryable = await Repository.WithDetailsAsync(); // this line changed - - //Prepare a query to join books and authors - var query = from book in queryable - join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id - select new {book, author}; - - //Paging - query = query - .OrderBy(NormalizeSorting(input.Sorting)) - .Skip(input.SkipCount) - .Take(input.MaxResultCount); - - //Execute the query and get a list - var queryResult = await AsyncExecuter.ToListAsync(query); - - //Convert the query result to a list of BookDto objects - var bookDtos = queryResult.Select(x => - { - var bookDto = ObjectMapper.Map(x.book); - bookDto.AuthorName = x.author.Name; - return bookDto; - }).ToList(); - - //Get the total count with another query - var totalCount = await Repository.GetCountAsync(); - - return new PagedResultDto( - totalCount, - bookDtos - ); - } - - public async Task> GetAuthorLookupAsync() - { - var authors = await _authorRepository.GetListAsync(); - - return new ListResultDto( - ObjectMapper.Map, List>(authors) - ); - } - - public async Task AddTranslationsAsync(Guid id, AddBookTranslationDto input) - { - var queryable = await Repository.WithDetailsAsync(); - - var book = await AsyncExecuter.FirstOrDefaultAsync(queryable, x => x.Id == id); - - if (book.Translations.Any(x => x.Language == input.Language)) - { - throw new UserFriendlyException($"Translation already available for {input.Language}"); - } - - book.Translations.Add(new BookTranslation - { - BookId = book.Id, - Name = input.Name, - Language = input.Language - }); - - await Repository.UpdateAsync(book); - } - - private static string NormalizeSorting(string sorting) - { - if (sorting.IsNullOrEmpty()) - { - return $"book.{nameof(Book.Name)}"; - } - - if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase)) - { - return sorting.Replace( - "authorName", - "author.Name", - StringComparison.OrdinalIgnoreCase - ); - } - - return $"book.{sorting}"; - } -} -``` - -Create the `MultiLingualBookObjectMapper` class as follows: - -```csharp -public class MultiLingualBookObjectMapper : IObjectMapper, ITransientDependency -{ - private readonly MultiLingualObjectManager _multiLingualObjectManager; - - private readonly ISettingProvider _settingProvider; - - public MultiLingualBookObjectMapper( - MultiLingualObjectManager multiLingualObjectManager, - ISettingProvider settingProvider) - { - _multiLingualObjectManager = multiLingualObjectManager; - _settingProvider = settingProvider; - } - - public BookDto Map(Book source) - { - var translation = AsyncHelper.RunSync(() => - _multiLingualObjectManager.FindTranslationAsync(source)); - - return new BookDto - { - Id = source.Id, - AuthorId = source.AuthorId, - Type = source.Type, - Name = translation?.Name ?? source.Name, - PublishDate = source.PublishDate, - Price = source.Price, - Language = translation?.Language ?? AsyncHelper.RunSync(() => _settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage)), - CreationTime = source.CreationTime, - CreatorId = source.CreatorId, - LastModificationTime = source.LastModificationTime, - LastModifierId = source.LastModifierId - }; - } - - public BookDto Map(Book source, BookDto destination) - { - return default; - } -} -``` - -To map the multi-lingual `Book` entity to `BookDto`, we implement custom mapping using the `IObjectMapper` interface. If no translation is found, default values are returned. - -So far we have created the entire infrastructure. We don't need to change anything in the UI, if there is a translation according to the language chosen by the user, the list view will change. However, I want to create a simple modal where we can add new translations to an existing book in order to see what we have done. - -#### Acme.BookStore.Web - -Create a new razor page named `AddTranslationModal` in the `Books` folder as below. - -**View** - -```html -@page -@using Microsoft.AspNetCore.Mvc.TagHelpers -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model Acme.BookStore.Web.Pages.Books.AddTranslationModal - -@{ - Layout = null; -} - -
- - Translations - - - - - - - - - -
-``` - -**Model** - -```csharp -public class AddTranslationModal : BookStorePageModel -{ - [HiddenInput] - [BindProperty(SupportsGet = true)] - public Guid Id { get; set; } - - public List Languages { get; set; } - - [BindProperty] - public BookTranslationViewModel TranslationViewModel { get; set; } - - private readonly IBookAppService _bookAppService; - private readonly ILanguageProvider _languageProvider; - - public AddTranslationModal( - IBookAppService bookAppService, - ILanguageProvider languageProvider) - { - _bookAppService = bookAppService; - _languageProvider = languageProvider; - } - - public async Task OnGetAsync() - { - Languages = await GetLanguagesSelectItem(); - - TranslationViewModel = new BookTranslationViewModel(); - } - - public async Task OnPostAsync() - { - await _bookAppService.AddTranslationsAsync(Id, ObjectMapper.Map(TranslationViewModel)); - - return NoContent(); - } - - private async Task> GetLanguagesSelectItem() - { - var result = await _languageProvider.GetLanguagesAsync(); - - return result.Select( - languageInfo => new SelectListItem - { - Value = languageInfo.CultureName, - Text = languageInfo.DisplayName + " (" + languageInfo.CultureName + ")" - } - ).ToList(); - } - - public class BookTranslationViewModel - { - [Required] - [SelectItems(nameof(Languages))] - public string Language { get; set; } - - [Required] - public string Name { get; set; } - - } -} -``` - -Then, we can open the `BookStoreWebAutoMapperProfile` class and define the required mapping as follows: - -```csharp -CreateMap(); -``` - -Finally, change the content of `index.js` in the `Books` folder as follows: - -```javascript -$(function () { - var l = abp.localization.getResource('BookStore'); - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - var addTranslationModal = new abp.ModalManager(abp.appPath + 'Books/AddTranslationModal'); // added this line - - var dataTable = $('#BooksTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), - columnDefs: [ - { - title: l('Actions'), - rowAction: { - items: - [ - { - text: l('Edit'), - visible: abp.auth.isGranted('BookStore.Books.Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - }, - { - text: l('Add Translation'), // added this action - visible: abp.auth.isGranted('BookStore.Books.Edit'), - action: function (data) { - addTranslationModal.open({ id: data.record.id }); - } - }, - { - text: l('Delete'), - visible: abp.auth.isGranted('BookStore.Books.Delete'), - confirmMessage: function (data) { - return l( - 'BookDeletionConfirmationMessage', - data.record.name - ); - }, - action: function (data) { - acme.bookStore.books.book - .delete(data.record.id) - .then(function() { - abp.notify.info( - l('SuccessfullyDeleted') - ); - dataTable.ajax.reload(); - }); - } - } - ] - } - }, - { - title: l('Name'), - data: "name" - }, - { - title: l('Author'), - data: "authorName" - }, - { - title: l('Type'), - data: "type", - render: function (data) { - return l('Enum:BookType:' + data); - } - }, - { - title: l('PublishDate'), - data: "publishDate", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(); - } - }, - { - title: l('Price'), - data: "price" - }, - { - title: l('CreationTime'), - data: "creationTime", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(luxon.DateTime.DATETIME_SHORT); - } - } - ] - }) - ); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); -``` - -## Conclusion - -With a multi-lingual application, you can expand your market share, but if not designed well, may your application will be unusable. So, I've tried to explain how to design a sustainable multi-lingual entity in this article. - -### Source Code - -You can find source code of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreMultiLingual). diff --git a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/data-model.png b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/data-model.png deleted file mode 100644 index d68bb5d7be..0000000000 Binary files a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/data-model.png and /dev/null differ diff --git a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/result.gif b/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/result.gif deleted file mode 100644 index 0bc41cb8f1..0000000000 Binary files a/docs/en/Community-Articles/15-08-2022-How-to-Design-Multi-Lingual-Entity/result.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md b/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md deleted file mode 100644 index 1694232f29..0000000000 --- a/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md +++ /dev/null @@ -1,97 +0,0 @@ -# How to Customize the SignIn Manager for ABP Applications - -After creating a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) for SignIn Manager and the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)). - -To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container. - -This document explains how to customize the SignIn Manager for your own application. - -## Create a CustomSignInManager - -Create a new class inheriting the [SignInManager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package. - -````csharp -public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager -{ - public CustomSignInManager( - Microsoft.AspNetCore.Identity.UserManager userManager, - Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, - Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory claimsFactory, - Microsoft.Extensions.Options.IOptions optionsAccessor, - Microsoft.Extensions.Logging.ILogger> logger, - Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes, - Microsoft.AspNetCore.Identity.IUserConfirmation confirmation) - : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) - { - } -} -```` - -> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application. - -Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow. - -## Overriding the GetExternalLoginInfoAsync Method - -In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented. - -A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept. - -````csharp -public async override Task GetExternalLoginInfoAsync(string expectedXsrf = null) -{ - var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme); - var items = auth?.Properties?.Items; - if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey)) - { - return null; - } - - if (expectedXsrf != null) - { - if (!items.ContainsKey(XsrfKey)) - { - return null; - } - var userId = items[XsrfKey] as string; - if (userId != expectedXsrf) - { - return null; - } - } - - var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier); - var provider = items[LoginProviderKey] as string; - if (providerKey == null || provider == null) - { - return null; - } - - var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName - ?? provider; - return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName) - { - AuthenticationTokens = auth.Properties.GetTokens(), - AuthenticationProperties = auth.Properties - }; -} -```` - -To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](https://docs.abp.io/en/abp/latest/Dependency-Injection). - -## Register to Dependency Injection - -Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs). - -Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one: - -````csharp -PreConfigure(identityBuilder => -{ - identityBuilder.AddSignInManager(); -}); -```` - -## The Source Code - -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). diff --git a/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md b/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md deleted file mode 100644 index e6c6774341..0000000000 --- a/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md +++ /dev/null @@ -1,260 +0,0 @@ -# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications - -This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**. - -Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly. - -Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage. - -1. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections. -3. **AddMicrosoftIdentityWebAppAuthentication:** This approach uses newly introduced [Microsoft.Identity.Web nuget package](https://www.nuget.org/packages/Microsoft.Identity.Web/) to replace AddAzureAD. -3. ~~**AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.~~ Now marked **Obsolete** (see https://github.com/aspnet/Announcements/issues/439). - -> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings. -> -> However there are key differences in integration to ABP applications because of default configured signin schemes which will be explained below. - -## 1. AddOpenIdConnect - -If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication. - -You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`. - -To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project: - -````json - "AzureAd": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "", - "ClientId": "", - "Domain": "domain.onmicrosoft.com", - "CallbackPath": "/signin-azuread-oidc" - } -```` - -Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following: - -````csharp -private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddAuthentication() - ... //Omitted other third party configurations - .AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options => - { - options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; - options.ClientId = configuration["AzureAd:ClientId"]; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.ClientSecret = configuration["AzureAd:ClientSecret"]; - options.RequireHttpsMetadata = false; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.Scope.Add("email"); - - options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); - }); - } -```` - -> **Don't forget to:** -> -> * Add `options.Scope.Add("email");` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). -> * Add `options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. - - - -And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers. - -## 2. Alternative Approach: AddMicrosoftIdentityWebApp - -With .Net 5.0, AzureAd is marked [obsolete](https://github.com/dotnet/aspnetcore/issues/25807) and will not be supported in the near future. However its expanded functionality is available in [microsoft-identity-web](https://github.com/AzureAD/microsoft-identity-web/wiki) packages. - -Add (or replace with) the new nuget package Microsoft.Identity.Web nuget package](https://www.nuget.org/packages/Microsoft.Identity.Web/). - -In your **.Web** project; you update the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following while having the AzureAd appsettings section as defined before: - -````csharp -private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddAuthentication() - ... //Omitted other third party configurations - .AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd")); - - context.Services.Configure(OpenIdConnectDefaults.AuthenticationScheme, options => - { - options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; - options.ClientId = configuration["AzureAd:ClientId"]; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.ClientSecret = configuration["AzureAd:ClientSecret"]; - options.RequireHttpsMetadata = false; - options.SaveTokens = false; - options.GetClaimsFromUserInfoEndpoint = true; - - options.SignInScheme = IdentityConstants.ExternalScheme; - - options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); - }); - } -```` - -And that's all to add new Microsoft-Identity-Web. - -> **Don't forget to:** -> -> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). -> * Add `options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. - -Keep in mind that [Microsoft-Identity-Web](https://github.com/AzureAD/microsoft-identity-web) is relatively new and keeps getting new enhancements, features and documentation. - -## 3. Obsolete Alternative Approach: AddAzureAD - -This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/). - -If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project. - -#### **Updating `appsettings.json`** - -You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`: - -````json - "AzureAd": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "", - "ClientId": "", - "Domain": "domain.onmicrosoft.com", - "CallbackPath": "/signin-azuread-oidc" - } -```` - -> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri. - -Then, you need to configure the `OpenIdConnectOptions` to complete the integration. - -#### Configuring OpenIdConnectOptions - -In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following: - -````csharp -private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddAuthentication() - ... //Omitted other third party configurations - .AddAzureAD(options => configuration.Bind("AzureAd", options)); - - context.Services.Configure(AzureADDefaults.OpenIdScheme, options => - { - options.Authority = options.Authority + "/v2.0/"; - options.ClientId = configuration["AzureAd:ClientId"]; - options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.RequireHttpsMetadata = false; - - options.TokenValidationParameters.ValidateIssuer = false; - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - options.SignInScheme = IdentityConstants.ExternalScheme; - - options.Scope.Add("email"); - - options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); - }); - } -```` - -> **Don't forget to:** -> -> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out. -> * Add `options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. -> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). -> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215). - -You are done and integration is completed. - -## The Source Code - -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). - -# FAQ - -* Help! `GetExternalLoginInfoAsync` returns `null`! (Using obsolute **AddAzureAD**) - - * There can be 2 reasons for this; - - 1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`: - - ````csharp - options.SignInScheme = IdentityConstants.ExternalScheme; - ```` - - 2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping: - - ````csharp - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); - ```` - - -* Help! `GetExternalLoginInfoAsync` returns `null`! (Using **AddMicrosoftIdentityWebAppAuthentication**) - - - * Pass cookieScheme parameter as **null**. (See [this issue](https://github.com/AzureAD/microsoft-identity-web/issues/133)). - -* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error! - - - * This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add - - ````csharp - options.Scope.Add("email"); - ```` - - to your openid configuration. - -* Help! I keep getting `AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application` error! - - * If you set your **CallbackPath** in appsettings as: - - ````csharp - "AzureAd": { - ... - "CallbackPath": "/signin-azuread-oidc" - } - ```` - - your **Redirect URI** of your application in azure portal must be with domain like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`. - -* Help! I keep getting ***AADSTS700051: The response_type 'token' is not enabled for the application.*** error! - - * This error occurs when you request **token** (access token) along with **id_token** without enabling Access tokens on Azure portal app registrations. Simply tick **Access tokens** checkbox located on top of ID tokens to be able to request token aswell. - -* Help! I keep getting ***AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret*** error! - - * This error occurs when you request **code** along with **id_token**. You need to add **client secret** on azure portal app registrations, under **Certificates & secrets** menu. Afterwards, you need to add openid configuration option like: - - ````csharp - options.ClientSecret = "Value of your secret on azure portal"; - ```` - -* How can I **debug/watch** which claims I get before they get mapped? - - * You can add a simple event under openid configuration to debug before mapping like: - - ````csharp - options.Events.OnTokenValidated = (async context => - { - var claimsFromOidcProvider = context.Principal.Claims.ToList(); - await Task.CompletedTask; - }); - ```` - -* I get page not found error on redirection to **https://login.live.com/oauth20_authorize.srf?**! - - * Probably you are trying to login with Microsoft account to Azure portal instead of the Azure AD account. Try azure AD user account for login instead of microsoft account. You can also check the answers for [this question](https://answers.microsoft.com/en-us/msoffice/forum/msoffice_o365admin-mso_dirservices-mso_o365b/cant-login-in-loginlivecom-with-a-ms-account-valid/6da991e6-9528-461a-9638-9c5680e95888) and [this question](https://docs.microsoft.com/en-us/answers/questions/34806/azure-ad-404-error-when-login-with-microsoft-accou.html) for more details. - -# May 2021 Update - -- **AddOpenIdConnect**: Removed `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();` and added `sub` claim mapping in ClaimActions rather than global mapping. -- Updated `AddMicrosoftIdentityWebAppAuthentication ` to `AddMicrosoftIdentityWebApp`. -- Updated OpenIdConnect and AddMicrosoftIdentityWebApp configurations. -- Obsolete approach moved to third place in the list. diff --git a/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md b/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md deleted file mode 100644 index 562d545ac6..0000000000 --- a/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md +++ /dev/null @@ -1,108 +0,0 @@ -# How to Customize the Login Page for MVC / Razor Page Applications - -When you create a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference. - -This document explains how to customize the login page for your own application. - -## Create a Login PageModel - -Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module. - -````csharp -public class CustomLoginModel : LoginModel -{ - public CustomLoginModel( - Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider, - Microsoft.Extensions.Options.IOptions accountOptions) - : base(schemeProvider, accountOptions) - { - } -} -```` - -> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](https://docs.abp.io/en/abp/latest/Dependency-Injection) system. - -Then you can override any method you need and add new methods and properties needed by the UI. - -## Overriding the Login Page UI - -Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module. - -A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below: - -````xml -@page -@using Volo.Abp.Account.Settings -@using Volo.Abp.Settings -@model Acme.BookStore.Web.Pages.Account.CustomLoginModel -@inject Volo.Abp.Settings.ISettingProvider SettingProvider -@if (Model.EnableLocalLogin) -{ -
-
-

@L["Login"]

- @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) - { - - @L["AreYouANewUser"] - @L["Register"] - - } -
- - -
- - - -
-
- - - -
-
- -
- @L["Login"] -
-
- - -
-} - -@if (Model.VisibleExternalProviders.Any()) -{ -
-

@L["UseAnotherServiceToLogIn"]

-
- - - @foreach (var provider in Model.VisibleExternalProviders) - { - - } -
-
-} - -@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) -{ -
- @L["InvalidLoginRequest"] - @L["ThereAreNoLoginSchemesConfiguredForThisClient"] -
-} -```` - -Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs. - -## The Source Code - -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md deleted file mode 100644 index 0d09112681..0000000000 --- a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md +++ /dev/null @@ -1,462 +0,0 @@ -# Real Time Messaging In A Distributed Architecture Using Abp Framework, SignalR & RabbitMQ - -In this article, we will build a basic real time messaging application in a distributed architecture. We will use [Abp Framework](https://abp.io) for infrastructure and tiered startup template, [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) for real time server-client communication and [RabbitMQ](https://www.rabbitmq.com/) as the distributed event bus. - -When Web & API tiers are separated, it is impossible to directly send a server-to-client message from the HTTP API. This is also true for a microservice architected application. We suggest to use the distributed event bus to deliver the message from API application to the web application, then to the client. - - -![data flow](dataflow-diagram.png) - -Above, you can see the data-flow that we will implement in this article. This diagram represents how data will flow in our application when **Client 1** sends a message to **Client 2**. It is explained in 5 steps: - -1. **Client 1** sends a message data to **Web Application** via REST call. -2. **Web Application** redirects the message data to **Http Api**. -3. The message data is processed in **Http Api** and **Http Api** publishes an event that holds the data that will be sent to **Client 2**. -4. **Web application**, that is subscribed to that event, receives it. -5. **Web Application** sends the message to **Client 2**. - -For this example flow, we could send message from **Client 1** to **Client 2** directly on the **SignalR Hub**. However, what we are trying here to demonstrate is sending a real-time message from the **Http Api** to a specific user who is connected to the web application. - -## Implementation - -### Startup template and initial run - -[Abp Framework](https://www.abp.io) offers startup templates to get into the business faster. We can download a new tiered startup template using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI): - -`abp new SignalRTieredDemo --tiered` - -After download is finished, we run ***.DbMigrator** project to create the database and seed initial data (admin user, role etc). Then we run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** to see our application working. - -### Creating Application Layer - -We create an [application service](https://docs.abp.io/en/abp/latest/Application-Services) that publishes the message as event. - -In ***.Application.Contracts** project: - -````csharp -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace SignalRTieredDemo -{ - public interface IChatAppService : IApplicationService - { - Task SendMessageAsync(SendMessageInput input); - } -} -```` -Input DTO for SendMessageAsync method: - -````csharp -namespace SignalRTieredDemo -{ - public class SendMessageInput - { - public string TargetUserName { get; set; } - - public string Message { get; set; } - } -} -```` -Event transfer object (ETO) for communication on event bus: - -````csharp -using System; - -namespace SignalRTieredDemo -{ - public class ReceivedMessageEto - { - public string ReceivedText { get; set; } - - public Guid TargetUserId { get; set; } - - public string SenderUserName { get; set; } - - public ReceivedMessageEto( - Guid targetUserId, string senderUserName, string receivedText) - { - ReceivedText = receivedText; - TargetUserId = targetUserId; - SenderUserName = senderUserName; - } - } -} - -```` - -In ***.Application** project: - -````csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Volo.Abp.EventBus.Distributed; -using Volo.Abp.Identity; - -namespace SignalRTieredDemo -{ - public class ChatAppService: SignalRTieredDemoAppService, IChatAppService - { - private readonly IIdentityUserRepository _identityUserRepository; - private readonly ILookupNormalizer _lookupNormalizer; - private readonly IDistributedEventBus _distributedEventBus; - - public ChatAppService(IIdentityUserRepository identityUserRepository, ILookupNormalizer lookupNormalizer, IDistributedEventBus distributedEventBus) - { - _identityUserRepository = identityUserRepository; - _lookupNormalizer = lookupNormalizer; - _distributedEventBus = distributedEventBus; - } - - public async Task SendMessageAsync(SendMessageInput input) - { - var targetId = (await _identityUserRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(input.TargetUserName))).Id; - - await _distributedEventBus.PublishAsync(new ReceivedMessageEto(targetId, CurrentUser.UserName, input.Message)); - } - } -} -```` - -### Creating API Layer - -We create an endpoint for sending message that redirects the process to application layer: - -In **controllers** folder of ***.HttpApi** project: - -````csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; - -namespace SignalRTieredDemo.Controllers -{ - [Route("api/app/chat")] - public class ChatController : AbpController, IChatAppService - { - private readonly IChatAppService _chatAppService; - - public ChatController(IChatAppService chatAppService) - { - _chatAppService = chatAppService; - } - - [HttpPost] - [Route("send-message")] - public async Task SendMessageAsync(SendMessageInput input) - { - await _chatAppService.SendMessageAsync(input); - } - } -} -```` -### Adding SignalR - -To add SignalR to our solution, we add `Volo.Abp.AspNetCore.SignalR` nuget package to ***.Web** project. - -And then add `AbpAspNetCoreSignalRModule` dependency: - -````csharp - -namespace SignalRTieredDemo.Web -{ - [DependsOn( - ... - typeof(AbpAspNetCoreSignalRModule) // <--- - )] - public class SignalRTieredDemoWebModule : AbpModule - { -```` - -Also, we need to add [@abp/signalr](https://www.npmjs.com/package/@abp/signalr) npm package to package.json in ***.Web** project, then run **abp install-libs** command. - -`````json -{ - . - . - "dependencies": { - . - . - "@abp/signalr": "^2.9.0" - } -} -````` - -*Remember to add the latest package version.* - -You can find more information for Abp SignalR Integration on [the related document](https://docs.abp.io/en/abp/latest/SignalR-Integration). - -### Creating A Hub - -We need a hub for SignalR connection. We can inherit it from `AbpHup` base class. - -In ***.Web** project: - -````csharp -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.AspNetCore.SignalR; - -namespace SignalRTieredDemo.Web -{ - [Authorize] - public class ChatHub : AbpHub - { - } -} - -```` - -While you could inherit from the standard `Hub` class, `AbpHub` has some common services pre-injected as base properties, which is useful on your development. - -### Adding & Configuring RabbitMQ - -To add RabbitMQ to our solution, we add `Volo.Abp.EventBus.RabbitMQ` nuget package to ***.HttpApi.Host** and ***.Web** projects. - -Launch a **command line**, navigate to directory where ***.HttpApi.Host.csproj** file exist, and run the command below using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI): - -````bash - abp add-package Volo.Abp.EventBus.RabbitMQ -```` - -Then do the same for ***.Web** project. - -After we add the package, we configure RabbitMQ by adding configuration in **appsettings.json** files of those projects. - -For ***.HttpApi.Host** project: - -````json -{ - ... - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "localhost" - } - }, - "EventBus": { - "ClientName": "SignalRTieredDemo_HttpApi", - "ExchangeName": "SignalRTieredDemoTest" - } - }, - ... -} -```` -For ***.Web** project: -````json -{ - ... - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "localhost" - } - }, - "EventBus": { - "ClientName": "SignalRTieredDemo_Web", - "ExchangeName": "SignalRTieredDemoTest" - } - }, - ... -} - -```` - -### Handling New Message Event - -Once we publish a new message event from `Http Api`, we must to handle it in `Web Application`. Therefore we need an event handler in ***.Web** Project: - -````csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus.Distributed; - -namespace SignalRTieredDemo.Web -{ - public class ReceivedMessageEventHandler : - IDistributedEventHandler, - ITransientDependency - { - private readonly IHubContext _hubContext; - - public ReceivedMessageEventHandler(IHubContext hubContext) - { - _hubContext = hubContext; - } - - public async Task HandleEventAsync(ReceivedMessageEto eto) - { - var message = $"{eto.SenderUserName}: {eto.ReceivedText}"; - - await _hubContext.Clients - .User(eto.TargetUserId.ToString()) - .SendAsync("ReceiveMessage", message); - } - } -} -```` - -### Creating Chat Page - -We create the files below in **Pages** folder of ***.Web** Project. - - **Chat.cshtml**: - -````html -@page -@using Volo.Abp.AspNetCore.Mvc.UI.Packages.SignalR -@model SignalRTieredDemo.Web.Pages.ChatModel -@section styles { - -} -@section scripts { - - -} -

Chat

- -
- - -
All Messages:
-
    -
-
- -
- - - - - - - - - - - - - - - - - -
-
-
-
-```` - - **Chat.cshtml.cs**: - -````csharp -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace SignalRTieredDemo.Web.Pages -{ - public class ChatModel : PageModel - { - public void OnGet() - { - } - } -} -```` - -**Chat.css**: - -````css -#MessageList { - border: 1px solid gray; - height: 400px; - overflow: auto; - list-style: none; - padding-left: 0; - padding: 10px; -} - -#TargetUser { - width: 100%; -} - -#Message { - width: 100%; -} -```` - -**Chat.js**: - -````javascript -$(function () { - var connection = new signalR.HubConnectionBuilder().withUrl("/signalr-hubs/chat").build(); - - connection.on("ReceiveMessage", function (message) { - console.log(message); - $('#MessageList').append('
  • ' + message + '
  • '); - }); - - connection.start().then(function () { - - }).catch(function (err) { - return console.error(err.toString()); - }); - - $('#SendMessageButton').click(function (e) { - e.preventDefault(); - - var targetUserName = $('#TargetUser').val(); - var message = $('#Message').val(); - $('#Message').val(''); - - - signalRTieredDemo.controllers.chat.sendMessage({ - targetUserName: targetUserName, - message: message - }).then(function() { - $('#MessageList') - .append('
  • ' + abp.currentUser.userName + ': ' + message + '
  • '); - }); - - }); -}); -```` - -Then we can add this new page to menu on ***MenuContributor.cs** in **Menus** folder: - -````csharp - ... - public class SignalRTieredDemoMenuContributor : IMenuContributor - { - ... - private Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - ... - context.Menu.Items.Add(new ApplicationMenuItem("SignalRDemo.Chat", "Chat", "/Chat")); // <-- We add this line - - return Task.CompletedTask; - } - ... - } -```` - -## Running & Testing - -We run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** in order. After ***.Web** project is ran, firstly login with `admin` username and `1q2w3E*` password. - -![click on login](login1.png) - -![login with `admin` username and `1q2w3E*` password.](login2.png) - -After we login, go to `/Identity/Users` page and create a new user. So that we can chat with them. - -![create a new user](new-user.png) - -Then we open the application in another browser and login with the user we created above. Now we can go to chat page and start messaging: - -![messaging](chat.png) - -We can test with more user. All sent and incoming messages are displayed in the left box. - -### Source code - -Source code of the final application can be found on the [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo). diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png deleted file mode 100644 index 1275bb7ac5..0000000000 Binary files a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png deleted file mode 100644 index c4ca5e2b14..0000000000 Binary files a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png deleted file mode 100644 index 0a06688c5b..0000000000 Binary files a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png deleted file mode 100644 index 65ec99c975..0000000000 Binary files a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png deleted file mode 100644 index f236cdfbf8..0000000000 Binary files a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png deleted file mode 100644 index 35c097f365..0000000000 Binary files a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md deleted file mode 100644 index a6fb03d6c4..0000000000 --- a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md +++ /dev/null @@ -1,396 +0,0 @@ -# File Upload/Download with BLOB Storage System in ASP.NET Core & ABP Framework - -## Introduction - -This step-by-step article describes how to upload a file to a Web server and also download by client with using ASP.NET Core & ABP Framework. By following this article, you will create a web project and its related code to upload and download files. - -Before the creating application, we need to know some fundamentals. - -## BLOB Storing - -It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. [BLOB](https://en.wikipedia.org/wiki/Binary_large_object)s, into a **storage**. For example, you may want to save user profile pictures. - -A BLOB is a typically **byte array**. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be options. - -The ABP Framework provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits; - -- You can **easily integrate** to your favorite BLOB storage provides with a few lines of configuration. -- You can then **easily change** your BLOB storage without changing your application code. -- If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored. - -ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](https://docs.abp.io/en/abp/latest/Multi-Tenancy). - -To get more information about ABP BLOB Storing system, please check this [documentation](https://docs.abp.io/en/abp/latest/Blob-Storing). - -## Preparing the Project - -### Startup template and the initial run - -Abp Framework offers startup templates to get into the business faster. We can download a new startup template using Abp CLI: - -`abp new FileActionsDemo -m none` - -After the download is finished, we run `FileActionsDemo.DbMigrator` project to create the database and seed initial data (admin user, role, etc). Then we run `FileActionsDemo.Web` to see our application working. - -> _Default admin username is **admin** and password is **1q2w3E\***_ - -![initial-project](initial-project.png) - -### Adding Blob Storing Module - -For this article, we use [Blob Storing Database Provider](https://docs.abp.io/en/abp/latest/Blob-Storing-Database). - -You can use [Azure](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure) or [File System](https://docs.abp.io/en/abp/latest/Blob-Storing-File-System) providers also. - -Open a command prompt (terminal) in the folder containing your solution (.sln) file and run the following command: - -`abp add-module Volo.Abp.BlobStoring.Database` - -This action will add the module dependencies and also module migration. After this action, run `FileActionsDemo.DbMigrator` to update the database. - -### Setting up Blob Storage - -BLOB Storage system works with `Containers`. Before the using blob storage, we need to create our blob container. - -Create a class that name `MyFileContainer` at the `FileActionsDemo.Domain` project. - -```csharp -using Volo.Abp.BlobStoring; - -namespace FileActionsDemo -{ - [BlobContainerName("my-file-container")] - public class MyFileContainer - { - - } -} -``` - -That's all, we can start to use BLOB storing in our application. - -## Creating Application Layer - -Before the creating Application Service, we need to create some [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)s that used by Application Service. - -Create following DTOs in `FileActionsDemo.Application.Contracts` project. - -- `BlobDto.cs` - - ```csharp - namespace FileActionsDemo - { - public class BlobDto - { - public byte[] Content { get; set; } - - public string Name { get; set; } - } - } - ``` - -- `GetBlobRequestDto.cs` - - ```csharp - using System.ComponentModel.DataAnnotations; - - namespace FileActionsDemo - { - public class GetBlobRequestDto - { - [Required] - public string Name { get; set; } - } - } - ``` - -- `SaveBlobInputDto.cs` - - ```csharp - using System.ComponentModel.DataAnnotations; - - namespace FileActionsDemo - { - public class SaveBlobInputDto - { - public byte[] Content { get; set; } - - [Required] - public string Name { get; set; } - } - } - ``` - -Create `IFileAppService.cs` interface at the same place with DTOs. - -- `IFileAppService` - - ```csharp - using System.Threading.Tasks; - using Volo.Abp.Application.Services; - - namespace FileActionsDemo - { - public interface IFileAppService : IApplicationService - { - Task SaveBlobAsync(SaveBlobInputDto input); - - Task GetBlobAsync(GetBlobRequestDto input); - } - } - ``` - -After creating DTOs and interface, `FileActionsDemo.Application.Contracts` project should be like as following image. - -![application-contracts-project](application-contracts-project.png) - -Then we can create our Application Service. - -Create `FileAppService.cs` in `FileActionsDemo.Application` project. - -```csharp -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using Volo.Abp.BlobStoring; - -namespace FileActionsDemo -{ - public class FileAppService : ApplicationService, IFileAppService - { - private readonly IBlobContainer _fileContainer; - - public FileAppService(IBlobContainer fileContainer) - { - _fileContainer = fileContainer; - } - - public async Task SaveBlobAsync(SaveBlobInputDto input) - { - await _fileContainer.SaveAsync(input.Name, input.Content, true); - } - - public async Task GetBlobAsync(GetBlobRequestDto input) - { - var blob = await _fileContainer.GetAllBytesAsync(input.Name); - - return new BlobDto - { - Name = input.Name, - Content = blob - }; - } - } -} -``` - -As you see in previous code block, we inject `IBlobContainer` to our app service. It will handle all blob actions for us. - -- `SaveBlobAsync` method uses `SaveAsync` of `IBlobContainer` to save the given blob to storage, this is a simple example so we don't check is there any file exist with same name. We sent blob name, blob content and `true` for `overrideExisting` parameter. - -- `GetBlobAsync` method is uses `GetAllBytesAsync` of `IBlobContainer` to get blob content by name. - -We finished the application layer for this project. After that we will create a `Controller` for API and `Razor Page` for UI. - -## Creating Controller - -Create `FileController.cs` in your `FileActionsDemo.HttpApi` project. - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; - -namespace FileActionsDemo -{ - public class FileController : AbpController - { - private readonly IFileAppService _fileAppService; - - public FileController(IFileAppService fileAppService) - { - _fileAppService = fileAppService; - } - - [HttpGet] - [Route("download/{fileName}")] - public async Task DownloadAsync(string fileName) - { - var fileDto = await _fileAppService.GetBlobAsync(new GetBlobRequestDto{ Name = fileName }); - - return File(fileDto.Content, "application/octet-stream", fileDto.Name); - } - } -} -``` - -As you see, `FileController` injects `IFileAppService` that we defined before. This controller has only one endpoint. - -`DownloadAsync` is using to send file from server to client. - -This endpoint is requires only a `string` parameter, then we use that parameter to get stored blob. If blob is exist, we return a `File` result so download process can start. - -## Creating User Interface - -We will create only one page to prove download and upload actions are working. - -Create folder that name `Files` in your `Pages` folder at `FileActionsDemo.Web` project. - -Create a Razor page that name `Index` with its model. - -- `Index.cshtml.cs` - -```csharp -using System.ComponentModel.DataAnnotations; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; - -namespace FileActionsDemo.Web.Pages.Files -{ - public class Index : AbpPageModel - { - [BindProperty] - public UploadFileDto UploadFileDto { get; set; } - - private readonly IFileAppService _fileAppService; - - public bool Uploaded { get; set; } = false; - - public Index(IFileAppService fileAppService) - { - _fileAppService = fileAppService; - } - - public void OnGet() - { - - } - - public async Task OnPostAsync() - { - using (var memoryStream = new MemoryStream()) - { - await UploadFileDto.File.CopyToAsync(memoryStream); - - await _fileAppService.SaveBlobAsync( - new SaveBlobInputDto - { - Name = UploadFileDto.Name, - Content = memoryStream.ToArray() - } - ); - } - - return Page(); - } - } - - public class UploadFileDto - { - [Required] - [Display(Name = "File")] - public IFormFile File { get; set; } - - [Required] - [Display(Name = "Filename")] - public string Name { get; set; } - } -} -``` - -As you see, we use `UploadFileDto` as a `BindProperty` and we inject `IFileAppService` to upload files. - -The `UploadFileDto` is requires a `string` parameter for using as a blob name and a `IFormFile` that sent by user. - -At the post action (`OnPostAsync`), if everything is well, we use `MemoryStream` to get all bytes from file content. - -Then we save file with `SaveBlobAsync` method of `IFileAppService`. - -- `Index.cshtml` - -```csharp -@page -@model FileActionsDemo.Web.Pages.Files.Index - -@section scripts{ - -} - - - -

    File Upload and Download

    -
    - - - -

    Upload File

    -
    -
    - - - - - -
    -
    - - -

    Download File

    -
    -
    -
    - * - -
    - - -
    -
    -
    -
    -
    -``` - -We divided the page vertically, left side will be using for upload and right side will be using for download. We use [ABP Tag Helpers](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Tag-Helpers/Index) to create page. - -- `index.js` - -```javascript -$(function () { - var DOWNLOAD_ENDPOINT = "/download"; - - var downloadForm = $("form#DownloadFile"); - - downloadForm.submit(function (event) { - event.preventDefault(); - - var fileName = $("#fileName").val().trim(); - - var downloadWindow = window.open( - DOWNLOAD_ENDPOINT + "/" + fileName, - "_blank" - ); - downloadWindow.focus(); - }); - - $("#UploadFileDto_File").change(function () { - var fileName = $(this)[0].files[0].name; - - $("#UploadFileDto_Name").val(fileName); - }); -}); -``` - -This jQuery codes are using for download. Also we wrote a simple code to autofill `Filename` input when user selects a file. - -After creating razor page and js file, `FileActionsDemo.Web` project should be like as following image. - -![web-project](web-project.png) - -## Result - -After completing code tutorial, run `FileActionsDemo.Web` project and go `/Files`. You can upload any file with any name and also download those uploaded files. - -![file-up](file-upload-result.gif) diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png deleted file mode 100644 index 446e04481f..0000000000 Binary files a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif deleted file mode 100644 index e59ee890c6..0000000000 Binary files a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png deleted file mode 100644 index ad4c8587b0..0000000000 Binary files a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png deleted file mode 100644 index 5594810dfa..0000000000 Binary files a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md b/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md deleted file mode 100644 index 8a34caf2db..0000000000 --- a/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md +++ /dev/null @@ -1,264 +0,0 @@ -# Implementing Passwordless Authentication in ASP.NET Core Identity - -## Introduction - -In this tutorial, we will show you how to add a custom token provider to authenticate a user with a link, instead of entering the password. - -This can be useful especially if you want to make someone login to the application with your user, without sharing your secret password. The generated link will be for a single use. - -### Source Code - -The completed sample is available on [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication). - -## Creating the Solution - -Before starting the development, create a new solution named `PasswordlessAuthentication` and run it by following the [getting started tutorial](https://docs.abp.io/en/abp/latest/Getting-Started?UI=MVC&DB=EF&Tiered=No). - -## Step-1 - -Create a class named **PasswordlessLoginProvider** in your ***.Web** project: - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; - -namespace PasswordlessAuthentication.Web -{ - public class PasswordlessLoginProvider : TotpSecurityStampBasedTokenProvider - where TUser : class - { - public override Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) - { - return Task.FromResult(false); - } - - //We need to override this method as well. - public async override Task GetUserModifierAsync(string purpose, UserManager manager, TUser user) - { - var userId = await manager.GetUserIdAsync(user); - - return "PasswordlessLogin:" + purpose + ":" + userId; - } - } -} -``` -## Step-2 - -Create **IdentityBuilderExtensions.cs** in your ***.Web** project. We will use this extension method in the `ConfigureServices`. - -```csharp -using Microsoft.AspNetCore.Identity; - -namespace PasswordlessAuthentication.Web -{ - public static class IdentityBuilderExtensions - { - public static IdentityBuilder AddPasswordlessLoginProvider(this IdentityBuilder builder) - { - var userType = builder.UserType; - var totpProvider = typeof(PasswordlessLoginProvider<>).MakeGenericType(userType); - return builder.AddTokenProvider("PasswordlessLoginProvider", totpProvider); - } - } -} -``` -## Step-3 - -Add the token provider to the `Identity` middleware. To do this, find the module class (eg: `PasswordlessAuthenticationWebModule.cs` in here) in your ***.Web** project and add the below into the `ConfigureServices()` method. - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - //... - context.Services - .GetObject() - .AddDefaultTokenProviders() - .AddPasswordlessLoginProvider(); -} -``` - -## Step-4 - -We need to create a user interface to be able to generate the magic login link. To do this quickly, open your existing **Index.cshtml.cs** in your ***.Web** project. It's under `Pages` folder. And copy-paste the below content. - -**Index.cshtml.cs** - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.Identity; - -namespace PasswordlessAuthentication.Web.Pages -{ - public class IndexModel : PasswordlessAuthenticationPageModel - { - protected IdentityUserManager UserManager { get; } - - private readonly IIdentityUserRepository _userRepository; - - public string PasswordlessLoginUrl { get; set; } - - public string Email { get; set; } - - public IndexModel(IdentityUserManager userManager, IIdentityUserRepository userRepository) - { - UserManager = userManager; - _userRepository = userRepository; - } - - public ActionResult OnGet() - { - if (!CurrentUser.IsAuthenticated) - { - return Redirect("/Account/Login"); - } - - return Page(); - } - - //added for passwordless authentication - public async Task OnPostGeneratePasswordlessTokenAsync() - { - var adminUser = await _userRepository.FindByNormalizedUserNameAsync("admin"); - - var token = await UserManager.GenerateUserTokenAsync(adminUser, "PasswordlessLoginProvider", - "passwordless-auth"); - - PasswordlessLoginUrl = Url.Action("Login", "Passwordless", - new {token = token, userId = adminUser.Id.ToString()}, Request.Scheme); - - return Page(); - } - } -} -``` - -We added `OnPostGeneratePasswordlessTokenAsync()` action to generate the link. We will generate a link for the **admin** user. Therefore, we injected `IIdentityUserRepository` to get admin user Id. Using the `UserManager.GenerateUserTokenAsync()` method, we generated a token. After that, we created the URL with the admin user Id and the token. Now we will show the `PasswordlessLoginUrl` on the page. - -## Step-5 - -Create a class named **PasswordlessAuthenticationMenus** under `Menus` folder in your ***.Web** project. And set the content as below. - -```csharp -namespace PasswordlessAuthentication.Web.Menus -{ - public class PasswordlessAuthenticationMenus - { - public const string GroupName = "PasswordlessAuthentication"; - - public const string Home = GroupName + ".Home"; - } -} -``` - -## Step-6 - -Open your **Index.cshtml** and set the content as below. We added a form that posts to `GeneratePasswordlessToken` action in the razor page. And it will set the `PasswordlessLoginUrl` field. - -```html -@page -@using MyBookStore.Web.Menus -@using Volo.Abp.AspNetCore.Mvc.UI.Layout -@model MyBookStore.Web.Pages.IndexModel -@using Microsoft.AspNetCore.Mvc.Localization -@using MyBookStore.Localization -@inject IHtmlLocalizer L -@{ - ViewBag.PageTitle = "Home"; -} -@inject IPageLayout PageLayout -@{ - PageLayout.Content.Title = L["Home"].Value; - PageLayout.Content.BreadCrumb.Add(L["Menu:Home"].Value); - PageLayout.Content.MenuItemName = MyBookStoreMenus.Home; -} - - -
    - - Generate passwordless token link - - @if (Model.PasswordlessLoginUrl != null) - { - - @Model.PasswordlessLoginUrl - - } -
    -
    -
    -``` -## Step-7 - -We implemented token generation infrastructure, now it's time validate the token and let the user in. To do this create a folder named `Controllers` in your ***.Web** project and create a controller, named **PasswordlessController** inside it: - -```csharp -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.Identity; -using Volo.Abp.Identity.AspNetCore; - -namespace PasswordlessAuthentication.Web.Controllers -{ - public class PasswordlessController : AbpController - { - protected IdentityUserManager UserManager { get; } - - protected AbpSignInManager SignInManager { get; } - - public PasswordlessController(IdentityUserManager userManager, AbpSignInManager signInManager) - { - UserManager = userManager; - SignInManager = signInManager; - } - - public virtual async Task Login(string token, string userId) - { - var user = await UserManager.FindByIdAsync(userId); - - var isValid = await UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token); - if (!isValid) - { - throw new UnauthorizedAccessException("The token " + token + " is not valid for the user " + userId); - } - - await UserManager.UpdateSecurityStampAsync(user); - - await SignInManager.SignInAsync(user, isPersistent: false); - - return Redirect("/"); - } - } -} -``` - -We created an endpoint for `/Passwordless/Login` that gets the token and the user Id. In this action, we find the user via repository and validate the token via `UserManager.VerifyUserTokenAsync()` method. If it's valid, we call `SignInManager.SignInAsync` to be able to create an encrypted cookie and add it to the current response. Finally we redirect the page to the root URL. - -That's all! We created a passwordless login with 7 steps. - -## Source Code - -The completed sample is available on [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication). - -〰️〰️〰️ - -Happy Coding 🤗 - ---- - -I'm Alper Ebicoglu 🧑🏽‍💻 ABP Framework Core Team Member - -Follow me for the latest news about .NET and software development: - -📌 [twitter.com/alperebicoglu](https://twitter.com/alperebicoglu) - -📌 [github.com/ebicoglu](https://github.com/ebicoglu) - -📌 [linkedin.com/in/ebicoglu](https://www.linkedin.com/in/ebicoglu) - -📌 [medium.com/@alperonline](https://medium.com/@alperonline) - - ---- diff --git a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/Block-Third-Party-Cookies.png b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/Block-Third-Party-Cookies.png deleted file mode 100644 index 5163ed6c0f..0000000000 Binary files a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/Block-Third-Party-Cookies.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md deleted file mode 100644 index f7483208f8..0000000000 --- a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md +++ /dev/null @@ -1,130 +0,0 @@ -# How to fix the Chrome login issue for the IdentityServer4 - -## Introduction - -When you use HTTP on your Identity Server 4 enabled website, users may not login because of the changes made by Chrome in the version 8x. This occurs when you use HTTP schema in your website. The issue is explained here https://docs.microsoft.com/en-gb/dotnet/core/compatibility/3.0-3.1#http-browser-samesite-changes-impact-authentication - -## How to solve it? - -### Step-1 - -Create the below extension in your ***.Web** project. - -```csharp -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class SameSiteCookiesServiceCollectionExtensions - { - public static IServiceCollection AddSameSiteCookiePolicy(this IServiceCollection services) - { - services.Configure(options => - { - options.MinimumSameSitePolicy = SameSiteMode.Unspecified; - options.OnAppendCookie = cookieContext => - CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); - options.OnDeleteCookie = cookieContext => - CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); - }); - - return services; - } - - private static void CheckSameSite(HttpContext httpContext, CookieOptions options) - { - if (options.SameSite == SameSiteMode.None) - { - var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); - if (!httpContext.Request.IsHttps || DisallowsSameSiteNone(userAgent)) - { - // For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1) - options.SameSite = SameSiteMode.Unspecified; - } - } - } - - private static bool DisallowsSameSiteNone(string userAgent) - { - // Cover all iOS based browsers here. This includes: - // - Safari on iOS 12 for iPhone, iPod Touch, iPad - // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad - // - Chrome on iOS 12 for iPhone, iPod Touch, iPad - // All of which are broken by SameSite=None, because they use the iOS networking stack - if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12")) - { - return true; - } - - // Cover Mac OS X based browsers that use the Mac OS networking stack. This includes: - // - Safari on Mac OS X. - // This does not include: - // - Chrome on Mac OS X - // Because they do not use the Mac OS networking stack. - if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && - userAgent.Contains("Version/") && userAgent.Contains("Safari")) - { - return true; - } - - // Cover Chrome 50-69, because some versions are broken by SameSite=None, - // and none in this range require it. - // Note: this covers some pre-Chromium Edge versions, - // but pre-Chromium Edge does not require SameSite=None. - if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")) - { - return true; - } - - return false; - } - } -} -``` - -### Step-2 - -Assume that your project name is *Acme.BookStore*. Then open `AcmeBookStoreWebModule.cs` class. - -Add the following line to `ConfigureServices()` method. - -```csharp - context.Services.AddSameSiteCookiePolicy(); // cookie policy to deal with temporary browser incompatibilities -``` -### Step-3 - -Go to`OnApplicationInitialization()` method in `AcmeBookStoreWebModule.cs` add `app.UseCookiePolicy();` - -```csharp -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - var app = context.GetApplicationBuilder(); - var env = context.GetEnvironment(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseErrorPage(); - app.UseHsts(); - } - - app.UseCookiePolicy(); // added this, Before UseAuthentication or anything else that writes cookies. - - //.... -} -``` - -It's all! You are ready to go! - -> Attention: This problem can't be solved if the user/browser/operating system blocked third-party cookies. - -![Block-Third-Party-Cookies](Block-Third-Party-Cookies.png) - ---- - -Referenced from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/Using-DevExtreme-Components-With-The-ABP-Framework.md b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/Using-DevExtreme-Components-With-The-ABP-Framework.md deleted file mode 100644 index fdaaefbea1..0000000000 --- a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/Using-DevExtreme-Components-With-The-ABP-Framework.md +++ /dev/null @@ -1,310 +0,0 @@ -## Using DevExtreme Components With the ABP Framework - -Hi, in this step by step article, I will show you how to integrate [DevExtreme](https://js.devexpress.com/) components into ABP Framework-based applications. - -![both-example-result](both-example-result.png) - -*(A screenshot from the example application developed in this article)* - -## Create the Project - -ABP Framework offers startup templates to get into the business faster. We can download a new startup template using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -````bash -abp new DevExtremeSample -```` - -After the download is finished, open the solution in the Visual Studio (or your favorite IDE): - -![initial-project](initial-project.png) - -Run the `DevExtremeSample.DbMigrator` application to create the database and seed initial data (which creates the admin user, admin role, related permissions, etc). Then we can run the `DevExtremeSample.Web` project to see our application working. - -> _Default admin username is **admin** and password is **1q2w3E\***_ - -## Install DevExtreme - -You can follow [this documentation](https://js.devexpress.com/Documentation/17_1/Guide/ASP.NET_MVC_Controls/Prerequisites_and_Installation/) to install DevExpress packages into your computer. - -> Don't forget to add _"DevExpress NuGet Feed"_ to your **Nuget Package Sources**. - -### Adding DevExtreme NuGet Packages - -Add the `DevExtreme.AspNet.Core` NuGet package to the `DevExtremeSample.Application.Contracts` project. - -``` -Install-Package DevExtreme.AspNet.Core -``` - -Add the `DevExtreme.AspNet.Data` package to your `DevExtremeSample.Web` project. - -``` -Install-Package DevExtreme.AspNet.Data -``` - -### Adding DevExtreme NPM Dependencies - -Open your `DevExtremeSample.Web` project folder with a command line and add `devextreme` and `devextreme-aspnet-data` NPM packages: - -````bash -npm install devextreme -```` - -````bash -npm install devextreme-aspnet-data -```` - -### Adding Resource Mappings - -The `devextreme` and `devextreme-aspnet-data` NPM packages are saved under `node_modules` folder. We need to move the needed files in our `wwwroot/libs` folder to use them in our web project. We can do it using the ABP [client side resource mapping](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Client-Side-Package-Management) system. - -Open the `abp.resourcemapping.js` file in your `DevExtremeSample.Web` project and add the following definitions to inside `mappings` object. - -````json -"@node_modules/devextreme/dist/**/*": "@libs/devextreme/", -"@node_modules/devextreme-aspnet-data/js/dx.aspnet.data.js": "@libs/devextreme/js/" -```` - -The final `abp.resourcemapping.js` file should look like below: - -``` -module.exports = { - aliases: {}, - mappings: { - "@node_modules/devextreme/dist/**/*": "@libs/devextreme/", - "@node_modules/devextreme-aspnet-data/js/dx.aspnet.data.js": "@libs/devextreme/" - }, -}; -``` - -Open your `DevExtremeSample.Web` project folder with a command line and run the `abp install-libs` command. This command will copy the needed library files into the `/wwwroot/libs/devextreme/` folder. - -````bash -abp install-libs -```` - -You can see `devextreme` folder inside the `wwwroot/libs`: - -![wwwroot-lib](wwwroot-lib.png) - -### Adding DevExtremeStyleContributor - -We will add DevExtreme CSS files to the global bundle by creating a [bundle contributor](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification). - -Create a `Bundling` folder in the `DevExtremeSample.Web` project and a `DevExtremeStyleContributor.cs` file with the following content: - -```csharp -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace DevExtremeSample.Web.Bundling -{ - public class DevExtremeStyleContributor : BundleContributor - { - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devextreme/css/dx.common.css"); - context.Files.AddIfNotContains("/libs/devextreme/css/dx.light.css"); - } - } -} -``` - -> You can choose another theme than the light theme. Check the `/libs/devextreme/css/` folder and the DevExtreme documentation for other themes. - -Open your `DevExtremeSampleWebModule.cs` file in your `DevExtremeSample.Web` project and add following code into the `ConfigureServices` method: - -```csharp -Configure(options => -{ - options - .StyleBundles - .Get(StandardBundles.Styles.Global) - .AddContributors(typeof(DevExtremeStyleContributor)); -}); -``` - -### Adding DevExtremeScriptContributor - -We can not add DevExtreme js packages to Global Script Bundles, just like done for the CSS files. Because DevExtreme requires to add its JavaScript files into the `` section of the HTML document, while ABP Framework adds all JavaScript files to the end of the `` (as a best practice). - -Fortunately, ABP Framework has a [layout hook system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface#layout-hooks) that allows you to add any code into some specific positions in the HTML document. All you need to do is to create a `ViewComponent` and configure the layout hooks. - -Let's begin by creating a `DevExtremeScriptContributor.cs` file in the `Bundling` folder by copying the following code inside it: - -```csharp -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; -using Volo.Abp.Modularity; - -namespace DevExtremeSample.Web.Bundling -{ - [DependsOn( - typeof(JQueryScriptContributor) - )] - public class DevExtremeScriptContributor : BundleContributor - { - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devextreme/js/dx.all.js"); - context.Files.AddIfNotContains("/libs/devextreme/js/dx.aspnet.mvc.js"); - context.Files.AddIfNotContains("/libs/devextreme/js/dx.aspnet.data.js"); - } - } -} -``` - -As you see, the `DevExtremeScriptContributor` depends on `JQueryScriptContributor` which adds JQuery related files before the DevExpress packages (see the [bundling system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification) for details). - -#### Create DevExtremeJsViewComponent - -Create a new view component, named `DevExtremeJsViewComponent` inside the `/Components/DevExtremeJs` folder of the Web project, by following the steps below: - -1) Create a `DevExtremeJsViewComponent` class inside the `/Components/DevExtremeJs` (create the folders first): - -```csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; - -namespace DevExtremeSample.Web.Components.DevExtremeJs -{ - public class DevExtremeJsViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View("/Components/DevExtremeJs/Default.cshtml"); - } - } -} -``` - -2) Create `Default.cshtml` file in the same folder with the following content: - -```csharp -@using DevExtremeSample.Web.Bundling -@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling - - -``` - -Your final Web project should be like the following: - -![devextreme-js](devextreme-js.png) - -3) Now, we can add this view component to `` section by using the layout hooks. - -Open your `DevExtremeSampleWebModule.cs` file in your `DevExtremeSample.Web` project and add following code into the `ConfigureServices` method: - -```csharp -Configure(options => -{ - options.Add( - LayoutHooks.Head.Last, //The hook name - typeof(DevExtremeJsViewComponent) //The component to add - ); -}); -``` - -#### Known Issue: Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object. - -> This issue does exist in the ABP Framework v3.0 and earlier versions. If you are using ABP Framework v3.1 or a later version, you can skip this section. - -When you run your `*.Web` project, you will see an exception (`Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object.`) at your console. - -To fix that issue, download this file [abp.jquery.js](https://github.com/abpframework/abp/blob/dev/npm/packs/jquery/src/abp.jquery.js) and replace with the `wwwroot/libs/abp/jquery/abp.jquery.js` file of your Web project. - -### Result - -The installation step was done. You can use any DevExtreme component in your application. - -Example: A button and a progress bar component: - -![devexp-result](devexp-result.gif) - -This example has been created by following [this documentation](https://js.devexpress.com/Demos/WidgetsGallery/Demo/ProgressBar/Overview/NetCore/Light/). - -## The Sample Application - -We have created a sample application with [Tree List](https://demos.devexpress.com/ASPNetCore/Demo/TreeList/Overview/) and [Data Grid](https://demos.devexpress.com/ASPNetCore/Demo/DataGrid/Overview/) examples. - -### The Source Code - -You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Mvc). - -### Data Grid - -You can see the full working example of [Data Grid](https://demos.devexpress.com/ASPNetCore/Demo/DataGrid/Overview/). - -![data-grid-final](data-grid-final.png) - -The related files for this example are highlighted at the following screenshots. - -![data-grid-app-contract](data-grid-app-contract.png) - -![data-grid-application](data-grid-application.png) - -![data-grid-web](data-grid-web.png) - -### Tree List - - -You can see the full working example of [Tree List](https://demos.devexpress.com/ASPNetCore/Demo/TreeList/Overview/). - -![tree-list-final](tree-list-final.png) - -The related files for this example are highlighted at the following screenshots. - -![tree-list-app-contract](tree-list-app-contract.png) - -![tree-list-application](tree-list-application.png) - -![tree-list-web](tree-list-web.png) - -### Additional Notes - -#### Data Storage - -I've used an in-memory list to store data for this example, instead of a real database. Because it is not related to DevExpress usage. There is a `SampleDataService.cs` file in `Data` folder at `.Application.Contracts` project. All the data is stored here. - -#### JSON Serialization - -You can see some `JsonProperty` attributes on the DTO properties. I use these attributes because DevExtreme example expects `PascalCase` property names in the serialized JSON that is sent to the client. But ABP Framework & ASP.NET Core conventionally uses `camelCase` property names on JSON serialization. Adding these `JsonProperty` attributes ensures that the related properties are serialized as `PascalCase`. - -#### DevExtreme Components vs Application Service Methods - -ABP Framework conventionally converts application services to API Controllers. For example, see the application service below: - -````csharp -public class OrderAppService : DevExtremeSampleAppService, IOrderAppService -{ - public async Task GetOrdersAsync(DataSourceLoadOptions loadOptions) - { - ... - } - - public async Task InsertOrder(string values) - { - ... - } - ... -} -```` - -You can use these service methods for your DevExtreme components as shown below: - -```csharp -Html.DevExtreme().DataGrid() - .DataSource(d => d.Mvc() - .Controller("Order") // Application Service Name without 'AppService' - .LoadAction("GetOrders") // Method Name without 'Async' - .InsertAction("InsertOrder") - .UpdateAction("UpdateOrder") - .DeleteAction("DeleteOrder") - .Key("OrderID") - ) -``` - -## Conclusion - -In this article, I've explained how to use [DevExtreme](https://js.devexpress.com/) components in your application. ABP Framework is designed so that it can work with any UI library/framework. diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/both-example-result.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/both-example-result.png deleted file mode 100644 index 528a4aa991..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/both-example-result.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-app-contract.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-app-contract.png deleted file mode 100644 index eb93d0d9f2..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-app-contract.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-application.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-application.png deleted file mode 100644 index 0c841852fa..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-application.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-final.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-final.png deleted file mode 100644 index 22cc25438e..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-final.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-web.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-web.png deleted file mode 100644 index 7fe5a1e7a5..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/data-grid-web.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devexp-result.gif b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devexp-result.gif deleted file mode 100644 index 03b0875468..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devexp-result.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devextreme-js.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devextreme-js.png deleted file mode 100644 index f20cec716e..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/devextreme-js.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/gulp.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/gulp.png deleted file mode 100644 index f72166732a..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/gulp.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/initial-project.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/initial-project.png deleted file mode 100644 index 4990d9df96..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/initial-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-app-contract.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-app-contract.png deleted file mode 100644 index 016e38b873..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-app-contract.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-application.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-application.png deleted file mode 100644 index 8396243546..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-application.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-final.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-final.png deleted file mode 100644 index 0dcc3d891c..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-final.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-web.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-web.png deleted file mode 100644 index 22d6dba8a7..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/tree-list-web.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/wwwroot-lib.png b/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/wwwroot-lib.png deleted file mode 100644 index 1d29a0093c..0000000000 Binary files a/docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/wwwroot-lib.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md deleted file mode 100644 index 3fd6b845a9..0000000000 --- a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md +++ /dev/null @@ -1,83 +0,0 @@ -# ABP Suite: How to Add the User Entity as a Navigation Property of Another Entity - -## Introduction - -[ABP Suite](https://commercial.abp.io/tools/suite), a part of the [ABP Commercial](https://commercial.abp.io/), is a productivity tool developed by the team behind the ABP Framework. The main functionality of the ABP Suite is to generate code for you. - -In this post, I'll show you how to add the user entity as a navigation property in your new entity, by the help of the ABP Suite. - -> In the sample project MVC UI is used, but the same steps are applicable to the Angular UI as well. - -## Code Generation - -### Create a New Entity - -Open the ABP Suite ([see how](https://docs.abp.io/en/commercial/latest/abp-suite/index)). Create a new entity called `Note`, as an example entity. - -![create-note-entity](create-note-entity.jpg) - -Then add a string property called `Title`, as an example property. - -![add-simple-property](add-simple-property.jpg) - -### Create AppUserDto - -_Note that, creating `AppUserDto` is not necessary after ABP v4.X_ - -ABP Suite needs a DTO for the target entity (user, in this case) in order to define a navigation property. - -To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`. - -![create-appuserdto](create-appuserdto.jpg) - -We should define the [object mapping](https://docs.abp.io/en/abp/latest/Object-To-Object-Mapping) to be able to convert the `AppUser` objects to `AppUserDto` objects. To do this, open `YourProjectApplicationAutoMapperProfile.cs` and add the below line: - -```csharp -CreateMap().Ignore(x => x.ExtraProperties); -``` - -![create-mapping](create-mapping.jpg) - -> Creating such a DTO class may not be needed for another entity than the `AppUser`, since it will probably be already available, especially if you had created the other entity using the ABP Suite. - -### Define the Navigation Property - -Get back to ABP Suite, open the **Navigation Properties** tab of the ABP Suite, click the **Add Navigation Property** button. Browse `AppUser.cs` in `*.Domain\Users` folder. Then choose the `Name` item as display property. Browse `AppUserDto.cs` in `*.Contracts\Users` folder. Choose `Users` from Collection Names dropdown. - -![add-user-navigation](add-user-navigation.jpg) - -### Generate the Code! - -That's it! Click **Save and generate** button to create your page. You'll see the following page if everything goes well. - -![final-page](final-page.jpg) - -This is the new page that has been created by the ABP Suite. It can perform the fundamental CRUD operations. Also, it has the "App user" column that shows the related user name (you can easily change the automatically created "App user" title from the **Entity Name** field of the navigation property creation screen). - -**Picking Users from Look Up Table** - -We used dropdown element to select a user from the user list. If you have a lot of users, then it's good to pick a user from a look up table. A look up table is a modal window that lets you filter data and pick one. To do this, get back to Suite and click **Edit** button of user navigation which is set as `AppUserId` name. Choose "Modal" from the "UI Pick Type" field. Then click **Save and generate** button to recreate your page. - -![ui-pick-type-modal](ui-pick-type-modal.jpg) - -After successful code generation, you'll see the the user can be picked from user table. - -![ui-pick-type-modal2](ui-pick-type-modal2.jpg) - -## About the ABP Commercial RC - -This example has been implemented with **ABP Commercial 3.1.0**. If you have not installed the ABP CLI and ABP Suite, follow the next steps: - -1- Uninstall the current version of the CLI and install: - -```bash -dotnet tool install --global Volo.Abp.Cli --version 3.1.0 -``` - -2- Uninstall the current version of the Suite and install: - -```bash -dotnet tool uninstall --global Volo.Abp.Suite && dotnet tool install -g Volo.Abp.Suite --version 3.1.0 --add-source https://nuget.abp.io//v3/index.json -``` - -Don't forget to replace the `` with your own key! diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-simple-property.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-simple-property.jpg deleted file mode 100644 index 4d86936b11..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-simple-property.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-user-navigation.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-user-navigation.jpg deleted file mode 100644 index 5ba338cc98..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/add-user-navigation.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-appuserdto.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-appuserdto.jpg deleted file mode 100644 index e7bb5d27ab..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-appuserdto.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-mapping.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-mapping.jpg deleted file mode 100644 index f865e7b16d..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-mapping.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-note-entity.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-note-entity.jpg deleted file mode 100644 index e44e692180..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/create-note-entity.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/final-page.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/final-page.jpg deleted file mode 100644 index 3282c0148b..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/final-page.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal.jpg deleted file mode 100644 index f616177a2b..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal2.jpg b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal2.jpg deleted file mode 100644 index fe0b90b658..0000000000 Binary files a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/ui-pick-type-modal2.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md deleted file mode 100644 index f412ca3170..0000000000 --- a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md +++ /dev/null @@ -1,477 +0,0 @@ -# Replacing Email Templates and Sending Emails - -## Introduction - -Hi, in this step by step article, we will send an email by using standard email template and then we will replace the standard email template with our new created template, thanks to [Text Templating System](https://docs.abp.io/en/abp/latest/Text-Templating#replacing-the-existing-templates) and [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). Let's start by explaining what these systems do. - -* ABP framework provides a strong and flexible [Text Templating System](https://docs.abp.io/en/abp/latest/Text-Templating). So, we can use the text templating system to create dynamic email contents on a template and a model. - -* In this article, we will use `StandardEmailTemplates.Message` as standard email template. Then we will create a new template and replace the standard email template with our new template by using [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). - -* The `Virtual File System` makes it possible to manage files that do not physically exist on the file system. That means we can override `StandardEmailTemplates.Message` template by changing it's path with our new template's path. - -## Creating the Solution - -> ABP Framework offers startup templates to get into the business faster. - -In this article, I will create a new startup template and perform the operations on this template. But if you already have a project you don't need to create a new startup template, you can implement the following steps to your existing project. (These steps can be applied to any project. (MVC, Angular etc.)) - -> If you have already a project you can skip this section. - -Before starting to development, we will create a solution named `TemplateReplace` (or whatever you want). We can create a new startup template by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) : - -````bash -abp new TemplateReplace -```` - -Our project boilerplate will be ready after the download is finished. Then, open the solution in the Visual Studio (or your favorite IDE). - -Run the `TemplateReplace.DbMigrator` application as below to create the database and seed initial data (which creates the admin user, admin role, permissions etc.). - -![db-migrator-1](db-migrator-1.jpg) - -* Right click to `TemplateReplace.DbMigrator` and choose the `Debug`. - -![db-migrator-2](db-migrator-2.jpg) - -* After that, click the `Start new instance` option to start the database migrations. - -![db-migrator-3](db-migrator-3.jpg) - -Then we can run the `TemplateReplace.Web` project to see our application working. - -> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_ - -## Starting the Development - -First thing we need to do is, creating a email service to sending emails. ABP Framework provides `IEmailSender` service that is used to send emails. - -### Step - 1 - -Create an `Emailing` folder in the `TemplateReplace.Domain` project and add a class named `EmailService` inside of it. - -```csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.TextTemplating; - -namespace TemplateReplace.Emailing -{ - public class EmailService : ITransientDependency - { - private readonly IEmailSender _emailSender; - private readonly ITemplateRenderer _templateRenderer; - - public EmailService(IEmailSender emailSender, ITemplateRenderer templateRenderer) - { - _emailSender = emailSender; - _templateRenderer = templateRenderer; - } - - public async Task SendAsync(string targetEmail) - { - var emailBody = await _templateRenderer.RenderAsync( - StandardEmailTemplates.Message, - new - { - message = "ABP Framework provides IEmailSender service that is used to send emails." - } - ); - - await _emailSender.SendAsync( - targetEmail, - "Subject", - emailBody - ); - } - } -} -``` - -* To create an email content, we need to inject `ITemplateRenderer` and use the `RenderAsync` method to render a template. - -* We've used `StandardEmailTemplates.Message` as standart email template. This provides us a standard and simple message template to send mails. - -* The resulting email body should be like shown below: - -```html - - - - - - - ABP Framework provides IEmailSender service that is used to send emails. - - -``` - -### Step - 2 (Configuring Email Settings) - -* Now, we need to configure some email settings by following [settings documentation](https://docs.abp.io/en/abp/latest/Settings#setting-values-in-the-application-configuration). For achieve this, open the `appsettings.json` file under `TemplateReplace.Web` and configure your email settings in **settings** section like below. - -![appsettings.json](settings.jpg) - -* Here, I used Google's SMTP settings to send emails via Gmail. You can change these setting values by your need. - -> **Note:** If you want to use Google's SMTP server settings and send emails via Gmail, you should confirm [this](https://myaccount.google.com/u/0/lesssecureapps). - -### Step - 3 - -* After that we need to open `TemplateReplaceDomainModule.cs` file and change its contents as below to sending real-time emails. - -```csharp -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using TemplateReplace.MultiTenancy; -using Volo.Abp.AuditLogging; -using Volo.Abp.BackgroundJobs; -using Volo.Abp.Emailing; -using Volo.Abp.FeatureManagement; -using Volo.Abp.Identity; -using Volo.Abp.IdentityServer; -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; -using Volo.Abp.PermissionManagement.Identity; -using Volo.Abp.PermissionManagement.IdentityServer; -using Volo.Abp.SettingManagement; -using Volo.Abp.TenantManagement; - -namespace TemplateReplace -{ - [DependsOn( - typeof(TemplateReplaceDomainSharedModule), - typeof(AbpAuditLoggingDomainModule), - typeof(AbpBackgroundJobsDomainModule), - typeof(AbpFeatureManagementDomainModule), - typeof(AbpIdentityDomainModule), - typeof(AbpPermissionManagementDomainIdentityModule), - typeof(AbpIdentityServerDomainModule), - typeof(AbpPermissionManagementDomainIdentityServerModule), - typeof(AbpSettingManagementDomainModule), - typeof(AbpTenantManagementDomainModule), - typeof(AbpEmailingModule) - )] - public class TemplateReplaceDomainModule : AbpModule - { - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - var settingManager = context.ServiceProvider.GetService(); - //encrypts the password on set and decrypts on get - settingManager.SetGlobalAsync(EmailSettingNames.Smtp.Password, "your_password"); - } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.IsEnabled = MultiTenancyConsts.IsEnabled; - }); - - // #if DEBUG - // context.Services.Replace(ServiceDescriptor.Singleton()); - // #endif - } - } -} - -``` - -* `NullEmailSender` is a built-in class that implements the `IEmailSender`, but writes email contents to the standard log system, rather than actually sending the emails. This class can be useful especially in development time where you generally don't want to send real emails. Therefore ABP framework defined this by default. But in our case we want to send real emails, so we must remove these lines or we must take it to the comment line. - -* `Abp.Mailing.Smtp.Password` must be an encrypted value. Therefore we used `SettingManager` in here to set the password. It internally **encrypts** the values on set and **decrypts** on get. - -* After all these steps, whenever we want to send an email, we can do it by using our `EmailService` class. We can inject this class and invoke the `SendAsync` method to sending email where its needed. - -After sending the email we should see the template like below. - -![email-message](message.jpg) - -### Step - 4 (Defining New Template) - -* So far we've sent mail by using standard email template of ABP. But we may want to replace the email template with the new one. We can achieve this by following the `Text Templating` [documentation](https://docs.abp.io/en/abp/latest/Text-Templating#replacing-the-existing-templates). - -* In this article, I will create a email template by using free template generator named **Bee**. You can reach the free templates from [here](https://beefree.io/templates/free/). - -* When we find a template for our purpose, we can hover the link and click the **get started** button to edit the template. (I chose a template named "gdpr".) - -* Here, you can edit your template as below. (You can delete or add sections, edit texts, and so on.) - -![bee](bee.gif) - -> **Note:** After editing our template, we need to export it to reach our created template's content. You can see the **export** button top-right of the template editing page. - -* After choosing and editing our free template, we can create a new **email template** in our project. For this, create a folder named `Templates` under `Emailing` folder in `TemplateReplace.Domain` and add `EmailTemplate.tpl` file inside of it. And copy-paste the below content or your template's content. - -```tpl - - - - - - - - - - - - - - - - - - - -``` - -* Then we need to make the template file as "Embedded Resource". We can do this as below. - -* First right click to **EmailTemplate.tpl** and choose `Properties`. - -![embedded-resource](embedded-resource.jpg) - -* Then be sure about build action is **Embedded resource**. - -![embedded-resource-2](embedded-resource-2.jpg) - -### Step - 4 (Replacing the Email Template) - -* To replace the current email template with our new email template, we need to override it. To achieve this, create a class named `EmailTemplateDefinitionProvider` under `Emailing` folder in `TemplateReplace.Domain` and fill it with the below content. - -```csharp -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.TextTemplating; - -namespace TemplateReplace.Emailing -{ - public class EmailTemplateDefinitionProvider : TemplateDefinitionProvider, ITransientDependency - { - public override void Define(ITemplateDefinitionContext context) - { - var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Message); - - emailLayoutTemplate - .WithVirtualFilePath( - "/Emailing/Templates/EmailTemplate.tpl", - isInlineLocalized: true - ); - } - } -} -``` - -* In here we've created a template definition provider class that gets the email layout template and change the virtual file path for the template. - -* This approach allows us to locate templates in any folder instead of the folder defined by the depended module. For more detail, check the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). - -### Step - 5 - -* Lastly, we need to configure the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). To do this open your `TemplateReplaceDomainModule.cs` in `TemplateReplace.Domain` and update the content as below. - -```csharp -using TemplateReplace.MultiTenancy; -using Volo.Abp.AuditLogging; -using Volo.Abp.BackgroundJobs; -using Volo.Abp.Emailing; -using Volo.Abp.FeatureManagement; -using Volo.Abp.Identity; -using Volo.Abp.IdentityServer; -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; -using Volo.Abp.PermissionManagement.Identity; -using Volo.Abp.PermissionManagement.IdentityServer; -using Volo.Abp.SettingManagement; -using Volo.Abp.TenantManagement; -using Volo.Abp.VirtualFileSystem; - -namespace TemplateReplace -{ - [DependsOn( - typeof(TemplateReplaceDomainSharedModule), - typeof(AbpAuditLoggingDomainModule), - typeof(AbpBackgroundJobsDomainModule), - typeof(AbpFeatureManagementDomainModule), - typeof(AbpIdentityDomainModule), - typeof(AbpPermissionManagementDomainIdentityModule), - typeof(AbpIdentityServerDomainModule), - typeof(AbpPermissionManagementDomainIdentityServerModule), - typeof(AbpSettingManagementDomainModule), - typeof(AbpTenantManagementDomainModule), - typeof(AbpEmailingModule) - )] - public class TemplateReplaceDomainModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.IsEnabled = MultiTenancyConsts.IsEnabled; - }); - - //Add this configuration - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - } - } -} - -``` - -* And now when we send a new email, we should see our newly defined template as the message like below. - -![email-last](email-last.jpg) - -## Text Template Management - -* Generally, more than one e-mail is required in applications. We create email templates for **"password changes"** or **"welcome"** etc in our applications. In such cases, it is necessary to create different templates for each mail. ABP Commercial allows us to perform these operations on UI in a simple way. Text Template Management provides UI to easily create and manage email templates. - -![template-definitions](template-definitions.png) - -* ABP Commercial's [Text Template Management](https://commercial.abp.io/modules/Volo.TextTemplateManagement) module is really fascinating. It makes it super easy to stores and edits template contents. We can list all templates on a page, editing them, localizing them, and so on. - -![inline-content](inline-content.png) - -* ABP Commercial's text template management module, allows us to modify a template through the UI. - -* I highly recommend you to [check it out](https://commercial.abp.io/modules/Volo.TextTemplateManagement). - -## References - -* [Text Templating](https://docs.abp.io/en/abp/latest/Text-Templating) -* [Emailing](https://docs.abp.io/en/abp/latest/Emailing) -* [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System) \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/bee.gif b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/bee.gif deleted file mode 100644 index 15a3a01e65..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/bee.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-1.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-1.jpg deleted file mode 100644 index 733457d028..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-1.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-2.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-2.jpg deleted file mode 100644 index db1277d561..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-2.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-3.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-3.jpg deleted file mode 100644 index 2d8bd4c484..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/db-migrator-3.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/email-last.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/email-last.jpg deleted file mode 100644 index fce8797b0c..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/email-last.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/embedded-resource-2.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/embedded-resource-2.jpg deleted file mode 100644 index 3bd8afcdc2..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/embedded-resource-2.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/embedded-resource.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/embedded-resource.jpg deleted file mode 100644 index 4fe868d572..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/embedded-resource.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/inline-content.png b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/inline-content.png deleted file mode 100644 index 5119b102e8..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/inline-content.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/message.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/message.jpg deleted file mode 100644 index 2c4704a1c7..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/message.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/settings.jpg b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/settings.jpg deleted file mode 100644 index 167996735f..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/settings.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/template-definitions.png b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/template-definitions.png deleted file mode 100644 index 00339c4c1a..0000000000 Binary files a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/template-definitions.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md deleted file mode 100644 index 1b56d1f2b7..0000000000 --- a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md +++ /dev/null @@ -1,115 +0,0 @@ -# How to Setup Azure Active Directory and Integrate ABP Angular Application - -This guide demonstrates how to register an application to Azure Active Directory and integrate AzureAD to an ABP Angular application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**. - -## Authentication Flow - -ABP Angular application uses **Authentication Code with PKCE** (specs [here](https://tools.ietf.org/html/rfc7636)) which is the most suitable flow for SPA applications by the time this article is written since implicit flow is deprecated. - -The most common question is; - -> Where to put OpenId connection code in the Angular project? - -The answer is, **you don't**. ABP Angular application is integrated with the backend (HttpApi.Host project) where it loads the configurations, **permissions** etc. For none-tiered angular applications, **HttpApi.Host** project also has IdentityServer4 embedded; also serving as **Authorization Server**. Angular application authentication flow is shown below. - -![auth-diagram](auth-diagram.jpeg) - -> What if I want Azure AD as my authorization server and not IdentityServer? - -This means your application will be using AzureAD user store for authentication. By registering both Angular app and HttpApi to AzureAD, authentication might work but **authorization won't**. Users need to be registered to ABP identity system for auditing, permissions etc. So the flow should be 3rd party registration. - -## Setting up OpenId Connection - -Lets start with adding OpenId connection. Open the **HttpApiHostModule.cs** and update the **ConfigureAuthentication** method as below: - -```csharp - -context.Services.AddAuthentication() - ... //Omitted other third party configurations - .AddOpenIdConnect("AzureOpenId", "Azure AD OpenId", options => - { - options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; - options.ClientId = configuration["AzureAd:ClientId"]; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.ClientSecret = configuration["AzureAd:ClientSecret"]; - options.RequireHttpsMetadata = false; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.Scope.Add("email"); - - options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); - }); -``` - -> If you are using tiered (separate identity server) application, open **IdentityServerModule.cs** and add the OpenIdConnection manually to **ConfigureServices** method as following: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var hostingEnvironment = context.Services.GetHostingEnvironment(); - var configuration = context.Services.GetConfiguration(); - - context.Services.AddAuthentication() - .AddOpenIdConnect("AzureOpenId", "Azure AD OpenId", options => - { - ... // Same configuration above - }); -``` - -Now we can add AzureAD settings. Open the **appsettings.json** located in **HttpApi.Host** project (or **IdentityServer** project if you are using tiered application). Add the following; - -```csharp -"AzureAd": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "", - "ClientId": "", - "Domain": "domain.onmicrosoft.com", - "CallbackPath": "/signin-azuread-oidc", - "ClientSecret": "" -}, -``` - -Keep on mind that **App.SelfUrl** + **AzureAd.CallbackPath** will be used in AzureAD app registration. We'll update the AzureAd settings after registering the application in Azure Portal. - -## Setting up Azure Active Directory - -Navigate to Manage Azure Active Directory in [azure portal](https://portal.azure.com/). Go to **App registrations** on left side menu and hit New registration. - -![azure-app-registration](azure-app-registration.jpg) - -Enter a name for your application and **App.SelfUrl** + **AzureAd.CallbackPath** as redirect uri then register. - -![azure-app-register](azure-app-register.JPG) - -Now navigate to **Authentication** on the left menu and enable **ID tokens**. - -![azure-app-authentication](azure-app-authentication.jpg) - -We also need to set a client secret. Navigate to **Certificates & secrets** menu on the left and create a new client secret. - -![azure-app-secret](azure-app-secret.jpg) - -**Copy** the secret and update your appsettings.json **AzureAd.ClientSecret** field. - -![azure-app-overview](azure-app-overview.jpg) - -Also update your **AzureAd.TenantId** and **AzureAd.ClientId** fields with the information located under **Overview** menu. And that's all. - -Next time you hit login, you should be seeing login screen enabled Azure AD like below. - -![app-login](app-login.JPG) - -# FAQ - -* I am getting errors when trying to login to AzureAD. - * You can check [this article FAQ](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf) which covers many errors. - - -* But I don't want my users to see default login screen. I want my users to login **only** from AzureAD. - * You can **mimic** this behavior by customizing the login page and instantly trigger Azure AD provider click. For more info, you can check [this article](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd). - -# May 2021 Update - -- **AddOpenIdConnect**: Removed `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();` and added `sub` claim mapping in ClaimActions rather than global mapping. -- Updated OpenIdConnect configurations. diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/app-login.JPG b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/app-login.JPG deleted file mode 100644 index 94500a4f15..0000000000 Binary files a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/app-login.JPG and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/auth-diagram.jpeg b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/auth-diagram.jpeg deleted file mode 100644 index dec4a66a17..0000000000 Binary files a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/auth-diagram.jpeg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-authentication.jpg b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-authentication.jpg deleted file mode 100644 index fdae1677eb..0000000000 Binary files a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-authentication.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-overview.jpg b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-overview.jpg deleted file mode 100644 index 714129693d..0000000000 Binary files a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-overview.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-register.JPG b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-register.JPG deleted file mode 100644 index 68c180cea0..0000000000 Binary files a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-register.JPG and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-registration.jpg b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-registration.jpg deleted file mode 100644 index 1b4685e261..0000000000 Binary files a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-registration.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-secret.jpg b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-secret.jpg deleted file mode 100644 index 451879453c..0000000000 Binary files a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-secret.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/Using-DevExtremeAngularComponents-With-The-ABP-Framework.md b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/Using-DevExtremeAngularComponents-With-The-ABP-Framework.md deleted file mode 100644 index c20b796073..0000000000 --- a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/Using-DevExtremeAngularComponents-With-The-ABP-Framework.md +++ /dev/null @@ -1,317 +0,0 @@ -# Using DevExtreme Angular Components With the ABP Framework - -In this article, I will show you how to integrate the [DevExpress Angular components](https://js.devexpress.com/Documentation/Guide/Angular_Components/DevExtreme_Angular_Components/) to a project created using the ABP Framework startup templates. Then I will use the [DataGrid](https://js.devexpress.com/Documentation/Guide/Widgets/DataGrid/Overview/) component to show a list of users on the UI. - -## Create the Project - -Let's create a new web application with the Angular UI using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI#new): - -```shell -abp new DevExtremeAngular -u angular -``` - -> For detail information about how to generate and start up a project, please refer to the [official docs](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No). For the scope of this post, we will not go into details of the backend applications. - -## Running the Solution - -### The Server Side - -Server side contains multiple projects in the solution: - -![A screenshot showing DevExtremeAngular.DbMigrator and DevExtremeAngular.HttpApi.Host](project-setup.png) - -Run following projects in order; - -* Run `DevExtremeAngular.DbMigrator` to create the database and seed the initial data. -* Run `DevExtremeAngular.HttpApi.Host` project to make the backend API up & running. - -### The Angular Application - -Open a command line terminal and navigate to `angular` folder then run `yarn` or `npm install` based on which package you are using. - -After installation process is done, you can start your angular project by running `yarn start` or `npm start`. This command should serve the application and open the application in your default browser. If it doesn't open, you can navigate to http://localhost:4200 in your browser: - -![A screenshot showing localhost is running](localhost-running.png) - -You can login to the application by using following credentials: - -> _Default admin username is **admin** and password is **1q2w3E\***_ - -![A screenshot showing login page](login-screen.png) - -After successful login, you should be redirected to home page. - -## Install DevExtreme - -You can follow [the guide](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Add_DevExtreme_to_an_Angular_CLI_Application/) provided by **DevExtreme** team or apply the following steps. - -* `npm install devextreme devextreme-angular` or `yarn add devextreme devextreme-angular` -* Import given two following styles in `angular.json` file: - -```javascript - // ... - "styles": [ - // ... - "src/styles.scss", - - "node_modules/devextreme/dist/css/dx.common.css", - "node_modules/devextreme/dist/css/dx.light.css" - ] -``` - -* Add `dx-viewport` to classes of `body` in `index.html` - -```html - - -
    -
    - -``` - -After completing these steps, you need to restart the angular application. - -## Create a lazy Angular Module for DevExtreme Demo - -Let's create a module which will be loaded lazily. - -Open up a terminal and navigate to `angular` to run following command. - -```shell -ng g m dev-extreme --route dev-extreme --module app -``` - -...or with `npx`, if you do not have `angular-cli` installed... - -```shell -npx ng g m dev-extreme --route dev-extreme --module app -``` - -Your terminal should log the following output: - -```shell -CREATE src/app/dev-extreme/dev-extreme-routing.module.ts (361 bytes) -CREATE src/app/dev-extreme/dev-extreme.module.ts (379 bytes) -CREATE src/app/dev-extreme/dev-extreme.component.scss (0 bytes) -CREATE src/app/dev-extreme/dev-extreme.component.html (26 bytes) -CREATE src/app/dev-extreme/dev-extreme.component.spec.ts (655 bytes) -CREATE src/app/dev-extreme/dev-extreme.component.ts (295 bytes) -UPDATE src/app/app-routing.module.ts (362 bytes) -``` - -The Angular CLI has created a module and configured it to lazy-load at `/dev-extreme` path. - -The last step to be able to see our newly created module in the browser, open `route.provider.ts` and edit the array being added into the routes. - -```typescript - // ... - routes.add([ - { - path: '/', - name: '::Menu:Home', - iconClass: 'fas fa-home', - order: 1, - layout: eLayoutType.application, - }, - { - path: '/dev-extreme', - name: 'Dev Extreme', - order: 2, - layout: eLayoutType.application, - }, - ]); - // ... -``` - -After completing the steps above, you should be able to see `Dev Extreme` on the header and when you click on it, you should be redirected to `/dev-extreme` page and see the following message on the screen. - -![A screenshot showing a page that says dev-extreme works!](dev-extreme-page.png) - -## Display users on the dev-extreme page - -For this demo, we will list users on the screen. We already have `admin` as our first user. - -Let's add couple of more to the list in `Administration -> Identity Management -> Users` page. - -![A screenshot showing users page after adding couple of users](users.png) - -Now we are ready to fetch our users and display them on `/dev-extreme` page. - -Firstly, let's create a service for our component. - -Navigate to the `dev-extreme` folder and run following command. If you run this command at the root, the service will be generated next to `app.module.ts` - -```shell -ng g s dev-extreme -``` - -Following files should be created - -```shell -CREATE src/app/dev-extreme/dev-extreme.service.spec.ts (378 bytes) -CREATE src/app/dev-extreme/dev-extreme.service.ts (139 bytes) -``` - -Let's import and inject `IdentityService` as dependency in `dev-extreme.service.ts`. After then, let's create a stream called `users$` to retrieve the users. - -`identityService.getUsers` returns `ABP.PagedResponse` which contains two fields, `items` and `totalCount`. We are only interested in `items` for now. - -When we apply the steps described above, the final version of `dev-extreme.service` should be as follows - -```typescript -import { Injectable } from '@angular/core'; -import { map } from 'rxjs/operators'; -import { IdentityService } from '@abp/ng.identity'; - -@Injectable({ - providedIn: 'root', -}) -export class DevExtremeService { - users$ = this.service.getUsers().pipe(map((result) => result.items)); - - constructor(private service: IdentityService) {} -} -``` - -Now we can simply inject `DevExtremeService` as public and utilize `users$` stream in `dev-extreme.component.ts` as follows: - -```typescript -import { Component } from '@angular/core'; -import { DevExtremeService } from './dev-extreme.service'; - -@Component({ - selector: 'app-dev-extreme', - templateUrl: './dev-extreme.component.html', - styleUrls: ['./dev-extreme.component.scss'], -}) -export class DevExtremeComponent { - constructor(public service: DevExtremeService) {} -} -``` - -And use it within `dev-extreme.component.html` - -```html - -
      -
    • - {{ user.name }} -
    • -
    -
    -``` - -This should list names of the users on the screen - -![A screenshot showing list users on the dev extreme page](users-on-dev-extreme.png) - -## Use DxDataGrid to list the users - -You can take a look at [demo](https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/ColumnCustomization/Angular/Light/) provided by **DevExtreme** team or apply the following steps. - -Now, our application is ready to use `dx-data-grid` in `dev-extreme.component.ts` - -Firstly, we need to import `DxDataGridModule` in our module as follows. - -```typescript -// ... - -import { DxDataGridModule } from 'devextreme-angular'; - -@NgModule({ - // ... - imports: [ - // ... - DxDataGridModule - ], -}) -export class DevExtremeModule {} -``` - -At this point `dx-data-grid` is avaliable within our module and we can use it in our template. - -Change `dev-extreme.component.html` to the following - -```html - - - -``` - -It should display a table on the screen - -![A screenshot displaying dev extreme data grid with lots of columns](devextreme-first.png) - -Since, we did not specify any columns, `dx-data-grid` displayed every column avaliable. Let's pick some columns to make it more readable. - -Change `dev-extreme.component.html` to the following: - -```html - - - - - - - - - -``` - -which will display following table on the screen - -![A screenshot displaying dev extreme data grid with these columns: username, name, surname, email and phone number](devextreme-second.png) - -We can also utilize `abpLocalization` pipe to translate the headers of the table. To use `abpLocalization` pipe in our templates, we need to import `CoreModule` from `@abp/ng.core` into our module. - -```typescript -import { CoreModule } from '@abp/ng.core'; - -@NgModule({ - // ... - imports: [ - // ... - CoreModule - ], -}) -export class DevExtremeModule {} -``` - -And change the template to the following: - -```html - - - - - - - - - -``` - -The headers should change when a new language is selected; - -![A gif showing the headers of the table getting translated into the chosen language](devextreme-final.gif) - -## Conclusion - -In this article, we have seen how to integrate `DevExtreme` angular components into a project generated by `ABP CLI`. - -You can download source code of [the demo here](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Angular). diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/dev-extreme-page.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/dev-extreme-page.png deleted file mode 100644 index 973a079e21..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/dev-extreme-page.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-final.gif b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-final.gif deleted file mode 100644 index 5a39c443f0..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-final.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-first.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-first.png deleted file mode 100644 index 4206aaf3a6..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-first.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-second.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-second.png deleted file mode 100644 index 9d64653425..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/devextreme-second.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/localhost-running.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/localhost-running.png deleted file mode 100644 index b1d81e1245..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/localhost-running.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/login-screen.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/login-screen.png deleted file mode 100644 index 74b9aab678..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/login-screen.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/project-setup.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/project-setup.png deleted file mode 100644 index 734780c4b6..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/project-setup.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users-on-dev-extreme.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users-on-dev-extreme.png deleted file mode 100644 index 46aae0099b..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users-on-dev-extreme.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users.png b/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users.png deleted file mode 100644 index f4bf506a65..0000000000 Binary files a/docs/en/Community-Articles/2020-09-28-Using-DevExtremeAngularComponents-With-The-ABP-Framework/users.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md deleted file mode 100644 index 178a8f261e..0000000000 --- a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md +++ /dev/null @@ -1,164 +0,0 @@ -# How to Add Custom Properties to the User Entity - -> **Note:** If your application is greater than version 4.3.3, please follow [this article](https://community.abp.io/posts/how-to-add-custom-properties-to-the-user-entity-rixchoha). - -## Introduction - -In this step-by-step article, I will explain how you can customize the user entity class, which is available in every web application you create using the ABP framework, according to your needs. When you read this article, you will learn how to override the services of built-in modules, extend the entities, extend data transfer objects and customize the user interface in the applications you develop using the ABP framework. - -> **Note:** This article is not about customizing the `Login` page. If you have such a need, please follow [this article](https://community.abp.io/posts/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd). - -You can see the screenshots below which we will reach at the end of the article. - -![custom-identity-user-list](./custom-identity-user-list.png) - -![new-user](./new-user.png) - -## Preparing the Project - -### Startup template and the initial run - -Abp Framework offers startup templates to get into the work faster. We can create a new startup template using Abp CLI: - -`abp new CustomizeUserDemo` - -> In this article, I will go through the MVC application, but it will work also in the [Angular](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No), [Blazor Server](https://docs.abp.io/en/abp/latest/Getting-Started?UI=BlazorServer&DB=EF&Tiered=No), and [Blazor WebAssembly](https://docs.abp.io/en/abp/latest/Getting-Started?UI=Blazor&DB=EF&Tiered=No) application. - -After the download is finished, we can run **CustomizeUserDemo.DbMigrator** project to create the database migrations and seed the initial data (admin user, role, etc). Then we can run `CustomizeUserDemo.Web` to see that our application is working. - -> Default admin username is **admin** and password is **1q2w3E\*** - -![initial-project](./initial-project.png) - -In this article, we will go through a scenario together and find the solutions to our questions through this scenario. However, since the scenario is not a real-life scenario, it may be strange, please don't get too about this issue :) - -## Step-1 - -Add two new properties to the `AppUser` in the Users folder of the **CustomizeUserDemo.Domain** project as follows: - -```csharp -public string Title { get; protected set; } - -public int Reputation { get; protected set; } -``` - -## Step-2 - -Create the Users folder in the **CustomizeUserDemo.Domain.Shared** project, create the class `UserConsts` inside the folder and update the class you created as below: - -```csharp -public static class UserConsts -{ - public const string TitlePropertyName = "Title"; - - public const string ReputationPropertyName = "Reputation"; - - public const int MaxTitleLength = 64; - - public const double MaxReputationValue = 1_000; - - public const double MinReputationValue = 1; -} -``` - -## Step-3 - -Update the `CustomizeUserDemoEfCoreEntityExtensionMappings` class in the **CustomizeUserDemo.EntityFramework** project in the EntityFrameworkCore folder as below: - -```csharp -public static class CustomizeUserDemoEfCoreEntityExtensionMappings -{ - private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); - - public static void Configure() - { - CustomizeUserDemoGlobalFeatureConfigurator.Configure(); - CustomizeUserDemoModuleExtensionConfigurator.Configure(); - - OneTimeRunner.Run(() => - { - ObjectExtensionManager.Instance - .MapEfCoreProperty( - nameof(AppUser.Title), - (entityBuilder, propertyBuilder) => - { - propertyBuilder.HasDefaultValue(""); - propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength); - } - ).MapEfCoreProperty( - nameof(AppUser.Reputation), - (entityBuilder, propertyBuilder) => - { - propertyBuilder.HasDefaultValue(UserConsts.MinReputationValue); - } - ); - }); - } -} -``` - -This class can be used to map these extra properties to table fields in the database. Please read [this](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities) article to improve your understanding of what we are doing. - -So far, we have added our extra features to the `User` entity and matched these features with the `ef core`. - -Now we need to add migration to see what has changed in our database. This for, open the Package Manager Console (PMC) under the menu Tools > NuGet Package Manager. - -![nuget-package-manager](./nuget-package-manager.png) - -Select the **CustomizeUserDemo.EntityFramework** as the **default project** and execute the following command: - -```bash -Add-Migration "Updated-User-Entity" -``` - -![added-new-migration](./added-new-migration.png) - -This will create a new migration class inside the `Migrations` folder of the **CustomizeUserDemo.EntityFrameworkCore** project. - -> If you are using another IDE than the Visual Studio, you can use `dotnet-ef` tool as [documented here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration). - -Finally, run the **CustomizeUserDemo.DbMigrator** project to update the database. - -When we updated the database, you can see that the `Title` and `Reputation` columns are added to the `Users` table. - -![user-table](./user-table.png) - -## Step-4 -Open the `CustomizeUserDemoModuleExtensionConfigurator` in the **CustomizeUserDemo.Domain.Shared** project, and change the contents of the `ConfigureExtraProperties` method as shown below: -```csharp -private static void ConfigureExtraProperties() -{ - ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity => - { - identity.ConfigureUser(user => - { - user.AddOrUpdateProperty( - UserConsts.TitlePropertyName, - options => - { - options.Attributes.Add(new RequiredAttribute()); - options.Attributes.Add( - new StringLengthAttribute(UserConsts.MaxTitleLength) - ); - } - ); - user.AddOrUpdateProperty( - UserConsts.ReputationPropertyName, - options => - { - options.DefaultValue = UserConsts.MinReputationValue; - options.Attributes.Add( - new RangeAttribute(UserConsts.MinReputationValue, UserConsts.MaxReputationValue) - ); - } - ); - }); - }); -} -``` - -That's it. Now let's run the application and look at the Identity user page. You can also try to edit and recreate a record if you want, it will work even though we haven't done anything extra. Here is the magic code behind ABP framework. - -If there is a situation you want to add, you can click the contribute button or make a comment. Also, if you like the article, don't forget to share it :) - -Happy coding :) diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png deleted file mode 100644 index ae9086c59d..0000000000 Binary files a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png deleted file mode 100644 index 5b4ce65f68..0000000000 Binary files a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png deleted file mode 100644 index 7f6c5e5f53..0000000000 Binary files a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png deleted file mode 100644 index f7899be026..0000000000 Binary files a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png deleted file mode 100644 index 929295db4d..0000000000 Binary files a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png deleted file mode 100644 index 5990a64111..0000000000 Binary files a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/POST.md b/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/POST.md deleted file mode 100644 index f1387b2a50..0000000000 --- a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/POST.md +++ /dev/null @@ -1,46 +0,0 @@ -# How to add a new language to your ABP project? - -Adding a new language to your ABP project is pretty simple. Let's add the German language to our ABP project: - - - -1. Go to your solution's root folder and write the following CLI command. This command will generate an empty translation file from English. - ```bash - abp translate -c de-DE - ``` - - Check out for [the complete supported culture codes](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes). - (For internal development `D:\Github\abp` and `D:\Github\volo\abp`) - -2. Fill the `target` fields in your target language. - - ![Fill target fields](language-target.png) - -3. Copy `abp-translation.json` your solution's root folder (Do not change the filename!) - -4. Run the following command. This command will create the necessary `json` files. - ```bash - abp translate --apply - ``` - -5. Open your solution and add the new language to the language list. To do this; - - * open `MyProjectNameDomainModule.cs` and in `ConfigureServices` you'll find `Configure`. If you have `HttpApi.Host` project then you need to add this in `MyProjectNameHttpApiHostModule.cs` - - ``` - options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch", "de")); - ``` - - ![Add to languages](add-to-languages.png) - - The last parameter is the flag icon. You can find the list of flag icons on https://flagicons.lipis.dev/ - - 6. The last step is running the DbMigrator project. It will seed the database for the new language. - ![The database table](database-table.png) - - - -Close the IIS Express / Kestrel to invalidate the language cache and run the project. You will see the new language on your website. - -![See the final result](website-new-language.png) - diff --git a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/add-to-languages.png b/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/add-to-languages.png deleted file mode 100644 index e61c94e78f..0000000000 Binary files a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/add-to-languages.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/database-table.png b/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/database-table.png deleted file mode 100644 index 0611d8ac53..0000000000 Binary files a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/database-table.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/language-target.png b/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/language-target.png deleted file mode 100644 index 2812c21bba..0000000000 Binary files a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/language-target.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/website-new-language.png b/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/website-new-language.png deleted file mode 100644 index 559642fb2c..0000000000 Binary files a/docs/en/Community-Articles/2020-11-02-How-To-Add-New-Language/website-new-language.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md b/docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md deleted file mode 100644 index 076ceb3aa0..0000000000 --- a/docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md +++ /dev/null @@ -1,943 +0,0 @@ -# Creating an Event Organizer Application with the ABP Framework & Blazor UI. - -## Introduction - -In this article, we will create an example application that is a simple **meeting/event organizer**: People create events and other people registers to the event. - -The application has been developed with **Blazor** as the UI framework and **MongoDB** as the database provider. - -> This tutorial is based on my notes that I'd created to implement this application in a workshop. It shows the necessary steps to build the application rather than detailed explanations. - -### Source Code - -Source code of the completed application is [available on GitHub](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer). - -### Screenshots - -Here, the pages of the final application. - -**Home Page - Event List** - -![event-list-ui](images/event-list-ui.png) - -**Creating a new Event** - -![event-create-ui](images/event-create-ui.png) - -**Event Detail Page** - -![event-detail-ui](images/event-detail-ui.png) - -## Requirements - -The following tools are needed to be able to run the solution. - -* .NET 5.0 SDK -* Visual Studio 2019 16.8.0+ or another compatible IDE -* MongoDB Server (with MongoDB Compass) - -## Development - -### Creating a new Application - -* Use the following ABP CLI command: - -````bash -abp new EventOrganizer -u blazor -d mongodb -```` - -### Open & Run the Application - -* Open the solution in Visual Studio (or your favorite IDE). -* Run the `EventOrganizer.DbMigrator` application to seed the initial data. -* Run the `EventOrganizer.HttpApi.Host` application that starts the server side. -* Run the `EventOrganizer.Blazor` application to start the UI. - -### Apply the Custom Styles - -* Add styles to `wwwroot/main.css`: - -````css -body.abp-application-layout { - background-color: #222 !important; - font-size: 18px; -} -nav#main-navbar.bg-dark { - background-color: #222 !important; - box-shadow: none !important; -} -.event-pic { - width: 100%; - border-radius: 12px; - box-shadow: 5px 5px 0px 0px rgba(0,0,0,.5); - margin-bottom: 10px; -} -.event-link:hover, .event-link:hover *{ - text-decoration: none; -} -.event-link:hover .event-pic { - box-shadow: 5px 5px 0px 0px #ffd800; -} -.event-form { - background-color: #333 !important; - box-shadow: 5px 5px 0px 0px rgba(0,0,0,.5); - border-radius: 12px; -} -.table { - background: #fff; - border-radius: 12px; - box-shadow: 5px 5px 0px 0px rgba(0,0,0,.5); -} -.table th{ - border: 0 !important; -} -.modal { - color: #333; -} -.page-item:first-child .page-link { - margin-left: 0; - border-top-left-radius: 12px; - border-bottom-left-radius: 12px; -} -.page-item:last-child .page-link { - border-top-right-radius: 12px; - border-bottom-right-radius: 12px; -} -.btn { - border-radius: 8px; -} -.att-list { - list-style: none; - padding: 0; -} -.att-list li { - padding: 4px 0 0 0; -} -```` - -* `wwwroot/index.html`: Remove `bg-light` class from the `body` tag and add `bg-dark text-light`. - -### Domain Layer - -* Add the following `Event` aggregate (with `EventAttendee`) to the solution: - -**Event** - -````csharp -using System; -using System.Collections.Generic; -using Volo.Abp.Domain.Entities.Auditing; - -namespace EventOrganizer.Events -{ - public class Event : FullAuditedAggregateRoot - { - public string Title { get; set; } - - public string Description { get; set; } - - public bool IsFree { get; set; } - - public DateTime StartTime { get; set; } - - public ICollection Attendees { get; set; } - - public Event() - { - Attendees = new List(); - } - } -} -```` - -**EventAttendee** - -```csharp -using System; -using Volo.Abp.Auditing; - -namespace EventOrganizer.Events -{ - public class EventAttendee : IHasCreationTime - { - public Guid UserId { get; set; } - - public DateTime CreationTime { get; set; } - } -} -``` - -### MongoDB Mapping - -* Add the following property to the `EventOrganizerMongoDbContext`: - -````csharp -public IMongoCollection Events => Collection(); -```` - -### Clean Index.razor & Add the Header & "Create Event" button - -* Clean the `Index.razor` file. -* Replace the content with the following code: - -````html -@page "/" -@inherits EventOrganizerComponentBase - - -

    Upcoming Events

    -
    - - @if (CurrentUser.IsAuthenticated) - { - - @L["CreateEvent"] - - } - -
    -```` - -* Open `Localization/EventOrganizer/en.json` in the `EventOrganizer.Domain.Shared` project and add the following entry: - -````json -"CreateEvent": "Create a new event!" -```` - -The Result (run the `EventOrganizer.Blazor` application to see): - -![index-title](images/index-title.png) - -### Event Creation - -* Create the Initial `IEventAppService` with the `CreateAsync` method: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace EventOrganizer.Events -{ - public interface IEventAppService : IApplicationService - { - Task CreateAsync(EventCreationDto input); - } -} -```` - -* Add `EventCreationDto` class: - -````csharp -using System; -using System.ComponentModel.DataAnnotations; - -namespace EventOrganizer.Events -{ - public class EventCreationDto - { - [Required] - [StringLength(100)] - public string Title { get; set; } - - [Required] - [StringLength(2000)] - public string Description { get; set; } - - public bool IsFree { get; set; } - - public DateTime StartTime { get; set; } - } -} -```` - -* Implement the `EventAppService`: - -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Domain.Repositories; - -namespace EventOrganizer.Events -{ - public class EventAppService : EventOrganizerAppService, IEventAppService - { - private readonly IRepository _eventRepository; - - public EventAppService(IRepository eventRepository) - { - _eventRepository = eventRepository; - } - - [Authorize] - public async Task CreateAsync(EventCreationDto input) - { - var eventEntity = ObjectMapper.Map(input); - await _eventRepository.InsertAsync(eventEntity); - return eventEntity.Id; - } - } -} -```` - -* Add AutoMapper mapping to the `EventOrganizerApplicationAutoMapperProfile` class: - -````csharp -using AutoMapper; -using EventOrganizer.Events; - -namespace EventOrganizer -{ - public class EventOrganizerApplicationAutoMapperProfile : Profile - { - public EventOrganizerApplicationAutoMapperProfile() - { - CreateMap(); - } - } -} -```` - -This will automatically create the HTTP (REST) API for the application service (run the `EventOrganizer.HttpApi.Host` application to see it on the Swagger UI): - -![swagger-event-create](images/swagger-event-create.png) - -* Create the `CreateEvent.razor` file: - -````csharp -@page "/create-event" -@inherits EventOrganizerComponentBase -Create Event - - -
    - - - @L["Title"] - - - - @L["Description"] - - - - @L["Free"] - - - @L["StartTime"] - - - - -
    -
    -
    -```` - -* Create a partial `CreateEvent` class in the same folder, with the `CreateEvent.razor.cs` as the file name: - -````csharp -using System.Threading.Tasks; -using EventOrganizer.Events; -using Microsoft.AspNetCore.Components; - -namespace EventOrganizer.Blazor.Pages -{ - public partial class CreateEvent - { - private EventCreationDto Event { get; set; } = new EventCreationDto(); - - private readonly IEventAppService _eventAppService; - private readonly NavigationManager _navigationManager; - - public CreateEvent( - IEventAppService eventAppService, - NavigationManager navigationManager) - { - _eventAppService = eventAppService; - _navigationManager = navigationManager; - } - - private async Task Create() - { - var eventId = await _eventAppService.CreateAsync(Event); - _navigationManager.NavigateTo("/events/" + eventId); - } - } -} -```` - -The final UI is (run the `EventOrganizer.Blazor` application and click to the "Create Event" button): - -![event-create-ui](images/event-create-ui.png) - -### Upcoming Events (Home Page) - -* Open the `IEventAppService` and add a `GetUpcomingAsync` method to get the list of upcoming events: - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace EventOrganizer.Events -{ - public interface IEventAppService : IApplicationService - { - Task CreateAsync(EventCreationDto input); - - Task> GetUpcomingAsync(); - } -} -```` - -* Add a `EventDto` class: - -````csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace EventOrganizer.Events -{ - public class EventDto : EntityDto - { - public string Title { get; set; } - - public string Description { get; set; } - - public bool IsFree { get; set; } - - public DateTime StartTime { get; set; } - - public int AttendeesCount { get; set; } - } -} -```` - -* Implement the `GetUpcomingAsync` in the `EventAppService` class: - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Domain.Repositories; - -namespace EventOrganizer.Events -{ - public class EventAppService : EventOrganizerAppService, IEventAppService - { - private readonly IRepository _eventRepository; - - public EventAppService(IRepository eventRepository) - { - _eventRepository = eventRepository; - } - - [Authorize] - public async Task CreateAsync(EventCreationDto input) - { - var eventEntity = ObjectMapper.Map(input); - await _eventRepository.InsertAsync(eventEntity); - return eventEntity.Id; - } - - public async Task> GetUpcomingAsync() - { - var queryable = await _eventRepository.GetQueryableAsync(); - var query = queryable - .Where(x => x.StartTime > Clock.Now) - .OrderBy(x => x.StartTime); - - var events = await AsyncExecuter.ToListAsync(query); - - return ObjectMapper.Map, List>(events); - } - } -} -```` - -* Add the following line into the `EventOrganizerApplicationAutoMapperProfile` constructor: - -````csharp -CreateMap(); -```` - -Run the `EventOrganizer.HttpApi.Host` application to see the new `upcoming` endpoint on the Swagger UI: - -![swagger-event-upcoming](images/swagger-event-upcoming.png) - -* Change the `Pages/Index.razor.cs` content in the `EventOrganizer.Blazor` project as shown below: - -```csharp -using System.Collections.Generic; -using System.Threading.Tasks; -using EventOrganizer.Events; - -namespace EventOrganizer.Blazor.Pages -{ - public partial class Index - { - private List UpcomingEvents { get; set; } = new List(); - - private readonly IEventAppService _eventAppService; - - public Index(IEventAppService eventAppService) - { - _eventAppService = eventAppService; - } - - protected override async Task OnInitializedAsync() - { - UpcomingEvents = await _eventAppService.GetUpcomingAsync(); - } - } -} -``` - -* Change the `Pages/Index.razor` content in the `EventOrganizer.Blazor` project as shown below: - -````html -@page "/" -@inherits EventOrganizerComponentBase - - -

    Upcoming Events

    -
    - - @if (CurrentUser.IsAuthenticated) - { - - @L["CreateEvent"] - - } - -
    - - @foreach (var upcomingEvent in UpcomingEvents) - { - - -
    - @if (upcomingEvent.IsFree) - { - FREE - } - - - @upcomingEvent.AttendeesCount - -
    - -
    - @upcomingEvent.StartTime.ToLongDateString() -

    @upcomingEvent.Title

    -

    @upcomingEvent.Description.TruncateWithPostfix(150)

    -
    -
    -
    - } -
    -```` - -The new home page is shown below: - -![event-list-ui](images/event-list-ui.png) - -### Event Detail Page - -* Add `GetAsync`, `RegisterAsync`, `UnregisterAsync` and `DeleteAsync` methods to the `IEventAppService`: - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace EventOrganizer.Events -{ - public interface IEventAppService : IApplicationService - { - Task CreateAsync(EventCreationDto input); - - Task> GetUpcomingAsync(); - - Task GetAsync(Guid id); - - Task RegisterAsync(Guid id); - - Task UnregisterAsync(Guid id); - - Task DeleteAsync(Guid id); - } -} -```` - -* Add `EventDetailDto` class: - -````csharp -using System; -using System.Collections.Generic; -using Volo.Abp.Application.Dtos; - -namespace EventOrganizer.Events -{ - public class EventDetailDto : CreationAuditedEntityDto - { - public string Title { get; set; } - - public string Description { get; set; } - - public bool IsFree { get; set; } - - public DateTime StartTime { get; set; } - - public List Attendees { get; set; } - } -} -```` - -* Add `EventAttendeeDto` class: - -````csharp -using System; - -namespace EventOrganizer.Events -{ - public class EventAttendeeDto - { - public Guid UserId { get; set; } - - public string UserName { get; set; } - - public DateTime CreationTime { get; set; } - } -} -```` - -* Implement the new methods in the `EventAppService`: - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using EventOrganizer.Users; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Users; - -namespace EventOrganizer.Events -{ - public class EventAppService : EventOrganizerAppService, IEventAppService - { - private readonly IRepository _eventRepository; - private readonly IRepository _userRepository; - - public EventAppService(IRepository eventRepository, IRepository userRepository) - { - _eventRepository = eventRepository; - _userRepository = userRepository; - } - - [Authorize] - public async Task CreateAsync(EventCreationDto input) - { - var eventEntity = ObjectMapper.Map(input); - await _eventRepository.InsertAsync(eventEntity); - return eventEntity.Id; - } - - public async Task> GetUpcomingAsync() - { - var queryable = await _eventRepository.GetQueryableAsync(); - var query = queryable - .Where(x => x.StartTime > Clock.Now) - .OrderBy(x => x.StartTime); - - var events = await AsyncExecuter.ToListAsync(query); - - return ObjectMapper.Map, List>(events); - } - - public async Task GetAsync(Guid id) - { - var @event = await _eventRepository.GetAsync(id); - var attendeeIds = @event.Attendees.Select(a => a.UserId).ToList(); - - var queryable = await _userRepository.GetQueryableAsync(); - var query = queryable - .Where(u => attendeeIds.Contains(u.Id)); - - var attendees = (await AsyncExecuter.ToListAsync(query)) - .ToDictionary(x => x.Id); - - var result = ObjectMapper.Map(@event); - - foreach (var attendeeDto in result.Attendees) - { - attendeeDto.UserName = attendees[attendeeDto.UserId].UserName; - } - - return result; - } - - [Authorize] - public async Task RegisterAsync(Guid id) - { - var @event = await _eventRepository.GetAsync(id); - if (@event.Attendees.Any(a => a.UserId == CurrentUser.Id)) - { - return; - } - - @event.Attendees.Add(new EventAttendee {UserId = CurrentUser.GetId(), CreationTime = Clock.Now}); - await _eventRepository.UpdateAsync(@event); - } - - [Authorize] - public async Task UnregisterAsync(Guid id) - { - var @event = await _eventRepository.GetAsync(id); - var removedItems = @event.Attendees.RemoveAll(x => x.UserId == CurrentUser.Id); - if (removedItems.Any()) - { - await _eventRepository.UpdateAsync(@event); - } - } - - [Authorize] - public async Task DeleteAsync(Guid id) - { - var @event = await _eventRepository.GetAsync(id); - - if (CurrentUser.Id != @event.CreatorId) - { - throw new UserFriendlyException("You don't have the necessary permission to delete this event!"); - } - - await _eventRepository.DeleteAsync(id); - } - } -} -```` - -* Add the following mappings into the `EventOrganizerApplicationAutoMapperProfile`: - -````csharp -CreateMap(); -CreateMap(); -```` - -Run the `EventOrganizer.HttpApi.Host` application to see the complete Event HTTP API in the Swagger UI: - -![swagger-event-all](images/swagger-event-all.png) - -* Create `EventDetail.razor` component with the following content: - -````html -@page "/events/{id}" -@inherits EventOrganizerComponentBase -@if (Event != null) -{ - - -

    @Event.Title

    -
    - - Back - @if (CurrentUser.IsAuthenticated && CurrentUser.Id == Event.CreatorId) - { - - } - -
    - - -
    -
    - @if (Event.IsFree) - { - FREE - } - - - @Event.Attendees.Count - -
    - - Start time: @Event.StartTime.ToLongDateString() -

    @Event.Description

    -
    -
    - -
    - @if (CurrentUser.IsAuthenticated) - { -
    - @if (!IsRegistered) - { - - } - else - { -

    You are registered in this event

    - - } -
    - } - else - { - - Login to attend! - - } -
    -
    - Attendees (@Event.Attendees.Count) -
      - @foreach (var attendee in Event.Attendees) - { -
    • @attendee.UserName
    • - } -
    -
    -
    -
    -} -```` - -* Create `EventDetail.razor.cs` file with the following content: - -````csharp -using System; -using System.Linq; -using System.Threading.Tasks; -using EventOrganizer.Events; -using Microsoft.AspNetCore.Components; - -namespace EventOrganizer.Blazor.Pages -{ - public partial class EventDetail - { - [Parameter] - public string Id { get; set; } - - private EventDetailDto Event { get; set; } - private bool IsRegistered { get; set; } - - private readonly IEventAppService _eventAppService; - private readonly NavigationManager _navigationManager; - - public EventDetail( - IEventAppService eventAppService, - NavigationManager navigationManager) - { - _eventAppService = eventAppService; - _navigationManager = navigationManager; - } - - protected override async Task OnInitializedAsync() - { - await GetEventAsync(); - } - - private async Task GetEventAsync() - { - Event = await _eventAppService.GetAsync(Guid.Parse(Id)); - if (CurrentUser.IsAuthenticated) - { - IsRegistered = Event.Attendees.Any(a => a.UserId == CurrentUser.Id); - } - } - - private async Task Register() - { - await _eventAppService.RegisterAsync(Guid.Parse(Id)); - await GetEventAsync(); - } - - private async Task UnRegister() - { - await _eventAppService.UnregisterAsync(Guid.Parse(Id)); - await GetEventAsync(); - } - - private async Task Delete() - { - if (!await Message.Confirm("This event will be deleted: " + Event.Title)) - { - return; - } - - await _eventAppService.DeleteAsync(Guid.Parse(Id)); - _navigationManager.NavigateTo("/"); - } - } -} -```` - -The resulting page is shown below: - -![event-detail-ui](images/event-detail-ui.png) - -### Integration Tests - -Create an `EventAppService_Tests` class in the `EventOrganizer.Application.Tests` project: - -````csharp -using System; -using System.Threading.Tasks; -using Shouldly; -using Xunit; - -namespace EventOrganizer.Events -{ - [Collection(EventOrganizerTestConsts.CollectionDefinitionName)] - public class EventAppService_Tests : EventOrganizerApplicationTestBase - { - private readonly IEventAppService _eventAppService; - - public EventAppService_Tests() - { - _eventAppService = GetRequiredService(); - } - - [Fact] - public async Task Should_Create_A_Valid_Event() - { - // Create an event - - var eventId = await _eventAppService.CreateAsync( - new EventCreationDto - { - Title = "My test event 1", - Description = "My test event description 1", - IsFree = true, - StartTime = DateTime.Now.AddDays(2) - } - ); - - eventId.ShouldNotBe(Guid.Empty); - - // Get the event - - var @event = await _eventAppService.GetAsync(eventId); - @event.Title.ShouldBe("My test event 1"); - - // Get upcoming events - - var events = await _eventAppService.GetUpcomingAsync(); - events.ShouldContain(x => x.Title == "My test event 1"); - } - } -} -```` - -## Source Code - -Source code of the completed application is [available on GitHub](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer). \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-create-ui.png b/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-create-ui.png deleted file mode 100644 index 4dd055d256..0000000000 Binary files a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-create-ui.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-detail-ui.png b/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-detail-ui.png deleted file mode 100644 index f916a129dc..0000000000 Binary files a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-detail-ui.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-list-ui.png b/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-list-ui.png deleted file mode 100644 index 4f049c2339..0000000000 Binary files a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/event-list-ui.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/index-title.png b/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/index-title.png deleted file mode 100644 index d923a0b7a7..0000000000 Binary files a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/index-title.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-all.png b/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-all.png deleted file mode 100644 index 2f9a07a24f..0000000000 Binary files a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-all.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-create.png b/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-create.png deleted file mode 100644 index 62e4569a57..0000000000 Binary files a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-create.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-upcoming.png b/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-upcoming.png deleted file mode 100644 index 49270afba4..0000000000 Binary files a/docs/en/Community-Articles/2020-12-04-Event-Organizer/images/swagger-event-upcoming.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/POST.md b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/POST.md deleted file mode 100644 index 4f2f5556eb..0000000000 --- a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/POST.md +++ /dev/null @@ -1,110 +0,0 @@ -## Using DevExpress Blazor UI Components With the ABP Framework - -Hi, in this step by step article, I will show you how to integrate [DevExpress](https://demos.devexpress.com/blazor/) blazor UI components into ABP Framework-based applications. - -![both-example-result](both-example-result.png) - -*(A screenshot from the example application developed in this article)* - -## Create the Project - -> ABP Framework offers startup templates to get into business faster. - -In this article, I will create a new startup template with EF Core as a database provider and Blazor for UI framework. But if you already have a project with Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. - -> If you already have a project with the Blazor UI, you can skip this section. - -* Before starting to development, we will create a solution named `DevExpressSample` (or whatever you want). We will create a new startup template with EF Core as a database provider and Blazor for UI framework by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -````bash -abp new DevExpressSample -u blazor -```` - -![initial-project](initial-project.png) - -* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE) and run the `DevExpressSample.DbMigrator` to create the database and seed initial data (which creates the admin user, admin role, permissions etc.) - -* After database and initial data created, -* Run the `DevExpressSample.HttpApi.Host` to see our server side working and -* Run the `DevExpressSample.Blazor` to see our UI working properly. - -> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_ - -## Install DevExpress - -You can follow [this documentation](https://docs.devexpress.com/Blazor/401986/getting-started/install-components-and-create-an-application/without-devexpress-installer/microsoft-templates) to install DevExpress packages into your computer. - -> Don't forget to add _"DevExpress NuGet Feed"_ to your **Nuget Package Sources**. - -### Adding DevExpress NuGet Packages - -Add the `DevExpress.Blazor` NuGet package to the `DevExpressSample.Blazor` project. - -``` -Install-Package DevExpress.Blazor -``` - -### Register DevExpress Resources - -1. Add the following line to the HEAD section of the `wwwroot/index.html` file within the `DevExpressSample.Blazor` project: - - ```Razor - - - - - ``` - -2. In the `DevExpressSampleBlazorModule` class, call the `AddDevExpressBlazor()` method from your project's `ConfigureServices()` method: - - ```csharp - public override void ConfigureServices(ServiceConfigurationContext context) - { - var environment = context.Services.GetSingletonInstance(); - var builder = context.Services.GetSingletonInstance(); - // ... - builder.Services.AddDevExpressBlazor(); - } - ``` - -3. Register the **DevExpressSample.Blazor** namespace in the `_Imports.razor` file: - - ```Razor - @using DevExpress.Blazor - ``` - -### Result - -The installation step was done. You can use any DevExpress Blazor UI component in your application: - -Example: A Scheduler: - -![sample-appointment](sample-appointment.gif) - -This example has been created by following [this documentation](https://demos.devexpress.com/blazor/SchedulerViewTypes). - -## The Sample Application - -We have created a sample application with [Data Grid](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxDataGrid-1) example. - -### The Source Code - -You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/DevExpress-Blazor). - -The related files for this example are marked in the following screenshots. - -![data-grid-app-contract](data-grid-app-contract.png) - -![data-grid-application](data-grid-application.png) - -![data-grid-web](data-grid-blazor.png) - -### Additional Notes - -#### Data Storage - -I've used an in-memory list to store data for this example, instead of a real database. Because it is not related to DevExpress usage. There is a `SampleDataService.cs` file in `Data` folder at `DevExpressSample.Application.Contracts` project. All the data is stored here. - -## Conclusion - -In this article, I've explained how to use [DevExpress](https://www.devexpress.com/blazor/) components in your application. ABP Framework is designed so that it can work with any UI library/framework. diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/both-example-result.png b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/both-example-result.png deleted file mode 100644 index 589d5f223a..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/both-example-result.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/cover-image.png b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/cover-image.png deleted file mode 100644 index 91bd4f5a95..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/cover-image.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-app-contract.png b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-app-contract.png deleted file mode 100644 index 79b694691a..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-app-contract.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-application.png b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-application.png deleted file mode 100644 index e382f7fd2d..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-application.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-blazor.png b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-blazor.png deleted file mode 100644 index b093ebdd3f..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/data-grid-blazor.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/initial-project.png b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/initial-project.png deleted file mode 100644 index 510c536606..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/initial-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/sample-appointment.gif b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/sample-appointment.gif deleted file mode 100644 index cde15e8f74..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/sample-appointment.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md deleted file mode 100644 index 971eb88f97..0000000000 --- a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md +++ /dev/null @@ -1,537 +0,0 @@ -# How to Integrate the Telerik Blazor Components to the ABP Blazor UI? - -## Introduction - -Hi, in this step by step article, we will see how we can integrate the Telerik Blazor Components to our Blazor UI. - -## Creating the Solution - -> ABP Framework offers startup templates to get into business faster. - -In this article, I will create a new startup template with EF Core as a database provider and Blazor for UI framework. But if you already have a project with Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. - -> If you already have a project with the Blazor UI, you can skip this section. - -* Before starting to development, we will create a solution named `TelerikComponents` (or whatever you want). We will create a new startup template with EF Core as a database provider and Blazor for UI framework by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -```bash -abp new TelerikComponents --ui blazor --database-provider ef -``` - -* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE) and run the `TelerikComponents.DbMigrator` to create the database and seed initial data (which creates the admin user, admin role, permissions, etc.) - -* After the database and initial data created, -* Run the `TelerikComponents.HttpApi.Host` to see our server-side working and -* Run the `TelerikComponents.Blazor` to see our UI working. - -> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_ - -## Starting the Development - -### Pre-requisite - -* First thing we need to do is downloading the [Progress Control Panel](https://www.telerik.com/download-trial-file/v2/control-panel?_ga=2.212029332.1667119438.1607582144-1944255175.1605161949) to get Telerik Blazor Components on our development machine. - -* If you will use the Telerik Blazor Components for the first time or you don't have an active license you can click [here](https://www.telerik.com/login/v2/download-b?ReturnUrl=https%3a%2f%2fwww.telerik.com%2fdownload-trial-file%2fv2-b%2fui-for-blazor%3f_ga%3d2.212029332.1667119438.1607582144-1944255175.1605161949#register) to download free trial. - -> You can find the more installation details from [here](https://docs.telerik.com/blazor-ui/getting-started/client-blazor?_ga=2.55603115.1667119438.1607582144-1944255175.1605161949&_gac=1.261851647.1607669357.CjwKCAiAq8f-BRBtEiwAGr3DgUDhBT25rs7hU0EQ8K-AfeUVxs3hSoIuIAuBOZ17CNPI4ZEArORPExoCyd4QAvD_BwE#step-0---download-the-components). - ->**Notes:** To download Telerik Blazor packages via NuGet, we need to setup Telerik NuGet package source. We can state it in the installer as below. In this way, we can download the required Telerik Blazor packages via NuGet. - -![setup-nuget-package-source](./automated-nuget-feed-setup.png) - -### Step 1 (Configurations) - -* We need to install the `Telerik.UI.for.Blazor` Nuget package to our Blazor project (`*.Blazor`). We need to choose package source to **telerik.com** for Visual Studio to see this package. - -* If you use trial version of Telerik, you can download **Telerik.UI.for.Blazor.Trial** package via NuGet. - -* After the installation finished, we need to open **index.html** (it's under *wwwroot* folder) to add css and js files in our application. - -* Add the following lines just before the closing head tag (**/head**). - -```html - ... - - - - - -``` - -* After that, we need to add the Telerik Blazor Components to our application's service collection. So just open the `TelerikComponentsBlazorModule` and update the `ConfigureServices` method with the following content. - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var environment = context.Services.GetSingletonInstance(); - var builder = context.Services.GetSingletonInstance(); - - ConfigureAuthentication(builder); - ConfigureHttpClient(context, environment); - ConfigureBlazorise(context); - ConfigureRouter(context); - ConfigureUI(builder); - ConfigureMenu(context); - ConfigureAutoMapper(context); - - //add this line to be able to use the components - builder.Services.AddTelerikBlazor(); -} -``` - -* After the service added we will continue by opening **_Imports.razor** file and add the global using statements as below. This will bring our Telerik components into scope throughout the application. - -![telerik-blazor-component-1](./telerik-blazor-component-1.jpg) - -* After all of these steps, the Telerik UI Components are ready to be used anywhere in our application. We can use the Telerik Blazor Components by wrapping our components or pages between `` and `` tags. - -### Step 2 - Checking Configurations - -* We should check, have we done the right configurations or not. For that, we can open the **Index.razor** file and we can update the component with the following content. - -```razor -@page "/" -@using Volo.Abp.MultiTenancy -@inherits TelerikComponentsComponentBase -@inject ICurrentTenant CurrentTenant -@inject AuthenticationStateProvider AuthenticationStateProvider -@using System.Timers - -@implements IDisposable - - -
    -
    Telerik Progress Bar Component
    - -
    -
    - -@code { - private const int TimerInterval = 1000; - private const int TotalTime = 10 * TimerInterval; - private double ProgressValue = 0; - private int ProgressStep = 100 / (TotalTime / TimerInterval); - private Timer Timer { get; set; } = new Timer(); - - private void Dispose() - { - StopProgress(); - Timer?.Close(); - } - - protected override void OnAfterRender(bool firstRender) - { - if (Timer.Enabled == false) - { - Timer.Interval = TimerInterval; - Timer.Elapsed -= OnTimerElapsed; - Timer.Elapsed += OnTimerElapsed; - Timer.AutoReset = true; - Timer.Start(); - } - } - - private void OnTimerElapsed(Object source, ElapsedEventArgs e) - { - if (ProgressValue < 100) - { - UpdateProgress(); - } - else - { - StopProgress(); - } - } - - private void UpdateProgress() - { - ProgressValue += ProgressStep; - - InvokeAsync(StateHasChanged); - } - - private void StopProgress() - { - Timer?.Stop(); - } -} - - -``` - -* In here, we've just added the `TelerikProgressBar` component to check the integration configured properly. - -* When we run `*.HttpApi.Host` and `*.Blazor` projects, we should see that the `TelerikProgressBar` component works and has similar view as the below gif. - -![telerik-progress-bar](./telerik-progress-bar.gif) - -* If you haven't seen this component like above, you should check the above configurations and be assure every step done as stated. - -### Step 3 - Using The Telerik Blazor Components (Sample Application) - -* Let's create a sample application for use other Telerik Blazor Components (like DataGrid). - -* We will use [jsonplaceholder](https://jsonplaceholder.typicode.com/) as **mock data** to the listing, adding, updating and deleting posts. - -* Firstly, we can create a folder named `Posts` and inside this folder, we can create the classes which are highlighted in the following screenshot. - -![sample-application](./sample-application.jpg) - -* After classes created we can fill the classes with the following contents. - -**Post.cs** -```csharp -using System; - -namespace TelerikComponents.Posts -{ - [Serializable] - public class Post - { - public int Id { get; set; } - - public string Title { get; set; } - - public string Body { get; set; } - - public int UserId { get; set; } - } -} -``` - -**Comment.cs** -```csharp -using System; - -namespace TelerikComponents.Posts -{ - [Serializable] - public class Comment - { - public int PostId { get; set; } - - public int Id { get; set; } - - public string Name { get; set; } - - public string Email { get; set; } - - public string Body { get; set; } - } -} -``` - -**IPostAppService.cs** -```csharp -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace TelerikComponents.Posts -{ - public interface IPostAppService : IApplicationService - { - Task> GetPostsAsync(); - - Task AddPostAsync(Post post); - - Task UpdatePostAsync(int postId, Post post); - - Task DeletePostAsync(int postId); - - Task GetFirstCommentByPostIdAsync(int postId); - } -} -``` -* In here, we basically created two class (which are **Post** and **Comment**). These classes are used to hold data returned as JSON. - -* After that, we need to implement `IPostAppService`. For achieve this, we can create a folder named `Posts` in **\*.Application** layer and inside this folder we can create a class named `PostAppService` with the following content. - -**PostAppService.cs** -```csharp -using System.Collections.Generic; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace TelerikComponents.Posts -{ - public class PostAppService : ApplicationService, IPostAppService - { - private JsonSerializerOptions _options = new JsonSerializerOptions - { - IncludeFields = true, - PropertyNameCaseInsensitive = true - }; - - public async Task> GetPostsAsync() - { - var url = "https://jsonplaceholder.typicode.com/posts"; - List _posts = new List(); - - using (var client = new HttpClient()) - { - var result = await client.GetAsync(url); - - if (result.IsSuccessStatusCode) - { - var content = await result.Content.ReadAsStringAsync(); - var deserializedPosts = JsonSerializer.Deserialize>(content, _options); - - _posts = deserializedPosts; - } - } - - return _posts; - } - - public async Task AddPostAsync(Post post) - { - var url = "https://jsonplaceholder.typicode.com/posts"; - Post addedPost = null; - - using (var client = new HttpClient()) - { - var serializePost = JsonSerializer.Serialize(post); - var content = new StringContent(serializePost, Encoding.UTF8, "application/json"); - var result = await client.PostAsync(url, content); - - if (result.IsSuccessStatusCode) - { - var response = await result.Content.ReadAsStringAsync(); - addedPost = JsonSerializer.Deserialize(response); - } - } - - return addedPost; - } - - public async Task UpdatePostAsync(int postId, Post post) - { - var url = $"https://jsonplaceholder.typicode.com/posts/{postId}"; - Post updatedPost = null; - - using (var client = new HttpClient()) - { - var serializePost = JsonSerializer.Serialize(post); - var content = new StringContent(serializePost, Encoding.UTF8, "application/json"); - var result = await client.PutAsync(url, content); - - if (result.IsSuccessStatusCode) - { - var response = await result.Content.ReadAsStringAsync(); - updatedPost = JsonSerializer.Deserialize(response); - } - } - - return updatedPost; - } - - public async Task DeletePostAsync(int postId) - { - var url = $"https://jsonplaceholder.typicode.com/posts/{postId}"; - - using (var client = new HttpClient()) - { - await client.DeleteAsync(url); - } - } - - public async Task GetFirstCommentByPostIdAsync(int postId) - { - var url = $"https://jsonplaceholder.typicode.com/posts/{postId}/comments"; - - List _comments = new List(); - - using (var client = new HttpClient()) - { - var result = await client.GetAsync(url); - - if (result.IsSuccessStatusCode) - { - var content = await result.Content.ReadAsStringAsync(); - var deserializedPosts = JsonSerializer.Deserialize>(content, _options); - - _comments = deserializedPosts; - } - } - - return _comments[0]; - } - } -} -``` - -* In here, we've implemented `IPostAppService` methods by using [jsonplaceholder](https://jsonplaceholder.typicode.co) API. These endpoints provide us basic crud functionallity. - -* After the implemenation, we can start to create the user interface. - -#### Blazor UI - -* We can create **/Posts** page for listing, updating, deleting and creating our posts. So, create a razor page named `Posts.razor` under **Pages** folder in `*.Blazor` project. - -**Posts.razor** -```razor -@page "/Posts" -@using TelerikComponents.Posts -@using IconName = Telerik.Blazor.IconName -@inject IPostAppService PostAppService - -

    Posts

    - - - - - - - - Update - Edit - Delete - Display Comment - Cancel - - - - Add Post - - - - @* Modal *@ - - - Comment - - - - - -

    Email: @Comment.Email

    -

    - Message: @Comment.Body -

    -
    -
    -
    -``` - -**Post.razor.cs** -```csharp -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Telerik.Blazor.Components; -using TelerikComponents.Posts; - -namespace TelerikComponents.Blazor.Pages -{ - public partial class Posts - { - private List GridData { get; set; } - private TelerikGrid Grid { get; set; } - private bool ModalVisible { get; set; } = false; - private Comment Comment { get; set; } - - public Posts() - { - Comment = new Comment(); - } - - protected override async Task OnInitializedAsync() - { - await LoadDataAsync(); - } - - private async Task LoadDataAsync() - { - GridData = await PostAppService.GetPostsAsync(); - } - - private async Task UpdateHandler(GridCommandEventArgs args) - { - var post = (Post) args.Item; - - await PostAppService.UpdatePostAsync(post.Id, post); - - var matchingPost = GridData.FirstOrDefault(x => x.Id == post.Id); - - if (matchingPost != null) - { - matchingPost.Body = post.Body; - matchingPost.Title = post.Title; - } - } - - private async Task DeleteHandler(GridCommandEventArgs args) - { - var post = (Post) args.Item; - - GridData.Remove(post); - } - - private async Task CreateHandler(GridCommandEventArgs args) - { - var post = (Post) args.Item; - - var addedPost = await PostAppService.AddPostAsync(post); - - GridData.Insert(0, addedPost); - } - - private async Task PostDetailAsync(GridCommandEventArgs args) - { - var post = (Post) args.Item; - - Comment = await PostAppService.GetFirstCommentByPostIdAsync(post.Id); - - ModalVisible = true; - } - } -} -``` - -* In here, we've used `TelerikGrid` component. - -* The `Telerik Grid` is a powerful component, which allows you to visualize and edit data via its table representation. It provides a variety of options about how to present and perform operations over the underlying data, such as paging, sorting, filtering and editing. - -* The Blazor UI Grid allows flexible customization of its items exposing rows, columns and edit templates for this purpose. - -### Final Result - -* After all of these steps, we can finally run our application. - * Run `*.HttpApi.Host` project for use the required endpoints, - * Run `*.Blazor` project for see the Blazor UI. - -* When we navigate to `Posts` route, we should see the following screenshot in this page. - -![final-result](./final-result.jpg) - -## Conclusion - -In this article, I've tried to explain how we can integrate [Telerik Blazor Component](https://www.telerik.com/blazor-ui) to our Blazor UI. ABP Framework designed as modular, so that it can work with any UI library/framework. \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/automated-nuget-feed-setup.png b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/automated-nuget-feed-setup.png deleted file mode 100644 index 39565de402..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/automated-nuget-feed-setup.png and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/final-result.jpg b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/final-result.jpg deleted file mode 100644 index 13d2a53423..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/final-result.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/sample-application.jpg b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/sample-application.jpg deleted file mode 100644 index 8615a1cb2a..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/sample-application.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-blazor-component-1.jpg b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-blazor-component-1.jpg deleted file mode 100644 index 03e671a4bc..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-blazor-component-1.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-progress-bar.gif b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-progress-bar.gif deleted file mode 100644 index e1eff85986..0000000000 Binary files a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/telerik-progress-bar.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/POST.md b/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/POST.md deleted file mode 100644 index 02cef11a2e..0000000000 --- a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/POST.md +++ /dev/null @@ -1,1585 +0,0 @@ -# Using Angular Material Components With the ABP Framework - -## Introduction - -Angular Material library is a popular and well-known library in the community. We will implement Angular Material components with the ABP framework in this article. We will follow the Book Store tutorial which documented in [ABP Documentation](https://docs.abp.io/en/abp/latest/Tutorials/Part-1) - -> This article doesn't include server-side parts except the **Author With Books Form** -> section. Please follow the tutorial for server-side parts. - -## Installation - -Create a project with ABP CLI. Run following command in terminal: - -```bash - abp new Acme.BookStore.AngularMaterial -u angular -o AcmeBookStoreAngularMaterial -``` - -Add Angular Material package to the created project. Run this command in the terminal at the `angular` directory: - -``` - ng add @angular/material -``` - -## Book CRUD Actions - -> Please complete the following steps before starting this section: -> -> - Follow the server-side steps at [Web Application Development Tutorial - Part 1: Creating the Server Side](https://docs.abp.io/en/abp/latest/Tutorials/Part-1) with remembering our application name is **Acme.BookStore.AngularMaterial** -> - Follow the localization part of [Web Application Development Tutorial - Part 2: The Book List Page](https://docs.abp.io/en/abp/latest/Tutorials/Part-2?UI=NG&DB=EF#localization) -> - Run following command in terminal at `angular` directory  -> `abp generate-proxy` - -In this section, we will create the book list page, book create and update dialog using the Angular Material Modules. - -Add material modules to `SharedModule`'s imports and exports arrays which placed in `angular/src/app/shared/shared.module.ts`: - -```typescript -import { MatCardModule } from "@angular/material/card"; -import { MatTableModule } from "@angular/material/table"; -import { MatPaginatorModule } from "@angular/material/paginator"; -import { MatSortModule } from "@angular/material/sort"; -import { MatButtonModule } from "@angular/material/button"; - -@NgModule({ - imports: [ - CoreModule, - ThemeSharedModule, - ThemeBasicModule, - NgbDropdownModule, - NgxValidateCoreModule, - - MatCardModule, // added this line - MatTableModule, // added this line - MatPaginatorModule, // added this line - MatSortModule, // added this line - MatButtonModule, // added this line - ], - exports: [ - CoreModule, - ThemeSharedModule, - ThemeBasicModule, - NgbDropdownModule, - NgxValidateCoreModule, - - MatCardModule, // added this line - MatTableModule, // added this line - MatPaginatorModule, // added this line - MatSortModule, // added this line - MatButtonModule, // added this line - ], -}) -export class SharedModule {} -``` - -Run the following command in the terminal at the `angular` directory to create the book module: - -``` -yarn ng generate module book --module app --routing --route books -``` - -Remove `CommonModule` form and import `SharedModule` from imports array at `book.module.ts`: - -```typescript -import { NgModule } from "@angular/core"; -import { BookRoutingModule } from "./book-routing.module"; -import { BookComponent } from "./book.component"; -import { SharedModule } from "../shared/shared.module"; - -@NgModule({ - declarations: [BookComponent], - imports: [ - BookRoutingModule, - SharedModule, // this line added - ], -}) -export class BookModule {} -``` - -> We deleted the `CommonModule` because `CommonModule` in `CoreModule`'s exports array and `CoreModule` in `SharedModule`'s exports array. - -We will add routes by adding items to the return array of the `routesProvider` created when creating a project for adding navigation elements for books route. For more information, see the [`RoutesService` document](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu#via-routesservice). - -Open the `src/app/route.provider.ts` file replace the `configureRoutes` function declaration as shown below: - -```typescript -import { eLayoutType, RoutesService } from "@abp/ng.core"; - -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - { - path: "/", - name: "::Menu:Home", - iconClass: "fas fa-home", - order: 1, - layout: eLayoutType.application, - }, - { - path: "/book-store", - name: "::Menu:BookStore", - iconClass: "fas fa-book", - order: 2, - layout: eLayoutType.application, - }, - { - path: "/books", - name: "::Menu:Books", - parentName: "::Menu:BookStore", - layout: eLayoutType.application, - }, - ]); - }; -} -``` - -### Book List - -Replace `BookComponent` with the following code placed at `angular/src/book/book.component.ts` : - -```typescript -import { Component, OnInit } from "@angular/core"; -import { ListService, PagedResultDto } from "@abp/ng.core"; -import { BookDto, BookService } from "@proxy/books"; -import { PageEvent } from "@angular/material/paginator"; -import { Sort } from "@angular/material/sort"; - -@Component({ - selector: "app-book", - templateUrl: "./book.component.html", - styleUrls: ["./book.component.scss"], - providers: [ListService], -}) -export class BookComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - columns: string[] = ["name", "type", "price"]; - - constructor( - public readonly list: ListService, - private bookService: BookService - ) { - this.list.maxResultCount = 2; - } - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getList(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - changePage(pageEvent: PageEvent) { - this.list.page = pageEvent.pageIndex; - } - - changeSort(sort: Sort) { - this.list.sortKey = sort.active; - this.list.sortOrder = sort.direction; - } -} -``` - -- We imported and injected the generated `BookService`. -- We used the [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service), a utility service of the ABP Framework which provides easy pagination, sorting and searching. -- We set `this.list.maxResultCount` to 2 in the constructor, it can be changed programmatically for example changing value with the dropdown in the template - -Replace the `book.component.html` in the `angular/src/book/` with following code: - -```html - - - - {{ '::Menu:Books' | abpLocalization }} - - - - - - - - - - - - - - - -
    - {{'::Name' | abpLocalization}} - {{element.name}} - {{'::Type' | abpLocalization}} - - {{ '::Enum:BookType:' + element.type | abpLocalization }} -
    - -
    -
    -``` - -- We used the [Material Card](https://material.angular.io/components/card/overview) component as a container -- We used [Material Table](https://material.angular.io/components/table/overview) and we made `name` and `type` columns sortable. `changeSort` method executes when sorting change. -- We used the [Material Pagination](https://material.angular.io/components/paginator/overview). `changePage` method executes when the page changed - -![Book List](book-list.gif) - -### Book Create - -In this section, we will create `BookDialogComponent` and we will display this component via [`Material Dialog`](https://material.angular.io/components/dialog/overview). We will use also [Material Input](https://material.angular.io/components/input/overview), [Material Select](https://material.angular.io/components/select/overview), [Material DatePicker](https://material.angular.io/components/datepicker/overview) modules in this component for book form. - -Create a new component named `BookDialogComponent` in the `angular/src/book/components` folder with the following command: - -``` -yarn ng generate component book/components/BookDialog --module book -``` - -> We used --module option for declaring in the component to a specific module. - -Add Material modules to `SharedModule`'s imports and exports arrays: - -```typescript -import { MatDialogModule } from "@angular/material/dialog"; -import { MatDatepickerModule } from "@angular/material/datepicker"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; -import { MatSelectModule } from "@angular/material/select"; -import { MatIconModule } from "@angular/material/icon"; -import { MatNativeDateModule } from "@angular/material/core"; - -@NgModule({ - imports: [ - // other imports - MatDialogModule, - MatDatepickerModule, - MatNativeDateModule, - MatFormFieldModule, - MatInputModule, - MatSelectModule, - MatIconModule, - ], - exports: [ - // other exports - MatDialogModule, - MatDatepickerModule, - MatFormFieldModule, - MatInputModule, - MatSelectModule, - MatIconModule, - ], -}) -export class SharedModule {} -``` - -Replace `book-dialog.component.ts` in `angular/src/book/` with following code: - -```typescript -import { Component, Inject, OnInit } from "@angular/core"; -import { - MAT_DIALOG_DATA, - MAT_DIALOG_DEFAULT_OPTIONS, -} from "@angular/material/dialog"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { bookTypeOptions } from "@proxy/books"; - -@Component({ - selector: "app-book-dialog", - templateUrl: "./book-dialog.component.html", - styleUrls: ["./book-dialog.component.scss"], - providers: [ - { - provide: MAT_DIALOG_DEFAULT_OPTIONS, - useValue: { hasBackdrop: true, width: "50vw" }, - }, - ], -}) -export class BookDialogComponent implements OnInit { - form: FormGroup; - - bookTypes = bookTypeOptions; - - constructor(private fb: FormBuilder) {} - - ngOnInit(): void { - this.buildForm(); - } - - buildForm() { - this.form = this.fb.group({ - name: [null, Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } - - getFormValue() { - const { publishDate } = this.form.value; - return { - ...this.form.value, - publishDate: `${publishDate?.getFullYear()}-${ - publishDate?.getMonth() + 1 - }-${publishDate?.getDate()}`, - }; - } -} -``` - -- We made a form which form controls' names are the same as BookDto -- We provided the `MAT_DIALOG_DEFAULT_OPTIONS` token to change Material Dialog options for this component. Provided options are only available for this component. - -Replace the `book-dialog.component.html` with following code: - -```html -

    {{ '::NewBook' | abpLocalization }}

    - -
    - - {{'::Name' | abpLocalization}} * - - - - - {{'::Price' | abpLocalization}} * - - - - - {{'::Type' | abpLocalization}} * - - {{ '::Enum:BookType:' + type.value | abpLocalization }} - - - - - {{'::PublishDate' | abpLocalization}} * - - - - -
    -
    - - - - -``` - -- We created a form with material form field components. -- We added 2 buttons for closing dialog and saving form in the `mat-dialog-actions` element. - -Create the `createBook` method and inject `MatDialog` in `book.component.ts`. Then use the material dialog's `open` method inside the `createBook` method: - -```typescript -import { BookDialogComponent } from "./components/book-dialog"; - -export class BookComponent { - constructor( - // ... - // inject dialog - public dialog: MatDialog - ) { - //... - } - //... other methods - createBook() { - const dialogRef = this.dialog.open(BookDialogComponent); - dialogRef.afterClosed().subscribe((result) => { - if (result) { - this.bookService.create(result).subscribe(() => { - this.list.get(); - }); - } - }); - } -} -``` - -- We displayed BookDialogComponent via Material Dialog. If the result has data after the dialog closes we made 2 HTTP requests for creating a book and refreshing the book list. - -Add create book button near the `mat-card-title` element in `book.component.html`: - -```html -{{ '::Menu:Books' | abpLocalization }} - -``` - -The final UI looks like below: - -![Book Create](./book-create.gif) - -### Edit Book - -We will use the same dialog component for editing the book. And we will add the `actions` column to the book list table. The actions column is a simple dropdown. We will use [Material Menu](https://material.angular.io/components/menu/overview) for creating dropdown - -Add `MatMenuModule` to `SharedModule` metadata's imports and exports array like this: - -```typescript -import { MatMenuModule } from "@angular/material/menu"; - -@NgModule({ - imports: [ - // other imports - MatMenuModule, - ], - exports: [ - // other exports - MatMenuModule, - ], -}) -export class SharedModule {} -``` - -Edit `columns` array and add `editBook` method in `book.component.ts` as shown below: - -```typescript -columns: string[] = ['actions', /* ... other columns*/]; - -editBook(id: string) { - this.bookService.get(id).subscribe((book) => { - const dialogRef = this.dialog.open(BookDialogComponent, { - data: book - }); - dialogRef.afterClosed().subscribe(result => { - if (result) { - this.bookService.update(id, result).subscribe(() => { - this.list.get(); - }); - } - }); - }); -} -``` - -- We passed the data to `BookDialogComponent` with passing the data property `open` method of the material dialog. -- We checked data after closing the dialog for sending HTTP requests as in the `Create Book` section. - -Add actions column before name column and add `mat-menu` end of file in the `book.component.html` as shown below: - -```html - - - - {{'::Actions' | abpLocalization}} - - - - - - - - - - - - - - -``` - -Get passed data which passed with material dialog's `open` method and use this data to create a form with initial values in `book-dialog.component.ts` as shown below: - -```typescript - constructor( - //inject data - @Inject(MAT_DIALOG_DATA) public data: BookDto, - ) { - } - - buildForm() { - this.form = this.fb.group({ - name: [this.data?.name /*modified*/, Validators.required], - type: [this.data?.type /*modified*/, Validators.required], - publishDate: [this.data?.publishDate ? new Date(this.data.publishDate) : null, /*modified*/, Validators.required], - price: [this.data?.price /*modified*/, Validators.required], - }); - } -``` - -Edit the dialog title if component has data, display **Edit Book** text otherwise **New Book** in `book-dialog.component.html`: - -```html -

    - {{ (data ? '::EditBook' : '::NewBook' )| abpLocalization }} -

    -``` - -![Book Edit](book-edit.gif) - -### Delete Book - -In the ABP Framework, a confirmation popup displays when the delete button is clicked. We will create a Confirmation Dialog and display this dialog with Material Dialog. - -Create `ConfirmationDialogComponent` in `angular/src/shared/components` directory with following command: - -``` -yarn ng generate component shared/components/ConfirmationDialog --module shared -``` - -Replace `ConfirmationDialogComponent` with following code: - -```typescript -import { Component, Inject } from "@angular/core"; -import { - MAT_DIALOG_DATA, - MAT_DIALOG_DEFAULT_OPTIONS, -} from "@angular/material/dialog"; - -export interface ConfirmationDialogData { - title: string; - description: string; -} -@Component({ - selector: "app-confirmation-dialog", - templateUrl: "./confirmation-dialog.component.html", - styleUrls: ["./confirmation-dialog.component.scss"], - providers: [ - { - provide: MAT_DIALOG_DEFAULT_OPTIONS, - useValue: { hasBackdrop: true, width: "450px" }, - }, - ], -}) -export class ConfirmationDialogComponent { - constructor(@Inject(MAT_DIALOG_DATA) public data: ConfirmationDialogData) {} -} -``` - -Replace `confirmation-dialog.component.html` with the following code: - -```html - -
    - warning -

    {{ data.title | abpLocalization }}

    -

    {{ data.description | abpLocalization }}

    -
    -
    - - - - -``` - -Replace `confirmation-dialog.component.scss` with following code: - -```scss -:host { - .dialog-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - } - mat-icon.warn-icon { - font-size: 100px; - height: 100px; - width: 100px; - line-height: 100px; - } - p { - color: #777; - font-size: 16px; - } -} -``` - -Add `deleteBook` method to `BookComponent`: - -```typescript -import { BookDialogComponent } from './components/book-dialog/book-dialog.component'; -import { ConfirmationDialogComponent } from '../shared/components/confirmation-dialog/confirmation-dialog.component'; - -deleteBook(id: string) { - const confirmationDialogRef = this.dialog.open(ConfirmationDialogComponent, { - data: { - title: '::AreYouSure', - description: '::AreYouSureToDelete' - } - }); - confirmationDialogRef.afterClosed().subscribe(confirmationResult => { - if (confirmationResult) { - this.bookService.delete(id).subscribe(() => this.list.get()); - } - }); -} -``` - -Add delete button to actions' button menu template in `book.component.html`: - -```html - - - - - - - - -``` - -![Book Delete](./book-delete.gif) - -## Authorization - -You can follow steps for authorization at the [Web Application Development Tutorial - Part 5: Authorization](https://docs.abp.io/en/abp/latest/Tutorials/Part-5?UI=NG&DB=EF) - -## Author CRUD Actions - -> Please complete the following steps before starting this section -> -> - [Web Application Development Tutorial - Part 6: Authors: Domain Layer](https://docs.abp.io/en/abp/latest/Tutorials/Part-6) -> - [Web Application Development Tutorial - Part 7: Authors: Database Integration](https://docs.abp.io/en/abp/latest/Tutorials/Part-7) -> - [Web Application Development Tutorial - Part 8: Authors: Application Layer](https://docs.abp.io/en/abp/latest/Tutorials/Part-8) -> - Run following command in terminal at `angular` directory: -> `abp generate-proxy` - -In this section, we will create an author list page and author create/update dialog by following the same steps in the `Book CRUD Actions` section - -Run the following command in the terminal for creating author module and components: - -``` -yarn ng generate module author --module app --routing --route authors -``` - -Create `AuthorDialogComponent` in `angular/src/app/author/components` directory with following command: - -``` -yarn ng generate component author/components/AuthorDialog -m author -``` - -Add `SharedModule` to `AuthorModule`'s imports array: - -```typescript -import { NgModule } from "@angular/core"; -import { AuthorWithBooksRoutingModule } from "./author-with-books-routing.module"; -import { AuthorWithBooksComponent } from "./author-with-books.component"; -import { SharedModule } from "../shared/shared.module"; - -@NgModule({ - declarations: [AuthorComponent, AuthorDialogComponent], - imports: [SharedModule, AuthorRoutingModule], -}) -export class AuthorModule {} -``` - -Open the `src/app/route.provider.ts` file replace the `configureRoutes` function declaration as shown below: - -```typescript -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - // other routes - { - path: "/authors", - name: "::Menu:Authors", - parentName: "::Menu:BookStore", - layout: eLayoutType.application, - }, - ]); - }; -} -``` - -Replace `AuthorDialogComponent` with following code below: - -```typescript -import { Component, Inject, OnInit } from "@angular/core"; -import { - MAT_DIALOG_DATA, - MAT_DIALOG_DEFAULT_OPTIONS, -} from "@angular/material/dialog"; -import { AuthorDto } from "@proxy/authors"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; - -@Component({ - selector: "app-author-dialog", - templateUrl: "./author-dialog.component.html", - styleUrls: ["./author-dialog.component.scss"], - providers: [ - { - provide: MAT_DIALOG_DEFAULT_OPTIONS, - useValue: { hasBackdrop: true, width: "50vw" }, - }, - ], -}) -export class AuthorDialogComponent implements OnInit { - form: FormGroup; - - constructor( - @Inject(MAT_DIALOG_DATA) public data: AuthorDto, - private fb: FormBuilder - ) {} - ngOnInit(): void { - this.buildForm(); - } - - buildForm() { - this.form = this.fb.group({ - name: [this.data?.name, Validators.required], - birthDate: [ - this.data?.birthDate ? new Date(this.data.birthDate) : null, - Validators.required, - ], - }); - } - - getFormValue() { - const { birthDate } = this.form.value; - return { - ...this.form.value, - publishDate: `${birthDate?.getFullYear()}-${ - birthDate?.getMonth() + 1 - }-${birthDate?.getDate()}`, - }; - } -} -``` - -Replace `author-dialog.component.html` with the following code below: - -```html -

    - {{ (data ? '::EditAuthor' : '::NewAuthor' )| abpLocalization }} -

    - -
    - - {{'::Name' | abpLocalization}} * - - - - - {{'::BirthDate' | abpLocalization}} * - - - - -
    -
    - - - - -``` - -Replace `author.component.ts` with following code below: - -```typescript -import { Component, OnInit } from "@angular/core"; -import { ListService, PagedResultDto } from "@abp/ng.core"; -import { AuthorDto, AuthorService } from "@proxy/authors"; -import { FormGroup } from "@angular/forms"; -import { PageEvent } from "@angular/material/paginator"; -import { Sort } from "@angular/material/sort"; -import { MatDialog } from "@angular/material/dialog"; -import { AuthorDialogComponent } from "./components/author-dialog/author-dialog.component"; -import { ConfirmationDialogComponent } from "../shared/components/confirmation-dialog/confirmation-dialog.component"; - -@Component({ - selector: "app-author", - templateUrl: "./author.component.html", - styleUrls: ["./author.component.scss"], - providers: [ListService], -}) -export class AuthorComponent implements OnInit { - author = { items: [], totalCount: 0 } as PagedResultDto; - - form: FormGroup; - - columns = ["actions", "name", "birthDate"]; - - constructor( - public readonly list: ListService, - private authorService: AuthorService, - public dialog: MatDialog - ) {} - - ngOnInit(): void { - const authorStreamCreator = (query) => this.authorService.getList(query); - - this.list.hookToQuery(authorStreamCreator).subscribe((response) => { - this.author = response; - }); - } - - changePage(pageEvent: PageEvent) { - this.list.page = pageEvent.pageIndex; - } - - changeSort(sort: Sort) { - this.list.sortKey = sort.active; - this.list.sortOrder = sort.direction; - } - - createAuthor() { - const dialogRef = this.dialog.open(AuthorDialogComponent); - dialogRef.afterClosed().subscribe((result) => { - if (result) { - this.authorService.create(result).subscribe(() => { - this.list.get(); - }); - } - }); - } - - editAuthor(id: any) { - this.authorService.get(id).subscribe((author) => { - const dialogRef = this.dialog.open(AuthorDialogComponent, { - data: author, - }); - dialogRef.afterClosed().subscribe((result) => { - if (result) { - this.authorService.update(id, result).subscribe(() => { - this.list.get(); - }); - } - }); - }); - } - - deleteAuthor(id: string) { - const confirmationDialogRef = this.dialog.open( - ConfirmationDialogComponent, - { - data: { - title: "::AreYouSure", - description: "::AreYouSureToDelete", - }, - } - ); - confirmationDialogRef.afterClosed().subscribe((confirmationResult) => { - if (confirmationResult) { - this.authorService.delete(id).subscribe(() => this.list.get()); - } - }); - } -} -``` - -Replace `author.component.html` with the following code: - -```html - - - - {{ '::Menu:Authors' | abpLocalization }} -
    - -
    -
    -
    - - - - - - - - - - - - - - -
    - {{'::Actions' | abpLocalization}} - - - - {{'::Name' | abpLocalization}} - {{element.name}} - {{'::BirthDate' | abpLocalization}} - - {{ element.birthDate | date }} -
    - -
    -
    - - - - - - -``` - -Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.AngularMaterial.Domain.Shared project` and add the following entries: - -```json - "Menu:Authors": "Authors", - "Authors": "Authors", - "AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?", - "BirthDate": "Birth date", - "NewAuthor": "New author", - "EditAuthor": "Edit Author" -``` - -![Author](./author-crud.gif) - -## Author And Book Relation - -> Please complete the following steps before starting this section -> -> - Complete server-side parts [Web Application Development Tutorial - Part 10: Book to Author Relation](https://docs.abp.io/en/abp/latest/Tutorials/Part-10) until [The User Interface](https://docs.abp.io/en/abp/latest/Tutorials/Part-10?UI=NG&DB=EF#the-user-interface) section -> - Run following command in terminal at `angular` directory: -> `abp generate-proxy` - -In this section, we will add author selection to the book creation form, create one form for adding an author with books using Material Stepper and display the author's name in the book list page. - -### Author Selection - -We will add the author select box using Material Select and we will get authors from the server in `BookDialogComponent`. - -Replace `book-dialog.component.ts` in `app/src/book/components/book-dialog` with following code: - -```typescript -import { Component, Inject, OnInit } from "@angular/core"; -import { - MAT_DIALOG_DATA, - MAT_DIALOG_DEFAULT_OPTIONS, -} from "@angular/material/dialog"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { - AuthorLookupDto, - BookDto, - BookService, - bookTypeOptions, -} from "@proxy/books"; -import { Observable } from "rxjs"; -import { map } from "rxjs/operators"; - -@Component({ - selector: "app-book-dialog", - templateUrl: "./book-dialog.component.html", - styleUrls: ["./book-dialog.component.scss"], - providers: [ - { - provide: MAT_DIALOG_DEFAULT_OPTIONS, - useValue: { hasBackdrop: true, width: "50vw" }, - }, - ], -}) -export class BookDialogComponent implements OnInit { - form: FormGroup; - - bookTypes = bookTypeOptions; - - authors$: Observable; // this line added - - constructor( - private fb: FormBuilder, - @Inject(MAT_DIALOG_DATA) public data: BookDto, - bookService: BookService // inject bookService - ) { - this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items)); // this line added - } - - ngOnInit(): void { - this.buildForm(); - } - - buildForm() { - this.form = this.fb.group({ - name: [this.data?.name, Validators.required], - type: [this.data?.type, Validators.required], - publishDate: [this.data?.publishDate, Validators.required], - price: [this.data?.price, Validators.required], - authorId: [this.data?.authorId, Validators.required], // this line added - }); - } -} -``` - -Add author select box before name field in `book-dialog.component.html` as shown below: - -```html - - - {{'::Author' | abpLocalization}} * - - {{ author.name }} - - - - - {{'::Name' | abpLocalization}} * - - -``` - -### Author Name Column - -Add the `authorName` item to columns array in `BookComponent`: - -```typescript -columns: string[] = [/* ...other columns*/, 'authorName']; -``` - -Add the `authorName` column after price column in `book.component.html`: - -```html - - - - -``` - -### Author With Books Form - -In this section, we will create an endpoint that takes author information and book list in the request body for creating an author and books by one request. - -Create a class named `CreateBookDto` in `Application.Contracts/Books` folder: - -```csharp -using System; -using System.ComponentModel.DataAnnotations; -using Acme.BookStore.Books; -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore.AngularMaterial.Books -{ - public class CreateBookDto: AuditedEntityDto - { - [Required] - [StringLength(128)] - public string Name { get; set; } - - [Required] - public BookType Type { get; set; } = BookType.Undefined; - - [Required] - [DataType(DataType.Date)] - public DateTime PublishDate { get; set; } = DateTime.Now; - - [Required] - public float Price { get; set; } - } -} -``` - -Create a class named `CreateAuthorWithBookDto` in `Application.Contracts/Books` folder: - -```csharp -using System.Collections.Generic; -using Acme.BookStore.AngularMaterial.Authors; - -namespace Acme.BookStore.AngularMaterial.Books -{ - public class CreateAuthorWithBookDto: CreateAuthorDto - { - public List Books { get; set; } - - public CreateAuthorWithBookDto() - { - Books = new List(); - } - } -} -``` - -Create a class named `AuthorWithDetailsDto` in `Application.Contracts/Books` folder: - -```csharp -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Acme.BookStore.AngularMaterial.Authors; - -namespace Acme.BookStore.AngularMaterial.Books -{ - public class AuthorWithDetailsDto: AuthorDto - { - [Required] - public List Books { get; set; } - - public AuthorWithDetailsDto() - { - Books = new List(); - } - } -} -``` - -Add following line to `IBookAppService` interface which placed in `Application.Contracts/Books`: - -```csharp -Task CreateAuthorWithBooksAsync(CreateAuthorWithBookDto input); -``` - -Add mappings for the above DTO's lines to `AngularMaterialApplicationAutoMapperProfile.cs` - -```csharp -CreateMap(); -CreateMap(); -CreateMap(); -``` - -Inject `AuthorManager` and add `CreateAuthorWithBooksAsync` method to `BookAppService.cs`: - -```csharp -namespace Acme.BookStore.AngularMaterial.Books -{ - [Authorize(AngularMaterialPermissions.Books.Default)] - public class BookAppService: - CrudAppService< - Book, - BookDto, - Guid, - PagedAndSortedResultRequestDto, - CreateUpdateBookDto>, - IBookAppService - { - private readonly IAuthorRepository _authorRepository; - private readonly AuthorManager _authorManager; // this line added - public BookAppService( - IRepository repository, - IAuthorRepository authorRepository, - // inject AuthorManager - AuthorManager authorManager) - : base(repository) - { - _authorRepository = authorRepository; - _authorManager = authorManager;// this line added - GetPolicyName = AngularMaterialPermissions.Books.Default; - GetListPolicyName = AngularMaterialPermissions.Books.Default; - CreatePolicyName = AngularMaterialPermissions.Books.Create; - UpdatePolicyName = AngularMaterialPermissions.Books.Edit; - DeletePolicyName = AngularMaterialPermissions.Books.Delete; - } - - // Other class methods - - // This method added - public async Task CreateAuthorWithBooksAsync(CreateAuthorWithBookDto input) - { - var author = await _authorManager.CreateAsync( - input.Name, - input.BirthDate, - input.ShortBio - ); - - var createdAuthor = await _authorRepository.InsertAsync(author); - var authorWithBooks = ObjectMapper.Map(createdAuthor); - foreach (var book in input.Books) - { - var bookEntity = ObjectMapper.Map(book); - bookEntity.AuthorId = author.Id; - var createdBook = await Repository.InsertAsync(bookEntity); - var bookDto = ObjectMapper.Map(createdBook); - bookDto.AuthorName = author.Name; - authorWithBooks.Books.Add(bookDto); - } - - return authorWithBooks; - } - } -} -``` - -Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.AngularMaterial.Domain.Shared project` and add the following entries: - -```json - "AuthorInfo": "Author Info", - "BookInfo": "Book Info", - "AddBook": "Add Book", - "NewAuthorWithBook": "New Author With Book", - "AuthorWithBook:Success": "{0} added with books successfully" -``` - -Run generate-proxy command in the terminal at `angular` directory: - -``` -abp generate-proxy -``` - -We will create `AuthorWithBooksModule` with components and we will use Material Stepper inside the component. - -Run the following command for creating `AuthorWithBooksModule`: - -``` -yarn ng generate module author-with-books --module app --routing --route author-with-books -``` - -Add `SharedModule` to `AuthorWithBooksModule`'s imports array as shown below: - -```typescript -import { SharedModule } from "../shared/shared.module"; - -@NgModule({ - declarations: [AuthorWithBooksComponent], - imports: [ - SharedModule, // this line added - AuthorWithBooksRoutingModule, - ], -}) -export class AuthorWithBooksModule {} -``` - -Add `MatStepperModule` to `SharedModule`'s imports array as shown below: - -```typescript -import { MatStepperModule } from "@angular/material/stepper"; - -@NgModule({ - imports: [ - // other imports - MatStepperModule, - ], - exports: [ - // other exports - MatStepperModule, - ], -}) -export class SharedModule {} -``` - -We will create one form which includes author form group and book form array. We will use these form elements at the [Material Stepper](https://material.angular.io/components/stepper/overview) integration. - -Replace `author-with-books.component.ts` with the following code: - -```typescript -import { Component, OnInit } from "@angular/core"; -import { bookTypeOptions } from "@proxy/books"; -import { FormArray, FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { STEPPER_GLOBAL_OPTIONS } from "@angular/cdk/stepper"; - -@Component({ - selector: "app-author-with-book", - templateUrl: "./author-with-book.component.html", - styleUrls: ["./author-with-book.component.scss"], - providers: [ - { - provide: STEPPER_GLOBAL_OPTIONS, - useValue: { displayDefaultIndicatorType: false }, - }, - ], -}) -export class AuthorWithBooksComponent implements OnInit { - form: FormGroup; - - bookTypes = bookTypeOptions; - - get bookFormArray(): FormArray { - return this.form.get("books") as FormArray; - } - - constructor( - private fb: FormBuilder, - private bookService: BookService, - private toasterService: ToasterService - ) {} - - ngOnInit(): void { - this.form = this.fb.group({ - author: this.fb.group({ - name: [null, Validators.required], - birthDate: [null, Validators.required], - }), - books: this.fb.array([this.getBookForm()]), - }); - } - - getBookForm() { - return this.fb.group({ - name: [null, Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } - - addBook() { - this.bookFormArray.push(this.getBookForm()); - } - - deleteBook(i: number) { - this.bookFormArray.removeAt(i); - } - - save() { - if (this.form.invalid) { - return; - } - const authorWithBook: CreateAuthorWithBookDto = { - ...this.form.value.author, - books: this.form.value.books, - }; - this.bookService.createAuthorWithBooks(authorWithBook).subscribe((res) => { - this.toasterService.success("::AuthorWithBook:Success", "", { - messageLocalizationParams: [res.name], - }); - }); - } -} -``` - -- We created form until component initialization in the `ngOnInit` method -- In the `getBookForm` method, returned -- In the `addBook` method, we pushed a form group instance created in the `getBookForm` method to the book form array. We will execute this method when clicked on the **Add Book** button -- We deleted a form group instance by index from the book form array in the `deleteBook` method -- In the `save` method, we send an HTTP request for creating author and books if creation will be successful toaster message will be display - -Replace the `author-with-books.component.html` content with following code: - -```html - - -
    - - {{'::Name' | abpLocalization}} * - - - - - {{'::BirthDate' | abpLocalization}} * - - - - -
    -
    - - -
    - - {{'::Name' | abpLocalization}} * - - - - - {{'::Price' | abpLocalization}} * - - - - - {{'::Type' | abpLocalization}} * - - {{'::SelectBookType' | abpLocalization}} - {{ '::Enum:BookType:' + type.value | abpLocalization - }} - - - - - {{'::PublishDate' | abpLocalization}} * - - - - -
    - -
    -
    - - -
    -
    - - person - - - book - -
    -``` - -- We created the same author form where we created in `AuthorDialogComponent` and we gave the author form group to mat-step's stepConrol input -- We created the same book form which we created at `BookDialogComponent` except the author selection - -Replace `author-with-books.component.scss` content with following code: - -```scss -.book-form { - margin-top: 20px; -} -.button-container { - display: flex; - width: 100%; - justify-content: space-between; - margin-top: 25px; -} -``` - -Finally add Create Author With Books button near the Create Author button in `author.component.html`: - -```html -
    - - -
    -``` - -Final UI looks as shown below: - -![Author With Books](./author-with-books.gif) - -## The Source Code - -You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreAngularMaterial). - -## Conclusion - -We implemented Angular Material Components to our angular application which was created with ABP Framework. There is no blocker case of using angular libraries with the ABP framework. diff --git a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-crud.gif b/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-crud.gif deleted file mode 100644 index be7e2246ed..0000000000 Binary files a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-crud.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-with-books.gif b/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-with-books.gif deleted file mode 100644 index 698507f780..0000000000 Binary files a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/author-with-books.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-create.gif b/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-create.gif deleted file mode 100644 index 613ccf4a45..0000000000 Binary files a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-create.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-delete.gif b/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-delete.gif deleted file mode 100644 index a10d8a3536..0000000000 Binary files a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-delete.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-edit.gif b/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-edit.gif deleted file mode 100644 index 387274aa38..0000000000 Binary files a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-edit.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-list.gif b/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-list.gif deleted file mode 100644 index 1a88413e40..0000000000 Binary files a/docs/en/Community-Articles/2020-12-11-Using-Angular-Material-Components-With-ABP-Framework/book-list.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md deleted file mode 100644 index f30f31d541..0000000000 --- a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md +++ /dev/null @@ -1,103 +0,0 @@ -## Using MatBlazor UI Components With the ABP Framework - -Hi, in this step by step article, I will show you how to integrate [MatBlazor](https://www.matblazor.com/), a blazor UI components into ABP Framework-based applications. - -![example-result](example-result.png) - -*(A screenshot from the example application developed in this article)* - -## Create the Project - -> First thing is to create the project. ABP Framework offers startup templates to get into business faster. - -In this article, I will create a new startup template with EF Core as a database provider and Blazor for UI framework. But if you already have a project with Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. - -> If you already have a project with the Blazor UI, you can skip this section. - -* Before starting the development, we will create a new solution named `MatBlazorSample` (or whatever you want). We will create a new startup template with EF Core as a database provider and Blazor for UI framework by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -````bash -abp new MatBlazorSample -u blazor -```` - -This will create new project inside of `aspnet-core`, so: - -````bash -cd aspnet-core -```` - -and - -````bash -dotnet restore -```` - -* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE) and run the `MatBlazorSample.DbMigrator` to create the database and seed initial data (which creates the admin user, admin role, permissions etc.) - -![initial-project](initial-project.png) - -* After database and initial data created, -* Run the `MatBlazorSample.HttpApi.Host` to see our server side working and -* Run the `MatBlazorSample.Blazor` to see our UI working properly. - -> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_ - -## Install MatBlazor - -You can follow [this documentation](https://www.matblazor.com/) to install MatBlazor packages into your computer. - -### Adding MatBlazor NuGet Packages - -```bash -Install-Package MatBlazor -``` - -### Register MatBlazor Resources - -1. Add the following line to the HEAD section of the `wwwroot/index.html` file within the `MatBlazorSample.Blazor` project: - - ```Razor - - - - - - ``` - -2. In the `MatBlazorSampleBlazorModule` class, call the `AddMatBlazor()` method from your project's `ConfigureServices()` method: - - ```csharp - public override void ConfigureServices(ServiceConfigurationContext context) - { - var environment = context.Services.GetSingletonInstance(); - var builder = context.Services.GetSingletonInstance(); - // ... - builder.Services.AddMatBlazor(); - } - ``` - -3. Register the **MatBlazorSample.Blazor** namespace in the `_Imports.razor` file: - - ```Razor - @using MatBlazor - ``` - -## The Sample Application - -We have created a sample application with [Table](https://www.matblazor.com/Table) example. - -### The Source Code - -You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/MatBlazorSample). - -The related files for this example are marked in the following screenshots. - -![table-app-contract](table-app-contract.png) - -![table-application](table-application.png) - -![table-web](table-web.png) - -## Conclusion - -In this article, I've explained how to use [MatBlazor](https://www.matblazor.com/) components in your application. ABP Framework is designed so that it can work with any UI library/framework. \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/example-result.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/example-result.png deleted file mode 100644 index 260bd4e795..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/example-result.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/initial-project.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/initial-project.png deleted file mode 100644 index 9d4a28dde5..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/initial-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-app-contract.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-app-contract.png deleted file mode 100644 index 5fdd50a178..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-app-contract.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-application.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-application.png deleted file mode 100644 index 315cf7302e..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-application.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-web.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-web.png deleted file mode 100644 index 2e481b070e..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-web.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/POST.md b/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/POST.md deleted file mode 100644 index 7a854291d5..0000000000 --- a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/POST.md +++ /dev/null @@ -1,343 +0,0 @@ -# How to Use PrimeNG Components with the ABP Angular UI - -## Introduction - -In this article, we will use components of the [PrimeNG](https://www.primefaces.org/primeng/) that is a popular UI component library for Angular with the ABP Framework Angular UI that will be generated via [ABP CLI](https://docs.abp.io/en/abp/latest/CLI). - -We will create an organization units page and use PrimeNG's [OrganizationChart](https://primefaces.org/primeng/showcase/#/organizationchart) and [Table](https://primefaces.org/primeng/showcase/#/table) components on the page. - -![Introduction](intro.gif) - -The UI shown above contains many PrimeNG components. You can reach the source code of this rich UI. Take a look at the source code section below. - -> This article does not cover any backend code. I used mock data to provide data source to the components. - -## Pre-Requirements - -The following tools should be installed on your development machine: - -* [.NET Core 5.0+](https://www.microsoft.com/net/download/dotnet-core/) -* [Node v12 or v14](https://nodejs.org/) -* [VS Code](https://code.visualstudio.com/) or another IDE - -## Source Code - -I have prepared a sample project that contains more PrimeNG components than described in this article. You can download the source code [on GitHub](https://github.com/abpframework/abp-samples/tree/master/PrimengSample). - -## Creating a New Solution - -In this step, we will create a new solution that contains Angular UI and backend startup templates. If you have a startup template with Angular UI, you can skip this step. - -Run the following command to install the ABP CLI: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -...or update: - -```bash -dotnet tool update -g Volo.Abp.Cli -``` - -Create a new solution named `AbpPrimengSample` by running the following command: - -```bash -abp new AbpPrimengSample -u angular -csf -``` - -See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all available options. - -You can also use the Direct Download tab on the [Get Started](https://abp.io/get-started) page. - -## Running the Solution - -You can run the solution as described in [here](https://docs.abp.io/en/abp/latest/Getting-Started-Running-Solution?UI=NG&DB=EF&Tiered=No). - -## PrimeNG Setup - -Open the `angular` folder and run the following command to install packages: - -```bash -npm install -``` - -Next, we need to install `primeng` and required packages (`primeicons` and `@angular/cdk`) for the library. Run the command below to install these packages: - -```bash -npm install primeng primeicons @angular/cdk --save -``` - -The packages we have installed; - - - `primeng` is the main package that is a component library. - - `primeicons` is an icon font library. Many PrimeNG components use this font internally. - - `@angular/cdk` is a component dev kit created by the Angular team. Some PrimeNG modules depend on it. - -As the last step of the setup, we should add the required style files for the library to `angular.json`: - -```js -//angular.json - -"projects": { - "AbpPrimengSample": { - //... - "styles": { - "node_modules/primeicons/primeicons.css", - "node_modules/primeng/resources/themes/saga-blue/theme.css", - "node_modules/primeng/resources/primeng.min.css", - //...other styles - } - } -} -``` - -We have added the `primeng.min.css`, Saga Blue theme's `theme.css`, and `primeicons.css` files to the project. You can choose another theme instead of the Sage Blue. See available themes on the [Get Started](https://www.primefaces.org/primeng/showcase/#/setup) document of the PrimeNG. - - -> You have to restart the running `ng serve` process to see the effect of the changes you made in the `angular.json`. - - -## Creating the Organization Units Page - -Run the following command to create a new module named `OrganizationUnits`: - -```bash -npm run ng -- generate module organization-units --route organization-units --module app.module -``` - -Then open the `src/route.provider.ts` and add a new route as an array element to add a navigation link labeled "Organization Units" to the menu: - -```js -//route.provider.ts - -import { eThemeSharedRouteNames } from '@abp/ng.theme.shared'; -//... - -routesService.add([ - //... - { - path: '/organization-units', - name: 'Organization Units', - parentName: eThemeSharedRouteNames.Administration, - iconClass: 'fas fa-sitemap', - layout: eLayoutType.application, - }, -]); -``` - -We have created a lazy-loadable module and defined a menu navigation link. We can navigate to the page as shown below: - -![organization units menu navigation item](organization-units-menu-item.jpg) - -## Using the PrimeNG Components - -### Implementing the Organization Chart Component - -When you would like to use any component from PrimeNG, you have to import the component's module to your module. Since we will use the `OrganizationChart` on the organization units page, we need to import `OrganizationChartModule` into `OrganizationUnitsModule`. - -Open the `src/organization-units/organization-units.module.ts` and add the `OrganizationChartModule` to the imports array as shown below: - -```js -import { OrganizationChartModule } from 'primeng/organizationchart'; -//... - -@NgModule({ - //... - imports: [ - //... - OrganizationChartModule - ], -}) -export class OrganizationUnitsModule {} -``` - -> Since NGCC need to work in some cases, restarting the `ng serve` process would be good when you import any modules from `primeng` package to your module. - -Let's define a mock data source for the `OrganizationChartComponent` and add the component to the page. - -Open the `src/organization-units/organization-units.component.ts` and add two variables as shown below: - -```js -//... -import { TreeNode } from 'primeng/api'; - -@Component(/* component metadata*/) -export class OrganizationUnitsComponent implements OnInit { - //... - - organizationUnits: TreeNode[] = [ - { - label: 'Management', - expanded: true, - children: [ - { - label: 'Selling', - expanded: true, - children: [ - { - label: 'Customer Relations', - }, - { - label: 'Marketing', - }, - ], - }, - { - label: 'Supporting', - expanded: true, - children: [ - { - label: 'Buying', - }, - { - label: 'Human Resources', - }, - ], - }, - ], - }, - ]; - - selectedUnit: TreeNode; -``` - -- First variable is `organizationUnits`. It provides mock data source to `OrganizationChartComponent`. -- Second variable is `selectedUnit`. It keeps chosen unit on the chart. - -Then, open the `src/organization-units/organization-units.component.html` and replace the file content with the following: - -```html -
    -
    -
    Organization Units
    -
    -
    - -
    -
    -``` - -We have implemented the `OrganizationChart`. The final UI looks like below: - -![organization chart](organization-chart.jpg) - -## Implementing the Table Component - -In order to use the `TableComponent`, we have to import the `TableModule` to the `OrganizationUnitsModule`. - -Open the `organization-units.module.ts` and add `TableModule` to the imports array as shown below: - -```js -import { TableModule } from 'primeng/table'; -//... - -@NgModule({ - //... - imports: [ - //... - TableModule - ], -}) -export class OrganizationUnitsModule {} -``` - -Open the `organization-units.component.ts` and add a variable named `members` with initial value and add a getter named `tableData` as shown below: - -```js -//... -export class OrganizationUnitsComponent implements OnInit { - //... - - members = [ - { - fullName: 'John Doe', - username: 'John.Doe', - phone: '+1-202-555-0125', - email: 'john.doe@example.com', - parent: 'Customer Relations', - }, - { - fullName: 'Darrion Walter', - username: 'Darrion.Walter', - phone: '+1-262-155-0355', - email: 'Darrion_Walter@example.com', - parent: 'Marketing', - }, - { - fullName: 'Rosa Labadie', - username: 'Rosa.Labadie', - phone: '+1-262-723-2255', - email: 'Rosa.Labadie@example.com', - parent: 'Marketing', - }, - { - fullName: 'Adelle Hills', - username: 'Adelle.Hills', - phone: '+1-491-112-9011', - email: 'Adelle.Hills@example.com', - parent: 'Buying', - }, - { - fullName: 'Brian Hane', - username: 'Brian.Hane', - phone: '+1-772-509-1823', - email: 'Brian.Hane@example.com', - parent: 'Human Resources', - }, - ]; - - get tableData() { - return this.members.filter(user => user.parent === this.selectedUnit.label); - } -``` - -What we have done above? - -- We defined a variable named `members` to provide mock data to the table. -- We have defined a getter named `tableData` to provide filtered data source to the table using `members` variable. - -We are now ready to add the table to the HTML template. - -Open the `organization-units.component.html`, find the `p-organizationChart` tag and place the following code to the bottom of this tag: - -```html -
    -
    Members of {{ selectedUnit.label }}
    - - - - - Name - Username - Email - Phone - - - - - {{ member.fullName }} - {{ member.username }} - {{ member.email }} - {{ member.phone }} - - - -
    -``` - -We have added a new `div` that contains the `TableComponent`. The table appears when an organization unit is selected. -The table contains 4 columns which are name, username, email, and phone for displaying the members' information. - -After adding the table, the final UI looks like this: - -![PrimeNG TableComponent](table.gif) - - -## Conclusion - -We have implemented the PrimeNG component library on the ABP Angular UI project and used two components on a page in a short time. You can use any PrimeNG components by following the documentation. The ABP Angular UI will not block you in any case. diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/intro.gif b/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/intro.gif deleted file mode 100644 index 5f96a65422..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/intro.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/organization-chart.jpg b/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/organization-chart.jpg deleted file mode 100644 index 9560651726..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/organization-chart.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/organization-units-menu-item.jpg b/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/organization-units-menu-item.jpg deleted file mode 100644 index 2cea349ebf..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/organization-units-menu-item.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/table.gif b/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/table.gif deleted file mode 100644 index d27dcb26bd..0000000000 Binary files a/docs/en/Community-Articles/2021-01-20-How-to-Use-PrimeNG-Components-with-the-ABP-Angular-UI/table.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/POST.md b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/POST.md deleted file mode 100644 index 12154dc538..0000000000 --- a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/POST.md +++ /dev/null @@ -1,117 +0,0 @@ -# Send Real-time Notifications via SignalR in ABP Project - -SignalR is an open source library that adds real-time operation functionality to applications. Real-time web functionality enables server-side code to instantly send content to clients without refreshing the page. I'll show you how to add SignalR and use it to send notifications from backend. I'll implement this functionality in MVC template of ABP Framework. - -![signalr-architecture](signalr-architecture.png) - -## Implement Backend - -### Create Notification Hub - -Create a new folder named `SignalR` in your root directory of your Web project. - -![SignalR Folder](signalr-folder.jpg) - -Then add the following classes to the folder: - -1. [INotificationClient.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-inotificationclient-cs) -2. [UiNotificationClient.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-uinotificationclient-cs) -3. [UiNotificationHub.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-uinotificationhub-cs) - -### Configure Module - -These 3 steps will be done in your web module class. - -#### 1- Add SignalR - -Open `YourProjectWebModule.cs` class and add the following line to the `PreConfigureServices` method: - -```csharp - context.Services.AddSignalR(); -``` - - - -![PreConfigureServices](preconfigureservices.jpg) - - - -#### 2- Add Client Scripts - -2- In the `ConfigureServices` method of your web module add the following code to add the `signalr.js` and `notification-hub.js`. We'll add these packages in the next steps. If you are not using Lepton Theme you can add the files to `StandardBundles.Scripts.Global`. - -```csharp - Configure(options => - { - options - .ScriptBundles - .Get(LeptonThemeBundles.Scripts.Global) //or -> Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling.StandardBundles.Scripts.Global - .AddFiles("/libs/signalr/signalr.js") - .AddFiles("/Pages/notification-hub.js"); - }); - ``` - -![Script Bundles](add-script-bundles.jpg) - -#### 3- Add Hub Endpoint - -Add the following code to add the notification hub endpoint in `OnApplicationInitialization` method: - -```csharp -app.UseEndpoints(endpoints => -{ - endpoints.MapHub("/notification-hub"); -}); -``` - -![Add endpoint](add-endpoint.jpg) - -### Implement Frontend - -We'll write the client-side code to be able to handle the SignalR response. - -#### 1- Add Notification Hub - -Add the following JavaScript class into your `Pages` folder in your Web project. We already added this script to our global scripts. - -[notification-hub.js](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-notification-hub-js) - -![notification-hub.js](notification-hub.jpg) - -#### 2- Add SignalR NPM package - -Add [Microsoft.SignalR](https://www.npmjs.com/package/@microsoft/signalr) JavaScript package to the `package.json` which is located in your root folder of the Web project. After you add it, run `yarn` command in your Web directory to be able to install this package. - -You can install the latest version (3.1.13 will be old) -``` - "@microsoft/signalr": "^3.1.13" -``` - -![Add SignalR package](signalr-package.jpg) - -#### 3- Add resource Mapping - -We added SignalR to the `package.json` but it comes into your `node_modules` folder. We need to copy the related files to `wwwroot/libs` folder. To do this copy the content of the following file to your `abp.resourcemappings.js` file. It's in your root directory of Web folder. After you do this, go to your web directory and run `abp install-libs` command. By doing this, it'll copy the related files into your `wwwroot/libs` folder. - -[`abp.resourcemappings.js`](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-abp-resourcemapping-js) - -![Resource mappings](resource-mappings.jpg) - -#### 4- Usage - -We have completed the implementation part. Let's check if it's running... -We will show the current time which comes from server. -To do this replace the `Index.cshtml` and `Index.cshtml.cs` with the followings: - -- [Index.cshtml](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-index-cshtml) - -- [Index.cshtml.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-index-cshtml-cs) - - -#### 5- See it in action - -Run your web project and in the Index page you'll Server Time will be updated every second. The time information comes from backend via SignalR. -This is a very basic usage of SignalR notification system. -You can implement your own SignalR notification system based on your requirements. - -![Result](result.jpg) diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-endpoint.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-endpoint.jpg deleted file mode 100644 index b53f582eb3..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-endpoint.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-script-bundles.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-script-bundles.jpg deleted file mode 100644 index 3741312935..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-script-bundles.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/article-signalr-banner.png b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/article-signalr-banner.png deleted file mode 100644 index 450d4e2339..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/article-signalr-banner.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/notification-hub.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/notification-hub.jpg deleted file mode 100644 index aad559b776..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/notification-hub.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/preconfigureservices.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/preconfigureservices.jpg deleted file mode 100644 index 70a4841f48..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/preconfigureservices.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/resource-mappings.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/resource-mappings.jpg deleted file mode 100644 index 452dab89fd..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/resource-mappings.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/result.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/result.jpg deleted file mode 100644 index e50b8ebe62..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/result.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-architecture.png b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-architecture.png deleted file mode 100644 index 5b1c7c6c52..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-architecture.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-folder.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-folder.jpg deleted file mode 100644 index 4a4a2ff4a1..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-folder.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-package.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-package.jpg deleted file mode 100644 index aa05fda81c..0000000000 Binary files a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-package.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/POST.md b/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/POST.md deleted file mode 100644 index 2e8fa867fb..0000000000 --- a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/POST.md +++ /dev/null @@ -1,114 +0,0 @@ -## Integrating SyncFusion UI with ABP Framework Blazor UI - -Hi, in this step by step article, I will show you how to integrate [Syncfusion](https://www.syncfusion.com/blazor-components), a blazor UI components into ABP Framework-based applications. - -![example-result](example-result.png) - -*(A screenshot from the example application developed in this article)* - -## Create the Project - -> First thing is to create the project. ABP Framework offers startup templates to get into business faster. - -In this article, I will create a new startup template with EF Core as a database provider and Blazor for UI framework. But if you already have a project with Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. - -> If you already have a project with the Blazor UI, you can skip this section. - -* Before starting the development, we will create a new solution named `SyncfusionSample` (or whatever you want). We will create a new startup template with EF Core as a database provider and Blazor for UI framework by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -````bash -md SyncfusionSample -cd SyncfusionSample -```` - -After we have navigated inside of `SyncfusionSample` directory, we can create a new project. Note that I am creating Blazor Server (`blazor-server`) project. If you want to create a regular Blazor WebAssembly project just use `blazor` keyword instead. - -````bash -abp new SyncfusionSample -u blazor-server -```` - -and - -````bash -dotnet restore -```` - -* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE) and run the `SyncfusionSample.DbMigrator` to create the database and seed initial data (which creates the admin user, admin role, permissions etc.) - -![initial-project](initial-project.png) - -* After database and initial data created, -* Run the `SyncfusionSample.Blazor` to see our UI working properly. - -> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_ - -## Install Syncfusion - -You can follow [this documentation](https://blazor.syncfusion.com/documentation/getting-started/blazor-server-side-visual-studio-2019/) to install Syncfusion packages into your computer. - -### Adding Syncfusion NuGet Packages - -Select `SyncfusionSample.Blazor` as the **default project** and then install NuGet packages. - -```bash -Install-Package Syncfusion.Blazor.Grid -``` - -### Register Syncfusion Resources - -1. Add the following line to the HEAD section of the `_Host.cshtml` file within the `SyncfusionSample.Blazor` project: - - ```Razor - - - - - ``` - -2. In the `SyncfusionSampleBlazorModule` class, call the `AddSyncfusionBlazor()` method from your project's `ConfigureServices()` method: - - ```csharp - public override void ConfigureServices(ServiceConfigurationContext context) - { - var hostingEnvironment = context.Services.GetHostingEnvironment(); - var configuration = context.Services.GetConfiguration(); - - // ... - - context.Services.AddSyncfusionBlazor(); - Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("YOUR LICENSE KEY"); - } - ``` - - > To get the `LICENSE KEY` you can login into your Syncfusion account and request the key. Trial keys are also available. - -3. Register the **SyncfusionSample.Blazor** namespace(s) in the `_Imports.razor` file: - - ```Razor - @using Syncfusion.Blazor - @using Syncfusion.Blazor.Buttons - @using Syncfusion.Blazor.Inputs - @using Syncfusion.Blazor.Calendars - @using Syncfusion.Blazor.Popups - @using Syncfusion.Blazor.Grids - ``` - -## The Sample Application - -We have created a sample application with [SfGrid](https://blazor.syncfusion.com/documentation/datagrid/getting-started/) example. - -### The Source Code - -You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/SyncfusionSample). - -The related files for this example are marked in the following screenshots. - -![table-app-contract](table-app-contract.png) - -![table-application](table-application.png) - -![table-web](table-web.png) - -## Conclusion - -In this article, I've explained how to use [Syncfusion](https://www.syncfusion.com/blazor-components) components in your application. ABP Framework is designed so that it can work with any UI library/framework. diff --git a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/example-result.png b/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/example-result.png deleted file mode 100644 index 8230495c34..0000000000 Binary files a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/example-result.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/initial-project.png b/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/initial-project.png deleted file mode 100644 index 7e5f921911..0000000000 Binary files a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/initial-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-app-contract.png b/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-app-contract.png deleted file mode 100644 index 98cd539cbc..0000000000 Binary files a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-app-contract.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-application.png b/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-application.png deleted file mode 100644 index e50c929166..0000000000 Binary files a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-application.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-web.png b/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-web.png deleted file mode 100644 index 475766e1b4..0000000000 Binary files a/docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/table-web.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/POST.md b/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/POST.md deleted file mode 100644 index d8fa243c3b..0000000000 --- a/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/POST.md +++ /dev/null @@ -1,456 +0,0 @@ -# Unifying DbContexts for EF Core / Removing the EF Core Migrations Project - -This article shows how to remove the `EntityFrameworkCore.DbMigrations` project from your solution to have a single `DbContext` for your database mappings and code first migrations. - -## Source Code - -You can find source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/UnifiedEfCoreMigrations). - -## Motivation - -If you create a new solution with **Entity Framework Core** as the database provider, you see two projects related to EF Core: - -![default-solution](default-solution.png) - -* `EntityFrameworkCore` project contains the actual `DbContext` of your application. It includes all the database mappings and your repository implementations. -* `EntityFrameworkCore.DbMigrations` project, on the other hand, contains another `DbContext` class that is only used to create and apply the database migrations. It contains the database mappings for all the modules you are using, so have a single, unified database schema. - -There were two main reasons we'd created such a separation; - -1. Your actual `DbContext` remains simple and focused. It only contains your own entities and doesn't contain anything related to the modules that are used by the application. -2. You can create your own classes that map to the tables of depending modules. For example, the `AppUser` entity (that is included in the downloaded solution) is mapped to `AbpUsers` table in the database, which is actually mapped to the `IdentityUser` entity of the [Identity Module](https://docs.abp.io/en/abp/latest/Modules/Identity). That means they share the same database table. `AppUser` includes less properties compared to `IdentityServer`. You only add the properties you need, not more. This also allows you to add new standard (type-safe) properties to the `AppUser` for your custom requirements as long as you carefully manage the database mappings. - -We've [documented the structure](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Migrations) in details. However, it has always been a problem for the developers since that structure makes your database mappings complicated when you re-use tables of the depended modules. Many developers are misunderstanding or making mistakes while mapping such classes, especially when they try to use these entities in relations to other entities. - -**So, [we've decided](https://github.com/abpframework/abp/issues/8776) to cancel that separation and remove the `EntityFrameworkCore.DbMigrations` project in the version 4.4. New startup solutions will come with a single `EntityFrameworkCore` project and single `DbContext` class.** - -If you want to make it in your solution with today, follow the steps in this article. - -## Warnings - -> There is one **drawback** with the new design (everything in software development is a trade-off). We need to remove the `AppUser` entity, because EF Core can't map two classes to single table without an inheritance relation. I will cover this later in this article and provide suggestions to deal with it. - -> If you are using **ABP Commercial**, ABP Suite code generation won't work correctly before the version 4.4. Please upgrade to v4.4 if you are using a previous version. - -## The Steps - -Our goal to enable database migrations in the `EntityFrameworkCore` project, remove the `EntityFrameworkCore.DbMigrations` project and revisit the code depending on that package. - -> I've created a new solution with v4.3, then made all the changes in a pull request, so you can see all the changes line by line. While this article will cover all, you may want to [check the changes done in this PR](https://github.com/abpframework/abp-samples/pull/88) if you have problems with the implementation. - -### 1) Add Microsoft.EntityFrameworkCore.Tools package to the EntityFrameworkCore project - -Add the following code into the `EntityFrameworkCore.csproj` file: - -````xml - - - runtime; build; native; contentfiles; analyzers - compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native - - -```` - -### 2) Create design time DbContext factory - -Create a class implementing `IDesignTimeDbContextFactory` inside the `EntityFrameworkCore` project: - -````csharp -using System.IO; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Configuration; - -namespace UnifiedContextsDemo.EntityFrameworkCore -{ - public class UnifiedContextsDemoDbContextFactory : IDesignTimeDbContextFactory - { - public UnifiedContextsDemoDbContext CreateDbContext(string[] args) - { - UnifiedContextsDemoEfCoreEntityExtensionMappings.Configure(); - - var configuration = BuildConfiguration(); - - var builder = new DbContextOptionsBuilder() - .UseSqlServer(configuration.GetConnectionString("Default")); - - return new UnifiedContextsDemoDbContext(builder.Options); - } - - private static IConfigurationRoot BuildConfiguration() - { - var builder = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../UnifiedContextsDemo.DbMigrator/")) - .AddJsonFile("appsettings.json", optional: false); - - return builder.Build(); - } - } -} -```` - -I basically copied from the `EntityFrameworkCore.DbMigrations` project, renamed and uses the actual `DbContext` of the application. - -### 3) Create DB schema migrator - -Copy `EntityFrameworkCore...DbSchemaMigrator` (`...` standard for your project name) class to the `EntityFrameworkCore` project and change the code in the `MigrateAsync` method to use the actual `DbContext` of the application. In my case, the final class is shown below: - -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using UnifiedContextsDemo.Data; -using Volo.Abp.DependencyInjection; - -namespace UnifiedContextsDemo.EntityFrameworkCore -{ - public class EntityFrameworkCoreUnifiedContextsDemoDbSchemaMigrator - : IUnifiedContextsDemoDbSchemaMigrator, ITransientDependency - { - private readonly IServiceProvider _serviceProvider; - - public EntityFrameworkCoreUnifiedContextsDemoDbSchemaMigrator( - IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public async Task MigrateAsync() - { - /* We intentionally resolving the UnifiedContextsDemoMigrationsDbContext - * from IServiceProvider (instead of directly injecting it) - * to properly get the connection string of the current tenant in the - * current scope. - */ - - await _serviceProvider - .GetRequiredService() - .Database - .MigrateAsync(); - } - } -} -```` - -### 4) Move module configurations - -The migrations `DbContext` typically contains code lines like `builder.ConfigureXXX()` for each module you are using. We can move these lines to our actual `DbContext` in the `EntityFrameworkCore` project. Also, remove the database mappings for the `AppUser` (we will remove this entity). Optionally, you may move the database mappings code for your own entities from `...DbContextModelCreatingExtensions` class in the `OnModelCreating` method of the actual `DbContext`, and remove the static extension class. - -For the example solution, the final `DbContext` class is shown below: - -````csharp -using Microsoft.EntityFrameworkCore; -using UnifiedContextsDemo.Users; -using Volo.Abp.AuditLogging.EntityFrameworkCore; -using Volo.Abp.BackgroundJobs.EntityFrameworkCore; -using Volo.Abp.Data; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.FeatureManagement.EntityFrameworkCore; -using Volo.Abp.Identity.EntityFrameworkCore; -using Volo.Abp.IdentityServer.EntityFrameworkCore; -using Volo.Abp.PermissionManagement.EntityFrameworkCore; -using Volo.Abp.SettingManagement.EntityFrameworkCore; -using Volo.Abp.TenantManagement.EntityFrameworkCore; - -namespace UnifiedContextsDemo.EntityFrameworkCore -{ - [ConnectionStringName("Default")] - public class UnifiedContextsDemoDbContext - : AbpDbContext - { - public DbSet Users { get; set; } - - /* Add DbSet properties for your Aggregate Roots / Entities here. - * Also map them inside UnifiedContextsDemoDbContextModelCreatingExtensions.ConfigureUnifiedContextsDemo - */ - - public UnifiedContextsDemoDbContext( - DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - builder.ConfigurePermissionManagement(); - builder.ConfigureSettingManagement(); - builder.ConfigureBackgroundJobs(); - builder.ConfigureAuditLogging(); - builder.ConfigureIdentity(); - builder.ConfigureIdentityServer(); - builder.ConfigureFeatureManagement(); - builder.ConfigureTenantManagement(); - - /* Configure your own tables/entities inside here */ - - //builder.Entity(b => - //{ - // b.ToTable(UnifiedContextsDemoConsts.DbTablePrefix + "YourEntities", UnifiedContextsDemoConsts.DbSchema); - // b.ConfigureByConvention(); //auto configure for the base class props - // //... - //}); - } - } -} -```` - -### 5) Remove `EntityFrameworkCore.DbMigrations` project from the solution - -Remove the `EntityFrameworkCore.DbMigrations` project from the solution and replace references given to that project by the `EntityFrameworkCore` project reference. - -Also, change usages of `...EntityFrameworkCoreDbMigrationsModule` to `...EntityFrameworkCoreModule` (`...` stands for your project name). - -In this example, I had to change references and usages in the `DbMigrator`, `Web` and `EntityFrameworkCore.Tests` projects. - -### 6) Remove AppUser Entity - -We need to remove the `AppUser` entity, because EF Core can't map two classes to single table without an inheritance relation. So, remove this class and all the usages. You can replace the usages with `IdentityUser` if you need to query users in your application code. See The AppUser Entity & Custom Properties section for more info. - -### 7) Create or move the migrations - -We've removed the `EntityFrameworkCore.DbMigrations` project. What about the migrations created and applied into the database until now? If you want to keep your migrations history, copy all the migrations from the `EntityFrameworkCore.DbMigrations` project to the `EntityFrameworkCore` project and manually change the `DbContext` type in the designer classes. - -If you want to clear the migrations history, but continue with the migrations already applied to the database, create a new database migration in the `EntityFrameworkCore` project, executing the following command in a command-line terminal in the directory of that project: - -````bash -dotnet ef migrations add InitialUnified -```` - -You can specify a different migration name, surely. This will create a migration class that contains all the database tables you already have in the database. Keep calm and delete all the content in the `Up` and `Down` methods. Then you can apply the migration to the database: - -````bash -dotnet ef database update -```` - -Your database won't have any change, because the migration is just empty and does nothing. From now, you can create new migrations as you change your entities, just like you normally do. - -All the changes are done. The next section explains how to add custom properties to entities of depending modules with this design. - -## The AppUser Entity & Custom Properties - -The database mapping logic, solution structure and migrations become much simpler and easier to manage with that new setup. - -As a drawback, we had to remove the `AppUser` entity, which was sharing the `AbpUsers` table with the `IdentityUser` entity of the Identity Module. Fortunately, ABP provides a flexible system to [extend existing entities](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) in case of you need to define some custom properties. In this section, I will show how to add a custom property to the `IdentityUser` entity and use it in your application code and database queries. - -I've done all the changes in this part as a single PR, so you may want to [check the changes done in this PR](https://github.com/abpframework/abp-samples/pull/89) if you have problems with the implementation. - -### Defining a Custom Property - -The application startup template provides a point to configure the custom properties for existing entities, which is located under `Domain.Shared` project, in the `...ModuleExtensionConfigurator.cs` (`...` standard for your project name) class. Open that class and add the following code into the `ConfigureExtraProperties` method: - -````csharp -ObjectExtensionManager.Instance.Modules() - .ConfigureIdentity(identity => - { - identity.ConfigureUser(user => - { - user.AddOrUpdateProperty( //property type: string - "SocialSecurityNumber", //property name - property => - { - //validation rules - property.Attributes.Add(new RequiredAttribute()); - property.Attributes.Add(new StringLengthAttribute(64)); - } - ); - }); - }); -```` - -After that setup, just run the application to see the new property on the Users table: - -![new-prop-on-table](new-prop-on-table.png) - -The new `SocialSecurityNumber` property will also be available on the create and edit modals with the validation rules. - -> See the [Module Entity Extensions](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) document to understand and control the new custom property with all the details. - -### Mapping to the Database Table - -By default, ABP saves all custom properties inside the `ExtraProperties` field as a single JSON object. If you prefer to create a table field for a custom property, you can configure it in the `...EfCoreEntityExtensionMappings.cs` (`...` standard for your project name) class in the `EntityFrameworkCore` project. You can write the following code inside this class (in the `OneTimeRunner.Run`): - -````csharp -ObjectExtensionManager.Instance - .MapEfCoreProperty( - "SocialSecurityNumber", - (entityBuilder, propertyBuilder) => - { - propertyBuilder.HasMaxLength(64).IsRequired().HasDefaultValue(""); - } - ); -```` - -After that, you can just run the following command in a command-line terminal to add a new database migration (in the directory of the `EntityFrameworkCore` project): - -````bash -dotnet ef migrations add Added_SocialSecurityNumber_To_IdentityUser -```` - -This will add a new migration class to your project. You can then run the following command (or run the `.DbMigrator` application) to apply changes to the database: - -````bash -dotnet ef database update -```` - -This will add a `SocialSecurityNumber` field to the `AbpUsers` table in the database. - -### Using Custom Properties in the Application Code - -Now, we can use `GetProperty` and `SetProperty` methods on the `IdentityUser` entity to work with the new property. The following code demonstrates to get/set the custom property: - -````csharp -public class MyUserService : ITransientDependency -{ - private readonly IRepository _userRepository; - - public MyUserService(IRepository userRepository) - { - _userRepository = userRepository; - } - - public async Task SetSocialSecurityNumberDemoAsync(string userName, string number) - { - var user = await _userRepository.GetAsync(u => u.UserName == userName); - user.SetProperty("SocialSecurityNumber", number); - await _userRepository.UpdateAsync(user); - } - - public async Task GetSocialSecurityNumberDemoAsync(string userName) - { - var user = await _userRepository.GetAsync(u => u.UserName == userName); - return user.GetProperty("SocialSecurityNumber"); - } -} -```` - -Tip: Using `SetProperty` and `GetProperty` with a string property name everywhere could be tedious and error-prone. I suggest you to create the following extension methods: - -````csharp -public static class MyUserExtensions -{ - public const string SocialSecurityNumber = "SocialSecurityNumber"; - - public static void SetSocialSecurityNumber(this IdentityUser user, string number) - { - user.SetProperty(SocialSecurityNumber, number); - } - - public static string GetSocialSecurityNumber(this IdentityUser user) - { - return user.GetProperty(SocialSecurityNumber); - } -} -```` - -Then we can change the previous demo method as shown below: - -````csharp -public async Task SetSocialSecurityNumberDemoAsync(string userName, string number) -{ - var user = await _userRepository.GetAsync(u => u.UserName == userName); - user.SetSocialSecurityNumber(number); //Using the new extension property - await _userRepository.UpdateAsync(user); -} - -public async Task GetSocialSecurityNumberDemoAsync(string userName) -{ - var user = await _userRepository.GetAsync(u => u.UserName == userName); - return user.GetSocialSecurityNumber(); //Using the new extension property -} -```` - -### Querying Based on a Custom Property - -You may want to query users based on `SocialSecurityNumber`. We will use Entity Framework's API to accomplish that. You have two options to use EF Core API in your application code: - -1. Reference to the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) NuGet package from your project (domain or application layer, depending on where you want to use the EF Core API). -2. Create a [repository](https://docs.abp.io/en/abp/latest/Repositories) interface in the domain layer and implement it in your `EntityFrameworkCore` project. - -I will prefer the second approach, so I am defining a new repository interface in the `Domain` project: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Identity; - -namespace UnifiedContextsDemo.Users -{ - public interface IMyUserRepository : IRepository - { - Task FindBySocialSecurityNumber(string number); - } -} -```` - -Then implementing it in the `EntityFrameworkCore` project: - -````csharp -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using UnifiedContextsDemo.EntityFrameworkCore; -using Volo.Abp.Domain.Repositories.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.Identity; - -namespace UnifiedContextsDemo.Users -{ - public class MyUserRepository - : EfCoreRepository, - IMyUserRepository - { - public MyUserRepository( - IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task FindBySocialSecurityNumber(string number) - { - var dbContext = await GetDbContextAsync(); - return await dbContext.Set() - .Where(u => EF.Property(u, "SocialSecurityNumber") == number) - .FirstOrDefaultAsync(); - } - } -} -```` - -Tip: Use the constant instead of `SocialSecurityNumber` as a magic string. - -Now, I can use that repository method in my service by injecting the `IMyUserRepository`: - -````csharp -public class MyUserService : ITransientDependency -{ - private readonly IMyUserRepository _userRepository; - - public MyUserService(IMyUserRepository userRepository) - { - _userRepository = userRepository; - } - - //...other methods - - public async Task FindBySocialSecurityNumberDemoAsync(string number) - { - return await _userRepository.FindBySocialSecurityNumber(number); - } -} -```` - -I changed `IRepository` dependency to `IMyUserRepository`. - -## Conclusion - -With this article, I wanted to show you how to remove the `EntityFrameworkCore.DbMigrations` project from your solution to simplify your database mappings, database migrations and your application code. In the next version (4.4), this [will be](https://github.com/abpframework/abp/issues/8776) the default approach. - -### Source Code - -You can find source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/UnifiedEfCoreMigrations). diff --git a/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/default-solution.png b/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/default-solution.png deleted file mode 100644 index de626c3507..0000000000 Binary files a/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/default-solution.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/new-prop-on-table.png b/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/new-prop-on-table.png deleted file mode 100644 index 5bf40d4e4a..0000000000 Binary files a/docs/en/Community-Articles/2021-05-24-Removing-EfCore-Migrations/new-prop-on-table.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md deleted file mode 100644 index 5880406ffb..0000000000 --- a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md +++ /dev/null @@ -1,456 +0,0 @@ -# Using Elsa Workflow with ABP Framework - -**Elsa Core** is an open-source workflows library that can be used in any kind of .NET Core application. Using such a workflow library can be useful to implement business rules visually or programmatically. - -![elsa-overview](./elsa-overview.gif) - -This article shows how we can use this workflow library within our ABP-based application. We will start with a couple of examples and then we will integrate the **Elsa Dashboard** (you can see it in the above gif) into our application to be able to design our workflows visually. - -## Source Code - -You can find the source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo). - -## Create the Project - -In this article, I will create a new startup template with EF Core as a database provider and MVC/Razor-Pages for the UI framework. - -> If you already have a project with MVC/Razor-Pages or Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project (you can skip this section). - -* We will create a new solution named `ElsaDemo` (or whatever you want). We will create a new startup template with **EF Core** as a database provider and **MVC/Razor-Pages** for the UI framework by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -```bash -abp new ElsaDemo -``` - -* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE). - -* We can run the `ElsaDemo.DbMigrator` project to apply migration into our database and seed initial data. - -* After the database and initial data created, we can run the `ElsaDemo.Web` to see our UI working properly. - -> Default admin username is **admin** and password is **1q2w3E*** - -## Let's Create The First Workflow (Console Activity) - -We can start with creating our first workflow. Let's get started with creating a basic hello-world workflow by using console activity. In this example, we will **programmatically** define a workflow definition that displays the text **"Hello World from Elsa!"** to the console using Elsa's Workflow Builder API and run this workflow when the application initialized. - -### Install Packages - -We need to add two packages: `Elsa` and `Elsa.Activities.Console` into our `ElsaDemo.Web` project. We can add these two packages with the following command: - -```bash -dotnet add package Elsa -dotnet add package Elsa.Activities.Console -``` - -* After the packages installed, we can define our first workflow. To do this, create a folder named **Workflows** and in this folder create a class named `HelloWorldConsole`. - -```csharp -using Elsa.Activities.Console; -using Elsa.Builders; - -namespace ElsaDemo.Web.Workflows -{ - public class HelloWorldConsole : IWorkflow - { - public void Build(IWorkflowBuilder builder) => builder.WriteLine("Hello World from Elsa!"); - } -} -``` - -* In here we've basically implemented the `IWorkflow` interface which only has one method named **Build**. In this method, we can define our workflow's execution steps (activities). - -* As you can see in the example above, we've used an activity named **WriteLine**, which writes a line of text to the console. Elsa Core has many pre-defined activities like that. E.g **HttpEndpoint** and **WriteHttpResponse** (we will see them both in the next section). - -> "An activity is an atomic building block that represents a single executable step on the workflow." - [Elsa Core Activity Definition](https://elsa-workflows.github.io/elsa-core/docs/next/concepts/concepts-workflows#activity) - -* After defining our workflow, we need to define service registrations which required for the Elsa Core library to work properly. To do that, open your `ElsaDemoWebModule` class and update your `ElsaDemoWebModule` with the following lines. Most of the codes are abbreviated for simplicity. - -```csharp -using ElsaDemo.Web.Workflows; -using Elsa.Services; - -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var hostingEnvironment = context.Services.GetHostingEnvironment(); - var configuration = context.Services.GetConfiguration(); - - //... - - ConfigureElsa(context); -} - -private void ConfigureElsa(ServiceConfigurationContext context) -{ - context.Services.AddElsa(options => - { - options - .AddConsoleActivities() - .AddWorkflow(); - }); -} - -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - //... - - var workflowRunner = context.ServiceProvider.GetRequiredService(); - workflowRunner.BuildAndStartWorkflowAsync(); -} -``` - -* Here we basically, configured Elsa's services in our `ConfigureServices` method and after that in our `OnApplicationInitialization` method we started the `HelloWorldConsole` workflow. - -* If we run the application and examine the console outputs, we should see the message that we defined in our workflow. - -![hello-world-workflow](./hello-world-workflow.jpg) - -## Creating A Workflow By Using Http Activities - -In this example, we will create a workflow that uses **Http Activities**. It will basically listen the specified route for incoming HTTP Request and writes back a simple response. - -### Add Elsa.Activities.Http Package - -* To be able to use **HTTP Activities** we need to add `Elsa` (we've already added in the previous section) and `Elsa.Activities.Http` packages into our web application. - -```bash -dotnet add package Elsa.Activities.Http -``` - -* After the package installed, we can create our workflow. Let's started with creating a class named `HelloWorldHttp` under **Workflows** folder. - -```csharp -using System.Net; -using Elsa.Activities.Http; -using Elsa.Builders; - -namespace ElsaDemo.Web.Workflows -{ - public class HelloWorldHttp : IWorkflow - { - public void Build(IWorkflowBuilder builder) - { - builder - .HttpEndpoint("/hello-world") - .WriteHttpResponse(HttpStatusCode.OK, "

    Hello World!

    ", "text/html"); - } - } -} -``` - -* The above workflow has two activities. The first activity `HttpEndpoint` represents an HTTP endpoint, which can be invoked using an HTTP client, including a web browser. The first activity is connected to the second activity `WriteHttpResponse`, which returns a simple response to us. - -* After defined the **HelloWorldHttp** workflow we need to define this class as workflow. So, open your `ElsaDemoWebModule` and update the `ConfigureElsa` method as below. - -```csharp -private void ConfigureElsa(ServiceConfigurationContext context) -{ - context.Services.AddElsa(options => - { - options - .AddConsoleActivities() - .AddHttpActivities() //add this line to be able to use the http activities - .AddWorkflow() - .AddWorkflow(); //workflow that we defined - }); -} -``` -* And add the **UseHttpActivities** middleware to `OnApplicationInitilization` method of your `ElsaDemoWebModule` class. - -```csharp -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - // ... - app.UseAuditing(); - app.UseAbpSerilogEnrichers(); - app.UseHttpActivities(); //add this line - app.UseConfiguredEndpoints(); - - var workflowRunner = context.ServiceProvider.GetRequiredService(); - workflowRunner.BuildAndStartWorkflowAsync(); -} -``` - -* If we run the application and navigate to the "/hello-world" route we should see the response message that we've defined (by using **WriteHttpResponse** activity) in our `HelloWorldHttp` workflow. - -![hello-world-http](./hello-world-http.jpg) - -## Integrate Elsa Dashboard To Application - -* Until now we've created two workflows programmatically. But also we can create workflows visually by using Elsa's **HTML5 Workflow Designer**. - -* Being able to design our workflows easily and taking advantage of **HTML5 Workflow Designer** we will integrate the Elsa Dashboard to our application. - -### Install Packages - -* Following three packages required for Elsa Server. - -```bash -dotnet add package Elsa.Activities.Temporal.Quartz -dotnet add package Elsa.Persistence.EntityFramework.SqlServer -dotnet add package Elsa.Server.Api -``` - -> Also, we need to install the **Elsa** and **Elsa.Activities.Http** packages but we've already installed these packages in the previous sections. - -* We need to install one more package named `Elsa.Designer.Components.Web`. This package provides us the **Elsa Dashboard** component. - -```bash -dotnet add package Elsa.Designer.Components.Web -``` - -* After the package installations completed, we need to make the necessary configurations to be able to use the **Elsa Server** and **Elsa Dashboard**. Therefore, open your `ElsaDemoWebModule` class and make the necessary changes as below. - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var configuration = context.Services.GetConfiguration(); - - //... - - ConfigureElsa(context, configuration); -} - -private void ConfigureElsa(ServiceConfigurationContext context, IConfiguration configuration) -{ - var elsaSection = configuration.GetSection("Elsa"); - - context.Services.AddElsa(elsa => - { - elsa - .UseEntityFrameworkPersistence(ef => - DbContextOptionsBuilderExtensions.UseSqlServer(ef, - configuration.GetConnectionString("Default"))) - .AddConsoleActivities() - .AddHttpActivities(elsaSection.GetSection("Server").Bind) - .AddQuartzTemporalActivities() - .AddJavaScriptActivities() - .AddWorkflowsFrom(); - }); - - context.Services.AddElsaApiEndpoints(); - context.Services.Configure(options => - { - options.UseApiBehavior = false; - }); - - context.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy - .AllowAnyHeader() - .AllowAnyMethod() - .AllowAnyOrigin() - .WithExposedHeaders("Content-Disposition")) - ); - - //Uncomment the below line if your abp version is lower than v4.4 to register controllers of Elsa . - //See https://github.com/abpframework/abp/pull/9299 (we will no longer need to specify this line of code from v4.4) - // context.Services.AddAssemblyOf(); - - //Disable antiforgery validation for elsa - Configure(options => - { - options.AutoValidateFilter = type => - type.Assembly != typeof(Elsa.Server.Api.Endpoints.WorkflowRegistry.Get).Assembly; - }); -} - -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - app.UseCors(); - - //... - - app.UseHttpActivities(); - app.UseConfiguredEndpoints(endpoints => - { - endpoints.MapFallbackToPage("/_Host"); - }); - - var workflowRunner = context.ServiceProvider.GetRequiredService(); - workflowRunner.BuildAndStartWorkflowAsync(); -} -``` - -* These services required for the dashboard. - -* We don't need to register our workflows one by one anymore. Because now we use `.AddWorkflowsFrom()`, and this registers workflows on our behalf. - -* As you may notice here, we use a section named `Elsa` and its sub-sections from the configuration system but we didn't define them yet. To define them open your `appsettings.json` and add the following Elsa section into this file. - -```json -{ - //... - - "Elsa": { - "Http": { - "BaseUrl": "https://localhost:44336" - } - } -} -``` - -#### Define Permission For Elsa Dashboard - -* We can define a [permission](https://docs.abp.io/en/abp/latest/Authorization#permission-system) to be assured of only allowed users can see the Elsa Dashboard. - -* Open your `ElsaDemoPermissions` class under the **Permissions** folder (in the `ElsaDemo.Application.Contracts` layer) and add the following permission name. - -```csharp -namespace ElsaDemo.Permissions -{ - public static class ElsaDemoPermissions - { - public const string GroupName = "ElsaDemo"; - - public const string ElsaDashboard = GroupName + ".ElsaDashboard"; - } -} -``` - -* After that, open your `ElsaDemoPermissionDefinitionProvider` class and define the permission for Elsa Dashboard. - -```csharp -using ElsaDemo.Localization; -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Localization; - -namespace ElsaDemo.Permissions -{ - public class ElsaDemoPermissionDefinitionProvider : PermissionDefinitionProvider - { - public override void Define(IPermissionDefinitionContext context) - { - var myGroup = context.AddGroup(ElsaDemoPermissions.GroupName); - - myGroup.AddPermission(ElsaDemoPermissions.ElsaDashboard, L("Permission:ElsaDashboard")); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } - } -} -``` - -* As you can notice, we've used a localized value (**L("Permission:ElsaDashboard")**) but haven't added this localization key and value to the localization file, so let's add this localization key and value. To do this, open your `en.json` file under **Localization/ElsaDemo** folder (under the **DomainShared** layer) and add this localization key. - -```json -{ - "culture": "en", - "texts": { - "Menu:Home": "Home", - "Welcome": "Welcome", - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", - "Permission:ElsaDashboard": "Elsa Dashboard" - } -} -``` - -#### Add Elsa Dashboard Component To Application - -* After those configurations, now we can add Elsa Dashboard to our application with an authorization check. To do this, create a razor page named **_Host.cshtml** (under **Pages** folder) and update its content as below. - -```html -@page "/elsa" -@using ElsaDemo.Permissions -@using Microsoft.AspNetCore.Authorization -@attribute [Authorize(ElsaDemoPermissions.ElsaDashboard)] -@{ - var serverUrl = $"{Request.Scheme}://{Request.Host}"; - Layout = null; -} - - - - - - Elsa Workflows - - - - - - - - - - - - - -``` - -* We've defined an attribute for authorization check here. With this authorization check, only the user who has the **Elsa Dashboard** permission allowed to see this page. - -#### Add Elsa Dashboard Page To Main Menu - -* We can open the `ElsaDemoMenuContributor` class under the **Menus** folder and define the menu item for reaching the Elsa Dashboard easily. - -```csharp -using System.Threading.Tasks; -using ElsaDemo.Localization; -using ElsaDemo.MultiTenancy; -using ElsaDemo.Permissions; -using Volo.Abp.Identity.Web.Navigation; -using Volo.Abp.SettingManagement.Web.Navigation; -using Volo.Abp.TenantManagement.Web.Navigation; -using Volo.Abp.UI.Navigation; - -namespace ElsaDemo.Web.Menus -{ - public class ElsaDemoMenuContributor : IMenuContributor - { - public async Task ConfigureMenuAsync(MenuConfigurationContext context) - { - if (context.Menu.Name == StandardMenus.Main) - { - await ConfigureMainMenuAsync(context); - } - } - - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - var administration = context.Menu.GetAdministration(); - var l = context.GetLocalizer(); - - context.Menu.Items.Insert( - 0, - new ApplicationMenuItem( - ElsaDemoMenus.Home, - l["Menu:Home"], - "~/", - icon: "fas fa-home", - order: 0 - ) - ); - - //add Workflow menu-item - context.Menu.Items.Insert( - 1, - new ApplicationMenuItem( - ElsaDemoMenus.Home, - "Workflow", - "~/elsa", - icon: "fas fa-code-branch", - order: 1, - requiredPermissionName: ElsaDemoPermissions.ElsaDashboard - ) - ); - - //... - } - } -} -``` - -* With that menu item configuration, only the user who has **Elsa Dashboard** permission allowed to see the defined menu item. - -## Result - -* Let's run the application and see how it looks like. - -> If the account you are logged in has the **ElsaDemoPermissions.ElsaDashboard** permission, you should see the **Workflow** menu item. If you do not see this menu item, please be assured that your logged-in account has that permission. - -* Now we can click the "Workflow" menu item, display the Elsa Dashboard and designing workflows. - -![elsa-demo-result](./elsa-demo-result.gif) \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-demo-result.gif b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-demo-result.gif deleted file mode 100644 index 4019b6eabe..0000000000 Binary files a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-demo-result.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-overview.gif b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-overview.gif deleted file mode 100644 index ba589cabfc..0000000000 Binary files a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-overview.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-http.jpg b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-http.jpg deleted file mode 100644 index 12c6e81c67..0000000000 Binary files a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-http.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-workflow.jpg b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-workflow.jpg deleted file mode 100644 index fa6e30c876..0000000000 Binary files a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-workflow.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/1.png b/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/1.png deleted file mode 100644 index 036165fe30..0000000000 Binary files a/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/1.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/2.png b/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/2.png deleted file mode 100644 index e4f5546aaa..0000000000 Binary files a/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/2.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/POST.md b/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/POST.md deleted file mode 100644 index cb1b21d7fd..0000000000 --- a/docs/en/Community-Articles/2021-09-25-How-to-Override-Localization-Strings-Of-Depending-Modules/POST.md +++ /dev/null @@ -1,88 +0,0 @@ -# How to override localization strings of depending modules - -## Source Code - -You can find the source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/DocumentationSamples/ExtendLocalizationResource). - -## Getting Started - -This example is based on the following document -https://docs.abp.io/en/abp/latest/Localization#extending-existing-resource - -We will change the default `DisplayName:Abp.Timing.Timezone` and `Description:Abp.Timing.Timezone` of [`AbpTimingResource`](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/AbpTimingResource.cs) and add localized text in [Russian language(`ru`)](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/en.json). - -I created the `AbpTiming` folder in the `Localization` directory of the `ExtendLocalizationResource.Domain.Shared` project. - -Create `en.json` and `ru.json` in its directory. - -`en.json` -```json -{ - "culture": "en", - "texts": { - "DisplayName:Abp.Timing.Timezone": "My Time zone", - "Description:Abp.Timing.Timezone": "My Application time zone" - } -} -``` - -`ru.json` -```json -{ - "culture": "ru", - "texts": { - "DisplayName:Abp.Timing.Timezone": "Часовой пояс", - "Description:Abp.Timing.Timezone": "Часовой пояс приложения" - } -} -``` - -![](1.png) - -We have below content in `ExtendLocalizationResource.Domain.Shared.csproj` file, See [Virtual-File-System](https://docs.abp.io/en/abp/latest/Virtual-File-System#working-with-the-embedded-files) understand how it works. - -```xml - - - - - - - - -``` - -Change the code of the `ConfigureServices` method in `ExtendLocalizationResourceDomainSharedModule`. - -```cs -Configure(options => -{ - options.Resources - .Add("en") - .AddBaseTypes(typeof(AbpValidationResource)) - .AddVirtualJson("/Localization/ExtendLocalizationResource"); - - //add following code - options.Resources - .Get() - .AddVirtualJson("/Localization/AbpTiming"); - - options.DefaultResourceType = typeof(ExtendLocalizationResourceResource); -}); -``` - -Execute `ExtendLocalizationResource.DbMigrator` to migrate the database and run `ExtendLocalizationResource.Web`. - -We have changed the English localization text and added Russian localization. - -### Index page - -```cs -

    @AbpTimingResource["DisplayName:Abp.Timing.Timezone"]

    -@using(CultureHelper.Use("ru")) -{ -

    @AbpTimingResource["DisplayName:Abp.Timing.Timezone"]

    -} -``` - -![](2.png) \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md deleted file mode 100644 index ae688d6981..0000000000 --- a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md +++ /dev/null @@ -1,1671 +0,0 @@ -# Many to Many Relationship with ABP and EF Core - -## Introduction - -In this article, we'll create a **BookStore** application like in [the ABP tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF) and add an extra `Category` feature to demonstrate how we can manage the many-to-many relationship with ABP-based applications (by following DDD rules). - -You can see the ER Diagram of our application below. This diagram will be helpful for us to demonstrate the relations between our entities. - -![ER-Diagram](./er-diagram.png) - -When we've examined the ER Diagram, we can see the one-to-many relationship between the **Author** and the **Book** tables. Also, the many-to-many relationship (**BookCategory** table) between the **Book** and the **Category** tables. (There can be more than one category on each book and vice-versa in our scenario). - -### Source Code - -You can find the source code of the application at https://github.com/EngincanV/ABP-Many-to-Many-Relationship-Demo . - -### Demo of the Final Application - -At the end of this article, we will have created an application same as in the gif below. - -![Demo of The Final Application](./application-final-demo.gif) - -## Creating the Solution - -In this article, we will create a new startup template with EF Core as a database provider and MVC for UI framework. - -* We can create a new startup template by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -```bash -abp new BookStore -t app --version 5.0.0-beta.2 -``` - -* Our project boilerplate will be ready after the download is finished. Then, we can open the solution and start developing. - -## Starting the Development - -Let's start with creating our Domain Entities. - -### Step 1 - (Creating the Domain Entities) - -We can create a folder-structure under the `BookStore.Domain` project like in the image below. - -![Domain-Layer-Folder-Structure](./domain-file-structure.png) - -Open the entity classes and add the following codes to each of these classes. - -* **Author.cs** - -```csharp -using System; -using JetBrains.Annotations; -using Volo.Abp; -using Volo.Abp.Domain.Entities.Auditing; - -namespace BookStore.Authors -{ - public class Author : FullAuditedAggregateRoot - { - public string Name { get; private set; } - - public DateTime BirthDate { get; set; } - - public string ShortBio { get; set; } - - /* This constructor is for deserialization / ORM purpose */ - private Author() - { - } - - public Author(Guid id, [NotNull] string name, DateTime birthDate, [CanBeNull] string shortBio = null) - : base(id) - { - SetName(name); - BirthDate = birthDate; - ShortBio = shortBio; - } - - public void SetName([NotNull] string name) - { - Name = Check.NotNullOrWhiteSpace( - name, - nameof(name), - maxLength: AuthorConsts.MaxNameLength - ); - } - } -} -``` - -> We'll create the `AuthorConsts` class later in this step. - -* **Book.cs** - -```csharp -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Volo.Abp; -using Volo.Abp.Domain.Entities.Auditing; - -namespace BookStore.Books -{ - public class Book : FullAuditedAggregateRoot - { - public Guid AuthorId { get; set; } - - public string Name { get; private set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - - public ICollection Categories { get; private set; } - - private Book() - { - } - - public Book(Guid id, Guid authorId, string name, DateTime publishDate, float price) - : base(id) - { - AuthorId = authorId; - SetName(name); - PublishDate = publishDate; - Price = price; - - Categories = new Collection(); - } - - public void SetName(string name) - { - Name = Check.NotNullOrWhiteSpace(name, nameof(name), BookConsts.MaxNameLength); - } - - public void AddCategory(Guid categoryId) - { - Check.NotNull(categoryId, nameof(categoryId)); - - if (IsInCategory(categoryId)) - { - return; - } - - Categories.Add(new BookCategory(bookId: Id, categoryId)); - } - - public void RemoveCategory(Guid categoryId) - { - Check.NotNull(categoryId, nameof(categoryId)); - - if (!IsInCategory(categoryId)) - { - return; - } - - Categories.RemoveAll(x => x.CategoryId == categoryId); - } - - public void RemoveAllCategoriesExceptGivenIds(List categoryIds) - { - Check.NotNullOrEmpty(categoryIds, nameof(categoryIds)); - - Categories.RemoveAll(x => !categoryIds.Contains(x.CategoryId)); - } - - public void RemoveAllCategories() - { - Categories.RemoveAll(x => x.BookId == Id); - } - - private bool IsInCategory(Guid categoryId) - { - return Categories.Any(x => x.CategoryId == categoryId); - } - } -} -``` - -* In our scenario, a book can have more than one category and a category can have more than one book so we need to create a many-to-many relationship between them. - -* For achieving this, we will create a **join entity** named `BookCategory`, and this class will simply have variables named `BookId` and `CategoryId`. - -* To manage this **join entity**, we can add it as a sub-collection to the **Book** entity, as we do above. We add this sub-collection -to **Book** class instead of **Category** class, because a book can have tens (or mostly hundreds) of categories but on the other perspective a category can have more than a hundred (or even way much) books inside of it. - -* It is a significant performance problem to load thousands of items whenever you query a category. Therefore it makes much more sense to add that sub-collection to the `Book` entity. - -> Don't forget: **Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit.** (See the full [description](https://martinfowler.com/bliki/DDD_Aggregate.html)) - -* Notice that, `BookCategory` is not an **Aggregate Root** so we are not violating one of the base rules about Aggregate Root (Rule: **"Reference Other Aggregates Only by ID"**). - -* If we examine the methods in the `Book` class (such as **RemoveAllCategories**, **RemoveAllCategoriesExceptGivenIds** and **AddCategory**) we will manage our sub-collection `Categories` (**BookCategory** - join table/entity) through them. (Adds or removes categories for books) - -> We'll create the `BookCategory` and `BookConsts` classes later in this step. - -* **BookCategory.cs** - -```csharp -using System; -using Volo.Abp.Domain.Entities; - -namespace BookStore.Books -{ - public class BookCategory : Entity - { - public Guid BookId { get; protected set; } - - public Guid CategoryId { get; protected set; } - - /* This constructor is for deserialization / ORM purpose */ - private BookCategory() - { - } - - public BookCategory(Guid bookId, Guid categoryId) - { - BookId = bookId; - CategoryId = categoryId; - } - - public override object[] GetKeys() - { - return new object[] {BookId, CategoryId}; - } - } -} -``` - -* Here, as you can notice, we've defined the `BookCategory` as the **Join Table/Entity** for our many-to-many relationship and ensured the required properties (BookId and CategoryId) were set in the constructor method of this class to create this object. - -* And also we've derived this class from the `Entity` class and therefore we've had to override the **GetKeys** method of this class to define the **Composite Key**. - -> The composite key is composed of `BookId` and `CategoryId` in our case. And they are unique together. - -> For more information about **Entities with Composite Keys**, you can read the relevant section from [Entities documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys). - -* **BookManager.cs** - -```csharp -using System; -using System.Linq; -using System.Threading.Tasks; -using BookStore.Categories; -using JetBrains.Annotations; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Domain.Services; - -namespace BookStore.Books -{ - public class BookManager : DomainService - { - private readonly IBookRepository _bookRepository; - private readonly IRepository _categoryRepository; - - public BookManager(IBookRepository bookRepository, IRepository categoryRepository) - { - _bookRepository = bookRepository; - _categoryRepository = categoryRepository; - } - - public async Task CreateAsync(Guid authorId, string name, DateTime publishDate, float price, [CanBeNull]string[] categoryNames) - { - var book = new Book(GuidGenerator.Create(), authorId, name, publishDate, price); - - await SetCategoriesAsync(book, categoryNames); - - await _bookRepository.InsertAsync(book); - } - - public async Task UpdateAsync( - Book book, - Guid authorId, - string name, - DateTime publishDate, - float price, - [CanBeNull] string[] categoryNames - ) - { - book.AuthorId = authorId; - book.SetName(name); - book.PublishDate = publishDate; - book.Price = price; - - await SetCategoriesAsync(book, categoryNames); - - await _bookRepository.UpdateAsync(book); - } - - private async Task SetCategoriesAsync(Book book, [CanBeNull] string[] categoryNames) - { - if (categoryNames == null || !categoryNames.Any()) - { - book.RemoveAllCategories(); - return; - } - - var query = (await _categoryRepository.GetQueryableAsync()) - .Where(x => categoryNames.Contains(x.Name)) - .Select(x => x.Id) - .Distinct(); - - var categoryIds = await AsyncExecuter.ToListAsync(query); - if (!categoryIds.Any()) - { - return; - } - - book.RemoveAllCategoriesExceptGivenIds(categoryIds); - - foreach (var categoryId in categoryIds) - { - book.AddCategory(categoryId); - } - } - } -} -``` - -* If we examine the codes in the `BookManager` class, we can see that we've managed the `BookCategory` class (our join table/entity) by using some methods that we've defined in the `Book` class such as **RemoveAllCategories**, **RemoveAllCategoriesExceptGivenIds** and **AddCategory**. - -* These methods basically add or remove categories related to the book by conditions. - -* In the `CreateAsync` method, if the category names are specified, we'll retrieve their ids from the database and by using the **AddCategory** method that we've defined in the `Book` class, we'll add them. - -* In the `UpdateAsync` method, the same logic is also valid. But in this case, the user might want to remove some categories from books, so if the user sends us an empty **categoryNames** array, we remove all categories from the book he wants to update. If the user sends us some category names, we remove the excluded ones and add the new ones according to the **categoryNames** array. - -* **BookWithDetails.cs** - -```csharp -using System; -using Volo.Abp.Auditing; - -namespace BookStore.Books -{ - public class BookWithDetails : IHasCreationTime - { - public Guid Id { get; set; } - - public string Name { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - - public string AuthorName { get; set; } - - public string[] CategoryNames { get; set; } - - public DateTime CreationTime { get; set; } - } -} -``` - -We will use this class to retrieve books with their sub-categories and author names. - -* **IBookRepository.cs** - -```csharp -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; - -namespace BookStore.Books -{ - public interface IBookRepository : IRepository - { - Task> GetListAsync( - string sorting, - int skipCount, - int maxResultCount, - CancellationToken cancellationToken = default - ); - - Task GetAsync(Guid id, CancellationToken cancellationToken = default); - } -} -``` - -We need to create two methods named **GetListAsync** and **GetAsync** and specify their return type as `BookWithDetails`. So by implementing these methods, we will return the book/books by their details (author name and categories). - -* **Category.cs** - -```csharp -using System; -using Volo.Abp; -using Volo.Abp.Domain.Entities.Auditing; - -namespace BookStore.Categories -{ - public class Category : AuditedAggregateRoot - { - public string Name { get; private set; } - - /* This constructor is for deserialization / ORM purpose */ - private Category() - { - } - - public Category(Guid id, string name) : base(id) - { - SetName(name); - } - - public Category SetName(string name) - { - Name = Check.NotNullOrWhiteSpace(name, nameof(name), CategoryConsts.MaxNameLength); - return this; - } - } -} -``` - -After defining our entities we can seed initial data to our database by using the [Data Seeding](https://docs.abp.io/en/abp/5.0/Data-Seeding#data-seeding) system of the ABP framework. We will create initial data for both the `Category` and `Author` entities because we will not create CRUD pages for these entities. - -> We will create only CRUD pages for the Book entity therefore we don't need to add initial data for the Book entity. We can create a new book by using the create modal of the Book page. (We will create it in the sixth step.) - -Create a class named `BookStoreDataSeederContributor` in your `*.Domain` project and update with the following code: - -* **BookStoreDataSeederContributor.cs** - -```csharp -using System; -using System.Threading.Tasks; -using BookStore.Authors; -using BookStore.Categories; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; - -namespace BookStore -{ - public class BookStoreDataSeederContributor : IDataSeedContributor, ITransientDependency - { - private readonly IGuidGenerator _guidGenerator; - private readonly IRepository _categoryRepository; - private readonly IRepository _authorRepository; - - public BookStoreDataSeederContributor( - IGuidGenerator guidGenerator, - IRepository categoryRepository, - IRepository authorRepository - ) - { - _guidGenerator = guidGenerator; - _categoryRepository = categoryRepository; - _authorRepository = authorRepository; - } - - public async Task SeedAsync(DataSeedContext context) - { - await SeedCategoriesAsync(); - await SeedAuthorsAsync(); - } - - private async Task SeedCategoriesAsync() - { - if (await _categoryRepository.GetCountAsync() <= 0) - { - await _categoryRepository.InsertAsync( - new Category(_guidGenerator.Create(), "History") - ); - - await _categoryRepository.InsertAsync( - new Category(_guidGenerator.Create(), "Unknown") - ); - - await _categoryRepository.InsertAsync( - new Category(_guidGenerator.Create(), "Adventure") - ); - - await _categoryRepository.InsertAsync( - new Category(_guidGenerator.Create(), "Action") - ); - - await _categoryRepository.InsertAsync( - new Category(_guidGenerator.Create(), "Crime") - ); - - await _categoryRepository.InsertAsync( - new Category(_guidGenerator.Create(), "Dystopia") - ); - } - } - - private async Task SeedAuthorsAsync() - { - if (await _authorRepository.GetCountAsync() <= 0) - { - await _authorRepository.InsertAsync( - new Author( - _guidGenerator.Create(), - "George Orwell", - new DateTime(1903, 06, 25), - "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)." - ) - ); - - await _authorRepository.InsertAsync( - new Author( - _guidGenerator.Create(), - "Dan Brown", - new DateTime(1964, 06, 22), - "Daniel Gerhard Brown (born June 22, 1964) is an American author best known for his thriller novels" - ) - ); - } - } - } -} -``` - -### Step 2 - (Define Consts) - -We can create a folder-structure under the `BookStore.Domain.Shared` project like in the image below. - -![Domain Shared File Structure](./domain-shared-file-structure.png) - -* **AuthorConsts.cs** - -```csharp -namespace BookStore.Authors -{ - public class AuthorConsts - { - public const int MaxNameLength = 128; - - public const int MaxShortBioLength = 256; - } -} -``` - -* **BookConsts.cs** - -```csharp -namespace BookStore.Books -{ - public class BookConsts - { - public const int MaxNameLength = 128; - } -} -``` - -* **CategoryConsts.cs** - -```csharp -namespace BookStore.Categories -{ - public class CategoryConsts - { - public const int MaxNameLength = 64; - } -} -``` - -In these classes, we've defined max text length for our entity properties that we will use in the **Database Integration** section to specify limits for our properties. (E.g. varchar(128) for BookName) - -### Step 3 - (Database Integration) - -After defining our entities, we can configure them for the database integration. -Open the `BookStoreDbContext` class in the `BookStore.EntityFrameworkCore` project and update the following code blocks. - -```csharp -namespace BookStore.EntityFrameworkCore -{ - [ReplaceDbContext(typeof(IIdentityDbContext))] - [ReplaceDbContext(typeof(ITenantManagementDbContext))] - [ConnectionStringName("Default")] - public class BookStoreDbContext : - AbpDbContext, - IIdentityDbContext, - ITenantManagementDbContext - { - //... - - //DbSet properties for our Aggregate Roots - public DbSet Authors { get; set; } - public DbSet Books { get; set; } - public DbSet Categories { get; set; } - - //NOTE: We don't need to add DbSet, because we will be query it via using the Book entity - // public DbSet BookCategories { get; set; } - - //... - - protected override void OnModelCreating(ModelBuilder builder) - { - //... - - /* Configure your own tables/entities inside here */ - builder.Entity(b => - { - b.ToTable(BookStoreConsts.DbTablePrefix + "Authors", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); - - b.Property(x => x.Name) - .HasMaxLength(AuthorConsts.MaxNameLength) - .IsRequired(); - - b.Property(x => x.ShortBio) - .HasMaxLength(AuthorConsts.MaxShortBioLength) - .IsRequired(); - }); - - builder.Entity(b => - { - b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); - - b.Property(x => x.Name) - .HasMaxLength(BookConsts.MaxNameLength) - .IsRequired(); - - //one-to-many relationship with Author table - b.HasOne().WithMany().HasForeignKey(x => x.AuthorId).IsRequired(); - - //many-to-many relationship with Category table => BookCategories - b.HasMany(x => x.Categories).WithOne().HasForeignKey(x => x.BookId).IsRequired(); - }); - - builder.Entity(b => - { - b.ToTable(BookStoreConsts.DbTablePrefix + "Categories", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); - - b.Property(x => x.Name) - .HasMaxLength(CategoryConsts.MaxNameLength) - .IsRequired(); - }); - - builder.Entity(b => - { - b.ToTable(BookStoreConsts.DbTablePrefix + "BookCategories", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); - - //define composite key - b.HasKey(x => new { x.BookId, x.CategoryId }); - - //many-to-many configuration - b.HasOne().WithMany(x => x.Categories).HasForeignKey(x => x.BookId).IsRequired(); - b.HasOne().WithMany().HasForeignKey(x => x.CategoryId).IsRequired(); - - b.HasIndex(x => new { x.BookId, x.CategoryId }); - }); - } - } -} -``` - -* In this class, we've defined the **DbSet** properties for our **Aggregate Roots** (**Book**, **Author** and **Category**). Notice, we didn't define the **DbSet** for the `BookCategory` class (our join table/entity). Because, the `Book` aggregate is responsible for managing it via sub-collection. - -* After that, we can use the **FluentAPI** to configure our tables in the `OnModelCreating` method of this class. - -```csharp -builder.Entity(b => -{ - //... - - //one-to-many relationship with Author table - b.HasOne().WithMany().HasForeignKey(x => x.AuthorId).IsRequired(); - - //many-to-many relationship with Category table => BookCategories - b.HasMany(x => x.Categories).WithOne().HasForeignKey(x => x.BookId).IsRequired(); -}); -``` - -Here, we have provided the one-to-many relationship between the **Book** and the **Author** in the above code-block. - -```csharp -builder.Entity(b => -{ - //... - - //define composite key - b.HasKey(x => new { x.BookId, x.CategoryId }); - - //many-to-many configuration - b.HasOne().WithMany(x => x.Categories).HasForeignKey(x => x.BookId).IsRequired(); - b.HasOne().WithMany().HasForeignKey(x => x.CategoryId).IsRequired(); - - b.HasIndex(x => new { x.BookId, x.CategoryId }); -}); -``` - -Here, firstly we've defined the composite key for our `BookCategory` entity. `BookId` and `CategoryId` are together as composite keys for the `BookCategory` table. Then we've configured the many-to-many relationship between the `Book` and the `Category` tables like in the above code-block. - -#### Implementing the `IBookRepository` Interface - -After making the relevant configurations for the database integration, we can now implement the `IBookRepository` interface. To do this, create a folder named `Books` in the `BookStore.EntityFrameworkCore` project and inside of this folder, create a class named `EfCoreBookRepository` and update this class with the following code: - -```csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Dynamic.Core; -using System.Threading; -using System.Threading.Tasks; -using BookStore.Authors; -using BookStore.Categories; -using BookStore.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Volo.Abp.Domain.Repositories.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore; - -namespace BookStore.Books -{ - public class EfCoreBookRepository : EfCoreRepository, IBookRepository - { - public EfCoreBookRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) - { - } - - public async Task> GetListAsync( - string sorting, - int skipCount, - int maxResultCount, - CancellationToken cancellationToken = default - ) - { - var query = await ApplyFilterAsync(); - - return await query - .OrderBy(!string.IsNullOrWhiteSpace(sorting) ? sorting : nameof(Book.Name)) - .PageBy(skipCount, maxResultCount) - .ToListAsync(GetCancellationToken(cancellationToken)); - } - - public async Task GetAsync(Guid id, CancellationToken cancellationToken = default) - { - var query = await ApplyFilterAsync(); - - return await query - .Where(x => x.Id == id) - .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); - } - - private async Task> ApplyFilterAsync() - { - var dbContext = await GetDbContextAsync(); - - return (await GetDbSetAsync()) - .Include(x => x.Categories) - .Join(dbContext.Set(), book => book.AuthorId, author => author.Id, - (book, author) => new {book, author}) - .Select(x => new BookWithDetails - { - Id = x.book.Id, - Name = x.book.Name, - Price = x.book.Price, - PublishDate = x.book.PublishDate, - CreationTime = x.book.CreationTime, - AuthorName = x.author.Name, - CategoryNames = (from bookCategories in x.book.Categories - join category in dbContext.Set() on bookCategories.CategoryId equals category.Id - select category.Name).ToArray() - }); - } - - public override Task> WithDetailsAsync() - { - return base.WithDetailsAsync(x => x.Categories); - } - } -} -``` - -* Here, we've implemented our custom repository methods and returned the book with details (author name and categories). - -### Step 4 - (Database Migration) - -* We've integrated our entities with the database in the previous step, now we can create a new database migration and apply it to the database. So let's do that. - -* Open the `BookStore.EntityFrameworkCore` project in the terminal. And create a new database migration by using the following command: - -```bash -dotnet ef migrations add -``` - -* Then, run the `BookStore.DbMigrator` application to create the database. - -### Step 5 - (Create Application Services) - -* Let's start with defining our DTOs and application service interfaces in the `BookStore.Application.Contracts` layer. We can create a folder-structure like in the image below: - -![Application Contracts Folder Structure](./application-contracts-folder-structure.png) - -* We can use the [`CrudAppService`](https://docs.abp.io/en/abp/latest/Application-Services#crud-application-services) base class of the ABP Framework to create application services to **Get**, **Create**, **Update** and **Delete** authors and categories. - -* **AuthorDto.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace BookStore.Authors -{ - public class AuthorDto : EntityDto - { - public string Name { get; set; } - - public DateTime BirthDate { get; set; } - - public string ShortBio { get; set; } - } -} -``` - -* **AuthorLookupDto.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace BookStore.Authors -{ - public class AuthorLookupDto : EntityDto - { - public string Name { get; set; } - } -} -``` - -We will use this DTO class as output DTO to get all the authors and list them in a select box in the book creation model. (Like in the image below.) - -![Book Create Modal](./book-creation-modal.png) - -* **CreateUpdateAuthorDto.cs** - -```csharp -using System; - -namespace BookStore.Authors -{ - public class CreateUpdateAuthorDto - { - public string Name { get; set; } - - public DateTime BirthDate { get; set; } - - public string ShortBio { get; set; } - } -} -``` - -* **IAuthorAppService.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace BookStore.Authors -{ - public interface IAuthorAppService : - ICrudAppService - { - } -} -``` - -* **BookDto.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace BookStore.Books -{ - public class BookDto : EntityDto - { - public string AuthorName { get; set; } - - public string Name { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - - public string[] CategoryNames { get; set; } - } -} -``` - -When listing the Book/Books we will retrieve them with all their details (author name and category names). - -* **BookGetListInput.cs** - -```csharp -using Volo.Abp.Application.Dtos; - -namespace BookStore.Books -{ - public class BookGetListInput : PagedAndSortedResultRequestDto - { - } -} -``` - -* **CreateUpdateBookDto.cs** - -```csharp -using System; - -namespace BookStore.Books -{ - public class CreateUpdateBookDto - { - public Guid AuthorId { get; set; } - - public string Name { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - - public string[] CategoryNames { get; set; } - } -} -``` - -To create or update a book we will use this input DTO. - -* **IBookAppService.cs** - -```csharp -using System; -using System.Threading.Tasks; -using BookStore.Authors; -using BookStore.Categories; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace BookStore.Books -{ - public interface IBookAppService : IApplicationService - { - Task> GetListAsync(BookGetListInput input); - - Task GetAsync(Guid id); - - Task CreateAsync(CreateUpdateBookDto input); - - Task UpdateAsync(Guid id, CreateUpdateBookDto input); - - Task DeleteAsync(Guid id); - - Task> GetAuthorLookupAsync(); - - Task> GetCategoryLookupAsync(); - } -} -``` - -* We will create custom application service method for managing Books instead of using the `CrudAppService`'s methods. - -* Also we will create two additional methods and they are `GetAuthorLookupAsync` and `GetCategoryLookupAsync`. We will use these two methods to retrieve all the authors and categories without pagination and list them as a select box item in create/update modals for the Book page. -(You can see the usage of these two methods in the gif below.) - -![New Book](./book-create.gif) - -* **CategoryDto.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace BookStore.Categories -{ - public class CategoryDto : EntityDto - { - public string Name { get; set; } - } -} -``` - -* **CategoryLookupDto.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace BookStore.Categories -{ - public class CategoryLookupDto : EntityDto - { - public string Name { get; set; } - } -} -``` - -We will use this DTO class as an output DTO to get all categories without pagination and list them in a select box in the book create/update modals. - -* **CreateUpdateCategoryDto.cs** - -```csharp -namespace BookStore.Categories -{ - public class CreateUpdateCategoryDto - { - public string Name { get; set; } - } -} -``` - -* **ICategoryAppService.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace BookStore.Categories -{ - public interface ICategoryAppService : - ICrudAppService - { - } -} -``` - -After creating the DTOs and application service interfaces, now we can define the implementation of those interfaces. So, we can create a folder-structure like in the image below for the `BookStore.Application` layer. Open the application service classes and add the following codes to each of these classes. - -![Application Folder Structure](./application-folder-structure.png) - -* **AuthorAppService.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace BookStore.Authors -{ - public class AuthorAppService : - CrudAppService, - IAuthorAppService - { - public AuthorAppService(IRepository repository) : base(repository) - { - } - } -} -``` - -* **CategoryAppService.cs** - -```csharp -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace BookStore.Categories -{ - public class CategoryAppService : - CrudAppService, - ICategoryAppService - { - public CategoryAppService(IRepository repository) : base(repository) - { - } - } -} -``` - -Thanks to the `CrudAppService`, we don't need to manually implement the crud methods for **AuthorAppService** and **CategoryAppService**. - -* **BookAppService.cs** - -```csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using BookStore.Authors; -using BookStore.Categories; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Domain.Repositories; - -namespace BookStore.Books -{ - public class BookAppService : BookStoreAppService, IBookAppService - { - private readonly IBookRepository _bookRepository; - private readonly BookManager _bookManager; - private readonly IRepository _authorRepository; - private readonly IRepository _categoryRepository; - - public BookAppService( - IBookRepository bookRepository, - BookManager bookManager, - IRepository authorRepository, - IRepository categoryRepository - ) - { - _bookRepository = bookRepository; - _bookManager = bookManager; - _authorRepository = authorRepository; - _categoryRepository = categoryRepository; - } - - public async Task> GetListAsync(BookGetListInput input) - { - var books = await _bookRepository.GetListAsync(input.Sorting, input.SkipCount, input.MaxResultCount); - var totalCount = await _bookRepository.CountAsync(); - - return new PagedResultDto(totalCount, ObjectMapper.Map, List>(books)); - } - - public async Task GetAsync(Guid id) - { - var book = await _bookRepository.GetAsync(id); - - return ObjectMapper.Map(book); - } - - public async Task CreateAsync(CreateUpdateBookDto input) - { - await _bookManager.CreateAsync( - input.AuthorId, - input.Name, - input.PublishDate, - input.Price, - input.CategoryNames - ); - } - - public async Task UpdateAsync(Guid id, CreateUpdateBookDto input) - { - var book = await _bookRepository.GetAsync(id, includeDetails: true); //return type is: Book (not BookWithDetails) Because, we don't need author name - - await _bookManager.UpdateAsync( - book, - input.AuthorId, - input.Name, - input.PublishDate, - input.Price, - input.CategoryNames - ); - } - - public async Task DeleteAsync(Guid id) - { - await _bookRepository.DeleteAsync(id); - } - - public async Task> GetAuthorLookupAsync() - { - var authors = await _authorRepository.GetListAsync(); - - return new ListResultDto( - ObjectMapper.Map, List>(authors) - ); - } - - public async Task> GetCategoryLookupAsync() - { - var categories = await _categoryRepository.GetListAsync(); - - return new ListResultDto( - ObjectMapper.Map, List>(categories) - ); - } - } -} -``` - -* As you can notice here, we've used our **Domain Service** class named `BookManager` in the **CreateAsync** and **UpdateAsync** methods. (Defined them in step 1) - -* As you may remember, in these methods, new categories are added to the book or removed from the sub-collection (**Categories** (`BookCategory`)) according to the relevant category names. - -* After implementing the application services, we need to define the mappings for our services to work. So open the `BookStoreApplicationAutoMapperProfile` class and update it with the following code: - -```csharp -using AutoMapper; -using BookStore.Authors; -using BookStore.Books; -using BookStore.Categories; - -namespace BookStore -{ - public class BookStoreApplicationAutoMapperProfile : Profile - { - public BookStoreApplicationAutoMapperProfile() - { - CreateMap(); - CreateMap(); - CreateMap(); - - CreateMap(); - CreateMap(); - CreateMap(); - - CreateMap(); - } - } -} - -``` - -### Step 6 - (UI) - -The only thing we need to do is, by using the application service methods that we've defined in the previous step to create the UI. - -![Web Folder Structure](./web-folder-structure.png) - -> To keep the article shorter, I'll just show you how to create the Book page (with Create/Edit modals). If you want to implement it to other pages, you can access the source code of the application at https://github.com/EngincanV/ABP-Many-to-Many-Relationship-Demo and copy-paste the relevant code-blocks to your application. - -#### Book Page - -* Create a razor page named **Index.cshtml** under the **Pages/Books** folder of the `BookStore.Web` project and paste the following code to that page. - -* **Index.cshtml** - -```html -@page -@model BookStore.Web.Pages.Books.Index - -@section scripts -{ - -} - - - - - - Books - - - - - - - - - - -``` - -In here we've added a **New Book** button and a table with an id named "BooksTable". We'll create an `Index.js` file and by using [datatable.js](https://datatables.net) we will fill the table with our records. - -* **Index.js** - -```js -$(function () { - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var bookService = bookStore.books.book; - - var dataTable = $('#BooksTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(bookService.getList), - columnDefs: [ - { - title: 'Actions', - rowAction: { - items: - [ - { - text: 'Edit', - action: function (data) { - editModal.open({ id: data.record.id }); - } - }, - { - text: 'Delete', - confirmMessage: function (data) { - return "Are you sure to delete the book '" + data.record.name +"'?"; - }, - action: function (data) { - bookService - .delete(data.record.id) - .then(function() { - abp.notify.info("Successfully deleted!"); - dataTable.ajax.reload(); - }); - } - } - ] - } - }, - { - title: 'Name', - data: "name" - }, - { - title: 'Publish Date', - data: "publishDate", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(); - } - }, - { - title: 'Author Name', - data: "authorName" - }, - { - title: 'Price', - data: "price" - }, - { - title: 'Categories', - data: "categoryNames", - render: function (data) { - return data.join(", "); - } - } - ] - }) - ); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); - -``` - -> `abp.libs.datatables.normalizeConfiguration` is a helper function defined by the ABP Framework. It simplifies the Datatables configuration by providing conventional default values for missing options. - -* Let's examine what we've done in the `Index.js` file. - -* Firstly, we've defined our `createModal` and `editModal` modals by using the [ABP Modals](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Modals). Then, we've created the DataTable and fetched our books by using the dynamic JavaScript proxy function (`bookStore.books.book.getList`) (It sends a request to the **GetListAsync** method that we've defined in the `BookAppService` under the hook) and we've shown them in the table with an id named "BooksTable". - -* Now let's run the application and navigate to the **/Books** route to see how our Book page looks. - -![Demo](./demo.png) - -We need to see a page similar to the image above. Our app is working properly, we can continue developing. - -> If you are stuck in any point, you can examine the [source codes](https://github.com/EngincanV/ABP-Many-to-Many-Relationship-Demo). - -#### Model Classes and Mapping Configurations - -Create a folder named **Models** and add a class named `CategoryViewModel` inside of it. We will use this view modal class to determine which categories are selected or not in our Create/Edit modals. - -* **CategoryViewModel.cs** - -```csharp -using System; -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; - -namespace BookStore.Web.Models -{ - public class CategoryViewModel - { - [HiddenInput] - public Guid Id { get; set; } - - public bool IsSelected { get; set; } - - [Required] - [HiddenInput] - public string Name { get; set; } - } -} -``` - -Then, we can open the `BookStoreWebAutoMapperProfile` class and define the required mappings as follows: - -```csharp -using AutoMapper; -using BookStore.Authors; -using BookStore.Books; -using BookStore.Categories; -using BookStore.Web.Models; -using BookStore.Web.Pages.Books; -using Volo.Abp.AutoMapper; - -namespace BookStore.Web -{ - public class BookStoreWebAutoMapperProfile : Profile - { - public BookStoreWebAutoMapperProfile() - { - CreateMap() - .Ignore(x => x.IsSelected); - - CreateMap(); - - CreateMap(); - - CreateMap(); - } - } -} -``` - -#### Create/Edit Modals - -After creating our index page for Books and configuring mappings, let's continue with creating the Create/Edit modals for Books. - -Create a razor page named **CreateModal.cshtml** (and **CreateModal.cshtml.cs**). - -* **CreateModal.cshtml** - -```html -@page -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model BookStore.Web.Pages.Books.CreateModal - -@{ - Layout = null; -} - -
    - - - - - -
    - - - - - - -
    -
    - -
    - @for (var i = 0; i < Model.Categories.Count; i++) - { - var category = Model.Categories[i]; - - - } -
    -
    -
    -
    - -
    -
    -``` - -* **CreateModal.cshtml.cs** - -```csharp -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using BookStore.Books; -using BookStore.Categories; -using BookStore.Web.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; - -namespace BookStore.Web.Pages.Books -{ - public class CreateModal : BookStorePageModel - { - [BindProperty] - public CreateUpdateBookDto Book { get; set; } - - [BindProperty] - public List Categories { get; set; } - - public List AuthorList { get; set; } - - private readonly IBookAppService _bookAppService; - - public CreateModal(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnGetAsync() - { - Book = new CreateUpdateBookDto(); - - //Get all authors and fill the select list - var authorLookup = await _bookAppService.GetAuthorLookupAsync(); - AuthorList = authorLookup.Items - .Select(x => new SelectListItem(x.Name, x.Id.ToString())) - .ToList(); - - //Get all categories - var categoryLookupDto = await _bookAppService.GetCategoryLookupAsync(); - Categories = ObjectMapper.Map, List>(categoryLookupDto.Items.ToList()); - } - - public async Task OnPostAsync() - { - ValidateModel(); - - var selectedCategories = Categories.Where(x => x.IsSelected).ToList(); - if (selectedCategories.Any()) - { - var categoryNames = selectedCategories.Select(x => x.Name).ToArray(); - Book.CategoryNames = categoryNames; - } - - await _bookAppService.CreateAsync(Book); - return NoContent(); - } - } -} -``` - -Here, we've got all categories and authors inside of the `OnGetAsync` method. And use them inside of the create modal to list them so the user can choose when creating a new book. - -![Create Book Modal](./book-creation-modal.png) - -* When the user submits the form, the `OnPostAsync` method runs. Inside of this method, we get the selected categories and pass them into the **CategoryNames** array of the Book object and call the `IBookAppService.CreateAsync` method to create a new book. - -Create a razor page named **EditModal.cshtml** (and **EditModal.cshtml.cs**). - -* **EditModal.cshtml** - -```html -@page -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model BookStore.Web.Pages.Books.EditModal - -@{ - Layout = null; -} - -
    - - - - - -
    - - - - - - - -
    -
    - -
    - @for (var i = 0; i < Model.Categories.Count; i++) - { - var category = Model.Categories[i]; - - - } -
    -
    -
    -
    - -
    -
    -``` - -* **EditModal.cshtml.cs** - -```csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using BookStore.Books; -using BookStore.Categories; -using BookStore.Web.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; - -namespace BookStore.Web.Pages.Books -{ - public class EditModal : BookStorePageModel - { - [HiddenInput] - [BindProperty(SupportsGet = true)] - public Guid Id { get; set; } - - [BindProperty] - public CreateUpdateBookDto EditingBook { get; set; } - - [BindProperty] - public List Categories { get; set; } - - public List AuthorList { get; set; } - - private readonly IBookAppService _bookAppService; - - public EditModal(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnGetAsync() - { - var bookDto = await _bookAppService.GetAsync(Id); - EditingBook = ObjectMapper.Map(bookDto); - - //get all authors - var authorLookup = await _bookAppService.GetAuthorLookupAsync(); - AuthorList = authorLookup.Items - .Select(x => new SelectListItem(x.Name, x.Id.ToString())) - .ToList(); - - //get all categories - var categoryLookupDto = await _bookAppService.GetCategoryLookupAsync(); - Categories = ObjectMapper.Map, List>(categoryLookupDto.Items.ToList()); - - //mark as Selected for Categories in the book - if (EditingBook.CategoryNames != null && EditingBook.CategoryNames.Any()) - { - Categories - .Where(x => EditingBook.CategoryNames.Contains(x.Name)) - .ToList() - .ForEach(x => x.IsSelected = true); - } - } - - public async Task OnPostAsync() - { - ValidateModel(); - - var selectedCategories = Categories.Where(x => x.IsSelected).ToList(); - if (selectedCategories.Any()) - { - var categoryNames = selectedCategories.Select(x => x.Name).ToArray(); - EditingBook.CategoryNames = categoryNames; - } - - await _bookAppService.UpdateAsync(Id, EditingBook); - return NoContent(); - } - } -} -``` - -* As in the `CreateModal.cshtml.cs`, we've got all categories and authors inside of the `OnGetAsync` method. And also we get the book by id and mark the selected categories properties' as `IsSelected = true`. - -* When the user updates the inputs and submits the form, the `OnPostAsync` method runs. Inside of this method, we get the selected categories and pass them into the **CategoryNames** array of the Book object and call the `IBookAppService.UpdateAsync` method to update the book. - -![Edit Book Modal](./book-update-modal.png) - -### Conclusion - -In this article, I've tried to explain how to create a many-to-many relationship by using the ABP framework. (by following DDD principles) - -Thanks for reading this article, I hope it was helpful. diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-contracts-folder-structure.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-contracts-folder-structure.png deleted file mode 100644 index a50563fbe1..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-contracts-folder-structure.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-final-demo.gif b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-final-demo.gif deleted file mode 100644 index 61289a8ffb..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-final-demo.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-folder-structure.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-folder-structure.png deleted file mode 100644 index f49369e90a..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/application-folder-structure.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-create.gif b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-create.gif deleted file mode 100644 index 0e534802f2..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-create.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-creation-modal.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-creation-modal.png deleted file mode 100644 index 39b6ddeb6c..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-creation-modal.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-update-modal.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-update-modal.png deleted file mode 100644 index a2033f3163..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/book-update-modal.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/demo.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/demo.png deleted file mode 100644 index 031a9e93cf..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/demo.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-file-structure.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-file-structure.png deleted file mode 100644 index e6c3388138..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-file-structure.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-shared-file-structure.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-shared-file-structure.png deleted file mode 100644 index 4c0417889e..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-shared-file-structure.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/er-diagram.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/er-diagram.png deleted file mode 100644 index 024a7dff02..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/er-diagram.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/web-folder-structure.png b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/web-folder-structure.png deleted file mode 100644 index 1e7a06eb11..0000000000 Binary files a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/web-folder-structure.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/Integrating-DevExpress-Reporting-To-ABP-MVC-Application.md b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/Integrating-DevExpress-Reporting-To-ABP-MVC-Application.md deleted file mode 100644 index 372a234389..0000000000 --- a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/Integrating-DevExpress-Reporting-To-ABP-MVC-Application.md +++ /dev/null @@ -1,717 +0,0 @@ -# Integrating DevExpress ASP.NET Core Reporting to ABP MVC Application - -In this step by step article, I will demonstrate how to integrate DevExpress Reporting [DocumentViewer](https://docs.devexpress.com/XtraReports/400248/web-reporting/asp-net-core-reporting/document-viewer-in-asp-net-core-applications) and [End-User Report Designer](https://docs.devexpress.com/XtraReports/400249/web-reporting/asp-net-core-reporting/end-user-report-designer-in-asp-net-core-applications) components to an existing ABP application. We will also create core bundling packages for styling, DevExpress javascript components and also DocumentViewer specific bundling packages. - -## Create the Project (Optional) - -ABP Framework offers startup templates to get to business faster. We can download a new startup template using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -````bash -abp new Acme.BookStore -```` - -After the download is finished, open the solution in Visual Studio (or your favorite IDE): - -![initial-project](initial-project.png) - -Run the `Acme.BookStore.DbMigrator` application to create the database and seed the initial data (which creates the admin user, admin role, related permissions, etc). Then we can run the `Acme.BookStore.Web` project to see our application working. - -> _Default admin username is **admin** and password is **1q2w3E\***_ - -## Installing Packages - -You can also follow [this documentation](https://js.devexpress.com/Documentation/17_1/Guide/ASP.NET_MVC_Controls/Prerequisites_and_Installation/) to install DevExpress packages into your computer. We will be installing both nuget references and npm references. - -> Don't forget to add _"DevExpress NuGet Feed"_ to your **Nuget Package Sources**. - -### Adding NuGet Packages - -- Add the `` NuGet package reference to the **Acme.BookStore.Application.Contracts.csproj**. Or run - - ```csharp - Install-Package DevExtreme.AspNet.Core - ``` - - command under **Acme.BookStore.Application.Contracts** project. - -- Add the `` NuGet package reference to the **Acme.BookStore.Web.csproj**. Or run - - ```csharp - Install-Package DevExpress.AspNetCore.Reporting - ``` - - command under the **Acme.BookStore.Web** project. **Note:** You need a commercial license to use this package. - -### Adding NPM Packages - -Open your `Acme.BookStore.Web` project folder with a command line. Add **devextreme** and [other required third party NPM packages](https://docs.devexpress.com/XtraReports/401763/web-reporting/asp-net-core-reporting/end-user-report-designer-in-asp-net-applications/quick-start/add-an-end-user-report-designer-to-an-aspnet-core-application): - -- `npm install devexpress-reporting @devexpress/analytics-core devextreme` -- `npm install globalize jquery-ui-dist cldrjs` - -At the end, your `package.json` should look as shown below: - -```json -{ - "version": "1.0.0", - "name": "my-app", - "private": true, - "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^5.0.0-rc.1", - "@devexpress/analytics-core": "^21.2.3", - "cldrjs": "^0.5.5", - "devexpress-reporting": "^21.2.3", - "devextreme": "^21.2.3", - "globalize": "^1.7.0", - "jquery-ui-dist": "^1.12.1" - } -} -``` - -### Adding Resource Mappings - -The `devextreme` and third party NPM packages will be saved under the `node_modules` folder. We need to move the needed files in our `wwwroot/libs` folder to use them in the web project. We can do it using the ABP [client side resource mapping](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Client-Side-Package-Management) system. - -Open the `abp.resourcemapping.js` file in your `Acme.BookStore.Web` project and add definitions inside the `mappings` object so the final `abp.resourcemapping.js` file can look as shown below: - -```javascript -module.exports = { - aliases: { - - }, - mappings: { - "@node_modules/devextreme/dist/**/*": "@libs/devextreme/", - "@node_modules/jquery-ui-dist/jquery-ui.min.js": "@libs/devextreme/js/", - "@node_modules/@devexpress/analytics-core/dist/**/*": "@libs/devexpress-analytics-core/", - "@node_modules/devexpress-reporting/dist/**/*": "@libs/devexpress-reporting/", - "@node_modules/knockout/build/output/knockout-latest.js":"@libs/devexpress-reporting/js/", - "@node_modules/cldrjs/dist/cldr.js":"@libs/devexpress-reporting/cldrjs/", - "@node_modules/cldrjs/dist/cldr/event.js":"@libs/devexpress-reporting/cldrjs/", - "@node_modules/cldrjs/dist/cldr/supplemental.js":"@libs/devexpress-reporting/cldrjs/", - "@node_modules/cldrjs/dist/cldr/unresolved.js":"@libs/devexpress-reporting/cldrjs/", - "@node_modules/globalize/dist/globalize.js":"@libs/devexpress-reporting/globalize/", - "@node_modules/globalize/dist/globalize/message.js":"@libs/devexpress-reporting/globalize/", - "@node_modules/globalize/dist/globalize/number.js":"@libs/devexpress-reporting/globalize/", - "@node_modules/globalize/dist/globalize/currency.js":"@libs/devexpress-reporting/globalize/", - "@node_modules/globalize/dist/globalize/date.js":"@libs/devexpress-reporting/globalize/", - "@node_modules/ace-builds/src-min-noconflict/ace.js":"@libs/devexpress-reporting/ace-builds/", - "@node_modules/ace-builds/src-min-noconflict/ext-language_tools.js":"@libs/devexpress-reporting/ace-builds/", - "@node_modules/ace-builds/src-min-noconflict/theme-dreamweaver.js":"@libs/devexpress-reporting/ace-builds/", - "@node_modules/ace-builds/src-min-noconflict/theme-ambiance.js":"@libs/devexpress-reporting/ace-builds/", - "@node_modules/ace-builds/src-min-noconflict/snippets/text.js":"@libs/devexpress-reporting/ace-builds/" - } -}; -``` - -Open your `Acme.BookStore.Web` project folder with a command line and run the `abp install-libs` command. This command will copy the needed library files into the `/wwwroot/libs/devextreme/` folder. - -````bash -abp install-libs -```` - -You can see `devextreme`, `devexpress-reporting` and `devexpress-analytics-core` folders inside the `wwwroot/libs`: - -![wwwroot-lib](wwwroot-lib.png) - - - -## Bundling - -We will create [bundle contributors](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification) to add the devextreme css and javascript files for DevExpress [DocumentViewer](https://docs.devexpress.com/XtraReports/400248/web-reporting/asp-net-core-reporting/document-viewer-in-asp-net-core-applications) and [End-User Report Designer](https://docs.devexpress.com/XtraReports/400249/web-reporting/asp-net-core-reporting/end-user-report-designer-in-asp-net-core-applications) components. - -To utilize bundling better, we will create hierarchical contributors for both styles and scripts as seen below: - -![bundle-hierarchy](bundle-hierarchy.png) - -It is recommended to follow this hierarchy because some of the scripts and styles need to be added before one another. Once you grab the relations between the dependent scripts and styles, you are free to modify the `Bundling hierarchy` as you desire and re-use the contributors for other DevExpress components. - -We'll start with creating folders in the `Acme.BookStore.Web` project starting with a folder named **Bundling**. Create the following folders for future usage: - -- Create a `Common` folder under `Bundling` folder -- Create a `Reporting` folder under `Bundling` folder -- Create a `ThirdParty` folder under `Reporting ` folder -- Create a `DocumentViewer` folder under `Reporting ` folder -- Create a `DocumentDesigner` folder under `Reporting ` folder - -At the end, you should have a folder structure as shown below: - -![bundling-folders](bundling-folders.png) - -With this structured foldering, you can easily add other devexpress components in an isolated and maintained way. - -### Adding the Common Style Contributor - -Create a `DevExtremeCommonStyleContributor.cs` file under the `Bundling/Common` folder with the following content: - -```csharp -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace Acme.BookStore.Web.Bundling.Common; - -public class DevExtremeCommonStyleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devextreme/css/dx.common.css"); - context.Files.AddIfNotContains("/libs/devextreme/css/dx.light.css"); - } -} -``` - -> You can choose other themes than the light theme. Check the `/libs/devextreme/css/` folder and the DevExtreme documentation for other themes. - -Now we will add the common bundle to the global style bundles. Open your `Acme.BookStoreWebModule.cs` file in your `Acme.BookStore.Web` project and add the following code into the `ConfigureServices` method: - -```csharp -Configure(options => -{ - options - .StyleBundles - .Get(StandardBundles.Styles.Global) - .AddContributors(typeof(DevExtremeCommonStyleContributor)); -}); -``` - -### Adding the Document Viewer Style Contributor - -Create a `DevExpressDocumentViewerStyleContributor.cs` file under the `Bundling/Reporting/DocumentViewer` folder with the following content: - -```csharp -using System.Collections.Generic; -using Acme.BookStore.Web.Bundling.Common; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.Modularity; - -namespace Acme.BookStore.Web.Bundling.Reporting.DocumentViewer; - -[DependsOn( - typeof(DevExtremeCommonStyleContributor) -)] -public class DevExpressDocumentViewerStyleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devexpress-reporting/css/dx-reporting-skeleton-screen.css"); - context.Files.AddIfNotContains("/libs/devexpress-analytics-core/css/dx-analytics.common.css"); - context.Files.AddIfNotContains("/libs/devexpress-analytics-core/css/dx-analytics.light.css"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/css/dx-webdocumentviewer.css"); - } -} -``` - -### Adding the Document Designer Style Contributor - -Create a `DevExtremeDocumentDesignerStyleContributor.cs` file under the `Bundling/Reporting/DocumentDesigner` folder with the following content: - -```csharp -using System.Collections.Generic; -using Acme.BookStore.Web.Bundling.Reporting.DocumentViewer; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.Modularity; - -namespace Acme.BookStore.Web.Bundling.Reporting.DocumentDesigner; - -[DependsOn( - typeof(DevExtremeDocumentViewerStyleContributor) -)] -public class DevExtremeDocumentDesignerStyleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devexpress-analytics-core/css/dx-querybuilder.css"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/css/dx-reportdesigner.css"); - } -} -``` - -### Adding the Reporting Common Script Contributor - -Create a `DevExtremeReportingCommonScriptContributor.cs` file under the `Bundling/Common` folder with the following content: - -```csharp -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; -using Volo.Abp.Modularity; - -namespace Acme.BookStore.Web.Bundling.Common; - -[DependsOn( - typeof(JQueryScriptContributor) -)] -public class DevExtremeReportingCommonScriptContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devextreme/js/jquery-ui.min.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/js/knockout-latest.js"); - context.Files.AddIfNotContains("/libs/devextreme/js/dx.all.js"); // Has to be added after jquery and knockout - } -} -``` - -The `DevExtremeReportingCommonScriptContributor` depends on the `JQueryScriptContributor` which adds JQuery related files before the DevExpress packages (see the [bundling system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification) for details). - -> Since not all of the components of DevExtreme use scripts like `knockout.js` and `jquery-ui.js`, we used *ReportingCommon* naming. You can choose another name or a *Common* contributor by only adding `dx.all.js`. - -### Adding the Third Party Script Contributor - -Create a `DevExpressReportingThirdPartyScriptContributor.cs` file under the `Bundling/Reporting/ThirdParty` folder with the following content: - -```csharp -using System.Collections.Generic; -using Acme.BookStore.Web.Bundling.Common; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.Modularity; - -namespace Acme.BookStore.Web.Bundling.Reporting.ThirdParty; - -[DependsOn( - typeof(DevExtremeReportingCommonScriptContributor) -)] -public class DevExpressReportingThirdPartyScriptContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - // cldrj related scripts - context.Files.AddIfNotContains("/libs/devexpress-reporting/cldrjs/cldr.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/cldrjs/event.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/cldrjs/supplemental.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/cldrjs/unresolved.js"); - // globalize related scripts - context.Files.AddIfNotContains("/libs/devexpress-reporting/globalize/globalize.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/globalize/currency.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/globalize/date.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/globalize/message.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/globalize/number.js"); - // ace-builds related scripts - context.Files.AddIfNotContains("/libs/devexpress-reporting/ace-builds/ace.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/ace-builds/ext-language_tools.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/ace-builds/theme-ambiance.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/ace-builds/theme-dreamweaver.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/ace-builds/text.js"); - } -} -``` - -The `DevExpressReportingThirdPartyScriptContributor` depends on the newly created `DevExtremeReportingCommonScriptContributor` which adds the specified scripts before third party scripts. - -### Adding the Document Viewer Script Contributor - -Create a `DevExpressDocumentViewerScriptContributor.cs` file under the `Bundling/Reporting/DocumentViewer` folder with the following content: - -```csharp -using System.Collections.Generic; -using Acme.BookStore.Web.Bundling.Reporting.ThirdParty; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.Modularity; - -namespace Acme.BookStore.Web.Bundling.Reporting.DocumentViewer; - -[DependsOn(typeof(DevExpressReportingThirdPartyScriptContributor))] -public class DevExpressDocumentViewerScriptContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devexpress-analytics-core/js/dx-analytics-core.min.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/js/dx-webdocumentviewer.min.js"); - } -} -``` - -Note that the `DevExpressDocumentViewerScriptContributor` depends on the newly created `DevExpressReportingThirdPartyScriptContributor`. - -### Adding the Document Designer Script Contributor - -```csharp -using System.Collections.Generic; -using Acme.BookStore.Web.Bundling.Reporting.DocumentViewer; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.Modularity; - -namespace Acme.BookStore.Web.Bundling.Reporting.DocumentDesigner; - -[DependsOn( - typeof(DevExpressDocumentViewerScriptContributor) -)] -public class DevExpressDocumentDesignerScriptContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/devexpress-analytics-core/js/dx-querybuilder.min.js"); - context.Files.AddIfNotContains("/libs/devexpress-reporting/js/dx-reportdesigner.min.js"); - } -} -``` - -## End User Report Designer Integration - -> You are free to create your desired pages and rooting. This sample will be creating a **Designer** page to demonstrate the layout for the DevExpress reporting components individually. - -Create a `Reporting` folder under `Pages` in the `Acme.BookStore.Web` project . - -### Adding a Document Designer Page - -Create a `Designer.cshtml` Razor page under the `Pages/Reporting` folder and add the following: - -```csharp -@page -@using Acme.BookStore.Web.Bundling.Reporting.DocumentDesigner -@using DevExpress.AspNetCore -@model Acme.BookStore.Web.Pages.Reporting.Designer - -@{ - var designerRender = Html.DevExpress().ReportDesigner("reportDesigner") - .Height("1000px"); - @designerRender.RenderHtml() -} - -@section Scripts { - - - - @designerRender.RenderScripts() -} -``` - -### Adding Controllers - -Create a `Controllers` folder and add a `ReportingController.cs` file to contain customized controllers for reporting endpoints: - -```csharp -using DevExpress.AspNetCore.Reporting.QueryBuilder; -using DevExpress.AspNetCore.Reporting.QueryBuilder.Native.Services; -using DevExpress.AspNetCore.Reporting.ReportDesigner; -using DevExpress.AspNetCore.Reporting.ReportDesigner.Native.Services; -using DevExpress.AspNetCore.Reporting.WebDocumentViewer; -using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services; - -namespace Acme.BookStore.Web.Controllers; - -public class CustomWebDocumentViewerController : WebDocumentViewerController -{ - public CustomWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService) - : base(controllerService) - { - } -} - -public class CustomReportDesignerController : ReportDesignerController -{ - public CustomReportDesignerController(IReportDesignerMvcControllerService controllerService) - : base(controllerService) - { - } -} - -public class CustomQueryBuilderController : QueryBuilderController -{ - public CustomQueryBuilderController(IQueryBuilderMvcControllerService controllerService) - : base(controllerService) - { - } -} -``` - -### Service Configuration and Midware Update - -Update the `ConfigureServices` method of the `BookStoreWebModule.cs` file in **Acme.BookStore.Web** project with the following: - -```csharp -context.Services.AddDevExpressControls(); -context.Services.AddTransient(); -context.Services.AddTransient(); -context.Services.AddTransient(); -// UnComment this line if you want to use report storage which is used for menus like "Save as" etc. -//context.Services.AddScoped(); -``` - -Add `UseDevExpressControls` before `UseConfiguredEndpoints` in `OnApplicationInitialization` as the following: - -```csharp -... -app.UseDevExpressControls(); - -app.UseCorrelationId(); -... -``` - -### Adding Custom Report Storage (Optional) - -Create `CustomReportStorageWebExtension.cs` under the **Acme.BookStore.Web** project as below: - -```csharp -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using DevExpress.XtraReports.UI; -using Microsoft.AspNetCore.Hosting; - -namespace Acme.BookStore.Web; - -public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension -{ - readonly string ReportDirectory; - const string FileExtension = ".repx"; - - public CustomReportStorageWebExtension(IWebHostEnvironment env) - { - ReportDirectory = Path.Combine(env.ContentRootPath, "Reports"); - if (!Directory.Exists(ReportDirectory)) - { - Directory.CreateDirectory(ReportDirectory); - } - } - - public override bool CanSetData(string url) - { - return true; - } - - public override bool IsValidUrl(string url) - { - return true; - } - - public override byte[] GetData(string url) - { - try - { - if (Directory.EnumerateFiles(ReportDirectory).Select(Path.GetFileNameWithoutExtension).Contains(url)) - { - return File.ReadAllBytes(Path.Combine(ReportDirectory, url + FileExtension)); - } - - if (ReportsFactory.Reports.ContainsKey(url)) - { - using (MemoryStream ms = new MemoryStream()) - { - ReportsFactory.Reports[url]().SaveLayoutToXml(ms); - return ms.ToArray(); - } - } - - throw new DevExpress.XtraReports.Web.ClientControls.FaultException( - string.Format("Could not find report '{0}'.", url)); - } - catch (Exception) - { - throw new DevExpress.XtraReports.Web.ClientControls.FaultException( - string.Format("Could not find report '{0}'.", url)); - } - } - - public override Dictionary GetUrls() - { - return Directory.GetFiles(ReportDirectory, "*" + FileExtension) - .Select(Path.GetFileNameWithoutExtension) - .Concat(ReportsFactory.Reports.Select(x => x.Key)) - .ToDictionary(x => x); - } - - public override void SetData(XtraReport report, string url) - { - report.SaveLayoutToXml(Path.Combine(ReportDirectory, url + FileExtension)); - } - - public override string SetNewData(XtraReport report, string defaultUrl) - { - SetData(report, defaultUrl); - return defaultUrl; - } -} - -public class ReportsFactory -{ - public static Dictionary> Reports { get; set; } -} -``` - -> You can check more about it at [DevExpress Report Storage documentation](https://docs.devexpress.com/XtraReports/400211/web-reporting/asp-net-core-reporting/end-user-report-designer-in-asp-net-applications/add-a-report-storage). - -### Adding a DataSource To Designer - -Update `Designer.cshtml.cs` under `Pages/Reporting` in the **Acme.BookStore.Web** project with the following: - -```csharp -using System; -using System.Collections.Generic; -using DevExpress.DataAccess.ConnectionParameters; -using DevExpress.DataAccess.Json; -using DevExpress.DataAccess.Sql; -using DevExpress.XtraReports.UI; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Acme.BookStore.Web.Pages.Reporting; - -public class Designer : PageModel -{ - public ReportDesignerModel DesignerModel { get; set; } - - public void OnGet() - { - // Create a SQL data source. - // MsSqlConnectionParameters parameters = new MsSqlConnectionParameters("localhost", - // "dbName", "userName", "password", MsSqlAuthorizationType.SqlServer); - // SqlDataSource dataSource = new SqlDataSource(parameters); - // SelectQuery query = SelectQueryFluentBuilder.AddTable("Products").SelectAllColumnsFromTable().Build("Products"); - // dataSource.Queries.Add(query); - // dataSource.RebuildResultSchema(); - - // Create a JSON data source. - JsonDataSource jsonDataSource = new JsonDataSource(); - jsonDataSource.JsonSource = new UriJsonSource(new Uri("https://raw.githubusercontent.com/DevExpress-Examples/DataSources/master/JSON/customers.json")); - jsonDataSource.Fill(); - - - DesignerModel = new ReportDesignerModel - { - Report = new XtraReport(), - DataSources = new Dictionary() - }; - // DesignerModel.DataSources.Add("BookStoreDb", dataSource); - DesignerModel.DataSources.Add("JsonDataSource", jsonDataSource); - } - - public class ReportDesignerModel - { - public XtraReport Report { get; set; } - public Dictionary DataSources { get; set; } - } -} -``` - -This will allow binding the `data-source` for your report designer. Also update `Designer.cshtml` to use these data sources: - -```csharp -@{ - var designerRender = Html.DevExpress().ReportDesigner("reportDesigner") - .Height("1000px") - .Bind(Model.DesignerModel.Report) - .DataSources(configureDS => - { - foreach (var ds in Model.DesignerModel.DataSources) - { - configureDS.Add(ds.Key, ds.Value); - } - }); - @designerRender.RenderHtml() -} -``` - -You can now see the **Add New DataSource** icon in the Field List: -![data-source](data-source.png) - -> You can check [DevExpress Reporting Use Data Sources and Connections documentation](https://docs.devexpress.com/XtraReports/401896/web-reporting/asp-net-core-reporting/end-user-report-designer-in-asp-net-applications/use-data-sources-and-connections) for more information. - -## Document Viewer Integration - -> You are free to create your desired pages and rooting. This sample will be creating a **Viewer** page to demonstrate the layout for the DevExpress reporting components individually. - -Since End User Report Designer already implements the required controllers for features, you should only add a new page for document viewing. - -### Adding a Document Viewer Page - -Create a `Viewer.cshtml` Razor page under the `Pages/Reporting` folder and add the following: - -```csharp -@page -@using Acme.BookStore.Web.Bundling.Reporting.DocumentViewer -@using DevExpress.AspNetCore -@model Acme.BookStore.Web.Pages.Reporting.Viewer - -@{ - var viewerRender = Html.DevExpress().WebDocumentViewer("DocumentViewer") - .Height("1000px") - .Bind("TestReport"); - @viewerRender.RenderHtml() -} - -@section Scripts { - - - - @viewerRender.RenderScripts() -} -``` - -> Note: `TestReport.repx` file must be found under `Reports` folder in **Acme.BookStore.Web** project. - -### Adding the Antiforgery Token or Passing Bearer Token - -While exporting data, you may come across **HTTP 400** error which logs as `[ERR] The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken".` Or you may want to pass **Bearer Token**. - -To add these functionalities, update the `Viewer.cshtml` file under the `Pages/Reporting` folder in the **Acme.BookStore.Web** project as shown below: - -```csharp -@page -@using Acme.BookStore.Web.Bundling.Reporting.DocumentViewer -@using DevExpress.AspNetCore -@model Acme.BookStore.Web.Pages.Reporting.Viewer -@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf - -@functions{ - public string GetAntiXsrfRequestToken() - { - return Xsrf.GetAndStoreTokens(HttpContext).RequestToken; - } -} - - - - - -@{ - var viewerRender = Html.DevExpress().WebDocumentViewer("DocumentViewer") - .ClientSideEvents(x => - { - x.BeforeRender("WebDocumentViewer_BeforeRender"); - x.OnExport("OnViewerExport"); - }) - .Height("1000px") - .Bind("CustomerReport"); - @viewerRender.RenderHtml() -} - -@section Scripts { - - - - @viewerRender.RenderScripts() -} -``` - -> You can also add similar configuration to Report Designer for exporting in Preview Mode. - -## Result - -![result](result.gif) - -## Source Code - -- You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Reports-Mvc). diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/bundle-hierarchy.png b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/bundle-hierarchy.png deleted file mode 100644 index c5f0b8477c..0000000000 Binary files a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/bundle-hierarchy.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/bundling-folders.png b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/bundling-folders.png deleted file mode 100644 index caa35dc979..0000000000 Binary files a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/bundling-folders.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/data-source.png b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/data-source.png deleted file mode 100644 index 9e6f2973a6..0000000000 Binary files a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/data-source.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/initial-project.png b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/initial-project.png deleted file mode 100644 index 0078b89523..0000000000 Binary files a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/initial-project.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/result.gif b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/result.gif deleted file mode 100644 index 50360ef5f1..0000000000 Binary files a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/result.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/wwwroot-lib.png b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/wwwroot-lib.png deleted file mode 100644 index aad31a11a1..0000000000 Binary files a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/wwwroot-lib.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md deleted file mode 100644 index 4095c62092..0000000000 --- a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md +++ /dev/null @@ -1,321 +0,0 @@ -# Integrating the Syncfusion MVC Components to the ABP MVC UI - -## Introduction - -In this article we will see how we can integrate the Syncfusion MVC Components into our ABP application. - -## Source Code - -You can find the source code of the application at https://github.com/EngincanV/ABP-Syncfusion-Components-Demo. - -## Prerequisites - -* [.NET 6](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) - - * In this article, we will create a new startup template in v5.0.0-rc.2 and if you follow this article from top to bottom and create a new startup template with me, you need to install the [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) before starting. - -**NOTE:** ABP v5.X stable version has been released. You can replace v5.0.0-rc.2 with the latest stable version in your steps. - -Also, you need to update your ABP CLI to the v5.0.0-rc.2, you can use the command below to update your CLI version: - -```bash -dotnet tool update Volo.Abp.Cli -g --version 5.0.0-rc.2 -``` - -or install it if you haven't installed it before: - -```bash -dotnet tool install Volo.Abp.Cli -g --version 5.0.0-rc.2 -``` - -## Creating the Solution - -In this article, we will create a new startup template with EF Core as a database provider and MVC for the UI framework. But if you already have a project with MVC UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. - -> If you already have a project with MVC/Razor Pages UI, you can skip this section. - -We can create a new startup template by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -```bash -abp new SyncfusionComponentsDemo -t app --preview -``` - -Our project boilerplate will be ready after the download is finished. Then, we can open the solution and start developing. - -## Starting the Development - -### Pre-requisite - -> If you've already had a license from Syncfusion, you can skip this section. - -* The first thing we need to do is create an account to be able to get a license from Syncfusion. - -* So, let's navigate to https://www.syncfusion.com/aspnet-core-ui-controls and click the "Download Free Trial" button. - -* Then fill the form and start your 30-day free trial. - -* After that, navigate to https://www.syncfusion.com/account/manage-trials/downloads to get our license key that will be used in our application. - -![](./manage-trial-1.png) - -Click the "Get License Key" link for "ASP.NET Core (Essential JS 2)". - -![](./manage-trial-2.png) - -Then a modal will be opened like in the above image, select a version and click the "Get License Key" button. - -![](./copy-license-key.png) - -Lastly, copy the generated license key value. - -In order to use the relevant components, Syncfusion needs to check this license key to know that our license is valid. - -### Configurations - -After providing a license key from Syncfusion, we can start with the configuration that needs to be done in our application. - -#### 1-) Install the Syncfusion.EJ2.AspNet.Core package - -We need to install the `Syncfusion.EJ2.AspNet.Core` Nuget package to our Web project (*.Web). - -We can install it via **Visual Studio's Nuget Package Manager**: - -![](./syncfusion-package.png) - -or via dotnet cli: - -```bash -dotnet add package Syncfusion.EJ2.AspNet.Core --version 19.3.0.57 -``` - -> In this article, I've used the package in version 19.3.0.57. - -#### 2-) Register the License Key - -* After installing the package, we need to register our license key to be able to use the Syncfusion Components. - -* To register the license key, open your web module class and update the `ConfigureServices` method as follows: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var hostingEnvironment = context.Services.GetHostingEnvironment(); - var configuration = context.Services.GetConfiguration(); - - //Register Syncfusion license - Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(licenseKey: configuration["Syncfusion:LicenseKey"].ToString()); - - ConfigureUrls(configuration); - ConfigureBundles(); - ConfigureAuthentication(context, configuration); - ConfigureAutoMapper(); - ConfigureVirtualFileSystem(hostingEnvironment); - ConfigureLocalizationServices(); - ConfigureNavigationServices(); - ConfigureAutoApiControllers(); - ConfigureSwaggerServices(context.Services); -} -``` - -Instead of writing the license key directly in here we can define it in the **appsettings.json** file and use it here by using the Configuration system of .NET. - - -* Open your **appsettings.json** file and add a new section named "Syncfusion" as below: - -```json -{ - //... - - "Syncfusion": { - "LicenseKey": "" - } -} -``` - -> Replace the ` part with your license key that we've obtained in the previous section.` - -* To be able to use the Syncfusion Components we need to define them in our **_ViewImports.cshtml** file. By doing that we can use the Syncfusion components everywhere in our application. - -* Open your **/Pages/_ViewImports.cshtml** file and add a new tag helper: - -```cshtml -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI -@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap -@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling -@addTagHelper *, Syncfusion.EJ2 //use Syncfusion components -``` - -#### 3-) Adding Syncfusion styles and scripts to our application - -Firstly, let's install the `@syncfusion/ej2` package from **npm**. - -* Open your **package.json** file and add the `@syncfusion/ej2` package with version **19.3.57**: - -```json -{ - "version": "1.0.0", - "name": "my-app", - "private": true, - "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^5.0.0-rc.2", - "@syncfusion/ej2": "^19.3.57" - } -} -``` - -* Then, open the **abp.resourcemapping.js** file and update the **mappings** section: - -```js -module.exports = { - aliases: { - - }, - mappings: { - "@node_modules/@syncfusion/ej2/dist/ej2.min.js": "@libs/syncfusion/", - "@node_modules/@syncfusion/ej2/material.css": "@libs/syncfusion/" - } -}; -``` - -> ABP copies related packages from **node_modules** folder to the **libs** folder by examining this file. You can read this [document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Client-Side-Package-Management#mapping-the-library-resources) for more info. - -* Then run the `abp install-libs` to install the dependencies and copy them into the libs folder by your mappings configuration. After running this command, in your **libs** folder it should be a folder named **syncfusion** folder. - -![](./syncfusion-libs.png) - -The last thing we need to do is, add some style and script files provided by Syncfusion, between our head-body tags. - -* We can do this by creating two view components (one for Styles and the other for Scripts). Let's do that. - -First, create a folder structure as shown below under the **Components** folder. - -![](./component-folder-structure.png) - -Then open the related files and add the following codes to each of these files. - -* **Default.cshtml** (/Components/Syncfusion/Script/Default.cshtml) - -```cshtml -@addTagHelper *, Syncfusion.EJ2 //add this line - - - - - - -``` - -* **SyncfusionScriptComponent.cs** - -```csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; - -namespace SyncfusionComponentsDemo.Web.Components.Syncfusion.Script -{ - public class SyncfusionScriptComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View("~/Components/Syncfusion/Script/Default.cshtml"); - } - } -} -``` - -* **Default.cshtml** (/Components/Syncfusion/Style/Default.cshtml) - -```cshtml - - -``` - -* SyncfusionStyleComponent.cs - -```csharp -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; - -namespace SyncfusionComponentsDemo.Web.Components.Syncfusion.Style -{ - public class SyncfusionStyleComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View("~/Components/Syncfusion/Style/Default.cshtml"); - } - } -} -``` - -After creating these two components, we can use the [**Layout Hooks**](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Layout-Hooks) system of ABP to inject these two components between head and script tags. - -To do this, open your web module class and update the `ConfigureServices` method as below: - - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var hostingEnvironment = context.Services.GetHostingEnvironment(); - var configuration = context.Services.GetConfiguration(); - - //Register Syncfusion license - var licenseKey = configuration["Syncfusion:LicenseKey"].ToString(); - Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(licenseKey: licenseKey); - - Configure(options => - { - //Now, the SyncfusionStyleComponent code will be inserted in the head of the page as the last item. - options.Add(LayoutHooks.Head.Last, typeof(SyncfusionStyleComponent)); - - //the SyncfusionScriptComponent will be inserted in the body of the page as the last item. - options.Add(LayoutHooks.Body.Last, typeof(SyncfusionScriptComponent)); - }); - - ConfigureUrls(configuration); - ConfigureBundles(); - ConfigureAuthentication(context, configuration); - ConfigureAutoMapper(); - ConfigureVirtualFileSystem(hostingEnvironment); - ConfigureLocalizationServices(); - ConfigureNavigationServices(); - ConfigureAutoApiControllers(); - ConfigureSwaggerServices(context.Services); -} -``` - -After injecting the Syncfusion style and script into our application, our configurations have been completed. We can try with a simple component to see if it works as we expected. - -* Let's try with the [Calendar](https://www.syncfusion.com/aspnet-core-ui-controls/calendar) component. Open your **Index.cshtml** file and update with the below content: - -```cshtml -@page -@using Microsoft.AspNetCore.Mvc.Localization -@using SyncfusionComponentsDemo.Localization -@using Volo.Abp.Users -@model SyncfusionComponentsDemo.Web.Pages.IndexModel - -@section styles { - -} - -@section scripts { - -} - -
    -

    Syncfusion - Calendar Component

    - -
    -``` - -* Then when we run the application, we need to see the relevant calendar component as below. - -![](./calendar-component.png) - -### Conclusion - -In this article, we've explained how to integrate the **Syncfusion Components** into our applications. After following this article, you can use the Syncfusion components in your application. - -Thanks for reading the article, I hope you've found it useful :) diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/calendar-component.png b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/calendar-component.png deleted file mode 100644 index 2f8df8b5da..0000000000 Binary files a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/calendar-component.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/component-folder-structure.png b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/component-folder-structure.png deleted file mode 100644 index 7d080f87bd..0000000000 Binary files a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/component-folder-structure.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/copy-license-key.png b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/copy-license-key.png deleted file mode 100644 index ebd206f0f2..0000000000 Binary files a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/copy-license-key.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-1.png b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-1.png deleted file mode 100644 index ed5e8b3629..0000000000 Binary files a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-1.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-2.png b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-2.png deleted file mode 100644 index fc38722380..0000000000 Binary files a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-2.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-libs.png b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-libs.png deleted file mode 100644 index f6863706c2..0000000000 Binary files a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-libs.png and /dev/null differ diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-package.png b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-package.png deleted file mode 100644 index c91da7a838..0000000000 Binary files a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-package.png and /dev/null differ diff --git a/docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md b/docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md deleted file mode 100644 index d87ee8cc48..0000000000 --- a/docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md +++ /dev/null @@ -1,123 +0,0 @@ -# How to Test Blazor Components in ABP - -## Source Code - -You can find the source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/BlazorPageUniTest). - - -In this article, I will use [bUnit](https://github.com/bUnit-dev/bUnit) for a simple test of a Blazor component. - -## Getting Started - -Use the ABP CLI to create a blazor app - -`abp new BookStore -t app -u blazor` - -Then add the `BookStore.Blazor.Tests` xunit test project to the solution, and add [bUnit](https://github.com/bUnit-dev/bUnit) package and `ProjectReference` to the test project. - -The contents of `BookStore.Blazor.Tests.csproj` -```xml - - - - net8.0 - enable - - false - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - -``` - -Create `BookStoreBlazorTestModule` that depends on `AbpAspNetCoreComponentsModule` and `BookStoreEntityFrameworkCoreTestModule`. - -```cs -[DependsOn( - typeof(AbpAspNetCoreComponentsModule), - typeof(BookStoreEntityFrameworkCoreTestModule) -)] -public class BookStoreBlazorTestModule : AbpModule -{ - -} -``` - -Create a `BookStoreBlazorTestBase` class and add the `CreateTestContext` method. The `CreateTestContext` have key code. - -It creates a `AutofacServiceProvider` and add all ABP's services to the `TestContext`. - -```cs -public abstract class BookStoreBlazorTestBase : BookStoreTestBase -{ - protected virtual TestContext CreateTestContext() - { - var testContext = new TestContext(); - var blazorise = testContext.JSInterop.SetupModule("./_content/Blazorise/utilities.js?v=1.5.1.0"); - blazorise.SetupVoid("log", _ => true); - - testContext.Services.UseServiceProviderFactory(serviceCollection => - { - foreach (var service in ServiceProvider.GetRequiredService().Services) - { - serviceCollection.Add(service); - } - var containerBuilder = new ContainerBuilder(); - containerBuilder.Populate(serviceCollection); - return new AutofacServiceProvider(containerBuilder.Build()); - }); - - testContext.Services.AddBlazorise().AddBootstrap5Providers().AddFontAwesomeIcons(); - testContext.Services.Replace(ServiceDescriptor.Transient()); - - return testContext; - } -} -``` - -Finally, we add an `Index_Tests` class to test the `Index` component. - -```cs -public class Index_Tests : BookStoreBlazorTestBase -{ -[Fact] - public void Index_Test() - { - // Arrange - var ctx = CreateTestContext(); - - // Act - var cut = ctx.RenderComponent(); - - // Assert - cut.Find(".lead").InnerHtml.Contains("Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.").ShouldBeTrue(); - cut.Find("#username").InnerHtml.Contains("Welcome admin").ShouldBeTrue(); - } -} -``` - -## Reference document - -https://github.com/bUnit-dev/bUnit - -https://docs.microsoft.com/en-us/aspnet/core/blazor/test diff --git a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md deleted file mode 100644 index a6ad7596d4..0000000000 --- a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md +++ /dev/null @@ -1,450 +0,0 @@ -# How to Hide ABP Related Endpoints on Swagger UI - -In this article, we will show how to show/hide ABP related endpoints on Swagger UI by enabling/disabling them on the Setting Management page. - -I wanted to write an article about this topic because there was a [Github issue](https://github.com/abpframework/abp/issues/3758) and when I saw it, it seemed that so many people needed to hide the ABP related endpoints since they didn't need to see them as they were developing an application, they only need to see their own endpoints most of the time. - -In the issue, there are helpful comments about hiding the ABP related endpoints such as creating a **Document Filter** or removing **Application Parts** from the application etc. - -I thought it would be better to enable/disable showing endpoints on runtime by simply selecting a checkbox on the Setting Management page and in this article I wanted to show you how, so let's dive in. - -## Source Code - -You can find the source code of the application at https://github.com/EngincanV/ABP-Hide-Swagger-Endpoint-Demo. - -## Creating the Solution - -In this article, we will create a new startup template with EF Core as a database provider and MVC for the UI framework. - -> But if you already have a project with MVC UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. - -We can create a new startup template by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): - -```bash -abp new -t app -csf -``` - -Our project boilerplate will be ready after the download is finished. Open the solution and run the `*.DbMigrator` project to seed the initial data. Then, we can run the `*.Web` project to see our application working. - -> Default credentials -> Username: admin and Password: 1q2w3E* - -## Starting the Development - -After we've run the application and signed in, we can navigate to **/swagger** to see our application's endpoints. - -![](./swagger.png) - -In our scenario, we will hide endpoints that start with "/api/abp". So let's start to do this. - -### Creating a Document Filter To Hide ABP Related Endpoints - -> In this sample project, we'll only hide our endpoints that start with the "/api/abp" prefix by defining it in our [CustomSwaggerFilter](https://github.com/EngincanV/ABP-Hide-Swagger-Endpoint-Demo/blob/main/src/SwaggerSettingsDemo.Web/Filters/CustomSwaggerFilter.cs#L28) class. If you want to hide some other endpoints, you can update the class. - -To hide/show ABP related endpoints on Swagger UI, we can create a [`DocumentFilter`](https://github.com/domaindrivendev/Swashbuckle.AspNetCore#document-filters). By creating a document filter, we can have full control over the endpoints that need to be shown. - -* Create a document filter class named `CustomSwaggerFilter`(/Filters/CustomSwaggerFilter.cs) in the `.Web` layer: - -> For now, we'll implement this class as hard-coded without checking any settings. Then we'll define a setting for this purpose, and add a new setting management section to manage it on runtime (Enable/Disable). - -```csharp -using System.Linq; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace SwaggerSettingsDemo.Web.Filters; - -public class CustomSwaggerFilter : IDocumentFilter -{ - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - //remove paths those start with /api/abp prefix - swaggerDoc.Paths - .Where(x => x.Key.ToLowerInvariant().StartsWith("/api/abp")) - .ToList() - .ForEach(x => swaggerDoc.Paths.Remove(x.Key)); - } -} -``` - -We've created a simple class that implements `IDocumentFilter.Apply` method. In that method, we simply remove paths those start with "/api/abp" prefix. You can customize this expression for your needs, I just wanted to hide endpoints those start with "/api/abp" prefix as a sample. - -* Then, we need to add this document filter as a swagger document filter. To do this we need to open the `ConfigureSwaggerServices` method in the `*WebModule.cs` class and update the content as below: - -```csharp -private void ConfigureSwaggerServices(IServiceCollection services) - { - services.AddAbpSwaggerGen( - options => - { - //... - - //add a new document filter - options.DocumentFilter(); - } - ); - } -``` - -Now we can run the application again and navigate to **/swagger** endpoint to see endpoints. - -![](./swagger-document-filter.png) - -As you can see in the above image, two endpoints are hidden now (**AbpApiDefinition** and **AbpApplicationConfiguration**). - -Let's make it more dynamic and add a setting management section to our **/Settings** page. By doing that, we can dynamically show/hide ABP-related endpoints by checking setting values and applying filtering to Swagger UI on runtime. - -### Defining Settings - -* Firstly, create a class named `SwaggerSettingConsts` (under `*.Domain.Shared` project): - -```csharp -namespace SwaggerSettingsDemo; - -public class SwaggerSettingConsts -{ - public const string HideEndpoint = "SwaggerHideEndpoint"; -} -``` - -We've created a class with a constant variable to avoid using the magic strings. This variable will be our setting name. - -ABP provides us a [Settings System](https://docs.abp.io/en/abp/latest/Settings) to easily define settings for our applications. We only need to create a class that derives from the `SettingDefinitionProvider` class, but we don't even need to do this because the ABP startup templates come with a pre-defined setting provider class. - -* So open the setting definition provider class (`SwaggerSettingsDemoSettingDefinitionProvider` in our case, it's under the /Settings folder of your domain layer) and update the class: - -```csharp -using SwaggerSettingsDemo.Localization; -using Volo.Abp.Localization; -using Volo.Abp.Settings; - -namespace SwaggerSettingsDemo.Settings; - -public class SwaggerSettingsDemoSettingDefinitionProvider : SettingDefinitionProvider -{ - public override void Define(ISettingDefinitionContext context) - { - context.Add(new SettingDefinition( - name: SwaggerSettingConsts.HideEndpoint, - defaultValue: "false", - displayName: L("SwaggerHideEndpoints"), - description: L("SwaggerHideEndpointsDescription"), - isVisibleToClients: true - ) - ); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } -} -``` - -Here we've defined a setting to use it in our application. - -> ABP automatically discovers this class and registers the setting definitions. - -### Creating Application Service Methods for Swagger Setting Management - -After defining a setting, now we can create an application service interface and add two methods to simply get or update the value of our setting. - -* Create an application service interface named `ISwaggerSettingAppService` (or any name you want): - -```csharp -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace SwaggerSettingsDemo; - -public interface ISwaggerSettingAppService : IApplicationService -{ - Task GetSettingByNameAsync(string name); - - Task UpdateSettingAsync(string name, string value); -} -``` - -* Implement the application service interface: - -```csharp -using System.Threading.Tasks; -using Volo.Abp.SettingManagement; -using Volo.Abp.Settings; - -namespace SwaggerSettingsDemo; - -public class SwaggerSettingAppService : SwaggerSettingsDemoAppService, ISwaggerSettingAppService -{ - private readonly ISettingProvider _settingProvider; - private readonly ISettingManager _settingManager; - - public SwaggerSettingAppService(ISettingProvider settingProvider, ISettingManager settingManager) - { - _settingProvider = settingProvider; - _settingManager = settingManager; - } - - public async Task GetSettingByNameAsync(string name) - { - return await _settingProvider.GetOrNullAsync(name); - } - - public async Task UpdateSettingAsync(string name, string value) - { - await _settingManager.SetGlobalAsync(name, value); - } -} -``` - -> Here, I didn't make any permission check to keep the article as short as possible. So, if you want to use it on your system, you should define permissions and make permission checks, in these application service methods. - -We've injected two interfaces for the application service implementation: `ISettingProvider` and `ISettingManager` - -> **ISettingProvider**: Used for getting the value of a setting or getting the values of all settings. It's recommended to use it to read the setting values because it implements caching. - -> **ISettingManager**: Used for getting and setting the values of the settings. - -### Creating a New Setting Management Section - -After implementing our application service, now we can add a new group to our "Setting Management UI". - -* Open the `*.Web` project and create a file named `SwaggerHideEndpointsViewComponent`(/Components/SwaggerHideEndpoints/SwaggerHideEndpointsViewComponent.cs): - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using SwaggerSettingsDemo.Web.Models; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Widgets; - -namespace SwaggerSettingsDemo.Web.Components.SwaggerHideEndpoints; - -[Widget(ScriptFiles = new []{ "/Components/SwaggerHideEndpoints/Default.js" })] -public class SwaggerHideEndpointsViewComponent : AbpViewComponent -{ - private readonly ISwaggerSettingAppService _swaggerSettingAppService; - - public SwaggerHideEndpointsViewComponent(ISwaggerSettingAppService swaggerSettingAppService) - { - _swaggerSettingAppService = swaggerSettingAppService; - } - - - public virtual async Task InvokeAsync() - { - var swaggerHideEndpointSetting = await _swaggerSettingAppService.GetSettingByNameAsync(SwaggerSettingConsts.HideEndpoint); - - return View("~/Components/SwaggerHideEndpoints/Default.cshtml", new SwaggerHideEndpointViewModel - { - HideEndpoints = !string.IsNullOrEmpty(swaggerHideEndpointSetting) && - bool.TryParse(swaggerHideEndpointSetting, out var hideEndpoints) && hideEndpoints - }); - } -} -``` - -Here we've created a simple view component that gets the current value of our setting by using the `ISwaggerSettingAppService.GetSettingByNameAsync` method and passing it to our page model. - -As you can see we've passed a modal to our page named `SwaggerHideEndpointViewModel`, but we haven't created it yet, so let's create it. - -* Create a model named `SwaggerHideEndpointViewModel`(/Models/SwaggerHideEndpointViewModel.cs) : - -```csharp -public class SwaggerHideEndpointViewModel -{ - public bool HideEndpoints { get; set; } -} -``` - -* After creating the model, now we can create the **Default.cshtml** (/Components/SwaggerHideEndpoints/Default.cshtml) file (which will render in our Setting Management page as a new group): - -```cshtml -@model SwaggerSettingsDemo.Web.Models.SwaggerHideEndpointViewModel - -
    -
    - - -
    -
    - -``` - -* Create the `Default.js` file (/Components/SwaggerHideEndpoints/Default.js): - -```js -(function ($) { - - $(function () { - - $("input[name='HideEndpoints']").change(function() { - $("#SwaggerHideEndpointsForm").submit(); - }); - - $("#SwaggerHideEndpointsForm").submit(function(e) { - e.preventDefault(); - - var form = $(this).serializeFormToObject(); - var value = form.hideEndpoints; - - swaggerSettingsDemo.swaggerSetting.updateSetting("SwaggerHideEndpoint", value) - .then(function() { - $(document).trigger("AbpSettingSaved"); - }); - }); - }); - -})(jQuery); - -``` - -After we've selected the select box which enables/disables to showing endpoints on Swagger UI, it should update the setting value by our choice (enable or disable). - -Here, when the user selects the checkbox, it will submit the form and update the setting value by our choice. - -Until now, we've defined a view component that we want to render on the Setting Management page, but we didn't add it to the UI yet. To do that, we need to add a settings group to the UI, so we need to create a class and that class should be inherited from the `ISettingPageContributor` interface and implement its' `ConfigureAsync` method. - -* Create a class named `SwaggerSettingPageContributor` (/Settings/SwaggerSettingPageContributor.cs) in the `*.Web` layer: - -```csharp -using System.Threading.Tasks; -using SwaggerSettingsDemo.Web.Components.SwaggerHideEndpoints; -using Volo.Abp.SettingManagement.Web.Pages.SettingManagement; - -namespace SwaggerSettingsDemo.Web.Settings; - -public class SwaggerSettingPageContributor : ISettingPageContributor -{ - public Task ConfigureAsync(SettingPageCreationContext context) - { - context.Groups.Add( - new SettingPageGroup( - "MySwaggerSettingWrapper", - "Swagger", - typeof(SwaggerHideEndpointsViewComponent) - ) - ); - - return Task.CompletedTask; - } - - public Task CheckPermissionsAsync(SettingPageCreationContext context) - { - //we can check a permission in here, but for now just assume the permission is granted. - return Task.FromResult(true); - } -} -``` - -To see our new setting group on the Setting Management page, we need to do one more thing. - -* So, open the `*WebModule.cs` class and configure the `SettingManagementPageOptions`: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var hostingEnvironment = context.Services.GetHostingEnvironment(); - var configuration = context.Services.GetConfiguration(); - - ConfigureUrls(configuration); - ConfigureBundles(); - ConfigureAuthentication(context, configuration); - ConfigureAutoMapper(); - ConfigureVirtualFileSystem(hostingEnvironment); - ConfigureLocalizationServices(); - ConfigureNavigationServices(); - ConfigureAutoApiControllers(); - ConfigureSwaggerServices(context.Services); - - //add the setting page contributor - Configure(options => - { - options.Contributors.Add(new SwaggerSettingPageContributor()); - }); -} -``` - -After all these steps, if we run our application and navigate to **/SettingManagement** page we need to see our setting group on this page. - -![](./swagger-settings.png) - -### Update the Document Filter - -We've already defined a document filter but it is hard-coded, in other words, it hides ABP-related endpoints in every case without checking the setting value. - -So, we need to update the `CustomSwaggerFilter` class and apply filtering only if the setting value is true. - -* To do that, update the `CustomSwaggerFilter` method as below: - -```csharp -using System.Linq; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using Volo.Abp.Threading; - -namespace SwaggerSettingsDemo.Web.Filters; - -public class CustomSwaggerFilter : IDocumentFilter -{ - private readonly ISwaggerSettingAppService _swaggerSettingAppService; - - public CustomSwaggerFilter(ISwaggerSettingAppService swaggerSettingAppService) - { - _swaggerSettingAppService = swaggerSettingAppService; - } - - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - //AsyncHelper.RunSync => runs async method as sync - var swaggerHideEndpointSetting = AsyncHelper.RunSync(() => _swaggerSettingAppService.GetSettingByNameAsync(SwaggerSettingConsts.HideEndpoint)); - - if (string.IsNullOrEmpty(swaggerHideEndpointSetting) || !bool.TryParse(swaggerHideEndpointSetting, out var hideEndpoints) || !hideEndpoints) - { - return; - } - - swaggerDoc.Paths - .Where(x => x.Key.ToLowerInvariant().StartsWith("/api/abp")) - .ToList() - .ForEach(x => swaggerDoc.Paths.Remove(x.Key)); - } -} -``` - -Here we simply need to get our setting value to see whether should we enable hiding ABP related endpoints or not. We've used the `ISwaggerSettingAppService.GetSettingByNameAsync` method to get the setting value, but as you can see we've wrapped it with the `AsyncHelper.RunSync` method because `IDocumentFilter.Apply` is not an async method so we need to run this method synchronously. - -After getting the setting value, we need to ensure that it's both a valid and true setting value, otherwise we don't need to filter the endpoints and show all of them. - -If the setting value is true, we can simply remove the paths that start with the "/api/abp" prefix as before. - -> ABP provides us a class named AsyncHelper and this class provides some helper methods to work with async methods. E.g. RunSync method in here runs the async method synchronously. - -That's it. Now we can open the Setting Management page and enable/disable the swagger option by selecting the checkbox and show/hide ABP-related endpoints on runtime. - -![](./swagger-hide-endpoints.gif) - ---- - -## July 2022 Update - -With ABP v5.2+, there is a built-in option to hide/show ABP related endpoints on runtime. To hide ABP's default endpoints, call the `HideAbpEndpoints` method in your Swagger configuration as below: - -```csharp -services.AddAbpSwaggerGen( - options => - { - //... other options - - //Hides ABP Related endpoints on Swagger UI - options.HideAbpEndpoints(); - } -) -``` - -> For more info, please see the [Swagger Integration](https://docs.abp.io/en/abp/latest/API/Swagger-Integration#hide-abp-endpoints-on-swagger-ui) docs. - ---- - -Thanks for reading. \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-document-filter.png b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-document-filter.png deleted file mode 100644 index dd0f58a5c7..0000000000 Binary files a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-document-filter.png and /dev/null differ diff --git a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-hide-endpoints.gif b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-hide-endpoints.gif deleted file mode 100644 index 3a90a384b1..0000000000 Binary files a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-hide-endpoints.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-settings.png b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-settings.png deleted file mode 100644 index 9331169235..0000000000 Binary files a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger-settings.png and /dev/null differ diff --git a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger.png b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger.png deleted file mode 100644 index 3f53cb3a7e..0000000000 Binary files a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/swagger.png and /dev/null differ diff --git a/docs/en/Community-Articles/2022-02-22-Integrating-MAUI-Client-via-using-OpenID-Connect/README.md b/docs/en/Community-Articles/2022-02-22-Integrating-MAUI-Client-via-using-OpenID-Connect/README.md deleted file mode 100644 index fa6ecdcc96..0000000000 --- a/docs/en/Community-Articles/2022-02-22-Integrating-MAUI-Client-via-using-OpenID-Connect/README.md +++ /dev/null @@ -1,601 +0,0 @@ -# Integrating MAUI Client via using OpenID Connect - This is a demonstration for connecting ABP backend from MAUI app via using openid connect. - - In this flow, a web browser will be opened when user tries to log in and user will perform login operation in the browser. Then IdentityServer will redirect user to application with login credentials (state, token etc.) will be handled by application. - -> This is by intent. The code flow does not allow the user to log in using a native view in the app. The reason being that this flow ensures that the username and password are never seen by the client (except the browser, which is part of the OS system - aka we trust it). You could enable using a native login view with the Resource Owner Password Credentials (ROPC) flow. But this is also an attack vector. Suppose someone makes a fraud duplicate of your application and tricking users into entering their credentials. The fraudulent app could store those credentials in-between. You just got to enjoy those tin-foil-hat moments when doing security. In other words, using the code flow does not give an attacker that opportunity and therefore is the recommended option for mobile clients. -> -> - [@Mark Allibone](https://mallibone.com/post/xamarin-oidc) - -By the way, my motivation for building this sample is presenting just another way for authentication. **Resource Owner Password Credentials** authentication is already provided and it's more common way to do. This is yet another way to authenticate users. - -## Source Code -You can also find source code on GitHub in ABP-Samples. -- [abpframework/abp-samples/MAUI-OpenId](https://github.com/abpframework/abp-samples/tree/master/MAUI-OpenId) - -## Creating projects -- Create an ABP project without UI - -```bash -abp new Acme.BookStore -t app --no-ui -d mongodb --no-random-ports -``` - -- Create a maui application - -```bash -mkdir maui -cd maui -dotnet new maui -n Acme.BookStore.MauiClient -``` - -There is a long way for configuring scopes and callback urls for both server and clients. We'll use [WebAuthenticator](https://docs.microsoft.com/en-us/xamarin/essentials/web-authenticator?tabs=android) to perform this operation. - -## Configuring IdentityServer - -- Go to DbMigrator folder and MAUI client in **appsettings.json**. Add following client code in **IdentityServer:Clients** path: - -```json - "BookStore_Maui": { - "ClientId": "BookStore_Maui", - "ClientSecret": "1q2w3e*", - "RootUrl": "bookstore://" - } -``` - -- Go to **IdentityServerDataSeedContributor** in Domain project under IdentityServer folder. Append following code section into **CreateClientsAsync()** method. - -```csharp -// Maui Client -var mauiClientId = configurationSection["BookStore_Maui:ClientId"]; -if (!mauiClientId.IsNullOrWhiteSpace()) -{ - var mauiRootUrl = configurationSection["BookStore_Maui:RootUrl"]; - - await CreateClientAsync( - name: mauiClientId, - scopes: commonScopes, - grantTypes: new[] { "authorization_code" }, - secret: configurationSection["BookStore_Maui:ClientSecret"]?.Sha256(), - requireClientSecret: false, - redirectUri: $"{mauiRootUrl}" - ); -} -``` - -- Run DbMigrator - -- Then run HttpApi.Host - -### Configuring NGROK -Client will check configuration from `/.well-known/openid-configuration` path and it must be a secured connection between client & server. I prefer to use ngrok to open my backend app to entire web. - -- Go to [getting started](https://dashboard.ngrok.com/get-started/setup) page of ngrok _(login or register first)_ and download the ngrok tool. - -- Don't forget to login from tool: - - ```bash - ngrok authtoken XXX - ``` - - _A sample command is being displayed at dashboard where you download ngrok from_ - -- Open your HttpApi.Host with ngrok - - ```bash - .\ngrok.exe http https://localhost:44350 - ``` - -- You'll see a generated xxx.ngrok.io url. Navigate to `/.well-known/openid-configuration` to check if it's working right. - - You should see something like that: - ![](art/openid-configuration.png) - Issuer must be your URL, not localhost! If you see still localhost, try to disable host header rewrite. - - -- Also, ValidIssuers must be defined to validate tokens. - - - Add **ValidIssuers** section to your `appsettings.json` of HttpApi.Host - - ```js - "AuthServer": { - "Authority": "https://localhost:44350", - "RequireHttpsMetadata": "false", - "SwaggerClientId": "BookStore_Swagger", - "SwaggerClientSecret": "1q2w3e*", - "ValidIssuers": [ - "https://46fd-45-156-29-175.ngrok.io" - ] - }, - ``` - - - Then define it in **ConfigureAuthentication** method in Module class - - ```csharp - private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddAuthentication() - .AddJwtBearer(options => - { - // ... - options.TokenValidationParameters.ValidIssuers = configuration.GetSection("AuthServer:ValidIssuers").Get(); - }); - } - ``` - -We're done with backend. Let's continue with MAUI app. - - -## Developing MAUI App - -Before we go, there is something to do like configuring dependency injection to get rid of unnecessary huge class coupling. - -### Configuring Dependency Injection - -- Go to **MauiApplication** class and add `MainPage` in services. - - ```csharp - public static MauiApp CreateMauiApp() - { - var builder = MauiApp.CreateBuilder(); - builder - .UseMauiApp() - .ConfigureFonts(fonts => - { - fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); - }); - - builder.Services.AddTransient(); - - return builder.Build(); - } - ``` - -- And inject MainPage from constructor in **App.xaml.cs** - - ```csharp - public App(MainPage mainPage) - { - InitializeComponent(); - - MainPage = mainPage; - } - ``` - -Now MainPage is ready for injecting dependencies to it. - - -### Configuring OIDC - -- Add `IdentityModel.OidcClient` package to project - - ```xml - - - - ``` - -- Create **WebAuthenticatorBrowser** - - ```csharp - internal class WebAuthenticatorBrowser : IBrowser - { - public async Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default) - { - try - { - WebAuthenticatorResult authResult = - await WebAuthenticator.AuthenticateAsync(new Uri(options.StartUrl), new Uri(options.EndUrl)); - var authorizeResponse = ToRawIdentityUrl(options.EndUrl, authResult); - - return new BrowserResult - { - Response = authorizeResponse - }; - } - catch (Exception ex) - { - Debug.WriteLine(ex); - return new BrowserResult() - { - ResultType = BrowserResultType.UnknownError, - Error = ex.ToString() - }; - } - } - - public string ToRawIdentityUrl(string redirectUrl, WebAuthenticatorResult result) - { - IEnumerable parameters = result.Properties.Select(pair => $"{pair.Key}={pair.Value}"); - var values = string.Join("&", parameters); - - return $"{redirectUrl}#{values}"; - } - } - ``` - -- Configure **OidcClient** in **MauiProgram** - - ```csharp - builder.Services.AddTransient(); - - builder.Services.AddTransient(sp => - new OidcClient(new OidcClientOptions - { - // Use your own ngrok url: - Authority = "https://46fd-45-156-29-175.ngrok.io", - ClientId = "BookStore_Maui", - RedirectUri = "bookstore://", - Scope = "openid email profile role BookStore", - ClientSecret = "1q2w3E*", - Browser = sp.GetRequiredService(), - }) - ); - ``` - -- Go to **MainPage.xaml**, remove everyting and add a button for login - - ```xml - - - - - - - ``` - -- Inject `NavigationManager` to `Books.razor` page. - ```csharp - @inject NavigationManager NavigationManager - ``` - -- Replace **Edit** button with a link to **UpdateBook** page. - - ```html - - ``` - - ```csharp - private void NavigateToEdit(Guid id) - { - NavigationManager.NavigateTo($"books/{id}/edit"); - } - ``` - -- Remove all methods in the `Books.razor` page except constructor. And add `GoToEditPage` as below: - - ```csharp - protected void GoToEditPage(BookDto book) - { - NavigationManager.NavigateTo($"books/{book.Id}"); - } - ``` - - ![bookstore-remove-methods](images/books-remove-methods.png) - -- Change Edit button to a link in the table. - - ```html - - ``` - - -# CreateBooks Page -Create new `CreateBook.razor` and `CreateBook.razor.cs` files in your project. - -- `CreateBook.razor` - -```html -@page "/books/new" -@attribute [Authorize(BookStorePermissions.Books.Create)] -@inherits BookStoreComponentBase - -@using Acme.BookStore.Books; -@using Acme.BookStore.Localization; -@using Acme.BookStore.Permissions; -@using Microsoft.Extensions.Localization; -@using Volo.Abp.AspNetCore.Components.Web; - -@inject IStringLocalizer L -@inject AbpBlazorMessageLocalizerHelper LH -@inject IBookAppService AppService -@inject NavigationManager NavigationManager - - - - - @L["NewBook"] - - - - - - - @L["Author"] - - - - @L["Name"] - - - - - - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - - - -``` - -- `CreateBook.razor.cs` - -```csharp -using Acme.BookStore.Books; -using Blazorise; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp; - -namespace Acme.BookStore.Blazor.Pages; - -public partial class CreateBook -{ - protected Validations CreateValidationsRef; - protected CreateUpdateBookDto NewEntity = new(); - IReadOnlyList authorList = Array.Empty(); - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - authorList = (await AppService.GetAuthorLookupAsync()).Items; - - if (!authorList.Any()) - { - throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); - } - - NewEntity.AuthorId = authorList.First().Id; - - if (CreateValidationsRef != null) - { - await CreateValidationsRef.ClearAll(); - } - } - - protected virtual async Task CreateEntityAsync() - { - try - { - var validate = true; - if (CreateValidationsRef != null) - { - validate = await CreateValidationsRef.ValidateAll(); - } - if (validate) - { - await AppService.CreateAsync(NewEntity); - NavigationManager.NavigateTo("books"); - } - } - catch (Exception ex) - { - await HandleErrorAsync(ex); - } - } -} -``` - -# EditBooks Page -Create new `EditBook.razor` and `EditBook.razor.cs` files in your project. - -- `EditBook.razor` - -```html -@page "/books/{Id}" -@attribute [Authorize(BookStorePermissions.Books.Edit)] -@inherits BookStoreComponentBase -@using Acme.BookStore.Books; -@using Acme.BookStore.Localization; -@using Acme.BookStore.Permissions; -@using Microsoft.Extensions.Localization; -@using Volo.Abp.AspNetCore.Components.Web; - -@inject IStringLocalizer L -@inject AbpBlazorMessageLocalizerHelper LH -@inject IBookAppService AppService -@inject NavigationManager NavigationManager - - - - - @EditingEntity.Name - - - - - - - @L["Author"] - - - - @L["Name"] - - - - - - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - - - -``` - -- `EditBook.razor.cs` - -```csharp -using Acme.BookStore.Books; -using Blazorise; -using Microsoft.AspNetCore.Components; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp; - -namespace Acme.BookStore.Blazor.Pages; - -public partial class EditBook -{ - protected CreateUpdateBookDto EditingEntity = new(); - protected Validations EditValidationsRef; - IReadOnlyList authorList = Array.Empty(); - - [Parameter] - public string Id { get; set; } - - public Guid EditingEntityId { get; set; } - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - // Blazor can't parse Guid as route constraint currently. - // See https://github.com/dotnet/aspnetcore/issues/19008 - EditingEntityId = Guid.Parse(Id); - - authorList = (await AppService.GetAuthorLookupAsync()).Items; - - if (!authorList.Any()) - { - throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); - } - - var entityDto = await AppService.GetAsync(EditingEntityId); - - EditingEntity = ObjectMapper.Map(entityDto); - - if (EditValidationsRef != null) - { - await EditValidationsRef.ClearAll(); - } - } - - protected virtual async Task UpdateEntityAsync() - { - try - { - var validate = true; - if (EditValidationsRef != null) - { - validate = await EditValidationsRef.ValidateAll(); - } - if (validate) - { - await AppService.UpdateAsync(EditingEntityId, EditingEntity); - - NavigationManager.NavigateTo("books"); - } - } - catch (Exception ex) - { - await HandleErrorAsync(ex); - } - } -} -``` - -You can check the following commit for details: -https://github.com/abpframework/abp-samples/commit/aae61ad6d66ebf6191dd4dcfb4e23d30bd680a4e \ No newline at end of file diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-methods.png b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-methods.png deleted file mode 100644 index 87ae87408a..0000000000 Binary files a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-methods.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png deleted file mode 100644 index 7514c463f1..0000000000 Binary files a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/new.gif b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/new.gif deleted file mode 100644 index 42784eb281..0000000000 Binary files a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/new.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/old.gif b/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/old.gif deleted file mode 100644 index 80a97a80f2..0000000000 Binary files a/docs/en/Community-Articles/2023-03-28-Converting-Create-Edit-Modal-To-Page/images/old.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md deleted file mode 100644 index 73d2e6bad5..0000000000 --- a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md +++ /dev/null @@ -1,325 +0,0 @@ -# Convert Create/Edit Modals to Page - -In this document we will explain how to convert the BookStore's Books create & edit modals to regular Angular component pages. - -## Before - -![bookstore-crud-before](images/old.gif) - -## After - -![bookstore-crud-after](images/new.gif) - -# BooksComponent - -This is the main component of the books management. The create & update operations are done in this page. So we'll remove the create & update operations from this page and move a separate angular component for each operation. Each component will be a page. - -- Remove the Create & Update modal on template - - ![remove-modal](images/books-remove-modals.png) - -- Modify the **NewBook** button with a link to the **CreateBookComponent** page. - - ```html - - ``` - -- Modify the **Edit** button with a link to the **EditBookComponent** page. - - ```html - - ``` - -- Remove all unused methods and variables in the `BookComponent` except the **book** variable, the **ngOnInit** and **delete** methods. - - - ![bookstore-remove-unsued](images/books-remove-unused.png) - -- Also we can clear unncessary imports - - ![bookstore-remove-unsued-imports](images/books-remove-unused-imports.png) - - - -# CreateBookComponent Page - -Create a new component by the name `create-book` in your project. -```bash - yarn ng g c book/create-book --skip-tests --style=none -``` - -- `create-book.component.html` - -```html -
    -
    -
    -
    - * - -
    - -
    - * - -
    - -
    - * - -
    - -
    - * - -
    - -
    - * - -
    - -
    - - - -
    -
    -
    -
    -``` - -- `create-book.component.ts` - -```ts -import { Component, inject } from '@angular/core'; -import { Router } from '@angular/router'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { map } from 'rxjs'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { BookService, bookTypeOptions } from '@proxy/books'; - -const { required } = Validators; - -@Component({ - selector: 'app-create-book', - templateUrl: './create-book.component.html', - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], -}) -export class CreateBookComponent { - //inject() function came with Angular v14, detail: https://angular.io/api/core/inject - private readonly router = inject(Router); - private readonly fb = inject(FormBuilder); - private readonly bookService = inject(BookService); - - form: FormGroup; - authors$ = this.bookService.getAuthorLookup().pipe(map(({ items }) => items)); - bookTypes = bookTypeOptions; - - private buildForm() { - this.form = this.fb.group({ - authorId: [null, required], - name: [null, required], - type: [null, required], - publishDate: [undefined, required], - price: [null, required], - }); - } - - constructor() { - this.buildForm(); - } - - save() { - if (this.form.invalid) return; - - this.bookService.create(this.form.value).subscribe(() => { - this.router.navigate(['/books']); - }); - } -} -``` - -# EditBookComponent Page - -Create a new component by the name **edit-book** in your project. - -```bash - yarn ng g c book/edit-book --skip-tests --style=none -``` - -- `edit-book.component.html` - -```html -
    -
    -
    -
    - * - -
    - -
    - * - -
    - -
    - * - -
    - -
    - * - -
    - -
    - * - -
    - -
    - - - -
    -
    -
    -
    -``` - -- `edit-book.component.ts` - -```ts -import { Component, inject } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { filter, map, switchMap, tap } from 'rxjs'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { BookDto, BookService, bookTypeOptions } from '@proxy/books'; - -const { required } = Validators; - -@Component({ - selector: 'app-edit-book', - templateUrl: './edit-book.component.html', - providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], -}) -export class EditBookComponent { - //inject() function came with Angular v14, detail: https://angular.io/api/core/inject - private readonly router = inject(Router); - private readonly activatedRoute = inject(ActivatedRoute); - private readonly fb = inject(FormBuilder); - private readonly bookService = inject(BookService); - - id: string; - form: FormGroup; - authors$ = this.bookService.getAuthorLookup().pipe(map(({ items }) => items)); - bookTypes = bookTypeOptions; - - private buildForm(book: BookDto): void { - this.form = this.fb.group({ - authorId: [book.authorId, required], - name: [book.name, required], - type: [book.type, required], - publishDate: [new Date(book.publishDate), required], - price: [book.price, required], - }); - } - - constructor() { - this.activatedRoute.params - .pipe( - filter(params => params.id), - tap(({ id }) => (this.id = id)), - switchMap(({ id }) => this.bookService.get(id)), - tap(book => this.buildForm(book)) - ) - .subscribe(); - } - - save(): void { - if (this.form.invalid) return; - - this.bookService.update(this.id, this.form.value).subscribe(() => { - this.router.navigate(['/books']); - }); - } -} -``` - -# Update BookRoutingModule -Finally add 2 items to the routes array in the `book-routing.module.ts` -```ts -import { CreateBookComponent } from './create-book/create-book.component'; -import { EditBookComponent } from './edit-book/edit-book.component'; - -const routes: Routes = [ - { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] }, - { path: 'create', component: CreateBookComponent }, - { path: 'edit/:id', component: EditBookComponent }, -]; -``` - - -You can check the following commit for more details: https://github.com/abpframework/abp-samples/commit/351ad5e093036702edbb15169968935496afea0e diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png deleted file mode 100644 index 42835630bb..0000000000 Binary files a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-modals.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-unused-imports.png b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-unused-imports.png deleted file mode 100644 index 74a75bf21a..0000000000 Binary files a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-unused-imports.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-unused.png b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-unused.png deleted file mode 100644 index f73bcfac28..0000000000 Binary files a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/books-remove-unused.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/new.gif b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/new.gif deleted file mode 100644 index a2e15e9c8d..0000000000 Binary files a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/new.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/old.gif b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/old.gif deleted file mode 100644 index 499af9f28c..0000000000 Binary files a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/images/old.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/POST.md b/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/POST.md deleted file mode 100644 index e7c540764f..0000000000 --- a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/POST.md +++ /dev/null @@ -1,36 +0,0 @@ -# Authority Delegation in ABP Commercial - -In this post, I'll explain a new feature that comes with the ABP Commercial `v7.2.0`. It's called **Authority Delegation**. - -## Authority Delegation - -Authority Delegation is a way of delegating the responsibility of the current user to a different user(s) for a limited time. Thus, the user can switch to the delegated user's account and perform actions on their behalf. - -> This feature is part of the [Account Pro module](https://commercial.abp.io/modules/Volo.Account.Pro), which is one of the application PRO modules of [ABP Commercial](https://commercial.abp.io/). - -### Delegating a new user - -After logging into the application, you can see the `Authority Delegation` menu item under the user menu. When you click the menu, a modal will open, and in the first tab of the modal, you will see the list of delegated users. - -![delegated-users](images/delegated-users.jpg) - -You can click the `Delegate New User` button to delegate a new user: - -![delegate-new-user](images/delegate-new-user.jpg) - -* You can specify a time range to ensure the delegation is only available within the time range. -* You can make multiple delegates to the same user and set different delegate time ranges. - -> The delegation has three states: `Expired`, `Active`, and `Future`. These states are set automatically by checking the specified time interval. - -### My delegated users - -A list of users who delegated me to log in on behalf of them can be seen in the figure: - -![my-delegated-users](images/my-delegated-users.jpg) - -You can click the `Login` button to log in to the application as a delegated user and go back to your account by clicking the `Back to my account` icon. - -![delegated-impersonate](images/delegated-impersonate.jpg) - -> The **Authority Delegation** feature uses the [impersonation system](https://docs.abp.io/en/commercial/latest/modules/account/impersonation) internally. diff --git a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegate-new-user.jpg b/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegate-new-user.jpg deleted file mode 100644 index 7fcedd824a..0000000000 Binary files a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegate-new-user.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegated-impersonate.jpg b/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegated-impersonate.jpg deleted file mode 100644 index ac9f26e62b..0000000000 Binary files a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegated-impersonate.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegated-users.jpg b/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegated-users.jpg deleted file mode 100644 index 199aa89ac9..0000000000 Binary files a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/delegated-users.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/my-delegated-users.jpg b/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/my-delegated-users.jpg deleted file mode 100644 index ff45f9e40e..0000000000 Binary files a/docs/en/Community-Articles/2023-05-03-Authority-Delegation-In-ABP-Commerical/images/my-delegated-users.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/01-microservice-solution.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/01-microservice-solution.png deleted file mode 100644 index 19fe694850..0000000000 Binary files a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/01-microservice-solution.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/02-run-single-service.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/02-run-single-service.png deleted file mode 100644 index 8c18599c45..0000000000 Binary files a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/02-run-single-service.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/03-tye-yaml-file.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/03-tye-yaml-file.png deleted file mode 100644 index 5b38d6fccf..0000000000 Binary files a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/03-tye-yaml-file.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/04-tye-dashboard.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/04-tye-dashboard.png deleted file mode 100644 index 49b2391897..0000000000 Binary files a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/04-tye-dashboard.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/05-example-microservice-solution.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/05-example-microservice-solution.png deleted file mode 100644 index 0b127ef725..0000000000 Binary files a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/05-example-microservice-solution.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/06-abp-studio-kubernetes-tunnel-how-it-works.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/06-abp-studio-kubernetes-tunnel-how-it-works.png deleted file mode 100644 index 97a95aa672..0000000000 Binary files a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/06-abp-studio-kubernetes-tunnel-how-it-works.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md deleted file mode 100644 index 58849266eb..0000000000 --- a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md +++ /dev/null @@ -1,226 +0,0 @@ -# Kubernetes Integrated Microservice Development with .NET and ABP Studio - -## Abstract - -**Microservice architecture** is a quite common approach to build **highly scalable** solutions with a large development team. While there are standard tools, like **Kubernetes**, to deploy, run and scale your microservices, the **development side** is not as mature as that. It is not easy to develop, run and test a single microservice that **depends on** other microservices and services. **Running** a copy of the entire system in the developer's machine is impractical. - -In this article, I will introduce an **efficient** way of creating a **development environment** that is well **integrated to Kubernetes**. In this way, you can just run the microservice you are building and let Kubernetes run all your dependencies in your local machine or a remote server. - -I will demonstrate the solution by introducing and using a brand new tool for ABP Developers: **ABP Studio**! - ->**This article is based on my *[Kubernetes Integrated Microservice Development with ABP Studio](https://www.youtube.com/watch?v=XiPRcIHJ3NE)* talk performed in [ABP Dotnet Conf'23](https://abp.io/conference). You can watch that talk if you would like watching rather than reading.** - -## Topics - -Let’s start by introducing the main topics I will cover in this article: - -* I will begin by defining the **problem**: Why it is hard to prepare a development environment for a microservice development. -* I will mention and demonstrate how **Microsoft’s Tye project** tries to solve the problem and why it falls short. -* Then I will introduce the **ABP Studio**, a brand new product for .NET developers, which has an efficient solution for **running multi-application solutions** locally. -* Finally, I will offer an efficient way of developing such complex solutions locally by **integrating Kubernetes into our development environment**, and demonstrate how ABP Studio automates it for us. - -## Difficulties of a Microservice Development Environment - -It is important to understand what is the essential problem when we try to set up a development environment for a microservice solution. To understand the main difficulty, let’s see what a microservice solution looks like: - -![example-microservice-solution](01-microservice-solution.png) - -A typical microservice solution consists of many components communicating to each other. In this example; - -* We have **two applications** for users. One for back-office admin users and one for the end-users of the system, a public website. -* These applications use dedicated **API Gateways** since we are using the Backend For Frontend pattern here. -* There is a separate **authentication service** application to login and manage user accounts. -* Behind the API gateways, we typically have many API applications, called **microservices**. Here, I show only three microservices, but there will be tens of microservices even in a medium-size system. Each of these microservices may have their own databases. -* The microservices typically **interact** with each other using REST API calls or asynchronous messaging through an event bus service, like RabbitMQ here. - -There will be much more **supporting services**, like Redis, Elasticsearch and so on. As you see, we have a lot of independent services and applications interacting with each other in a typical microservice solution. - -### How to run a single service locally? - -Generally, your responsibility is developing **one or a few of these microservices** or applications, not all of them. For example, you may be responsible for developing or testing the Product microservice: - -![run-single-service-in-a-microservice-solution](02-run-single-service.png) - -All you want to do is to write your code, then run and test your service if it is correctly working. However, to be able to **develop**, **run**, **test** or **debug** the product microservice, all of the service and infrastructure dependencies need to be running and communicating to each other. - -Okay, maybe not all, but most of them are needed. Otherwise, you can not open the application UI, login to the system, add products to basket and place an order to see if it is working properly, or you can not **live debug** your service code in case of trouble. - -So, how to make the whole system up and running easily to be able to **focus on developing**, testing and debugging the Product microservice? - -## The Project Tye - -You are not the only one getting trouble to develop, run, test and debug a microservice solution. Microsoft also thought of it and introduced a tool called the [Project Tye](https://github.com/dotnet/tye). With its own words in their GitHub repository: - -> Tye is a tool that makes developing, testing, and deploying microservices and distributed applications easier. - -Tye can **simplify microservices development** by making it easy to run many services with one command, use dependencies in containers, discover addresses of other services using simple conventions. It can also deploy .NET applications to Kubernetes by automatically creating containers for .NET applications and generating Kubernetes manifests with minimal knowledge or configuration. - -The deployment part is not mature and they have to do a lot of work. However, the solution runner part works very well. - -Tye project consists of three major components: - -* A **YAML file** to configure your services -* A **CLI tool** to run and deploy the solution -* And a **dashboard** to visualize the running services. - -### The tye.yaml file - -Tye uses a YAML file to configure your solution. It typically contains many service definitions. There are three type of services can be defined in this YAML file: - -* You can add a **.NET Project**, so Tye runs it locally with **dotnet run** -* You can add a **container definition**, and Tye runs it using **Docker** -* And an arbitrary **executable application** that can be run using a terminal command - -Here, an example content of a `tye.yaml` file: - -![tye-yaml-file](03-tye-yaml-file.png) - -### The Tye CLI - -Once you define your YAML file, Tye can run all the services with a single terminal command: **tye run**. It builds all .NET applications before running them. If you specify the **watch parameter**, it watches the changes in your .NET projects, re-builds and runs them when a change happens. There are also [other commands](https://github.com/dotnet/tye/blob/main/docs/reference/commandline/README.md) to build and deploy your solution. - -### The Tye Dashboard - -The final major component is the Tye Dashboard. With this dashboard, you can see the **list of the services** with their statuses. You can also view their **logs** and open their **UI** with a single click. - -A screenshot from the Tye Dashboard: - -![project-tye-dashboard](04-tye-dashboard.png) - -### The Example Microservice Solution - -I’ve prepared a microservice solution for demos in this article and I will use the same solution in all demos. Here, an overall diagram of the example solution: - -![example-microservice-solution](05-example-microservice-solution.png) - -We have two web applications, two API Gateways, 5 microservices. 3 of them are shown here: Product, Ordering and Identity microservices. Every microservice has its independent SQL Server database. They communicate through REST API calls and distributed events via RabbitMQ. - -### The Project Tye Demo - -You can watch my 6-minutes demo to see the Project Tye in action: - - - -[Click here to watch the demo on YouTube](https://www.youtube.com/watch?v=S0-z29lMokA) - -### Tye: Shortcomings - -The demo shows how the Tye project is useful. However, there are many missing points and problems with the current project state. Let’s talk about them; - -* The first problem is that it **starts very slow**, because every time you run it, it builds all the services. It takes more than 1 minute for a solution with only ten services. -* It currently can run everything in your **local computer**. Even if you want to develop a single service, running tens of services locally consumes your system resources. You need a lot of RAM and CPU if you want to work a large solution locally. -* The **UI part** is currently very simple. For example, if you want to debug one of your services by running it in Visual Studio, you need to stop all the services, change the YAML file to exclude that service from Tye, then run all the services again. Only after that you can run your service in Visual Studio to debug it as integrated to other services. Obviously this process is not easy and comfortable. -* We can only see the application logs in the UI. **More insights** about the internals of services would be very helpful. -* Tye currently has some **partial documents** in its GitHub repository. However, the documentation is far away from being complete. -* Finally, and most importantly, there is **no active development** on the Tye project on GitHub. It was already an experimental project with its early state, as declared in its GitHub repository. - -So, basically, it seems Microsoft has no further interest in this project and you can expect it will be retired in the near future. - -## ABP Studio - -Okay, now we came to the ABP Studio part. At [Volosoft](https://www.volosoft.com/), we were silently working on the ABP Studio project for more than one year. - -ABP Studio is a **cross-platform desktop application** for ABP developers. It is **well integrated** to the ABP Framework and aims to provide a comfortable development environment for you by **automating things**, **providing insights** about your solution, making **develop**, **run** and **deploy** your solutions much easier. - -We are planning to release a **beta version** in **Q3 of 2023**. You can expect the following features shipping with the initial release: - -* You can **create new ABP solutions** and modules easily with a lot of options. -* You can easily **install or uninstall modules** to your solution. -* You can **explore** the fundamental **structures** of your solution or used modules, like entities, repositories, application services, UI pages, HTTP APIs, database tables and much more. -* You can build **multi-module monolith applications** or distributed microservice solutions by easily adding modules and services into your solution. -* You can easily **run multiple applications** and services with a single click, just like I demonstrated with the Project Tye. However, it has much more features. -* You can run your service by **integrating** it into a **Kubernetes** cluster, so you don’t need to run all your dependencies and all other services in your local development environment. This is the essential topic of this talk and I will later demonstrate how it works. - -## ABP Studio Solution Runner - -I've prepared a demo of the ABP Studio Solution Runner, but first I want to mention the main features of the solution runner: - -* First of all, it can run one, **multiple** or **all services** with a single click. In this way, it is very easy to stop a service, run it in Visual Studio to **test** or **debug**. -* All the services are **connected to ABP Studio** and send their internal data to **visualize** on the ABP Studio UI. -* In the **overall view**, you can see a list of services, view **real-time** HTTP Request and exception counts for each service. -* You can see all details of all **HTTP requests** coming to any service. -* You can see **exception details** as real-time in any service, easily filter and search. -* Just like Tye, you can see the **application logs**. But as more, you can filter logs by log level or search in the log texts. -* Last but not least, ABP Studio has an **integrated Chrome browser** inside it. You can browse the UI of your application without leaving the solution runner. - -### ABP Studio Solution Runner Demo - -You can watch my 9-minutes demo to see ABP Studio Solution Runner in action: - - - -[Click here to watch the demo on YouTube](https://www.youtube.com/watch?v=sSCxyccoHqE) - -## ABP Studio Kubernetes Tunnel - -Finally, we came to the essential topic I want to talk about. Until that point, we had a good understanding of the problem and possible solutions. In this part, we will make a final touch to the solution to have **a great development environment for a microservice solution**. - -ABP Studio Kubernetes Tunnel System allows you to **connect your local development environment to a local or remote Kubernetes cluster**, where that cluster already runs your microservice solution. In this way, you can access any service in Kubernetes with their service name as DNS, just like they are running in your local computer. This is established with a secure VPN connection. - -Secondly, you can **intercept any service** in that cluster, so all the **traffic to the intercepted service is automatically redirected to your service** that is running in your local machine. When your service needs to use any service in Kubernetes, the traffic is redirected back to the cluster, just like your local service is running inside the Kubernetes. In this way, you don’t need to care about how all other services are configured and running. You just focus on the service you are responsible for developing, testing or debugging. You can use your favorite IDE since you are running your service in your local machine as you always do. - -The solution runner is a great way of running multiple services locally. However, if your solution consists of hundreds of services, running all them in your local machine will consume your system resources and slow down your development speed. The best news is that: You can use the **Kubernetes Tunnelling combined with all the solution runner features** to have a perfect local microservice development environment. - -### How ABP Studio Kubernetes Tunnel works - -I am sure that you want to see it in action, but before that, let me explain how the solution works. - -![abp-studio-kubernetes-tunnel-how-it-works](06-abp-studio-kubernetes-tunnel-how-it-works.png) - -* **Kubernetes cluster** is shown on the right side and your **local development machine** is shown on the left side. -* As you know, when a **user requests a web page** from your web application, the request is accepted by an **Ingress Controller** inside your Kubernetes cluster. -* The Ingress controller forwards the request to your **web application**, which then uses an **API gateway** to consume your microservices. -* Assume that we have the **Product**, **Ordering** and **Identity** microservices. -* These microservices have their own SQL Server **database** and they are also using **RabbitMQ**, **Redis** and some other **infrastructure services**. -* On the other hand, we want to develop, run or test our **Product microservice** in our **own laptop** using our favorite IDE, let’s say using Visual Studio. -* If we don’t make anything special, the **product microservice in the Kubernetes cluster** will be used by the users, as you can expect. Even if you can somehow run the product microservice in your local computer, the Kubernetes system won’t have any knowledge about it. They are in different systems. -* At this point, ABP Studio comes into play. When you **connect** to the Kubernetes cluster with ABP Studio, it first installs a **VPN server** into your cluster and a **VPN client** into your local machine. -* Then it establishes a secure **VPN tunnel** between your computer and the target Kubernetes cluster. In this way, you can access all services in Kubernetes with their internal cluster IP addresses. -* ABP Studio also installs the **ABP Studio Client Proxy Server** pod into the Kubernetes cluster, and the **ABP Studio Proxy Client** into the developer machine. It is used to collect data from the services and show them in the ABP Studio UI, like HTTP Requests, exceptions, or logs as we’ve seen in the solution runner before. -* Finally, ABP Studio also adds **DNS records to your hosts file**, so you can access to internal Kubernetes services directly with their service names, in addition to their IP addresses. - -All of these happen when you click the Connect button on the ABP Studio. Now, your computer can use the **internals of the Kubernetes cluster**. But, how the HTTP Requests coming to the Product service in the Kubernetes cluster are **redirected to your local machine**? - -* When you want to run and test a service locally, you **intercept the matched service** in the Kubernetes cluster. When you Intercept a service, ABP Studio makes some more changes in your local computer and in your Kubernetes cluster. -* As first, it installs a **pod** and a **service** into the Kubernetes cluster to intercept the requests coming to the Product microservice and **proxies the requests to** a client application in **your local computer**. -* The local client application then **forwards requests** to the Product microservice instance you are running in your local machine. In this way, whenever the Product service is used in Kubernetes, your local product service is executed. You can easily run, test or debug your service as you normally do with your standard IDE, for example with Visual Studio. -* What happens when your local product service needs to **access** the SQL Server database or RabbitMQ, or another microservice in the Kubernetes cluster? It can **directly use** them just like local services, through the **VPN tunnel** we have already created. -* ABP Studio makes a final change in your local product microservice project: It overrides all the **environment variables** with the ones obtained from the product service in the Kubernetes pod. In this way, your local product service instance feels itself in the Kubernetes cluster. Database connection strings and all other settings will be the same with the Kubernetes environment. - -Don’t worry, ABP Studio **doesn’t change your local environment variables**. The change only affects the live product service instance, and it is **reverted** when you disable the interception in ABP Studio. Also, when you click the **Disconnect** button in ABP Studio, all the changes made to your cluster and your local computer will be cleared. It won’t leave any sign in your systems. - -### ABP Studio Kubernetes Tunnel Demo - -So, now we have a fully Kubernetes integrated development environment. Let’s see how it is used in ABP Studio in the following 9-minutes demo: - - - -[Click here to watch the demo on YouTube](https://www.youtube.com/watch?v=CeUq2ysz-mQ) - -## ABP Studio Roadmap - -In the next months, the team will be working on the following items: - -* Our first goal is to **finalize and stabilize** the current features. We will also **document** how to use them. Only after that, we will open it to customers as a beta version. -* We’ve focused on the development part until now. We also want to add some **deployment** capabilities to ABP Studio in the future. -* Another goal is to make **module customizations** much easier than current by the help of ABP Studio. With ABP Studio, we have completely re-though how to discover and install modules. Based on that, we will also work on a common place to publish and consume application modules for the ABP Framework. - -We have other great plans for that product. However, I can not declare them yet as they are secrets for now :) We’ve planned to release the first beta version in **Q3 of 2023**. - -I want to notice that ABP Studio is not a new version or replacement of **ABP Suite**. We built the ABP Studio from scratch. Both products will be developed and shipped in parallel. ABP Suite will continue to focus on code generation. ABP Studio doesn’t have any code generation capabilities yet. So, you will use both of them together. - -## Summary - -Okay let’s summarize what I’ve explained in this article: - -* First, I explained **why it is hard to set up a microservice development environment**. We’ve talked about the hardness of developing, running and testing a single microservice that depends on infrastructure services and other microservices. -* I’ve introduced **Project Tye**, a project developed by Microsoft to allow you to easily run multi-application solutions. We made a demo, then discussed its missing points. -* Then I **introduced ABP Studio**, a new product by Volosoft for ABP developers. - As the first important feature, I explained the ABP Studio **Solution Runner** and demonstrated how it works and how you can develop and debug your services. -* Finally, I introduced ABP Studio’s **Kubernetes Tunneling** feature. First, I explained how it works with a complicated diagram. Then I made a short demo to see it in action. - -With that final part, we had a **great and easy to use microservice development environment**. It is great, because we can focus on only a single service, while we don’t care about all other parts of the solution. Also, we can save our local development machine system resources by delegating running the dependencies to a remote Kubernetes server. - -## See Also - -* ABP Dotnet Conf'23 Talk on YouTube: [Kubernetes Integrated Microservice Development with ABP Studio](https://www.youtube.com/watch?v=XiPRcIHJ3NE) \ No newline at end of file diff --git a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md b/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md deleted file mode 100644 index 9023417b20..0000000000 --- a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md +++ /dev/null @@ -1,293 +0,0 @@ -# Image Compression and Resize with ABP Framework - -## Introduction - -In this article, I will show how to compress and resize images easily with the ABP Framework's new [Image Manipulation System](https://docs.abp.io/en/abp/7.3/Image-Manipulation), which is introduced in v7.3.0. - -ABP Framework provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/) and [Magick.NET](https://github.com/dlemstra/Magick.NET) libraries. Currently, only these two providers are officially supported by the ABP Framework but thanks to the system being designed extensible, you can implement your own image resizer/compressor and use it in your application. - -> Refer to the documentation for more info: [Image Manipulation](https://docs.abp.io/en/abp/7.3/Image-Manipulation) - -### Source Code - -You can find the source code of the application at [https://github.com/abpframework/abp-samples/tree/master/ImageManipulation](https://github.com/abpframework/abp-samples/tree/master/ImageManipulation). Don't hesitate to check the source code, if you are stuck on any point. - -## Demo: Image Compression and Resize - -The best way to see what ABP's Image Manipulation System is capable of is to see it in action. Thus, we can create a simple application that basically allows us to upload, search and display images. - -### Creating a New ABP Solution - -> I have created an ABP solution and you can find the [full source code of the demo application here](https://github.com/abpframework/abp-samples/tree/master/ImageManipulation). If you want to create the same solution from scratch, you can apply the following steps: - -Install the ABP CLI, if you haven't installed it before: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -Create a new solution with the ABP Framework's Application Startup Template with MVC UI and EF Core database (default options): - -```bash -abp new ImageManipulationDemo -t app --version 7.3.0-rc.2 -``` - -> As I have mentioned above, ABP introduced the Image Manipulation System in v7.3.0. So, ensure your application is v7.3.0 or higher. - -After creating the application, let's create the database and seed the initial data by running the `*.DbMigrator` project. Also, you can run the application to see if it's working as expected. - -### Configuring the BLOB Storing System - -Since we are creating an image upload application, we need to store our images somewhere and read these image contents when it's needed. [BLOB Storing System](https://docs.abp.io/en/abp/latest/Blob-Storing) is a great solution to achieve this. Let's install & configure the BLOB Storing System into our application. - -First, run the following command under the directory of your `*.HttpApi` project: - -```bash -abp add-package Volo.Abp.BlobStoring -``` - -Then, we need to select and configure a storage provider to tell the BLOB Storing System where to store the file contents. There are [multiple providers](https://docs.abp.io/en/abp/latest/Blob-Storing#blob-storage-providers) that we can choose. For the simplicity of the demo, let's continue with the **database provider** and run the following command under the directory of your solution (`*.sln`): - -```bash -abp add-module Volo.Abp.BlobStoring.Database -``` - -* This command adds all the NuGet packages to the corresponding layers of your solution. -* Also, it makes the necessary configurations, adds a new database migration, and updates the database. -* Since we are not configuring the connection string, the BLOB Storing system will use the default connection string in our application. - -That's it. We have installed and configured the BLOB Storing System in our application. - -### Configuring the Image Manipulation System - -After, configuring the BLOB Storing System, now we can install and configure the Image Manipulation System to be able to compress and resize images. - -ABP Framework provides two image resizer/compressor implementations out of the box: [ImageSharp](https://docs.abp.io/en/abp/7.3/Image-Manipulation#imagesharp-provider) and [Magick.NET](https://docs.abp.io/en/abp/7.3/Image-Manipulation#magick-net-provider). - -We can use the `Volo.Abp.Imaging.ImageSharp` as the provider for our application. To install the package, run the following command under the `*.HttpApi` project: - -```bash -abp add-package Volo.Abp.Imaging.ImageSharp -``` - -* This package will provide the required services to compress and resize images. -* You can [configure the `ImageSharpCompressOptions`](https://docs.abp.io/en/abp/7.3/Image-Manipulation#configuration-1) to define *DefaultQuality* and encoders. - - -After installing the provider, now we can use the services to compress and resize our images, such as `IImageCompression` and `IImageResizer`. But there is an easier way. The `Volo.Abp.Imaging.AspNetCore` NuGet package defines some attributes for controller actions that can automatically compress and/or resize the uploaded files. - -To be able to use these attributes, we need to install the `Volo.Abp.Imaging.AspNetCore` package. Type the following command under the `*.HttpApi` project: - -```bash -abp add-package Volo.Abp.Imaging.AspNetCore -``` - -This package provides two attributes: `[CompressImage]` and `[ResizeImage]`. Whenever we use these attributes, the Image Manipulation System will automatically compress and/or resize uploaded files. - -### Image Upload (with Compress & Resize) - -After all the required package installations and configurations are done, now we can start implementing the API and UI for the Image Upload. - -Let's start with creating the API. Create a controller in the `*.HttpApi` project named `ImageController` and perform the image upload and image display operations: - -```csharp -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using Volo.Abp.BlobStoring; -using Volo.Abp.Imaging; - -namespace ImageManipulationDemo.Controllers -{ - [Controller] - [Route("api/image")] - public class ImageController : ImageManipulationDemoController - { - private readonly IBlobContainer _blobContainer; - - public ImageController(IBlobContainer blobContainer) - { - _blobContainer = blobContainer; - } - - [HttpPost("upload")] - [CompressImage] - [ResizeImage(width: 200, height: 200)] - public async Task UploadAsync(IFormFile file) - { - var fileBytes = await file.GetAllBytesAsync(); - var blobName = file.FileName; - - await _blobContainer.SaveAsync(blobName, fileBytes, overrideExisting: true); - - return Ok(); - } - - [HttpGet("")] - public async Task GetImageAsync(string fileName) - { - return await _blobContainer.GetAllBytesAsync(fileName); - } - } -} -``` - -* Here, we have used both `CompressImage` and `ResizeImage` attributes to automatically compress & resize the uploaded file. -* As you can see, we used the `IBlobContainer` service to save our file content. -* Since we are using the *database provider* as BLOB storing provider, the file contents will be added to our database and then we will be able to fetch them whenever it's needed like we have done in the `GetImageAsync` method above. -* We simply used the required attributes (and they do the rest on behalf of us and call the related image resize and compress services) to resize & compress images and save the new resized/compressed image into the database. - -Before implementing the UI side, as you may notice, we've injected the `IBlobContainer` as a typed service (`IBlobContainer`). A typed BLOB container system is a way of creating and managing multiple containers in an application. We haven't created the `ImageManipulationContainer` class yet. - -Let's create this class as below: - -```csharp -using Volo.Abp.BlobStoring; - -namespace ImageManipulationDemo -{ - [BlobContainerName("image-manipulation-demo")] - public class ImageManipulationContainer - { - } -} -``` - -* We have used the `BlobContainerName` attribute to define the name of the container. -* If we haven't used the `BlobContainerName` attribute, ABP Framework uses the full name of the class with its namespace. - -We have implemented the endpoints and now can start implementing the UI side. You can see the following figure to see what we are going to design for the image upload page: - -![](image-upload-ui.png) - -Let's start designing this page. Open the `Index.cshtml` file (*/Pages/Index.cshtml*) under the `*.Web` project and replace it with the following content: - -```html -@page -@using Microsoft.AspNetCore.Mvc.Localization -@using ImageManipulationDemo.Localization -@using Volo.Abp.Users -@model ImageManipulationDemo.Web.Pages.IndexModel -@inject IHtmlLocalizer L -@inject ICurrentUser CurrentUser -@section styles { - -} -@section scripts { - -} - -
    -
    -
    -
    -
    -
    -
    - - -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    Search & Display Images
    - -
    -
    - - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -``` - -Then, open the `index.js` file and replace it with the following content: - -```js -$(function () { - - $("#upload-image").submit(function (e) { - e.preventDefault(); - - var file = document.getElementById("formFile").files[0]; - var formData = new FormData(); - formData.append("file", file); - - $.ajax( - { - url: "/api/image/upload", - data: formData, - processData: false, - contentType: false, - type: "POST", - success: function (data) { - abp.message.success("Image saved successfully!"); - }, - error: function (err) { - abp.message.error("An error occured while saving the image."); - } - } - ); - }); - - $("#search-image").submit(function (e) { - e.preventDefault(); - - var imgResult = $("#image-result"); - imgResult.removeClass("d-none"); - - imgResult.html("

    Loading...

    "); - - var fileName = $("#img-search-input").val(); - - imageManipulationDemo.controllers.image.getImage(fileName) - .then(function (imageFile) { - var src = "data:image/png;base64," + imageFile; - var img = ""; - - imgResult.html(img); - }) - .catch(function (err) { - imgResult.html("

    Could not find the image...

    "); - }); - }); -}); -``` - -Now, we can run the application and see the Image Manipulation System in action: - -![](image-manipulation.gif) - -The results are impressive for the example above: - -* The original image was 12 KB and now the compressed & resized image has been reduced to 8 KB. -* The original image was 225x225 and now resized as 200x200. - -## Conclusion - -In this article, I have shown you how to compress and/or resize images with ABP Framework's Image Manipulation System by just defining some attributes to the top of the controller actions. - -Also, I have shown that you can use the BLOB Storing System to store file contents and compress/resize images before saving them into BLOB Storages thanks to the image resizers/compressors provided by ABP Framework. - -## See Also - -* [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing) -* [Image Manipulation](https://docs.abp.io/en/abp/7.3/Image-Manipulation#iimageresizer) diff --git a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/image-manipulation.gif b/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/image-manipulation.gif deleted file mode 100644 index d8b2a10c16..0000000000 Binary files a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/image-manipulation.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/image-upload-ui.png b/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/image-upload-ui.png deleted file mode 100644 index 53c8d7b458..0000000000 Binary files a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/image-upload-ui.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/README.md b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/README.md deleted file mode 100644 index 2db041436c..0000000000 --- a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/README.md +++ /dev/null @@ -1,450 +0,0 @@ -# Cascading Option Loading with Extensions System in ABP Angular - -This article will show how to load cascading options with an extensions system in ABP Angular. For this example, we'll simulate renting a book process. Besides our default form properties, we'll contribute `Name` property to our `Rent Form Modal` in the Books module. This property will be loaded after `Genre` is selected. - -> Before starting this article, I suggest you read the [ABP Angular Dynamic Form Extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Dynamic-Form-Extensions) - -### Environment - -- **ABP Framework Version:** ~7.3.0 (`~` means that use the latest patch version of the specified release) -- **DB Provider:** MongoDB -- **Angular Version:** ~16.0.0 - -### Project structure - -The books module is not a library; for this demo, it'll placed in the application itself. - -![Folder structure](./assets/img/folder-structure.png) - -- **books folder:** Contains default form properties, tokens, models, etc. It's similar to the ABP module structure. -- Also I've used **standalone** and **signals** feature in this demo. -- **books-extended folder:** Contains only `Name` property for the contribute `Rent Form Modal` inside the Books module. -- **For more readability, I've used TS path aliases in this demo. Don't forget to export files in `index.ts` file 🙂** - -![tsconfig.json file](./assets/img/ts-config-file.png) - -### First look at the demo - -![Cascading Loading Demo](assets/gif/cascading-loading-demo.gif) - -### What is the Extension system? - -![Extensions System Document](./assets/img/extensions-system-document.png) - -# Reviewing the code step by step - -**1. Create default form properties for `Rent Form` in the `Books` module** - -- `getInjected` function is the key point of the cascading loading -- We can reach and track any value from `Service` or `Component` -- In that way we can load options according to the selected value - -```ts -// ~/books/defaults/default-books-form.props.ts - -import { Validators } from "@angular/forms"; -import { map, of } from "rxjs"; -import { ePropType, FormProp } from "@abp/ng.theme.shared/extensions"; -import { BookDto, AuthorService, BooksService } from "../proxy"; -import { RentBookComponent } from "../components"; -import { DefaultOption } from "../utils"; - -const { required } = Validators; - -export const DEFAULT_RENT_FORM_PROPS = FormProp.createMany([ - { - type: ePropType.String, - id: "authorId", - name: "authorId", - displayName: "BookStore::Author", - defaultValue: null, - validators: () => [required], - options: (data) => { - const { authors } = data.getInjected(AuthorService); - - return of([ - DefaultOption, - ...authors().map((author) => ({ value: author.id, key: author.name })), - ]); - }, - }, - { - type: ePropType.String, - id: "genreId", - name: "genreId", - displayName: "BookStore::Genre", - defaultValue: null, - validators: () => [required], - options: (data) => { - const rentBookComponent = data.getInjected(RentBookComponent); - const { genres } = data.getInjected(BooksService); - - const genreOptions = genres().map(({ id, name }) => ({ - value: id, - key: name, - })); - - return rentBookComponent.form.controls.authorId.valueChanges.pipe( - map((value: string | undefined) => - value ? [DefaultOption, ...genreOptions] : [DefaultOption] - ) - ); - }, - }, - { - type: ePropType.Date, - id: "returnDate", - name: "returnDate", - displayName: "BookStore::ReturnDate", - defaultValue: null, - validators: () => [required], - }, -]); -``` - -**2. Configure tokens and config options** - -The documentation explains these steps; that's why I won't explain it again. If documents or samples are not enough, please let me know in the comments 🙂 - -**Extensions Token** - -```ts -// ~/books/tokens/extensions.token.ts - -import { CreateFormPropContributorCallback } from "@abp/ng.theme.shared/extensions"; -import { InjectionToken } from "@angular/core"; -import { BookDto } from "../proxy"; -import { eBooksComponents } from "../enums"; -import { DEFAULT_RENT_FORM_PROPS } from "../defaults"; - -export const DEFAULT_BOOK_STORE_CREATE_FORM_PROPS = { - [eBooksComponents.RentBook]: DEFAULT_RENT_FORM_PROPS, -}; - -export const BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS = - new InjectionToken( - "BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS" - ); - -type CreateFormPropContributors = Partial<{ - [eBooksComponents.RentBook]: CreateFormPropContributorCallback[]; - /** - * Other creation form prop contributors... - */ - // [eBooksComponents.CreateBook]: CreateFormPropContributorCallback[]; -}>; -``` - -**Extensions Config Option** - -```ts -// ~/books/models/config-options.ts - -import { CreateFormPropContributorCallback } from "@abp/ng.theme.shared/extensions"; -import { BookDto } from "../proxy"; -import { eBooksComponents } from "../enums"; - -export type BookStoreRentFormPropContributors = Partial<{ - [eBooksComponents.RentBook]: CreateFormPropContributorCallback[]; -}>; - -export interface BooksConfigOptions { - rentFormPropContributors?: BookStoreRentFormPropContributors; -} -``` - -**3. Extensions Guard** - -It'll to collect all contributors from [ExtensionsService](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/extensions/src/lib/services/extensions.service.ts) - -```ts -// ~/books/guards/extensions.guard.ts - -import { Injectable, inject } from "@angular/core"; -import { Observable, map, tap } from "rxjs"; -import { ConfigStateService, IAbpGuard } from "@abp/ng.core"; -import { - ExtensionsService, - getObjectExtensionEntitiesFromStore, - mapEntitiesToContributors, - mergeWithDefaultProps, -} from "@abp/ng.theme.shared/extensions"; -import { - BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS, - DEFAULT_BOOK_STORE_CREATE_FORM_PROPS, -} from "../tokens"; - -@Injectable() -export class BooksExtensionsGuard implements IAbpGuard { - protected readonly configState = inject(ConfigStateService); - protected readonly extensions = inject(ExtensionsService); - - canActivate(): Observable { - const createFormContributors = - inject(BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS, { optional: true }) || {}; - - return getObjectExtensionEntitiesFromStore( - this.configState, - "BookStore" - ).pipe( - mapEntitiesToContributors(this.configState, "BookStore"), - tap((objectExtensionContributors) => { - mergeWithDefaultProps( - this.extensions.createFormProps, - DEFAULT_BOOK_STORE_CREATE_FORM_PROPS, - objectExtensionContributors.createForm, - createFormContributors - ); - }), - map(() => true) - ); - } -} -``` - -Yes, I'm still using class-based guard 🙂 much more flexible... - -**4. RentBookComponent** - -- Our trackable variable is defined here `(form:FormGroup)`, which means We'll track this variable in `options` property at defaults || contributors files. -- Providing `AuthorService`, also `EXTENSIONS_IDENTIFIER` for the reach dynamic properties - -```ts -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Injector, - Output, - inject, -} from "@angular/core"; -import { FormGroup } from "@angular/forms"; -import { CoreModule, uuid } from "@abp/ng.core"; -import { ThemeSharedModule } from "@abp/ng.theme.shared"; -import { - EXTENSIONS_IDENTIFIER, - FormPropData, - UiExtensionsModule, - generateFormFromProps, -} from "@abp/ng.theme.shared/extensions"; -import { AuthorService, BookDto, BooksService } from "../../proxy"; -import { eBooksComponents } from "../../enums"; - -@Component({ - standalone: true, - selector: "app-rent-book", - templateUrl: "./rent-book.component.html", - imports: [CoreModule, UiExtensionsModule, ThemeSharedModule], - providers: [ - { - provide: EXTENSIONS_IDENTIFIER, - useValue: eBooksComponents.RentBook, - }, - AuthorService, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class RentBookComponent { - protected readonly injector = inject(Injector); - protected readonly authorService = inject(AuthorService); - protected readonly booksService = inject(BooksService); - - //#region Just for demo - readonly #authors = this.authorService.authors(); - readonly #genres = this.booksService.genres(); - readonly #books = this.booksService.books(); - //#endregion - - protected modalVisible = true; - @Output() modalVisibleChange = new EventEmitter(); - - selected: BookDto; - form: FormGroup; - modalBusy = false; - - protected buildForm(): void { - const data = new FormPropData(this.injector, this.selected); - this.form = generateFormFromProps(data); - } - - constructor() { - this.buildForm(); - } - - save(): void { - if (this.form.invalid) { - return; - } - - this.modalBusy = true; - - const { authorId, genreId, bookId, returnDate } = this.form.value; - - //#region Just for demo - const authorName = this.#authors.find(({ id }) => id === authorId).name; - const genreName = this.#genres.find(({ id }) => id === genreId).name; - const bookName = this.#books.find(({ id }) => id === bookId).name; - //#endregion - - this.booksService.rentedBooks.update((books) => [ - { - id: uuid(), - name: bookName, - author: authorName, - genre: genreName, - returnDate, - }, - ...books, - ]); - - this.modalBusy = false; - this.modalVisible = false; - } -} -``` - -```html - - -

    {{ 'BookStore::RentABook' | abpLocalization }}

    -
    - - - -
    - -
    -
    - -
    - -
    -
    - - - - - {{ 'AbpIdentity::Save' | abpLocalization }} - - -
    -``` - -Up to now, we have constructed our module's default form properties. - -- As you can see, there are no book names we'll add them via contributors - -![Rent Form Without Contribution](./assets/img/rent-form-without-contribution.png) - -## Next, add new property dynamically (book name list as dropdown) - -- Created new folder ./src/app/books-extended -- Create contributors/form-prop.contributors.ts - -```ts -// ~/books-extened/contributors/form-prop.contributors.ts - -import { Validators } from "@angular/forms"; -import { map } from "rxjs"; -import { - ePropType, - FormProp, - FormPropList, -} from "@abp/ng.theme.shared/extensions"; -import { - BookDto, - BookStoreRentFormPropContributors, - BooksService, - DefaultOption, - RentBookComponent, - eBooksComponents, -} from "@book-store/books"; - -const { required, maxLength } = Validators; - -const bookIdProp = new FormProp({ - type: ePropType.String, - id: "bookId", - name: "bookId", - displayName: "BookStore::Name", - options: (data) => { - const rentBook = data.getInjected(RentBookComponent); - const { books } = data.getInjected(BooksService); - const bookOptions = books().map(({ id, name }) => ({ - value: id, - key: name, - })); - - return rentBook.form.controls.genreId.valueChanges.pipe( - map((value: string | undefined) => - value ? [DefaultOption, ...bookOptions] : [DefaultOption] - ) - ); - }, - validators: () => [required, maxLength(255)], -}); - -export function bookIdPropContributor(propList: FormPropList) { - propList.addByIndex(bookIdProp, 2); -} - -export const bookStoreRentFormPropContributors: BookStoreRentFormPropContributors = - { - [eBooksComponents.RentBook]: [bookIdPropContributor], - }; -``` - -- Load new contributions via routing & forLazy method - -```ts -// ~/app-routing.module.ts -import { bookStoreRentFormPropContributors } from "./books-extended/contributors/form-prop.contributors"; - -const routes: Routes = [ - // other routes... - { - path: "books", - loadChildren: () => - import("@book-store/books").then((m) => - m.BooksModule.forLazy({ - rentFormPropContributors: bookStoreRentFormPropContributors, - }) - ), - }, -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes, {})], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - -Finally, we've added a new property to our module, and it'll be loaded after `Genre` is selected. - -## Conclusion - -![Cascading Loading Demo](assets/gif/cascading-loading-demo.gif) - -- In ABP Angular, we can create form properties and load dropdown options dynamically via the Extensions System -- We can reach and track any value from `Service` or `Component` -- We can create our custom library or module and contribute it to any module in the application - -Thanks for reading, I hope it was helpful. If you have any questions, please let me know in the comments section. 👋👋 - -> You can find the source code of this article on [Github](https://github.com/abpframework/abp-samples/tree/master/AngularCascadingOptionLoading/Volo.BookStore) diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/gif/cascading-loading-demo.gif b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/gif/cascading-loading-demo.gif deleted file mode 100644 index ae69070f28..0000000000 Binary files a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/gif/cascading-loading-demo.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/extensions-system-document.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/extensions-system-document.png deleted file mode 100644 index c752d7c08c..0000000000 Binary files a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/extensions-system-document.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/folder-structure.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/folder-structure.png deleted file mode 100644 index 3d10f03cf2..0000000000 Binary files a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/folder-structure.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/rent-form-without-contribution.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/rent-form-without-contribution.png deleted file mode 100644 index 84a47e28d2..0000000000 Binary files a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/rent-form-without-contribution.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/ts-config-file.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/ts-config-file.png deleted file mode 100644 index 2ee9ccf13f..0000000000 Binary files a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/ts-config-file.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/POST.md b/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/POST.md deleted file mode 100644 index 00325751ef..0000000000 --- a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/POST.md +++ /dev/null @@ -1,125 +0,0 @@ -# Adding Dark Mode Support to the Basic Theme -Basic Theme uses plain bootstrap and does not have any custom colors & styles. This article will show you how to add dark mode support to the [Basic Theme](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Basic-Theme). - -Bootstrap brings the [Color Modes](https://getbootstrap.com/docs/5.3/customize/color-modes/#dark-mode) feature with version **5.3**. This feature allows you to add dark mode support to your website with a single line of code. Adding the `data-bs-theme="dark"` attribute changes the color mode of the element to dark mode. - -## Instructions - -1. Create a new project with the following command: - ```bash - abp new BasicThemeDarkMode -t app --theme basic - ``` - -2. Create a component that toggles the color mode. - - - Create a new file named `Components/ChangeTheme/Default.cshtml`: - ```html -
    - -
    - ``` - - - Create a new file named `Components/ChangeTheme/ChangeThemeViewComponent.cs`: - ```csharp - using Microsoft.AspNetCore.Mvc; - using Volo.Abp.AspNetCore.Mvc; - - namespace BasicThemeDarkMode.Web.Components.ChangeTheme; - - [Widget(ScriptFiles = new[]{"/Components/ChangeTheme/ChangeTheme.js"})] - public class ChangeThemeViewComponent : AbpViewComponent - { - public IViewComponentResult Invoke() - { - return View("~/Components/ChangeTheme/Default.cshtml"); - } - } - ``` - - - Create a JavaScript that manages the last selected theme and toggles the color mode. It stores the last selected theme in the *local storage*. So, you don't need to store it in the database. - - - Create a new file named `Components/ChangeTheme/ChangeTheme.js`: - ```js - $(function () { - function changeTheme(theme) { - window.localStorage.setItem('theme', theme); - document.getElementsByTagName('body')[0].setAttribute('data-bs-theme', theme); - } - - function toggleTheme(){ - getTheme() == 'light' ? changeTheme('dark') : changeTheme('light'); - } - - function getTheme(){ - return window.localStorage.getItem('theme') ?? 'dark'; - } - - function init(){ - let theme = getTheme(); - if(theme){ - changeTheme(theme); - } - } - - document.getElementById('ToolbarChangeTheme').addEventListener('click', () => { - toggleTheme(); - }); - - init(); - }); - ``` - -3. Create a new [Toolbar Contributor](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Toolbars) and add a newly created view component to the application toolbar. - - - Create a new class named `BasicThemeDarkModeToolbarContributor.cs`: - ```csharp - using BasicThemeDarkMode.Web.Components.ChangeTheme; - using System.Threading.Tasks; - using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; - - namespace BasicThemeDarkMode.Web; - - public class BasicThemeDarkModeToolbarContributor : IToolbarContributor - { - public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) - { - if (context.Toolbar.Name == StandardToolbars.Main) - { - context.Toolbar.Items - .Add(new ToolbarItem(typeof(ChangeThemeViewComponent))); - } - - return Task.CompletedTask; - } - } - ``` - - - Configure [Toolbar Options](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Toolbars) and add a newly created contributor: - ```csharp - Configure(options => - { - options.Contributors.Add(new BasicThemeDarkModeToolbarContributor()); - }); - ``` - -That's it! Now, you can toggle the color mode by clicking the sun icon in the toolbar: - -![Dark Mode](basictheme-toggle-demo.gif) - -- Users Page in Dark Mode: - - ![Users Page in Dark Mode](basictheme-dark-users.png) - -- Settings Page in Dark Mode: - - ![Settings Page in Dark Mode](basictheme-dark-settings.png) - -- Login Page in Dark Mode: - - ![Login Page in Dark Mode](basictheme-dark-login.png) - -## Conclusion - -The theme is stored in **local storage** and it's initialized on the client-side. You can use **Cookies** to render the page in the last selected theme on **server-side** to prevent the flash effect while navigating pages. This document shows the concept of adding dark mode support of bootstrap to the Basic Theme. diff --git a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-login.png b/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-login.png deleted file mode 100644 index 69d5afa8c5..0000000000 Binary files a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-login.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-settings.png b/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-settings.png deleted file mode 100644 index 464f48bd84..0000000000 Binary files a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-settings.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-users.png b/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-users.png deleted file mode 100644 index 31cb010169..0000000000 Binary files a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-dark-users.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-toggle-demo.gif b/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-toggle-demo.gif deleted file mode 100644 index 536b30e6ee..0000000000 Binary files a/docs/en/Community-Articles/2023-08-07-Basic-Theme-Dark-Mode/basictheme-toggle-demo.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md deleted file mode 100644 index 38cfab67e9..0000000000 --- a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md +++ /dev/null @@ -1,148 +0,0 @@ -# ABP Commercial - GDPR Module Overview - -In this article, I will highlight ABP Commercial's [GDPR Module](https://commercial.abp.io/modules/Volo.Gdpr) and show you how to provide personal data to the GDPR Module that is collected by your application and make it aligned with personal data download results. - -## GDPR Module - -[GDPR Module](https://docs.abp.io/en/commercial/latest/modules/gdpr) allows users to download and delete the data collected by the application. It's used for Personal Data Management obligating by the GDPR regulation and provides the following features: - -* Allows users to request their personal data. -* Lists the personal data requests and users can download their personal data by the listed request times. -* Allows users to delete their personal data and their accounts permanently. - -The GDPR module is pre-installed in the [Application](https://docs.abp.io/en/commercial/latest/startup-templates/application/index) and [Application (Single Layer)](https://docs.abp.io/en/commercial/latest/startup-templates/application-single-layer/index) Pro Startup templates for ABP Commercial customers. Therefore, most of the time you don't need to manually install it. - -> If you need to install the GDPR Module manually, you can refer to the [GDPR Module documentation](https://docs.abp.io/en/commercial/latest/modules/gdpr#how-to-install). - -Let's create an Application template with the following command and see the UI for the GDPR module: - -```csharp -abp new GdprDemo -t app-pro -``` - -After the application is created, we can run the application and login to the application. Then, when we check the user menu, we should be able to see the "Personal Data" menu item: - -![](./gdpr-personal-data-menu.png) - -We can click this menu item to navigate to the "Personal Data Management" page: - -![](./gdpr-personal-data-page.png) - -This page is used to manage personal data requests. You can view past requests, the current status of the latest request, create a new personal data download request, download the prepared personal data or delete all your personal data (makes the data anonymized) and account from the application. - -### Downloading Personal Data - -After a quick overview of the GDPR module, now we can deep dive into the Personal Data Download process. - -The GDPR module requests information from the other modules that reference the `Volo.Abp.Gdpr.Abstractions` package and merges the response data into a single JSON file and the personal data can be downloaded later by the user from the "Personal Data Management" page when it's prepared (when the specified preparation time has been passed). - -Currently, only the [Identity Pro module](https://docs.abp.io/en/commercial/latest/modules/identity) provides some personal data to the GDPR Module because it's the only module that can be considered as collecting personal data, such as the user's name, surname, email address, and so on. Therefore, ABP makes everything that is needed such as getting all data collected when the personal data download request has been made or deleting/anonymizing the personal data when the personal data delete request has been made. - -This might be enough for some of your applications and you would not need to collect any other sensitive data from a user. However, most of the time you would probably need to collect some personal data from users for certain reasons. - -In that case, you should provide the personal data that you collected for a certain user in your application/module or provider to the GPDR module. So, when the personal data download request has been made, the GDPR module can request the information from the different part of your application and merges the response data into a single JSON file to be downloaded. In other words, the data provided by you will be considered as personal data and whenever a user requests his/her personal data, it'll be included in the result. - -In the next section, we will see how to provide personal data from a different module/provider. - -## Providing Personal Data - -Let's assume that we have an e-commerce application and we need to get the address from a user to send their order. Address information is considered as personal data according to GDPR regulations. Therefore, we need to provide the address information to the GDPR Module. - -For this purpose, we need to subscribe to the `GdprUserDataDeletionRequestedEto` and `GdprUserDataRequestEto` distributed events. These ETOs (event transfer objects) come from the `Volo.Abp.Gdpr.Abstractions` package, which is used to start deleting/anonymizing and downloading personal data requests. `Volo.Abp.Gdpr.Domain` package is pre-installed in the domain layer and has reference to that package, so most of the time you don't need to reference to the `Volo.Abp.Gdpr.Abstractions` package. - -But, if you want to use these ETOs in a different project, you should add the `Volo.Abp.Gdpr.Abstractions` package. You can use the following command to add it to your project: - -```bash -abp add-package Volo.Abp.Gdpr.Abstractions -``` - -After we have ensured that we have reference to the package, then we can create a new event handler class named `AddressGdprEventHandler` and subscribe to the `GdprUserDataDeletionRequestedEto` and `GdprUserDataRequestEto` distributed events: - -```csharp -public class AddressGdprEventHandler : - IDistributedEventHandler, - IDistributedEventHandler, - ITransientDependency -{ - //inject your services - protected IDistributedEventBus EventBus { get; } - - public AddressGdprEventHandler(IDistributedEventBus eventBus) - { - EventBus = eventBus; - } - - public Task HandleEventAsync(GdprUserDataDeletionRequestedEto eventData) - { - //anonymize the personal data - - return Task.CompletedTask; - } - - public async Task HandleEventAsync(GdprUserDataRequestedEto eventData) - { - //assume we have retrieved the address for a certain user - //by querying eventData.UserId - var address = new Address - { - City = "Istanbul", - Postcode = "...", - - //other properties... - }; - - var gdprDataInfo = new GdprDataInfo - { - { "City", address.City }, - { "Postcode", address.Postcode } - }; - - await EventBus.PublishAsync( - new GdprUserDataPreparedEto - { - RequestId = eventData.RequestId, - Data = gdprDataInfo, //collected personal data - Provider = GetType().FullName //your module/provider name - }); - } -} -``` - -* Here, we have subscribed to the `GdprUserDataDeletionRequestedEto` and `GdprUserDataRequestEto` distributed events. -* When a user made a personal data preparation request, then the `GdprUserDataRequestEto` is published by the GDPR module and the data preparation process starts. Modules/providers provide sensitive personal data by subscribing to this event and publishing a `GdprUserDataPreparedEto` distributed event, with the data and the provider information. Thanks to this, you can distinguish the personal data by the collected provider/module. -* Then, the GDPR module subscribes to the `GdprUserDataPreparedEto` distributed events that are published by multiple modules/providers and merge the response data into a single JSON file, and the personal data can be downloaded later by the user when the data preparation is done. -* On the other hand, when a user made a request to delete his/her personal data from the application, the `GdprUserDataDeletionRequestedEto` is published, so we can subscribe to the event and anonymize the personal data in our application. -* Also, the Identity Pro module deletes the user's account permanently. - -Now, let's run the application once again and make a personal data download request. But before that, we can configure the `AbpGdprOptions` as follows to not wait for long and show our personal data result: - -```csharp -Configure(options => -{ - options.MinutesForDataPreparation = 5; //wait 5 minutes for data-preperation -}); -``` - -After we have made a personal data preparation request, we need to wait for 5 minutes for personal data to be collected and then we can download it. After 5 minutes have passed, we can see the "Download" action button for the request on the "Personal Data Management" page: - -![](./personal-data-download.png) - -Then, when we clicked the download button, a zip file will be downloaded. We can unzip the file and examine the JSON files that contain our personal data. If you have followed and applied the steps from now, you should be seeing two JSON files. One of them is for user-specific information provided by the Identity Pro module and the other one is the address information provided by us (our application-specific personal data). - -![](./personal-data-files.png) - -*Basic information for the user:* - -```json -{"Username":"admin","Name":"admin","Surname":null,"Email":"admin@abp.io","Phone Number":null} -``` - -*Address information that we provided in this article:* - -```json -{"City":"Istanbul","Postcode":"..."} -``` - -## Conclusion - -In this article, I've explained the GDPR Module's data collection system and given you a brief overview of the module. GDPR Module allows you to request to download personal data, delete/anonymize your own personal data and delete your account permanently. It's pre-installed in the Application and Application(single layer) Pro Startup templates, but you can easily configure it to your existing application. diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/cover-image.png b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/cover-image.png deleted file mode 100644 index f60b514203..0000000000 Binary files a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/cover-image.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/gdpr-personal-data-menu.png b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/gdpr-personal-data-menu.png deleted file mode 100644 index ce92423738..0000000000 Binary files a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/gdpr-personal-data-menu.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/gdpr-personal-data-page.png b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/gdpr-personal-data-page.png deleted file mode 100644 index 4b81b2645c..0000000000 Binary files a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/gdpr-personal-data-page.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/personal-data-download.png b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/personal-data-download.png deleted file mode 100644 index afb1a18165..0000000000 Binary files a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/personal-data-download.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/personal-data-files.png b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/personal-data-files.png deleted file mode 100644 index 8d9467c48e..0000000000 Binary files a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/personal-data-files.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/POST.md b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/POST.md deleted file mode 100644 index a68bc106a5..0000000000 --- a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/POST.md +++ /dev/null @@ -1,236 +0,0 @@ -# Moving Background Job Execution To A Separate Application - -In this article, I will show you how to move the background job execution to a separate application. - -Here are some benefits of doing this: - -* If your background jobs consume high system resources (CPU, RAM or Disk), then you can deploy that background application to a dedicated server so it won't affect your application's performance. -* You can scale your background job application independently from your web application. For example, you can deploy multiple instances of your background job application to a Kubernetes cluster and scale it easily. - -Here are some disadvantages of doing this: - -* You need to deploy and maintain at least two applications instead of one. -* You need to implement a mechanism to share the common code between your applications. For example, you can create a shared project and add it to your applications as a project reference. - -## Source code - -You can find the source code of the application at [abpframework/abp-samples](https://github.com/abpframework/abp-samples/tree/master/SeparateBackgroundJob). - -You can check the PR to see the changes step by step: [abpframework/abp-samples#250](https://github.com/abpframework/abp-samples/pull/250) - -## Creating the Web Application - -First, we need to create a new web application using the ABP CLI: - -```bash -abp new SeparateBackgroundJob -t app -``` - -* Create a shared project named `SeparateBackgroundJob.Common.Shared` to share the `BackgroundJob` and `BackgroundJobArgs` classes between the web and job executor applications. -* Install the `Volo.Abp.BackgroundJobs.Abstractions` package to the `SeparateBackgroundJob.Common.Shared` project. - -Add the `SeparateBackgroundJobCommonSharedModule` class to the `SeparateBackgroundJob.Common.Shared` project: - -```csharp -[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))] -public class SeparateBackgroundJobCommonSharedModule : AbpModule -{ -} -``` - -Add the `MyReportJob` and `MyReportJobArgs` classes to the `SeparateBackgroundJob.Common.Shared` project: - -```csharp -public class MyReportJob : AsyncBackgroundJob, ITransientDependency -{ - public override Task ExecuteAsync(MyReportJobArgs args) - { - Logger.LogInformation("Executing MyReportJob with args: {0}", args.Content); - return Task.CompletedTask; - } -} - -public class MyReportJobArgs -{ - public string? Content { get; set; } -} -``` - -Add the `SeparateBackgroundJob.Common.Shared` project reference to the `SeparateBackgroundJob.Domain` project and add `SeparateBackgroundJobCommonSharedModule` to the `DependsOn` attribute of the `SeparateBackgroundJobDomainModule` class: - -```csharp -[DependsOn( - typeof(SeparateBackgroundJobDomainSharedModule), - typeof(AbpAuditLoggingDomainModule), - typeof(AbpBackgroundJobsDomainModule), - typeof(AbpFeatureManagementDomainModule), - typeof(AbpIdentityDomainModule), - typeof(AbpOpenIddictDomainModule), - typeof(AbpPermissionManagementDomainOpenIddictModule), - typeof(AbpPermissionManagementDomainIdentityModule), - typeof(AbpSettingManagementDomainModule), - typeof(AbpTenantManagementDomainModule), - typeof(AbpEmailingModule), - typeof(SeparateBackgroundJobCommonSharedModule) //Add this line -)] -public class SeparateBackgroundJobDomainModule : AbpModule -``` - -Open the `Index.cshtml` and replace the content with the following code: - -```csharp -@page -@using Microsoft.AspNetCore.Mvc.Localization -@using SeparateBackgroundJob.Localization -@using Volo.Abp.Users -@model SeparateBackgroundJob.Web.Pages.IndexModel -@inject IHtmlLocalizer L -@inject ICurrentUser CurrentUser - -@section styles { - -} - -@section scripts { - -} - -
    - - - - Add NEW BACKGROUND JOB - - - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -``` - -Open the `Index.cshtml.cs` and replace the content with the following code: - -```csharp -public class IndexModel : SeparateBackgroundJobPageModel -{ - private readonly IBackgroundJobManager _backgroundJobManager; - - [BindProperty(SupportsGet = true)] - public string? ReportContent { get; set; } - - public IndexModel(IBackgroundJobManager backgroundJobManager) - { - _backgroundJobManager = backgroundJobManager; - } - - public void OnGet() - { - - } - - public async Task OnPostAsync() - { - await _backgroundJobManager.EnqueueAsync(new MyReportJobArgs - { - Content = ReportContent - }); - - Alerts.Success("Job is queued!"); - } -} -``` - -Run the application and navigate to the home page. You should see the following page: - -![1](images/1.png) - -When you enter some text and click the **Add** button, the job will be queued and executed in the web application: - -## Creating the Console Application - -Now we split the background job execution to a separate console application. - -Open the `SeparateBackgroundJobWebModule` class to disable the background job execution in the web application: - -```csharp -public class SeparateBackgroundJobWebModule : AbpModule -{ - .... - - public override void ConfigureServices(ServiceConfigurationContext context) - { - ... - - //Disable background job execution in the web application - Configure(options => - { - options.IsJobExecutionEnabled = false; - }); - } - - ... -} -``` - -* Create a new console application using the ABP CLI: - -```bash -abp new BackgroundJobExecutor -t console -``` - -* Add the `BackgroundJobExecutor` project to the solution of the web application. -* Add the `SeparateBackgroundJob.Common.Shared` project reference to the `BackgroundJobExecutor` project. -* Install the `Volo.Abp.BackgroundJobs.EntityFrameworkCore` and `Volo.Abp.EntityFrameworkCore.SqlServer` packages to the `BackgroundJobExecutor` project. - -Update the `BackgroundJobExecutorModule` class as follows: - -```csharp -[DependsOn( - typeof(AbpAutofacModule), - typeof(AbpBackgroundJobsEntityFrameworkCoreModule), - typeof(AbpEntityFrameworkCoreSqlServerModule), - typeof(SeparateBackgroundJobCommonSharedModule) -)] -public class BackgroundJobExecutorModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.UseSqlServer(); - }); - } - - .... -} -``` - -Open the `appsettings.json` file to configure the [connection string](https://docs.abp.io/en/abp/latest/Connection-Strings#configure-the-connection-strings): - -```json -{ - "ConnectionStrings": { - "AbpBackgroundJobs": "Server=(LocalDb)\\MSSQLLocalDB;Database=SeparateBackgroundJob;Trusted_Connection=True" - } -} -``` - -> You must use the same connection string for the web application, `AbpBackgroundJobs` is the default connection string name for the background job module. - -The solution structure should look like this: - -![solution](images/solution.png) - -Now, run the web and console application. When you enter some text and click the **Add** button, the job will be queued and executed in the console application: - -![2](images/2.png) diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/1.png b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/1.png deleted file mode 100644 index 31f62a51ee..0000000000 Binary files a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/1.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/2.png b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/2.png deleted file mode 100644 index 0e56eee637..0000000000 Binary files a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/2.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/solution.png b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/solution.png deleted file mode 100644 index ab7abe9a89..0000000000 Binary files a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/solution.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-10-09-How-to-Upload-and-Download-Files-in-the-ABP-Framework-using-Angular/POST.md b/docs/en/Community-Articles/2023-10-09-How-to-Upload-and-Download-Files-in-the-ABP-Framework-using-Angular/POST.md deleted file mode 100644 index b6e2cc581a..0000000000 --- a/docs/en/Community-Articles/2023-10-09-How-to-Upload-and-Download-Files-in-the-ABP-Framework-using-Angular/POST.md +++ /dev/null @@ -1,114 +0,0 @@ -# How to Upload and Download Files in the ABP Framework using Angular -In this article, I will describe how to upload and download files with the ABP framework using Angular as the UI template, most of the code is compatible with all template types. In this article, I just gather some information in one post. Nothing is new. I Googled how to manage files in ABP and I didn't find anything. So I decided write a simple article as an answer to this question. - -### Creating App Service. - -An empty AppService that uses `IRemoteStreamContent` was created. ABP describes the IRemoteStreamContent as: - -> ABP Framework provides a special type, IRemoteStreamContent to be used to get or return streams in the application services. - -```csharp -public class StorageAppService: AbpFileUploadDownloadDemoAppService // <- a inherited from ApplicationService. `ProjectName`+'AppService'. -{ - public Guid UploadFile(IRemoteStreamContent file) - { - Stream fs = file.GetStream(); - - //save your file with guid or implement your logic - var id = Guid.NewGuid(); - var filePath = "Insert your file path here/" + id.ToString(); - using var stream = new FileStream(filePath, FileMode.Create); - fs.CopyTo(stream); - return id; - } - public IRemoteStreamContent DownloadFile(Guid FileName) - { - //find your file with guid or implement your logic - var filePath = "Insert your file path here" ; - var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); - return new RemoteStreamContent(fs); - } -} -``` - -When you want to upload a file, app service param must be IRemoteStreamContent or RemoteStreamContent. You should be able to access a file data with the getStream method in the AppService. After that, There is no ABP spesific code. It's a c# spesific class. You can save a file system, move somewhere or serilize as base64 etc. - -when you want to download a file, A method should return IRemoteStreamContent or RemoteStreamContent. -RemoteStreamContent gets a required parameter. The parameter type must be Stream. (FileStream, MemoryStream, Custom etc...) - -For more information please read the topic in the Documentation: https://docs.abp.io/en/abp/latest/Application-Services#working-with-streams - -### Creating Angular proxy services - -ABP creates proxy files with the `abp generate-proxy -t ng` command. let's check the code. - -```javascript - - uploadFileByFile = (file: FormData, config?: Partial) => - this.restService.request({ - method: 'POST', - responseType: 'text', - url: '/api/app/storage/upload-file', - body: file, - }, - { apiName: this.apiName,...config }); - -``` -The function name is a little bit weird but let's focus the first parameter. The type of file param is `FormData`. FormData is a native object in JavaSript Web API. See the details. https://developer.mozilla.org/en-US/docs/Web/API/FormData . - -How to use the `uploadFileByFile` function. - -```javascript -const myFormData = new FormData(); -myFormData.append('file', inputFile); // file must match variable name in AppService -storageService.uploadFileByFile(myFormData).subscribe() -``` -The inputFile type is File. In most cases it comes from the ``, File belongs to the Javacsript Web Api. see the details https://developer.mozilla.org/en-US/docs/Web/API/File - - -Let's continue with "download" - -```javascript - - downloadFileByFileName = (FileName: string, config?: Partial) => - this.restService.request({ - method: 'POST', - responseType: 'blob', - url: '/api/app/storage/download-file', - params: { fileName: FileName }, - }, - { apiName: this.apiName,...config }); - -``` - -The return type of function is Blob. Blob is another javacript object. See the details: https://developer.mozilla.org/en-US/docs/Web/API/Blob. - -Now our code is not ABP Spesific. It is just javascript code. If you don't want to save the blob, here I asked my best AI friend ChatGPT. `Hello, chat! The programming lang is javascript. My variable type is Blob. How do I save file to client's machine?` - -Our the gifted friend gives us the code -```javascript -function saveBlobToFile(blob, fileName) { - // Create a blob URL - const blobURL = window.URL.createObjectURL(blob); - - // Create an anchor element for the download - const a = document.createElement("a"); - a.href = blobURL; - a.download = fileName || 'download.dat'; // Provide a default file name if none is provided - - // Append the anchor to the document - document.body.appendChild(a); - - // Simulate a click on the anchor to initiate the download - a.click(); - - // Clean up: remove the anchor and revoke the blob URL - document.body.removeChild(a); - window.URL.revokeObjectURL(blobURL); -} - -// Usage example -const blob = new Blob(['Hello, World!'], { type: 'text/plain' }); -saveBlobToFile(blob, 'hello.txt'); -``` - diff --git a/docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md b/docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md deleted file mode 100644 index 73bea079a1..0000000000 --- a/docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md +++ /dev/null @@ -1,30 +0,0 @@ -# New Containers feature with NET 8.0 - -This article will show you the new feature of containers with NET 8.0. - -## Non-root user - -The `Non-root user` feature on net 8 is a security measure that allows users to have limited access to the system without having full administrative privileges. Hosting containers as `non-root` aligns with the principle of least privilege. -It’s free security provided by the operating system. If you run your app as root, your app process can do anything in the container, like modify files, install packages, or run arbitrary executables. -That’s a concern if your app is ever attacked. If you run your app as non-root, your app process cannot do much, greatly limiting what a bad actor could accomplish. - -## Default ASP.NET Core port changed from 80 to 8080 - -In .NET 8, there has been a change in the default port used by ASP.NET Core applications. Previously, the default port assigned to ASP.NET Core applications was `80`. However, starting from .NET 8, the default port has been changed to `8080`. -This change was made to avoid conflicts with other applications and services that commonly use port 80, such as web servers like IIS or Apache. By using port 8080 as the default, there is less potential for clashes and easier deployment of ASP.NET Core applications alongside other services. - -It's important to note that this change only affects the default port used when an ASP.NET Core application is run without explicitly specifying a port. - -If you want your application to continue using port 80, you can still specify it during the application launch or configure it in the application settings. - -* Recommended: Explicitly set the `ASPNETCORE_HTTP_PORTS`, `ASPNETCORE_HTTPS_PORTS`, and `ASPNETCORE_URLS` environment variables to the desired port. Example: `docker run --rm -it -p 9999:80 -e ASPNETCORE_HTTP_PORTS=80 ` -* Update existing commands and configuration that rely on the expected default port of port 80 to reference port 8080 instead. Example: `docker run --rm -it -p 9999:8080 ` - -> The `dockerfile` of ABP templates has been updated to use port `80`. - -## References - -- [Secure your .NET cloud apps with rootless Linux Containers](https://devblogs.microsoft.com/dotnet/securing-containers-with-rootless/) -- [Containers breaking changes](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#containers) -- [ASP.NET Core apps use port 8080 by default](https://learn.microsoft.com/en-us/dotnet/core/compatibility/8.0#containers) -- [Docker images for ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-8.0) diff --git a/docs/en/Community-Articles/2023-10-23-NET-8-Feature-raw-sql-queries-for-unmapped-types/POST.md b/docs/en/Community-Articles/2023-10-23-NET-8-Feature-raw-sql-queries-for-unmapped-types/POST.md deleted file mode 100644 index 11d8e67563..0000000000 --- a/docs/en/Community-Articles/2023-10-23-NET-8-Feature-raw-sql-queries-for-unmapped-types/POST.md +++ /dev/null @@ -1,53 +0,0 @@ -# New Raw SQL queries for unmapped types feature with EF Core 8.0 - -## Introduction - -I would love to talk about the new feature in EF Core 8.0, specifically the `raw SQL queries for unmapped types`. -This feature was recently introduced by Microsoft and is aimed at providing more flexibility and customization in database queries. - -## What is the raw SQL queries for the unmapped types feature? - -To give you a better understanding, let's look at a sample repository method with the ABP framework. -Here is an example of a raw SQL query using the new feature: - -````csharp -public interface IAuthorRepository : IRepository -{ - Task> GetAllAuthorNamesAsync(); -} - -public class AuthorIdWithNames -{ - public Guid Id { get; set; } - - public string Name { get; set; } -} - -public class EfCoreAuthorRepository : EfCoreRepository, IAuthorRepository -{ - public EfCoreAuthorRepository(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public virtual async Task> GetAllAuthorNamesAsync() - { - return await (await GetDbContextAsync()).Database.SqlQuery(@$"SELECT Id, Name FROM Authors").ToListAsync(); - } -} -```` - -In this code, we can see that we are using the `SqlQuery` method to execute a raw SQL query on a custom type, `AuthorIdWithNames` in this case. This allows us to retrieve data that may not be mapped to any of our entity classes in the context. - -## In summary - -This feature can be particularly useful in scenarios where we need to access data from tables or views that are not directly mapped to our entities. It also provides an alternative to using stored procedures for querying data. - -However, it's important to note that using raw SQL queries can increase the risk of SQL injection attacks. So, it's recommended to use parameterized queries to prevent this. Additionally, this feature may not work with certain database providers, so it's important to check for compatibility before implementing it. - -In conclusion, the raw SQL queries for unmapped types feature in EF Core 8.0 is a great addition for developers looking for more flexibility in database queries. It allows us to work with data that may not be directly mapped to our entities and can be a useful tool in certain scenarios. Just remember to use parameterized queries and check for compatibility before implementing it. - -## References - -- [Raw SQL queries for unmapped types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#raw-sql-queries-for-unmapped-types) -- [SQL Queries](https://learn.microsoft.com/en-us/ef/core/querying/sql-queries#querying-scalar-(non-entity)-types) diff --git a/docs/en/Community-Articles/2023-10-28-EF-Core-8-Primitive-Collections/POST.md b/docs/en/Community-Articles/2023-10-28-EF-Core-8-Primitive-Collections/POST.md deleted file mode 100644 index 14a1e91cfd..0000000000 --- a/docs/en/Community-Articles/2023-10-28-EF-Core-8-Primitive-Collections/POST.md +++ /dev/null @@ -1,118 +0,0 @@ -# EF Core 8 Primitive collections - -What can we do when we want to store a list of primitive types? Before EF Core 8, there were two options: - -- Create a wrapper class and a related table, then add a foreign key linking each value to its owner of the collection. -- Use [value converter](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) to serialize-deserialize as JSON. - -The first option covers most scenarios if we need to add some additional properties to this type but let's say we're never gonna need this type additionality. - -## Which collection types are supported ? - -EF Core has the capability to map the `IEnumerable` public properties that have both a getter and a setter, with the `T` representing a primitive type - -```csharp -public class PrimitiveCollections -{ - public IEnumerable Ints { get; set; } - public ICollection Strings { get; set; } - public ISet DateTimes { get; set; } - public IList Dates { get; set; } - public uint[] UnsignedInts { get; set; } - public List Booleans { get; set; } - public List Urls { get; set; } -} -``` - -> Some generic arguments are not considered primitive on the database side, such as `uint` and `Uri`. However, these types are also considered as primitive because there are [built-in value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations#built-in-converters). - -## Demo - -In this sample, we have a `Car` class with a `Color` enum, and the `Car` class has a `Colors` property. - -```csharp -public enum Color -{ - Black, - White, - Red, - Blue -} - -public class Car -{ - public int Id { get; set; } - public string Brand { get; set; } - public string Model { get; set; } - public ISet Colors { get; set; } = new HashSet(); - - public Car(string brand, string model) - { - Brand = brand; - Model = model; - } -} -``` - -When we want to list the cars if they have any of the specific colors. - -```csharp -var colors = new HashSet { Color.Blue, Color.White }; -var cars = await context - .Cars - .Where(x=> x.Colors.Intersect(colors).Any()) - .ToListAsync(); -``` - -The SQL result looks like this; as you can see, it sends colors as parameters instead of adding them inline. It also uses the `json_each` function to deserialize on the database side. - -```sql -SELECT "c"."Id", "c"."Brand", "c"."Colors", "c"."Model" - FROM "Cars" AS "c" - WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT "c0"."value" - FROM json_each("c"."Colors") AS "c0" - INTERSECT - SELECT "c1"."value" - FROM json_each(@__colors_0) AS "c1" - ) AS "t") - -``` - -When we insert to the car table. - -```csharp -var car = new Car("Maserati", "GranTurismo") -{ - Colors = new HashSet() - { - Color.Black, - Color.Blue - } -}; -context.Cars.Add(car); -await context.SaveChangesAsync(); -``` - -The SQL statement looks like this, and as you can see, it automatically serializes into the Colors parameter as JSON. - -```sql - Executed DbCommand (0ms) [Parameters= - [@p0='Maserati' (Nullable = false) (Size = 8), - @p1='[0,3]' (Nullable = false) (Size = 5), - @p2='GranTurismo' (Nullable = false) (Size = 4) - ], CommandType='Text', CommandTimeout='30'] - INSERT INTO "Cars" ("Brand", "Colors", "Model") - VALUES (@p0, @p1, @p2) - RETURNING "Id"; -``` -## Conclusion - -We don't need to do anything if we just use a collection of primitive types. It serializes and deserializes them as JSON automatically. Additionally, it sends the primitive collection as a parameter to cache the query. - -## References - -- [EF Core 8 Primitive collections](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#primitive-collection-properties) -- [.NET Data Community Standup - Collections of primitive values in EF Core](https://www.youtube.com/watch?v=AUS2OZjsA2I) diff --git a/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md b/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md deleted file mode 100644 index db8f72ca17..0000000000 --- a/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md +++ /dev/null @@ -1,124 +0,0 @@ -# EF Core 8 - Enhancements to JSON column mapping - -In this article, we will examine the enhancements introduced in EF Core 8 for the JSON column feature, building upon the foundation laid by [JSON columns in Entity Framework Core 7](https://community.abp.io/posts/json-columns-in-entity-framework-core-7-cjaom76j). - -## The entity classes we will be using in the article - -```csharp -public class Person -{ - public int Id { get; set; } - [Required] - public string Name { get; set; } - [Required] - public ContactDetails ContactDetails { get; set; } -} - -public class ContactDetails -{ - public List
    Addresses { get; set; } = new(); - public string? Phone { get; set; } -} - -public class Address -{ - public Address(string street, string city, string postcode, string country) - { - Street = street; - City = city; - Postcode = postcode; - Country = country; - } - - public string Street { get; set; } - public string City { get; set; } - public string Postcode { get; set; } - public string Country { get; set; } - public bool IsMainAddress { get; set; } -} -``` - -## The DbContext class we will be using in the article - -```csharp -public class AppDbContext : DbContext -{ - public DbSet Persons { get; set; } = null!; - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { -#if SQLSERVER - optionsBuilder.UseSqlServer("Server=localhost;Database=EfCore8Json;Trusted_Connection=True;TrustServerCertificate=True"); -#elif SQLITE - optionsBuilder.UseSqlite("Data Source=EfCore8Json.db"); -#endif - base.OnConfiguring(optionsBuilder); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(b => - { - b.ToTable("Persons"); - b.HasKey(x => x.Id); - b.Property(x => x.Name).IsRequired(); - b.OwnsOne(x => x.ContactDetails, cb => - { - cb.ToJson(); - cb.Property(x => x.Phone); - cb.OwnsMany(x => x.Addresses); - }); - }); - - base.OnModelCreating(modelBuilder); - } -} -``` - -## Translate element access into JSON arrays - -EF Core 8 supports indexing in JSON arrays when executing queries. For example, the following query returns individuals whose first address is the main address in the database: - -```csharp -var query = dbContext.Persons - .Select(x => x.ContactDetails.Addresses[0]) - .Where(x => x.IsMainAddress == true) - .ToListAsync(); -``` - -The generated SQL query is as follows when using SQL Server: - -```sql -SELECT JSON_QUERY([p].[ContactDetails], '$.Addresses[0]'), [p].[Id] -FROM [Persons] AS [p] -WHERE CAST(JSON_VALUE([p].[ContactDetails], '$.Addresses[0].IsMainAddress') AS bit) = CAST(1 AS bit) -``` - -> Note: If you attempt to access an index that is outside of the array, it will return null. - -## JSON Columns for SQLite - -In EF Core 7, JSON column mapping was supported for Azure SQL/SQL Server. In EF Core 8, this support has been extended to include SQLite as well. - -### Queries into JSON columns - -The following query returns individuals whose first address is the main address in the database: - -```csharp -var query = dbContext.Persons - .Select(x => x.ContactDetails.Addresses[0]) - .Where(x => x.IsMainAddress == true) - .ToListAsync(); -``` - -The generated SQL query is as follows when using SQLite: - -```sql -SELECT "p"."ContactDetails" ->> '$.Addresses[0]', "p"."Id" -FROM "Persons" AS "p" -WHERE "p"."ContactDetails" ->> '$.Addresses[0].IsMainAddress' = 0 -``` - -## References - -- [EF Core 8 - Enhancements to JSON column mapping](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#enhancements-to-json-column-mapping) \ No newline at end of file diff --git a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md deleted file mode 100644 index 9142a36be9..0000000000 --- a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md +++ /dev/null @@ -1,196 +0,0 @@ -# .NET 8 - ASP.NET Core Metrics - -In this article, I'll show you the new built-in metrics of .NET 8, which are basically numerical measurements reported over time in your application and can be used to monitor the health of your application and generate reports according to those numerical values. We will see what the metrics are, why to use them, and how to use them in detail. So, let's dive in. - -## What are Metrics? - -Metrics are numerical measurements reported over time. These measurements are crucial for monitoring the health of an application and generating alerts when necessary. In the context of a web service, various metrics can be tracked, such as: - -* Requests per second. -* Response time. -* Status code counts etc... - -These metrics are not just collected; they are reported to a monitoring system at regular intervals. By doing so, the development and operations teams can visualize these metrics on dashboards. These dashboards provide a real-time overview of the application's performance and health. - -## Pre-Built Metrics - -ASP.NET Core has many built-in metrics. You can see the following figure for a list of built-in metrics for ASP.NET Core: - -![](built-in-metrics.png) - -> See https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics-aspnetcore and https://github.com/dotnet/aspnetcore/issues/47536 for all built-in metrics and their descriptions. - -For example, we have the `kestrel-current-connections` metric that shows the _number of connections that are currently active on the server_ and the `http-server-request-duration` metric that shows _the duration of HTTP requests on the server_. - -All of these and other built-in metrics are produced by using the [**System.Diagnostics.Metrics**](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics) API and with a small amount of code, we can use & view them. - -## Using Pre-Built Metrics - -Let's see the pre-built metrics in action. - -Create a new ASP.NET Core app with the following command and change the directory to the created application folder: - -```bash -dotnet new web -o MetricsDemo -cd MetricsDemo -``` - -After that, open the application in your favorite IDE and add the following service registration code to your application: - -```csharp -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddOpenTelemetry() - .WithMetrics(builder => - { - builder.AddPrometheusExporter(); - - builder.AddMeter("Microsoft.AspNetCore.Hosting", - "Microsoft.AspNetCore.Server.Kestrel"); - builder.AddView("http.server.request.duration", - new ExplicitBucketHistogramConfiguration - { - Boundaries = new double[] { 0, 0.005, 0.01, 0.025, 0.05, - 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 } - }); - }); -``` - -Here, we have done the following steps: - -* The `AddOpenTelemetry` method registers the required **OpenTelemetry** services into the DI container. -* In the `WithMetrics` method, we add pre-built metrics to the `OpenTelemetryBuilder` in the `AddMeter` method (_Microsoft.AspNetCore.Hosting_ and _Microsoft.AspNetCore.Server.Kestrel_ are pre-built metrics provided by ASP.NET Core). -* We have also used the `AddView` method to customize the output of the metrics by the SDK. It can also be used to customize which [Instruments](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument) are to be processed or ignored. - -After making the related configurations, let's add the middleware below and view the metrics: - -```csharp -app.MapPrometheusScrapingEndpoint(); - -app.MapGet("/", () => -{ - Thread.Sleep(2000); - - return "Hello, ABP Community member: " + DateTime.Now.Ticks.ToString()[^3..]; -}); -``` - -* `MapPrometheusScrapingEndpoint`: Adds the OpenTelemetry Prometheus scraping endpoint middleware to the pipeline and then we can see the all metrics by navigating to the **/metrics** endpoints (optional): - -![](metrics-endpoint.png) - -### View Metrics - -You can use the `dotnet-counters` command-line tool, which allows you to view live metrics for .NET Core apps. You can run the following command to install the tool: - -```bash -dotnet tool update -g dotnet-counters -``` - -After the tool is installed, you can run the application and by running the `dotnet-counters` tool in a terminal, you can monitor for the specific metrics: - -```bash -dotnet-counters monitor -n MetricsDemo --counters Microsoft.AspNetCore.Hosting -``` - -When you run the command, it will print a message like the one below and will wait for an initial request to your application: - -```txt -Press p to pause, r to resume, q to quit. - Status: Waiting for initial payload... -``` - -If you send a request to your application, then the related built-in metrics that you have added will be collected and will be shown in the terminal instantly: - -![](built-in-metric-response.png) - -Also, you can check for the other metric that we have used in the configuration, which is _Microsoft.AspNetCore.Server.Kestrel_ by setting it as a counter as in the command below, and when you send a request to your application, you will see different metrics: - -```bash -dotnet-counters monitor -n MetricsDemo --counters Microsoft.AspNetCore.Server.Kestrel -``` - -## Creating Custom Metrics in ASP.NET Core Applications - -So far, we have seen what metrics are, which built-in metrics are provided by ASP.NET Core, the use of the built-in metrics and running the `dotnet-counters` global tool to view these metrics. At this point, let's see how we can create custom metrics and view them via the `dotnet-counters` tool. - -`IMeterFactory` is the recommended service to create *Meter* instances. ASP.NET Core registers the `IMeterFactory` in dependency injection (DI) by default. The meter factory integrates metrics with DI, making isolating and collecting metrics easy. - -Assume that, we want to create a metric that holds the sold products in our application. For that purpose, as a first step, we can create a metric class as follows: - -```csharp -using System.Diagnostics.Metrics; - -namespace MetricsDemo; - -public class ProductMetrics -{ - private readonly Counter _soldProductsCount; - - public ProductMetrics(IMeterFactory meterFactory) - { - var meterInstance = meterFactory.Create("MetricsDemo.ProductStore"); - _soldProductsCount = meterInstance.CreateCounter("metricsdemo.productstore.sold_products_count"); - } - - public void IncreaseSoldProductCount(int quantity) - { - _soldProductsCount.Add(quantity); - } -} -``` - -In this class, we are creating a counter and defining metrics that we want to collect and view. `MetricsDemo.ProductStore` is the **counter name** and `metricsdemo.productstore.sold_products_count` is the **metric**. In our example, we need to register this class with a `Singleton` lifetime because we don't want to reset the counter and want to keep it during the application runtime. - -> The [System.Diagnostics.Metrics.Meter](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter) type is the entry point for a library to create a named group of instruments. Instruments record the numeric measurements that are needed to calculate metrics. Here we used [CreateCounter](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter.createcounter) to create a Counter instrument named _metricsdemo.productstore.sold_products_count_. - -So, register the type with the DI container in the `Program.cs` file (or you can implement the `ISingletonDependency` interface, if you are using an ABP based application): - -```csharp -builder.Services.AddSingleton(); -``` - -Then, we can inject this class and increase the sold product count whenever it's needed. Since we have registered it in DI, we can use it in minimal APIs as in the following example: - -```csharp -app.MapPost("/complete-sale", ([FromBody] ProductSoldModel model, ProductMetrics metrics) => -{ - // ... business logic such as saving the product to the database etc... ... - - metrics.IncreaseSoldProductCount(model.Quantity); -}); -``` - -Whenever a request is made to this endpoint the metric that we've defined (_metricsdemo.productstore.sold_products_count_) will be increased and we can see this metric via the `dotnet-counters` global tool. You can run the following command to see the metrics: - -```bash -dotnet-counters monitor -n MetricsDemo --counters MetricsDemo.ProductStore -``` - -Here is an example output that you would see if you have made a POST request to the `/complete-sale` endpoint: - -```txt -Press p to pause, r to resume, q to quit. - Status: Paused - -[MetricsDemo.ProductStore] - metricsdemo.productstore.sold_products_count (Count / 1 sec) 20 -``` - -### Advanced: View Metrics in Prometheus & Grafana - Building Dashboards - -It's a common requirement to build a dashboard and monitor the metrics and check the health of your applications. At that point, you can expose the metrics and scrape them using the [Prometheus](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/metrics?view=aspnetcore-8.0#set-up-and-configure-prometheus) and build a dashboard by using the [Grafana](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/metrics?view=aspnetcore-8.0#show-metrics-on-a-grafana-dashboard). - -If you want to build a dashboard, you can see [this documentation from Microsoft](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/metrics?view=aspnetcore-8.0#view-metrics-in-grafana-with-opentelemetry-and-prometheus), which shows how to set up the **Prometheus server** and show metrics on a **Grafana** dashboard. - -Also, you can check [this video](https://www.youtube.com/watch?v=A2pKhNQoQUU) if you want to see it in action. - -## Conclusion - -In this article, I've shown you the new built-in metrics of .NET8, described what metrics are, which pre-built metrics are provided by ASP.NET Core, how to use & view these pre-built metrics and finally, I have shown you how to create custom metrics and view them in a dashboard. If you want to learn more about the **Metrics System of .NET**, you can check out the references that I have shared below. - -## References - -* https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/metrics?view=aspnetcore-8.0 -* https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-5/ -* https://www.youtube.com/watch?v=A2pKhNQoQUU diff --git a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/built-in-metric-response.png b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/built-in-metric-response.png deleted file mode 100644 index ae5751c688..0000000000 Binary files a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/built-in-metric-response.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/built-in-metrics.png b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/built-in-metrics.png deleted file mode 100644 index 9fce4f3da0..0000000000 Binary files a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/built-in-metrics.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/metrics-endpoint.png b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/metrics-endpoint.png deleted file mode 100644 index ef0842ff90..0000000000 Binary files a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/metrics-endpoint.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md b/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md deleted file mode 100644 index 03ca906884..0000000000 --- a/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md +++ /dev/null @@ -1,156 +0,0 @@ -# New Minimal APIs features in ASP.NET Core 8.0 - -In this article, we will see the new features of Minimal APIs in ASP.NET Core 8.0. - -## Binding to forms - -We can bind to forms using the [FromForm] attribute. Let's see an example: - -```csharp -app.MapPost("/books", async ([FromForm] string name, - [FromForm] BookType bookType, IFormFile? cover, BookDb db) => -{ - var book = new Book - { - Name = name, - BookType = bookType - }; - - if (cover is not null) - { - var coverName = Path.GetRandomFileName(); - - using var stream = File.Create(Path.Combine("wwwroot", coverName)); - await cover.CopyToAsync(stream); - book.Cover = coverName; - } - - db.Books.Add(book); - await db.SaveChangesAsync(); - - return Results.Ok(); -}); -``` - -Another way is using the [AsParameters] attribute, the following code binds from form values to properties of the `NewBookRequest` record struct: - -```csharp -public record NewBookRequest([FromForm] string Name, [FromForm] BookType BookType, IFormFile? Cover); - -app.MapPost("/books", async ([AsParameters] NewBookRequest request, BookDb db) => -{ - var book = new Book - { - Name = request.Name, - BookType = request.BookType - }; - - if (request.Cover is not null) - { - var coverName = Path.GetRandomFileName(); - - using var stream = File.Create(Path.Combine("wwwroot", coverName)); - await request.Cover.CopyToAsync(stream); - book.Cover = coverName; - } - - db.Books.Add(book); - await db.SaveChangesAsync(); - - return Results.Ok(); -}); -``` - -## Antiforgery - -ASP.NET Core 8.0 adds support for antiforgery tokens. We can call the `AddAntiforgery` method to register the antiforgery services and `WebApplicationBuilder` will automatically add the antiforgery middleware to the pipeline: - -```csharp -var builder = WebApplication.CreateBuilder(); - -builder.Services.AddAntiforgery(); - -var app = builder.Build(); - -// Implicitly added by WebApplicationBuilder if AddAntiforgery is called. -// app.UseAntiforgery(); - -app.MapGet("/", () => "Hello World!"); - -app.Run(); -``` - -Example of using antiforgery tokens: - -```csharp - -// Use the antiforgery service to generate tokens. -app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) => -{ - var token = antiforgery.GetAndStoreTokens(context); - return Results.Content(...., "text/html"); -}); - -// It will automatically validate the token. -app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo)); - -// Disable antiforgery validation for this endpoint. -app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo)) - .DisableAntiforgery(); -``` - - -## Native AOT - -ASP.NET Core 8.0 adds support for Native AOT. - -You can add `PublishAot` to the project file to enable Native AOT: - -```xml - - true - -``` - -### Web API (native AOT) template - -You can use the `dotnet new webapiaot` command to create a new Minimal APIs project with Native AOT enabled. - -```diff -+using System.Text.Json.Serialization; - --var builder = WebApplication.CreateBuilder(); -+var builder = WebApplication.CreateSlimBuilder(args); - -+builder.Services.ConfigureHttpJsonOptions(options => -+{ -+ options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); -+}); - -var app = builder.Build(); - -var sampleTodos = TodoGenerator.GenerateTodos().ToArray(); - -var todosApi = app.MapGroup("/todos"); -todosApi.MapGet("/", () => sampleTodos); -todosApi.MapGet("/{id}", (int id) => - sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo - ? Results.Ok(todo) - : Results.NotFound()); - -app.Run(); - -+[JsonSerializable(typeof(Todo[]))] -+internal partial class AppJsonSerializerContext : JsonSerializerContext -+{ -+ -+} -``` - -* Reflection isn't supported in native AOT, you must use the `JsonSerializable` attribute to specify the types that you want to serialize/deserialize. - -## References - -* https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0#minimal-apis -* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding?view=aspnetcore-8.0#explicit-binding-from-form-values -* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0 diff --git a/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/POST.md b/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/POST.md deleted file mode 100644 index 22c621aa1b..0000000000 --- a/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/POST.md +++ /dev/null @@ -1,138 +0,0 @@ -# ASP.NET 8: What's New About Authentication and Authorization - -In ASP.NET 8, the concept of authentication and authorization is undergoing a transformation. Specifically, ASP.NET Core Identity is transitioning from a focus on traditional web pages to a more API-driven approach. We will see what the Identity API endpoints are, why we need them, and how to use them in detail. So, let's dive in. - -## What is ASP.NET Core Identity? - -ASP.NET Core Identity is like an extra toolkit that comes with ASP.NET Core. It offers a bunch of services to help you deal with user accounts in your ASP.NET Core application. These services include both basic concepts and ready-made solutions for each task. - -With ASP.NET Core Identity, you can save user accounts in your app, handle user information, add extra security with two-factor authentication, and even connect other log in options, like social media logins, to user accounts. In simple terms, it's all about keeping user accounts in your app and making it easy for users to log in. - -![](./identity_endpoints_scaffold.png) - -Prior to ASP.NET 8, there were limitations on using ASP.NET Identity in Single Page Applications (SPAs). Because the ASP.NET Core Identity framework is primarily tailored for traditional server-rendered web applications, like ASP.NET Core MVC or Razor Pages apps. However, the Identity API endpoints that come with ASP.NET 8 aim to solve token-based authentication and authorization in SPA without external dependencies. - -## Why do we need Identity APIs? - -The introduction of Identity endpoints in ASP.NET 8 aims to streamline the process of integrating ASP.NET Core Identity into both API server applications and front-end SPAs like those built with JavaScript or Blazor. To understand the importance of these endpoints, let's first examine the disadvantages of using ASP.NET Core Identity before ASP.NET 8. - -Let's delve into a common scenario: - -Imagine you have a straightforward application composed of an ASP.NET Core backend that exposes APIs. On the client side, you have a SPA application that communicates with these APIs. Now, you want to incorporate user accounts, complete with authentication and authorization, into your application. - -Before the advent of the identity endpoints, you could add ASP.NET Core Identity and the default Razor Pages UI to your app by adding a few packages, updating your database schema, and registering some services. However, this approach had some disadvantages. From a user experience perspective, the full-page refreshes of Razor Pages can be a major drawback especially compared to the fluidity of SPAs. On the other hand, from a developer's perspective, if you want the default pages to be compatible with the rest of your application, you may need to update more than 30 pages. Moreover, the default Razor Pages UI uses traditional cookie-based authentication, not bearer tokens. - -In ASP.NET 8, identity endpoints were introduced to simplify the process of adding user accounts to ASP.NET Core API apps used with SPAs or mobile. These endpoints streamline the user management process by providing an alternative to the traditional Razor Page Identity UI pages. Additionally, a new endpoint is included for retrieving bearer tokens, which can be used for authentication. These changes directly address the concerns mentioned earlier, making it easier to create a user-friendly and integrated experience within your app, with no full-page refreshes or styling conflicts. - -## How to add Identity APIs? - -In this part, I'll create a demo application, add all of the required packages, and lastly map the Identity APIs. - -Create a new ASP.NET Core app with the following command and change the directory to the created application folder: - -```bash -dotnet new webapi --name NewIdentityEndpoints -o IdentityDemo -cd IdentityDemo -``` - -After that, open the application in your favorite IDE and add the required packages: - -```csharp - - -``` - -**Note:** I used the `.NET 8.0.100-rc.2-**` SDK for everything in this post. - -Then replace `Program.cs` as below: - -```csharp -using System.Security.Claims; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme); -builder.Services.AddAuthorizationBuilder(); - -builder.Services.AddDbContext(options => options.UseInMemoryDatabase("AppDb")); - -builder.Services.AddIdentityCore() -.AddEntityFrameworkStores() -.AddApiEndpoints(); - -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - -var app = builder.Build(); - -app.MapGroup("/my-identity-api").MapIdentityApi(); - -app.MapGet("/", (ClaimsPrincipal user) => $"Hello {user.Identity!.Name}").RequireAuthorization(); - -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.Run(); - -class MyCustomUser : IdentityUser { } - -class ApplicationDbContext : IdentityDbContext -{ - public ApplicationDbContext(DbContextOptions options) : base(options) { } -} - -``` - -When you run the application, you will see a result as the following: - -![](./new-identity-endpoints.png) - -## Custom Authorization Policies - -Prior to ASP.NET 8, adding a parameterized authorization policy to an endpoint required writing a lot of code. - -- Implementing an `AuthorizeAttribute` for each policy -- Implementing an `AuthorizationPolicyProvider` to process a custom policy from a string-based contract -- Implementing an `AuthorizationRequirement` for the policy -- Implementing an `AuthorizationHandler` for each requirement - -However, implementing the `IAuthorizationRequirementData` interface that comes with ASP.NET 8, your application code should look like this: - -```csharp -class MinimumAgeAuthorizeAttribute : AuthorizeAttribute, IAuthorizationRequirement, IAuthorizationRequirementData -{ - public MinimumAgeAuthorizeAttribute(int age) => Age =age; - public int Age { get; } - - public IEnumerable GetRequirements() - { - yield return this; - } -} - -class MinimumAgeAuthorizationHandler : AuthorizationHandler -{ - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeAuthorizeAttribute requirement) - { - ... - } -} -``` - -See [here](https://gist.github.com/captainsafia/7c54e92d12df695ff0908e989fb8531f) for the complete code. - -## Conclusion - -In this article, I've shown you the Identity APIs introduced with ASP.NET 8, why we need them, and how we can add them to our application, and finally I explained how to define a custom authorization policy with fewer lines of code. - -## References -- https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0#authentication-and-authorization -- https://auth0.com/blog/whats-new-dotnet8-authentication-authorization/ -- https://andrewlock.net/exploring-the-dotnet-8-preview-introducing-the-identity-api-endpoints/ -- https://andrewlock.net/should-you-use-the-dotnet-8-identity-api-endpoints/ diff --git a/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/identity_endpoints_scaffold.png b/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/identity_endpoints_scaffold.png deleted file mode 100644 index 9330b47645..0000000000 Binary files a/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/identity_endpoints_scaffold.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/new-identity-endpoints.png b/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/new-identity-endpoints.png deleted file mode 100644 index 38ba0746f4..0000000000 Binary files a/docs/en/Community-Articles/2023-11-03-ASPNET8-authentication-and-authorization/new-identity-endpoints.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD deleted file mode 100644 index f11dec060d..0000000000 --- a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD +++ /dev/null @@ -1,189 +0,0 @@ -# Using Complex Types as Value Objects with Entity Framework Core 8.0 - -Entity Framework Core 8.0 is being shipped in a week as a part of .NET 8.0. In this article, I will introduce the new **[Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types)** feature of EF Core 8 and show some examples of how you can use it in your projects built with ABP Framework. - -## What is a Value Object? - -A [Value Object](https://docs.abp.io/en/abp/latest/Value-Objects) is a simple object that has no conceptual identity (Id). Instead, a Value Object is identified by its properties. - -A Value Object is typically owned by a parent [Entity](https://docs.abp.io/en/abp/latest/Entities) object. Using [Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) with EF Core 8 is the best way to create Value Objects that are stored as a part of your main entity. - -## Creating an ABP Project - -> **WARNING**: ABP Framework has not a .NET 8.0 compatible version yet. It is planned to be released on November 15, 2023. I created this project with ABP 7.4.1 (based on .NET 7.0), then manually changed the `TargetFramework` (in the `csproj`) to `net8.0` and added the `Microsoft.EntityFrameworkCore.SqlServer` package with version `8.0.0-rc.2.23480.1`. After that, the project is being compiled, but some parts of this article may not work as expected until ABP Framework 8.0-RC.1 is released. -> -> **I will update the article once ABP Framework 8.0-RC.1 is released.** - -I will show code examples, so I am creating a new ABP project using the following [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) command: - -````bash -abp new ComplexTypeDemo -t app-nolayers -```` - -> I prefer a non-layered project to keep the things simple. If you are new to ABP Framework, follow the [Getting Started](https://docs.abp.io/en/abp/latest/Getting-Started-Overall) tutorial to learn how to create a new project from scratch. - -Once I created the solution, I am running the following command to execute the database migrations in order to create the initial database: - -````bash -dotnet run --project ComplexTypeDemo --migrate-database -```` - -> We could also execute the `migrate-database.ps1` (that is coming as a part of the solution) as a shortcut. - -## Creating a Complex Type - -Assume that we have a `Customer` [entity](https://docs.abp.io/en/abp/latest/Entities) as shown below: - -````csharp -public class Customer : BasicAggregateRoot -{ - public string Name { get; set; } - public Address HomeAddress { get; set; } - public Address BusinessAddress { get; set; } -} -```` - -Here, we have two address properties, one for home and the other one for business. Since an address is a multi-values property, we can define it as a separate object: - -````csharp -public class Address -{ - public string City { get; set; } - public string Line1 { get; set; } - public string? Line2 { get; set; } - public string PostCode { get; set; } -} -```` - -`Address` is a typical complex object. It is actually a part of the `Customer` object, but we wanted to collect its parts into a dedicated class to make it a domain concept and easily manage its properties together. - -## Configure EF Core Mappings - -We should set the `Address` class as a Complex Type in our EF Core mapping configuration. There are two ways of it. - -As the first way, we can add the `ComplexType` attribute on top of the `Address` class: - -````csharp -[ComplexType] // Added this line -public class Address -{ - ... -} -```` - -As an alternative, we can configure the mapping using the fluent mapping API. You can write the following code into the `OnModelCreating` method of your `DbContext` class: - -````csharp -builder.Entity(b => -{ - b.ToTable("Customers"); - b.ComplexProperty(x => x.HomeAddress); // Mapping a Complex Type - b.ComplexProperty(x => x.BusinessAddress); // Mapping another Complex Type - //... configure other properties -}); - -```` - -You can further configure the properties of the `Address` class: - -````csharp -b.ComplexProperty(x => x.HomeAddress, a => -{ - a.Property(x => x.City).HasMaxLength(50).IsRequired(); -}); -```` - -Once you configure the mappings, you can use the [EF Core command-line tool](https://learn.microsoft.com/en-us/ef/core/cli/dotnet) to create a database migration: - -````bash -dotnet ef migrations add "Added_Customer_And_Address" -```` - -And update the database: - -````bash -dotnet ef database update -```` - -If you check the fields of the `Customers` table in your database, you will see the following fields: - -* `Id` -* `Name` -* `HomeAddress_City` -* `HomeAddress_Line1` -* `HomeAddress_Line2` -* `HomeAddress_PostCode` -* `BusinessAddress_City` -* `BusinessAddress_Line1` -* `BusinessAddress_Line2` -* `BusinessAddress_PostCode` - -As you see, EF Core stores the `Address` properties as a part of your main entity. - -## Querying Objects - -You can query entities from database using the properties of a complex type as same as you query by the properties of the main entity. - -**Example: Find customers by `BusinessAddress.PostCode`:** - -````csharp -public class MyService : ITransientDependency -{ - private readonly IRepository _customerRepository; - - public MyService(IRepository customerRepository) - { - _customerRepository = customerRepository; - } - - public async Task DemoAsync() - { - var customers = await _customerRepository.GetListAsync( - c => c.BusinessAddress.PostCode == "12345" - ); - - //... - } -} -```` - -## Mutable vs Immutable - -Entity Framework Core Complex Types can work with mutable and immutable types. In the `Address` example above, I've shown a simple mutable class - that means you can change an individual property of an `Address` object after creating it (or after querying from database). However, designing Value Objects as immutable is a highly common approach. - -For example, you can use C#'s `struct` type to define an immutable `Address` type: - -````csharp -public readonly struct Address(string line1, string? line2, string city, string postCode) -{ - public string City { get; } = city; - public string Line1 { get; } = line1; - public string? Line2 { get; } = line2; - public string PostCode { get; } = postCode; -} -```` - -See the [Microsoft's documentation](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#mutability) for more examples and different usages. - -## Final Notes - -There are more details about using Complex Types in your applications. I want to mention some of them here: - -* You can have nested complex types. For example, `Address` may have one or more `PhoneNumber` objects as its properties. Everything will work seamlessly. -* A single instance of a Complex Type can be set to multiple properties (of the same or different entities). In that case, changing the Complex object's properties will affect all of the properties. However, try to avoid that since it may create unnecessary complexities in your code that is hard to understand. -* You can manipulate mutable complex object properties just as another property in your entity. EF Core change tracking system will track them as you expect. -* Currently, EF Core doesn't support to have a collection of complex objects in an entity. It works only for properties. - -For more details and examples, see the Microsoft's document in the *References* section. - -## Source Code - -You can find the sample project here: - -[https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo](https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo) - -## References - -* [Value objects using Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) in [What's new with EF Core 8.0](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew) document by Microsoft. -* [ABP Entity Framework Core integration document](https://docs.abp.io/en/abp/latest/Entity-Framework-Core) - diff --git a/docs/en/Community-Articles/2023-11-06- SerializationDeserialization-Improvements/POST.md b/docs/en/Community-Articles/2023-11-06- SerializationDeserialization-Improvements/POST.md deleted file mode 100644 index 74022bd572..0000000000 --- a/docs/en/Community-Articles/2023-11-06- SerializationDeserialization-Improvements/POST.md +++ /dev/null @@ -1,100 +0,0 @@ -# .NET 8: Serialization Improvements - -The [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) namespace provides functionality for serializing to and deserializing from JSON. In .NET 8, there have been many improvements to the System.Text.Json serialization and deserialization functionality. - -## Source generator - -.NET 8 includes enhancements of the System.Text.Json source generator that are aimed at making the Native AOT experience on par with the reflection-based serializer. This is important because now you can select the source generator for System.Text.Json. To see a comparison of Reflection versus source generation in System.Text.Json, check out the comparison [documentation](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/reflection-vs-source-generation?pivots=dotnet-8-0#reflection). - -## Interface hierarchies - -.NET 8 adds support for serializing properties from interface hierarchies. Here is a code sample that shows this feature. - -```csharp -ICar car = new MyCar { Color = "Red", NumberOfWheels = 4 }; -JsonSerializer.Serialize(car); // {"Color":"Red","NumberOfWheels":4} - -public interface IVehicle -{ - public string Color { get; set; } -} - -public interface ICar : IVehicle -{ - public int NumberOfWheels { get; set; } -} - -public class MyCar : ICar -{ - public string Color { get; set; } - public int NumberOfWheels { get; set; } -} -``` - -## Naming policies - -Two new naming policies `snake_case` and `kebab-case` have been added. You can use them as shown below: - -```csharp -var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; -JsonSerializer.Serialize(new { CreationTime = new DateTime(2023,11,6) }, options); // {"creation_time":"2023-11-06T00:00:00"} -``` - -## Read-only properties - -With .NET 8, you can deserialize onto read-only fields or properties. This feature can be globally enabled by setting `PreferredObjectCreationHandling` to `JsonObjectCreationHandling.Populate`. You can also enable this feature for a class or one of its members by adding the `[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]` attribute. Here is a sample: - -```csharp -using System.Text.Json; -using System.Text.Json.Serialization; - -var book = JsonSerializer.Deserialize("""{"Contributors":["John Doe"],"Author":{"Name":"Sample Author"}}""")!; -Console.WriteLine(JsonSerializer.Serialize(book)); - -class Author -{ - public required string Name { get; set; } -} - -[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] -class Book -{ - // Both of these properties are read-only. - public List Contributors { get; } = new(); - public Author Author { get; } = new() {Name = "Undefined"}; -} -``` - -Before .NET 8, the output was like this: - -```json -{"Contributors":[],"Author":{"Name":"Undefined"}} -``` - -With .NET 8, the output now looks like this: - -```json -{"Contributors":["John Doe"],"Author":{"Name":"Sample Author"}} -``` - -## Disable reflection-based default - -One of the nice features about Serialization is, now you can disable using the reflection-based serializer by default. To disable default reflection-based serialization, set the `JsonSerializerIsReflectionEnabledByDefault` MSBuild property to `false` in your project file. - -## Streaming deserialization APIs - -.NET 8 includes new `IAsyncEnumerable` streaming deserialization extension methods. The new extension methods invoke streaming APIs and return `IAsyncEnumerable`. Here is a sample code that uses this new feature. - -```csharp -const string RequestUri = "https://yourwebsite.com/api/saas/tenants?skipCount=0&maxResultCount=10"; -using var client = new HttpClient(); -IAsyncEnumerable tenants = client.GetFromJsonAsAsyncEnumerable(RequestUri); - -await foreach (Tenant tenant in tenants) -{ - Console.WriteLine($"* '{tenant.name}' uses '{tenant.editionName -}' edition"); -} -``` - -I have mentioned some of the items for Serialization Improvements in .NET 8. If you want to check the full list, you can read it in the .NET 8's [What's new document](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#serialization). diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md deleted file mode 100644 index c1c38d7818..0000000000 --- a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md +++ /dev/null @@ -1,95 +0,0 @@ -# Blazor's History and Full-stack Web UI - -![Cover Image](cover-image.png) - - -Blazor is a web framework that allows developers to build interactive web applications using .NET instead of JavaScript. The first version of Blazor was released on May 14, 2020. Since its initial release, Blazor has evolved with the new versions. Until now, six different versions have been declared. Sometimes, it can be not very clear to see the differences between these approaches. First, let's try to understand these. - -* **Blazor-Server**: >> *Loads fast at first* >> In this version, heavy things are done in the server. Browsers are thin clients and download a small page for the first load. The page updates are done via SignalR connection. This was released with .NET Core 3. -* **Blazor WebAssembly (WASM):** >> *Loads slow at first* >> In this version, some binary files are being downloaded to the browser. This approach takes longer initialization time than the "Server" approach. The hard work is done on the browser. -* **Blazor Hybrid:** It's a combination of Blazor and Xamarin.Forms. It allows you to run your app on iOS, Android, macOS, and Windows. Blazor Hybrid uses a WebView component to host the Blazor-based user interface within the mobile app. -* **Blazor Native**: It runs natively on the devices and uses a common UI abstraction to render native controls for that device. This is very similar to how frameworks like Xamarin Forms or React Native work today. It has also been considered but has not reached [the planning stage](https://devblogs.microsoft.com/dotnet/blazor-server-in-net-core-3-0-scenarios-and-performance/). - -* **Blazor United**: Recently, Microsoft updated this name to "Fullstack web UI". Blazor-Server and Blazor WASM both have some disadvantages and advantages. So, Microsoft decided to combine these two approaches and find an optimum solution for the entire Blazor version. We can call it *Best of Blazor* 😀 - -## Why is "Blazor Fullstack Web UI" the best? - -These apps are a combination of both Blazor Server and Blazor WASM. It provides the advantages of Blazor Server and WASM. Developers would be able to more fine-tune the rendering mode. **This approach overcomes the large binary downloads of Blazor WASM, and it resolves the Blazor Server's problem, which always needs to be connected to the server via SignalR.** - -> Blazor Fullstack Web UI comes with the .NET 8, and it will be published on November 14, 2023. - -I took the following photo from Steven Sanderson's talk at NDC Porto 2023. You can read my impressions of this conf at https://volosoft.com/blog/ndc-port-2023-impressions, but after reading this one. - -![image-20231106163046763](image-20231106163046763-1699282281622-2.png) - - - -There are two basic page styles: - -* **HTML**; simple and loads fast! Eg: MVC, Razor Pages -* **Single Page Apps (SPA):** high interaction with the client and loads slower! Eg: Blazor WASM, Blazor Server... - -## HTML + SPA => Blazor Fullstack - -Blazor Fullstack (formerly United) is a technology that turns Blazor Server into a SPA style. - -.NET 8 will combine Blazor Server's server-side rendering and WebAssembly's client-side interaction. - -You can switch between two rendering modes and even mix them on the same page. With .NET 8 there also comes amazing features like; - -* [Streaming rendering](https://github.com/dotnet/aspnetcore/issues/46352): With this feature, most of the page will be rendered, and long async operations on the server will still be in progress. - -* [Progressive enhancement of form submission & navigation](https://github.com/dotnet/aspnetcore/issues/46399): With this feature, it doesn't fully reload the page after submitting the form. This gives the user a better and smoother experience. - - - -## How it works? - -### Rendering on Server - -You can add `WebComponentRenderMode.Server` to your Blazor components so that these components will run interactively. In the below example, the list editor will make AJAX requests to the server like single-page applications. - -![image-20231106172420148](image-20231106172420148.png) - - - -And sure, you can add `WebComponentRenderMode.Server` to your page level, and the complete page will be rendered as a server component. All inputs on this page can work as an interactive server component like SPA mode. - -![image-20231106172638604](image-20231106172638604.png) - - - -### Rendering on Client - -You can switch to WebAssembly mode by writing `WebComponentRenderMode.WebAssembly` attribute to your page. By doing so, the whole page should run interactively using WebAssembly. This time there's no server connection anymore because it loads the binaries (WebAssembly runtimes) at the page load. - -![image-20231106173021958](image-20231106173021958.png) - - -## Enabling the Blazor Fullstack UI? - -To enable Blazor Full-stack Web UI, you need to write `net8.0;net7.0-browser` into the `TargetFrameworks` area of your `csproj` file. These two keywords change your app like this; `net8.0` framework renders on the server, and `net7.0-browser` framework renders on the browser. - -![image-20231106173411309](image-20231106173411309.png) - - -## Let the System Decide WebAssembly or Server Approach - -You can let the system decide whether it uses `WebAssembly` or `Server`. This can be done with the `Auto` mode of the `WebComponentRenderMode`. In this case, it will not load binary files (WebAssembly files) for the initial page that has `WebComponentRenderMode.Server` attribute, but whenever the user navigates to a page that has `WebComponentRenderMode.WebAssembly`, it will download the runtimes. This will allow us to load the initial page very fast, and when we need interactivity, we can switch to `WebAssembly` and wait for the binaries to download. But this download will be done one time because it will be cached. - -![image-20231106173849303](image-20231106173849303.png) - - - -## Conclusion - -I summarized the new generation Blazor in a very simple way. This architecture will be useful to everyone who uses Blazor. - ---- - -*Resources:* - -* You can check Dan Roth's GitHub issue 👉 [github.com/dotnet/aspnetcore/issues/46636](https://github.com/dotnet/aspnetcore/issues/46636). -* Steven Sanderson's YouTube video is very good for understanding these concepts 👉 [Blazor United Prototype Video](https://youtu.be/48G_CEGXZZM). -* "Full Stack Web UI with Blazor" — .NET Conf 2023 video 👉 [learn.microsoft.com/en-us/shows/dotnetconf-2023/full-stack-web-ui-with-blazor-in-dotnet-8](https://learn.microsoft.com/en-us/shows/dotnetconf-2023/full-stack-web-ui-with-blazor-in-dotnet-8) - diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/cover-image.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/cover-image.png deleted file mode 100644 index dc8a251743..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/cover-image.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106163046763-1699282281622-2.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106163046763-1699282281622-2.png deleted file mode 100644 index 62c2bcea95..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106163046763-1699282281622-2.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106172420148.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106172420148.png deleted file mode 100644 index 62d2a704b8..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106172420148.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106172638604.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106172638604.png deleted file mode 100644 index ed48e95257..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106172638604.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173021958.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173021958.png deleted file mode 100644 index 6120776e90..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173021958.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173411309.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173411309.png deleted file mode 100644 index 2cc92a9ee3..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173411309.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173849303.png b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173849303.png deleted file mode 100644 index 13413ca55f..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/image-20231106173849303.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md deleted file mode 100644 index a3c143a9c8..0000000000 --- a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md +++ /dev/null @@ -1,79 +0,0 @@ -# HierarchyId Support in Entity Framework Core - -Entity Framework Core has official support for HierarchyId, which allows you to store and query hierarchical data in SQL Server databases. Hierarchical data is a common data structure found in many applications. Whether you are dealing with organizational structures, product categories, or threaded discussions, handling hierarchies efficiently is crucial. In this blog post, we will explore how to manage hierarchical data using Entity Framework Core (EF Core) in combination with HierarchyId. - -## How to use HierarchyId in EF Core - -To use HierarchyId in EF Core, you need to install the [Microsoft.EntityFrameworkCore.SqlServer.HierarchyId](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer.HierarchyId/) NuGet package. This package contains query and update support for HierarchyId. - -To configure EF Core to use HierarchyId, you need to call the `UseHierarchyId` method when configuring the SQL Server provider. - -```csharp -options.UseSqlServer( - connectionString, - x => x.UseHierarchyId()); -``` - -## Modeling Hierarchies - -The `HierarchyId` type can be used for properties of an entity type. For example, assume we want to model the personnel in an organization. Each person has a name and a manager. The manager is also a person. We can model this using the following entity type: - -```csharp -public class Person -{ - public int Id { get; set; } - public string Name { get; set; } - public HierarchyId Manager { get; set; } -} -``` - -Each person can be traced from the patriarch down the tree using its `Manager` property. SQL Server uses a compact binary format for these paths, but it is common to parse to and from a human-readable string representation when working with code. In this representation, the position at each level is separated by a `/` character. - -![Hierarchy Tree](hierarchy-tree.png) - -## Querying Hierarchies - -HierarchyId exposes several methods that can be used in LINQ queries. - -* `GetAncestor(int level)` returns the ancestor at the specified level. - -* `GetDescendant(HierarchyId? child1, HierarchyId? child2)` returns the descendant at the specified level. - -* `GetLevel()` returns the level of the node in the hierarchy. - -* `GetReparentedValue(HierarchyId? oldRoot, HierarchyId? newRoot)` returns the node with a new parent. - -* `IsDescendantOf(HierarchyId? parent)` returns whether the node is a descendant of the specified node. - -Example: - -```csharp -// Get entities at a given level in the tree -var generation = await context.Persons.Where(x => x.Manager.GetLevel() == level).ToListAsync(); - -// Get the direct ancestor of an entity -var parent = await context.Persons.Where(x => x.Manager.GetAncestor(1) == manager).SingleAsync(); -``` - -For example, your company may want to change the manager of an organizational unit. To do this, you can use the `GetReparentedValue` method to update the manager for all descendants of the organizational unit. - -```csharp -var newManager = await context.Persons.SingleAsync(x => x.Id == newManagerId); - -var descendants = await context.Persons - .Where(x => x.Manager.IsDescendantOf(oldManager)) - .ToListAsync(); - -foreach (var descendant in descendants) -{ - descendant.Manager = descendant.Manager.GetReparentedValue(oldManager.Manager, newManager.Manager); -} - -await context.SaveChangesAsync(); -``` - -## References - -For more information about hierarchy id, see the following resource: - -- [https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core) diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png deleted file mode 100644 index 8fb2e6e008..0000000000 Binary files a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png and /dev/null differ diff --git a/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md b/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md deleted file mode 100644 index c0bf748876..0000000000 --- a/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md +++ /dev/null @@ -1,101 +0,0 @@ -# Upgrade Your Existing Projects to .NET 8 & ABP 8.0 - -A new .NET version was released on November 14, 2023 and ABP 8.0 RC.1 shipped based on .NET 8.0 just after Microsoft's .NET 8.0 release. Therefore, it's a good time to see what we need to do to upgrade our existing projects to .NET 8.0. - -Despite all the related dependency upgrades and changes made on the ABP Framework and ABP Commercial sides, we still need to make some changes. Let's see the required actions that need to be taken in the following sections. - -## Installing the .NET 8.0 SDK - -To get started with ASP.NET Core in .NET 8.0, you need to install the .NET 8 SDK. You can install it at [https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). - -After installing the SDK & Runtime, you can upgrade your existing ASP.NET Core application to .NET 8.0. - -## Updating the Target Framework - -First, you need to update all your `*.csproj` files to support .NET 8. Find and replace all your `TargetFramework` definitions in the `*.csproj` files to support .NET 8.0: - -```xml -net8.0 -``` - -> This and all other changes mentioned in this article have already been done in the ABP Framework and ABP Commercial side, so you would not get any problems related to that. - -## Updating Microsoft Package Versions - -You are probably using some Microsoft packages in your solution, so you need to update them to the latest .NET 8.0 version. Therefore, update all `Microsoft.AspNetCore.*` and `Microsoft.Extensions.*` packages' references to `8.0.0`. - -## Checking the Breaking Changes in .NET 8.0 - -As I have mentioned earlier in this article, on the ABP Framework & ABP Commercial sides all the related code changes have been made, so you would not get any error related to breaking changes introduced with .NET 8.0. However, you still need to check the [Breaking Changes in .NET 8.0 documentation](https://learn.microsoft.com/en-us/dotnet/core/compatibility/8.0), because the breaking changes listed in this documentation still might affect you. Therefore, read them accordingly and make the related changes in your application, if needed. - -## Update Your Global Dotnet CLI Tools (optional) - -You can update the global dotnet tools to the latest version by running the `dotnet tool update` command. For example, if you are using EF Core, you can update your `dotnet-ef` CLI tool with the following command: - -```bash -dotnet tool update dotnet-ef --global -``` - -## Installing/Restoring the Workloads (required for Blazor WASM & MAUI apps) - -The `dotnet workload restore` command installs the workloads needed for a project or a solution. This command analyzes a project or solution to determine which workloads are needed and if you have a .NET MAUI or Blazor-WASM project, you can update your workloads by running the following command in a terminal: - -```bash -dotnet workload restore -``` - -## Docker Image Updates - -If you are using Docker to automate the deployment of applications, you also need to update your images. - -For example, you can update the ASP.NET Core image as follows: - -```diff -- FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim AS base -+ FROM mcr.microsoft.com/dotnet/aspnet:8.0 -``` - -You can check the related images from Docker Hub and update them accordingly: - -* [https://hub.docker.com/_/microsoft-dotnet-aspnet/](https://hub.docker.com/_/microsoft-dotnet-aspnet/) -* [https://hub.docker.com/_/microsoft-dotnet-sdk/](https://hub.docker.com/_/microsoft-dotnet-sdk/) -* [https://hub.docker.com/_/microsoft-dotnet-runtime/](https://hub.docker.com/_/microsoft-dotnet-runtime/) - -## Upgrading Your Existing Projects to ABP 8.0 - -Updating your application to ABP 8.0 is pretty straight-forward. You first need to upgrade the ABP CLI to version `8.0.0` using a command line terminal: - -````bash -dotnet tool update Volo.Abp.Cli -g --version 8.0.0 -```` - -**or install** it if you haven't before: - -````bash -dotnet tool install Volo.Abp.Cli -g --version 8.0.0 -```` - -Then, you can use the `abp update` command to update all the ABP related NuGet and NPM packages in your solution: - -```bash -abp update --version 8.0.0 -``` - -Also, if you are using ABP Commercial, you can update the ABP Suite version with the following command: - -```bash -abp suite update --version 8.0.0 -``` - -After that, you need to check the migration guide documents, listed below: - -* [ABP Framework 7.x to 8.0 Migration Guide](https://docs.abp.io/en/abp/8.0/Migration-Guides/Abp-8_0) -* [ABP Commercial 7.x to 8.0 Migration Guide](https://docs.abp.io/en/commercial/8.0/migration-guides/v8_0) - -> Check these documents carefully and make the related changes in your solution to prevent errors. - -## Final Words - -That's it! These were all the related steps that need to be taken to upgrade your application to .NET 8 and ABP 8.0. Now, you can enjoy the .NET 8 & ABP 8.0 and benefit from the performance improvements and new features. - -Happy Coding 🤗 diff --git a/docs/en/Community-Articles/2023-11-17-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-17-AOT-Compilation/Post.md deleted file mode 100644 index b39a2af339..0000000000 --- a/docs/en/Community-Articles/2023-11-17-AOT-Compilation/Post.md +++ /dev/null @@ -1,72 +0,0 @@ -# Native AOT Compilation in .NET 8 -Native AOT (Ahead-of-Time) compilation is a feature that allows developers to create a self-contained app compiled to native code that can run on machines without the .NET runtime installed. It results in benefits such as minimized disk footprint, reduced executable size, reduced startup time, and reduced memory demand. - -Native AOT compilation isn't a new feature in .NET 8. It's first introduced in .NET 7. - - -Differences between the AOT Compilation of .NET 7 and .NET 8 are: - - -- **System.Text.Json improvements**: .NET 8 adds support for more types, source generation, interface hierarchies, naming policies, read-only properties, and more. -- **New types for performance**: .NET 8 introduces new types such as FrozenDictionary, FrozenSet, SearchValues, CompositeFormat, TimeProvider, and ITimer to improve the app performance. -- **System.Numerics and System.Runtime.Intrinsics enhancements**: .NET 8 adds support for Vector512, AVX-512, IUtf8SpanFormattable, Lerp, and more. -- **System.ComponentModel.DataAnnotations additions**: .NET 8 adds new data validation attributes for cloud-native services and a new ValidateOptionsResultBuilder type. -- **Hosted services lifecycle methods**: .NET 8 adds new methods such as StartAsync, StopAsync, StartBackgroundAsync, and StopBackgroundAsync for hosted services. - -It's important to note that not all features in ASP.NET Core are currently compatible with native AOT. For more information, see [Native AOT deployment overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/). - -## How to use Native AOT Compilation in .NET 8 - -You can add `true` in your project .csproj file to enable Native AOT Compilation. - - - For the new projects, you can create them with the `--aot` parameter. Example: `dotnet new console --aot`. - -By default, the compiler chooses a blended approach code optimization but you can specify an optimization preference inside your .csproj file. You can choose **size** or **speed** according your requirements. - -```xml -Size -``` - -or - -```xml -Speed -``` - -### Results - -I have created a simple console application to test the Native AOT Compilation. I have used a simple console application that writes "Hello World!" to the console 100 times. I have tested the application with different optimization preferences. I have used the following results: - - -| | Size | Speed | -| --- | --- | --- | -| .NET 8
    _(Self-Contained, Single File)_ | 65938 kb | 00.0051806 ~5ms | -| .NET 7 AOT (default) | 4452 kb | 00.0029823 ~2ms | -| .NET 8 AOT (default) | 1242 kb | 00.0028638 ~2ms | -| AOT (Speed)| 1280 kb | 00.0023838 ~2ms | -| AOT (Size) | 1111 kb | 00.0025145 ~2ms | - -Most of existing libraries don't support AOT compilation yet, so I couldn't use [BenchmarkDotnet](https://github.com/dotnet/BenchmarkDotNet) to measure the performance. I have used [Stopwatch](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=net-8.0) to measure the performance. So the performance results may not be accurate but gives insight about the performance difference. - -## AOT Support in MAUI -You can now use Native AOT Compilation on iOS-like target frameworks in .NET MAUI. You can enable AOT compilation with the exact same method by adding `true` to your project .csproj file. According to the dotnet team, apps sizes reduced by 35% and startup times reduced by 28% with AOT compilation. And runtime performance is also improved by 50%. - -But there are some limitations in MAUI AOT Compilation. A lot of libraries still don't support AOT compilation and some of platform-specific feaetures may not work at the moment. - -## When to use Native AOT Compilation? - -Native AOT Compilation is beneficial when you need to optimize your .NET application for speed and size. It's particularly useful for applications that require quick startup times and efficient runtime performance, such as mobile apps or high-performance computing applications. - -However, due to its current limitations, it might not be suitable for all projects. If your project relies heavily on libraries that do not support AOT compilation, or if it uses platform-specific features that are not yet compatible with AOT, you might want to hold off on using Native AOT Compilation until further improvements are made. - -Always consider the specific needs and constraints of your project before deciding to use Native AOT Compilation. - -## Conclusion - -Native AOT Compilation is a great feature that improves the performance of .NET applications. It's still in early-stages and not all libraries support it yet. But it's a great beginning for the future of .NET 🚀 - -## Links -- Native AOT deployment overview - .NET | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/. -- Optimize AOT deployments https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/optimizing -- What's new in .NET 8 | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8. - diff --git a/docs/en/Community-Articles/2024-01-04-CLI-Online-Translate/Post.md b/docs/en/Community-Articles/2024-01-04-CLI-Online-Translate/Post.md deleted file mode 100644 index 285d7d5dc9..0000000000 --- a/docs/en/Community-Articles/2024-01-04-CLI-Online-Translate/Post.md +++ /dev/null @@ -1,72 +0,0 @@ -# Use Deepl to translate localization files - -Translating localized text during the development of ABP modules is a boring job. For this reason, we have added the `translate` command to the CLI tool to translate the localization files, but it still requires manual translation of texts by the developer. - -Now, we introduce a new way to translate the target language, which is to use the [Deepl](https://www.deepl.com/translator) translation service to translate the target language. - -You can use the `translate --online` command in your module directory to translate the target language. - -For example, if you have added all `en` localization texts to your module, and you want to translate them to `zh-Hans`, you can run the following command: - -``` -abp translate -r en -c zh-Hans --online --deepl-auth-key your_auth_key -``` - -* `-r` parameter is used to specify the source language. It is usually a localized file that you have created. -* `-c` parameter is used to specify the target language. It is usually a language that you want to translate. -* `--online` parameter is used to indicate that the translation is performed from the Deepl service. -* `--deepl-auth-key` parameter is the API key of your Deepl account. You can get it from [here](https://support.deepl.com/hc/en-us/articles/360020695820-Authentication-Key). - - -The output of the above command is as follows: - -``` -ABP CLI 8.0.0 -Abp translate online... -Target culture: zh-Hans -Reference culture: en -Create translation: Settings => 设置 -Create translation: SuccessfullySaved => 成功保存 -Create translation: Permission:SettingManagement => 设置管理 -Create translation: Permission:Emailing => 发送电子邮件 -Create translation: Permission:EmailingTest => 电子邮件测试 -Create translation: Permission:TimeZone => 时区 -Create translation: SendTestEmail => 发送测试电子邮件 -Create translation: SenderEmailAddress => 发件人电子邮件地址 -Create translation: TargetEmailAddress => 目标电子邮件地址 -Create translation: Subject => 主题 -Create translation: Body => 正文 -Create translation: TestEmailSubject => 测试电子邮件 {0} -Create translation: TestEmailBody => 在此测试电子邮件正文信息 -Create translation: SuccessfullySent => 成功发送 -Create translation: Send => 发送 -Create translation: Menu:Emailing => 发送电子邮件 -Create translation: Menu:TimeZone => 时区 -Create translation: DisplayName:Timezone => 时区 -Create translation: TimezoneHelpText => 此设置用于应用程序范围或基于租户的设置。 -Create translation: SmtpHost => 主机 -Create translation: SmtpPort => 端口 -Create translation: SmtpUserName => 用户名 -Create translation: SmtpPassword => 密码 -Create translation: SmtpDomain => 域名 -Create translation: SmtpEnableSsl => 启用 ssl -Create translation: SmtpUseDefaultCredentials => 使用默认凭据 -Create translation: DefaultFromAddress => 默认地址 -Create translation: DefaultFromDisplayName => 显示名称的默认值 -Create translation: Feature:SettingManagementGroup => 设置管理 -Create translation: Feature:SettingManagementEnable => 启用设置管理 -Create translation: Feature:SettingManagementEnableDescription => 在应用程序中启用设置管理系统。 -Create translation: Feature:AllowChangingEmailSettings => 允许更改电子邮件设置。 -Create translation: Feature:AllowChangingEmailSettingsDescription => 允许更改电子邮件设置。 -Write translation json to setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hans.json. -``` - -The generated `zh-Hans.json` as follow: - -![zh-Hans.json](deepl.jpg) - -In this example, It only translates one `en.json` to `zh-Hans.json`, but if there are multiple `en.json` files in the module, it will translate all `en.json` files to `zh-Hans.json`. - -Of course, the translation is not always correct, you can update the translation in the generated `zh-Hans.json` files. - -Enjoy it! diff --git a/docs/en/Community-Articles/2024-01-04-CLI-Online-Translate/deepl.jpg b/docs/en/Community-Articles/2024-01-04-CLI-Online-Translate/deepl.jpg deleted file mode 100644 index b944b25c51..0000000000 Binary files a/docs/en/Community-Articles/2024-01-04-CLI-Online-Translate/deepl.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/Post.md b/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/Post.md deleted file mode 100644 index a45f893b16..0000000000 --- a/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/Post.md +++ /dev/null @@ -1,173 +0,0 @@ -![cover](cover.png) - -# ABP Now Supports .NET 8 - -Recently we have published ABP v8.0. With this version [the ABP Framework](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj#L6) and ABP Commercial both supports for .NET 8, aligning itself with the latest enhancements and new features of the ASP.NET's new version 8. - -Here's the [related PR](https://commercial.abp.io/releases/pr/15676) for this upgrade. This update ensures that developers using ABP can leverage the new features and improvements of .NET 8, enhancing the capability and performance of their applications. With .NET 8 support, ABP 8.0 likely offers improved performance, enhanced security, and greater efficiency, making it a robust choice for building modern web applications. -Here's the summary of .NET 8 features and enhancements: - -## What's new in .NET 8 - -### .NET Aspire - -[.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/) is a tool to observe and manage distributed web applications. It's still preview version. You can manage your containers, executables, logs, traces and metrics of your running web application. For more information see this article https://devblogs.microsoft.com/dotnet/introducing-dotnet-aspire-simplifying-cloud-native-development-with-dotnet-8/ - -### Serialization - -12 features have been implemented for [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) serialization and deserialization library with .NET 8. See these enhancements -* [Built-in support for additional types](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#built-in-support-for-additional-types) -* [Source generator](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#source-generator) -* [Interface hierarchies](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#interface-hierarchies) -* [Naming policies](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#naming-policies) -* [Read-only properties](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#read-only-properties) -* [Disable reflection-based default](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#disable-reflection-based-default) -* [New JsonNode API methods](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#new-jsonnode-api-methods) -* [Non-public members](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#non-public-members) -* [Streaming deserialization APIs](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#streaming-deserialization-apis) -* [WithAddedModifier extension method](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#withaddedmodifier-extension-method) -* [New JsonContent.Create overloads](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#new-jsoncontentcreate-overloads) -* [Freeze a JsonSerializerOptions instance](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#freeze-a-jsonserializeroptions-instance) - -### Time abstraction - -You may mock time in test scenarios with the new [TimeProvider class](https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider?view=net-8.0) and [ITimer interface](https://learn.microsoft.com/en-us/dotnet/api/system.threading.itimer?view=net-8.0), which add time abstraction functionality. - -### UTF8 improvements - -The new [IUtf8SpanFormattable](https://learn.microsoft.com/en-us/dotnet/api/system.iutf8spanformattable) interface targets `UTF8` and `Span` instead of `UTF16` and `Span`. Also the new [Utf8.TryWrite](https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.utf8.trywrite) methods provide a `UTF8` based counterpart to the existing [MemoryExtensions.TryWrite](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.trywrite) methods, which are UTF16-based. - -### Data validation - -There are new validation attributes for cloud-native services. The new properties were added to the [RangeAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute) and [RequiredAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.requiredattribute) types. See the list below for the new validation attributes: - -| New Data Attribute | Description | -| ------------------------------------------------------------ | ------------------------------------------------------------ | -| [MinimumIsExclusive](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute.minimumisexclusive#system-componentmodel-dataannotations-rangeattribute-minimumisexclusive), [MaximumIsExclusive](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute.maximumisexclusive#system-componentmodel-dataannotations-rangeattribute-maximumisexclusive) | Checks whether bounds are included in the allowable range | -| [Length](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.lengthattribute) | Checks both lower and upper bounds for strings or collections. For example, `[Length(5, 10)]` requires at least 5 elements and at most 10 elements in a collection | -| [Base64String](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.base64stringattribute) | Checks that a string is a valid Base64 format | -| [AllowedValues](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.allowedvaluesattribute), [DeniedValues](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.deniedvaluesattribute) | Checks if values are from the allowed or disallowed list eg: `[AllowedValues("man", "woman", "child")]` | - - - -### Randomness methods - -The [System.Random](https://learn.microsoft.com/en-us/dotnet/api/system.random) and [System.Security.Cryptography.RandomNumberGenerator](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator) types present 2 new approaches to handling randomness. - -### Performance-focused types - -* The new [System.Collections.Frozen](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen) namespace includes the collection types [FrozenDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozendictionary-2) and [FrozenSet](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozenset-1). -* Methods like [MemoryExtensions.IndexOfAny](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.indexofany) look for the first occurrence of *any value in the passed collection*. -* The new [System.Text.CompositeFormat](https://learn.microsoft.com/en-us/dotnet/api/system.text.compositeformat) type is useful for optimizing format strings that aren't known at compile time (for example, if the format string is loaded from a resource file). -* New [System.IO.Hashing.XxHash3](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash3) and [System.IO.Hashing.XxHash128](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash128) types provide implementations of the fast XXH3 and XXH128 hash algorithms. - -### System.Numerics and System.Runtime.Intrinsics - -The [System.Numerics](https://learn.microsoft.com/en-us/dotnet/api/system.numerics) and [System.Runtime.Intrinsics](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics) namespaces introduces some hardware improvements with .NET 8. The following structs reimplemented for running with the best performance on ARM64. - -[Vector256](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256-1), [Matrix3x2](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.matrix3x2), and [Matrix4x4](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.matrix4x4) have improved - -### Metrics - -New APIs let you attach key-value pair tags to [Meter](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter) and [Instrument](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.instrument) objects when you create them. Aggregators of published metric measurements can use the tags to differentiate the aggregated values. - -### Cryptography - -[SHA-3](https://en.wikipedia.org/wiki/SHA-3) (Secure Hash Algorithm 3) is the latest member of the Secure Hash Algorithm family of standards. With .NET 8 SHA-3 is being supported. - -### HTTPS proxy support - -Before [HttpClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient) allows hackers to make [man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) with .NET 8, it creates an encrypted channel between the client and the proxy so all requests can be handled with full privacy. - -### Stream-based ZipFile methods - -The new [ZipFile.ExtractToDirectory](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.zipfile.extracttodirectory) provides a stream containing a zipped file and extract its contents into the filesystem. - -### Hosted lifecycle services - -Hosted services now have more options for execution during the application lifecycle. [IHostedService](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedservice) provided `StartAsync` and `StopAsync`, and now [IHostedLifecycleService](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice) provides these additional methods: - -- [StartingAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.startingasync#microsoft-extensions-hosting-ihostedlifecycleservice-startingasync(system-threading-cancellationtoken)) -- [StartedAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.startedasync#microsoft-extensions-hosting-ihostedlifecycleservice-startedasync(system-threading-cancellationtoken)) -- [StoppingAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.stoppingasync#microsoft-extensions-hosting-ihostedlifecycleservice-stoppingasync(system-threading-cancellationtoken)) -- [StoppedAsync(CancellationToken)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice.stoppedasync#microsoft-extensions-hosting-ihostedlifecycleservice-stoppedasync(system-threading-cancellationtoken)) - -### Native AOT support - -The option to [publish as Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) was first introduced in .NET 7. Publishing an app with Native AOT creates a fully self-contained version of your app that doesn't need a runtime—everything is included in a single file. .NET 8 brings the following improvements to Native AOT publishing: - -| Operating system | .NET 7 | .NET 8 | -| :-------------------------------------- | :------ | :------ | -| Linux x64 (with `-p:StripSymbols=true`) | 3.76 MB | 1.84 MB | -| Windows x64 | 2.85 MB | 1.77 MB | - -### Target iOS-like platforms with Native AOT - -.NET 8 starts the work to enable Native AOT support for iOS-like platforms. You can now build and run .NET iOS and .NET MAUI applications with Native AOT on the following platforms: - -- `ios` -- `iossimulator` -- `maccatalyst` -- `tvos` -- `tvossimulator` - -### Performance improvements - -.NET 8 includes improvements to code generation and just-in time (JIT) compilation: - -- Arm64 performance improvements -- SIMD improvements -- Support for AVX-512 ISA extensions (see [Vector512 and AVX-512](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#vector512-and-avx-512)) -- Cloud-native improvements -- JIT throughput improvements -- Loop and general optimizations -- Optimized access for fields marked with [ThreadStaticAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.threadstaticattribute) -- Consecutive register allocation. Arm64 has two instructions for table vector lookup, which require that all entities in their tuple operands are present in consecutive registers. -- JIT/NativeAOT can now unroll and auto-vectorize some memory operations with SIMD, such as comparison, copying, and zeroing, if it can determine their sizes at compile time. - - - -### .NET SDK - -This section contains the following subtopics: - -- [CLI-based project evaluation](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#cli-based-project-evaluation) -- [Terminal build output](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#terminal-build-output) -- [Simplified output paths](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#simplified-output-paths) -- ['dotnet workload clean' command](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#dotnet-workload-clean-command) -- ['dotnet publish' and 'dotnet pack' assets](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#dotnet-publish-and-dotnet-pack-assets) -- [Template engine](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#template-engine) -- [Source Link](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#source-link) -- [Source-build SDK](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#source-build-sdk) - -### ASP.NET composite images - -As part of an effort to improve containerization performance, new ASP.NET Docker images are available that have a composite version of the runtime. This composite is built by compiling multiple MSIL assemblies into a single ready-to-run (R2R) output binary. Because these assemblies are embedded into a single image, jitting takes less time, and the startup performance of apps improves. The other big advantage of the composite over the regular ASP.NET image is that the composite images have a smaller size on disk. - -### Source-generated COM interop - -.NET 8 includes a new source generator that supports interoperating with COM interfaces. - -### Minimum support baselines for Linux - -The minimum support baselines for Linux have been updated for .NET 8. .NET is built targeting Ubuntu 16.04, for all architectures. - -### AOT compilation for Android apps - -To decrease app size, .NET and .NET MAUI apps that target Android use *profiled* ahead-of-time (AOT) compilation mode when they're built in Release mode. Profiled AOT compilation affects fewer methods than regular AOT compilation. .NET 8 introduces the `` property that lets you opt-in to further AOT compilation for Android apps to decrease app size even more. - -### Code analysis - -.NET 8 includes several new code analyzers and fixers to help verify that you're using .NET library APIs correctly and efficiently. The following table summarizes the new analyzers. - -### NuGet - -Starting in .NET 8, NuGet verifies signed packages on Linux by default. NuGet continues to verify signed packages on Windows as well. - -### C# Hot Reload supports modifying generics - -Starting in .NET 8, C# Hot Reload [supports modifying generic types and generic methods](https://devblogs.microsoft.com/dotnet/hot-reload-generics/). When you debug console, desktop, mobile, or WebAssembly applications with Visual Studio, you can apply changes to generic classes and generic methods in C# code or Razor pages. For more information, see the [full list of edits supported by Roslyn](https://github.com/dotnet/roslyn/blob/main/docs/wiki/EnC-Supported-Edits.md) - -*References:* - -* https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8 \ No newline at end of file diff --git a/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/cover.png b/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/cover.png deleted file mode 100644 index 15a121a959..0000000000 Binary files a/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/cover.png and /dev/null differ diff --git a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md b/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md deleted file mode 100644 index a5f2c694ed..0000000000 --- a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md +++ /dev/null @@ -1,198 +0,0 @@ -# ABP Now Supports Keyed Services! - -In this post, I describe the new **"keyed service"** support for the dependency injection container, which came with .NET 8.0. Then, I'll show you an example of usage within the ABP Framework. - -## What Are Keyed Services? - -ASP.NET Core ships with a built-in dependency injection container, which is a pretty basic DI container that supports minimal features a dependency injection container is supposed to have. For that reason, most of the .NET users use third-party containers like [Autofac](https://autofac.org/), or Ninject. - -> ABP Framework uses Autofac by default in startup templates, and it supports more features. For example, the built-in DI container does not natively support property injection but Autofac does. - -[**Keyed dependency injection (DI) services**](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#keyed-services) were added to the built-in DI container as a new feature with .NET 8.0. This is an important feature, which allows for registering and retrieving DI services using keys/names. - -Prior to this version, you could register multiple services as follows: - -```csharp -var builder = WebApplication.CreateBuilder(args); - -//service registrations -builder.Services.AddTransient(); -builder.Services.AddTransient(); -builder.Services.AddTransient(); -``` - -But without keyed services, you could only retrieve all of the services like this: - -```csharp -public class BookService(IEnumerable services) {} -``` - -Or the last registered one (`MyServiceThree` in our case): - -```csharp -public class BookService(IMyService service) {} -``` - -There was not a simple way to retrieve a specific implementation type directly. With keyed services, now we can register services with a key and then resolve them by the key anywhere we want and retrieve the only one that we want to use. - -## ABP Framework Now Supports Keyed Services! - -Autofac was already supporting the named/keyed DI services for a long time and with v9.0.0, they made it compatible with the `Microsoft.Extensions.DependencyInjection` package (including the keyed service support). - -After the v9.0.0 was released for Autofac, then the ABP Framework Core team immediately updated the `Autofac.Extensions.DependencyInjection` package to `v9.0.0`, made the related changes in its own Autofac package and included in the v8.0.2 release. - -You can see the related changes in the following PRs: - -* https://github.com/abpframework/abp/pull/18775 -* https://github.com/abpframework/abp/pull/18777 - -## Using Keyed Services with ABP Framework - -After the quick background of the process and the feature itself, now let's see the keyed services in action. - -First, update the ABP CLI to v8.0.2 with the following command (if your CLI version is newer, no need to apply this): - -```bash -dotnet tool update -g Volo.Abp.Cli --version 8.0.2 -``` - -Then, we can create an application template (single-layer) with the following command (MVC as the UI option and EF Core as the DB provider): - -```bash -abp new KeyedServiceDemo -t app-nolayers -csf --version 8.0.2 -``` - -After the application is created, we can open it in an IDE and start developing... - -**Example**: - -Let's assume that we have an interface called `IMyService`, which has three implementation types `MyServiceOne`, `MyServiceTwo` and `MyServiceThree`: - -```csharp -public interface IMyService -{ - string GetMessage(); -} - -public class MyServiceOne : IMyService -{ - public string GetMessage() => "MyServiceOne"; -} - -public class MyServiceTwo : IMyService -{ - public string GetMessage() => "MyServiceTwo"; -} - -public class MyServiceThree : IMyService -{ - public string GetMessage() => "MyServiceThree"; -} -``` - -After creating our services, now we can register them in our module class. So, open the module class and add the following lines in the `ConfigureServices` method: - -```csharp - public override void ConfigureServices(ServiceConfigurationContext context) - { - //code abbreviation for clarity... - - context.Services.AddKeyedTransient("myserviceone"); - context.Services.AddKeyedTransient("myservicetwo"); - context.Services.AddKeyedTransient("myservicethree"); - } -``` - -* To register a keyed service, you can use one of the `AddKeyedTransient`, `AddKeyedScoped`, or `AddKeyedSingleton` overloads, and provide a key for the registration. -* In the example above, I used `AddKeyedTransient` overload, and registered all of these services as in the `Transient` lifetime. - -Then, when you want to retrieve a keyed service, you can use the `[FromKeyedServices(object key)]` attribute: - -```csharp -public class NotificationService([FromKeyedServices("myserviceone")] IMyService myServiceOne) -{ - public string Notify() => myServiceOne.GetMessage(); -} -``` - -With this kind of use, you can be certain that not the latest registered service is resolved and instead the keyed service will be resolved, and then you can use it. - -Also, you can use the `FromKeyedServicesAttribute` with minimal APIs as follows: - -```csharp -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - //code abbreviation for clarity... - - app.UseConfiguredEndpoints(endpoints => - { - endpoints.MapGet("/my-service", ([FromKeyedServices("myserviceone")] IMyService myservice) => myservice.GetMessage()); - }); -} -``` - -That's how it's easy to register multiple services in a keyed fashion and resolve them by keys! - -## Advanced - -In this section, I want to briefly mention some advanced topics (relatively 😀), such as what happens if I register more than one service with the same key, or is there a way to get the keyed services from the `ServiceProvider` etc. - -### Registering Multiple Services with the Same Key - -Let's assume that you mistakenly registered multiple services with the same key: - -```csharp - public override void ConfigureServices(ServiceConfigurationContext context) - { - //code abbreviation for clarity... - - context.Services.AddKeyedTransient("myserviceone"); - context.Services.AddKeyedTransient("myserviceone"); - } -``` - -In that case, when you try to resolve the dependency with the key, then the last registered one will be used without having a problem: - -```csharp -//it will resolve the `MyServiceTwo` service! -endpoints.MapGet("/my-service", ([FromKeyedServices("myserviceone")] IMyService myservice) => myservice.GetMessage()); -``` - -Also, since you used the same key for multiple services, then you can inject the `IEnumerable` to get all services for a certain key and use them: - -```csharp -//it will resolve both `MyServiceOne` and `MyServiceTwo` services! -endpoints.MapGet("/my-service", ([FromKeyedServices("myserviceone")] IEnumerable myservices) => -{ - var sb = new StringBuilder(); - foreach (var myService in myservices) - { - sb.AppendLine(myService.GetMessage()); - } - - return sb.ToString(); -}); -``` - -### Resolving Keyed Services from ServiceProvider & LazyServiceProvider - -You can resolve keyed services by using one of the extension methods (`.GetKeyedServices`, `.GetKeyedService<>`, `.GetRequiredKeyedService<>`, ...): - -```csharp -//uses the `MyServiceTwo` service! -var myService = ServiceProvider.GetRequiredKeyedService("myserviceone"); -``` - -On the other hand, resolving keyed services from `LazyServiceProvider` is not supported in v8.0.2, but there is a PR, which you can find at https://github.com/abpframework/abp/pull/18792, it will fix this problem and it will be included in the next version. - -### Automatically Registering Keyed Services - -Currently, if you want to register a keyed service, you need to do it manually as we see in the previous sections by using one of the overloads (`.AddKeyedTransient`, `.AddKeyedScoped` and `.AddKeyedSingleton`). - -It would be good if we could make this process automatically and not need to manually register services, and for that purpose, I have [created an issue](https://github.com/abpframework/abp/issues/18794) that aims to introduce an attribute, which allows us to automatically register multiple services as keyed services. - -You can [follow the issue](https://github.com/abpframework/abp/issues/18794) if you are considering using keyed services in your application and don't want to register them manually. - -## Summary - -In this post, I described the new **"keyed service"** support added to the built-in dependency injection container that was released in .NET 8, and wanted to announce it's being supported from v8.0.2 for ABP Framework. It's a really good addition to the built-in dependency injection container and can be pretty useful for certain use-cases. diff --git a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/cover-image.png b/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/cover-image.png deleted file mode 100644 index aec71f7d7a..0000000000 Binary files a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/cover-image.png and /dev/null differ diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/POST.md b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/POST.md deleted file mode 100644 index 2b55a0002c..0000000000 --- a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/POST.md +++ /dev/null @@ -1,266 +0,0 @@ - -# Global Error Handling in Angular - -![Error Handling](error_handling.jpg) - -Error handling is how we deal with errors that go wrong when we are running a program. There is no code that runs perfectly forever :) Things can go wrong and your application might crash. **So, in order to run your program smoothly you must handle errors.** It is just not for keeping your application in a running state. It is also useful to show messages about the error to the client. Like what went wrong, why it is not allowed to access this page etc. - -### How to handle errors - -- First of all, you have to catch them 😀. You can catch them via **try-catch block**. See an example; -#### Create an basic error -```ts -({} as any).doSomething() -``` - -![Error Image](error-image-1.png) - -#### Handle it -```ts -try { - ({} as any).doSomething(); -} catch (error) { - this.toastService.showError(error.message); -} -``` - -![toast-gif](show-toast-2.gif) - -- See, we catch the error and handle it. -- In this case, we know where the error will be thrown. Most of the time we won't know where the error will appear. Should we cover the entire application with try-catch blocks? Of course not 😀 -- **We are going to handle errors globally**. Angular provides a great way to do it. Let's do it step by step; - -### 1.Create a **service** and implement the **`ErrorHandler`** interface. - -```ts -import { ErrorHandler, Injectable, inject } from '@angular/core'; -import { ToastService } from './toast.service'; - -@Injectable({ - providedIn: 'root' -}) -export class CustomErrorHandlerService implements ErrorHandler { - toastService = inject(ToastService); - - //This method comes from interface - handleError(error: any): void { - this.toastService.showError(error.message); - } -} - -``` - -### 2.Provide the service by using the **`ErrorHandler`** class from **`@angular/core`**. - -```ts -import { ErrorHandler } from '@angular/core'; - -providers: [ - { provide: ErrorHandler, useExisting: CustomErrorHandlerService } -] - -``` - -![toast-gif](show-toast-2.gif) - -- It behaves exactly the same. Nice, now we catch the entire errors in one simple service. -- Is it that simple? I wish it is but it's not 😀. This handling mechanism only works synchronously. When we start making http requests, our **`CustomErrorHandlerService`** won't catch the errors. - -## How to handle HTTP Requests -Make an HTTP request and check if it's working. - -![http-request](http-request-4.gif) - -As you can see it doesn’t work. So how can we catch the http errors? with **`catchError()`** operator in rxjs or **`observer object`**. I will go with **`catchError()`** operator. - -```ts -getTodo(id: number) { - this.http - .get(`https://jsonplaceholder.typicode.com/todos/${id}`) - .pipe(catchError((err) => { - this.toastService.showError(err.message); - return EMPTY; - }) - ) - .subscribe(todo => this.todo = todo); -} - -``` - -![http-request](http-request-5.gif) - -- So are we going to add this **`catchError()`** operator to the entire http requests? **NO, we will use HTTP Interceptors!** -- Let's do it step by step. - -### 1.Remove catchError pipe - -```ts -getTodo(id: number) { - this.http.get('https://jsonplaceholder.typicode.com/todos/${id}').subscribe(todo => this.todo = todo); -} -``` - -### 2.Create an HTTP Interceptor - -```ts -import { Injectable, inject } from '@angular/core'; -import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; -import { EMPTY, catchError } from 'rxjs'; -import { ToastService } from './toast.service'; - -@Injectable({ - providedIn: 'root' -}) -export class ToastInterceptor implements HttpInterceptor { - toastService = inject(ToastService); - - intercept(req: HttpRequest, next: HttpHandler) { - return next.handle(req).pipe(catchError((error) => { - this.toastService.showError(error.message); - return EMPTY; - })); - } -} -``` - -### 3.Provide the interceptor - -```ts -import { HTTP_INTERCEPTORS} from '@angular/common/http'; - -providers: [ - { provide: HTTP_INTERCEPTORS, useExisting: ToastInterceptor, multi: true } -] -``` -Now everything has set up. Let's make an HTTP request and try again. - -![http-request](http-request-5.gif) - -- So, now we are handling http errors globally. Whenever an error occurs, it will be catched from interceptor and will be showed via toast message. -- But this method has a one little disadvantage. What if we dont want to show toast message for a spesific case? [related issue about this problem](https://github.com/angular/angular/issues/18155). -- **[ABP Framework](https://abp.io/) has great solution for this problem. Let's understand the solution and apply it straightforwardly.** - - Create a singleton service called **`HttpErrorReporterService`.** This service is going to store **HttpError** in a subject, and share the httpError as an observable for subscribers. - - Create a new service called **`RestService`** which is a layer on top of **`HttpClient`**, this new service is able to get a skipHandleError parameter. If skipHandleError value is **false** then it will be reported to the **`HttpErrorReporterService`** otherwise error will be throwed. - - Create a service called **`ErrorHandler`**, This service is going to subscribe to observable in **`HttpErrorReporterService`** and handle the errors (in our case we will show toast message). -- When i first see this solution, i loved it. For more information and detail you can check the source code from the links below; - - [**Http Error Reporter Service**](https://github.com/abpframework/abp/blob/360a3395aa0e44fb77574ea7eac745e8ba94b82e/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts#L6) - - [**Rest Service**](https://github.com/abpframework/abp/blob/360a3395aa0e44fb77574ea7eac745e8ba94b82e/npm/ng-packs/packages/core/src/lib/services/rest.service.ts#L4) - - [**Error Handler**](https://github.com/abpframework/abp/blob/360a3395aa0e44fb77574ea7eac745e8ba94b82e/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts#L19) - -You can copy the source codes from ABP or use ABP directly 😀 -Lets simulate the solution in our application, this simulation is not suitable for your application it just for demonstration. - -**Rest Service** - -```ts -import { HttpClient, HttpRequest } from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; -import { HttpErrorReporterService } from './http-error-reporter.service'; -import { catchError, throwError } from 'rxjs'; - -@Injectable({ - providedIn: 'root' -}) -export class RestService { - http = inject(HttpClient); - httpErrorReporterService = inject(HttpErrorReporterService); - - request(req: HttpRequest | { method: string, url: string; }, skipHandleError = false) { - const { method, url } = req; - return this.http.request(method, url).pipe(catchError((err) => { - if (!skipHandleError) { - this.httpErrorReporterService.reportError(err); - } - return throwError(() => err); - })); - } -} - -``` - -**HttpErrorReporterService** - -```ts -import { HttpErrorResponse } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs'; - -@Injectable({ - providedIn: 'root' -}) -export class HttpErrorReporterService { - private _error$ = new Subject(); - - get error$() { - return this._error$.asObservable(); - } - - reportError(error: HttpErrorResponse) { - this._error$.next(error); - } -} - -``` - -**ErrorHandler** - -```ts -import { Injectable, inject } from '@angular/core'; -import { HttpErrorReporterService } from './http-error-reporter.service'; -import { ToastService } from './toast.service'; - -@Injectable({ - providedIn: 'root' -}) -export class ErrorHandlerService { - httpErrorReporterService = inject(HttpErrorReporterService); - toastMessageService = inject(ToastService); - - constructor(){ - this.httpErrorReporterService.error$.subscribe((error) => { - this.toastMessageService.showError(error.message); - }); - } -} - -``` - -Now lets make an http request to check is it working - -```ts -restService = inject(RestService); - -getTodo() { - this.restService.request( - { method: 'GET', url: 'https://jsonplaceholder.typicode.com/todos/1111' }, - ).subscribe(todo => { - this.todo = todo; - }); -} -``` - -![http-request](http-request-7.gif) - -Now let's pass **true to the `skipHandleError` parameter**, let's see if the **errorHandler** going to skip this error or not. - -```ts -restService = inject(RestService); - -getTodo() { - this.restService.request( - { method: 'GET', url: '' }, - skipHandleError: true, - ).subscribe(todo => { - this.todo = todo; - }); -} - -``` - -![http-request](http-request-8.gif) - -### Conclusion -- To handle synchronous errors globally, you can use [Error Handler](https://angular.io/api/core/ErrorHandler). -- To handle http errors globally, you can use [HTTP - interceptor](https://angular.io/guide/http-interceptor-use-cases). But in this method, you won't be able to skip specific cases. I recommend you use the ABP Framework's solution. - -Thanks for reading, if you have any advice please share it with me in the comments. diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error-image-1.png b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error-image-1.png deleted file mode 100644 index cc0a8d2c9f..0000000000 Binary files a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error-image-1.png and /dev/null differ diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error_handling.jpg b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error_handling.jpg deleted file mode 100644 index cc21b0db27..0000000000 Binary files a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error_handling.jpg and /dev/null differ diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-4.gif b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-4.gif deleted file mode 100644 index 15ae846827..0000000000 Binary files a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-4.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-5.gif b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-5.gif deleted file mode 100644 index f97361f784..0000000000 Binary files a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-5.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-7.gif b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-7.gif deleted file mode 100644 index 682621e47c..0000000000 Binary files a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-7.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-8.gif b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-8.gif deleted file mode 100644 index 7d376674b9..0000000000 Binary files a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-8.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/show-toast-2.gif b/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/show-toast-2.gif deleted file mode 100644 index 24aed2c728..0000000000 Binary files a/docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/show-toast-2.gif and /dev/null differ diff --git a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/POST.md b/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/POST.md deleted file mode 100644 index d319cf709f..0000000000 --- a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/POST.md +++ /dev/null @@ -1,209 +0,0 @@ -# Using Testcontainers in ABP Unit Test - -## What is Testcontainers? - -Testcontainers is a library that provides easy and lightweight APIs for bootstrapping local development and test dependencies with real services wrapped in Docker containers. - -Using Testcontainers, you can write tests that depend on the same services you use in production without mocks or in-memory services. - -Get more information about Testcontainers from [https://testcontainers.com/](https://testcontainers.com/). - -## How to Use Testcontainers in ABP Unit Test? - -ABP Framework provides a built-in unit test infrastructure, allowing you to add your unit and integration tests easily. - -It uses [SQLite in-memory](https://learn.microsoft.com/en-us/ef/core/testing/testing-without-the-database#sqlite-in-memory) and [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) as the default database for unit tests and it's enough for most of the cases. However, you may need to test your code with a real database like PostgreSQL, MySQL, SQL Server, etc. - -In this article, I will show you how to use Testcontainers in ABP unit tests to test your code with a real database from a Docker container. - -> The Testcontainers will pull the Docker images of the databases you want to use. You can pull them manually before running the tests to speed them up. - -```bash -docker pull mcr.microsoft.com/mssql/server:2019-CU18-ubuntu-20.04 -docker pull mongo:6.0 -``` - -### Code Changes For Entity Framework Core Tests - -1. Remove `Volo.Abp.EntityFrameworkCore.Sqlite` package and add the `Testcontainers.MsSql` package to the `MyProjectName.EntityFrameworkCore.Tests` project. - -```csharp - - - - - - net8.0 - enable - MyCompanyName.MyProjectName - - - - - - - - - - - - - -``` - -2. Update `MyProjectNameEntityFrameworkCoreFixture` class as shown below: - -We start an SQL Server container in the `InitializeAsync` method and dispose of it in the `DisposeAsync` method. The `GetRandomConnectionString` method sets a random database for each test. - -```csharp -using System; -using System.Threading.Tasks; -using Testcontainers.MsSql; -using Xunit; - -namespace MyCompanyName.MyProjectName.EntityFrameworkCore; - -public class MyProjectNameEntityFrameworkCoreFixture : IAsyncLifetime -{ - private readonly static MsSqlContainer _msSqlContainer = new MsSqlBuilder().Build(); - - public async Task InitializeAsync() - { - await _msSqlContainer.StartAsync(); - } - - public static string GetRandomConnectionString() - { - var randomDbName = "Database=Db_" + Guid.NewGuid().ToString("N"); - return _msSqlContainer.GetConnectionString().Replace("Database=master", randomDbName, StringComparison.OrdinalIgnoreCase); - } - - public async Task DisposeAsync() - { - await _msSqlContainer.DisposeAsync().AsTask(); - } -} -``` - -3. Update `MyProjectNameEntityFrameworkCoreTestModule` class as shown below: - -```csharp -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.FeatureManagement; -using Volo.Abp.Modularity; -using Volo.Abp.PermissionManagement; -using Volo.Abp.SettingManagement; -using Volo.Abp.Uow; - -namespace MyCompanyName.MyProjectName.EntityFrameworkCore; - -[DependsOn( - typeof(MyProjectNameApplicationTestModule), - typeof(MyProjectNameEntityFrameworkCoreModule) - )] -public class MyProjectNameEntityFrameworkCoreTestModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - ConfigureMsSqlDatabase(context.Services); - } - - private void ConfigureMsSqlDatabase(IServiceCollection services) - { - var connectionString = MyProjectNameEntityFrameworkCoreFixture.GetRandomConnectionString(); - using (var context = new MyProjectNameDbContext(new DbContextOptionsBuilder() - .UseSqlServer(connectionString) - .Options)) - { - context.Database.Migrate(); - } - services.Configure(options => - { - options.Configure(context => - { - context.DbContextOptions.UseSqlServer(connectionString); - }); - }); - } -} -``` - -The EF Core unit tests results will be like the following: - -![ef core](efcore.png) - -### Code Changes For MongoDB Tests - -1. Remove `EphemeralMongo` related packages and add the `Testcontainers.MongoDb` package to the `MyProjectName.EntityFrameworkCore.Tests` project. - -```csharp - - - - - - net8.0 - enable - MyCompanyName.MyProjectName - - - - - - - - - - - - - -``` - -2. Update `MyProjectNameMongoDbFixture` class as shown below: - -We start a MongoDB container in the `InitializeAsync` method and dispose of it in the `DisposeAsync` method. The `GetRandomConnectionString` method sets a random database for each test. - -```csharp -using System; -using System.Threading.Tasks; -using Testcontainers.MongoDb; -using Xunit; - -namespace MyCompanyName.MyProjectName.MongoDB; - -public class MyProjectNameMongoDbFixture : IAsyncLifetime -{ - private readonly static MongoDbContainer _mongoDbContainer = new MongoDbBuilder().WithCommand().Build(); - - public async Task InitializeAsync() - { - await _mongoDbContainer.StartAsync(); - } - - public static string GetRandomConnectionString() - { - var randomDbName = "Db_" + Guid.NewGuid().ToString("N"); - return _mongoDbContainer.GetConnectionString().EnsureEndsWith('/') + randomDbName + "?authSource=admin"; - } - - public async Task DisposeAsync() - { - await _mongoDbContainer.DisposeAsync().AsTask(); - } -} -``` - -The MongoDB unit tests results will be like the following: - -![mongodb](mongodb.png) - -## Summary - -The Testcontainers works well with ABP Framework and it's easy to use. If you need to test your code with a real database, Testcontainers is a good choice for you. - -While it still needs to be faster than in-memory databases, but its advantages are obvious. - -Enjoy testing with Testcontainers! diff --git a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png b/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png deleted file mode 100644 index a8c8cbe8b6..0000000000 Binary files a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png and /dev/null differ diff --git a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png b/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png deleted file mode 100644 index 7cb02af550..0000000000 Binary files a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png and /dev/null differ diff --git a/docs/en/Community-Articles/2024-03-03-Share-Cookies-BetweenSubDomains/POST.md b/docs/en/Community-Articles/2024-03-03-Share-Cookies-BetweenSubDomains/POST.md deleted file mode 100644 index b4c7e00289..0000000000 --- a/docs/en/Community-Articles/2024-03-03-Share-Cookies-BetweenSubDomains/POST.md +++ /dev/null @@ -1,85 +0,0 @@ -# How to share the cookies between subdomains - -## Introduction - -Sharing cookies between subdomains is a common requirement in web development. For example, you have a website with multiple subdomains, and you want to share the login status between these subdomains. Once a user logs in to one subdomain, the user should be logged in to all subdomains. - -This article will show you how to achieve this in an ASP.NET Core application. - -## Implementation principle - -The `cookie` has a `Domain` attribute which specifies which server can receive a cookie. -If specified, then cookies are available on the server and its subdomains. For example, if you set `Domain=.abp.io`, cookies are available on `abp.io` and its subdomains like `community.abp.io`. - -If the server does not specify a **Domain**, the cookies are available on the server but not on its subdomains. Therefore, specifying the **Domain** is less restrictive than omitting it. However, it can be helpful when subdomains need to share information about a user. - -## Change the domain of the cookie in ASP.NET Core - -There is a `CookiePolicyMiddleware` in ASP.NET Core, you can add some policies to the `CookiePolicyOptions` during cookies are appended or deleted. - -We will add a policy to the `CookiePolicyOptions` to change the `domain` of the cookie: - -```csharp -services.Configure(options => -{ - options.OnAppendCookie = cookieContext => - { - ChangeCookieDomain(cookieContext, null); - }; - - options.OnDeleteCookie = cookieContext => - { - ChangeCookieDomain(null, cookieContext); - }; -}); - -private static void ChangeCookieDomain(AppendCookieContext appendCookieContext, DeleteCookieContext deleteCookieContext) -{ - if (appendCookieContext != null) - { - // Change the domain of all cookies - //appendCookieContext.CookieOptions.Domain = ".abp.io"; - - // Change the domain of the specific cookie - if (appendCookieContext.CookieName == ".AspNetCore.Culture") - { - appendCookieContext.CookieOptions.Domain = ".abp.io"; - } - } - - if (deleteCookieContext != null) - { - // Change the domain of all cookies - //appendCookieContext.CookieOptions.Domain = ".abp.io"; - - // Change the domain of the specific cookie - if (deleteCookieContext.CookieName == ".AspNetCore.Culture") - { - deleteCookieContext.CookieOptions.Domain = ".abp.io"; - } - } -} -``` - -Add the `app.UseCookiePolicy()` in the ASP.NET Core pipeline: - -```csharp -//... -app.UseStaticFiles(); -app.UseCookiePolicy(); -//... -``` - -If you check the HTTP response headers, you will see the `Set-Cookie` header with the `domain` attribute as follows: - -```http -Set-Cookie: .AspNetCore.Culture=c%3Den%7Cuic%3Den; expires=Mon, 09 Mar 2026 02:00:00 GMT; domain=.abp.io; path=/ -``` - -The subdomains can share the `.AspNetCore.Culture` cookie now. - -In another community article, we use the same middleware to [fix the Chrome login issue for the IdentityServer4](https://community.abp.io/posts/patch-for-chrome-login-issue-identityserver4-samesite-cookie-problem-weypwp3n) - -## Summary - -The `CookiePolicy` middleware provides a way to control cookies in an ASP.NET Core, It is very useful if you have more complex requirements for Cookies. diff --git a/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md b/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md deleted file mode 100644 index e4f763f279..0000000000 --- a/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md +++ /dev/null @@ -1,137 +0,0 @@ -# Performing Case-Insensitive Search in ABP Based-PostgreSQL Application: Using `citext` and Collation - -PostgreSQL, by default, is a case-sensitive database. This means that text data stored in the database is treated as case-sensitive. However, in many cases, users may need to perform searches regardless of case sensitivity. Since PostgreSQL is case-sensitive this creates some questions in the mind, when selecting and using it within an ABP-based application. For example: - -* Does not ABP Framework support case-sensitive queries for the PostgreSQL database? -* ABP Framework supports PostgreSQL but does not support case-insensitive search for it? -* [But PostgreSQL still a challenge to work with Accent and Case Insensitive Searches with ABP](https://twitter.com/iSephit/status/1780568810291913029) -* ... - -None of these questions are related to ABP Framework but PostgreSQL is being case-sensitive database by default and in this article, I will try to answer to these questions and I will address two possible solutions to perform case-insensitive operations: **Using the `citext` data type for text fields** and **using collations**. - -> As you would know, ABP Framework provides a [EF Core PostgreSQL Provider package](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-PostgreSQL) called `Volo.Abp.EntityFrameworkCore.PostgreSql`. Throughout this article, I will give the example codes, by assuming that you created an ABP-based application with PostgreSQL as the database option, however all the steps in this article, are also applicable to any .NET-based application. - -## Using the `citext` Data Type - -`citext` is a PostgreSQL-specific data type that stands for "case-insensitive text". This data type stores text data while ignoring case differences, effectively making searches case-insensitive. When you create an ABP-based application with PostgreSQL as the database option, you can easily use the `citext` data type. - -To use the `citext` data type, you mainly need to do two things: - -1. The `citext` data type is available in a PostgreSQL-bundled extension, so you'll first have to install it. For that purpose, you should use the [`HasPostgresExtension`](https://www.npgsql.org/efcore/api/Microsoft.EntityFrameworkCore.NpgsqlModelBuilderExtensions.html) method, -2. Then, you should map all of your text fields to the `citext` datatype in your `*DbContext.cs` file as follows: - -```csharp -[ReplaceDbContext(typeof(IIdentityDbContext))] -[ReplaceDbContext(typeof(ITenantManagementDbContext))] -[ConnectionStringName("Default")] -public class MyProjectNameDbContext : - AbpDbContext, - IIdentityDbContext, - ITenantManagementDbContext -{ - //... - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - //db configurations... - - //👇 install the citext datatype 👇 - builder.HasPostgresExtension("citext"); - } - - protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) - { - base.ConfigureConventions(configurationBuilder); - - // 👇 configure all of the string property types as 'citext' data type 👇 - configurationBuilder.Properties().HaveColumnType("citext"); - } -} -``` - -In addition to that, you should configure the `AbpDbContextOptions` in the module class of the `*.EntityFrameworkCore` project to also apply this change in the dependent ABP modules (also for your own modules) as follows: - -```csharp - public class MyProjectEntityFrameworkCoreModule : AbpModule - { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - // https://www.npgsql.org/efcore/release-notes/6.0.html#opting-out-of-the-new-timestamp-mapping-logic - AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); - } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - - //other configurations... - - Configure(options => - { - options.UseNpgsql(); - - // 👇 configure all of the string property types as 'citext' data type for all of the dependent modules 👇 - options.ConfigureDefaultConvention((_, builder) => - { - builder.Properties().HaveColumnType("citext"); - }); - }); - - } - - //... - } -``` - -After you make these changes, you can create a new migration and apply it to your database. When you do that, all of the types of text-based fields will be changed as `citext` data type. Then, you can write case-insensitive queries in your application without worry. - -## Using Collations - -**Collation** is a set of rules that determine how text data is sorted and compared in a dataset. PostgreSQL provides different collation settings for various languages and cultures. These settings can determine how text data is compared and can be configured to ignore case differences. - -To perform case-insensitive or accent-insensitive operations, you should choose one of the [non-deterministic collations](https://postgresql.verite.pro/blog/2019/10/14/nondeterministic-collations.html). For example, you can define a collation as follows (in your `*DbContext.cs` file): - -```csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) -{ - modelBuilder.HasCollation("my_collation", locale: "en-u-ks-primary", provider: "icu", deterministic: false); - - //👇 using collations for a specific entity. -> entity level 👇 - modelBuilder.Entity() - .Property(c => c.Name) - .UseCollation("my_collation"); - - //👇 specify collations at the database level 👇 - modelBuilder.UseCollation("my_collation"); -} -``` - -You can define collations both on entity level and database level like in the example above. If you want to use it in the database layer, you should also configure the collation usage in the `ConfigureConvention` method: - -```csharp -protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) -{ - configurationBuilder.Properties().UseCollation("my_collation"); -} -``` - -After these configurations, you should create a migration and apply it to your database as always. - -However, this solution comes with some problems, for example, by using non-deterministic collations, it's not yet possible to use pattern-matching operators such as `LIKE` on columns. This is a huge problem because it makes it hard to use LINQ. For example, you can't use the `.EndsWith` or `.StartsWith` methods, because they are [translated to `LIKE` command on the SQL level](https://www.npgsql.org/efcore/mapping/translations.html). - -## Conclusion - -In PostgreSQL, you can perform case-insensitive searches by using the `citext` data type or by utilizing collation settings. Nevertheless, if you have an ABP-based PostgreSQL application or a plain .NET application with PostgreSQL as the database option, to make a decision to pick one of these options, you can follow the following points: - -* If the accent is not important for you and the only thing you want to do, is make the PostgreSQL queries case-insensitive, using the `citext` data type option should be selected -* If the accent is really important to you, and you don't use LINQ methods (such as `StartsWith` and `EndsWith` methods), you can use collations. - * Note that, with this approach, queries that are defined in the dependent modules also must not use these LINQ methods. Therefore, this approach is not suitable with the ABP Framework. Because some of the modules use LINQ methods (some pattern-matching operators). - -Regardless of the method chosen, you can enable users to perform searches without worrying about case sensitivity. This is crucial for providing a user-friendly experience and making your database queries more flexible. - -## References - -* https://www.npgsql.org/efcore/misc/collations-and-case-sensitivity.html -* https://postgresql.verite.pro/blog/2019/10/14/nondeterministic-collations.html -* https://www.npgsql.org/efcore/mapping/translations.html diff --git a/docs/en/Concurrency-Check.md b/docs/en/Concurrency-Check.md deleted file mode 100644 index 25e60713c7..0000000000 --- a/docs/en/Concurrency-Check.md +++ /dev/null @@ -1,149 +0,0 @@ -## Concurrency Check - -### Introduction - -Concurrency Check (also known as **Concurrency Control**) refers to specific mechanisms used to ensure data consistency in the presence of concurrent changes (multiple processes, users access or change the same data in a database at the same time). - -There are two commonly used concurrency control mechanisms/approaches: -* **Optimistic Concurrency Control**: Optimistic Concurrency Control allows multiple users to attempt to **update** the same record without informing the users that others are also attempting to **update** it. - - * If a user successfully updates the record, the other users need to get the latest changes for the current record to be able to make changes. - * ABP's concurrency check system uses the **Optimistic Concurrency Control**. - -* **Pessimistic Concurrency Control**: Pessimistic Concurrency Control prevents simultaneous updates to records and uses a locking mechanism. For more information please see [here](https://www.martinfowler.com/eaaCatalog/pessimisticOfflineLock.html). - -### Usage - -#### `IHasConcurrencyStamp` Interface - -To enable **concurrency control** to your entity class, you should implement the `IHasConcurrencyStamp` interface, directly or indirectly. - -```csharp -public interface IHasConcurrencyStamp -{ - public string ConcurrencyStamp { get; set; } -} -``` - -* It is the base interface for **concurrency control** and only has a simple property named `ConcurrencyStamp`. -* While a new record is **creating**, if the entity implements the `IHasConcurrencyStamp` interface, ABP Framework automatically sets a unique value to the **ConcurrencyStamp** property. -* While a record is **updating**, ABP Framework compares the **ConcurrencyStamp** property of the entity with the provided **ConcurrencyStamp** value by the user and if the values match, it automatically updates the **ConcurrencyStamp** property with the new unique value. If there is a mismatch, `AbpDbConcurrencyException` is thrown. - -> If there is a unit of work, you need to call the [SaveChangesAsync](./Unit-Of-Work.md#savechangesasync) method to get the generated `ConcurrencyStamp` when creating or updating. - -**Example: Applying Concurrency Control for the Book Entity** - -Implement the `IHasConcurrencyStamp` interface for your entity: - -```csharp -public class Book : Entity, IHasConcurrencyStamp -{ - public string ConcurrencyStamp { get; set; } - - //... -} -``` - -Also, implement your output and update the DTO classes from the `IHasConcurrencyStamp` interface: - -```csharp -public class BookDto : EntityDto, IHasConcurrencyStamp -{ - //... - - public string ConcurrencyStamp { get; set; } -} - -public class UpdateBookDto : IHasConcurrencyStamp -{ - //... - - public string ConcurrencyStamp { get; set; } -} -``` - -Set the **ConcurrencyStamp** input value to the entity in the **UpdateAsync** method of your application service as below: - -```csharp -public class BookAppService : ApplicationService, IBookAppService -{ - //... - - public virtual async Task UpdateAsync(Guid id, UpdateBookDto input) - { - var book = await BookRepository.GetAsync(id); - - book.ConcurrencyStamp = input.ConcurrencyStamp; - - //set other input values to the entity ... - //use autoSave: true to get the latest ConcurrencyStamp - await BookRepository.UpdateAsync(book, autoSave: true); - } -} -``` - -* After that, when multiple users try to update the same record at the same time, the concurrency stamp mismatch occurs and `AbpDbConcurrencyException` is thrown. - -#### Base Classes - -[Aggregate Root](./Entities.md#aggregateroot-class) entity classes already implement the `IHasConcurrencyStamp` interface. So, if you are deriving from one of these base classes, you don't need to manually implement the `IHasConcurrencyStamp` interface: - -- `AggregateRoot`, `AggregateRoot` -- `CreationAuditedAggregateRoot`, `CreationAuditedAggregateRoot` -- `AuditedAggregateRoot`, `AuditedAggregateRoot` -- `FullAuditedAggregateRoot`, `FullAuditedAggregateRoot` - -**Example: Applying Concurrency Control for the Book Entity** - -You can inherit your entity from one of [the base classes](#base-classes): - -```csharp -public class Book : FullAuditedAggregateRoot -{ - //... -} -``` - -Then, you can implement your output and update the DTO classes from the `IHasConcurrencyStamp` interface: - -```csharp -public class BookDto : EntityDto, IHasConcurrencyStamp -{ - //... - - public string ConcurrencyStamp { get; set; } -} - -public class UpdateBookDto : IHasConcurrencyStamp -{ - //... - - public string ConcurrencyStamp { get; set; } -} -``` - -Set the **ConcurrencyStamp** input value to the entity in the **UpdateAsync** method of your application service as below: - -```csharp -public class BookAppService : ApplicationService, IBookAppService -{ - //... - - public virtual async Task UpdateAsync(Guid id, UpdateBookDto input) - { - var book = await BookRepository.GetAsync(id); - - book.ConcurrencyStamp = input.ConcurrencyStamp; - - //set other input values to the entity ... - //use autoSave: true to get the latest ConcurrencyStamp - await BookRepository.UpdateAsync(book, autoSave: true); - } -} -``` - -After that, when multiple users try to update the same record at the same time, the concurrency stamp mismatch occurs and `AbpDbConcurrencyException` is thrown. You can either handle the exception manually or let the ABP Framework handle it for you. - -ABP Framework shows a user-friendly error message as in the image below, if you don't handle the exception manually. - -![Optimistic Concurrency](./images/optimistic-concurrency.png) diff --git a/docs/en/Configuration.md b/docs/en/Configuration.md deleted file mode 100644 index 30d881c06e..0000000000 --- a/docs/en/Configuration.md +++ /dev/null @@ -1,3 +0,0 @@ -# Configuration - -ASP.NET Core has an flexible and extensible key-value based configuration system. In fact, the configuration system is a part of Microsoft.Extensions libraries and it is independent from ASP.NET Core. That means it can be used in any type of application. See [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) to learn the configuration infrastructure. ABP framework is 100% compatible with the configuration system. \ No newline at end of file diff --git a/docs/en/Connection-Strings.md b/docs/en/Connection-Strings.md deleted file mode 100644 index 76e535a960..0000000000 --- a/docs/en/Connection-Strings.md +++ /dev/null @@ -1,138 +0,0 @@ -# Connection Strings - -> Connection string system is especially needed when you want to create or use a modular system. If you have a monolithic application with a single database, you can go with the [ABP startup solution template](Startup-Templates/Application.md), which is properly configured for you. - -ABP Framework is designed to be [modular](Module-Development-Basics.md) and [multi-tenancy](Multi-Tenancy.md) aware. Connection string management is also designed to support these scenarios; - -* Allows to set separate connection strings for every module, so every module can have its own physical database. Modules even might be configured to use different database providers. -* Allows to set separate connection string and use a separate database per tenant (in a SaaS application). - -It also supports hybrid scenarios; - -* Allows to group modules into databases (e.g., all modules into a single shared database or two modules to database A, three modules to database B, one module to database C and rest of the modules to database D) -* Allows to group tenants into databases, just like the modules. -* Allows to separate databases per tenant per module (which might be hard to maintain for you because of too many databases, but the ABP framework supports it). - -All the [pre-built application modules](Modules/Index.md) are designed to be compatible these scenarios. - -## Configure the Connection Strings - -See the following configuration: - -````json -"ConnectionStrings": { - "Default": "Server=localhost;Database=MyMainDb;Trusted_Connection=True;", - "AbpIdentityServer": "Server=localhost;Database=MyIdsDb;Trusted_Connection=True;", - "AbpPermissionManagement": "Server=localhost;Database=MyPermissionDb;Trusted_Connection=True;" -} -```` - -> ABP uses the `IConfiguration` service to get the application configuration. While the simplest way to write configuration into the `appsettings.json` file, it is not limited to this file. You can use environment variables, user secrets, Azure Key Vault... etc. See the [configuration](Configuration.md) document for more. - -This configuration defines three different connection strings: - -* `MyMainDb` (the `Default` connection string) is the main connection string of the application. If you don't specify a connection string for a module, it fallbacks to the `Default` connection string. The [application startup template](Startup-Templates/Application.md) is configured to use a single connection string, so all the modules uses a single, shared database. -* `MyIdsDb` (the `AbpIdentityServer` connection string) is used by the [IdentityServer](Modules/IdentityServer.md) module. -* `MyPermissionDb` (the `AbpPermissionManagement` connection string) is used by the [Permission Management](Modules/Permission-Management.md) module. - -[Pre-built application modules](Modules/Index.md) define constants for the connection string names. For example, the [IdentityServer module](Modules/IdentityServer.md) defines a ` ConnectionStringName ` constant in the ` AbpIdentityServerDbProperties ` class (located in the ` Volo.Abp.IdentityServer ` namespace). Other modules similarly define constants, so you can investigate the connection string name. - -### AbpDbConnectionOptions - -`AbpDbConnectionOptions` is the options class that is used to set the connection strings and configure database structures. - -#### Setting the connection strings - -ABP uses the `AbpDbConnectionOptions` to get the connection strings. If you configure the connection strings as explained above, `AbpDbConnectionOptions` is automatically filled. However, you can set or override the connection strings using [the options pattern](Options.md). You can configure the `AbpDbConnectionOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) as shown below: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - Configure(options => - { - options.ConnectionStrings.Default = "..."; - options.ConnectionStrings["AbpPermissionManagement"] = "..."; - }); -} -```` - -#### Configuring the database structures - -`Databases` property of the `AbpDbConnectionOptions` class is used to group multiple connection strings (of multiple modules) to a single connection string. - -See the following connection strings: - -````json -"ConnectionStrings": { - "Default": "Server=localhost;Database=MyMainDb;Trusted_Connection=True;", - "AbpIdentity": "Server=localhost;Database=MySecondaryDb;Trusted_Connection=True;", - "AbpIdentityServer": "Server=localhost;Database=MySecondaryDb;Trusted_Connection=True;", - "AbpPermissionManagement": "Server=localhost;Database=MySecondaryDb;Trusted_Connection=True;" -} -```` - -In this example, we've defined four connection strings, but the last three of them are the same; `AbpIdentity`, `AbpIdentityServer` and `AbpPermissionManagement` uses the same database, named `MySecondaryDb`. The main application and the other modules use the `Default` connection string, hence the `MyMainDb` database. - -What we want to do here is to group three modules (`AbpIdentity`, `AbpIdentityServer` and `AbpPermissionManagement`) in a single database, but we needed to specify each one manually. Because the fallback connection string is the `Default` one, if we don't specify it for a module. - -To eliminate the repetitive connection string definition, we can configure the `AbpDbConnectionOptions.Databases` property to group these connection strings, as shown in the following code (we place that in the `ConfigureServices` method of our [module class](Module-Development-Basics.md)): - -````csharp -Configure(options => -{ - options.Databases.Configure("MySecondaryDb", db => - { - db.MappedConnections.Add("AbpIdentity"); - db.MappedConnections.Add("AbpIdentityServer"); - db.MappedConnections.Add("AbpPermissionManagement"); - }); -}); -```` - -Then we can change the `appsettings.json` file as shown in the following code block: - -````json -"ConnectionStrings": { - "Default": "Server=localhost;Database=MyMainDb;Trusted_Connection=True;", - "MySecondaryDb": "Server=localhost;Database=MySecondaryDb;Trusted_Connection=True;" -} -```` - -`MySecondaryDb` becomes the new connection string for the mapped connections. - -> ABP first looks for the module-specific connection string, then looks if a database mapping is available, finally fallbacks to the `Default` connection string. - -## Set the Connection String Name - -A module typically has a unique connection string name associated to its `DbContext` class using the `ConnectionStringName` attribute. Example: - -````csharp -[ConnectionStringName("AbpIdentityServer")] -public class IdentityServerDbContext - : AbpDbContext, IIdentityServerDbContext -{ -} -```` - -For [Entity Framework Core](Entity-Framework-Core.md) and [MongoDB](MongoDB.md), write this to your `DbContext` class (and the interface if it has). In this way, ABP uses the specified connection string for the related `DbContext` instances. - -## Database Migrations for the Entity Framework Core - -Relational databases require to create the database and the database schema (tables, views... etc.) before using it. - -The startup template (with EF Core ORM) comes with a single database and a `.EntityFrameworkCore` project that contains related classes and the migration files for that database. This project mainly defines a `YourProjectNameDbContext` class that calls the `Configure...()` methods of the used modules, like `builder.ConfigurePermissionManagement()`. - -Once you want to separate a module's database, you typically will need to create a second migration path. See the [EF Core Migrations](Entity-Framework-Core-Migrations.md) document to learn how to create and use a different database for a desired module. - -## Multi-Tenancy - -See [the multi-tenancy document](Multi-Tenancy.md) to learn how to use separate databases for tenants. - -## Replace the Connection String Resolver - -ABP defines the `IConnectionStringResolver` and uses it whenever it needs a connection string. It has two pre-built implementations: - -* `DefaultConnectionStringResolver` uses the `AbpDbConnectionOptions` to select the connection string based on the rules defined in the "Configure the Connection Strings" section above. -* `MultiTenantConnectionStringResolver` used for multi-tenant applications and tries to get the configured connection string for the current tenant if available. It uses the `ITenantStore` to find the connection strings. It inherits from the `DefaultConnectionStringResolver` and fallbacks to the base logic if no connection string specified for the current tenant. - -If you need a custom logic to determine the connection string, implement the `IConnectionStringResolver` interface (optionally derive from the existing implementations) and replace the existing implementation using the [dependency injection](Dependency-Injection.md) system. \ No newline at end of file diff --git a/docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md b/docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md deleted file mode 100644 index 719caf844f..0000000000 --- a/docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md +++ /dev/null @@ -1,58 +0,0 @@ -# How to contribute to abp.io as a frontend developer - -## How to setup development environment - -### Pre-requirements - -- Dotnet core SDK https://dotnet.microsoft.com/en-us/download -- Nodejs LTS https://nodejs.org/en/ -- Docker https://docs.docker.com/engine/install -- Angular CLI. https://angular.io/guide/what-is-angular#angular-cli -- Abp CLI https://docs.abp.io/en/abp/latest/cli -- A code editor - -Note: This article prepare Windows OS. You may change the path type of your OS. an Example - -Windows: `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator\appsettings.json` - -Unix: `templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json` - -### Sample docker commands - -You need to install SQL Server and Redis. You can install these programs without docker, but my example uses docker containers. Your computer should have Docker Engine. Then open the terminal en execute the commands one by one. -For the Sql Server - -```cmd -docker run -v sqlvolume:/var/opt/mssql -e 'ACCEPT_EULA=Y' -e "SA_PASSWORD=yourpassword" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2019-CU3-ubuntu-18.04 -``` - -For the Redis - -```cmd -docker run -p 6379:6379 -d redis -``` - -Then we are ready to download and execute the code. - -## Folder Structure - -The app has a backend written in .net core (c#) and an angular app. It would help if you ran both of them. - -### Running Backend App - -The path of the Backend app is “templates\app\aspnet-core.” If you want to work with dockerized SQL Server, you should change connection strings for running with docker. The path of the connection string is -`templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator\appsettings.json`. - -Before running the backend, you should run the Db migrator project. The DbMigrator created initial tables and values. The path of DbMigrator is `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator`. Open a terminal in the path and execute the command `dotnet run` in terminal - -One last step before the running the backend is installing client-side libraries. Go to `templates\app\aspnet-core`. Open a terminal in the path and execute the command `abp install-libs` in terminal - -Next step you should go to path of backend host project. The path is `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.HttpApi.HostWithIds`. Open a terminal in the path and execute the command `dotnet run` in terminal - -Your backend should be running successfully - -### Running Frontend App - -There is a demo app. The path of the demo app is `npm\ng-packs\apps\dev-app`. The demo app is connected to the packages with local references. Open the terminal in `npm\ng-packs\apps\dev-app` and execute `yarn` or `npm i` in terminal. After the package installed run `npm start` or `yarn start`. - -The repo uses Nx and packages connected with `local references`. The packages path is `npm\ng-packs\packages` diff --git a/docs/en/Contribution/Index.md b/docs/en/Contribution/Index.md deleted file mode 100644 index 8ebac70c20..0000000000 --- a/docs/en/Contribution/Index.md +++ /dev/null @@ -1,80 +0,0 @@ -# Contribution Guide - -ABP is an [open source](https://github.com/abpframework) and community driven project. This guide is aims to help anyone wants to contribute to the project. - -## ABP Community Website - -If you want to write **articles** or **how to guides** related to the ABP Framework and ASP.NET Core, please submit your article to the [community.abp.io](https://community.abp.io/) website. - -## Code Contribution - -You can always send pull requests to the GitHub repository. - -- [Fork](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo) the [ABP repository](https://github.com/abpframework/abp/) from GitHub. -- Build the repository using the `/build/build-all.ps1 -f` for one time. -- Make the necessary changes, including unit/integration tests. -- Send a pull request. - -> When you open a solution in Visual Studio, you may need to execute `dotnet restore` in the root folder of the solution for one time, after it is fully opened in the Visual Studio. This is needed since VS can't properly resolves local references to projects out of the solution. - -### GitHub Issues - -Before making any change, please discuss it on the [Github issues](https://github.com/abpframework/abp/issues). In this way, no other developer will work on the same issue and your PR will have a better chance to be accepted. - -#### Bug Fixes & Enhancements - -You may want to fix a known bug or work on a planned enhancement. See [the issue list](https://github.com/abpframework/abp/issues) on Github. - -#### Feature Requests - -If you have a feature idea for the framework or modules, [create an issue](https://github.com/abpframework/abp/issues/new) on Github or attend to an existing discussion. Then you can implement it if it's embraced by the community. - -## Document Translation - -You may want to translate the complete [documentation](https://docs.abp.io) (including this one) to your mother language. If so, follow these steps: - -* Clone the [ABP repository](https://github.com/abpframework/abp/) from Github. -* To add a new language, create a new folder inside the [docs](https://github.com/abpframework/abp/tree/master/docs) folder. Folder names can be "en", "es", "fr", "tr" and so on based on the language (see [all culture codes](https://msdn.microsoft.com/en-us/library/hh441729.aspx)). -* Get the ["en" folder](https://github.com/abpframework/abp/tree/master/docs/en) as a reference for the file names and folder structure. Keep the same naming if you are translating the same documentation. -* Send a pull request (PR) once you translate any document. Please translate documents & send PRs one by one. Don't wait to finish translations for all documents. - -There are some fundamental documents need to be translated before publishing a language on the [ABP documentation web site](https://docs.abp.io): - -* Index (Home) -* Getting Started -* Web Application Development Tutorial - -A new language is published after these minimum translations have been completed. - -## Resource Localization - -ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application. - -In addition to that, the framework and the [pre-build modules](https://docs.abp.io/en/abp/latest/Modules/Index) have localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). - -### Using the "abp translate" command - -This is the recommended approach, since it automatically finds all missing texts for a specific culture and lets you to translate in one place. - -* Clone the [ABP repository](https://github.com/abpframework/abp/) from Github. -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Run `abp translate -c ` command for your language in the root folder of the abp repository. For example, use `abp translate -c fr` for French. Check [this document](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes) to find the culture code for your language. -* This command creates a file in the same folder, named `abp-translation.json`. Open this file in your favorite editor and fill the missing text values. -* Once you done the translation, use `abp translate -a` command to apply changes to the related files. -* Send a pull request on GitHub. - -### Manual Translation - -If you want to make a change on a specific resource file, you can find the file yourself, make the necessary change (or create a new file for your language) and send a pull request on GitHub. - -## Bug Report - -If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new). - -## Setup Frontend Development Environment - -[How to contribute to abp.io as a frontend developer](How-to-Contribute-abp.io-as-a-frontend-developer.md) - -## See Also - -* [ABP Community Talks 2022.4: How can you contribute to the open source ABP Framework?](https://www.youtube.com/watch?v=Wz4Z-O-YoPg&list=PLsNclT2aHJcOsPustEkzG6DywiO8eh0lB) \ No newline at end of file diff --git a/docs/en/CorrelationId.md b/docs/en/CorrelationId.md deleted file mode 100644 index 99bcd448f4..0000000000 --- a/docs/en/CorrelationId.md +++ /dev/null @@ -1,3 +0,0 @@ -# Correlation ID - -TODO \ No newline at end of file diff --git a/docs/en/CurrentUser.md b/docs/en/CurrentUser.md deleted file mode 100644 index ac2aaa20bf..0000000000 --- a/docs/en/CurrentUser.md +++ /dev/null @@ -1,167 +0,0 @@ -# Current User - -It is very common to retrieve the information about the logged in user in a web application. The current user is the active user related to the current request in a web application. - -## ICurrentUser - -`ICurrentUser` is the main service to get info about the current active user. - -Example: [Injecting](Dependency-Injection.md) the `ICurrentUser` into a service: - -````csharp -using System; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Users; - -namespace AbpDemo -{ - public class MyService : ITransientDependency - { - private readonly ICurrentUser _currentUser; - - public MyService(ICurrentUser currentUser) - { - _currentUser = currentUser; - } - - public void Foo() - { - Guid? userId = _currentUser.Id; - } - } -} -```` - -Common base classes have already injected this service as a base property. For example, you can directly use the `CurrentUser` property in an [application service](Application-Services.md): - -````csharp -using System; -using Volo.Abp.Application.Services; - -namespace AbpDemo -{ - public class MyAppService : ApplicationService - { - public void Foo() - { - Guid? userId = CurrentUser.Id; - } - } -} -```` - -### Properties - -Here are the fundamental properties of the `ICurrentUser` interface: - -* **IsAuthenticated** (bool): Returns `true` if the current user has logged in (authenticated). If the user has not logged in then `Id` and `UserName` returns `null`. -* **Id** (Guid?): Id of the current user. Returns `null`, if the current user has not logged in. -* **UserName** (string): User name of the current user. Returns `null`, if the current user has not logged in. -* **TenantId** (Guid?): Tenant Id of the current user, which can be useful for a [multi-tenant](Multi-Tenancy.md) application. Returns `null`, if the current user is not assigned to a tenant. -* **Email** (string): Email address of the current user.Returns `null`, if the current user has not logged in or not set an email address. -* **EmailVerified** (bool): Returns `true`, if the email address of the current user has been verified. -* **PhoneNumber** (string): Phone number of the current user. Returns `null`, if the current user has not logged in or not set a phone number. -* **PhoneNumberVerified** (bool): Returns `true`, if the phone number of the current user has been verified. -* **Roles** (string[]): Roles of the current user. Returns a string array of the role names of the current user. - -### Methods - -`ICurrentUser` is implemented on the `ICurrentPrincipalAccessor` (see the section below) and works with the claims. So, all of the above properties are actually retrieved from the claims of the current authenticated user. - -`ICurrentUser` has some methods to directly work with the claims, if you have custom claims or get other non-common claim types. - -* **FindClaim**: Gets a claim with the given name. Returns `null` if not found. -* **FindClaims**: Gets all the claims with the given name (it is allowed to have multiple claim values with the same name). -* **GetAllClaims**: Gets all the claims. -* **IsInRole**: A shortcut method to check if the current user is in the specified role. - -Beside these standard methods, there are some extension methods: - -* **FindClaimValue**: Gets the value of the claim with the given name, or `null` if not found. It has a generic overload that also casts the value to a specific type. -* **GetId**: Returns `Id` of the current user. If the current user has not logged in, it throws an exception (instead of returning `null`) . Use this only if you are sure that the user has already authenticated in your code context. - -### Authentication & Authorization - -`ICurrentUser` works independently of how the user is authenticated or authorized. It seamlessly works with any authentication system that works with the current principal (see the section below). - -## ICurrentPrincipalAccessor - -`ICurrentPrincipalAccessor` is the service that should be used (by the ABP Framework and your application code) whenever the current principal of the current user is needed. - -For a web application, it gets the `User` property of the current `HttpContext`. For a non-web application, it returns the `Thread.CurrentPrincipal`. - -> You generally don't need to use this low level `ICurrentPrincipalAccessor` service and just directly work with the `ICurrentUser` explained above. - -### Basic Usage - -You can inject `ICurrentPrincipalAccessor` and use the `Principal` property to the the current principal: - -````csharp -public class MyService : ITransientDependency -{ - private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; - - public MyService(ICurrentPrincipalAccessor currentPrincipalAccessor) - { - _currentPrincipalAccessor = currentPrincipalAccessor; - } - - public void Foo() - { - var allClaims = _currentPrincipalAccessor.Principal.Claims.ToList(); - //... - } -} -```` - -### Changing the Current Principal - -Current principal is not something you want to set or change, except at some advanced scenarios. If you need it, use the `Change` method of the `ICurrentPrincipalAccessor`. It takes a `ClaimsPrincipal` object and makes it "current" for a scope. - -Example: - -````csharp -public class MyAppService : ApplicationService -{ - private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; - - public MyAppService(ICurrentPrincipalAccessor currentPrincipalAccessor) - { - _currentPrincipalAccessor = currentPrincipalAccessor; - } - - public void Foo() - { - var newPrincipal = new ClaimsPrincipal( - new ClaimsIdentity( - new Claim[] - { - new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString()), - new Claim(AbpClaimTypes.UserName, "john"), - new Claim("MyCustomCliam", "42") - } - ) - ); - - using (_currentPrincipalAccessor.Change(newPrincipal)) - { - var userName = CurrentUser.UserName; //returns "john" - //... - } - } -} -```` - -Use the `Change` method always in a `using` statement, so it will be restored to the original value after the `using` scope ends. - -This can be a way to simulate a user login for a scope of the application code, however try to use it carefully. - -## AbpClaimTypes - -`AbpClaimTypes` is a static class that defines the names of the standard claims and used by the ABP Framework. - -* Default values for the `UserName`, `UserId`, `Role` and `Email` properties are set from the [System.Security.Claims.ClaimTypes](https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes) class, but you can change them. -* Other properties, like `EmailVerified`, `PhoneNumber`, `TenantId`... are defined by the ABP Framework by following the standard names wherever possible. - -It is suggested to use properties of this class instead of magic strings for claim names. - diff --git a/docs/en/Customizing-Application-Modules-Extending-Entities.md b/docs/en/Customizing-Application-Modules-Extending-Entities.md deleted file mode 100644 index c2cacb4e4c..0000000000 --- a/docs/en/Customizing-Application-Modules-Extending-Entities.md +++ /dev/null @@ -1,137 +0,0 @@ -# Customizing the Application Modules: Extending Entities - -In some cases, you may want to add some additional properties (and database fields) for an entity defined in a depended module. This section will cover some different approaches to make this possible. - -## Extra Properties - -[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these objects. - -Example: - -````csharp -//SET AN EXTRA PROPERTY -var user = await _identityUserRepository.GetAsync(userId); -user.SetProperty("Title", "My custom title value!"); -await _identityUserRepository.UpdateAsync(user); - -//GET AN EXTRA PROPERTY -var user = await _identityUserRepository.GetAsync(userId); -return user.GetProperty("Title"); -```` - -This approach is very easy to use and available out of the box. No extra code needed. You can store more than one property at the same time by using different property names (like `Title` here). - -Extra properties are stored as a single `JSON` formatted string value in the database for the EF Core. For MongoDB, they are stored as separate fields of the document. - -See the [entities document](Entities.md) for more about the extra properties system. - -> It is possible to perform a **business logic** based on the value of an extra property. You can [override a service method](Customizing-Application-Modules-Overriding-Services.md), then get or set the value as shown above. - -## Entity Extensions (EF Core) - -As mentioned above, all extra properties of an entity are stored as a single JSON object in the database table. This is not so natural especially when you want to; - -* Create **indexes** and **foreign keys** for an extra property. -* Write **SQL** or **LINQ** using the extra property (search table by the property value, for example). -* Creating your **own entity** maps to the same table, but defines an extra property as a **regular property** in the entity (see the [EF Core migration document](Entity-Framework-Core-Migrations.md) for more). - -To overcome the difficulties described above, ABP Framework entity extension system for the Entity Framework Core that allows you to use the same extra properties API defined above, but store a desired property as a separate field in the database table. - -Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `ObjectExtensionManager`: - -````csharp -ObjectExtensionManager.Instance - .MapEfCoreProperty( - "SocialSecurityNumber", - (entityBuilder, propertyBuilder) => - { - propertyBuilder.HasMaxLength(32); - } - ); -```` - -* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table). -* You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties). - -> This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEfCoreEntityExtensionMappings`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself. - -Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database. - -You can then use the same extra properties system defined in the previous section to manipulate the property over the entity. - -## Creating a New Entity Maps to the Same Database Table/Collection - -Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database). - -## Creating a New Entity with Its Own Database Table/Collection - -Mapping your entity to an **existing table** of a depended module has a few disadvantages; - -* You deal with the **database migration structure** for EF Core. While it is possible, you should extra care about the migration code especially when you want to add **relations** between entities. -* Your application database and the module database will be the **same physical database**. Normally, a module database can be separated if needed, but using the same table restricts it. - -If you want to **loose couple** your entity with the entity defined by the module, you can create your own database table/collection and map your entity to your own table in your own database. - -In this case, you need to deal with the **synchronization problems**, especially if you want to **duplicate** some properties/fields of the related entity. There are a few solutions; - -* If you are building a **monolithic** application (or managing your entity and the related module entity within the same process), you can use the [local event bus](Local-Event-Bus.md) to listen changes. -* If you are building a **distributed** system where the module entity is managed (created/updated/deleted) on a different process/service than your entity is managed, then you can subscribe to the [distributed event bus](Distributed-Event-Bus.md) for change events. - -Once you handle the event, you can update your own entity in your own database. - -### Subscribing to Local Events - -[Local Event Bus](Local-Event-Bus.md) system is a way to publish and subscribe to events occurring in the same application. - -Assume that you want to get informed when a `IdentityUser` entity changes (created, updated or deleted). You can create a class that implements the `ILocalEventHandler>` interface. - -````csharp -public class MyLocalIdentityUserChangeEventHandler : - ILocalEventHandler>, - ITransientDependency -{ - public async Task HandleEventAsync(EntityChangedEventData eventData) - { - var userId = eventData.Entity.Id; - var userName = eventData.Entity.UserName; - - //... - } -} -```` - -* `EntityChangedEventData` covers create, update and delete events for the given entity. If you need, you can subscribe to create, update and delete events individually (in the same class or different classes). -* This code will be executed in the **current unit of work**, the whole process becomes transactional. - -> Reminder: This approach needs to change the `IdentityUser` entity in the same process contains the handler class. It perfectly works even for a clustered environment (when multiple instances of the same application are running on multiple servers). - -### Subscribing to Distributed Events - -[Distributed Event Bus](Distributed-Event-Bus.md) system is a way to publish an event in one application and receive the event in the same or different application running on the same or different server. - -Assume that you want to get informed when `Tenant` entity (of the [Tenant Management](Modules/Tenant-Management.md) module) has created. In this case, you can subscribe to the `EntityCreatedEto` event as shown in the following example: - -````csharp -public class MyDistributedEventHandler : - IDistributedEventHandler>, - ITransientDependency -{ - public async Task HandleEventAsync(EntityCreatedEto eventData) - { - var tenantId = eventData.Entity.Id; - var tenantName = eventData.Entity.Name; - //...your custom logic - } - - //... -} -```` - -This handler is executed only when a new tenant has been created. All the pre-built ABP [application modules](Modules/Index.md) define corresponding `ETO` types for their entities. So, you can easily get informed when they changes. - -> Notice that ABP doesn't publish distributed events for an entity by default. Because it has a cost and should be enabled by intention. See the [distributed event bus document](Distributed-Event-Bus.md) to learn more. - -## See Also - -* [Migration System for the EF Core](Entity-Framework-Core-Migrations.md) -* [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md) diff --git a/docs/en/Customizing-Application-Modules-Guide.md b/docs/en/Customizing-Application-Modules-Guide.md deleted file mode 100644 index 4fe3c71dbe..0000000000 --- a/docs/en/Customizing-Application-Modules-Guide.md +++ /dev/null @@ -1,108 +0,0 @@ -# Customizing the Existing Modules - -ABP Framework has been designed to support to build fully [modular applications](Module-Development-Basics.md) and systems. It also provides some [pre-built application modules](Modules/Index.md) those are **ready to use** in any kind of application. - -For example, you can **re-use** the [Identity Management Module](Modules/Identity.md) to add user, role and permission management to your application. The [application startup template](Startup-Templates/Application.md) already comes with Identity and some other modules **pre-installed**. - -## Re-Using an Application Module - -You have two options to re-use an application module. - -### As Package References - -You can add **NuGet** & **NPM** package references of the related module to your application and configure the module (based on its documentation) to integrate to your application. - -As mentioned before, the [application startup template](Startup-Templates/Application.md) already comes with some **fundamental modules pre-installed**. It uses the modules as NuGet & NPM package references. - -This approach has the following benefits: - -* Your solution will be **clean** and only contains your **own application code**. -* You can **easily upgrade** a module when a new version is available. `abp update` [CLI](CLI.md) command makes it even easier. In this way, you can continue to get **new features and bug fixes**. - -However, there is a drawback: - -* You may not able to **customize** the module because the module source is not in your solution. - -This document explains **how to customize or extend** a depended module without need to change its source code. While it is limited compared to a full source code change opportunity, there are still some good ways to make some customizations. - -If you don't think to make huge changes on the pre-built modules, re-using them as package reference is the recommended way. - -### Including the Source Code - -If you want to make **huge changes** or add **major features** on a pre-built module, but the available extension points are not enough, you can consider to directly work the source code of the depended module. - -In this case, you typically **add the source code** of the module to your solution and replace **every** package reference in the solution with its corresponding local project references. **[ABP CLI](CLI.md)**'s `add-module` command automates this process for you with the `--with-source-code` parameter. This command can also replace a module by its source code if the module already installed as NuGet packages. - - -#### Separating the Module Solution - -You may prefer to not include the module source code **directly into your solution**. Every module consists of 10+ project files and adding **multiple modules** may impact on the **size** of your solution **load & development time.** Also, you may have different development teams working on different modules, so you don't want to make the module code available to the application development team. - -In any case, you can create a **separate solution** for the desired module and depend on the module as project references out of the solution. We do it like that for the [abp repository](https://github.com/abpframework/abp/). - -> One problem we see is Visual Studio doesn't play nice with this kind of approach (it doesn't support well to have references to local projects out of the solution directory). If you get error while building the application (depends on an external module), run `dotnet restore` in the command line after opening the application's solution in the Visual Studio. - -#### Publishing the Customized Module as Packages - -One alternative scenario could be re-packaging the module source code (as NuGet/NPM packages) and using as package references. You can use a local private NuGet/NPM server for your company, for example. - -## Module Customization / Extending Approaches - -This section suggests some approaches if you decided to use pre-built application modules as NuGet/NPM package references. The following documents explain how to customize/extend existing modules in different ways. - -### Module Entity Extension System - -> Module entity extension system is the **main and high level extension system** that allows you to **define new properties** for existing entities of the depended modules. It automatically **adds properties to the entity, database, HTTP API and the user interface** in a single point. - -See the [Module Entity Extensions document](Module-Entity-Extensions.md) to learn how to use it. - -### Extending Entities - -If you only need to get/set extra data on an existing entity, follow the [Extending Entities](Customizing-Application-Modules-Extending-Entities.md) document. - -### Overriding Services/Components - -In addition to the extensibility systems, you can partially or completely override any service or user interface page/component. - -* [Overriding Services](Customizing-Application-Modules-Overriding-Services.md) -* [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md) - -### Additional UI Extensibility Points - -There are some low level systems that you can control entity actions, table columns and page toolbar of a page defined by a module. - -#### Entity Actions - -Entity action extension system allows you to add a new action to the action menu for an entity on the user interface; - -* [Entity Action Extensions for ASP.NET Core UI](UI/AspNetCore/Entity-Action-Extensions.md) -* [Entity Action Extensions for Blazor UI](UI/Blazor/Entity-Action-Extensions.md) -* [Entity Action Extensions for Angular](UI/Angular/Entity-Action-Extensions.md) - -#### Data Table Column Extensions - -Data table column extension system allows you to add a new column in the data table on the user interface; - -* [Data Table Column Extensions for ASP.NET Core UI](UI/AspNetCore/Data-Table-Column-Extensions.md) -* [Data Table Column Extensions for Blazor UI](UI/Blazor/Data-Table-Column-Extensions.md) -* [Data Table Column Extensions for Angular](UI/Angular/Data-Table-Column-Extensions.md) - -#### Page Toolbar - -Page toolbar system allows you to add components to the toolbar of a page; - -* [Page Toolbar Extensions for ASP.NET Core UI](UI/AspNetCore/Page-Toolbar-Extensions.md) -* [Page Toolbar Extensions for Blazor UI](UI/Blazor/Page-Toolbar-Extensions.md) -* [Page Toolbar Extensions for Angular](UI/Angular/Page-Toolbar-Extensions.md) - -#### Others - -* [Dynamic Form Extensions for Angular](UI/Angular/Dynamic-Form-Extensions.md) - -## See Also - -Also, see the following documents: - -* See [the localization document](Localization.md) to learn how to extend existing localization resources. -* See [the settings document](Settings.md) to learn how to change setting definitions of a depended module. -* See [the authorization document](Authorization.md) to learn how to change permission definitions of a depended module. diff --git a/docs/en/Customizing-Application-Modules-Overriding-Services.md b/docs/en/Customizing-Application-Modules-Overriding-Services.md deleted file mode 100644 index a1a6c8b778..0000000000 --- a/docs/en/Customizing-Application-Modules-Overriding-Services.md +++ /dev/null @@ -1,385 +0,0 @@ -# Customizing the Application Modules: Overriding Services - -You may need to **change behavior (business logic)** of a depended module for your application. In this case, you can use the power of the [dependency injection system](Dependency-Injection.md) to replace a service, controller or even a page model of the depended module by your own implementation. - -**Replacing a service** is possible for any type of class registered to the dependency injection, including services of the ABP Framework. - -You have different options can be used based on your requirement those will be explained in the next sections. - -> Notice that some service methods may not be virtual, so you may not be able to override. We make all virtual by design. If you find any method that is not overridable, please [create an issue](https://github.com/abpframework/abp/issues/new) or do it yourself and send a **pull request** on GitHub. - -## Replacing an Interface - -If given service defines an interface, like the `IdentityUserAppService` class implements the `IIdentityUserAppService`, you can re-implement the same interface and replace the current implementation by your class. Example: - -````csharp -public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency -{ - //... -} -```` - -`MyIdentityUserAppService` replaces the `IIdentityUserAppService` by naming convention (since both ends with `IdentityUserAppService`). If your class name doesn't match, you need to manually expose the service interface: - -````csharp -[ExposeServices(typeof(IIdentityUserAppService))] -public class TestAppService : IIdentityUserAppService, ITransientDependency -{ - //... -} -```` - -The dependency injection system allows to register multiple services for the same interface. The last registered one is used when the interface is injected. It is a good practice to explicitly replace the service. - -Example: - -````csharp -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IIdentityUserAppService))] -public class TestAppService : IIdentityUserAppService, ITransientDependency -{ - //... -} -```` - -In this way, there will be a single implementation of the `IIdentityUserAppService` interface, while it doesn't change the result for this case. Replacing a service is also possible by code: - -````csharp -context.Services.Replace( - ServiceDescriptor.Transient() -); -```` - -You can write this inside the `ConfigureServices` method of your [module](Module-Development-Basics.md). - -## Overriding a Service Class - -In most cases, you will want to change one or a few methods of the current implementation for a service. Re-implementing the complete interface would not be efficient in this case. As a better approach, inherit from the original class and override the desired method. - -### Example: Overriding an Application Service - -````csharp -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))] -public class MyIdentityUserAppService : IdentityUserAppService -{ - //... - public MyIdentityUserAppService( - IdentityUserManager userManager, - IIdentityUserRepository userRepository, - IGuidGenerator guidGenerator - ) : base( - userManager, - userRepository, - guidGenerator) - { - } - - public async override Task CreateAsync(IdentityUserCreateDto input) - { - if (input.PhoneNumber.IsNullOrWhiteSpace()) - { - throw new AbpValidationException( - "Phone number is required for new users!", - new List - { - new ValidationResult( - "Phone number can not be empty!", - new []{"PhoneNumber"} - ) - } - ); } - - return await base.CreateAsync(input); - } -} -```` - -This class **overrides** the `CreateAsync` method of the `IdentityUserAppService` [application service](Application-Services.md) to check the phone number. Then calls the base method to continue to the **underlying business logic**. In this way, you can perform additional business logic **before** and **after** the base logic. - -You could completely **re-write** the entire business logic for a user creation without calling the base method. - -### Example: Overriding a Domain Service - -````csharp -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IdentityUserManager))] -public class MyIdentityUserManager : IdentityUserManager -{ - public MyIdentityUserManager( - IdentityUserStore store, - IIdentityRoleRepository roleRepository, - IIdentityUserRepository userRepository, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - ILookupNormalizer keyNormalizer, - IdentityErrorDescriber errors, - IServiceProvider services, - ILogger logger, - ICancellationTokenProvider cancellationTokenProvider) : - base(store, - roleRepository, - userRepository, - optionsAccessor, - passwordHasher, - userValidators, - passwordValidators, - keyNormalizer, - errors, - services, - logger, - cancellationTokenProvider) - { - } - - public async override Task CreateAsync(IdentityUser user) - { - if (user.PhoneNumber.IsNullOrWhiteSpace()) - { - throw new AbpValidationException( - "Phone number is required for new users!", - new List - { - new ValidationResult( - "Phone number can not be empty!", - new []{"PhoneNumber"} - ) - } - ); - } - - return await base.CreateAsync(user); - } -} -```` - -This example class inherits from the `IdentityUserManager` [domain service](Domain-Services.md) and overrides the `CreateAsync` method to perform the same phone number check implemented above. The result is same, but this time we've implemented it inside the domain service assuming that this is a **core domain logic** for our system. - -> `[ExposeServices(typeof(IdentityUserManager))]` attribute is **required** here since `IdentityUserManager` does not define an interface (like `IIdentityUserManager`) and dependency injection system doesn't expose services for inherited classes (like it does for the implemented interfaces) by convention. - -Check the [localization system](Localization.md) to learn how to localize the error messages. - -### Example: Overriding a Repository - -````csharp -public class MyEfCoreIdentityUserRepository : EfCoreIdentityUserRepository -{ - public MyEfCoreIdentityUserRepository( - IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - /* You can override any base method here */ -} -```` - -In this example, we are overriding the `EfCoreIdentityUserRepository` class that is defined by the [Identity module](Modules/Identity.md). This is the [Entity Framework Core](Entity-Framework-Core.md) implementation of the user repository. - -Thanks to the naming convention (`MyEfCoreIdentityUserRepository` ends with `EfCoreIdentityUserRepository`), no additional setup is required. You can override any base method to customize it for your needs. - -However, if you inject `IRepository` or `IRepository`, it will still use the default repository implementation. To replace the default repository implementation, write the following code in the `ConfigureServices` method of your module class: - -````csharp -context.Services.AddDefaultRepository( - typeof(Volo.Abp.Identity.IdentityUser), - typeof(MyEfCoreIdentityUserRepository), - replaceExisting: true -); -```` - -In this way, your implementation will be used if you inject `IRepository`, `IRepository` or `IIdentityUserRepository`. - -If you want to add extra methods to your repository and use it in your own code, you can define an interface and expose it from your repository implementation. You can also extend the pre-built repository interface. Example: - -````csharp -public interface IMyIdentityUserRepository : IIdentityUserRepository -{ - public Task DeleteByEmailAddress(string email); -} -```` - -The `IMyIdentityUserRepository` interface extends the Identity module's `IIdentityUserRepository` interface. Then you can implement it as shown in the following example: - -````csharp -[ExposeServices(typeof(IMyIdentityUserRepository), IncludeDefaults = true)] -public class MyEfCoreIdentityUserRepository - : EfCoreIdentityUserRepository, IMyIdentityUserRepository -{ - public MyEfCoreIdentityUserRepository( - IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task DeleteByEmailAddress(string email) - { - var dbContext = await GetDbContextAsync(); - var user = await dbContext.Users.FirstOrDefaultAsync(u => u.Email == email); - if (user != null) - { - dbContext.Users.Remove(user); - } - } -} -```` - -The `MyEfCoreIdentityUserRepository` class implements the `IMyIdentityUserRepository` interface. `ExposeServices` attribute is needed since ABP can not expose `IMyIdentityUserRepository` by naming conventions (`MyEfCoreIdentityUserRepository` doesn't end with `MyIdentityUserRepository`). Now, you can inject the `IMyIdentityUserRepository` interface into your services and call its `DeleteByEmailAddress` method. - -### Example: Overriding a Controller - -````csharp -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Volo.Abp.Account; -using Volo.Abp.DependencyInjection; - -namespace MyProject.Controllers -{ - [Dependency(ReplaceServices = true)] - [ExposeServices(typeof(AccountController))] - public class MyAccountController : AccountController - { - public MyAccountController(IAccountAppService accountAppService) - : base(accountAppService) - { - - } - - public async override Task SendPasswordResetCodeAsync( - SendPasswordResetCodeDto input) - { - Logger.LogInformation("Your custom logic..."); - - await base.SendPasswordResetCodeAsync(input); - } - } -} -```` - -This example replaces the `AccountController` (An API Controller defined in the [Account Module](Modules/Account.md)) and overrides the `SendPasswordResetCodeAsync` method. - -**`[ExposeServices(typeof(AccountController))]` is essential** here since it registers this controller for the `AccountController` in the dependency injection system. `[Dependency(ReplaceServices = true)]` is also recommended to clear the old registration (even the ASP.NET Core DI system selects the last registered one). - -In addition, the `MyAccountController` will be removed from [`ApplicationModel`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.applicationmodels.applicationmodel.controllers) because it defines `ExposeServicesAttribute`. - -If `IncludeSelf = true` is specified, i.e. `[ExposeServices(typeof(AccountController), IncludeSelf = true)]`, then `AccountController` will be removed instead. This is useful for **extending** a controller. - -If you don't want to remove either controller, you can configure `AbpAspNetCoreMvcOptions`: - -```csharp -Configure(options => -{ - options.IgnoredControllersOnModelExclusion - .AddIfNotContains(typeof(MyAccountController)); -}); -``` - -### Overriding Other Classes - -Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above. - -## Extending Data Transfer Objects - -**Extending [entities](Entities.md)** is possible as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md). In this way, you can add **custom properties** to entities and perform **additional business logic** by overriding the related services as described above. - -It is also possible to extend Data Transfer Objects (**DTOs**) used by the application services. In this way, you can get extra properties from the UI (or client) and return extra properties from the service. - -### Example - -Assuming that you've already added a `SocialSecurityNumber` as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md) and want to include this information while getting the list of users from the `GetListAsync` method of the `IdentityUserAppService`. - -You can use the [object extension system](Object-Extensions.md) to add the property to the `IdentityUserDto`. Write this code inside the `YourProjectNameDtoExtensions` class comes with the application startup template: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber" - ); -```` - -This code defines a `SocialSecurityNumber` to the `IdentityUserDto` class as a `string` type. That's all. Now, if you call the `/api/identity/users` HTTP API (which uses the `IdentityUserAppService` internally) from a REST API client, you will see the `SocialSecurityNumber` value in the `extraProperties` section. - -````json -{ - "totalCount": 1, - "items": [{ - "tenantId": null, - "userName": "admin", - "name": "admin", - "surname": null, - "email": "admin@abp.io", - "emailConfirmed": false, - "phoneNumber": null, - "phoneNumberConfirmed": false, - "twoFactorEnabled": false, - "lockoutEnabled": true, - "lockoutEnd": null, - "concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c", - "isDeleted": false, - "deleterId": null, - "deletionTime": null, - "lastModificationTime": "2020-04-09T21:25:47.0740706", - "lastModifierId": null, - "creationTime": "2020-04-09T21:25:46.8308744", - "creatorId": null, - "id": "8edecb8f-1894-a9b1-833b-39f4725db2a3", - "extraProperties": { - "SocialSecurityNumber": "123456789" - } - }] -} -```` - -Manually added the `123456789` value to the database for now. - -All pre-built modules support extra properties in their DTOs, so you can configure easily. - -### Definition Check - -When you [define](Customizing-Application-Modules-Extending-Entities.md) an extra property for an entity, it doesn't automatically appear in all the related DTOs, because of the security. The extra property may contain a sensitive data and you may not want to expose it to the clients by default. - -So, you need to explicitly define the same property for the corresponding DTO if you want to make it available for the DTO (as just done above). If you want to allow to set it on user creation, you also need to define it for the `IdentityUserCreateDto`. - -If the property is not so secure, this can be tedious. Object extension system allows you to ignore this definition check for a desired property. See the example below: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - options.MapEfCore(b => b.HasMaxLength(32)); - options.CheckPairDefinitionOnMapping = false; - } - ); -```` - -This is another approach to define a property for an entity (`ObjectExtensionManager` has more, see [its document](Object-Extensions.md)). This time, we set `CheckPairDefinitionOnMapping` to false to skip definition check while mapping entities to DTOs and vice verse. - -If you don't like this approach but want to add a single property to multiple objects (DTOs) easier, `AddOrUpdateProperty` can get an array of types to add the extra property: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - new[] - { - typeof(IdentityUserDto), - typeof(IdentityUserCreateDto), - typeof(IdentityUserUpdateDto) - }, - "SocialSecurityNumber" - ); -```` - -### About the User Interface - -This system allows you to add extra properties to entities and DTOs and execute custom business code, however it does nothing related to the User Interface. - -See [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md) guide for the UI part. - -## How to Find the Services? - -[Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services. diff --git a/docs/en/Customizing-Application-Modules-Overriding-User-Interface.md b/docs/en/Customizing-Application-Modules-Overriding-User-Interface.md deleted file mode 100644 index b6ad7b05c2..0000000000 --- a/docs/en/Customizing-Application-Modules-Overriding-User-Interface.md +++ /dev/null @@ -1,7 +0,0 @@ -# Overriding the User Interface - -You may want to override a page, a component, a JavaScript, CSS or an image file of your depended module. Overriding the UI completely depends on the UI framework you're using. Select the UI framework to continue: - -* [ASP.NET Core (MVC / Razor Pages)](UI/AspNetCore/Customization-User-Interface.md) -* [Angular](UI/Angular/Customization-User-Interface.md) -* [Blazor](UI/Blazor/Customization-Overriding-Components.md) \ No newline at end of file diff --git a/docs/en/Dapper.md b/docs/en/Dapper.md deleted file mode 100644 index 727d4961c2..0000000000 --- a/docs/en/Dapper.md +++ /dev/null @@ -1,69 +0,0 @@ -# Dapper Integration - -[Dapper](https://github.com/DapperLib/Dapper) is a simple and lightweight object mapper for .NET. A key feature of Dapper is its [high performance](https://github.com/DapperLib/Dapper#performance) compared to other ORMs. - -While you can use Dapper as is in your ABP applications, there is also an integration package that simplifies creating repository classes using Dapper. - -> ABP's Dapper integration package is based on Entity Framework Core (EF Core). That means it assumes you will use Dapper mixed with EF Core where EF Core is the primary database provider and you use Dapper when you need to fine-tune your quires and get the maximum performance. See [this article](https://community.abp.io/posts/using-dapper-with-the-abp-framework-shp74p2l) if you want to know why it is like that. - -## Installation - -You can use the [ABP CLI](CLI.md) to install the [Volo.Abp.Dapper](https://www.nuget.org/packages/Volo.Abp.Dapper) package to your project. Execute the following command in the folder of the `.csproj` file that you want to install the package on: - -````bash -abp add-package Volo.Abp.Dapper -```` - -> If you haven't done it yet, you first need to install the ABP CLI. For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Dapper). -> -> If you have a layered solution, it is suggested to install that package to your database layer of the solution. - -## Implement a Dapper Repository - -The best way to interact with Dapper is to create a [repository](Repositories.md) class that abstracts your Dapper database operations. The following example creates a new repository class that works with the `People` table: - -```C# -public class PersonDapperRepository : - DapperRepository, ITransientDependency -{ - public PersonDapperRepository(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public virtual async Task> GetAllPersonNamesAsync() - { - var dbConnection = await GetDbConnectionAsync(); - return (await dbConnection.QueryAsync( - "select Name from People", - transaction: await GetDbTransactionAsync()) - ).ToList(); - } - - public virtual async Task UpdatePersonNamesAsync(string name) - { - var dbConnection = await GetDbConnectionAsync(); - return await dbConnection.ExecuteAsync( - "update People set Name = @NewName", - new { NewName = name }, - await GetDbTransactionAsync() - ); - } -} -``` - -Let's examine this class: - -- It inherits from the `DapperRepository` class, which provides useful methods and properties for database operations. It also implements the `IUnitOfWorkEnabled` interface, so ABP makes the database connection (and transaction if requested) available in the method body by implementing dynamic proxies (a.k.a. interception). -- It gets an `IDbContextProvider` object where `MyAppDbContext` is type of your Entity Framework Core `DbContext` class. It should be configured as explained in the [EF Core document](Entity-Framework-Core.md). If you've created by ABP's startup template, then it should already be configured. -- The `GetAllPersonNamesAsync` and `UpdatePersonNamesAsync` method's been made `virtual`. That's needed to make the interception process working. -- We've used the `GetDbConnectionAsync` and `GetDbTransactionAsync` methods to obtain the current database connection and transaction (that is managed by ABP's [Unit of Work](Unit-Of-Work.md) system). - -Then you can [inject](Dependency-Injection.md) `PersonDapperRepository` to any service to perform these database operations. If you want to implement a layered solution, we suggest to introduce an `IPersonDapperRepository` interface in your domain layer, implement it in your database later, then inject the interface to use the repository service. - -> If you want to learn more details and examples of using Dapper with the ABP Framework, [check this community article](https://community.abp.io/posts/using-dapper-with-the-abp-framework-shp74p2l). - -## See Also - -* [Community Article: Using Dapper with the ABP Framework](https://community.abp.io/posts/using-dapper-with-the-abp-framework-shp74p2l) -* [Entity Framework Core integration document](Entity-Framework-Core.md) diff --git a/docs/en/Dapr/Index.md b/docs/en/Dapr/Index.md deleted file mode 100644 index 464b56ea30..0000000000 --- a/docs/en/Dapr/Index.md +++ /dev/null @@ -1,443 +0,0 @@ -# ABP Dapr Integration - -> This document assumes that you are already familiar with [Dapr](https://dapr.io/) and you want to use it in your ABP based applications. - -[Dapr](https://dapr.io/) (Distributed Application Runtime) provides APIs that simplify microservice connectivity. It is an open source project that is mainly backed by Microsoft. It is also a CNCF (Cloud Native Computing Foundation) project and trusted by the community. - -ABP and Dapr have some intersecting features like service-to-service communication, distributed message bus and distributed locking. However their purposes are totally different. ABP's goal is to provide an end-to-end developer experience by offering an opinionated architecture and providing the necessary infrastructure libraries, reusable modules and tools to implement that architecture properly. Dapr's purpose, on the other hand, is to provide a runtime to decouple common microservice communication patterns from your application logic. - -ABP and Dapr can perfectly work together in the same application. ABP offers some packages to provide better integration where Dapr features intersect with ABP. You can use other Dapr features with no ABP integration packages based on [its own documentation](https://docs.dapr.io/). - -## ABP Dapr Integration Packages - -ABP provides the following NuGet packages for the Dapr integration: - -* [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr): The main Dapr integration package. All other packages depend on this package. -* [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr): Integration package for ABP's [dynamic](../API/Dynamic-CSharp-API-Clients.md) and [static](../API/Static-CSharp-API-Clients.md) C# API Client Proxies systems with Dapr's [service invocation](https://docs.dapr.io/developing-applications/building-blocks/service-invocation/service-invocation-overview/) building block. -* [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr): Implements ABP's distributed event bus with Dapr's [publish & subscribe](https://docs.dapr.io/developing-applications/building-blocks/pubsub/) building block. With this package, you can send events, but can not receive. -* [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus): Provides the endpoints to receive events from Dapr's [publish & subscribe](https://docs.dapr.io/developing-applications/building-blocks/pubsub/) building block. Use this package to send and receive events. -* [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr): Uses Dapr's [distributed lock](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/) building block for [distributed locking](../Distributed-Locking.md) service of the ABP Framework. - -In the following sections, we will see how to use these packages to use Dapr in your ABP based solutions. - -## Basics - -### Installation - -> This section explains how to add [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr), the core Dapr integration package to your project. If you are using one of the other Dapr integration packages, you can skip this section since this package will be indirectly added. - -Use the ABP CLI to add the [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.Dapr` package. -* Run the `abp add-package Volo.Abp.Dapr` command. - -If you want to do it manually, install the [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. - -### AbpDaprOptions - -`AbpDaprOptions` is the main [options class](../Options.md) that you can configure the global Dapr settings with. **All settings are optional and you mostly don't need to configure them.** If you need, you can configure it in the `ConfigureServices` method of your [module class](../Module-Development-Basics.md): - -````csharp -Configure(options => -{ - // ... -}); -```` - -Available properties of the `AbpDaprOptions` class: - -* `HttpEndpoint` (optional): HTTP endpoint that is used while creating a `DaprClient` object. If you don't specify, the default value is used. -* `GrpcEndpoint` (optional): The gRPC endpoint that is used while creating a `DaprClient` object. If you don't specify, the default value is used. -* `DaprApiToken` (optional): The [Dapr API token](https://docs.dapr.io/operations/security/api-token/) that is used while sending requests from the application to Dapr. It is filled from the `DAPR_API_TOKEN` environment variable by default (which is set by Dapr once it is configured). See the *Security* section in this document for details. -* `AppApiToken` (optional): The [App API token](https://docs.dapr.io/operations/security/app-api-token/) that is used to validate requests coming from Dapr. It is filled from the `APP_API_TOKEN` environment variable by default (which is set by Dapr once it is configured). See the *Security* section in this document for details. - -Alternatively, you can configure the options in the `Dapr` section of your `appsettings.json` file. Example: - -````csharp -"Dapr": { - "HttpEndpoint": "http://localhost:3500/" -} -```` - -### IAbpDaprClientFactory - -`IAbpDaprClientFactory` can be used to create `DaprClient` or `HttpClient` objects to perform operations on Dapr. It uses `AbpDaprOptions`, so you can configure the settings in a central place. - -**Example usages:** - -````csharp -public class MyService : ITransientDependency -{ - private readonly IAbpDaprClientFactory _daprClientFactory; - - public MyService(IAbpDaprClientFactory daprClientFactory) - { - _daprClientFactory = daprClientFactory; - } - - public async Task DoItAsync() - { - // Create a DaprClient object with default options - DaprClient daprClient = await _daprClientFactory.CreateAsync(); - - /* Create a DaprClient object with configuring - * the DaprClientBuilder object */ - DaprClient daprClient2 = await _daprClientFactory - .CreateAsync(builder => - { - builder.UseDaprApiToken("..."); - }); - - // Create an HttpClient object - HttpClient httpClient = await _daprClientFactory.CreateHttpClientAsync("target-app-id"); - } -} -```` - -`CreateHttpClientAsync` method also gets optional `daprEndpoint` and `daprApiToken` parameters. - -> You can use Dapr API to create client objects in your application. Using `IAbpDaprClientFactory` is recommended, but not required. - -## C# API Client Proxies Integration - -ABP can [dynamically](../API/Dynamic-CSharp-API-Clients.md) or [statically](../API/Static-CSharp-API-Clients.md) generate proxy classes to invoke your HTTP APIs from a Dotnet client application. It makes perfect sense to consume HTTP APIs in a distributed system. The [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) package configures the client-side proxies system, so it uses Dapr's service invocation building block for the communication between your applications. - -### Installation - -Use the ABP CLI to add the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package to your project (to the client side): - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.Http.Client.Dapr` package to. -* Run the `abp add-package Volo.Abp.Http.Client.Dapr` command. - -If you want to do it manually, install the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpHttpClientDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. - -### Configuration - -Once you install the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package, all you need to do is to configure ABP's remote services option either in `appsettings.json` or using the `AbpRemoteServiceOptions` [options class](../Options.md). - -**Example:** - -````csharp -{ - "RemoteServices": { - "Default": { - "BaseUrl": "http://dapr-httpapi/" - } - } -} -```` - -`dapr-httpapi` in this example is the application id of the server application in your Dapr configuration. - -The remote service name (`Default` in this example) should match the remote service name specified in the `AddHttpClientProxies` call for dynamic client proxies or the `AddStaticHttpClientProxies` call for static client proxies. Using `Default` is fine if your client communicates to a single server. However, if your client uses multiple servers, you typically have multiple keys in the `RemoteServices` configuration. Once you configure the remote service endpoints as Dapr application ids, it will automatically work and make the HTTP calls through Dapr when you use ABP's client proxy system. - -> See the [dynamic](../API/Dynamic-CSharp-API-Clients.md) and [static](../API/Static-CSharp-API-Clients.md) client proxy documents for details about the ABP's client proxy system. - -## Distributed Event Bus Integration - -[ABP's distributed event bus](../Distributed-Event-Bus.md) system provides a convenient abstraction to allow applications to communicate asynchronously via events. ABP has integration packages with various distributed messaging systems, like RabbitMQ, Kafka, and Azure. Dapr also has a [publish & subscribe building block](https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-overview/) for the same purpose: distributed messaging / events. - -ABP's [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) and [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) packages make it possible to use the Dapr infrastructure for ABP's distributed event bus. - -The [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package can be used by any type of application (e.g., a Console or ASP.NET Core application) to publish events through Dapr. To be able to receive messages (by subscribing to events), you need to have the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package installed, and your application should be an ASP.NET Core application. - -### Installation - -If your application is an ASP.NET Core application and you want to send and receive events, you need to install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package as described below: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.AspNetCore.Mvc.Dapr.EventBus` package to. -* Run the `abp add-package Volo.Abp.AspNetCore.Mvc.Dapr.EventBus` command. - -If you want to do it manually, install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) NuGet package to your project and add `[DependsOn(typeof(AbpAspNetCoreMvcDaprEventBusModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. - -> **If you install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package, you don't need to install the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package, because the first one already has a reference to the latter one.** - -If your application is not an ASP.NET Core application, you can't receive events from Dapr, at least with ABP's integration packages (see [Dapr's document](https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/) if you want to receive events in a different type of application). However, you can still publish messages using the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package. In this case, follow the steps below to install that package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Dapr` package to. -* Run the `abp add-package Volo.Abp.EventBus.Dapr` command. - -If you want to do it manually, install the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. - -### Configuration - -You can configure the `AbpDaprEventBusOptions` [options class](../Options.md) for Dapr configuration: - -````csharp -Configure(options => -{ - options.PubSubName = "pubsub"; -}); -```` - -Available properties of the `AbpDaprEventBusOptions` class: - -* `PubSubName` (optional): The `pubsubName` parameter while publishing messages through the `DaprClient.PublishEventAsync` method. Default value: `pubsub`. - -### The ABP Subscription Endpoints - -ABP provides the following endpoints to receive events from Dapr: - -* `dapr/subscribe`: Dapr uses this endpoint to get a list of subscriptions from the application. ABP automatically returns all the subscriptions for your distributed event handler classes and custom controller actions with the `Topic` attribute. -* `api/abp/dapr/event`: The unified endpoint to receive all the events from Dapr. ABP dispatches the events to your event handlers based on the topic name. - -> **Since ABP will call `MapSubscribeHandler` internally, you should not manually call it anymore.** You can use the `app.UseCloudEvents()` middleware in your ASP.NET Core pipeline if you want to support the [CloudEvents](https://cloudevents.io/) standard. - -### Usage - -#### The ABP Way - -You can follow [ABP's distributed event bus documentation](../Distributed-Event-Bus.md) to learn how to publish and subscribe to events in the ABP way. No change required in your application code to use Dapr pub-sub. ABP will automatically subscribe to Dapr for your event handler classes (that implement the `IDistributedEventHandler` interface). - -ABP provides `api/abp/dapr/event` - -**Example: Publish an event using the `IDistributedEventBus` service** - -````csharp -public class MyService : ITransientDependency -{ - private readonly IDistributedEventBus _distributedEventBus; - - public MyService(IDistributedEventBus distributedEventBus) - { - _distributedEventBus = distributedEventBus; - } - - public async Task DoItAsync() - { - await _distributedEventBus.PublishAsync(new StockCountChangedEto - { - ProductCode = "AT837234", - NewStockCount = 42 - }); - } -} -```` - -**Example: Subscribe to an event by implementing the `IDistributedEventHandler` interface** - -````csharp -public class MyHandler : - IDistributedEventHandler, - ITransientDependency -{ - public async Task HandleEventAsync(StockCountChangedEto eventData) - { - var productCode = eventData.ProductCode; - // ... - } -} -```` - -See [ABP's distributed event bus documentation](../Distributed-Event-Bus.md) to learn the details. - -#### Using the Dapr API - -In addition to ABP's standard distributed event bus system, you can also use Dapr's API to publish events. - -> If you directly use the Dapr API to publish events, you may not benefit from ABP's standard distributed event bus features, like the outbox/inbox pattern implementation. - -**Example: Publish an event using `DaprClient`** - -````csharp -public class MyService : ITransientDependency -{ - private readonly DaprClient _daprClient; - - public MyService(DaprClient daprClient) - { - _daprClient = daprClient; - } - - public async Task DoItAsync() - { - await _daprClient.PublishEventAsync( - "pubsub", // pubsub name - "StockChanged", // topic name - new StockCountChangedEto // event data - { - ProductCode = "AT837234", - NewStockCount = 42 - } - ); - } -} -```` - -**Example: Subscribe to an event by creating an ASP.NET Core controller** - -````csharp -public class MyController : AbpController -{ - [HttpPost("/stock-changed")] - [Topic("pubsub", "StockChanged")] - public async Task TestRouteAsync([FromBody] StockCountChangedEto model) - { - HttpContext.ValidateDaprAppApiToken(); - - // Do something with the event - return Ok(); - } -} -```` - -`HttpContext.ValidateDaprAppApiToken()` extension method is provided by ABP to check if the request is coming from Dapr. This is optional. You should configure Dapr to send the App API token to your application if you want to enable the validation. If not configured, `ValidateDaprAppApiToken()` does nothing. See [Dapr's App API Token document](https://docs.dapr.io/operations/security/app-api-token/) for more information. Also see the *AbpDaprOptions* and *Security* sections in this document. - -See the [Dapr documentation](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/publish-subscribe) to learn the details of sending & receiving events with the Dapr API. - -## Distributed Lock - -> Dapr's distributed lock feature is currently in the Alpha stage and may not be stable yet. It is not suggested to replace ABP's distributed lock with Dapr in that point. - -ABP provides a [Distributed Locking](../Distributed-Locking.md) abstraction to control access to a shared resource by multiple applications. Dapr also has a [distributed lock building block](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/). The [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) package makes ABP use Dapr's distributed locking system. - -### Installation - -Use the ABP CLI to add the [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) NuGet package to your project (to the client side): - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed it before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.DistributedLocking.Dapr` package to. -* Run the `abp add-package Volo.Abp.DistributedLocking.Dapr` command. - -If you want to do it manually, install the [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpDistributedLockingDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. - -### Configuration - -You can use the `AbpDistributedLockDaprOptions` options class in the `ConfigureServices` method of [your module](../Module-Development-Basics.md) to configure the Dapr distributed lock: - -````csharp -Configure(options => -{ - options.StoreName = "mystore"; -}); -```` - -The following options are available: - -* **`StoreName`** (required): The store name used by Dapr. Lock key names are scoped in the same store. That means different applications can acquire the same lock name in different stores. Use the same store name for the same resources you want to control the access of. -* `Owner` (optional): The `owner` value used by the `DaprClient.Lock` method. If you don't specify, ABP uses a random value, which is fine in general. -* `DefaultExpirationTimeout` (optional): Default value of the time after which the lock gets expired. Default value: 2 minutes. - -### Usage - -You can inject and use the `IAbpDistributedLock` service, just like explained in the [Distributed Locking document](../Distributed-Locking.md). - -**Example:** - -````csharp -public class MyService : ITransientDependency -{ - private readonly IAbpDistributedLock _distributedLock; - - public MyService(IAbpDistributedLock distributedLock) - { - _distributedLock = distributedLock; - } - - public async Task MyMethodAsync() - { - await using (var handle = - await _distributedLock.TryAcquireAsync("MyLockName")) - { - if (handle != null) - { - // your code that access the shared resource - } - } - } -} -```` - -There are two points we should mention about the `TryAcquireAsync` method, as different from ABP's standard usage: - -* The `timeout` parameter is currently not used (even if you specify it), because Dapr doesn't support waiting to obtain the lock. -* Dapr uses the expiration timeout system (that means the lock is automatically released after that timeout even if you don't release the lock by disposing the handler). However, ABP's `TryAcquireAsync` method has no such a parameter. Currently, you can set `AbpDistributedLockDaprOptions.DefaultExpirationTimeout` as a global value in your application. - -As mentioned first, Dapr's distributed lock feature is currently in the Alpha stage and its API is a candidate to change. You should use it as is if you want, but be ready for the changes in the future. For now, we are recommending to use the [DistributedLock](https://github.com/madelson/DistributedLock) library as explained in ABP's [Distributed Locking document](../Distributed-Locking.md). - -## Security - -If you are using Dapr, most or all the incoming and outgoing requests in your application pass through Dapr. Dapr uses two kinds of API tokens to secure the communication between your application and Dapr. - -### Dapr API Token - -> This token is automatically set by default and generally you don't care about it. - -The [Enable API token authentication in Dapr](https://docs.dapr.io/operations/security/api-token/) document describes what the Dapr API token is and how it is configured. Please read that document if you want to enable it for your application. - -If you enable the Dapr API token, you should send that token in every request to Dapr from your application. `AbpDaprOptions` defines a `DaprApiToken` property as a central point to configure the Dapr API token in your application. - -The default value of the `DaprApiToken` property is set from the `DAPR_API_TOKEN` environment variable and that environment variable is set by Dapr when it runs. So, most of the time, you don't need to configure `AbpDaprOptions.DaprApiToken` in your application. However, if you need to configure (or override) it, you can do in the `ConfigureServices` method of your module class as shown in the following code block: - -````csharp -Configure(options => -{ - options.DaprApiToken = "..."; -}); -```` - -Or you can set it in your `appsettings.json` file: - -````json -"Dapr": { - "DaprApiToken": "..." -} -```` - -Once you set it, it is used when you use `IAbpDaprClientFactory`. If you need that value in your application, you can inject `IDaprApiTokenProvider` and use its `GetDaprApiToken()` method. - -### App API Token - -> Enabling App API token validation is strongly recommended. Otherwise, for example, any client can directly call your event subscription endpoint, and your application acts like an event has occurred (if there is no other security policy in your event subscription endpoint). - -The [Authenticate requests from Dapr using token authentication](https://docs.dapr.io/operations/security/app-api-token/) document describes what the App API token is and how it is configured. Please read that document if you want to enable it for your application. - -If you enable the App API token, you can validate it to ensure that the request is coming from Dapr. ABP provides useful shortcuts to validate it. - -**Example: Validate the App API token in an event handling HTTP API** - -````csharp -public class MyController : AbpController -{ - [HttpPost("/stock-changed")] - [Topic("pubsub", "StockChanged")] - public async Task TestRouteAsync([FromBody] StockCountChangedEto model) - { - // Validate the App API token! - HttpContext.ValidateDaprAppApiToken(); - - // Do something with the event - return Ok(); - } -} -```` - -`HttpContext.ValidateDaprAppApiToken()` is an extension method provided by the ABP Framework. It throws an `AbpAuthorizationException` if the token was missing or wrong in the HTTP header (the header name is `dapr-api-token`). You can also inject `IDaprAppApiTokenValidator` and use its methods to validate the token in any service (not only in a controller class). - -You can configure `AbpDaprOptions.AppApiToken` if you want to set (or override) the App API token value. The default value is set by the `APP_API_TOKEN` environment variable. You can change it in the `ConfigureServices` method of your module class as shown in the following code block: - -````csharp -Configure(options => -{ - options.AppApiToken = "..."; -}); -```` - -Or you can set it in your `appsettings.json` file: - -````json -"Dapr": { - "AppApiToken": "..." -} -```` - -If you need that value in your application, you can inject `IDaprApiTokenProvider` and use its `GetAppApiToken()` method. - -## See Also - -* [Dapr for .NET Developers](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/) -* [The Official Dapr Documentation](https://docs.dapr.io/) diff --git a/docs/en/Data-Access.md b/docs/en/Data-Access.md deleted file mode 100644 index 7ba85956b9..0000000000 --- a/docs/en/Data-Access.md +++ /dev/null @@ -1,13 +0,0 @@ -# Data Access - -ABP framework was designed as database agnostic. It can work any type of data source by the help of the [repository](Repositories.md) and [unit of work](Unit-Of-Work.md) abstractions. Currently, the following providers are implemented as official: - -* [Entity Framework Core](Entity-Framework-Core.md) (works with [various DBMS and providers](https://docs.microsoft.com/en-us/ef/core/providers/).) -* [MongoDB](MongoDB.md) -* [Dapper](Dapper.md) - -## See Also - -* [Connection Strings](Connection-Strings.md) -* [Data Seeding](Data-Seeding.md) -* [Data Filtering](Data-Filtering.md) \ No newline at end of file diff --git a/docs/en/Data-Filtering.md b/docs/en/Data-Filtering.md deleted file mode 100644 index 830979e710..0000000000 --- a/docs/en/Data-Filtering.md +++ /dev/null @@ -1,318 +0,0 @@ -# Data Filtering - -[Volo.Abp.Data](https://www.nuget.org/packages/Volo.Abp.Data) package defines services to automatically filter data on querying from a database. - -## Pre-Defined Filters - -ABP defines some filters out of the box. - -### ISoftDelete - -Used to mark an [entity](Entities.md) as deleted instead of actually deleting it. Implement the `ISoftDelete` interface to make your entity "soft delete". - -Example: - -````csharp -using System; -using Volo.Abp; -using Volo.Abp.Domain.Entities; - -namespace Acme.BookStore -{ - public class Book : AggregateRoot, ISoftDelete - { - public string Name { get; set; } - - public bool IsDeleted { get; set; } //Defined by ISoftDelete - } -} -```` - -`ISoftDelete` defines the `IsDeleted` property. When you delete a book using [repositories](Repositories.md), ABP automatically sets `IsDeleted` to true and protects it from actual deletion (you can also manually set the `IsDeleted` property to true if you need). In addition, it **automatically filters deleted entities** when you query the database. - -> `ISoftDelete` filter is enabled by default and you can not get deleted entities from database unless you explicitly disable it. See the `IDataFilter` service below. - -> Soft-delete entities can be hard-deleted when you use `HardDeleteAsync` method on the repositories. - -### IMultiTenant - -[Multi-tenancy](Multi-Tenancy.md) is an efficient way of creating SaaS applications. Once you create a multi-tenant application, you typically want to isolate data between tenants. Implement `IMultiTenant` interface to make your entity "multi-tenant aware". - -Example: - -````csharp -using System; -using Volo.Abp; -using Volo.Abp.Domain.Entities; -using Volo.Abp.MultiTenancy; - -namespace Acme.BookStore -{ - public class Book : AggregateRoot, ISoftDelete, IMultiTenant - { - public string Name { get; set; } - - public bool IsDeleted { get; set; } //Defined by ISoftDelete - - public Guid? TenantId { get; set; } //Defined by IMultiTenant - } -} -```` - -`IMultiTenant` interface defines the `TenantId` property which is then used to automatically filter the entities for the current tenant. See the [Multi-tenancy](Multi-Tenancy.md) document for more. - -## IDataFilter Service: Enable/Disable Data Filters - -You can control the filters using `IDataFilter` service. - -Example: - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore -{ - public class MyBookService : ITransientDependency - { - private readonly IDataFilter _dataFilter; - private readonly IRepository _bookRepository; - - public MyBookService( - IDataFilter dataFilter, - IRepository bookRepository) - { - _dataFilter = dataFilter; - _bookRepository = bookRepository; - } - - public async Task> GetAllBooksIncludingDeletedAsync() - { - //Temporary disable the ISoftDelete filter - using (_dataFilter.Disable()) - { - return await _bookRepository.GetListAsync(); - } - } - } -} -```` - -* [Inject](Dependency-Injection.md) the `IDataFilter` service to your class. -* Use the `Disable` method within a `using` statement to create a code block where the `ISoftDelete` filter is disabled inside it. - -In addition to the `Disable()` method; - -* `IDataFilter.Enable()` method can be used to enable a filter. `Enable` and `Disable` methods can be used in a **nested** way to define inner scopes. - -* `IDataFilter.IsEnabled()` can be used to check whether a filter is currently enabled or not. - -> Always use the `Disable` and `Enable` methods it inside a `using` block to guarantee that the filter is reset to its previous state. - -### The Generic IDataFilter Service - -`IDataFilter` service has a generic version, `IDataFilter` that injects a more restricted and explicit data filter based on the filter type. - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore -{ - public class MyBookService : ITransientDependency - { - private readonly IDataFilter _softDeleteFilter; - private readonly IRepository _bookRepository; - - public MyBookService( - IDataFilter softDeleteFilter, - IRepository bookRepository) - { - _softDeleteFilter = softDeleteFilter; - _bookRepository = bookRepository; - } - - public async Task> GetAllBooksIncludingDeletedAsync() - { - //Temporary disable the ISoftDelete filter - using (_softDeleteFilter.Disable()) - { - return await _bookRepository.GetListAsync(); - } - } - } -} -```` - -* This usage determines the filter type while injecting the `IDataFilter` service. -* In this case you can use the `Disable()` and `Enable()` methods without specifying the filter type. - -## AbpDataFilterOptions - -`AbpDataFilterOptions` can be used to [set options](Options.md) for the data filter system. - -The example code below disables the `ISoftDelete` filter by default which will cause to include deleted entities when you query the database unless you explicitly enable the filter: - -````csharp -Configure(options => -{ - options.DefaultStates[typeof(ISoftDelete)] = new DataFilterState(isEnabled: false); -}); -```` - -> Carefully change defaults for global filters, especially if you are using a pre-built module which might be developed assuming the soft delete filter is turned on by default. But you can do it for your own defined filters safely. - -## Defining Custom Filters - -Defining and implementing a new filter highly depends on the database provider. ABP implements all pre-defined filters for all database providers. - -When you need it, start by defining an interface (like `ISoftDelete` and `IMultiTenant`) for your filter and implement it for your entities. - -Example: - -````csharp -public interface IIsActive -{ - bool IsActive { get; } -} -```` - -Such an `IIsActive` interface can be used to filter active/passive data and can be easily implemented by any [entity](Entities.md): - -````csharp -public class Book : AggregateRoot, IIsActive -{ - public string Name { get; set; } - - public bool IsActive { get; set; } //Defined by IIsActive -} -```` - -### EntityFramework Core - -ABP uses [EF Core's Global Query Filters](https://docs.microsoft.com/en-us/ef/core/querying/filters) system for the [EF Core Integration](Entity-Framework-Core.md). So, it is well integrated to EF Core and works as expected even if you directly work with `DbContext`. - -Best way to implement a custom filter is to override `ShouldFilterEntity` and `CreateFilterExpression` method for your `DbContext`. Example: - -````csharp -protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled() ?? false; - -protected override bool ShouldFilterEntity(IMutableEntityType entityType) -{ - if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) - { - return true; - } - - return base.ShouldFilterEntity(entityType); -} - -protected override Expression> CreateFilterExpression() -{ - var expression = base.CreateFilterExpression(); - - if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) - { - Expression> isActiveFilter = - e => !IsActiveFilterEnabled || EF.Property(e, "IsActive"); - expression = expression == null - ? isActiveFilter - : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter); - } - - return expression; -} -```` - -* Added a `IsActiveFilterEnabled` property to check if `IIsActive` is enabled or not. It internally uses the `IDataFilter` service introduced before. -* Overrided the `ShouldFilterEntity` and `CreateFilterExpression` methods, checked if given entity implements the `IIsActive` interface and combines the expressions if necessary. - -In addition you can also use `HasAbpQueryFilter` to set a filter for an entity. It will combine your filter with ABP EF Core builtin global query filters. - -````csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) -{ - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.HasAbpQueryFilter(e => e.Name.StartsWith("abp")); - }); -} -```` - -### MongoDB - -ABP abstracts the `IMongoDbRepositoryFilterer` interface to implement data filtering for the [MongoDB Integration](MongoDB.md), it works only if you use the repositories properly. Otherwise, you should manually filter the data. - -Currently, the best way to implement a data filter for the MongoDB integration is to create a derived class of `MongoDbRepositoryFilterer` and override `FilterQueryable`. Example: - -````csharp -[ExposeServices(typeof(IMongoDbRepositoryFilterer))] -public class BookMongoDbRepositoryFilterer : MongoDbRepositoryFilterer , ITransientDependency -{ - public BookMongoDbRepositoryFilterer( - IDataFilter dataFilter, - ICurrentTenant currentTenant) : - base(dataFilter, currentTenant) - { - } - - public override TQueryable FilterQueryable(TQueryable query) - { - if (DataFilter.IsEnabled()) - { - return (TQueryable)query.Where(x => x.IsActive); - } - - return base.FilterQueryable(query); - } -} -```` - -This example implements it only for the `Book` entity. If you want to implement for all entities (those implement the `IIsActive` interface), create your own custom MongoDB repository filterer base class and override the `AddGlobalFilters` as shown below: - -````csharp -public abstract class MyMongoRepository : MongoDbRepository - where TMongoDbContext : IAbpMongoDbContext - where TEntity : class, IEntity -{ - protected MyMongoRepository(IMongoDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - - } - - protected override void AddGlobalFilters(List> filters) - { - base.AddGlobalFilters(filters); - - if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)) - && DataFilter.IsEnabled()) - { - filters.Add(Builders.Filter.Eq(e => ((IIsActive)e).IsActive, true)); - } - } -} - - -public class MyMongoDbModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //....... - context.Services - .Replace(ServiceDescriptor.Transient(typeof(IMongoDbRepositoryFilterer<,>),typeof(MyMongoDbRepositoryFilterer<,>))); - } -} -```` \ No newline at end of file diff --git a/docs/en/Data-Seeding.md b/docs/en/Data-Seeding.md deleted file mode 100644 index 011a5bf37a..0000000000 --- a/docs/en/Data-Seeding.md +++ /dev/null @@ -1,176 +0,0 @@ -# Data Seeding - -## Introduction - -Some applications (or modules) using a database may need to have some **initial data** to be able to properly start and run. For example, an **admin user** & roles must be available at the beginning. Otherwise you can not **login** to the application to create new users and roles. - -Data seeding is also useful for [testing](Testing.md) purpose, so your automatic tests can assume some initial data available in the database. - -### Why a Data Seed System? - -While EF Core Data Seeding system provides a way, it is very limited and doesn't cover production scenarios. Also, it is only for EF Core. - -ABP Framework provides a data seed system that is; - -* **Modular**: Any [module](Module-Development-Basics.md) can silently contribute to the data seeding process without knowing and effecting each other. In this way, a module seeds its own initial data. -* **Database Independent**: It is not only for EF Core, it also works for other database providers (like [MongoDB](MongoDB.md)). -* **Production Ready**: It solves the problems on production environments. See the "*On Production*" section below. -* **Dependency Injection**: It takes the full advantage of dependency injection, so you can use any internal or external service while seeding the initial data. Actually, you can do much more than data seeding. - -## IDataSeedContributor - -`IDataSeedContributor` is the interface that should be implemented in order to seed data to the database. - -**Example: Seed one initial book to the database if there is no book** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; - -namespace Acme.BookStore -{ - public class BookStoreDataSeedContributor - : IDataSeedContributor, ITransientDependency - { - private readonly IRepository _bookRepository; - private readonly IGuidGenerator _guidGenerator; - private readonly ICurrentTenant _currentTenant; - - public BookStoreDataSeedContributor( - IRepository bookRepository, - IGuidGenerator guidGenerator, - ICurrentTenant currentTenant) - { - _bookRepository = bookRepository; - _guidGenerator = guidGenerator; - _currentTenant = currentTenant; - } - - public async Task SeedAsync(DataSeedContext context) - { - using (_currentTenant.Change(context?.TenantId)) - { - if (await _bookRepository.GetCountAsync() > 0) - { - return; - } - - var book = new Book( - id: _guidGenerator.Create(), - name: "The Hitchhiker's Guide to the Galaxy", - type: BookType.ScienceFiction, - publishDate: new DateTime(1979, 10, 12), - price: 42 - ); - - await _bookRepository.InsertAsync(book); - } - } - } -} -```` - -* `IDataSeedContributor` defines the `SeedAsync` method to execute the **data seed logic**. -* It is typical to **check database** if the seeding data is already present. -* You can **inject** service and perform any logic needed to seed the data. - -> Data seed contributors are automatically discovered by the ABP Framework and executed as a part of the data seed process. - -### DataSeedContext - -`DataSeedContext` contains `TenantId` if your application is [multi-tenant](Multi-Tenancy.md), so you can use this value while inserting data or performing custom logic based on the tenant. - -`DataSeedContext` also contains name-value style configuration parameters for passing to the seeder contributors from the `IDataSeeder`. - -## Modularity - -An application can have multiple data seed contributor (`IDataSeedContributor`) class. So, any reusable module can also implement this interface to seed its own initial data. - -For example, the [Identity Module](Modules/Identity.md) has a data seed contributor that creates an admin role and admin user and assign all the permissions. - -## IDataSeeder - -> You typically never need to directly use the `IDataSeeder` service since it is already done if you've started with the [application startup template](Startup-Templates/Application.md). But its suggested to read it to understand the design behind the data seed system. - -`IDataSeeder` is the main service that is used to seed initial data. It is pretty easy to use; - -````csharp -public class MyService : ITransientDependency -{ - private readonly IDataSeeder _dataSeeder; - - public MyService(IDataSeeder dataSeeder) - { - _dataSeeder = dataSeeder; - } - - public async Task FooAsync() - { - await _dataSeeder.SeedAsync(); - } -} -```` - -You can [inject](Dependency-Injection.md) the `IDataSeeder` and use it to seed the initial data when you need. It internally calls all the `IDataSeedContributor` implementations to complete the data seeding. - -It is possible to send named configuration parameters to the `SeedAsync` method as shown below: - -````csharp -await _dataSeeder.SeedAsync( - new DataSeedContext() - .WithProperty("MyProperty1", "MyValue1") - .WithProperty("MyProperty2", 42) -); -```` - -Then the data seed contributors can access to these properties via the `DataSeedContext` explained before. - -If a module needs to a parameter, it should be declared on the [module documentation](Modules/Index.md). For example, the [Identity Module](Modules/Identity.md) can use `AdminEmail` and `AdminPassword` parameters if you provide (otherwise uses the default values). - -### Separate Unit Of Works - -The default seed will be in a unit of work and may use transactions. If there are multiple `IDataSeedContributor` or too much data written, it may cause a database timeout error. - -We provide an extension method of `SeedInSeparateUowAsync` for the `IDataSeeder` service to create a separate unit of work for each `IDataSeedContributor`. - -````csharp -public static Task SeedInSeparateUowAsync(this IDataSeeder seeder, Guid? tenantId = null, AbpUnitOfWorkOptions options = null, bool requiresNew = false) -```` - -### Where & How to Seed Data? - -It is important to understand where & how to execute the `IDataSeeder.SeedAsync()`? - -#### On Production - -The [application startup template](Startup-Templates/Application.md) comes with a *YourProjectName***.DbMigrator** project (Acme.BookStore.DbMigrator on the picture below), which is a **console application** that is responsible to **migrate** the database schema (for relational databases) and **seed** the initial data: - -![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png) - -This console application is properly configured for you. It even supports **multi-tenant** scenarios where each tenant has its own database (migrates & seeds all necessary databases). - -It is expected to run this DbMigrator application whenever you **deploy a new version** of your solution to the server. It will migrate your **database schema** (create new tables/fields... etc.) and **seed new initial data** needed to properly run the new version of your solution. Then you can deploy/start your actual application. - -Even if you are using MongoDB or another NoSQL database (that doesn't need to schema migrations), it is recommended to use the DbMigrator application to seed your data or perform your data migration. - -Having such a separate console application has several advantages; - -* You can **run it before** updating your application, so your application will run on the ready database. -* Your application **starts faster** compared to if it seeds the initial data itself. -* Your application can properly run on a **clustered environment** (where multiple instances of your application run concurrently). If you seed data on application startup you would have conflicts in this case. - -#### On Development - -We suggest the same way on development. Run the DbMigrator console application whenever you [create a database migration](https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/) (using EF Core `Add-Migration` command, for example) or change the data seed code (will be explained later). - -> You can continue to use the standard `Update-Database` command for EF Core, but it will not seed if you've created a new seed data. - -#### On Testing - -You probably want to seed the data also for automated [testing](Testing.md), so want to use the `IDataSeeder.SeedAsync()`. In the [application startup template](Startup-Templates/Application.md), it is done in the [OnApplicationInitialization](Module-Development-Basics.md) method of the *YourProjectName*TestBaseModule class of the TestBase project. - -In addition to the standard seed data (that is also used on production), you may want to seed additional data unique to the automated tests. If so, you can create a new data seed contributor in the test project to have more data to work on. diff --git a/docs/en/Data-Transfer-Objects.md b/docs/en/Data-Transfer-Objects.md deleted file mode 100644 index 45533cca72..0000000000 --- a/docs/en/Data-Transfer-Objects.md +++ /dev/null @@ -1,284 +0,0 @@ -# Data Transfer Objects - -## Introduction - -**Data Transfer Objects** (DTO) are used to transfer data between the **Application Layer** and the **Presentation Layer** or other type of clients. - -Typically, an [application service](Application-Services.md) is called from the presentation layer (optionally) with a **DTO** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer. - -### The Need for DTOs - -> **You can skip this section** if you feel that you know and confirm the benefits of using DTOs. - -At first, creating a DTO class for each application service method can be seen as tedious and time-consuming work. However, they can save your application if you correctly use them. Why & how? - -#### Abstraction of the Domain Layer - -DTOs provide an efficient way of **abstracting domain objects** from the presentation layer. In effect, your **layers** are correctly separated. If you want to change the presentation layer completely, you can continue with the existing application and domain layers. Alternatively, you can re-write your domain layer, completely change the database schema, entities and O/RM framework, all without changing the presentation layer. This, of course, is as long as the contracts (method signatures and DTOs) of your application services remain unchanged. - -#### Data Hiding - -Say you have a `User` entity with the properties Id, Name, EmailAddress and Password. If a `GetAllUsers()` method of a `UserAppService` returns a `List`, anyone can access the passwords of all your users, even if you do not show it on the screen. It's not just about security, it's about data hiding. Application services should return only what it needs by the presentation layer (or client). Not more, not less. - -#### Serialization & Lazy Load Problems - -When you return data (an object) to the presentation layer, it's most likely serialized. For example, in a REST API that returns JSON, your object will be serialized to JSON and sent to the client. Returning an Entity to the presentation layer can be problematic in that regard, especially if you are using a relational database and an ORM provider like Entity Framework Core. How? - -In a real-world application, your entities may have references to each other. The `User` entity can have a reference to it's `Role`s. If you want to serialize `User`, its `Role`s are also serialized. The `Role` class may have a `List` and the `Permission` class can has a reference to a `PermissionGroup` class and so on... Imagine all of these objects being serialized at once. You could easily and accidentally serialize your whole database! Also, if your objects have circular references, they may **not** be serialized at all. - -What's the solution? Marking properties as `NonSerialized`? No, you can not know when it should be serialized and when it shouldn't be. It may be needed in one application service method, and not needed in another. Returning safe, serializable, and specially designed DTOs is a good choice in this situation. - -Almost all O/RM frameworks support lazy-loading. It's a feature that loads entities from the database when they're needed. Say a `User` class has a reference to a `Role` class. When you get a `User` from the database, the `Role` property (or collection) is not filled. When you first read the `Role` property, it's loaded from the database. So, if you return such an Entity to the presentation layer, it will cause it to retrieve additional entities from the database by executing additional queries. If a serialization tool reads the entity, it reads all properties recursively and again your whole database can be retrieved (if there are relations between entities). - -More problems can arise if you use Entities in the presentation layer. **It's best not to reference the domain/business layer assembly in the presentation layer.** - -If you are convinced about using DTOs, we can continue to what ABP Framework provides and suggests about DTOs. - -> ABP doesn't force you to use DTOs, however using DTOs is **strongly suggested as a best practice**. - -## Standard Interfaces & Base Classes - -A DTO is a simple class that has no dependency and you can design it in any way. However, ABP introduces some **interfaces** to determine the **conventions** for naming **standard properties** and **base classes** to **don't repeat yourself** while declaring **common properties**. - -**None of them are required**, but using them **simplifies and standardizes** your application code. - -### Entity Related DTOs - -You typically create DTOs corresponding to your entities, which results similar classes to your entities. ABP Framework provides some base classes to simplify while creating such DTOs. - -#### EntityDto - -`IEntityDto` is a simple interface that only defines an `Id` property. You can implement it or inherit from the `EntityDto` for your DTOs that matches to an [entity](Entities.md). - -**Example:** - -````csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace AbpDemo -{ - public class ProductDto : EntityDto - { - public string Name { get; set; } - //... - } -} -```` - -#### Audited DTOs - -If your entity inherits from audited entity classes (or implements auditing interfaces), you can use the following base classes to create your DTOs: - -* `CreationAuditedEntityDto` -* `CreationAuditedEntityWithUserDto` -* `AuditedEntityDto` -* `AuditedEntityWithUserDto` -* `FullAuditedEntityDto` -* `FullAuditedEntityWithUserDto` - -#### Extensible DTOs - -If you want to use the [object extension system](Object-Extensions.md) for your DTOs, you can use or inherit from the following DTO classes: - -* `ExtensibleObject` implements the `IHasExtraProperties` (other classes inherits this class). -* `ExtensibleEntityDto` -* `ExtensibleCreationAuditedEntityDto` -* `ExtensibleCreationAuditedEntityWithUserDto` -* `ExtensibleAuditedEntityDto` -* `ExtensibleAuditedEntityWithUserDto` -* `ExtensibleFullAuditedEntityDto` -* `ExtensibleFullAuditedEntityWithUserDto` - -### List Results - -It is common to return a list of DTOs to the client. `IListResult` interface and `ListResultDto` class is used to make it standard. - -The definition of the `IListResult` interface: - -````csharp -public interface IListResult -{ - IReadOnlyList Items { get; set; } -} -```` - -**Example: Return a list of products** - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace AbpDemo -{ - public class ProductAppService : ApplicationService, IProductAppService - { - private readonly IRepository _productRepository; - - public ProductAppService(IRepository productRepository) - { - _productRepository = productRepository; - } - - public async Task> GetListAsync() - { - //Get entities from the repository - List products = await _productRepository.GetListAsync(); - - //Map entities to DTOs - List productDtos = - ObjectMapper.Map, List>(products); - - //Return the result - return new ListResultDto(productDtos); - } - } -} -```` - -You could simply return the `productDtos` object (and change the method return type) and it has nothing wrong. Returning a `ListResultDto` makes your `List` wrapped into another object as an `Items` property. This has one advantage: You can later add more properties to your return value without breaking your remote clients (when they get the value as a JSON result). So, it is especially suggested when you are developing reusable application modules. - -### Paged & Sorted List Results - -It is more common to request a paged list from server and return a paged list to the client. ABP defines a few interface and classes to standardize it: - -#### Input (Request) Types - -The following interfaces and classes is to standardize the input sent by the clients. - -* `ILimitedResultRequest`: Defines a `MaxResultCount` (`int`) property to request a limited result from the server. -* `IPagedResultRequest`: Inherits from the `ILimitedResultRequest` (so it inherently has the `MaxResultCount` property) and defines a `SkipCount` (`int`) to declare the skip count while requesting a paged result from the server. -* `ISortedResultRequest`: Defines a `Sorting` (`string`) property to request a sorted result from the server. Sorting value can be "*Name*", "*Name DESC*", "*Name ASC, Age DESC*"... etc. -* `IPagedAndSortedResultRequest` inherits from both of the `IPagedResultRequest` and `ISortedResultRequest`, so has `MaxResultCount`, `SkipCount` and `Sorting` properties. - -Instead of implementing the interfaces manually, it is suggested to inherit one of the following base DTO classes: - -* `LimitedResultRequestDto` implements `ILimitedResultRequest`. -* `PagedResultRequestDto` implements `IPagedResultRequest` (and inherits from the `LimitedResultRequestDto`). -* `PagedAndSortedResultRequestDto` implements `IPagedAndSortedResultRequest` (and inherit from the `PagedResultRequestDto`). - -##### Max Result Count - -`LimitedResultRequestDto` (and inherently the others) limits and validates the `MaxResultCount` by the following rules; - -* If the client doesn't set `MaxResultCount`, it is assumed as **10** (the default page size). This value can be changed by setting the `LimitedResultRequestDto.DefaultMaxResultCount` static property. -* If the client sends `MaxResultCount` greater than **1,000**, it produces a **validation error**. It is important to protect the server from abuse of the service. If you want, you can change this value by setting the `LimitedResultRequestDto.MaxMaxResultCount` static property. - -Static properties suggested to be set on application startup since they are static (global). - -#### Output (Response) Types - -The following interfaces and classes is to standardize the output sent to the clients. - -* `IHasTotalCount` defines a `TotalCount` (`long`) property to return the total count of the records in case of paging. -* `IPagedResult` inherits from the `IListResult` and `IHasTotalCount`, so it has the `Items` and `TotalCount` properties. - -Instead of implementing the interfaces manually, it is suggested to inherit one of the following base DTO classes: - -* `PagedResultDto` inherits from the `ListResultDto` and also implements the `IPagedResult`. - -**Example: Request a paged & sorted result from server and return a paged list** - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Dynamic.Core; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace AbpDemo -{ - public class ProductAppService : ApplicationService, IProductAppService - { - private readonly IRepository _productRepository; - - public ProductAppService(IRepository productRepository) - { - _productRepository = productRepository; - } - - public async Task> GetListAsync( - PagedAndSortedResultRequestDto input) - { - //Create the query - var query = _productRepository - .OrderBy(input.Sorting); - - //Get total count from the repository - var totalCount = await query.CountAsync(); - - //Get entities from the repository - List products = await query - .Skip(input.SkipCount) - .Take(input.MaxResultCount).ToListAsync(); - - //Map entities to DTOs - List productDtos = - ObjectMapper.Map, List>(products); - - //Return the result - return new PagedResultDto(totalCount, productDtos); - } - } -} -```` - -ABP Framework also defines a `PageBy` extension method (that is compatible with the `IPagedResultRequest`) that can be used instead of `Skip` + `Take` calls: - -````csharp -var query = _productRepository - .OrderBy(input.Sorting) - .PageBy(input); -```` - -> Notice that we added `Volo.Abp.EntityFrameworkCore` package to the project to be able to use the `ToListAsync` and `CountAsync` methods since they are not included in the standard LINQ, but defined by the Entity Framework Core. - -See also the [repository documentation](Repositories.md) to if you haven't understood the example code. - -## Related Topics - -### Validation - -Inputs of [application service](Application-Services.md) methods, controller actions, page model inputs... are automatically validated. You can use the standard data annotation attributes or a custom validation method to perform the validation. - -See the [validation document](Validation.md) for more. - -### Object to Object Mapping - -When you create a DTO that is related to an entity, you generally need to map these objects. ABP provides an object to object mapping system to simplify the mapping process. See the following documents: - -* [Object to Object Mapping document](Object-To-Object-Mapping.md) covers all the features. -* [Application Services document](Application-Services.md) provides a full example. - -## Best Practices - -You are free to design your DTO classes. However, there are some best practices & suggestions that you may want to follow. - -### Common Principles - -* DTOs should be **well serializable** since they are generally serialized and deserialized (to JSON or other format). It is suggested to have an empty (parameterless) public constructor if you have another constructor with parameter(s). -* DTOs **should not contain any business logic**, except some formal [validation](Validation.md) code. -* Do not inherit DTOs from entities and **do not reference to entities**. The [application startup template](Startup-Templates/Application.md) already prevents it by separating the projects. -* If you use an auto [object to object mapping](Object-To-Object-Mapping.md) library, like AutoMapper, enable the **mapping configuration validation** to prevent potential bugs. - -### Input DTO Principles - -* Define only the **properties needed** for the use case. Do not include properties not used for the use case, which confuses developers if you do so. - -* **Don't reuse** input DTOs among different application service methods. Because, different use cases will need to and use different properties of the DTO which results some properties are not used in some cases and that makes harder to understand and use the services and causes potential bugs in the future. - -### Output DTO Principles - -* You can **reuse output DTOs** if you **fill all the properties** on all the cases. - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/data-transfer-objects) \ No newline at end of file diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md deleted file mode 100644 index 6a1ea66d41..0000000000 --- a/docs/en/Dependency-Injection.md +++ /dev/null @@ -1,570 +0,0 @@ -# Dependency Injection - -ABP's Dependency Injection system is developed based on Microsoft's [dependency injection extension](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) library (Microsoft.Extensions.DependencyInjection nuget package). So, its documentation is valid in ABP too. - -> While ABP has no core dependency to any 3rd-party DI provider. However, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. Startup templates come with [Autofac](https://autofac.org/) installed. See [Autofac integration](Autofac-Integration.md) document for more information. - -## Modularity - -Since ABP is a modular framework, every module defines its own services and registers via dependency injection in its own separate [module class](Module-Development-Basics.md). Example: - -````C# -public class BlogModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //register dependencies here - } -} -```` - -## Conventional Registration - -ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` in the constructor of your module class. Example: - -````C# -public class BlogModule : AbpModule -{ - public BlogModule() - { - SkipAutoServiceRegistration = true; - } -} -```` - -Once you skip the auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example: - -````c# -public class BlogModule : AbpModule -{ - public BlogModule() - { - SkipAutoServiceRegistration = true; - } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddAssemblyOf(); - } -} -```` - -The sections below explain the conventions and configurations. - -### Inherently Registered Types - -Some specific types are registered to dependency injection by default. Examples: - -* Module classes are registered as singleton. -* MVC controllers (inherit ``Controller`` or ``AbpController``) are registered as transient. -* MVC page models (inherit ``PageModel`` or ``AbpPageModel``) are registered as transient. -* MVC view components (inherit ``ViewComponent`` or ``AbpViewComponent``) are registered as transient. -* Application services (inherit ``ApplicationService`` class or its subclasses) are registered as transient. -* Repositories (implement ``BasicRepositoryBase`` class or its subclasses) are registered as transient. -* Domain services (implement ``IDomainService`` interface or inherit ``DomainService`` class) are registered as transient. - -Example: - -````C# -public class BlogPostAppService : ApplicationService -{ -} -```` - -``BlogPostAppService`` is automatically registered with transient lifetime since it's derived from a known base class. - -### Dependency Interfaces - -If you implement these interfaces, your class is registered to dependency injection automatically: - -* ``ITransientDependency`` to register with transient lifetime. -* ``ISingletonDependency`` to register with singleton lifetime. -* ``IScopedDependency`` to register with scoped lifetime. - -Example: - -````C# -public class TaxCalculator : ITransientDependency -{ -} -```` - -``TaxCalculator`` is automatically registered with a transient lifetime since it implements ``ITransientDependency``. - -### Dependency Attribute - -Another way of configuring a service for dependency injection is to use ``DependencyAttribute``. It has the following properties: - -* ``Lifetime``: Lifetime of the registration: ``Singleton``, ``Transient`` or ``Scoped``. -* ``TryRegister``: Set ``true`` to register the service only if it's not registered before. Uses TryAdd... extension methods of IServiceCollection. -* ``ReplaceServices``: Set ``true`` to replace services if they are already registered before. Uses Replace extension method of IServiceCollection. - -Example: - -````C# -[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] -public class TaxCalculator -{ -} -```` - -``Dependency`` attribute has a higher priority than other dependency interfaces if it defines the ``Lifetime`` property. - -### ExposeServices Attribute - -``ExposeServicesAttribute`` is used to control which services are provided by the related class. Example: - -````C# -[ExposeServices(typeof(ITaxCalculator))] -public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency -{ -} -```` - -``TaxCalculator`` class only exposes ``ITaxCalculator`` interface. That means you can only inject ``ITaxCalculator``, but can not inject ``TaxCalculator`` or ``ICalculator`` in your application. - -### Exposed Services by Convention - -If you do not specify which services to expose, ABP expose services by convention. So taking the ``TaxCalculator`` defined above: - -* The class itself is exposed by default. That means you can inject it by ``TaxCalculator`` class. -* Default interfaces are exposed by default. Default interfaces are determined by naming convention. In this example, ``ICalculator`` and ``ITaxCalculator`` are default interfaces of ``TaxCalculator``, but ``ICanCalculate`` is not. A generic interface (e.g. `ICalculator`) is also considered as a default interface if the naming convention is satisfied. -* The resolved instances will be the same if multiple services are exposed for **Singleton** and **Scoped** services. This behavior requires exposing the class itself. - -### Combining All Together - -Combining attributes and interfaces is possible as long as it's meaningful. - -````C# -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(ITaxCalculator))] -public class TaxCalculator : ITaxCalculator, ITransientDependency -{ - -} -```` - -### ExposeKeyedService Attribute - -`ExposeKeyedServiceAttribute` is used to control which keyed services are provided by the related class. Example: - -````C# -[ExposeKeyedService("taxCalculator")] -[ExposeKeyedService("calculator")] -public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency -{ -} -```` - -In the example above, the `TaxCalculator` class exposes the `ITaxCalculator` interface with the key `taxCalculator` and the `ICalculator` interface with the key `calculator`. That means you can get keyed services from the `IServiceProvider` as shown below: - -````C# -var taxCalculator = ServiceProvider.GetRequiredKeyedService("taxCalculator"); -var calculator = ServiceProvider.GetRequiredKeyedService("calculator"); -```` - -Also, you can use the [`FromKeyedServicesAttribute`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.fromkeyedservicesattribute?view=dotnet-plat-ext-8.0) to resolve a certain keyed service in the constructor: - -```csharp -public class MyClass -{ - //... - - public MyClass([FromKeyedServices("taxCalculator")] ITaxCalculator taxCalculator) - { - TaxCalculator = taxCalculator; - } -} -``` - -> Notice that the `ExposeKeyedServiceAttribute` only exposes the keyed services. So, you can not inject the `ITaxCalculator` or `ICalculator` interfaces in your application without using the `FromKeyedServicesAttribute` as shown in the example above. If you want to expose both keyed and non-keyed services, you can use the `ExposeServicesAttribute` and `ExposeKeyedServiceAttribute` attributes together as shown below: - -````C# -[ExposeKeyedService("taxCalculator")] -[ExposeKeyedService("calculator")] -[ExposeServices(typeof(ITaxCalculator), typeof(ICalculator))] -public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency -{ -} -```` - -### Manually Registering - -In some cases, you may need to register a service to the `IServiceCollection` manually, especially if you need to use custom factory methods or singleton instances. In that case, you can directly add services just as [Microsoft documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) describes. Example: - -````C# -public class BlogModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //Register an instance as singleton - context.Services.AddSingleton(new TaxCalculator(taxRatio: 0.18)); - - //Register a factory method that resolves from IServiceProvider - context.Services.AddScoped( - sp => sp.GetRequiredService() - ); - } -} -```` - -### Replace a Service - -If you need to replace an existing service (defined by the ABP framework or another module dependency), you have two options; - -1. Use the `Dependency` attribute of the ABP framework as explained above. -2. Use the `IServiceCollection.Replace` method of the Microsoft Dependency Injection library. Example: - -````csharp -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //Replacing the IConnectionStringResolver service - context.Services.Replace( - ServiceDescriptor.Transient< - IConnectionStringResolver, - MyConnectionStringResolver - >()); - } -} -```` - -## Injecting Dependencies - -There are three common ways of using a service that has already been registered. - -### Constructor Injection - -This is the most common way of injecting a service into a class. For example: - -````C# -public class TaxAppService : ApplicationService -{ - private readonly ITaxCalculator _taxCalculator; - - public TaxAppService(ITaxCalculator taxCalculator) - { - _taxCalculator = taxCalculator; - } - - public async Task DoSomethingAsync() - { - //...use _taxCalculator... - } -} -```` - -``TaxAppService`` gets ``ITaxCalculator`` in its constructor. The dependency injection system automatically provides the requested service at runtime. - -Constructor injection is preffered way of injecting dependencies to a class. In that way, the class can not be constructed unless all constructor-injected dependencies are provided. Thus, the class explicitly declares it's required services. - -### Property Injection - -Property injection is not supported by Microsoft Dependency Injection library. However, ABP can integrate with 3rd-party DI providers ([Autofac](https://autofac.org/), for example) to make property injection possible. Example: - -````C# -public class MyService : ITransientDependency -{ - public ILogger Logger { get; set; } - - public MyService() - { - Logger = NullLogger.Instance; - } - - public async Task DoSomethingAsync() - { - //...use Logger to write logs... - } -} -```` - -For a property-injection dependency, you declare a public property with public setter. This allows the DI framework to set it after creating your class. - -Property injected dependencies are generally considered as **optional** dependencies. That means the service can properly work without them. ``Logger`` is such a dependency, ``MyService`` can continue to work without logging. - -To make the dependency properly optional, we generally set a default/fallback value to the dependency. In this sample, NullLogger is used as fallback. Thus, ``MyService`` can work but does not write logs if DI framework or you don't set Logger property after creating ``MyService``. - -One restriction of property injection is that you cannot use the dependency in your constructor, since it's set after the object construction. - -Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class. - -#### DisablePropertyInjection Attribute - -You can use `[DisablePropertyInjection]` attribute on classes or their properties to disable property injection for the whole class or some specific properties. - -````C# -// Disabling for all properties of the MyService class -[DisablePropertyInjection] -public class MyService : ITransientDependency -{ - public ILogger Logger { get; set; } - - public ITaxCalculator TaxCalculator { get; set; } -} - -// Disabling only for the TaxCalculator property -public class MyService : ITransientDependency -{ - public ILogger Logger { get; set; } - - [DisablePropertyInjection] - public ITaxCalculator TaxCalculator { get; set; } -} -```` - -#### IInjectPropertiesService - -You can use the `IInjectPropertiesService` service to inject properties of an object. Generally, it is a service outside of DI, such as manually created services. - -````C# -var injectPropertiesService = serviceProvider.GetRequiredService(); -var instance = new TestService(); - -// Set any properties on instance that can be resolved by IServiceProvider. -injectPropertiesService.InjectProperties(instance); - -// Set any null-valued properties on instance that can be resolved by the IServiceProvider. -injectPropertiesService.InjectUnsetProperties(instance); -```` - -### Resolve Service from IServiceProvider - -You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject `IServiceProvider` into your class and use the ``GetService`` or the `GetRequiredService` method as shown below: - -````C# -public class MyService : ITransientDependency -{ - private readonly ITaxCalculator _taxCalculator; - - public MyService(IServiceProvider serviceProvider) - { - _taxCalculator = serviceProvider.GetRequiredService(); - } -} -```` - -### Dealing with multiple implementations - -You can register multiple implementations of the same service interface. Assume that you have an `IExternalLogger` interface with two implementations: - -````csharp -public interface IExternalLogger -{ - Task LogAsync(string logText); -} - -public class ElasticsearchExternalLogger : IExternalLogger -{ - public async Task LogAsync(string logText) - { - //TODO... - } -} - -public class AzureExternalLogger : IExternalLogger -{ - public Task LogAsync(string logText) - { - throw new System.NotImplementedException(); - } -} -```` - -In this example, we haven't registered any of the implementation classes to the dependency injection system yet. So, if we try to inject the `IExternalLogger` interface, we get an error indicating that no implementation found. - -If we register both of the `ElasticsearchExternalLogger` and `AzureExternalLogger` services for the `IExternalLogger` interface, and then try to inject the `IExternalLogger` interface, then the last registered implementation will be used. - -An example service injecting the `IExternalLogger` interface: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IExternalLogger _externalLogger; - - public MyService(IExternalLogger externalLogger) - { - _externalLogger = externalLogger; - } - - public async Task DemoAsync() - { - await _externalLogger.LogAsync("Example log message..."); - } -} -```` - -Here, as said before, we get the last registered implementation. However, how to determine the last registered implementation? - -If we implement one of the dependency interfaces (e.g. `ITransientDependency`), then the registration order will be uncertain (it may depend on the namespaces of the classes). The *last registered implementation* can be different than you expect. So, it is not suggested to use the dependency interfaces to register multiple implementations. - -You can register your services in the `ConfigureServices` method of your module: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.AddTransient(); - context.Services.AddTransient(); -} -```` - -In this case, you get an `AzureExternalLogger` instance when you inject the `IExternalLogger` interface, because the last registered implementation is the `AzureExternalLogger` class. - -When you have multiple implementation of an interface, you may want to work with all these implementations. Assume that you want to write log to all the external loggers. We can change the `MyService` implementation as the following: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IEnumerable _externalLoggers; - - public MyService(IEnumerable externalLoggers) - { - _externalLoggers = externalLoggers; - } - - public async Task DemoAsync() - { - foreach (var externalLogger in _externalLoggers) - { - await externalLogger.LogAsync("Example log message..."); - } - } -} -```` - -In this example, we are injecting `IEnumerable` instead of `IExternalLogger`, so we have a collection of the `IExternalLogger` implementations. Then we are using a `foreach` loop to write the same log text to all the `IExternalLogger` implementations. - -If you are using `IServiceProvider` to resolve dependencies, then use its `GetServices` method to obtain a collection of the service implementations: - -````csharp -IEnumerable services = _serviceProvider.GetServices(); -```` - -### Releasing/Disposing Services - -If you used a constructor or property injection, you don't need to be concerned about releasing the service's resources. However, if you have resolved a service from ``IServiceProvider``, in some cases, you might need to take care about releasing the service resources. - -ASP.NET Core releases all services at the end of a current HTTP request, even if you directly resolved from ``IServiceProvider`` (assuming you injected `IServiceProvider`). But, there are several cases where you may want to release/dispose manually resolved services: - -* Your code is executed outside of ASP.NET Core request and the executer hasn't handled the service scope. -* You only have a reference to the root service provider. -* You may want to immediately release & dispose services (for example, you may creating too many services with big memory usages and don't want to overuse the memory). - -In any case, you can create a service scope block to safely and immediately release services: - -````C# -using (var scope = _serviceProvider.CreateScope()) -{ - var service1 = scope.ServiceProvider.GetService(); - var service2 = scope.ServiceProvider.GetService(); -} -```` - -Both services are released when the created scope is disposed (at the end of the `using` block). - -### Cached Service Providers - -ABP provides two special services to optimize resolving services from `IServiceProvider`. `ICachedServiceProvider` and `ITransientCachedServiceProvider` both inherits from the `IServiceProvider` interface and internally caches the resolved services, so you get the same service instance even if you resolve a service multiple times. - -The main difference is the `ICachedServiceProvider` is itself registered as scoped, while the `ITransientCachedServiceProvider` is registered as transient to the dependency injection system. - -The following example injects the `ICachedServiceProvider` service and resolves a service in the `DoSomethingAsync` method: - -````csharp -public class MyService : ITransientDependency -{ - private readonly ICachedServiceProvider _serviceProvider; - - public MyService(ICachedServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public async Task DoSomethingAsync() - { - var taxCalculator = _serviceProvider.GetRequiredService(); - // TODO: Use the taxCalculator - } -} - -```` - -With such a usage, you don't need to deal with creating service scopes and disposing the resolved services (as explained in the *Releasing/Disposing Services* section above). Because all the services resolved from the `ICachedServiceProvider` will be released once the service scope of the `MyService` instance is disposed. Also, you don't need to care about memory leaks (because of creating too many `ITaxCalculator` instances if we call `DoSomethingAsync` too many times), because only one `ITaxCalculator` instance is created, and it is reused. - -Since `ICachedServiceProvider` and `ITransientCachedServiceProvider` extends the standard `IServiceProvider` interface, you can use all the extension method of the `IServiceProvider` interface on them. In addition, they provides some other methods to provide a default value or a factory method for the services that are not found (that means not registered to the dependency injection system). Notice that the default value (or the value returned from your factory method) is also cached and reused. - -Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unless you need to create the service cache per usage. `ITransientCachedServiceProvider` guarantees that the created service instances are not shared with any other service, even they are in the same service scope. The services resolved from `ICachedServiceProvider` are shared with other services in the same service scope (in the same HTTP Request, for example), so it can be thought as more optimized. - -> ABP Framework also provides the `IAbpLazyServiceProvider` service. It does exists for backward compatibility and works exactly same with the `ITransientCachedServiceProvider` service. So, use the `ITransientCachedServiceProvider` since the `IAbpLazyServiceProvider` might be removed in future ABP versions. - -## Advanced Features - -### IServiceCollection.OnRegistered Event - -You may want to perform an action for every service registered to the dependency injection. In the `PreConfigureServices` method of your module, register a callback using the `OnRegistered` method as shown below: - -````csharp -public class AppModule : AbpModule -{ - public override void PreConfigureServices(ServiceConfigurationContext context) - { - context.Services.OnRegistered(ctx => - { - var type = ctx.ImplementationType; - //... - }); - } -} -```` - -`ImplementationType` provides the service type. This callback is generally used to add interceptor to a service. Example: - -````csharp -public class AppModule : AbpModule -{ - public override void PreConfigureServices(ServiceConfigurationContext context) - { - context.Services.OnRegistered(ctx => - { - if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true)) - { - ctx.Interceptors.TryAdd(); - } - }); - } -} -```` - -This example simply checks if the service class has `MyLogAttribute` attribute and adds `MyLogInterceptor` to the interceptor list if so. - -> Notice that `OnRegistered` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors. - -### IServiceCollection.OnActivated Event - -The `OnActivated` event is raised once a service is fully constructed. Here you can perform application-level tasks that depend on the service being fully constructed - these should be rare. - -````csharp -var serviceDescriptor = ServiceDescriptor.Transient(); -services.Add(serviceDescriptor); -if (setIsReadOnly) -{ - services.OnActivated(serviceDescriptor, x => - { - x.Instance.As().IsReadOnly = true; - }); -} -```` - -> Notice that `OnActivated` event can be registered multiple times for the same `ServiceDescriptor`. - -## 3rd-Party Providers - -While ABP has no core dependency to any 3rd-party DI provider, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. - -Startup templates come with Autofac installed. See [Autofac integration](Autofac-Integration.md) document for more information. - -## See Also - -* [ASP.NET Core Dependency Injection Best Practices, Tips & Tricks](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) -* [Video tutorial](https://abp.io/video-courses/essentials/dependency-injection) diff --git a/docs/en/Deploy-azure-app-service.md b/docs/en/Deploy-azure-app-service.md deleted file mode 100644 index 9e0309f3b9..0000000000 --- a/docs/en/Deploy-azure-app-service.md +++ /dev/null @@ -1,452 +0,0 @@ -# Deploying ABP Project to Azure App Service - -In this document, you will learn how to create and deploy your first ABP web app to [Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/overview). The App Service supports various versions of .NET apps, and provides a highly scalable, self-patching web hosting service. ABP web apps are cross-platform and can be hosted on Linux, Windows or MacOS. - -****Prerequisites**** - -- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/dotnet). -- A GitHub account [Create an account for free](http://github.com/). - - - -## Creating a new ABP application - -Create a repository on [GitHub.com](https://github.com/) (keep all settings as default). - -Open the command prompt and clone the repository into a folder on your computer - -```bash -git clone https://github.com/your-username/your-repository-name.git -``` - -Check your dotnet version. It should be at least 3.1.x - -```bash -dotnet --version -``` - -Install or update the [ABP CLI](https://docs.abp.io/en/abp/latest/cli) with the following command: - -```bash -dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli -``` - -Open the command prompt in the *GitHub repository folder* and create a new ABP Blazor solution with the command below: - -```bash -abp new YourAppName -u blazor -``` - - - -## Running the application - -Open the command prompt in the *[YourAppName].DbMigrator* project and enter the command below to apply the database migrations: - -```bash -dotnet run -``` - -Open the command prompt in the *[YourAppName].HttpApi.Host* project to run the API project: - -```bash -dotnet run -``` - -Navigate to the *applicationUrl* specified in *the launchSettings.json* file of the *[YourAppName].HttpApi.Host project*. You should get the *Swagger window* - -Open the command prompt in the *[YourAppName].Blazor* folder and enter the command below to run the Blazor project: - -```bash -dotnet run -``` - -Navigate to the *applicationUrl* specified in the *launchSettings.json* file of the *[YourAppName].Blazor* project and you should see the landing page. - -Stop both the *API* and the *Blazor* project by pressing **CTRL+C** - - - -## Committing to GitHub - -Before the GitHub commit, you have to delete the line "**/wwwroot/libs/*" at *.gitignore* file. - -![azdevops-23](images/azdevops-23.png) - -Open the command prompt in the root folder of your project and *add, commit and push* all your changes to your GitHub repository: - -```bash -git add . -git commit -m initialcommit -git push -``` - - - -## Configuring Azure database connection string - -Create a SQL database on Azure and change the connection string in all the *appsettings.json* files. - -* Login into [Azure Portal](https://portal.azure.com/) - -* Click **Create a resource** - -* Search for *SQL Database* - -* Click the **Create** button in the *SQL Database window* - -* Create a new resource group. Name it *rg[YourAppName]* - -* Enter *[YourAppName]Db* as database name - -* Create a new Server and name it *[yourappname]server* - -* Enter a serveradmin login and passwords. Click the **OK** button - -* Select your *Location* - -* Check *Allow Azure services to access server* - -* Click **Configure database**. Go to the *Basic* version and click the **Apply** button - -* Click the **Review + create** button. Click **Create** - -* Click **Go to resource** and click **SQL server** when the SQL Database is created - -* Click **Networking** under Security left side menu - -* Select **Selected networks** and click **Add your client IP$ address** at the Firewall rules - -* Select **Allow Azure and resources to access this seerver** and save - -* Go to your **SQL database**, click **Connection strings** and copy the connection string - -* Copy/paste the *appsettings.json* files of the *[YourAppName].HttpApi.Host* and the *[YourAppName].DbMigrator* project - -* Do not forget to replace {your_password} with the correct server password you entered in Azure SQL Database - - - -## Running DB Migrations - -Open the command prompt in the *[YourAppName].DbMigrator* project again and enter the command below to apply the database migrations: - -```bash -dotnet run -``` - -Open the command prompt in the *[YourAppName].HttpApi.Host* project and enter the command below to check your API is working: - -```bash -dotnet run -``` - -Stop the *[YourAppName].HttpApi.Host* by pressing CTRL+C. - - - -## Committing to GitHub - -Open the command prompt in the root folder of your project and add, commit and push all your changes to your GitHub repository - -```bash -git add . -git commit -m initialcommit -git push -``` - - - -## Setting up the Build pipeline in AzureDevops and publish the Build Artifacts - -* Sign in Azure DevOps - -* Click **New organization** and follow the steps to create a new organisation. Name it [YourAppName]org - -* Enter [YourAppName]Proj as project name in the ***Create a project to get started*** window - -* Select **Public visibility** and click the **Create project** button - -* Click the **Pipelines** button to continue - -* Click the **Create Pipeline** button - - Select GitHub in the Select your repository window - -![azdevops-1](images/azdevops-1.png) - -* Enter the Connection name. *[YourAppName]GitHubConnection* and click **Authorize using OAuth** - -* Select your **GitHub** [YourAppName]repo and click Continue - -* Search for **ASP.NET** in the ***Select a template*** window - -![azdevops-2](images/azdevops-2.png) - -* Select the ASP.NET Core template and click the **Apply** button - -* Add the below commands block as a first step in the pipeline - - ``` - - task: UseDotNet@2 - inputs: - packageType: 'sdk' - version: '6.0.106' - ``` - -![azdevops-18](images/azdevops-18.png) - -* Select **Settings** on the second task(Nugetcommand@2) in the pipeline - -* Select **Feeds in my Nuget.config** and type **Nuget.config** in the text box - -![azdevops-3](images/azdevops-3.png) - -* Add the below commands block to the end of the pipeline - - ``` - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact' - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - ArtifactName: '$(Parameters.ArtifactName)' - condition: succeededOrFailed() - ``` - - ![azdevops-4](images/azdevops-4.png) - -``` -# ASP.NET -# Build and test ASP.NET projects. -# Add steps that publish symbols, save build artifacts, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4 - -trigger: -- main - -pool: - vmImage: 'windows-latest' - -variables: - solution: '**/*.sln' - buildPlatform: 'Any CPU' - buildConfiguration: 'Release' - -steps: -- task: UseDotNet@2 - inputs: - packageType: 'sdk' - version: '6.0.106' - -- task: NuGetToolInstaller@1 - -- task: NuGetCommand@2 - inputs: - command: 'restore' - restoreSolution: '$(solution)' - feedsToUse: 'config' - nugetConfigPath: 'NuGet.config' - -- task: VSBuild@1 - inputs: - solution: '$(solution)' - msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"' - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - -- task: VSTest@2 - inputs: - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - -- task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact' - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - ArtifactName: '$(Parameters.ArtifactName)' - publishLocation: 'Container' - condition: succeededOrFailed() -``` - -* Click **Save & queue** in the top menu. Click **Save & queue** again and click **Save and run** to run the Build pipeline - -* When the Build pipeline has finished. Click **1 published; 1 consumed** - - - -## Creating a Web App in the Azure Portal to deploy [YourAppName].HttpApi.Host project - -* Search for Web App in the *Search the Marketplace* field - -* Click the **Create** button in the Web App window - -* Select rg[YourAppName] in the *Resource Group* dropdown - -* Enter [YourAppName]API in the *Name input* field - -* Select code, .NET Core 3.1 (LTS) and windows as *Operating System* - -* Enter [YourAppName]API in the *Name input* field - -* Select .NET Core 3.1 (LTS) in the *Runtime stack* dropdown - -* Select Windows as *Operating System* - -* Select the same *Region* as in the SQL server you created in Part 3 - -![azdevops-5](images/azdevops-5.png) - -* Click **Create new** in the Windows Plan. Name it [YourAppName]ApiWinPlan - -* Click **Change size** in Sku and size. Go to the Dev/Test Free F1 version and click the **Apply** button - -![azdevops-6](images/azdevops-6.png) - -* Click the **Review + create** button. Click the **Create** button - -* Click **Go to resource** when the Web App has been created - - - -## Creating a release pipeline in the AzureDevops and deploy [YourAppName].HttpApi.Host project - -* Sign in into [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) - -* Click [YourAppName]Proj and click **Releases** in the *Pipelines* menu - -* Click the **New pipeline** button in the *No release pipelines found* window - -* Select *Azure App Service deployment* and click the **Apply** button - -![azdevops-7](images/azdevops-7.png) - -* Enter *[YourAppName]staging* in the *Stage name* field in the *Stage* window. And close the window - -* Click **+ Add an artifact** in the *Pipeline* tab - -* Select the **Build** icon as *Source type* in the *Add an artifact* window - -* Select Build pipeline in the *Source (build pipeline)* dropdown and click the **Add** button - -![azdevops-8](images/azdevops-8.png) - -* Click the **Continuous deployment trigger (thunderbolt icon)** - -* Set the toggle to **Enabled** in the the *Continuous deployment trigger* window - -* Click **+ Add** in *No filters added*. Select **Include** in the *Type* dropdown. Select your branch in the *Build branch* dropdown and close the window - -![azdevops-9](images/azdevops-9.png) - -* Click **the little red circle with the exclamation mark** in the *Tasks* tab menu - -* Select your subscription in the *Azure subscription* dropdown. - -![azdevops-10](images/azdevops-10.png) - -* Click **Authorize** and enter your credentials in the next screens - -* After Authorization, select the **[YourAppName]API** in the *App service name* dropdown - -* Click the **Deploy Azure App Service** task - -* Select **[YourAppName].HttpApi.Host.zip** in the *Package or folder* input field - -![azdevops-11](images/azdevops-11.png) - -* Click the **Save** icon in the top menu and click **OK** - -* Click **Create release** in the top menu. Click **Create** to create a release - -* Click the *Pipeline* tab and wait until the Deployment succeeds - -![azdevops-12](images/azdevops-12.png) - -* Open a browser and navigate to the URL of your Web App - -``` -https://[YourAppName]api.azurewebsites.net -``` - -![azdevops-13](images/azdevops-13.png) - - - -## Creating a Web App in Azure Portal to deploy [YourAppName].Blazor project - -* Login into [Azure Portal](https://portal.azure.com/) - -* Click **Create a resource** - -* Search for *Web App* in the *Search the Marketplace* field - -* Click the **Create** button in the *Web App* window - -* Select *rg[YourAppName]* in the *Resource Group* dropdown - -* Enter *[YourAppName]Blazor* in the *Name* input field - -* Select *.NET Core 3.1 (LTS)* in the *Runtime stack* dropdown - -* Select *Windows* as *Operating System* - -* Select the same region as the SQL server you created in Part 3 - -* Select the [YourAppName]ApiWinPlan in the *Windows Plan* dropdown - -![azdevops-14](images/azdevops-14.png) - -* Click the **Review + create** button. Click **Create** button - -* Click **Go to resource** when the Web App has been created - -* Copy the URL of the Blazor Web App for later use - -``` -https://[YourAppName]blazor.azurewebsites.net -``` - - -## Changing the Web App configuration for the Azure App Service - -Copy the URL of the Api Host and Blazor Web App. Change appsettings.json files in the Web App as follows images. - -![azdevops-19](images/azdevops-19.png) - -![azdevops-20](images/azdevops-20.png) - -![azdevops-21](images/azdevops-21.png) - - - -## Adding an extra Stage in the Release pipeline in the AzureDevops to deploy [YourAppName].Blazor project - -* Go to the *Release* pipeline in [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) and click **Edit** - -* Click the **+ Add** link and add a **New Stage** - -![azdevops-15](images/azdevops-15.png) - -* Select *Azure App Service deployment* and click the **Apply** button - -* Enter *BlazorDeployment* in the *Stage name* input field and close the *Stage* window - -* Click the **little red circle with the exclamation mark** in the BlazorDeployment stage - -* Select your subscription in the *Azure subscription* dropdown - -* Select your Blazor Web App in the *App service name* dropdown - -* Click the **Deploy Azure App Service task** - -* Select *[YourAppName].Blazor.zip* in the *Package or folder* input field - -![azdevops-16](images/azdevops-16.png) - -* Click **Save** in the top menu and click the **OK** button after - -* Click **Create release** in the top menu and click the **Create** button - -![azdevops-17](images/azdevops-17.png) - -![azdevops-22](images/azdevops-22.png) diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md deleted file mode 100644 index e3294b3469..0000000000 --- a/docs/en/Deployment/Clustered-Environment.md +++ /dev/null @@ -1,109 +0,0 @@ -# Deploying to a Clustered Environment - -This document introduces the topics that you should consider when you are deploying your application to a clustered environment where **multiple instances of your application run concurrently**, and explains how you can deal with these topics in your ABP based application. - -> This document is valid regardless you have a monolith application or a microservice solution. The Application term is used for a process. An application can be a monolith web application, a service in a microservice solution, a console application, or another kind of an executable process. -> -> For example, if you are deploying your application to Kubernetes and configure your application or service to run in multiple pods, then your application or service runs in a clustered environment. - -## Understanding the Clustered Environment - -> You can skip this section if you are already familiar with clustered deployment and load balancers. - -### Single Instance Deployment - -Consider an application deployed as a **single instance**, as illustrated in the following figure: - -![deployment-single-instance](../images/deployment-single-instance.png) - -Browsers and other client applications can directly make HTTP requests to your application. You can put a web server (e.g. IIS or NGINX) between the clients and your application, but you still have a single application instance running in a single server or container. Single-instance configuration is **limited to scale** since it runs in a single server and you are limited with the server's capacity. - -### Clustered Deployment - -**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve different requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer**: - -![deployment-clustered](../images/deployment-clustered.png) - -### Load Balancers - -[Load balancers](https://en.wikipedia.org/wiki/Load_balancing_(computing)) have a lot of features, but they fundamentally **forward an incoming HTTP request** to an instance of your application and return your response back to the client application. - -Load balancers can use different algorithms for selecting the application instance while determining the application instance that is used to deliver the incoming request. **Round Robin** is one of the simplest and most used algorithms. Requests are delivered to the application instances in rotation. First instance gets the first request, second instance gets the second, and so on. It returns to the first instance after all the instances are used, and the algorithm goes like that for the next requests. - -### Potential Problems - -Once multiple instances of your application run in parallel, you should carefully consider the following topics: - -* Any **state (data) stored in memory** of your application will become a problem when you have multiple instances. A state stored in memory of an application instance may not be available in the next request since the next request will be handled by a different application instance. While there are some solutions (like sticky sessions) to overcome this problem user-basis, it is a **best practice to design your application as stateless** if you want to run it in a cluster, container or/and cloud. -* **In-memory caching** is a kind of in-memory state and should not be used in a clustered application. You should use **distributed caching** instead. -* You shouldn't store data in the **local file system**. It should be available to all instances of your application. Different application instance may run in different containers or servers and they may not be able to have access to the same file system. You can use a **cloud or external storage provider** as a solution. -* If you have **background workers** or **job queue managers**, you should be careful since multiple instances may try to execute the same job or perform the same work concurrently. As a result, you may have the same work done multiple times or you may get a lot of errors while trying to access and change the same resources. - -You may have more problems with clustered deployment, but these are the most common ones. ABP has been designed to be compatible with the clustered deployment scenario. The following sections explain what you should do when you are deploying your ABP based application to a clustered environment. - -## Switching to a Distributed Cache - -ASP.NET Core provides different kind of caching features. [In-memory cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) stores your objects in the memory of the local server and is only available to the application that stored the object. Non-sticky sessions in a clustered environment should use the [distributed caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) except some specific scenarios (for example, you can cache a local CSS file into memory. It is read-only data and it is the same in all application instances. You can cache it in memory for performance reasons without any problem). - -[ABP's Distributed Cache](../Caching.md) extends [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) infrastructure. It works in-memory by default. You should configure an actual distributed cache provider when you want to deploy your application to a clustered environment. - -> You should configure the cache provider for clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because the ABP Framework and the pre-built [application modules](../Modules/Index.md) are using distributed cache. - -ASP.NET Core provides multiple integrations to use as your distributed cache provider, like [Redis](https://redis.io/) and [NCache](https://www.alachisoft.com/ncache/). You can follow [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to learn how to use them in your applications. - -If you decided to use Redis as your distributed cache provider, **follow [ABP's Redis Cache Integration document](../Redis-Cache.md)** for the steps you need to follow to install it into your application and setup your Redis configuration. - -> Based on your preferences while creating a new ABP solution, Redis cache might be pre-installed in your solution. For example, if you have selected the *Tiered* option with the MVC UI, Redis cache comes as pre-installed. Because, in this case, you have two applications in your solution and they should use the same cache source to be consistent. - -## Using a Proper BLOB Storage Provider - -If you have used ABP's [BLOB Storing](../Blob-Storing.md) feature with the [File System provider](../Blob-Storing-File-System.md), you should use another provider in your clustered environment since the File System provider uses the application's local file system. - -The [Database BLOB provider](../Blob-Storing-Database) is the easiest way since it uses your application's main database (or another database if you configure) to store BLOBs. However, you should remember that BLOBs are large objects and may quickly increase your database's size. - -> [ABP Commercial](https://commercial.abp.io/) startup solution templates come with the database BLOB provider as pre-installed, and stores BLOBs in the application's database. - -Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOB storage providers. - -## Configuring Background Jobs - -ABP's [background job system](../Background-Jobs.md) is used to queue tasks to be executed in the background. Background job queue is persistent and a queued task is guaranteed to be executed (it is re-tried if it fails). - -ABP's default background job manager is compatible with clustered environments. It uses a [distributed lock](../Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance at a time. See the *Configuring a Distributed Lock Provider* section below to learn how to configure a distributed lock provider for your application, so the default background job manager properly works in a clustered environment. - -If you don't want to use a distributed lock provider, you may go with the following options: - -* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). -* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance. - -> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer to your provider's documentation to learn how it should be configured for a clustered environment. - -## Configuring a Distributed Lock Provider - -ABP provides a distributed locking abstraction with an implementation made with the [DistributedLock](https://github.com/madelson/DistributedLock) library. A distributed lock is used to control concurrent access to a shared resource by multiple applications to prevent corruption of the resource because of concurrent writes. The ABP Framework and some pre-built [application modules](../Modules/Index.md) are using distributed locking for several reasons. - -However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](../Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. - -## Configuring SignalR - -ABP provides [SignalR](../SignalR-Integration.md) integration packages to simplify integration and usage. SignalR can be used whenever you need to add real-time web functionality (real-time messaging, real-time notification etc.) into your application. - -SignalR requires that all HTTP requests for a specific connection be handled (needs to keep track of all its connections) by the same server process. So, when SignalR is running on a clustered environment (with multiple servers) **"sticky sessions"** must be used. - -If you are considering [scaling out](https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-6.0#scale-out) your servers and don't want to have inconsistency with the active socket connections, you can use [Azure SignalR Service](https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-6.0#azure-signalr-service) or [Redis backplane](https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-6.0#redis-backplane). - -> To learn more about how to host and scale SignalR in a clustered environment, please check the [ASP.NET Core SignalR hosting and scaling](https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-6.0). - -## Implementing Background Workers - -ASP.NET Core provides [hosted services](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services) and ABP provides [background workers](../Background-Workers.md) to perform tasks in background threads in your application. - -If your application has tasks running in the background, you should consider how they will behave in a clustered environment, especially if your background tasks are using the same resources. You should design your background tasks so that they continue to work properly in the clustered environment. - -Assume that your background worker in your SaaS application checks user subscriptions and sends emails if their subscription renewal date approaches. If the background task runs in multiple application instances, it is probable to send the same email many times to some users, which will disturb them. - -We suggest you to use one of the following approaches to overcome the problem: - -* Implement your background workers so that they work in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait for the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring about how the application is deployed. -* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. -* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance. diff --git a/docs/en/Deployment/Configuring-OpenIddict.md b/docs/en/Deployment/Configuring-OpenIddict.md deleted file mode 100644 index b044bcb275..0000000000 --- a/docs/en/Deployment/Configuring-OpenIddict.md +++ /dev/null @@ -1,47 +0,0 @@ -# Configuring OpenIddict - -This document introduces how to configure `OpenIddict` in the `AuthServer` project. - -There are different configurations in the `AuthServer` project for the `Development` and `Production` environments. - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - var hostingEnvironment = context.Services.GetHostingEnvironment(); - - if (!hostingEnvironment.IsDevelopment()) - { - PreConfigure(options => - { - options.AddDevelopmentEncryptionAndSigningCertificate = false; - }); - - PreConfigure(serverBuilder => - { - serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", "00000000-0000-0000-0000-000000000000"); - }); - } -} -```` - -## Development Environment - -We've enabled `AddDevelopmentEncryptionAndSigningCertificate` by default on the development environment. It registers (and generates if necessary) a user-specific development encryption/development signing certificate. This is a certificate used for signing and encrypting the tokens and for **development environment only**. - -`AddDevelopmentEncryptionAndSigningCertificate` cannot be used in applications deployed on IIS or Azure App Service: trying to use them on IIS or Azure App Service will result in an exception being thrown at runtime (unless the application pool is configured to [load a user profile](https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#user-profile)). - -To avoid that, consider creating self-signed certificates and storing them in the X.509 certificates storage of the host machine(s). This is the way we do it in the production environment. - -## Production Environment - -We've disabled `AddDevelopmentEncryptionAndSigningCertificate` in the production environment and tried to setup signing and encrypting certificates using `openiddict.pfx` file. - -You can use the `dotnet dev-certs https -v -ep openiddict.pfx -p 00000000-0000-0000-0000-000000000000` command to generate the `authserver.pfx` certificate. - -> `00000000-0000-0000-0000-000000000000` is the password of the certificate, you can change it to any password you want. - -> Also, please remember to copy `openiddict.pfx` to the [Content Root Folder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.ihostingenvironment.contentrootpath?view=aspnetcore-7.0) of the `AuthServer` website. - -> It is recommended to use **two** RSA certificates, distinct from the certificate(s) used for HTTPS: one for encryption, one for signing. - -For more information, please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-certificate-recommended-for-production-ready-scenarios diff --git a/docs/en/Deployment/Configuring-Production.md b/docs/en/Deployment/Configuring-Production.md deleted file mode 100644 index db928c8590..0000000000 --- a/docs/en/Deployment/Configuring-Production.md +++ /dev/null @@ -1,104 +0,0 @@ -# Configuring Your Application for Production Environments - -ABP Framework has a lot of options to configure and fine-tune its features. They are all explained in their own documents. Default values for these options are pretty well for most of the deployment environments. However, you may need to care about some options based on how you've structured your deployment environment. In this document, we will highlight these kind of options. So, it is highly recommended to read this document in order to not have unexpected behaviors in your system in production. - -## Distributed Cache Prefix - -ABP's [distributed cache infrastructure](../Caching.md) provides an option to set a key prefix for all of your data that is saved into your distributed cache provider. The default value of this option is not set (it is `null`). If you are using a distributed cache server that is shared by different applications, then you can set a prefix value to isolate an application's cache data from others. - -````csharp -Configure(options => -{ - options.KeyPrefix = "MyCrmApp"; -}); -```` - -That's all. ABP, then will add this prefix to all of your cache keys in your application as along as you use ABP's `IDistributedCache` or `IDistributedCache` services. See the [Caching documentation](../Caching.md) if you are new to distributed caching. - -> **Warning**: If you use ASP.NET Core's standard `IDistributedCache` service, it's your responsibility to add the key prefix (you can get the value by injecting `IOptions`). ABP can not do it. - -> **Warning**: Even if you have never used distributed caching in your own codebase, ABP still uses it for some features. So, you should always configure this prefix if your caching server is shared among multiple systems. - -> **Warning**: If you are building a microservice system, then you will have multiple applications that share the same distributed cache server. In such systems, all applications (or services) should normally use the same cache prefix, because you want all the applications to use the same cache data to have consistency between them. - -> **Warning**: Some of ABP's startup templates are pre-configured to set a prefix value for the distributed cache. So, please check your application code if it is already configured. - -## Distributed Lock Prefix - -ABP's [distributed locking infrastructure](../Distributed-Locking.md) provides an option to set a prefix for all the keys you are using in the distributed lock server. The default value of this option is not set (it is `null`). If you are using a distributed lock server that is shared by different applications, then you can set a prefix value to isolate an application's lock from others. - -````csharp -Configure(options => -{ - options.KeyPrefix = "MyCrmApp"; -}); -```` - -That's all. ABP, then will add this prefix to all of your keys in your application. See the [Distributed Locking documentation](../Distributed-Locking.md) if you are new to distributed locking. - -> **Warning**: Even if you have never used distributed locking in your own codebase, ABP still uses it for some features. So, you should always configure this prefix if your distributed lock server is shared among multiple systems. - -> **Warning**: If you are building a microservice system, then you will have multiple applications that share the same distributed locking server. In such systems, all applications (or services) should normally use the same lock prefix, because you want to globally lock your resources in your system. - -> **Warning**: Some of ABP's startup templates are pre-configured to set a prefix value for distributed locking. So, please check your application code if it is already configured. - -## Email Sender - -ABP's [Email Sending](../Emailing.md) system abstracts sending emails from your application and module code and allows you to configure the email provider and settings in a single place. - -Email service is configured to write email contents to the standard [application log](../Logging.md) in development environment. You should configure the email settings to be able to send emails to users in your production environment. - -Please see the [Email Sending](../Emailing.md) document to learn how to configure its settings to really send emails. - -> **Warning**: If you don't configure the email settings, you will get errors while trying to send emails. For example, the [Account module](../Modules/Account.md)'s *Password Reset* feature sends email to the users to reset their passwords if they forget it. - -## SMS Sender - -ABP's [SMS Sending abstraction](https://docs.abp.io/en/abp/latest/SMS-Sending) provides a unified interface to send SMS to users. However, its implementation is left to you. Because, typically a paid SMS service is used to send SMS, and ABP doesn't depend on a specific SMS provider. - -So, if you are using the `ISmsSender` service, you must implement it yourself, as shown in the following code block: - -````csharp -public class MySmsSender : ISmsSender, ITransientDependency -{ - public async Task SendAsync(SmsMessage smsMessage) - { - // TODO: Send it using your provider... - } -} -```` - -> [ABP Commercial](https://commercial.abp.io/) provides a [Twilio SMS Module](https://docs.abp.io/en/commercial/latest/modules/twilio-sms) as a pre-built integration with the popular [Twilio](https://www.twilio.com/) platform. - -## BLOB Provider - -If you use ABP's [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing) infrastructure, you should care about the BLOB provider in your production environment. For example, if you use the [File System](../Blob-Storing-File-System.md) provider and your application is running in a Docker container, you should configure a volume mapping for the BLOB storage path. Otherwise, your data will be lost when the container is restarted. Also, the File System is not a good provider for production if you have a [clustered deployment](Clustered-Environment.md) or a microservice system. - -Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOB storage providers. - -> **Warning**: Even if you don't directly use the BLOB Storage system, a module you are depending on may use it. For example, ABP Commercial's [File Management](https://docs.abp.io/en/commercial/latest/modules/file-management) module stores file contents, and the [Account](https://docs.abp.io/en/commercial/latest/modules/account) module stores user profile pictures in the BLOB Storage system. So, be careful with the BLOB Storing configuration in production. Note that ABP Commercial uses the [Database Provider](../Blob-Storing-Database.md) as a pre-configured BLOB storage provider, which works in production without any problem, but you may still want to use another provider. - -## String Encryption - -ABP's [`IStringEncryptionService` Service](../String-Encryption.md) simply encrypts and decrypts given strings based on a password phrase. You should configure the `AbpStringEncryptionOptions` options for the production with a strong password and keep it as a secret. You can also configure the other properties of those options class. See the following example: - -````csharp -Configure(options => -{ - options.DefaultPassPhrase = "gs5nTT042HAL4it1"; -}); -```` - -Note that ABP CLI automatically sets the password to a random value on a new project creation. However, it is stored in the `appsettings.json` file and is generally added to your source control. It is suggested to use [User Secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) or [Environment Variables](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration) to set that value. - -## Logging - -ABP uses .NET's standard [Logging services](../Logging.md). So, it is compatible with any logging provider that works with .NET. ABP's startup solution templates come with [Serilog](https://serilog.net/) pre-installed and configured for you. It writes logs to file system and console with the initial configuration. File system is useful for development environment, but it is suggested you to use a different provider for your production environment, like Elasticsearch, database or any other provider that can properly work. - -## The Swagger UI - -ABP's startup solution templates come with [Swagger UI](https://swagger.io/) pre-installed. Swagger is a pretty standard and useful tool to discover and test your HTTP APIs on a built-in UI that is embedded into your application or service. It is typically used in development environment, but you may want to enable it on staging or production environments too. - -While you will always secure your HTTP APIs with other techniques (like the [Authorization](../Authorization.md) system), allowing malicious software and people to easily discover your HTTP API endpoint details can be considered as a security problem for some systems. So, be careful while taking the decision of enabling or disabling Swagger for the production environment. - -> You may also want to see the [ABP Swagger integration](../API/Swagger-Integration.md) document. diff --git a/docs/en/Deployment/Distributed-Microservice.md b/docs/en/Deployment/Distributed-Microservice.md deleted file mode 100644 index 9af4a1c839..0000000000 --- a/docs/en/Deployment/Distributed-Microservice.md +++ /dev/null @@ -1,45 +0,0 @@ -# Deploying Distributed / Microservice Solutions - -The ABP Framework is designed to consider distributed and microservice systems, where you have multiple applications and/or services communicating internally. All of its features are compatible with distributed scenarios. This document highlights some points you should care about when you deploy your distributed or microservice solution. - -## Application Name & Instance Id - -ABP provides the `IApplicationInfoAccessor` service that provides the following properties: - -* `ApplicationName`: A human-readable name for an application. It is a unique value for an application. -* `InstanceId`: A random (GUID) value generated by the ABP Framework each time you start the application. - -These values are used by the ABP Framework in several places to distinguish the application and the application instance (process) in the system. For example, the [audit logging](../Audit-Logging.md) system saves the `ApplicationName` in each audit log record written by the related application, so you can understand which application has created the audit log entry. So, if your system consists of multiple applications saving audit logs to a single point, you should be sure that each application has a different `ApplicationName`. - -The `ApplicationName` property's value is set automatically from the **entry assembly's name** (generally, the project name in a .NET solution) by default, which is proper for most cases, since each application typically has a unique entry assembly name. - -There are two ways to set the application name to a different value. In this first approach, you can set the `ApplicationName` property in your application's [configuration](../Configuration.md). The easiest way is to add an `ApplicationName` field to your `appsettings.json` file: - -````json -{ - "ApplicationName": "Services.Ordering" -} -```` - -Alternatively, you can set `AbpApplicationCreationOptions.ApplicationName` while creating the ABP application. You can find the `AddApplication` or `AddApplicationAsync` call in your solution (typically in the `Program.cs` file), and set the `ApplicationName` option as shown below: - -````csharp -await builder.AddApplicationAsync(options => -{ - options.ApplicationName = "Services.Ordering"; -}); -```` - -## Using a Distributed Event Bus - -ABP's [Distributed Event Bus](../Distributed-Event-Bus.md) system provides a standard interface to communicate with other applications and services. While the name is "distributed", the default implementation is in-process. That means, your applications / services can not communicate with each other unless you explicitly configure a distributed event bus provider. - -If you are building a distributed system, then the applications should communicate through an external distributed messaging server. Please follow the [Distributed Event Bus](../Distributed-Event-Bus.md) document to learn how to install and configure your distributed event bus provider. - -> **Warning**: Even if you don't use the distributed event bus directly in your application code, the ABP Framework and some of the modules you are using may use it. So, if you are building a distributed system, always configure a distributed event bus provider. - -> **Info**: [Clustered deployment](Clustered-Environment.md) of a single application is not considered as a distributed system. So, if you only have a single application with multiple instances serving behind a load balancer, a real distributed messaging server may not be needed. - -## See Also - -* [Deploying to a Clustered Environment](Clustered-Environment.md) diff --git a/docs/en/Deployment/Index.md b/docs/en/Deployment/Index.md deleted file mode 100644 index cf7137464f..0000000000 --- a/docs/en/Deployment/Index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Deployment - -Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. You can deploy it to a cloud provider (e.g. Azure, AWS, Google Could) or on-premise server, IIS or any other web server. ABP's documentation doesn't contain much information on deployment. You can refer to your provider's documentation. - -However, there are some topics that you should care about when you are deploying your applications. Most of them are general software deployment considerations, but you should understand how to handle them within your ABP based applications. We've prepared guides for this purpose and we suggest you to read these guides carefully before designing your deployment configuration. - -## Guides - -* [Configuring SSL certificate(HTTPS)](SSL.md): Explains how to configure SSL certificate(HTTPS) for your application. -* [Configuring OpenIddict](Configuring-OpenIddict.md): Notes for some essential configurations for OpenIddict. -* [Configuring for Production](Configuring-Production.md): Notes for some essential configurations for production environments. -* [Optimization for Production](Optimizing-Production.md): Tips and suggestions for optimizing your application on production environments. -* [Deploying to a Clustered Environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently. -* [Deploying Distributed / Microservice Solutions](Distributed-Microservice.md): Deployment notes for solutions consisting of multiple applications and/or services. diff --git a/docs/en/Deployment/Optimizing-Production.md b/docs/en/Deployment/Optimizing-Production.md deleted file mode 100644 index 7e896f9b1c..0000000000 --- a/docs/en/Deployment/Optimizing-Production.md +++ /dev/null @@ -1,22 +0,0 @@ -# Optimizing Your Application for Production Environments - -ABP Framework and the startup solution templates are configured well to get the maximum performance on production environments. However, there are still some points you need to pay attention to in order to optimize your system in production. In this document, we will mention some of these topics. - -## Caching Static Contents - -The following items are contents that can be cached in the client side (typically in the Browser) or in a CDN server: - -* **Static images** can always be cached. Here, you should be careful that if you change an image, use a different file name, or use a versioning query-string parameter, so the browser (or CDN) understands it's been changed. -* **CSS and JavaScript files**. ABP's [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) system always uses a query-string versioning parameter and a hash value in the files names of the CSS & JavaScript files for the [MVC (Razor Pages)](../UI/AspNetCore/Overall.md) UI. So, you can safely cache these files in the client side or in a CDN server. -* **Application bundle files** of an [Angular UI](../UI/Angular/Quick-Start.md) application. -* **[Application Localization Endpoint](../API/Application-Localization.md)** can be cached per culture (it already has a `cultureName` query string parameter) if you don't use dynamic localization on the server-side. ABP Commercial's [Language Management](https://commercial.abp.io/modules/Volo.LanguageManagement) module provides dynamic localization. If you're using it, you can't cache that endpoint forever. However, you can still cache it for a while. Applying dynamic localization text changes to the application can delay for a few minutes, even for a few hours in a real life scenario. - -There may be more ways based on your solution structure and deployment environment, but these are the essential points you should consider to client-side cache in a production environment. - -## Bundling & Minification for MVC (Razor Pages) UI - -ABP's [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) system automatically bundles, minifies and versions your CSS and JavaScript files in production environment. Normally, you don't need to do anything, if you haven't disabled it yourself in your application code. It is important to follow the [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) document and truly use the system to get the maximum optimization. - -## Background Jobs - -ABP's [Background Jobs](../Background-Jobs.md) system provides an abstraction with a basic implementation to enqueue jobs and execute them in a background thread. ABP's Default Background Job Manager may not be enough if you are adding too many jobs to the queue and want them to be executed in parallel by multiple servers with a high performance. If you need these, you should consider to configure a dedicated background job software, like [Hangfire](https://www.hangfire.io/). ABP has a pre-built [Hangfire integration](../Background-Jobs-Hangfire.md), so you can switch to Hangfire without changing your application code. diff --git a/docs/en/Deployment/SSL.md b/docs/en/Deployment/SSL.md deleted file mode 100644 index c48c916d93..0000000000 --- a/docs/en/Deployment/SSL.md +++ /dev/null @@ -1,34 +0,0 @@ -# Configuring Configuring SSL certificate(HTTPS) - -A website needs an SSL certificate in order to keep user data secure, verify ownership of the website, prevent attackers from creating a fake version of the site, and gain user trust. - -This document introduces how to get and use SSL certificate(HTTPS) for your application. - -## Get a SSL Certificate from a Certificate Authority - -You can get a SSL certificate from a certificate authority (CA) such as [Let's Encrypt](https://letsencrypt.org/) or [Cloudflare](https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/) and so on. - -Once you have a certificate, you need to configure your web server to use it. The following references show how to configure your web server to use a certificate. - -* [Host ASP.NET Core on Linux with Apache: HTTPS configuration](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache) -* [Host ASP.NET Core on Linux with Nginx: HTTPS configuration](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx) -* [How to Set Up SSL on IIS 7 or later](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis) - -## Create a Self-Signed Certificate - -You can create a self-signed certificate for testing purposes or internal use. - -There is an article about [how to create a self-signed certificate](https://learn.microsoft.com/en-us/dotnet/core/additional-tools/self-signed-certificates-guide), If you are using IIS, you can use the following this document to [obtain a Certificate](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis#obtain-a-certificate) - -## Common Problems - -### The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot - -This error may occur when using IIS. You need to trust your certificate by `Manage computer certificates`. - -## References - -* [ABP commercial IIS Deployment](https://docs.abp.io/en/commercial/latest/startup-templates/application/deployment-iis) -* [HTTPS in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl) -* [Let's Encrypt](https://letsencrypt.org/getting-started) -* [Cloudflare's Free SSL / TLS](https://www.cloudflare.com/application-services/products/ssl/) \ No newline at end of file diff --git a/docs/en/Distributed-Event-Bus-Azure-Integration.md b/docs/en/Distributed-Event-Bus-Azure-Integration.md deleted file mode 100644 index acca2e0172..0000000000 --- a/docs/en/Distributed-Event-Bus-Azure-Integration.md +++ /dev/null @@ -1,133 +0,0 @@ -# Distributed Event Bus Azure Integration - -> This document explains **how to configure the [Azure Service Bus](https://azure.microsoft.com/en-us/services/service-bus/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system - -## Installation - -Use the ABP CLI to add [Volo.Abp.EventBus.Azure](https://www.nuget.org/packages/Volo.Abp.EventBus.Azure) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Azure` package. -* Run `abp add-package Volo.Abp.EventBus.Azure` command. - -If you want to do it manually, install the [Volo.Abp.EventBus.Azure](https://www.nuget.org/packages/Volo.Abp.EventBus.Azure) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusAzureModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -You can configure using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes. - -### `appsettings.json` file configuration - -This is the simplest way to configure the Azure Service Bus settings. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/). - -**Example: The minimal configuration to connect to Azure Service Bus Namespace with default configurations** - -````json -{ - "Azure": { - "ServiceBus": { - "Connections": { - "Default": { - "ConnectionString": "Endpoint=sb://sb-my-app.servicebus.windows.net/;SharedAccessKeyName={%{{{Policy Name}}}%};SharedAccessKey={};EntityPath=marketing-consent" - } - } - }, - "EventBus": { - "ConnectionName": "Default", - "SubscriberName": "MySubscriberName", - "TopicName": "MyTopicName" - } - } -} -```` - -* `MySubscriberName` is the name of this subscription, which is used as the **Subscriber** on the Azure Service Bus. -* `MyTopicName` is the **topic name**. - -See [the Azure Service Bus document](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-queues-topics-subscriptions) to understand these options better. - -#### Connections - -If you need to connect to another Azure Service Bus Namespace the Default, you need to configure the connection properties. - -**Example: Declare two connections and use one of them for the event bus** - -````json -{ - "Azure": { - "ServiceBus": { - "Connections": { - "Default": { - "ConnectionString": "Endpoint=sb://sb-my-app.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey={%{{{SharedAccessKey}}}%}" - }, - "SecondConnection": { - "ConnectionString": "Endpoint=sb://sb-my-app.servicebus.windows.net/;SharedAccessKeyName={%{{{Policy Name}}}%};SharedAccessKey={%{{{SharedAccessKey}}}%}" - } - } - }, - "EventBus": { - "ConnectionName": "SecondConnection", - "SubscriberName": "MySubscriberName", - "TopicName": "MyTopicName" - } - } -} -```` - -This allows you to use multiple Azure Service Bus namespaces in your application, but select one of them for the event bus. - -You can use any of the [ServiceBusAdministrationClientOptions](https://docs.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.administration.servicebusadministrationclientoptions?view=azure-dotnet), [ServiceBusClientOptions](https://docs.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusclientoptions?view=azure-dotnet), [ServiceBusProcessorOptions](https://docs.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusprocessoroptions?view=azure-dotnet) properties for the connection. - -**Example: Specify the Admin, Client and Processor options** - -````json -{ - "Azure": { - "ServiceBus": { - "Connections": { - "Default": { - "ConnectionString": "Endpoint=sb://sb-my-app.servicebus.windows.net/;SharedAccessKeyName={%{{{Policy Name}}}%};SharedAccessKey={};EntityPath=marketing-consent", - "Admin": { - "Retry": { - "MaxRetries": 3 - } - }, - "Client": { - "RetryOptions": { - "MaxRetries": 1 - } - }, - "Processor": { - "AutoCompleteMessages": true, - "ReceiveMode": "ReceiveAndDelete" - } - } - } - }, - "EventBus": { - "ConnectionName": "Default", - "SubscriberName": "MySubscriberName", - "TopicName": "MyTopicName" - } - } -} -```` - -### The Options Classes - -`AbpAzureServiceBusOptions` and `AbpAzureEventBusOptions` classes can be used to configure the connection strings and event bus options for Azure Service Bus. - -You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md). - -**Example: Configure the connection** - -````csharp -Configure(options => -{ - options.Connections.Default.ConnectionString = "Endpoint=sb://sb-my-app.servicebus.windows.net/;SharedAccessKeyName={%{{{Policy Name}}}%};SharedAccessKey={}"; - options.Connections.Default.Admin.Retry.MaxRetries = 3; - options.Connections.Default.Client.RetryOptions.MaxRetries = 1; -}); -```` - -Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. diff --git a/docs/en/Distributed-Event-Bus-Kafka-Integration.md b/docs/en/Distributed-Event-Bus-Kafka-Integration.md deleted file mode 100644 index 82e658f48d..0000000000 --- a/docs/en/Distributed-Event-Bus-Kafka-Integration.md +++ /dev/null @@ -1,167 +0,0 @@ -# Distributed Event Bus Kafka Integration - -> This document explains **how to configure the [Kafka](https://kafka.apache.org/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system - -## Installation - -Use the ABP CLI to add [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Kafka` package. -* Run `abp add-package Volo.Abp.EventBus.Kafka` command. - -If you want to do it manually, install the [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusKafkaModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -You can configure using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes. - -### `appsettings.json` file configuration - -This is the simplest way to configure the Kafka settings. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/). - -**Example: The minimal configuration to connect to a local kafka server with default configurations** - -````json -{ - "Kafka": { - "Connections": { - "Default": { - "BootstrapServers": "localhost:9092" - } - }, - "EventBus": { - "GroupId": "MyGroupId", - "TopicName": "MyTopicName" - } - } -} -```` - -* `MyGroupId` is the name of this application, which is used as the **GroupId** on the Kakfa. -* `MyTopicName` is the **topic name**. - -See [the Kafka document](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.html) to understand these options better. - -#### Connections - -If you need to connect to another server than the localhost, you need to configure the connection properties. - -**Example: Specify the host name (as an IP address)** - -````json -{ - "Kafka": { - "Connections": { - "Default": { - "BootstrapServers": "123.123.123.123:9092" - } - }, - "EventBus": { - "GroupId": "MyGroupId", - "TopicName": "MyTopicName" - } - } -} -```` - -Defining multiple connections is allowed. In this case, you can specify the connection that is used for the event bus. - -**Example: Declare two connections and use one of them for the event bus** - -````json -{ - "Kafka": { - "Connections": { - "Default": { - "BootstrapServers": "123.123.123.123:9092" - }, - "SecondConnection": { - "BootstrapServers": "321.321.321.321:9092" - } - }, - "EventBus": { - "GroupId": "MyGroupId", - "TopicName": "MyTopicName", - "ConnectionName": "SecondConnection" - } - } -} -```` - -This allows you to use multiple Kafka cluster in your application, but select one of them for the event bus. - -You can use any of the [ClientConfig](https://docs.confluent.io/current/clients/confluent-kafka-dotnet/api/Confluent.Kafka.ClientConfig.html) properties as the connection properties. - -**Example: Specify the socket timeout** - -````json -{ - "Kafka": { - "Connections": { - "Default": { - "BootstrapServers": "123.123.123.123:9092", - "SocketTimeoutMs": 60000 - } - } - } -} -```` - -### The Options Classes - -`AbpKafkaOptions` and `AbpKafkaEventBusOptions` classes can be used to configure the connection strings and event bus options for the Kafka. - -You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md). - -**Example: Configure the connection** - -````csharp -Configure(options => -{ - options.Connections.Default.BootstrapServers = "123.123.123.123:9092"; - options.Connections.Default.SaslUsername = "user"; - options.Connections.Default.SaslPassword = "pwd"; -}); -```` - -**Example: Configure the consumer config** - -````csharp -Configure(options => -{ - options.ConfigureConsumer = config => - { - config.GroupId = "MyGroupId"; - config.EnableAutoCommit = false; - }; -}); -```` - -**Example: Configure the producer config** - -````csharp -Configure(options => -{ - options.ConfigureProducer = config => - { - config.MessageTimeoutMs = 6000; - config.Acks = Acks.All; - }; -}); -```` - -**Example: Configure the topic specification** - -````csharp -Configure(options => -{ - options.ConfigureTopic = specification => - { - specification.ReplicationFactor = 3; - specification.NumPartitions = 3; - }; -}); -```` - -Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. diff --git a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md b/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md deleted file mode 100644 index ef174f92b0..0000000000 --- a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md +++ /dev/null @@ -1,165 +0,0 @@ -# Distributed Event Bus RabbitMQ Integration - -> This document explains **how to configure the [RabbitMQ](https://www.rabbitmq.com/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system - -## Installation - -Use the ABP CLI to add [Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.RabbitMQ` package. -* Run `abp add-package Volo.Abp.EventBus.RabbitMQ` command. - -If you want to do it manually, install the [Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusRabbitMqModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -You can configure using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes. - -### `appsettings.json` file configuration - -This is the simplest way to configure the RabbitMQ settings. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/). - -**Example: The minimal configuration to connect to a local RabbitMQ server with default configurations** - -````json -{ - "RabbitMQ": { - "EventBus": { - "ClientName": "MyClientName", - "ExchangeName": "MyExchangeName" - } - } -} -```` - -* `ClientName` is the name of this application, which is used as the **queue name** on the RabbitMQ. -* `ExchangeName` is the **exchange name**. - -See [the RabbitMQ document](https://www.rabbitmq.com/dotnet-api-guide.html#exchanges-and-queues) to understand these options better. - -#### Connections - -If you need to connect to another server than the localhost, you need to configure the connection properties. - -**Example: Specify the host name (as an IP address)** - -````json -{ - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "123.123.123.123" - } - }, - "EventBus": { - "ClientName": "MyClientName", - "ExchangeName": "MyExchangeName" - } - } -} -```` - -Defining multiple connections is allowed. In this case, you can specify the connection that is used for the event bus. - -**Example: Declare two connections and use one of them for the event bus** - -````json -{ - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "123.123.123.123" - }, - "SecondConnection": { - "HostName": "321.321.321.321" - } - }, - "EventBus": { - "ClientName": "MyClientName", - "ExchangeName": "MyExchangeName", - "ConnectionName": "SecondConnection" - } - } -} -```` - -This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus. - -You can use any of the [ConnectionFactory](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) properties as the connection properties. - -**Example: Specify the connection port** - -````json -{ - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "123.123.123.123", - "Port": "5672" - } - } - } -} -```` - -If you need to connect to the RabbitMQ cluster, you can use the `;` character to separate the host names. - -**Example: Connect to the RabbitMQ cluster** - -```json -{ - "RabbitMQ": { - "Connections": { - "Default": { - "HostName": "123.123.123.123;234.234.234.234" - } - }, - "EventBus": { - "ClientName": "MyClientName", - "ExchangeName": "MyExchangeName" - } - } -} -``` - -### The Options Classes - -`AbpRabbitMqOptions` and `AbpRabbitMqEventBusOptions` classes can be used to configure the connection strings and event bus options for the RabbitMQ. - -You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md). - -**Example: Configure the connection** - -````csharp -Configure(options => -{ - options.Connections.Default.UserName = "user"; - options.Connections.Default.Password = "pass"; - options.Connections.Default.HostName = "123.123.123.123"; - options.Connections.Default.Port = 5672; -}); -```` - -**Example: Configure the client, exchange names and prefetchCount** - -````csharp -Configure(options => -{ - options.ClientName = "TestApp1"; - options.ExchangeName = "TestMessages"; - options.PrefetchCount = 1; -}); -```` - -**Example: Configure the queue and exchange optional arguments** - -```csharp -Configure(options => -{ - options.ExchangeArguments["x-delayed-type"] = "direct"; - options.QueueArguments["x-message-ttl"] = 60000; -}); -``` - -Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file. diff --git a/docs/en/Distributed-Event-Bus-Rebus-Integration.md b/docs/en/Distributed-Event-Bus-Rebus-Integration.md deleted file mode 100644 index 39fb35ede9..0000000000 --- a/docs/en/Distributed-Event-Bus-Rebus-Integration.md +++ /dev/null @@ -1,65 +0,0 @@ -# Distributed Event Bus Rebus Integration - -> This document explains **how to configure the [Rebus](http://mookid.dk/category/rebus/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system - -## Installation - -Use the ABP CLI to add [Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus) NuGet package to your project: - -* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. -* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Rebus` package. -* Run `abp add-package Volo.Abp.EventBus.Rebus` command. - -If you want to do it manually, install the [Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusRebusModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. - -## Configuration - -You can configure using the standard [configuration system](Configuration.md), like using the [options](Options.md) classes. - -### The Options Classes - -`AbpRebusEventBusOptions` class can be used to configure the event bus options for the Rebus. - -You can configure this options inside the `PreConfigureServices` of your [module](Module-Development-Basics.md). - -**Example: Minimize configuration** - -```csharp -PreConfigure(options => -{ - options.InputQueueName = "eventbus"; -}); -``` - -Rebus has many options, you can use the `Configurer` property of `AbpRebusEventBusOptions` class to configure. - -Default events are **stored in memory**. See the [rebus document](https://github.com/rebus-org/Rebus/wiki/Transport) for more details. - -**Example: Configure the store** - -````csharp -PreConfigure(options => -{ - options.InputQueueName = "eventbus"; - options.Configurer = rebusConfigurer => - { - rebusConfigurer.Transport(t => t.UseMsmq("eventbus")); - rebusConfigurer.Subscriptions(s => s.UseJsonFile(@"subscriptions.json")); - }; -}); -```` - -You can use the `Publish` properpty of `AbpRebusEventBusOptions` class to change the publishing method - -**Example: Configure the event publishing** - -````csharp -PreConfigure(options => -{ - options.InputQueueName = "eventbus"; - options.Publish = async (bus, type, data) => - { - await bus.Publish(data); - }; -}); -```` diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md deleted file mode 100644 index fa825911f7..0000000000 --- a/docs/en/Distributed-Event-Bus.md +++ /dev/null @@ -1,710 +0,0 @@ -# Distributed Event Bus - -Distributed Event bus system allows to **publish** and **subscribe** to events that can be **transferred across application/service boundaries**. You can use the distributed event bus to asynchronously send and receive messages between **microservices** or **applications**. - -## Providers - -Distributed event bus system provides an **abstraction** that can be implemented by any vendor/provider. There are four providers implemented out of the box: - -* `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider. -* `AzureDistributedEventBus` implements the distributed event bus with the [Azure Service Bus](https://azure.microsoft.com/en-us/services/service-bus/). See the [Azure Service Bus integration document](Distributed-Event-Bus-Azure-Integration.md) to learn how to configure it. -* `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it. -* `KafkaDistributedEventBus` implements the distributed event bus with the [Kafka](https://kafka.apache.org/). See the [Kafka integration document](Distributed-Event-Bus-Kafka-Integration.md) to learn how to configure it. -* `RebusDistributedEventBus` implements the distributed event bus with the [Rebus](http://mookid.dk/category/rebus/). See the [Rebus integration document](Distributed-Event-Bus-Rebus-Integration.md) to learn how to configure it. - -Using a local event bus as default has a few important advantages. The most important one is that: It allows you to write your code compatible to distributed architecture. You can write a monolithic application now that can be split into microservices later. It is a good practice to communicate between bounded contexts (or between application modules) via distributed events instead of local events. - -For example, [pre-built application modules](Modules/Index.md) is designed to work as a service in a distributed system while they can also work as a module in a monolithic application without depending an external message broker. - -## Publishing Events - -There are two ways of publishing distributed events explained in the following sections. - -### Using IDistributedEventBus to Publish Events - -`IDistributedEventBus` can be [injected](Dependency-Injection.md) and used to publish a distributed event. - -**Example: Publish a distributed event when the stock count of a product changes** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus.Distributed; - -namespace AbpDemo -{ - public class MyService : ITransientDependency - { - private readonly IDistributedEventBus _distributedEventBus; - - public MyService(IDistributedEventBus distributedEventBus) - { - _distributedEventBus = distributedEventBus; - } - - public virtual async Task ChangeStockCountAsync(Guid productId, int newCount) - { - await _distributedEventBus.PublishAsync( - new StockCountChangedEto - { - ProductId = productId, - NewCount = newCount - } - ); - } - } -} -```` - -`PublishAsync` method gets the event object, which is responsible to hold the data related to the event. It is a simple plain class: - -````csharp -using System; - -namespace AbpDemo -{ - [EventName("MyApp.Product.StockChange")] - public class StockCountChangedEto - { - public Guid ProductId { get; set; } - - public int NewCount { get; set; } - } -} -```` - -Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). - -> `Eto` is a suffix for **E**vent **T**ransfer **O**bjects we use by convention. While it is not required, we find it useful to identify such event classes (just like [DTOs](Data-Transfer-Objects.md) on the application layer). - -#### Event Name - -`EventName` attribute is optional, but suggested. If you don't declare it for an event type (ETO class), the event name will be the full name of the event class, `AbpDemo.StockCountChangedEto` in this case. - -#### About Serialization for the Event Objects - -Event transfer objects (ETOs) **must be serializable** since they will be serialized/deserialized to JSON or other format when it is transferred to out of the process. - -Avoid circular references, polymorphism, private setters and provide default (empty) constructors if you have any other constructor as a good practice (while some serializers may tolerate it), just like the DTOs. - -### Publishing Events Inside Entity / Aggregate Root Classes - -[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish distributed events inside entity / aggregate root classes. - -**Example: Publish a distributed event inside an aggregate root method** - -````csharp -using System; -using Volo.Abp.Domain.Entities; - -namespace AbpDemo -{ - public class Product : AggregateRoot - { - public string Name { get; set; } - - public int StockCount { get; private set; } - - private Product() { } - - public Product(Guid id, string name) - : base(id) - { - Name = name; - } - - public void ChangeStockCount(int newCount) - { - StockCount = newCount; - - //ADD an EVENT TO BE PUBLISHED - AddDistributedEvent( - new StockCountChangedEto - { - ProductId = Id, - NewCount = newCount - } - ); - } - } -} -```` - -`AggregateRoot` class defines the `AddDistributedEvent` to add a new distributed event, that is published when the aggregate root object is saved (created, updated or deleted) into the database. - -> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. - -#### IGeneratesDomainEvents Interface - -Actually, adding distributed events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you. - -> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example. - -#### How It Was Implemented? - -Calling the `AddDistributedEvent` doesn't immediately publish the event. The event is published when you save changes to the database; - -* For EF Core, it is published on `DbContext.SaveChanges`. -* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). - -## Subscribing to Events - -A service can implement the `IDistributedEventHandler` to handle the event. - -**Example: Handle the `StockCountChangedEto` defined above** - -````csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus.Distributed; - -namespace AbpDemo -{ - public class MyHandler - : IDistributedEventHandler, - ITransientDependency - { - public async Task HandleEventAsync(StockCountChangedEto eventData) - { - var productId = eventData.ProductId; - } - } -} -```` - -That's all. - -* `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEto` event occurs. -* If you are using a distributed message broker, like RabbitMQ, ABP automatically **subscribes to the event on the message broker**, gets the message, executes the handler. -* It sends **confirmation (ACK)** to the message broker if the event handler was successfully executed (did not throw any exception). - -You can inject any service and perform any required logic here. A single event handler class can **subscribe to multiple events** but implementing the `IDistributedEventHandler` interface for each event type. - -If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope. - -> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. - -## Monitoring Distributed Events - -The ABP Framework allows you to stay informed when your application **receives** or **sends** a distributed event. This capability enables you to track the event flow within your application and take appropriate actions based on the received or sent distributed events. - -### Received Events - -The `DistributedEventReceived` local event is published when your application receives an event from the distributed event bus. `DistributedEventReceived` class has the following fields: - -- **`Source`:** It represents the source of the distributed event. Source can be `Direct`, `Inbox`, `Outbox`. -- **`EventName`:** It represents the [name](#event-name) of the event received. -- **`EventData`:** It represents the actual data associated with the event received. Since it is of type `object`, it can hold any type of data. - -**Example: Get informed when your application receives an event from the distributed event bus** - -```csharp -public class DistributedEventReceivedHandler : ILocalEventHandler, ITransientDependency -{ - public async Task HandleEventAsync(DistributedEventReceived eventData) - { - // TODO: IMPLEMENT YOUR LOGIC... - } -} -``` - -### Sent Events - -The `DistributedEventSent` local event is published when your application sends an event to the distributed event bus. `DistributedEventSent` class has the following fields: - -- **`Source`:** It represents the source of the distributed event. Source can be `Direct`, `Inbox`, `Outbox`. -- **`EventName`:** It represents the [name](#event-name) of the event sent. -- **`EventData`:** It represents the actual data associated with the event sent. Since it is of type `object`, it can hold any type of data. - -**Example: Get informed when your application sends an event to the distributed event bus** - -```csharp -public class DistributedEventSentHandler : ILocalEventHandler, ITransientDependency -{ - public async Task HandleEventAsync(DistributedEventSent eventData) - { - // TODO: IMPLEMENT YOUR LOGIC... - } -} -``` - -You can seamlessly integrate event-tracking capabilities into your application by subscribing to the `DistributedEventReceived` and `DistributedEventSent` local events as above examples. This empowers you to effectively monitor the messaging flow, diagnose any potential issues, and gain valuable insights into the behavior of your distributed messaging system. - -## Pre-Defined Events - -ABP Framework **automatically publishes** distributed events for **create, update and delete** operations for an [entity](Entities.md) once you configure it. - -### Event Types - -There are three pre-defined event types: - -* `EntityCreatedEto` is published when an entity of type `T` was created. -* `EntityUpdatedEto` is published when an entity of type `T` was updated. -* `EntityDeletedEto` is published when an entity of type `T` was deleted. - -These types are generics. `T` is actually the type of the **E**vent **T**ransfer **O**bject (ETO) rather than the type of the entity. Because, an entity object can not be transferred as a part of the event data. So, it is typical to define a ETO class for an entity class, like `ProductEto` for `Product` entity. - -### Subscribing to the Events - -Subscribing to the auto events is same as subscribing a regular distributed event. - -**Example: Get notified once a product updated** - -````csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Entities.Events.Distributed; -using Volo.Abp.EventBus.Distributed; - -namespace AbpDemo -{ - public class MyHandler : - IDistributedEventHandler>, - ITransientDependency - { - public async Task HandleEventAsync(EntityUpdatedEto eventData) - { - var productId = eventData.Entity.Id; - //TODO - } - } -} -```` - -* `MyHandler` implements the `IDistributedEventHandler>`. -* It is required to register your handler class to the [dependency injection](Dependency-Injection.md) system. Implementing `ITransientDependency` like in this example is an easy way. - -### Configuration - -You can configure the `AbpDistributedEntityEventOptions` in the `ConfigureServices` of your [module](Module-Development-Basics.md) to add a selector. - -**Example: Configuration samples** - -````csharp -Configure(options => -{ - //Enable for all entities - options.AutoEventSelectors.AddAll(); - - //Enable for a single entity - options.AutoEventSelectors.Add(); - - //Enable for all entities in a namespace (and child namespaces) - options.AutoEventSelectors.AddNamespace("MyProject.Products"); - - //Custom predicate expression that should return true to select a type - options.AutoEventSelectors.Add( - type => type.Namespace.StartsWith("MyProject.") - ); -}); -```` - -* The last one provides flexibility to decide if the events should be published for the given entity type. Returns `true` to accept a `Type`. - -You can add more than one selector. If one of the selectors match for an entity type, then it is selected. - -### Event Transfer Object - -Once you enable **auto events** for an entity, ABP Framework starts to publish events on the changes on this entity. If you don't specify a corresponding **E**vent **T**ransfer **O**bject (ETO) for the entity, ABP Framework uses a standard type, named `EntityEto`, which has only two properties: - -* `EntityType` (`string`): Full name (including namespace) of the entity class. -* `KeysAsString` (`string`): Primary key(s) of the changed entity. If it has a single key, this property will be the primary key value. For a composite key, it will contain all keys separated by `,` (comma). - -So, you can implement the `IDistributedEventHandler>` to subscribe the update events. However, it is not a good approach to subscribe to such a generic event, because you handle the update events for all entities in a single handler (since they all use the same ETO object). You can define the corresponding ETO type for the entity type. - -**Example: Declare to use `ProductEto` for the `Product` entity** - -````csharp -public class ProductEto -{ - public Guid Id { get; set; } - public string Name { get; set; } - public float Price { get; set; } -} -```` - -Then you can use the `AbpDistributedEntityEventOptions.EtoMappings` option to map your `Product` entity to the `ProductEto`: - -````csharp -Configure(options => -{ - options.AutoEventSelectors.Add(); - options.EtoMappings.Add(); -}); -```` - -This example; - -* Adds a selector to allow to publish the create, update and delete events for the `Product` entity. -* Configure to use the `ProductEto` as the event transfer object to publish for the `Product` related events. - -> Distributed event system use the [object to object mapping](Object-To-Object-Mapping.md) system to map `Product` objects to `ProductEto` objects. So, you need to configure the object mapping (`Product` -> `ProductEto`) too. You can check the [object to object mapping document](Object-To-Object-Mapping.md) to learn how to do it. - -## Entity Synchronizer - -In a distributed (or microservice) system, it is typical to subscribe to change events for an [entity](Entities.md) type of another service, so you can get notifications when the subscribed entity changes. In that case, you can use ABP's Pre-Defined Events as explained in the previous section. - -If your purpose is to store your local copies of a remote entity, you typically subscribe to create, update and delete events of the remote entity and update your local database in your event handler. ABP provides a pre-built `EntitySynchronizer` base class to make that operation easier for you. - -Assume that there is a `Product` entity (probably an aggregate root entity) in a Catalog microservice, and you want to keep copies of the products in your Ordering microservice, with a local `OrderProduct` entity. In practice, properties of the `OrderProduct` class will be a subset of the `Product` properties, because not all the product data is needed in the Ordering microservice (however, you can make a full copy if you need). Also, the `OrderProduct` entity may have additional properties that are populated and used in the Ordering microservice. - -The first step to establish the synchronization is to define an ETO (Event Transfer Object) class in the Catalog microservice that is used to transfer the event data. Assuming the `Product` entity has a `Guid` key, your ETO can be as shown below: - -````csharp -[EventName("product")] -public class ProductEto : EntityEto -{ - // Your Product properties here... -} -```` - -`ProductEto` can be put in a shared project (DLL) that is referenced by the Catalog and the Ordering microservices. Alternatively, you can put a copy of the `ProductEto` class in the Ordering microservice if you don't want to introduce a common project dependency between the services. In this case, the `EventName` attribute becomes critical to map the `ProductEto` classes across two services (you should use the same event name). - -Once you define an ETO class, you should configure the ABP Framework to publish auto (create, update and delete) events for the `Product` entity, as explained in the previous section: - -````csharp -Configure(options => -{ - options.AutoEventSelectors.Add(); - options.EtoMappings.Add(); -}); -```` - -Finally, you should create a class in the Ordering microservice, that is derived from the `EntitySynchronizer` class: - -````csharp -public class ProductSynchronizer : EntitySynchronizer -{ - public ProductSynchronizer( - IObjectMapper objectMapper, - IRepository repository - ) : base(objectMapper, repository) - { - } -} -```` - -The main point of this class is it subscribes to the create, update and delete events of the source entity and updates the local entity in the database. It uses the [Object Mapper](Object-To-Object-Mapping.md) system to create or update the `OrderProduct` objects from the `ProductEto` objects. So, you should also configure the object mapper to make it properly work. Otherwise, you should manually perform the object mapping by overriding the `MapToEntityAsync(TSourceEntityEto)` and `MapToEntityAsync(TSourceEntityEto,TEntity)` methods in your `ProductSynchronizer` class. - -If your entity has a composite primary key (see the [Entities document](Entities.md)), then you should inherit from the `EntitySynchronizer` class (just don't use the `Guid` generic argument in the previous example) and implement `FindLocalEntityAsync` to find the entity in your local database using the `Repository`. - -`EntitySynchronizer` is compatible with the *Entity Versioning* system (see the [Entities document](Entities.md)). So, it works as expected even if the events are received as disordered. If the entity's version in your local database is newer than the entity in the received event, then the event is ignored. You should implement the `IHasEntityVersion` interface for the entity and ETO classes (for this example, you should implement for the `Product`, `ProductEto` and `OrderProduct` classes). - -If you want to ignore some type of change events, you can set `IgnoreEntityCreatedEvent`, `IgnoreEntityUpdatedEvent` and `IgnoreEntityDeletedEvent` in the constructor of your class. Example: - -````csharp -public class ProductSynchronizer - : EntitySynchronizer -{ - public ProductSynchronizer( - IObjectMapper objectMapper, - IRepository repository - ) : base(objectMapper, repository) - { - IgnoreEntityDeletedEvent = true; - } -} -```` - -> Notice that the `EntitySynchronizer` can only create/update the entities after you use it. If you have an existing system with existing data, you should manually copy the data for one time, because the `EntitySynchronizer` starts to work. - -## Transaction and Exception Handling - -Distributed event bus works in-process (since default implementation is `LocalDistributedEventBus`) unless you configure an actual provider (e.g. [Kafka](Distributed-Event-Bus-Kafka-Integration.md) or [RabbitMQ](Distributed-Event-Bus-RabbitMQ-Integration.md)). In-process event bus always executes event handlers in the same [unit of work](Unit-Of-Work.md) scope that you publishes the events in. That means, if an event handler throws an exception, then the related unit of work (the database transaction) is rolled back. In this way, your application logic and event handling logic becomes transactional (atomic) and consistent. If you want to ignore errors in an event handler, you must use a `try-catch` block in your handler and shouldn't re-throw the exception. - -When you switch to an actual distributed event bus provider (e.g. [Kafka](Distributed-Event-Bus-Kafka-Integration.md) or [RabbitMQ](Distributed-Event-Bus-RabbitMQ-Integration.md)), then the event handlers will be executed in different processes/applications as their purpose is to create distributed systems. In this case, the only way to implement transactional event publishing is to use the outbox/inbox patterns as explained in the *Outbox / Inbox for Transactional Events* section. - -If you don't configure outbox/inbox pattern or use the `LocalDistributedEventBus`, then events are published at the end of the unit of work by default, just before the unit of work is completed (that means throwing exception in an event handler still rollbacks the unit of work), even if you publish them in the middle of unit of work. If you want to immediately publish the event, you can set `onUnitOfWorkComplete` to `false` while using `IDistributedEventBus.PublishAsync` method. - -> Keeping the default behavior is recommended unless you don't have a unique requirement. `onUnitOfWorkComplete` option is not available when you publish events inside entity / aggregate root classes (see the *Publishing Events Inside Entity / Aggregate Root Classes* section). - -## Outbox / Inbox for Transactional Events - -The **[transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html)** is used to publishing distributed events within the **same transaction** that manipulates the application's database. When you enable outbox, distributed events are saved into the database inside the same transaction with your data changes, then sent to the actual message broker by a separate [background worker](Background-Workers.md) with a re-try system. In this way, it ensures the consistency between your database state and the published events. - -The **transactional inbox pattern**, on the other hand, saves incoming events into database first. Then (in a [background worker](Background-Workers.md)) executes the event handler in a transactional manner and removes the event from the inbox queue in the same transaction. It ensures that the event is only executed one time by keeping the processed messages for a while and discarding the duplicate events received from the message broker. - -Enabling the event outbox and inbox systems require a few manual steps for your application. Please apply the instructions in the following sections to make them running. - -> Outbox and Inbox can be separately enabled and configured, so you may only use one of them if you want. - -### Pre-requirements - -* The outbox/inbox system uses the distributed lock system to handle concurrency when you run multiple instances of your application/service. So, you should **configure the distributed lock system** with one of the providers as [explained in this document](Distributed-Locking.md). -* The outbox/inbox system supports [Entity Framework Core](Entity-Framework-Core.md) (EF Core) and [MongoDB](MongoDB.md) **database providers** out of the box. So, your applications should use one of these database providers. For other database providers, see the *Implementing a Custom Database Provider* section. - -> If you are using MongoDB, be sure that you enabled multi-document database transactions that was introduced in MongoDB version 4.0. See the *Transactions* section of the [MongoDB](MongoDB.md) document. - -### Enabling event outbox - -Enabling event outbox depends on your database provider. - -#### Enabling event outbox for Entity Framework Core - -Open your `DbContext` class, implement the `IHasEventOutbox` interface. You should end up by adding a `DbSet` property into your `DbContext` class: - -```csharp -public DbSet OutgoingEvents { get; set; } -``` - -Add the following lines inside the `OnModelCreating` method of your `DbContext` class: - -```csharp -builder.ConfigureEventOutbox(); -``` - -Use the standard `Add-Migration` and `Update-Database` commands to apply changes into your database. If you want to use the command-line terminal, run the following commands in the root directory of the database integration project: - -```bash -dotnet ef migrations add "Added_Event_Outbox" -dotnet ef database update -``` - -Finally, write the following configuration code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md) (replace `YourDbContext` with your own `DbContext` class): - -````csharp -Configure(options => -{ - options.Outboxes.Configure(config => - { - config.UseDbContext(); - }); -}); -```` - -#### Enabling event outbox for MongoDB - -Open your `DbContext` class, implement the `IHasEventOutbox` interface. You should end up by adding a `IMongoCollection` property into your `DbContext` class: - -```csharp -public IMongoCollection OutgoingEvents => Collection(); -``` - -Add the following lines inside the `CreateModel` method of your `DbContext` class: - -```csharp -modelBuilder.ConfigureEventOutbox(); -``` - -Finally, write the following configuration code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md) (replace `YourDbContext` with your own `DbContext` class): - -````csharp -Configure(options => -{ - options.Outboxes.Configure(config => - { - config.UseMongoDbContext(); - }); -}); -```` - -#### Distributed Locking for Outbox - -> **IMPORTANT**: Outbox sending service uses distributed locks to ensure only a single instance of your application consumes the outbox queue concurrently. Distributed locking key should be unique per database. The `config` object (in the `options.Outboxes.Configure(...)` method) has a `DatabaseName` property, which is used in the distributed lock key to ensure the uniqueness. `DatabaseName` is automatically set by the `UseDbContext` method, getting the database name from the `ConnectionStringName` attribute of the `YourDbContext` class. So, if you have multiple databases in your system, ensure that you use the same connection string name for the same database, but different connection string names for different databases. If you can't ensure that, you can manually set `config.DatabaseName` (after the `UseDbContext` line) to ensure that uniqueness. - -### Enabling event inbox - -Enabling event inbox depends on your database provider. - -#### Enabling event inbox for Entity Framework Core - -Open your `DbContext` class, implement the `IHasEventInbox` interface. You should end up by adding a `DbSet` property into your `DbContext` class: - -```csharp -public DbSet IncomingEvents { get; set; } -``` - -Add the following lines inside the `OnModelCreating` method of your `DbContext` class: - -```csharp -builder.ConfigureEventInbox(); -``` - -Use the standard `Add-Migration` and `Update-Database` commands to apply changes into your database. If you want to use the command-line terminal, run the following commands in the root directory of the database integration project: - -```bash -dotnet ef migrations add "Added_Event_Inbox" -dotnet ef database update -``` - -Finally, write the following configuration code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md) (replace `YourDbContext` with your own `DbContext` class): - -````csharp -Configure(options => -{ - options.Inboxes.Configure(config => - { - config.UseDbContext(); - }); -}); -```` - -#### Enabling event inbox for MongoDB - -Open your `DbContext` class, implement the `IHasEventInbox` interface. You should end up by adding a `IMongoCollection` property into your `DbContext` class: - -```csharp -public IMongoCollection IncomingEvents => Collection(); -``` - -Add the following lines inside the `CreateModel` method of your `DbContext` class: - -```csharp -modelBuilder.ConfigureEventInbox(); -``` - -Finally, write the following configuration code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md) (replace `YourDbContext` with your own `DbContext` class): - -````csharp -Configure(options => -{ - options.Inboxes.Configure(config => - { - config.UseMongoDbContext(); - }); -}); -```` - -#### Distributed Locking for Inbox - -> **IMPORTANT**: Inbox processing service uses distributed locks to ensure only a single instance of your application consumes the inbox queue concurrently. Distributed locking key should be unique per database. The `config` object (in the `options.Inboxes.Configure(...)` method) has a `DatabaseName` property, which is used in the distributed lock key to ensure the uniqueness. `DatabaseName` is automatically set by the `UseDbContext` method, getting the database name from the `ConnectionStringName` attribute of the `YourDbContext` class. So, if you have multiple databases in your system, ensure that you use the same connection string name for the same database, but different connection string names for different databases. If you can't ensure that, you can manually set `config.DatabaseName` (after the `UseDbContext` line) to ensure that uniqueness. - -### Additional Configuration - -> The default configuration will be enough for most cases. However, there are some options you may want to set for outbox and inbox. - -#### Outbox configuration - -Remember how outboxes are configured: - -````csharp -Configure(options => -{ - options.Outboxes.Configure(config => - { - // TODO: Set options - }); -}); -```` - -Here, the following properties are available on the `config` object: - -* `IsSendingEnabled` (default: `true`): You can set to `false` to disable sending outbox events to the actual event bus. If you disable this, events can still be added to outbox, but not sent. This can be helpful if you have multiple applications (or application instances) writing to outbox, but use one of them to send the events. -* `Selector`: A predicate to filter the event (ETO) types to be used for this configuration. Should return `true` to select the event. It selects all the events by default. This is especially useful if you want to ignore some ETO types from the outbox, or want to define named outbox configurations and group events within these configurations. See the *Named Configurations* section. -* `ImplementationType`: Type of the class that implements the database operations for the outbox. This is normally set when you call `UseDbContext` as shown before. See *Implementing a Custom Outbox/Inbox Database Provider* section for advanced usages. -* `DatabaseName`: Unique database name for the database that is used for this outbox configuration. See the **IMPORTANT** paragraph at the end of the *Enabling event outbox/inbox* sections. - -#### Inbox configuration - -Remember how inboxes are configured: - -````csharp -Configure(options => -{ - options.Inboxes.Configure(config => - { - // TODO: Set options - }); -}); -```` - -Here, the following properties are available on the `config` object: - -* `IsProcessingEnabled` (default: `true`): You can set to `false` to disable processing (handling) events in the inbox. If you disable this, events can still be received, but not executed. This can be helpful if you have multiple applications (or application instances), but use one of them to execute the event handlers. -* `EventSelector`: A predicate to filter the event (ETO) types to be used for this configuration. This is especially useful if you want to ignore some ETO types from the inbox, or want to define named inbox configurations and group events within these configurations. See the *Named Configurations* section. -* `HandlerSelector`: A predicate to filter the event handled types (classes implementing the `IDistributedEventHandler` interface) to be used for this configuration. This is especially useful if you want to ignore some event handler types from inbox processing, or want to define named inbox configurations and group event handlers within these configurations. See the *Named Configurations* section. -* `ImplementationType`: Type of the class that implements the database operations for the inbox. This is normally set when you call `UseDbContext` as shown before. See *Implementing a Custom Outbox/Inbox Database Provider* section for advanced usages. -* `DatabaseName`: Unique database name for the database that is used for this outbox configuration. See the **IMPORTANT** paragraph at the end of the *Enabling event inbox* section. - -#### AbpEventBusBoxesOptions - -`AbpEventBusBoxesOptions` can be used to fine-tune how inbox and outbox systems work. For most of the systems, using the defaults would be more than enough, but you can configure it to optimize your system when it is needed. - -Just like all the [options classes](Options.md), `AbpEventBusBoxesOptions` can be configured in the `ConfigureServices` method of your [module class](Module-Development-Basics.md) as shown in the following code block: - -````csharp -Configure(options => -{ - // TODO: configure the options -}); -```` - -`AbpEventBusBoxesOptions` has the following properties to be configured: - -* `BatchPublishOutboxEvents`: Can be used to enable or disable batch publishing events to the message broker. Batch publishing works if it is supported by the distributed event bus provider. If not supported, events are sent one by one as the fallback logic. Keep it as enabled since it has a great performance gain wherever possible. Default value is `true` (enabled). -* `PeriodTimeSpan`: The period of the inbox and outbox message processors to check if there is a new event in the database. Default value is 2 seconds (`TimeSpan.FromSeconds(2)`). -* `CleanOldEventTimeIntervalSpan`: The event inbox system periodically checks and deletes the old processed events from the inbox in the database. You can set this value to determine the check period. Default value is 6 hours (`TimeSpan.FromHours(6)`). -* `WaitTimeToDeleteProcessedInboxEvents`: Inbox events are not deleted from the database for a while even if they are successfully processed. This is for a system to prevent multiple process of the same event (if the event broker sends it twice). This configuration value determines the time to keep the processed events. Default value is 2 hours (`TimeSpan.FromHours(2)`). -* `InboxWaitingEventMaxCount`: The maximum number of events to query at once from the inbox in the database. Default value is 1000. -* `OutboxWaitingEventMaxCount`: The maximum number of events to query at once from the outbox in the database. Default value is 1000. -* `DistributedLockWaitDuration`: ABP uses [distributed locking](Distributed-Locking.md) to prevent concurrent access to the inbox and outbox messages in the database, when running multiple instance of the same application. If an instance of the application can not obtain the lock, it tries after a duration. This is the configuration of that duration. Default value is 15 seconds (`TimeSpan.FromSeconds(15)`). - -### Skipping Outbox - -`IDistributedEventBus.PublishAsync` method provides an optional parameter, `useOutbox`, which is set to `true` by default. If you bypass outbox and immediately publish an event, you can set it to `false` for a specific event publishing operation. - -### Advanced Topics - -#### Named Configurations - -> All the concepts explained in this section is also valid for inbox configurations. We will show examples only for outbox to keep the document shorter. - -See the following outbox configuration code: - -````csharp -Configure(options => -{ - options.Outboxes.Configure(config => - { - //TODO - }); -}); -```` - -This is equivalent of the following code: - -````csharp -Configure(options => -{ - options.Outboxes.Configure("Default", config => - { - //TODO - }); -}); -```` - -`Default` is this code indicates the configuration name. If you don't specify it (like in the previous code block), `Default` is used as the configuration name. - -That means you can define more than one configuration for outbox (also for inbox) with different names. ABP runs all the configured outboxes. - -Multiple outboxes can be needed if your application have more than one database and you want to run different outbox queues for different databases. In this case, you can use the `Selector` option to decide the events should be handled by an outbox. See the *Additional Configurations* section above. - -#### Implementing a Custom Outbox/Inbox Database Provider - -If your application or service is using a database provider other than [EF Core](Entity-Framework-Core.md) and [MongoDB](MongoDB.md), you should manually integrate outbox/inbox system with your database provider. - -> Outbox and Inbox table/data must be stored in the same database with your application's data (since we want to create a single database transaction that includes application's database operations and outbox/inbox table operations). Otherwise, you should care about distributed (multi-database) transaction support which is not provided by most of the vendors and may require additional configuration. - -ABP provides `IEventOutbox` and `IEventInbox` abstractions as extension point for the outbox/inbox system. You can create classes by implementing these interfaces and register them to [dependency injection](Dependency-Injection.md). - -Once you implement your custom event boxes, you can configure `AbpDistributedEventBusOptions` to use your event box classes: - -````csharp -Configure(options => -{ - options.Outboxes.Configure(config => - { - config.ImplementationType = typeof(MyOutbox); //Your Outbox class - }); - - options.Inboxes.Configure(config => - { - config.ImplementationType = typeof(MyInbox); //Your Inbox class - }); -}); -```` - -## See Also - -* [Local Event Bus](Local-Event-Bus.md) diff --git a/docs/en/Distributed-Locking.md b/docs/en/Distributed-Locking.md deleted file mode 100644 index 6474949dec..0000000000 --- a/docs/en/Distributed-Locking.md +++ /dev/null @@ -1,131 +0,0 @@ -# Distributed Locking -Distributed locking is a technique to manage many applications that try to access the same resource. The main purpose is to allow only one of many applications to access the same resource at the same time. Otherwise, accessing the same object from various applications may corrupt the value of the resources. - -> ABP's current distributed locking implementation is based on the [DistributedLock](https://github.com/madelson/DistributedLock) library. - -## Installation - -You can open a command-line terminal and type the following command to install the [Volo.Abp.DistributedLocking](https://www.nuget.org/packages/Volo.Abp.DistributedLocking) package into your project: - -````bash -abp add-package Volo.Abp.DistributedLocking -```` - -This package provides the necessary API to use the distributed locking system, however, you should configure a provider before using it. - -### Configuring a Provider - -The [DistributedLock](https://github.com/madelson/DistributedLock) library provides [various of implementations](https://github.com/madelson/DistributedLock#implementations) for the locking, like [Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md) and [ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md). - -For example, if you want to use the [Redis provider](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md), you should add [DistributedLock.Redis](https://www.nuget.org/packages/DistributedLock.Redis) NuGet package to your project, then add the following code into the `ConfigureServices` method of your ABP [module](Module-Development-Basics.md) class: - -````csharp -using Medallion.Threading; -using Medallion.Threading.Redis; - -namespace AbpDemo -{ - [DependsOn( - typeof(AbpDistributedLockingModule) - //If you have the other dependencies, you should do here - )] - public class MyModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - var configuration = context.Services.GetConfiguration(); - - context.Services.AddSingleton(sp => - { - var connection = ConnectionMultiplexer - .Connect(configuration["Redis:Configuration"]); - return new - RedisDistributedSynchronizationProvider(connection.GetDatabase()); - }); - } - } -} -```` - -This code gets the Redis connection string from the [configuration](Configuration.md), so you can add the following lines to your `appsettings.json` file: - -````json -"Redis": { - "Configuration": "127.0.0.1" -} -```` - -## Usage - -There are two ways to use the distributed locking API: ABP's `IAbpDistributedLock` abstraction and [DistributedLock](https://github.com/madelson/DistributedLock) library's API. - -### Using the IAbpDistributedLock Service - -`IAbpDistributedLock` is a simple service provided by the ABP framework for simple usage of distributed locking. - -**Example: Using the `IAbpDistributedLock.TryAcquireAsync` method** - -````csharp -using Volo.Abp.DistributedLocking; - -namespace AbpDemo -{ - public class MyService : ITransientDependency - { - private readonly IAbpDistributedLock _distributedLock; - public MyService(IAbpDistributedLock distributedLock) - { - _distributedLock = distributedLock; - } - - public async Task MyMethodAsync() - { - await using (var handle = - await _distributedLock.TryAcquireAsync("MyLockName")) - { - if (handle != null) - { - // your code that access the shared resource - } - } - } - } -} -```` - -`TryAcquireAsync` may not acquire the lock. It returns `null` if the lock could not be acquired. In this case, you shouldn't access the resource. If the handle is not `null`, it means that you've obtained the lock and can safely access the resource. - -`TryAcquireAsync` method gets the following parameters: - -* `name` (`string`, required): Unique name of your lock. Different named locks are used to access different resources. -* `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application. -* `cancellationToken`: A cancellation token that can be triggered later to cancel the operation. - -### Configuration - -#### AbpDistributedLockOptions - -`AbpDistributedLockOptions` is the main options class to configure the distributed locking. - -**Example: Set the distributed lock key prefix for the application** - -```csharp -Configure(options => -{ - options.KeyPrefix = "MyApp1"; -}); -``` - -> Write that code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md). - -##### Available Options - -* KeyPrefix (string, default: null): Specify the lock name prefix. - -### Using DistributedLock Library's API - -ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details. - -## The Volo.Abp.DistributedLocking.Abstractions Package - -If you are building a reusable library or an application module, then you may not want to bring an additional dependency to your module for simple applications that run as a single instance. In this case, your library can depend on the [Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions) package which defines the `IAbpDistributedLock` service and implements it as in-process (not distributed actually). In this way, your library can run properly (without a distributed lock provider dependency) in an application that runs as a single instance. If the application is deployed to a [clustered environment](Deployment/Clustered-Environment.md), then the application developer should install a real distributed provider as explained in the *Installation* section. diff --git a/docs/en/Domain-Driven-Design-Implementation-Guide.md b/docs/en/Domain-Driven-Design-Implementation-Guide.md deleted file mode 100644 index dc28845396..0000000000 --- a/docs/en/Domain-Driven-Design-Implementation-Guide.md +++ /dev/null @@ -1,5 +0,0 @@ -# Implementing Domain Driven Design - -This document has been packaged as a **free e-book** and can be downloaded from the following URL: - -https://abp.io/books/implementing-domain-driven-design diff --git a/docs/en/Domain-Driven-Design.md b/docs/en/Domain-Driven-Design.md deleted file mode 100644 index 03a6232ba3..0000000000 --- a/docs/en/Domain-Driven-Design.md +++ /dev/null @@ -1,37 +0,0 @@ -# Domain Driven Design - -## What is DDD? - -ABP framework provides an **infrastructure** to make **Domain Driven Design** based development easier to implement. DDD is [defined in the Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design) as below: - -> **Domain-driven design** (**DDD**) is an approach to software development for complex needs by connecting the implementation to an evolving model. The premise of domain-driven design is the following: -> -> - Placing the project's primary focus on the core domain and domain logic; -> - Basing complex designs on a model of the domain; -> - Initiating a creative collaboration between technical and domain experts to iteratively refine a conceptual model that addresses particular domain problems. - -## Layers & Building Blocks - -ABP follows DDD principles and patterns to achieve a layered application model which consists of four fundamental layers: - -- **Presentation Layer**: Provides an interface to the user. Uses the *Application Layer* to achieve user interactions. -- **Application Layer**: Mediates between the Presentation and Domain Layers. Orchestrates business objects to perform specific application tasks. Implements use cases as the application logic. -- **Domain Layer**: Includes business objects and the core (domain) business rules. This is the heart of the application. -- **Infrastructure Layer**: Provides generic technical capabilities that support higher layers mostly using 3rd-party libraries. - -DDD mostly interest in the **Domain** and the **Application** layers, rather than the Infrastructure and the Presentation layers. The following documents explains the **infrastructure** provided by the ABP Framework to implement **Building Blocks** of the DDD: - -* **Domain Layer** - * [Entities & Aggregate Roots](Entities.md) - * [Repositories](Repositories.md) - * [Domain Services](Domain-Services.md) - * [Value Objects](Value-Objects.md) - * [Specifications](Specifications.md) -* **Application Layer** - * [Application Services](Application-Services.md) - * [Data Transfer Objects (DTOs)](Data-Transfer-Objects.md) - * [Unit of Work](Unit-Of-Work.md) - -## Free E-Book: Implementing DDD - -See the [Implementing Domain Driven Design book](https://abp.io/books/implementing-domain-driven-design) as a **complete reference**. This book explains the Domain Driven Design and introduces explicit **rules and examples** to give a deep understanding of the **implementation details**. \ No newline at end of file diff --git a/docs/en/Domain-Services.md b/docs/en/Domain-Services.md deleted file mode 100644 index 877ccfe27e..0000000000 --- a/docs/en/Domain-Services.md +++ /dev/null @@ -1,139 +0,0 @@ -# Domain Services - -## Introduction - -In a [Domain Driven Design](Domain-Driven-Design.md) (DDD) solution, the core business logic is generally implemented in aggregates ([entities](Entities.md)) and the Domain Services. Creating a Domain Service is especially needed when; - -* You implement a core domain logic that depends on some services (like repositories or other external services). -* The logic you need to implement is related to more than one aggregate/entity, so it doesn't properly fit in any of the aggregates. - -## ABP Domain Service Infrastructure - -Domain Services are simple, stateless classes. While you don't have to derive from any service or interface, ABP Framework provides some useful base classes and conventions. - -### DomainService & IDomainService - -Either derive a Domain Service from the `DomainService` base class or directly implement the `IDomainService` interface. - -**Example: Create a Domain Service deriving from the `DomainService` base class.** - -````csharp -using Volo.Abp.Domain.Services; - -namespace MyProject.Issues -{ - public class IssueManager : DomainService - { - - } -} -```` - -When you do that; - -* ABP Framework automatically registers the class to the Dependency Injection system with a Transient lifetime. -* You can directly use some common services as base properties, without needing to manually inject (e.g. [ILogger](Logging.md) and [IGuidGenerator](Guid-Generation.md)). - -> It is suggested to name a Domain Service with a `Manager` or `Service` suffix. We typically use the `Manager` suffix as used in the sample above. - -**Example: Implement the domain logic of assigning an Issue to a User** - -````csharp -public class IssueManager : DomainService -{ - private readonly IRepository _issueRepository; - - public IssueManager(IRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task AssignAsync(Issue issue, AppUser user) - { - var currentIssueCount = await _issueRepository - .CountAsync(i => i.AssignedUserId == user.Id); - - //Implementing a core business validation - if (currentIssueCount >= 3) - { - throw new IssueAssignmentException(user.UserName); - } - - issue.AssignedUserId = user.Id; - } -} -```` - -Issue is an [aggregate root](Entities.md) defined as shown below: - -````csharp -public class Issue : AggregateRoot -{ - public Guid? AssignedUserId { get; internal set; } - - //... -} -```` - -* Making the setter `internal` ensures that it can not directly set in the upper layers and forces to always use the `IssueManager` to assign an `Issue` to a `User`. - -### Using a Domain Service - -A Domain Service is typically used in an [application service](Application-Services.md). - -**Example: Use the `IssueManager` to assign an Issue to a User** - -````csharp -using System; -using System.Threading.Tasks; -using MyProject.Users; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace MyProject.Issues -{ - public class IssueAppService : ApplicationService, IIssueAppService - { - private readonly IssueManager _issueManager; - private readonly IRepository _userRepository; - private readonly IRepository _issueRepository; - - public IssueAppService( - IssueManager issueManager, - IRepository userRepository, - IRepository issueRepository) - { - _issueManager = issueManager; - _userRepository = userRepository; - _issueRepository = issueRepository; - } - - public async Task AssignAsync(Guid id, Guid userId) - { - var issue = await _issueRepository.GetAsync(id); - var user = await _userRepository.GetAsync(userId); - - await _issueManager.AssignAsync(issue, user); - await _issueRepository.UpdateAsync(issue); - } - } -} -```` - -Since the `IssueAppService` is in the Application Layer, it can't directly assign an issue to a user. So, it uses the `IssueManager`. - -## Application Services vs Domain Services - -While both of [Application Services](Application-Services.md) and Domain Services implement the business rules, there are fundamental logical and formal differences; - -* Application Services implement the **use cases** of the application (user interactions in a typical web application), while Domain Services implement the **core, use case independent domain logic**. -* Application Services get/return [Data Transfer Objects](Data-Transfer-Objects.md), Domain Service methods typically get and return the **domain objects** ([entities](Entities.md), [value objects](Value-Objects.md)). -* Domain services are typically used by the Application Services or other Domain Services, while Application Services are used by the Presentation Layer or Client Applications. - -## Lifetime - -Lifetime of Domain Services are [transient](https://docs.abp.io/en/abp/latest/Dependency-Injection) and they are automatically registered to the dependency injection system. - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/domain-services) \ No newline at end of file diff --git a/docs/en/Dynamic-Claims.md b/docs/en/Dynamic-Claims.md deleted file mode 100644 index 5985be9510..0000000000 --- a/docs/en/Dynamic-Claims.md +++ /dev/null @@ -1,92 +0,0 @@ -# Dynamic Claims - -When a client authenticates and obtains an access token or an authentication cookie, the claims in that token or cookie are not changed unless it re-authenticates. For most of the claims, that may not be a problem since claims are not frequently changing values. However, some claims may be changed and these changes should be reflected to the current session immediately. For example, we can revoke a role from a user and that should be immediately effective, otherwise user will continue to use that role's permissions until re-login to the application. - -ABP's dynamic claims feature is used to automatically and dynamically override the configured claim values in the client's authentication token/cookie by the latest values of these claims. - -## How to Use - -This feature is disabled by default. You should enable it for your application and use the Dynamic Claims middleware. - -> **Beginning from the v8.0, all the [startup templates](Startup-Templates/Index.md) are pre-configured and the dynamic claims feature is enabled by default. So, if you have created a solution with v8.0 and above, you don't need to make any configuration. Follow the instructions only if you've upgraded from a version lower than 8.0.** - -### Enabling the Dynamic Claims - -You can enable it by the following code: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.Configure(options => - { - options.IsDynamicClaimsEnabled = true; - }); -} -```` - -This is typically done on the authentication server. In a monolith application, you will typically have a single application, so you can configure it. If you are using the tiered solution structure (where the UI part is hosted in a separate application) you will need to also set the `RemoteRefreshUrl` to the Authentication Server's URL in the UI application. Example: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.Configure(options => - { - options.IsDynamicClaimsEnabled = true; - options.RemoteRefreshUrl = configuration["AuthServerUrl"] + options.RemoteRefreshUrl; - }); -} -```` - -> The `RemoteRefreshUrl` is already configured inside methods `AddAbpOpenIdConnect` and `AddAbpJwtBearer`. - - -### The Dynamic Claims Middleware - -Add the `DynamicClaims` middleware to all the applications that performs authentication (including the authentication server): - -````csharp -public override void OnApplicationInitialization( - ApplicationInitializationContext context) -{ - //... - app.UseDynamicClaims(); // Add this line before UseAuthorization. - app.UseAuthorization(); - //... -} -```` - -## How It Works - -The `DynamicClaims` middleware will use `IAbpClaimsPrincipalFactory` to dynamically generate claims for the current user(`HttpContext.User`) in each request. - -There are three pre-built implementations of `IAbpDynamicClaimsPrincipalContributor` for different scenarios: - -* `IdentityDynamicClaimsPrincipalContributor`: Provided by the [Identity module](Modules/Identity.md) and generates and overrides the actual dynamic claims, and writes to the distributed cache. Typically works in the authentication server in a distributed system. -* `RemoteDynamicClaimsPrincipalContributor`: For distributed scenarios, this implementation works in the UI application. It tries to get dynamic claim values in the distributed cache. If not found in the distributed cache, it makes an HTTP call to the authentication server and requests filling it by the authentication server. `AbpClaimsPrincipalFactoryOptions.RemoteRefreshUrl` should be properly configure to make it running. -* `WebRemoteDynamicClaimsPrincipalContributor`: Similar to the `RemoteDynamicClaimsPrincipalContributor` but works in the microservice applications. - -### IAbpDynamicClaimsPrincipalContributor - -If you want to add your own dynamic claims contributor, you can create a class that implement the `IAbpDynamicClaimsPrincipalContributor` interface (and register it to the [dependency injection](Dependency-Injection.md) system. ABP Framework will call the `ContributeAsync` method to get the claims. It better to use a kind of cache to improve the performance since that is a frequently executed method (in every HTTP request). - -## AbpClaimsPrincipalFactoryOptions - -`AbpClaimsPrincipalFactoryOptions` is the main options class to configure the behavior of the dynamic claims system. It has the following properties: - -* `IsDynamicClaimsEnabled`: Enable or disable the dynamic claims feature. -* `RemoteRefreshUrl`: The `url ` of the Auth Server to refresh the cache. It will be used by the `RemoteDynamicClaimsPrincipalContributor`. The default value is `/api/account/dynamic-claims/refresh ` and you should provide the full URL in the authentication server, like `http://my-account-server/api/account/dynamic-claims/refresh `. -* `DynamicClaims`: A list of dynamic claim types. Only the claims in that list will be overridden by the dynamic claims system. -* `ClaimsMap`: A dictionary to map the claim types. This is used when the claim types are different between the Auth Server and the client. Already set up for common claim types by default. - -## WebRemoteDynamicClaimsPrincipalContributorOptions - -`WebRemoteDynamicClaimsPrincipalContributorOptions` is the options class to configure the behavior of the `WebRemoteDynamicClaimsPrincipalContributor`. It has the following properties: - -* `IsEnabled`: Enable or disable the `WebRemoteDynamicClaimsPrincipalContributor`. `false` by default. -* `AuthenticationScheme`: The authentication scheme to authenticate the HTTP call to the authentication server. - -## See Also - -* [Authorization](Authorization.md) -* [Claims-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims) -* [Mapping, customizing, and transforming claims in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims) diff --git a/docs/en/Dynamic-Proxying-Interceptors.md b/docs/en/Dynamic-Proxying-Interceptors.md deleted file mode 100644 index e8b57b80fb..0000000000 --- a/docs/en/Dynamic-Proxying-Interceptors.md +++ /dev/null @@ -1,7 +0,0 @@ -## Dynamic Proxying / Interceptors - -TODO - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/interception) \ No newline at end of file diff --git a/docs/en/Emailing.md b/docs/en/Emailing.md deleted file mode 100644 index 48635b2565..0000000000 --- a/docs/en/Emailing.md +++ /dev/null @@ -1,260 +0,0 @@ -# Email Sending - -ABP Framework provides various services, settings and integrations for sending emails; - -* Provides `IEmailSender` service that is used to send emails. -* Defines [settings](Settings.md) to configure email sending. -* Integrates to the [background job system](Background-Jobs.md) to send emails via background jobs. -* Provides [MailKit integration](MailKit.md) package. - -## Installation - -> This package is already installed if you are using the [application startup template](Startup-Templates/Application.md). - -It is suggested to use the [ABP CLI](CLI.md) to install this package. Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.Emailing -```` - -If you haven't done it yet, you first need to install the ABP CLI. For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Emailing). - -## Sending Emails - -### IEmailSender - -[Inject](Dependency-Injection.md) the `IEmailSender` into any service and use the `SendAsync` method to send emails. - -**Example** - -````csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing; - -namespace MyProject -{ - public class MyService : ITransientDependency - { - private readonly IEmailSender _emailSender; - - public MyService(IEmailSender emailSender) - { - _emailSender = emailSender; - } - - public async Task DoItAsync() - { - await _emailSender.SendAsync( - "target@domain.com", // target email address - "Email subject", // subject - "This is email body..." // email body - ); - } - } -} -```` - -`SendAsync` method has overloads to supply more parameters like; - -* **from**: You can set this as the first argument to set a sender email address. If not provided, the default sender address is used (see the email settings below). -* **to**: You can set the target email address. -* **subject**: You can set the email subject. -* **body**: You can set the email body. -* **isBodyHtml**: Indicates whether the email body may contain HTML tags. **Default: true**. -* **additionalEmailSendingArgs**: This parameter is used to pass additional arguments to the `IEmailSender` implementation. Include: CC(Carbon copy), a list of `EmailAttachment` and an extra properties. - -> `IEmailSender` is the suggested way to send emails, since it makes your code provider independent. - -#### MailMessage - -In addition to primitive parameters, you can pass a **standard `MailMessage` object** ([see](https://docs.microsoft.com/en-us/dotnet/api/system.net.mail.mailmessage)) to the `SendAsync` method to set more options, like adding attachments. - -### ISmtpEmailSender - -Sending emails is implemented by the standard `SmtpClient` class ([see](https://docs.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient)) by default. The implementation class is the `SmtpEmailSender`. This class also expose the `ISmtpEmailSender` service (in addition to the `IEmailSender`). - -Most of the time you want to directly use the `IEmailSender` to make your code provider independent. However, if you want to create an `SmtpClient` object with the same email settings, you can inject the `ISmtpEmailSender` and use its `BuildClientAsync` method to obtain a `SmtpClient` object and send the email yourself. - -## Queueing Emails / Background Jobs - -`IEmailSender` has a `QueueAsync` method that can be used to add emails to the background job queue to send them in a background thread. In this way, you don't take time of the user by waiting to send the email. `QueueAsync` method gets the same arguments with the `SendAsync` method. - -Queueing emails tolerates errors since the background job system has re-try mechanism to overcome temporary network/server problems. - -See the [background jobs document](Background-Jobs.md) for more about the background job system. - -## Email Settings - -Email sending uses the [setting system](Settings.md) to define settings and get the values of these settings on the runtime. `Volo.Abp.Emailing.EmailSettingNames` defines constants for the setting names, just listed below: - -* **Abp.Mailing.DefaultFromAddress**: Used as the sender's email address when you don't specify a sender when sending emails (just like in the example above). -* **Abp.Mailing.DefaultFromDisplayName**: Used as the sender's display name when you don't specify a sender when sending emails (just like in the example above). -* **Abp.Mailing.Smtp.Host**: The IP/Domain of the SMTP server (default: 127.0.0.1). -* **Abp.Mailing.Smtp.Port**: The Port of the SMTP server (default: 25). -* **Abp.Mailing.Smtp.UserName**: Username, if the SMTP server requires authentication. -* **Abp.Mailing.Smtp.Password**: Password, if the SMTP server requires authentication. **This value is encrypted **(see the section below). -* **Abp.Mailing.Smtp.Domain**: Domain for the username, if the SMTP server requires authentication. -* **Abp.Mailing.Smtp.EnableSsl**: A value that indicates if the SMTP server uses SSL or not ("true" or "false". Default: "false"). -* **Abp.Mailing.Smtp.UseDefaultCredentials**: If true, uses default credentials instead of the provided username and password ("true" or "false". Default: "true"). - -Email settings can be managed from the *Settings Page* of the [Setting Management](Modules/Setting-Management.md) module: - -![email-settings](images/email-settings.png) - -> Setting Management module is already installed if you've created your solution from the ABP Startup template. - -If you don't use the Setting Management module, you can simply define the settings inside your `appsettings.json` file: - -````json -"Settings": { - "Abp.Mailing.Smtp.Host": "127.0.0.1", - "Abp.Mailing.Smtp.Port": "25", - "Abp.Mailing.Smtp.UserName": "", - "Abp.Mailing.Smtp.Password": "", - "Abp.Mailing.Smtp.Domain": "", - "Abp.Mailing.Smtp.EnableSsl": "false", - "Abp.Mailing.Smtp.UseDefaultCredentials": "true", - "Abp.Mailing.DefaultFromAddress": "noreply@abp.io", - "Abp.Mailing.DefaultFromDisplayName": "ABP application" -} -```` - -You can set/change these settings programmatically using the `ISettingManager` and store values in a database. See the [setting system document](Settings.md) to understand the setting system better. - -### Encrypt the SMTP Password - -*Abp.Mailing.Smtp.Password* must be an **encrypted** value. If you use the `ISettingManager` to set the password, you don't have to worry. It internally encrypts the values on set and decrypts on get. - -If you use the `appsettings.json` to store the password, you should manually inject the `ISettingEncryptionService` and use its `Encrypt` method to obtain an encrypted value. This can be done by creating a simple code in your application. Then you can delete the code. As better, you can create a UI in your application to configure the email settings. In this case, you can directly use the `ISettingManager` without worrying the encryption. - -### ISmtpEmailSenderConfiguration - -If you don't want to use the setting system to store the email sending configuration, you can replace the `ISmtpEmailSenderConfiguration` service with your own implementation to get the configuration from any other source. `ISmtpEmailSenderConfiguration` is implemented by the `SmtpEmailSenderConfiguration` by default, which gets the configuration from the setting system as explained above. - -## Text Template Integration - -ABP Framework provides a strong and flexible [text templating system](Text-Templating.md). You can use the text templating system to create dynamic email contents. Inject the `ITemplateRenderer` and use the `RenderAsync` to render a template. Then use the result as the email body. - -While you can define and use your own text templates, email sending system provides two simple built-in text templates. - -**Example: Use the standard and simple message template to send emails** - -````csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.TextTemplating; - -namespace Acme.BookStore.Web -{ - public class MyService : ITransientDependency - { - private readonly IEmailSender _emailSender; - private readonly ITemplateRenderer _templateRenderer; - - public MyService( - IEmailSender emailSender, - ITemplateRenderer templateRenderer) - { - _emailSender = emailSender; - _templateRenderer = templateRenderer; - } - - public async Task DoItAsync() - { - var body = await _templateRenderer.RenderAsync( - StandardEmailTemplates.Message, - new - { - message = "This is email body..." - } - ); - - await _emailSender.SendAsync( - "target-address@domain.com", - "Email subject", - body - ); - } - } -} -```` - -The resulting email body will be shown below: - -````html - - - - - - - This is email body... - - -```` - -Emailing system defines the built-in text templates with the given names: - -"**Abp.StandardEmailTemplates.Message**" is simplest template that has a text message: - -````html -{%{{{model.message}}}%} -```` - -This template uses the "Abp.StandardEmailTemplates.Layout" as its layout. - -"**Abp.StandardEmailTemplates.Layout**" is a simple template to provide an HTML document layout: - -````html - - - - - - - {%{{{content}}}%} - - -```` - -The final rendered message was shown above. - -> These template names are contants defined in the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` class. - -### Overriding/Replacing the Standard Templates - -You typically want to replace the standard templates with your own ones, so you can prepare a branded email messages. To do that, you can use the power of the [virtual file system](Virtual-File-System.md) (VFS) or replace them in your own template definition provider. - -Pathes of the templates in the virtual file system are shown below: - -* `/Volo/Abp/Emailing/Templates/Layout.tpl` -* `/Volo/Abp/Emailing/Templates/Message.tpl` - -If you add files to the same location in the virtual file system, your files will override them. - -Templates are inline localized, that means you can take the power of the [localization system](Localization.md) to make your templates multi-cultural. - -See the [text templating system](Text-Templating.md) document for details. - -> Notice that you can define and use your own templates for your application, rather than using the standard simple templates. These standard templates are mostly for reusable modules where they don't define their own templates but rely on the built-in ones. This makes easy to customize emails sent by the used modules, by just overriding the standard email layout template. - -## NullEmailSender - -`NullEmailSender` is a built-in class that implements the `IEmailSender`, but writes email contents to the [standard log system](Logging.md), rathen than actually sending the emails. - -This class can be useful especially in development time where you generally don't want to send real emails. The [application startup template](Startup-Templates/Application.md) already uses this class in the **DEBUG mode** with the following configuration in the domain layer: - -````csharp -#if DEBUG - context.Services.Replace(ServiceDescriptor.Singleton()); -#endif -```` - -So, don't confuse if you don't receive emails on DEBUG mode. Emails will be sent as expected on production (RELEASE mode). Remove these lines if you want to send real emails on DEBUG too. - -## See Also - -* [MailKit integration for sending emails](MailKit.md) diff --git a/docs/en/Entities.md b/docs/en/Entities.md deleted file mode 100644 index b5498c4adb..0000000000 --- a/docs/en/Entities.md +++ /dev/null @@ -1,441 +0,0 @@ -# Entities - -Entities are one of the core concepts of DDD (Domain Driven Design). Eric Evans describes it as "*An object that is not fundamentally defined by its attributes, but rather by a thread of continuity and identity*". - -An entity is generally mapped to a table in a relational database. - -## Entity Class - -Entities are derived from the `Entity` class as shown below: - -```C# -public class Book : Entity -{ - public string Name { get; set; } - - public float Price { get; set; } -} -``` - -> If you do not want to derive your entity from the base `Entity` class, you can directly implement `IEntity` interface. - -`Entity` class just defines an `Id` property with the given primary **key type**, which is `Guid` in the example above. It can be other types like `string`, `int`, `long`, or whatever you need. - -### Entities with GUID Keys - -If your entity's Id type is `Guid`, there are some good practices to implement: - -* Create a constructor that gets the Id as a parameter and passes to the base class. - * If you don't set a GUID Id, **ABP Framework sets it on save**, but it is good to have a valid Id on the entity even before saving it to the database. -* If you create an entity with a constructor that takes parameters, also create a `private` or `protected` empty constructor. This is used while your database provider reads your entity from the database (on deserialization). -* Don't use the `Guid.NewGuid()` to set the Id! **Use [the `IGuidGenerator` service](Guid-Generation.md)** while passing the Id from the code that creates the entity. `IGuidGenerator` optimized to generate sequential GUIDs, which is critical for clustered indexes in the relational databases. - -An example entity: - -````csharp -public class Book : Entity -{ - public string Name { get; set; } - - public float Price { get; set; } - - protected Book() - { - - } - - public Book(Guid id) - : base(id) - { - - } -} -```` - -Example usage in an [application service](Application-Services.md): - -````csharp -public class BookAppService : ApplicationService, IBookAppService -{ - private readonly IRepository _bookRepository; - - public BookAppService(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task CreateAsync(CreateBookDto input) - { - await _bookRepository.InsertAsync( - new Book(GuidGenerator.Create()) - { - Name = input.Name, - Price = input.Price - } - ); - } -} -```` - -* `BookAppService` injects the default [repository](Repositories.md) for the book entity and uses its `InsertAsync` method to insert a `Book` to the database. -* `GuidGenerator` is type of `IGuidGenerator` which is a property defined in the `ApplicationService` base class. ABP defines such frequently used base properties as pre-injected for you, so you don't need to manually [inject](Dependency-Injection.md) them. -* If you want to follow the DDD best practices, see the *Aggregate Example* section below. - -### Entities with Composite Keys - -Some entities may need to have **composite keys**. In that case, you can derive your entity from the non-generic `Entity` class. Example: - -````C# -public class UserRole : Entity -{ - public Guid UserId { get; set; } - - public Guid RoleId { get; set; } - - public DateTime CreationTime { get; set; } - - public UserRole() - { - - } - - public override object[] GetKeys() - { - return new object[] { UserId, RoleId }; - } -} -```` - -For the example above, the composite key is composed of `UserId` and `RoleId`. For a relational database, it is the composite primary key of the related table. Entities with composite keys should implement the `GetKeys()` method as shown above. - -> Notice that you also need to define keys of the entity in your **object-relational mapping** (ORM) configuration. See the [Entity Framework Core](Entity-Framework-Core.md) integration document for example. - -> Also note that Entities with Composite Primary Keys cannot utilize the `IRepository` interface since it requires a single Id property. However, you can always use `IRepository`. See [repositories documentation](Repositories.md) for more. - -### EntityEquals - -`Entity.EntityEquals(...)` method is used to check if two Entity Objects are equals. - -Example: - -```csharp -Book book1 = ... -Book book2 = ... - -if (book1.EntityEquals(book2)) //Check equality -{ - ... -} -``` - -## AggregateRoot Class - -"*Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it's useful to treat the order (together with its line items) as a single aggregate.*" (see the [full description](http://martinfowler.com/bliki/DDD_Aggregate.html)) - -`AggregateRoot` class extends the `Entity` class. So, it also has an `Id` property by default. - -> Notice that ABP creates default repositories only for aggregate roots by default. However, it's possible to include all entities. See the [repositories documentation](Repositories.md) for more. - -ABP does not force you to use aggregate roots, you can in fact use the `Entity` class as defined before. However, if you want to implement the [Domain Driven Design](Domain-Driven-Design.md) and want to create aggregate root classes, there are some best practices you may want to consider: - -* An aggregate root is responsible for preserving its own integrity. This is also true for all entities, but the aggregate root has responsibility for its sub-entities too. So, the aggregate root must always be in a valid state. -* An aggregate root can be referenced by its `Id`. Do not reference it by its navigation property. -* An aggregate root is treated as a single unit. It's retrieved and updated as a single unit. It's generally considered as a transaction boundary. -* Work with sub-entities over the aggregate root- do not modify them independently. - -See the [entity design best practice guide](Best-Practices/Entities.md) if you want to implement DDD in your application. - -### Aggregate Example - -This is a full sample of an aggregate root with a related sub-entity collection: - -````C# -public class Order : AggregateRoot -{ - public virtual string ReferenceNo { get; protected set; } - - public virtual int TotalItemCount { get; protected set; } - - public virtual DateTime CreationTime { get; protected set; } - - public virtual List OrderLines { get; protected set; } - - protected Order() - { - - } - - public Order(Guid id, string referenceNo) - { - Check.NotNull(referenceNo, nameof(referenceNo)); - - Id = id; - ReferenceNo = referenceNo; - - OrderLines = new List(); - } - - public void AddProduct(Guid productId, int count) - { - if (count <= 0) - { - throw new ArgumentException( - "You can not add zero or negative count of products!", - nameof(count) - ); - } - - var existingLine = OrderLines.FirstOrDefault(ol => ol.ProductId == productId); - - if (existingLine == null) - { - OrderLines.Add(new OrderLine(this.Id, productId, count)); - } - else - { - existingLine.ChangeCount(existingLine.Count + count); - } - - TotalItemCount += count; - } -} - -public class OrderLine : Entity -{ - public virtual Guid OrderId { get; protected set; } - - public virtual Guid ProductId { get; protected set; } - - public virtual int Count { get; protected set; } - - protected OrderLine() - { - - } - - internal OrderLine(Guid orderId, Guid productId, int count) - { - OrderId = orderId; - ProductId = productId; - Count = count; - } - - internal void ChangeCount(int newCount) - { - Count = newCount; - } - - public override object[] GetKeys() - { - return new Object[] {OrderId, ProductId}; - } -} -```` - -> If you do not want to derive your aggregate root from the base `AggregateRoot` class, you can directly implement the `IAggregateRoot` interface. - -`Order` is an **aggregate root** with `Guid` type `Id` property. It has a collection of `OrderLine` entities. `OrderLine` is another entity with a composite primary key (`OrderId` and ` ProductId`). - -While this example may not implement all the best practices of an aggregate root, it still follows some good practices: - -* `Order` has a public constructor that takes **minimal requirements** to construct an `Order` instance. So, it's not possible to create an order without an id and reference number. The **protected/private** constructor is only necessary to **deserialize** the object while reading from a data source. -* `OrderLine` constructor is internal, so it is only allowed to be created by the domain layer. It's used inside of the `Order.AddProduct` method. -* `Order.AddProduct` implements the business rule to add a product to an order. -* All properties have `protected` setters. This is to prevent the entity from arbitrary changes from outside of the entity. For example, it would be dangerous to set `TotalItemCount` without adding a new product to the order. Its value is maintained by the `AddProduct` method. - -ABP Framework does not force you to apply any DDD rule or patterns. However, it tries to make it possible and easier when you do want to apply them. The documentation also follows the same principle. - -### Aggregate Roots with Composite Keys - -While it's not common (and not suggested) for aggregate roots, it is in fact possible to define composite keys in the same way as defined for the mentioned entities above. Use non-generic `AggregateRoot` base class in that case. - -### BasicAggregateRoot Class - -`AggregateRoot` class implements the `IHasExtraProperties` and `IHasConcurrencyStamp` interfaces which brings two properties to the derived class. `IHasExtraProperties` makes the entity extensible (see the *Extra Properties* section below) and `IHasConcurrencyStamp` adds a `ConcurrencyStamp` property that is managed by the ABP Framework to implement the [optimistic concurrency](https://docs.microsoft.com/en-us/ef/core/saving/concurrency). In most cases, these are wanted features for aggregate roots. - -However, if you don't need these features, you can inherit from the `BasicAggregateRoot` (or `BasicAggregateRoot`) for your aggregate root. - -## Base Classes & Interfaces for Audit Properties - -There are some properties like `CreationTime`, `CreatorId`, `LastModificationTime`... which are very common in all applications. ABP Framework provides some interfaces and base classes to **standardize** these properties and also **sets their values automatically**. - -### Auditing Interfaces - -There are a lot of auditing interfaces, so you can implement the one that you need. - -> While you can manually implement these interfaces, you can use **the base classes** defined in the next section to simplify it. - -* `IHasCreationTime` defines the following properties: - * `CreationTime` -* `IMayHaveCreator` defines the following properties: - * `CreatorId` -* `ICreationAuditedObject` inherits from the `IHasCreationTime` and the `IMayHaveCreator`, so it defines the following properties: - * `CreationTime` - * `CreatorId` -* `IHasModificationTime` defines the following properties: - * `LastModificationTime` -* `IModificationAuditedObject` extends the `IHasModificationTime` and adds the `LastModifierId` property. So, it defines the following properties: - * `LastModificationTime` - * `LastModifierId` -* `IAuditedObject` extends the `ICreationAuditedObject` and the `IModificationAuditedObject`, so it defines the following properties: - * `CreationTime` - * `CreatorId` - * `LastModificationTime` - * `LastModifierId` -* `ISoftDelete` (see the [data filtering document](Data-Filtering.md)) defines the following properties: - * `IsDeleted` -* `IHasDeletionTime` extends the `ISoftDelete` and adds the `DeletionTime` property. So, it defines the following properties: - * `IsDeleted` - * `DeletionTime` -* `IDeletionAuditedObject` extends the `IHasDeletionTime` and adds the `DeleterId` property. So, it defines the following properties: - * `IsDeleted` - * `DeletionTime` - * `DeleterId` -* `IFullAuditedObject` inherits from the `IAuditedObject` and the `IDeletionAuditedObject`, so it defines the following properties: - * `CreationTime` - * `CreatorId` - * `LastModificationTime` - * `LastModifierId` - * `IsDeleted` - * `DeletionTime` - * `DeleterId` - -Once you implement any of the interfaces, or derive from a class defined in the next section, ABP Framework automatically manages these properties wherever possible. - -> Implementing `ISoftDelete`, `IDeletionAuditedObject` or `IFullAuditedObject` makes your entity **soft-delete**. See the [data filtering document](Data-Filtering.md) to learn about the soft-delete pattern. - -### Auditing Base Classes - -While you can manually implement any of the interfaces defined above, it is suggested to inherit from the base classes defined here: - -* `CreationAuditedEntity` and `CreationAuditedAggregateRoot` implement the `ICreationAuditedObject` interface. -* `AuditedEntity` and `AuditedAggregateRoot` implement the `IAuditedObject` interface. -* `FullAuditedEntity` and `FullAuditedAggregateRoot` implement the `IFullAuditedObject` interface. - -All these base classes also have non-generic versions to take `AuditedEntity` and `FullAuditedAggregateRoot` to support the composite primary keys. - -All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser` and `FullAuditedAggregateRootWithUser`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern). Also, if you add navigation properties to the AppUser class that comes with the startup template, consider to handle (ignore/map) it on the migration dbcontext (see [the EF Core migration document](Entity-Framework-Core-Migrations.md)). - -## Caching Entities - -ABP Framework provides a [Distributed Entity Cache System](Entity-Cache.md) for caching entities. It is useful if you want to use caching for quicker access to the entity rather than repeatedly querying it from the database. - -It's designed as read-only and automatically invalidates a cached entity if the entity is updated or deleted. - -> See the [Entity Cache](Entity-Cache.md) documentation for more information. - -## Versioning Entities - -ABP defines the `IHasEntityVersion` interface for automatic versioning of your entities. It only provides a single `EntityVersion` property, as shown in the following code block: - -````csharp -public interface IHasEntityVersion -{ - int EntityVersion { get; } -} -```` - -If you implement the `IHasEntityVersion` interface, ABP automatically increases the `EntityVersion` value whenever you update your entity. The initial `EntityVersion` value will be `0`, when you first create an entity and save to the database. - -> ABP can not increase the version if you directly execute SQL `UPDATE` commands in the database. It is your responsibility to increase the `EntityVersion` value in that case. Also, if you are using the aggregate pattern and change sub-collections of an aggregate root, it is your responsibility if you want to increase the version of the aggregate root object. - -## Extra Properties - -ABP defines the `IHasExtraProperties` interface that can be implemented by an entity to be able to dynamically set and get properties for the entity. `AggregateRoot` base class already implements the `IHasExtraProperties` interface. If you've derived from this class (or one of the related audit class defined above), you can directly use the API. - -### GetProperty & SetProperty Extension Methods - -These extension methods are the recommended way to get and set data for an entity. Example: - -````csharp -public class ExtraPropertiesDemoService : ITransientDependency -{ - private readonly IIdentityUserRepository _identityUserRepository; - - public ExtraPropertiesDemoService(IIdentityUserRepository identityUserRepository) - { - _identityUserRepository = identityUserRepository; - } - - public async Task SetTitle(Guid userId, string title) - { - var user = await _identityUserRepository.GetAsync(userId); - - //SET A PROPERTY - user.SetProperty("Title", title); - - await _identityUserRepository.UpdateAsync(user); - } - - public async Task GetTitle(Guid userId) - { - var user = await _identityUserRepository.GetAsync(userId); - - //GET A PROPERTY - return user.GetProperty("Title"); - } -} -```` - -* Property's **value is object** and can be any type of object (string, int, bool... etc). -* `GetProperty` returns `null` if given property was not set before. -* You can store more than one property at the same time by using different **property names** (like `Title` here). - -It would be a good practice to **define a constant** for the property name to prevent typo errors. It would be even a better practice to **define extension methods** to take the advantage of the intellisense. Example: - -````csharp -public static class IdentityUserExtensions -{ - private const string TitlePropertyName = "Title"; - - public static void SetTitle(this IdentityUser user, string title) - { - user.SetProperty(TitlePropertyName, title); - } - - public static string GetTitle(this IdentityUser user) - { - return user.GetProperty(TitlePropertyName); - } -} -```` - -Then you can directly use `user.SetTitle("...")` and `user.GetTitle()` for an `IdentityUser` object. - -### HasProperty & RemoveProperty Extension Methods - -* `HasProperty` is used to check if the object has a property set before. -* `RemoveProperty` is used to remove a property from the object. You can use this instead of setting a `null` value. - -### How it is Implemented? - -`IHasExtraProperties` interface requires to define a `Dictionary` property, named `ExtraProperties`, for the implemented class. - -So, you can directly use the `ExtraProperties` property to use the dictionary API, if you like. However, `SetProperty` and `GetProperty` methods are the recommended ways since they also check for `null`s. - -#### How is it Stored? - -The way to store this dictionary in the database depends on the database provider you're using. - -* For [Entity Framework Core](Entity-Framework-Core.md), here are two type of configurations; - * By default, it is stored in a single `ExtraProperties` field as a `JSON` string (that means all extra properties stored in a single database table field). Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core. - * If you want, you can use the `ObjectExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `ObjectExtensionManager` will continue to use a single `JSON` field as described above. This feature is especially useful when you are using a pre-built [application module](Modules/Index.md) and want to [extend its entities](Customizing-Application-Modules-Extending-Entities.md). See the [EF Core integration document](Entity-Framework-Core.md) to learn how to use the `ObjectExtensionManager`. -* For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system. - -### Discussion for the Extra Properties - -Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way. - -You typically **don't need** to use this system for your own entities, because it has the following drawbacks: - -* It is **not fully type safe** since it works with strings as property names. -* It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects. - -### Extra Properties Behind Entities - -`IHasExtraProperties` is not restricted to be used with entities. You can implement this interface for any kind of class and use the `GetProperty`, `SetProperty` and other related methods. - -## See Also - -* [Best practice guide to design the entities](Best-Practices/Entities.md) -* [Video tutorial](https://abp.io/video-courses/essentials/entities) diff --git a/docs/en/Entity-Cache.md b/docs/en/Entity-Cache.md deleted file mode 100644 index 6eaa186e53..0000000000 --- a/docs/en/Entity-Cache.md +++ /dev/null @@ -1,131 +0,0 @@ -# Entity Cache - -ABP Framework provides an entity caching system that works on top of the [distributed caching](Caching.md) system. It does the following operations on behalf of you: - -* Gets the entity from the database (by using the [repositories](Repositories.md)) in its first call and then gets it from the cache in subsequent calls. -* Automatically invalidates the cached entity if the entity is updated or deleted. Thus, it will be retrieved from the database in the next call and will be re-cached. - -## Caching Entity Objects - -`IEntityCache` is a simple service provided by the ABP Framework for caching entities. Assume that you have a `Product` entity as shown below: - -```csharp -public class Product : AggregateRoot -{ - public string Name { get; set; } - public string Description { get; set; } - public float Price { get; set; } - public int StockCount { get; set; } -} -``` - -If you want to cache this entity, you should first configure the [dependency injection](Dependency-Injection.md) system to register the `IEntityCache` service in the `ConfigureServices` method of your [module class](Module-Development-Basics.md): - -```csharp -context.Services.AddEntityCache(); -``` - -Now you can inject the `IEntityCache` service wherever you need: - -```csharp -public class ProductAppService : ApplicationService, IProductAppService -{ - private readonly IEntityCache _productCache; - - public ProductAppService(IEntityCache productCache) - { - _productCache = productCache; - } - - public async Task GetAsync(Guid id) - { - var product = await _productCache.GetAsync(id); - return ObjectMapper.Map(product); - } -} -``` - -> Note that we've used the `ObjectMapper` service to map from `Product` to `ProductDto`. You should configure that [object mapping](Object-To-Object-Mapping.md) to make that example service properly work. - -That's all. The cache name (in the distributed cache server) will be the full name (with namespace) of the `Product` class. You can use the `[CacheName]` attribute to change it. Please refer to the [caching document](Caching.md) for details. - -## Using a Cache Item Class - -In the previous section, we've directly cached the `Product` entity. In that case, the `Product` class must be serializable to JSON (and deserializable from JSON). Sometimes that might not be possible or you may want to use another class to store the cache data. For example, we may want to use the `ProductDto` class instead of the `Product` class for the cached object of the `Product` entity. - -Assume that we've created a `ProductDto` class as shown below: - -```csharp -public class ProductDto : EntityDto -{ - public string Name { get; set; } - public string Description { get; set; } - public float Price { get; set; } - public int StockCount { get; set; } -} -``` - -Now, we can register the entity cache services to [dependency injection](Dependency-Injection.md) in the `ConfigureServices` method of your [module class](Module-Development-Basics.md) with three generic parameters, as shown below: - -```csharp -context.Services.AddEntityCache(); -``` - -Since the entity cache system will perform the [object mapping](Object-To-Object-Mapping.md) (from `Product` to `ProductDto`), we should configure the object map. Here, an example configuration with [AutoMapper](https://automapper.org/): - -```csharp -public class MyMapperProfile : Profile -{ - public MyMapperProfile() - { - CreateMap(); - } -} -``` - -Now, you can inject the `IEntityCache` service wherever you want: - -```csharp -public class ProductAppService : ApplicationService, IProductAppService -{ - private readonly IEntityCache _productCache; - - public ProductAppService(IEntityCache productCache) - { - _productCache = productCache; - } - - public async Task GetAsync(Guid id) - { - return await _productCache.GetAsync(id); - } -} -``` - -Notice that the `_productCache.GetAsync` method already returns a `ProductDto` object, so we could directly return it from our application service. - -## Configuration - -All of the `context.Services.AddEntityCache()` methods get an optional `DistributedCacheEntryOptions` parameter where you can easily configure the caching options: - -```csharp -context.Services.AddEntityCache( - new DistributedCacheEntryOptions - { - SlidingExpiration = TimeSpan.FromMinutes(30) - } -); -``` - -> The default cache duration is **2 minutes** with the `AbsoluteExpirationRelativeToNow` configuration. - -## Additional Notes - -* Entity classes should be serializable/deserializable to/from JSON to be cached (because it's serialized to JSON when saving in the [Distributed Cache](Caching.md)). If your entity class is not serializable, you can consider using a cache-item/DTO class instead, as explained before. -* Entity Caching System is designed as **read-only**. You should use the standard [repository](Repositories.md) methods to manipulate the entity if you need to. If you need to manipulate (update) the entity, do not get it from the entity cache. Instead, read it from the repository, change it and update using the repository. - -## See Also - -* [Distributed caching](Caching.md) -* [Entities](Entities.md) -* [Repositories](Repositories.md) diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md deleted file mode 100644 index 9ac7e76f24..0000000000 --- a/docs/en/Entity-Framework-Core-Migrations.md +++ /dev/null @@ -1,557 +0,0 @@ -# EF Core Database Migrations - -This document begins by **introducing the default structure** provided by [the application startup template](Startup-Templates/Application.md) and **discusses various scenarios** you may want to implement for your own application. - -> This document is for who want to fully understand and customize the database structure comes with [the application startup template](Startup-Templates/Application.md). If you simply want to create entities and manage your code first migrations, just follow [the startup tutorials](Tutorials/Part-1.md). - -### Source Code - -You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code. - -## About the EF Core Code First Migrations - -Entity Framework Core provides an easy to use and powerful [database migration system](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). ABP Framework [startup templates](Startup-Templates/Index.md) take the advantage of this system to allow you to develop your application in a standard way. - -However, EF Core migration system is **not so good in a modular environment** where each module maintains its **own database schema** while two or more modules may **share a single database** in practical. - -Since ABP Framework cares about modularity in all aspects, it provides a **solution** to this problem. It is important to understand this solution if you need to **customize your database structure**. - -> See [EF Core's own documentation](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to fully learn the EF Core Code First Migrations and why you need to such a system. - -## The Default Solution & Database Configuration - -When you [create a new web application](https://abp.io/get-started) (with EF Core, which is the default database provider), your solution structure will be similar to the picture below: - -![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png) - -Actual solution structure may be a bit different based on your preferences, but the database part will be same. - -> This document will use the `Acme.BookStore` example project name to refer the projects and classes. You need to find the corresponding class/project in your solution. - -### The Database Structure - -The startup template has some [application modules](Modules/Index.md) pre-installed. Each layer of the solution has corresponding module **package references**. So, the `.EntityFrameworkCore` project has the NuGet references for the `.EntityFrameworkCore` packages of the used modules: - -![bookstore-efcore-dependencies](images/bookstore-efcore-dependencies.png) - -In this way, you collect all the **EF Core dependencies** under the `.EntityFrameworkCore` project. - -> In addition to the module references, it references to the `Volo.Abp.EntityFrameworkCore.SqlServer` package since the startup template is pre-configured for the **SQL Server**. See the documentation if you want to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md). - -While every module has its own `DbContext` class by design and can use its **own physical database**, the solution is configured to use a **single shared database** as shown in the figure below: - -![single-database-usage](images/single-database-usage.png) - -This is **the simplest configuration** and suitable for most of the applications. `appsettings.json` file has a **single connection string**, named `Default`: - -````json -"ConnectionStrings": { - "Default": "..." -} -```` - -So, you have a **single database schema** which contains all the tables of the modules **sharing** this database. - -ABP Framework's [connection string](Connection-Strings.md) system allows you to easily **set a different connection string** for a desired module: - -````json -"ConnectionStrings": { - "Default": "...", - "AbpAuditLogging": "..." -} -```` - -The example configuration tells to the ABP Framework to use the second connection string for the [Audit Logging module](Modules/Audit-Logging.md) (if you don't specify connection string for a module, it uses the `Default` connection string). - -**However, this can work only if the audit log database with the given connection string is available**. So, you need to create the second database, create audit log tables inside it and maintain the database tables. No problem if you manually do all these. However, the recommended approach is the code first migrations. One of the main purposes of this document is to guide you on such **database separation** scenarios. - -#### Module Tables - -Every module uses its **own databases tables**. For example, the [Identity Module](Modules/Identity.md) has some tables to manage the users and roles in the system. - -##### Table Prefixes - -Since it is allowed to share a single database by all modules (it is the default configuration), a module typically uses a **table name prefix** to group its own tables. - -The fundamental modules, like [Identity](Modules/Identity.md), [Tenant Management](Modules/Tenant-Management.md) and [Audit Logs](Modules/Audit-Logging.md), use the `Abp` prefix, while some other modules use their own prefixes. [Identity Server](Modules/IdentityServer.md) module uses the `IdentityServer` prefix for example. - -If you want, you can **change the database table name prefix** for a module for your application. Example: - -````csharp -Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids"; -```` - -This code changes the prefix of the [Identity Server](Modules/IdentityServer.md) module. Write this code **at the very beginning** in your application. - -> Every module also defines `DbSchema` property (near to `DbTablePrefix`), so you can set it for the databases support the schema usage. - -### .EntityFrameworkCore Project - -The solution contains a project, which's name ends with `.EntityFrameworkCore`. This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application. - -**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your [repositories](Repositories.md) if you follow the best practices and hide your data access code behind the repositories). It is almost an empty `DbContext` since your application don't have any entities at the beginning: - -````csharp -[ReplaceDbContext(typeof(IIdentityDbContext))] -[ReplaceDbContext(typeof(ITenantManagementDbContext))] -[ConnectionStringName("Default")] -public class BookStoreDbContext : - AbpDbContext, - IIdentityDbContext, - ITenantManagementDbContext -{ - /* Add DbSet properties for your Aggregate Roots / Entities here. */ - - /* DbSet for entities from the replaced DbContexts */ - - public BookStoreDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - builder.ConfigurePermissionManagement(); - builder.ConfigureSettingManagement(); - builder.ConfigureBackgroundJobs(); - builder.ConfigureAuditLogging(); - builder.ConfigureIdentity(); - builder.ConfigureIdentityServer(); - builder.ConfigureFeatureManagement(); - builder.ConfigureTenantManagement(); - - /* Configure your own tables/entities here. Example: */ - //builder.Entity(b => - //{ - // b.ToTable("YourEntities"); - // b.ConfigureByConvention(); //auto configure for the base properties - // //... - //}); - } -} -```` - -This `DbContext` class needs some explanations: - -* It defines `[ReplaceDbContext]` attributes for `IIdentityDbContext` and `ITenantManagementDbContext` those replaces Identity and Tenant Management module's `DbContext`s by your `DbContext` on runtime. This allows us to easily perform LINQ queries by joining your entities with the entities (over the repositories) coming from those modules. -* It defines a `[ConnectionStringName]` attribute which tells ABP to always use the `Default` connection string for this `Dbcontext`. -* It inherits from the `AbpDbContext` instead of the standard `DbContext` class. You can see the [EF Core integration](Entity-Framework-Core.md) document for more. For now, know that the `AbpDbContext` base class implements some conventions of the ABP Framework to automate some common tasks for you. -* It declares `DbSet` properties for entities from the replaced `DbContext`s (by implementing the corresponding interfaces). These `DbSet` properties are not shown above (for the sake of brevity), but you can find in your application's code in a `region`. -* The constructor takes a `DbContextOptions` instance. -* It overrides the `OnModelCreating` method to define the EF Core mappings. - * It first calls the the `base.OnModelCreating` method to let the ABP Framework to implement the base mappings for us. - * It then calls some `builder.ConfigureXXX()` methods for the used modules. This makes possible to add database mappings for these modules to this `DbContext`, so it creates the database tables of the modules when we add a new EF Core database migration. - * You can configure the mappings for your own entities as commented in the example code. At this point, you can also change mappings for the modules you are using. - -### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path - -As mentioned before, in the `.EntityFrameworkCore` project, we merge all the database mappings of all the modules (plus your application's mappings) to create a unified migration path. - -An alternative approach would be to allow each module to have its own migrations to maintain its database tables. While it seems more modular in the beginning, it has some drawbacks in practical: - -* **EF Core migration system depends on the DBMS provider**. For example, if a module has created migrations for SQL Server, then you can not use this migration code for MySQL. It is not practical for a module to maintain migrations for all available DBMS providers. Leaving the migration to the application code (as explained in this document) allows you to **choose the DBMS in the application** code. If you can depend on a specific DBMS in your module, that's not an issue for you, however all pre-built ABP modules are DBMS agnostic. -* It would be harder to **customize/enhance** the mapping and the resulting migration code, in the final application. -* It would be harder to track and **apply changes** to database when you use multiple modules. - -## Using Multiple Databases - -The default startup template is organized to use a **single database** used by all the modules and by your application. However, the ABP Framework and all the pre-built modules are designed so that **they can use multiple databases**. Each module can use its own database or you can group modules into a few databases. - -This section will explain how to move Audit Logging, Setting Management and Permission Management module tables to a **second database** while the remaining modules continue to use the main ("Default") database. - -The resulting structure will be like the figure below: - -![single-database-usage](images/multiple-database-usage.png) - -### Change the Connection Strings Section - -First step is to change the connection string section inside all the `appsettings.json` files. Initially, it is like that: - -````json -"ConnectionStrings": { - "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True" -} -```` - -Change it as shown below: - -````json -"ConnectionStrings": { - "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True", - "AbpPermissionManagement": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True", - "AbpSettingManagement": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True", - "AbpAuditLogging": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True" -} -```` - -Added **three more connection strings** for the related module to target the `BookStore_SecondDb` database (they are all the same). For example, `AbpPermissionManagement` is the connection string name used by the permission management module. - -The `AbpPermissionManagement` is a constant [defined](https://github.com/abpframework/abp/blob/97eaa6ff5a044f503465455c86332e5a277b077a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDbProperties.cs#L11) by the permission management module. ABP Framework [connection string selection system](Connection-Strings.md) selects this connection string for the permission management module if you define. If you don't define, it fallbacks to the `Default` connection string. - -### Create a Second DbContext - -Defining the connection strings as explained above is enough **on runtime**. However, `BookStore_SecondDb` database doesn't exist yet. You need to create the database and the tables for the related modules. - -Just like the main database, we want to use the EF Core Code First migration system to create and maintain the second database. So, create a new `DbContext` class inside the `.EntityFrameworkCore` project: - -````csharp -using Microsoft.EntityFrameworkCore; -using Volo.Abp.AuditLogging.EntityFrameworkCore; -using Volo.Abp.Data; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.PermissionManagement.EntityFrameworkCore; -using Volo.Abp.SettingManagement.EntityFrameworkCore; - -namespace BookStore.EntityFrameworkCore -{ - [ConnectionStringName("AbpPermissionManagement")] - public class BookStoreSecondDbContext : - AbpDbContext - { - public BookStoreSecondDbContext( - DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - builder.ConfigurePermissionManagement(); - builder.ConfigureSettingManagement(); - builder.ConfigureAuditLogging(); - } - } -} -```` - -> `[ConnectionStringName(...)]` attribute is important here and tells to the ABP Framework which connection string should be used for this `DbContext`. We've used `AbpPermissionManagement`, but all are the same. - -We need to register this `BookStoreSecondDbContext` class to the dependency injection system. Open the `BookStoreEntityFrameworkCoreModule` class in the `BookStore.EntityFrameworkCore` project and add the following line into the `ConfigureServices` method: - -````csharp -context.Services.AddAbpDbContext(); -```` - -We should also create a **Design Time Db Factory** class, that is used by the EF Core tooling (by `Add-Migration` and `Update-Database` PCM commands for example): - -````csharp -using System.IO; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Configuration; - -namespace BookStore.EntityFrameworkCore -{ - /* This class is needed for EF Core console commands - * (like Add-Migration and Update-Database commands) */ - public class BookStoreSecondDbContextFactory - : IDesignTimeDbContextFactory - { - public BookStoreSecondDbContext CreateDbContext(string[] args) - { - var configuration = BuildConfiguration(); - var builder = new DbContextOptionsBuilder() - .UseSqlServer(configuration.GetConnectionString("AbpPermissionManagement")); - return new BookStoreSecondDbContext(builder.Options); - } - - private static IConfigurationRoot BuildConfiguration() - { - var builder = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../BookStore.DbMigrator/")) - .AddJsonFile("appsettings.json", optional: false); - - return builder.Build(); - } - } -} -```` - -Now, you can open the Package Manager Console, select the `.EntityFrameworkCore` project as the default project (make sure the `.Web` project is still the startup project) and run the following command: - -````bash -Add-Migration "Initial" -OutputDir "SecondDbMigrations" -Context BookStoreSecondDbContext -```` - -This will add a `SecondDbMigrations` folder in the `.EntityFrameworkCore` project and a migration class inside it. `OutputDir` and `Context` parameters are required since we currently have two `DbContext` class and two migrations folder in the same project. - -You can now run the following command to create the database and the tables inside it: - -````bash -Update-Database -Context BookStoreSecondDbContext -```` - -A new database, named `BookStore_SecondDb` should be created. - -### Remove Modules from the Main Database - -We've **created a second database** that contains tables for the Audit Logging, Permission Management and Setting Management modules. So, we should **delete these tables from the main database**. It is pretty easy. - -First, remove the following lines from the `BookStoreDbContext` class: - -````csharp -builder.ConfigurePermissionManagement(); -builder.ConfigureSettingManagement(); -builder.ConfigureAuditLogging(); -```` - -Open the Package Manager Console, select the `.EntityFrameworkCore` as the Default project (make sure that the `.Web` project is still the startup project) and run the following command: - -```` -Add-Migration "Removed_Audit_Setting_Permission_Modules" -Context BookStoreDbContext -```` - -This command will create a new migration class as shown below: - -````csharp -public partial class Removed_Audit_Setting_Permission_Modules : Migration -{ - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AbpAuditLogActions"); - - migrationBuilder.DropTable( - name: "AbpEntityPropertyChanges"); - - migrationBuilder.DropTable( - name: "AbpPermissionGrants"); - - migrationBuilder.DropTable( - name: "AbpSettings"); - - migrationBuilder.DropTable( - name: "AbpEntityChanges"); - - migrationBuilder.DropTable( - name: "AbpAuditLogs"); - } - - ... -} -```` - -Be careful in this step: - -* If you have a **live system**, then you should care about the **data loss**. You need to move the table contents to the second database before deleting the tables. -* If you **haven't started** your project yet, you can consider to **remove all the migrations** and re-create the initial one to have a cleaner migration history. - -Run the following command to delete the tables from your main database: - -````bash -Update-Database -Context BookStoreDbContext -```` - -Notice that you've also **deleted some initial seed data** (for example, permission grants for the admin role) if you haven't copied it to the new database. If you run the application, you may not login anymore. The solution is simple: **Re-run the `.DbMigrator` console application** in your solution, it will seed the new database. - -### Automate the Second Database Schema Migration - -`.DbMigrator` console application can run the database seed code across multiple databases, without any additional configuration. However, it can not apply the EF Core Code First Migrations for the database of the `BookStoreSecondDbContext`. Now, you will see how to configure the console migration application to handle both databases. - -`EntityFrameworkCoreBookStoreDbSchemaMigrator` class inside the `Acme.BookStore.EntityFrameworkCore` project is responsible to migrate the database schema for the `BookStoreMigrationsDbContext`. It should be like that: - -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using BookStore.Data; -using Volo.Abp.DependencyInjection; - -namespace BookStore.EntityFrameworkCore -{ - public class EntityFrameworkCoreBookStoreDbSchemaMigrator - : IBookStoreDbSchemaMigrator, ITransientDependency - { - private readonly IServiceProvider _serviceProvider; - - public EntityFrameworkCoreBookStoreDbSchemaMigrator( - IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public async Task MigrateAsync() - { - /* We intentionally resolving the BookStoreDbContext - * from IServiceProvider (instead of directly injecting it) - * to properly get the connection string of the current tenant in the - * current scope. - */ - - await _serviceProvider - .GetRequiredService() - .Database - .MigrateAsync(); - } - } -} -```` - -Add the following code inside the `MigrateAsync` method: - -````csharp -await _serviceProvider - .GetRequiredService() - .Database - .MigrateAsync(); -```` - -So, the `MigrateAsync` method should look like the following: - -````csharp -public async Task MigrateAsync() -{ - /* We intentionally resolving the BookStoreDbContext - * from IServiceProvider (instead of directly injecting it) - * to properly get the connection string of the current tenant in the - * current scope. - */ - - await _serviceProvider - .GetRequiredService() - .Database - .MigrateAsync(); - - await _serviceProvider - .GetRequiredService() - .Database - .MigrateAsync(); -} -```` - -That's all. You can now run the `.DbMigrator` application to migrate & seed the databases. To test, you can delete both databases and run the `.DbMigrator` application again to see if it creates both of the databases. - -### Fixing the Tests - -Creating a new DbContext will break the integration tests. It is easy to fix. Open the `BookStoreEntityFrameworkCoreTestModule` class in the `BookStore.EntityFrameworkCore.Tests` project, find the `CreateDatabaseAndGetConnection` method. It should be like that: - -````csharp -private static SqliteConnection CreateDatabaseAndGetConnection() -{ - var connection = new SqliteConnection("Data Source=:memory:"); - connection.Open(); - - var options = new DbContextOptionsBuilder() - .UseSqlite(connection) - .Options; - - using (var context = new BookStoreDbContext(options)) - { - context.GetService().CreateTables(); - } - - return connection; -} -```` - -Change it as the following: - -````csharp -private static SqliteConnection CreateDatabaseAndGetConnection() -{ - var connection = new SqliteConnection("Data Source=:memory:"); - connection.Open(); - - var options = new DbContextOptionsBuilder() - .UseSqlite(connection) - .Options; - - using (var context = new BookStoreDbContext(options)) - { - context.GetService().CreateTables(); - } - - // Add the following code -------------- - var optionsForSecondDb = new DbContextOptionsBuilder() - .UseSqlite(connection) - .Options; - - using (var context = new BookStoreSecondDbContext(optionsForSecondDb)) - { - context.GetService().CreateTables(); - } - //-------------------------------------- - - return connection; -} -```` - -Integration tests now will work. I've used the same database in the tests to keep it simple. - -## Separating Host & Tenant Database Schemas - -In a multi-tenant solution, you may want to separate your database schemas, so host-related tables don't locate in the tenant databases when tenants have separate databases. - -Some pre-built ABP modules are related only with the host side, like the [Tenant Management](Modules/Tenant-Management.md) module. So, in the tenant `DbContext` class you don't call `modelBuilder.ConfigureTenantManagement()` and that's all. - -Some modules, like the [Identity](Modules/Identity.md) module, is both used in host and tenant sides. It stores tenant users in the tenant database and host users in the host database. However, it stores some entities, like `IdentityClaimType`, only in the host side. In this case, you don't want to add these tables in the tenant database, even if they are not used and will always be empty for tenants. - -ABP provides a simple way to set the multi-tenancy side for a `DbContext`, so the modules can check it and decide to map tables to the database, or not. - -````csharp -public class MyTenantDbContext : AbpDbContext -{ - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.SetMultiTenancySide(MultiTenancySides.Tenant); - - base.OnModelCreating(modelBuilder); - - modelBuilder.ConfigureIdentity(); - modelBuilder.ConfigureFeatureManagement(); - modelBuilder.ConfigureAuditLogging(); - } -} -```` - -The first line in the `OnModelCreating` sets multi-tenancy side to `Tenant`. For this example, Feature management tables are not created (because all the tables are host-specific), so calling `modelBuilder.ConfigureFeatureManagement()` has no effect. Also, `ConfigureIdentity()` call respects to the multi-tenancy side and doesn't create host-specific tables for this database. - -`SetMultiTenancySide` can get the following values: - -* `MultiTenancySides.Both` (**default value**): This `DbContext` (and the related database) is shared by host and tenant. -* `MultiTenancySides.Host`: This `DbContext` (and the related database) is used only by the host side. -* `MultiTenancySides.Tenant`: This `DbContext` (and the related database) is only for tenants. - -If you create a re-usable application module or want to check that value in your application code, you can use `modelBuilder.GetMultiTenancySide()` to check the current side. - -````csharp -var side = modelBuilder.GetMultiTenancySide(); -if (!side.HasFlag(MultiTenancySides.Host)) -{ - ... -} -```` - -Or practically you can use one of the shortcut extension methods: - -````csharp -if (modelBuilder.IsTenantOnlyDatabase()) -{ - ... -} -```` - -There are four methods to check the current side: - -* `IsHostDatabase()`: Returns `true` if you should create host-related tables. It is equivalent of checking `modelBuilder.GetMultiTenancySide().HasFlag(MultiTenancySides.Host)`. -* `IsHostOnlyDatabase()`: Returns `true` if you should only create host-related tables, but should not create tenant-related tables. It is equivalent of checking `modelBuilder.GetMultiTenancySide() == MultiTenancySides.Host`. -* `IsTenantDatabase()`: Returns `true` if you should create tenant-related tables. It is equivalent of checking `modelBuilder.GetMultiTenancySide().HasFlag(MultiTenancySides.Tenant)`. -* `IsTenantOnlyDatabase()`: Returns `true` if you should only create tenant-related tables, but should not create host-related tables. It is equivalent of checking `modelBuilder.GetMultiTenancySide() == MultiTenancySides.Tenant`. - -All pre-built ABP [modules](Modules/Index.md) checks this value in their `modelBuilder.ConfigureXXX()` methods. - -## Conclusion - -This document explains how to split your databases and manage your database migrations of your solution for Entity Framework Core. In brief, you need to have a separate migration project per different databases. - -## Source Code - -You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). You can also find the changes explained in this document as a [single commit](https://github.com/abpframework/abp-samples/pull/95/commits/c2ffd76175e0a6fdfcf6477bbaea23dc2793fedd). \ No newline at end of file diff --git a/docs/en/Entity-Framework-Core-MySQL.md b/docs/en/Entity-Framework-Core-MySQL.md deleted file mode 100644 index ccfdd89da3..0000000000 --- a/docs/en/Entity-Framework-Core-MySQL.md +++ /dev/null @@ -1,43 +0,0 @@ -# Switch to EF Core MySQL Provider - -> [ABP CLI](CLI.md) and the [Get Started](https://abp.io/get-started) page already provides an option to create a new solution with MySQL. See [that document](Entity-Framework-Core-Other-DBMS.md) to learn how to use. This document provides guidance for who wants to manually switch to MySQL after creating the solution. - -This document explains how to switch to the **MySQL** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. - -## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package - -`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.MySQL](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.MySQL) package. - -## Replace the Module Dependency - -Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreMySQLModule)` (also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.MySQL;`). - -## UseMySQL() - -Find `UseSqlServer()` calls in your solution. Check the following files: - -* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. Replace `UseSqlServer()` with `UseMySQL()`. -* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. Replace `UseSqlServer()` with `UseMySql()`. Then add a new parameter (`ServerVersion`) to `UseMySql()` method. Example: `.UseMySql(configuration.GetConnectionString("Default"), ServerVersion.Parse("8.0.21-mysql"))`. See [this issue](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/1233) for more information about `ServerVersion`) - -> Depending on your solution structure, you may find more code files need to be changed. - -## Change the Connection Strings - -MySQL connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/mysql/ ) for details of MySQL connection string options. - -You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. - -## Re-Generate the Migrations - -The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. - -* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). - -This will create a database migration with all database objects (tables) configured. - -Run the `.DbMigrator` project to create the database and seed the initial data. - -## Run the Application - -It is ready. Just run the application and enjoy coding. diff --git a/docs/en/Entity-Framework-Core-Oracle-Devart.md b/docs/en/Entity-Framework-Core-Oracle-Devart.md deleted file mode 100644 index 92cecadec9..0000000000 --- a/docs/en/Entity-Framework-Core-Oracle-Devart.md +++ /dev/null @@ -1,137 +0,0 @@ -# Switch to EF Core Oracle Devart Provider - -This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. - -> This document uses a paid library of [Devart](https://www.devart.com/dotconnect/oracle/) company, See [this document](Entity-Framework-Core-Oracle.md) for other options. - -> Before switching your provider, please ensure your Oracle version is **v12.2+**. In the earlier versions of Oracle, there were long identifier limitations that prevents creating a database table, column or index longer than 30 bytes. With [v12.2](https://docs.oracle.com/en/database/oracle/oracle-database/12.2/newft/new-features.html#GUID-64283AD6-0939-47B0-856E-5E9255D7246B) "The maximum length of identifiers is increased to 128 bytes". **v12.2** and later versions, you can use the database tables, columns and indexes provided by ABP without any problems. - -## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package - -`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) package. - -## Replace the Module Dependency - -Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreOracleDevartModule)` - -Also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`. - -## UseOracle() - -Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: - -* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. -* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. - - -In the `CreateDbContext()` method of the *YourProjectName*DbContextFactory.cs, replace the following code block - -```csharp -var builder = new DbContextOptionsBuilder() - .UseSqlServer(configuration.GetConnectionString("Default")); -``` - -with this one -```csharp -var builder = (DbContextOptionsBuilder) - new DbContextOptionsBuilder().UseOracle - ( - configuration.GetConnectionString("Default") - ); -``` - -> Depending on your solution structure, you may find more code files need to be changed. - -## Change the Connection Strings - -Oracle connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/oracle/ ) for details of Oracle connection string options. - -You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. - -## Re-Generate the Migrations - -The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default. -EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations. - -* Delete the `Migrations` folder under the `.EntityFrameworkCore` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). - -This will scaffold a new migration for Oracle. - -Run the `.DbMigrator` project to create the database, apply the changes and seed the initial data. - -## ORA-12899: value too large for column fix - -Oracle limits strings to `NVARCHAR2(2000)` when the migration is created. Some of the entity properties may extend it. You can check the known and reported properties of ABP modules entities that can extend this limit. To prevent this problem, you need to convert the `string` type to `long` type first and generate a new migration. Then convert the `long` type to `clob` type with maximum length. - -### First Migration - -Update you application DbContext `OnModelCreating` method: - -```csharp -protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.Entity(b => - { - b.Property(q => q.Payload).HasColumnType("long").HasMaxLength(int.MaxValue); - }); - - builder.Entity(b => - { - b.Property(x => x.Exceptions).HasColumnType("long").HasMaxLength(int.MaxValue); - }); - - builder.Entity(b => - { - b.Property(x => x.Parameters).HasColumnType("long").HasMaxLength(int.MaxValue); - }); - - /* Configure your own tables/entities inside here */ - } -``` - -Create a new migration using dotnet tooling under EntityFrameworkCore layer of your solution: `dotnet ef migrations add Oracle_Long_Conversion` - -### Second Migration - -Update you application DbContext `OnModelCreating` method: - -```csharp -protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.Entity(b => - { - b.Property(q => q.Payload).HasColumnType("clob").HasMaxLength(4000); - }); - - builder.Entity(b => - { - b.Property(x => x.Exceptions).HasColumnType("clob").HasMaxLength(4000); - }); - - builder.Entity(b => - { - b.Property(x => x.Parameters).HasColumnType("clob").HasMaxLength(4000); - }); - - /* Configure your own tables/entities inside here */ - } -``` - -Create a new migration using dotnet tooling under EntityFrameworkCore layer of your solution: `dotnet ef migrations add Oracle_Clob_Conversion` - -Run DbMigrator (or use dotnet tooling) to migrate the oracle database. - - - -## Run the Application - -It is ready. Just run the application and enjoy coding. diff --git a/docs/en/Entity-Framework-Core-Oracle-Official.md b/docs/en/Entity-Framework-Core-Oracle-Official.md deleted file mode 100644 index fcc776f2c1..0000000000 --- a/docs/en/Entity-Framework-Core-Oracle-Official.md +++ /dev/null @@ -1,130 +0,0 @@ -# Switch to EF Core Oracle Provider - -This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. - -> Before switching your provider, please ensure your Oracle version is **v12.2+**. In the earlier versions of Oracle, there were long identifier limitations that prevents creating a database table, column or index longer than 30 bytes. With [v12.2](https://docs.oracle.com/en/database/oracle/oracle-database/12.2/newft/new-features.html#GUID-64283AD6-0939-47B0-856E-5E9255D7246B) "The maximum length of identifiers is increased to 128 bytes". **v12.2** and later versions, you can use the database tables, columns and indexes provided by ABP without any problems. - -## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package - -`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Oracle](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle) package. - -## Replace the Module Dependency - -Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreOracleModule)` - -Also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Oracle;`. - -## UseOracle() - -Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: - -* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. -* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. - - -In the `CreateDbContext()` method of the *YourProjectName*DbContextFactory.cs, replace the following code block - -```csharp -var builder = new DbContextOptionsBuilder() - .UseSqlServer(configuration.GetConnectionString("Default")); -``` - -with this one (just changes `UseSqlServer(...)` to `UseOracle(...)`) -```csharp -var builder = new DbContextOptionsBuilder() - .UseOracle(configuration.GetConnectionString("Default")); -``` - -> Depending on your solution structure, you may find more code files need to be changed. - -## Change the Connection Strings - -Oracle connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/oracle/ ) for details of Oracle connection string options. - -You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. - -## Re-Generate the Migrations - -The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default. -EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations. - -* Delete the `Migrations` folder under the `.EntityFrameworkCore` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). - -This will scaffold a new migration for Oracle. - -Run the `.DbMigrator` project to create the database, apply the changes and seed the initial data. - -## ORA-12899: value too large for column fix - -Oracle limits strings to `NVARCHAR2(2000)` when the migration is created. Some of the entity properties may extend it. You can check the known and reported properties of ABP modules entities that can extend this limit. To prevent this problem, you need to convert the `string` type to `long` type first and generate a new migration. Then convert the `long` type to `clob` type with maximum length. - -### First Migration - -Update you application DbContext `OnModelCreating` method: - -```csharp -protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.Entity(b => - { - b.Property(q => q.Payload).HasColumnType("long").HasMaxLength(int.MaxValue); - }); - - builder.Entity(b => - { - b.Property(x => x.Exceptions).HasColumnType("long").HasMaxLength(int.MaxValue); - }); - - builder.Entity(b => - { - b.Property(x => x.Parameters).HasColumnType("long").HasMaxLength(int.MaxValue); - }); - - /* Configure your own tables/entities inside here */ - } -``` - -Create a new migration using dotnet tooling under EntityFrameworkCore layer of your solution: `dotnet ef migrations add Oracle_Long_Conversion` - -### Second Migration - -Update you application DbContext `OnModelCreating` method: - -```csharp -protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.Entity(b => - { - b.Property(q => q.Payload).HasColumnType("clob").HasMaxLength(4000); - }); - - builder.Entity(b => - { - b.Property(x => x.Exceptions).HasColumnType("clob").HasMaxLength(4000); - }); - - builder.Entity(b => - { - b.Property(x => x.Parameters).HasColumnType("clob").HasMaxLength(4000); - }); - - /* Configure your own tables/entities inside here */ - } -``` - -Create a new migration using dotnet tooling under EntityFrameworkCore layer of your solution: `dotnet ef migrations add Oracle_Clob_Conversion` - -Run DbMigrator (or use dotnet tooling) to migrate the oracle database. - -## Run the Application - -It is ready. Just run the application and enjoy coding. diff --git a/docs/en/Entity-Framework-Core-Oracle.md b/docs/en/Entity-Framework-Core-Oracle.md deleted file mode 100644 index 161f1abf45..0000000000 --- a/docs/en/Entity-Framework-Core-Oracle.md +++ /dev/null @@ -1,12 +0,0 @@ -# Switch to EF Core Oracle Provider - -> [ABP CLI](CLI.md) and the [Get Started](https://abp.io/get-started) page already provides an option to create a new solution with Oracle. See [that document](Entity-Framework-Core-Other-DBMS.md) to learn how to use. This document provides guidance for who wants to manually switch to Oracle after creating the solution. - -This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. - -ABP Framework provides integrations for two different Oracle packages. See one of the following documents based on your provider decision: - -* **[`Volo.Abp.EntityFrameworkCore.Oracle`](Entity-Framework-Core-Oracle-Official.md)** package uses the official & free oracle driver. -* **[`Volo.Abp.EntityFrameworkCore.Oracle.Devart`](Entity-Framework-Core-Oracle-Devart.md)** package uses the commercial (paid) driver of [Devart](https://www.devart.com/) company. - -> You can choose one of the package you want. If you don't know the differences of the packages, please search for it. ABP Framework only provides integrations it doesn't provide support for such 3rd-party libraries. diff --git a/docs/en/Entity-Framework-Core-Other-DBMS.md b/docs/en/Entity-Framework-Core-Other-DBMS.md deleted file mode 100644 index 93c3a6276f..0000000000 --- a/docs/en/Entity-Framework-Core-Other-DBMS.md +++ /dev/null @@ -1,112 +0,0 @@ -# Switch to Another DBMS for Entity Framework Core - -[ABP CLI](CLI.md) provides a `-dbms` option to allow you to choose your Database Management System (DBMS) while creating a new solution. It accepts the following values: - -- `SqlServer` (default) -- `MySQL` -- `SQLite` -- `Oracle` -- `Oracle-Devart` -- `PostgreSQL` - -So, if you want to use MySQL for your solution, you can use the `-dbms MySQL` option while using the `abp new` command. Example: - -````bash -abp new BookStore -dbms MySQL -```` - -Also, the [Get Started page](https://abp.io/get-started) on the ABP website allows you to select one of the providers. - -> **This document provides guidance for who wants to manually change their DBMS after creating the solution.** - -You can use the following documents to learn how to **switch to your favorite DBMS**: - -* [MySQL](Entity-Framework-Core-MySQL.md) -* [PostgreSQL](Entity-Framework-Core-PostgreSQL.md) -* [Oracle](Entity-Framework-Core-Oracle.md) -* [SQLite](Entity-Framework-Core-SQLite.md) - -You can also configure your DBMS provider **without** these integration packages. While using the integration package is always recommended (it also makes standard for the depended version across different modules), you can do it yourself if there is no integration package for your DBMS provider. - -For an example, this document explains how to switch to MySQL without using [the MySQL integration package](Entity-Framework-Core-MySQL.md). - -## Replace the SQL Server Dependency - -* Remove the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package dependency from the `.EntityFrameworkCore` project. -* Add the [Pomelo.EntityFrameworkCore.MySql](https://www.nuget.org/packages/Pomelo.EntityFrameworkCore.MySql/) NuGet package dependency to your `.EntityFrameworkCore` project. - -## Remove the Module Dependency - -Remove the `AbpEntityFrameworkCoreSqlServerModule` from the dependency list of your ***YourProjectName*EntityFrameworkCoreModule** class. - -## Change the UseSqlServer() Calls - -Find the following code part inside the *YourProjectName*EntityFrameworkCoreModule class: - -````csharp -Configure(options => -{ - options.UseSqlServer(); -}); -```` - -Replace it with the following code part: - -````csharp -Configure(options => -{ - options.Configure(ctx => - { - if (ctx.ExistingConnection != null) - { - ctx.DbContextOptions.UseMySql(ctx.ExistingConnection); - } - else - { - ctx.DbContextOptions.UseMySql(ctx.ConnectionString); - } - }); -}); -```` - -* `UseMySql` calls in this code is defined by the Pomelo.EntityFrameworkCore.MySql package and you can use its additional options if you need. -* This code first checks if there is an existing (active) connection to the same database in the current request and reuses it if possible. This allows to share a single transaction among different DbContext types. ABP handles the rest of the things. -* It uses `ctx.ConnectionString` and passes to the `UseMySql` if there is no active connection (which will cause to create a new database connection). Using the `ctx.ConnectionString` is important here. Don't pass a static connection string (or a connection string from a configuration). Because ABP [dynamically determines the correct connection string](Connection-Strings.md) in a multi-database or [multi-tenant](Multi-Tenancy.md) environment. - -## Change the Connection Strings - -MySQL connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/mysql/ ) for details of MySQL connection string options. - -You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. - -## DBMS restrictions - -MySQL DBMS has some slight differences than the SQL Server. Some module database mapping configuration (especially the field lengths) causes problems with MySQL. For example, some of the the [IdentityServer module](Modules/IdentityServer.md) tables has such problems and it provides an option to configure the fields based on your DBMS. - -The module may provide some built-in solutions. You can configure it via `ModelBuilder`. eg: `Auth Server` module. - -```csharp -builder.ConfigureIdentityServer(options => -{ - options.DatabaseProvider = EfCoreDatabaseProvider.MySql; -}); -``` - -Then `ConfigureIdentityServer()` method will set the field lengths to not exceed the MySQL limits. Refer to related module documentation if you have any problem while creating or executing the database migrations. - -## Re-Generate the Migrations - -The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. - -* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). - -This will create a database migration with all database objects (tables) configured. - -Run the `.DbMigrator` project to create the database and seed the initial data. - -## Run the Application - -It is ready. Just run the application and enjoy coding. - -Related discussions: https://github.com/abpframework/abp/issues/1920 \ No newline at end of file diff --git a/docs/en/Entity-Framework-Core-PostgreSQL.md b/docs/en/Entity-Framework-Core-PostgreSQL.md deleted file mode 100644 index 9cdaeae34c..0000000000 --- a/docs/en/Entity-Framework-Core-PostgreSQL.md +++ /dev/null @@ -1,43 +0,0 @@ -# Switch to EF Core PostgreSQL Provider - -> [ABP CLI](CLI.md) and the [Get Started](https://abp.io/get-started) page already provides an option to create a new solution with PostgreSQL. See [that document](Entity-Framework-Core-Other-DBMS.md) to learn how to use. This document provides guidance for who wants to manually switch to PostgreSQL after creating the solution. - -This document explains how to switch to the **PostgreSQL** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. - -## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package - -`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.PostgreSql](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.PostgreSql) package. - -## Replace the Module Dependency - -Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCorePostgreSqlModule)` (also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.PostgreSql;`). - -## UseNpgsql() - -Find `UseSqlServer()` call in *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project and replace with `UseNpgsql()`. - - -Find `UseSqlServer()` call in *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project and replace with `UseNpgsql()`. - -> Depending on your solution structure, you may find more `UseSqlServer()` calls that needs to be changed. - -## Change the Connection Strings - -PostgreSql connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/postgresql/ ) for details of PostgreSql connection string options. - -You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. - -## Re-Generate the Migrations - -The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. - -* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). - -This will create a database migration with all database objects (tables) configured. - -Run the `.DbMigrator` project to create the database and seed the initial data. - -## Run the Application - -It is ready. Just run the application and enjoy coding. diff --git a/docs/en/Entity-Framework-Core-SQLite.md b/docs/en/Entity-Framework-Core-SQLite.md deleted file mode 100644 index 5015320af1..0000000000 --- a/docs/en/Entity-Framework-Core-SQLite.md +++ /dev/null @@ -1,53 +0,0 @@ -# Switch to EF Core SQLite Provider - -> [ABP CLI](CLI.md) and the [Get Started](https://abp.io/get-started) page already provides an option to create a new solution with SQLite. See [that document](Entity-Framework-Core-Other-DBMS.md) to learn how to use. This document provides guidance for who wants to manually switch to SQLite after creating the solution. - -This document explains how to switch to the **SQLite** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. - -## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package - -`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Sqlite](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Sqlite) package. - -## Replace the Module Dependency - -Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreSqliteModule)` (also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Sqlite;`). - -## UseSqlite() - -Find `UseSqlServer()` calls in your solution, replace with `UseSqlite()`. Check the following files: - -* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. -* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. - -> Depending on your solution structure, you may find more code files need to be changed. - -## Change the Connection Strings - -SQLite connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/sqlite/ ) for details of SQLite connection string options. - -An example connection string is - -``` -{ - "ConnectionStrings": { - "Default": "Filename=./MySQLiteDBFile.sqlite" - } -} -``` - -You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. - -## Re-Generate the Migrations - -The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. - -* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). - -This will create a database migration with all database objects (tables) configured. - -Run the `.DbMigrator` project to create the database and seed the initial data. - -## Run the Application - -It is ready. Just run the application and enjoy coding. diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md deleted file mode 100644 index 53de9eb8cd..0000000000 --- a/docs/en/Entity-Framework-Core.md +++ /dev/null @@ -1,965 +0,0 @@ -# Entity Framework Core Integration - -This document explains how to integrate EF Core as an ORM provider to ABP based applications and how to configure it. - -## Installation - -`Volo.Abp.EntityFrameworkCore` is the main NuGet package for the EF Core integration. Install it to your project (for a layered application, to your data/infrastructure layer): - -```` shell -abp add-package Volo.Abp.EntityFrameworkCore -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore). -> - -> Note: Instead, you can directly download a [startup template](https://abp.io/Templates) with EF Core pre-installed. - -### Database Management System Selection - -Entity Framework Core supports various database management systems ([see all](https://docs.microsoft.com/en-us/ef/core/providers/)). ABP framework and this document don't depend on any specific DBMS. If you are creating a [reusable application module](Modules/Index.md), avoid to depend on a specific DBMS package. However, in a final application you eventually will select a DBMS. - -> See [Switch to Another DBMS for Entity Framework Core](Entity-Framework-Core-Other-DBMS.md) document to learn how to switch the DBMS. - -## Creating DbContext - -Your `DbContext` class should be derived from `AbpDbContext` as shown below: - -````C# -using Microsoft.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore; - -namespace MyCompany.MyProject -{ - public class MyDbContext : AbpDbContext - { - //...your DbSet properties here - - public MyDbContext(DbContextOptions options) - : base(options) - { - } - } -} -```` - -### About the EF Core Fluent Mapping - -The [application startup template](Startup-Templates/Application.md) has been configured to use the [EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/) to map your entities to your database tables. - -You can still use the **data annotation attributes** (like `[Required]`) on the properties of your entity while the ABP documentation generally follows the **fluent mapping API** approach. It is up to you. - -ABP Framework has some **base entity classes** and **conventions** (see the [entities document](Entities.md)) and it provides some useful **extension methods** to configure the properties inherited from the base entity classes. - -#### ConfigureByConvention Method - -`ConfigureByConvention()` is the main extension method that **configures all the base properties** and conventions for your entities. So, it is a **best practice** to call this method for all your entities, in your fluent mapping code. - -**Example**: Assume that you've a `Book` entity derived from `AggregateRoot` base class: - -````csharp -public class Book : AuditedAggregateRoot -{ - public string Name { get; set; } -} -```` - -You can override the `OnModelCreating` method in your `DbContext` and configure the mapping as shown below: - -````csharp -protected override void OnModelCreating(ModelBuilder builder) -{ - //Always call the base method - base.OnModelCreating(builder); - - builder.Entity(b => - { - b.ToTable("Books"); - - //Configure the base properties - b.ConfigureByConvention(); - - //Configure other properties (if you are using the fluent API) - b.Property(x => x.Name).IsRequired().HasMaxLength(128); - }); -} -```` - -* Calling `b.ConfigureByConvention()` is important here to properly **configure the base properties**. -* You can configure the `Name` property here or you can use the **data annotation attributes** (see the [EF Core document](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)). - -> While there are many extension methods to configure your base properties, `ConfigureByConvention()` internally calls them if necessary. So, it is enough to call it. - -### Configure the Connection String Selection - -If you have multiple databases in your application, you can configure the connection string name for your `DbContext` using the `[ConnectionStringName]` attribute. Example: - -```csharp -[ConnectionStringName("MySecondConnString")] -public class MyDbContext : AbpDbContext -{ - -} -``` - -If you don't configure, the `Default` connection string is used. If you configure a specific connection string name, but not define this connection string name in the application configuration then it fallbacks to the `Default` connection string (see [the connection strings document](Connection-Strings.md) for more information). - -### AbpDbContextOptions - -`AbpDbContextOptions` is used to configure the `DbContext` options. When you create a new solution with the ABP's application startup template, you will see a simple configuration (in the `EntityFrameworkCore` integration project's module class) as shown below: - -````csharp -Configure(options => -{ - options.UseSqlServer(); -}); -```` - -That configuration configures the default DBMS as SQL Server for all the `DbContext`s of the application. That configuration was a shorthand notation and it can be done with the following code block: - -````csharp -Configure(options => -{ - options.Configure(opts => - { - opts.UseSqlServer(); - }); -}); -```` - -`options.Configure(...)` method has more options to configure. For example, you can set `DbContextOptions` (EF Core's native options) as shown below: - -````csharp -Configure(options => -{ - options.Configure(opts => - { - opts.DbContextOptions.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - }); -}); -```` - -Add actions for the `ConfigureConventions` and `OnModelCreating` methods of the `DbContext` as shown below: - -````csharp -options.DefaultConventionAction = (dbContext, builder) => -{ - // This action is called for ConfigureConventions method of all DbContexts. -}; - -options.ConfigureConventions((dbContext, builder) => -{ - // This action is called for ConfigureConventions method of specific DbContext. -}); - -options.DefaultOnModelCreatingAction = (dbContext, builder) => -{ - // This action is called for OnModelCreating method of all DbContexts. -}; - -options.ConfigureOnModelCreating((dbContext, builder) => -{ - // This action is called for OnModelCreating method of specific DbContext. -}); -```` - -If you have a single `DbContext` or you have multiple `DbContext`s but want to use the same DBMS and configuration for all, you can leave it as is. However, if you need to configure a different DBMS or customize the configuration for a specific `DbContext`, you can specify it as shown below: - -````csharp -Configure(options => -{ - // Default configuration for all DbContexts - options.Configure(opts => - { - opts.UseSqlServer(); - }); - - // Customized configuration for a specific DbContext - options.Configure(opts => - { - opts.UseMySQL(); - }); -}); -```` - -> See [Switch to Another DBMS for Entity Framework Core](Entity-Framework-Core-Other-DBMS.md) document to learn how to configure the DBMS. - -## Registering DbContext To Dependency Injection - -Use `AddAbpDbContext` method in your module to register your `DbContext` class for [dependency injection](Dependency-Injection.md) system. - -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.Modularity; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpEntityFrameworkCoreModule))] - public class MyModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddAbpDbContext(); - - //... - } - } -} -```` - -### Add Default Repositories - -ABP can automatically create default [generic repositories](Repositories.md) for the entities in your DbContext. Just use `AddDefaultRepositories()` option on the registration: - -````C# -services.AddAbpDbContext(options => -{ - options.AddDefaultRepositories(); -}); -```` - -This will create a repository for each [aggregate root entity](Entities.md) (classes derived from `AggregateRoot`) by default. If you want to create repositories for other entities too, then set `includeAllEntities` to `true`: - -````C# -services.AddAbpDbContext(options => -{ - options.AddDefaultRepositories(includeAllEntities: true); -}); -```` - -Then you can inject and use `IRepository` in your services. Assume that you have a `Book` entity with `Guid` primary key: - -```csharp -public class Book : AggregateRoot -{ - public string Name { get; set; } - - public BookType Type { get; set; } -} -``` - -(`BookType` is a simple `enum` here and not important) And you want to create a new `Book` entity in a [domain service](Domain-Services.md): - -````csharp -public class BookManager : DomainService -{ - private readonly IRepository _bookRepository; - - //inject default repository to the constructor - public BookManager(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task CreateBook(string name, BookType type) - { - Check.NotNullOrWhiteSpace(name, nameof(name)); - - var book = new Book - { - Id = GuidGenerator.Create(), - Name = name, - Type = type - }; - - //Use a standard repository method - await _bookRepository.InsertAsync(book); - - return book; - } -} -```` - -This sample uses `InsertAsync` method to insert a new entity to the database. - -### Add Custom Repositories - -Default generic repositories are powerful enough in most cases (since they implement `IQueryable`). However, you may need to create a custom repository to add your own repository methods. Assume that you want to delete all books by type. - -It's suggested to define an interface for your custom repository: - -````csharp -public interface IBookRepository : IRepository -{ - Task DeleteBooksByType(BookType type); -} -```` - -You generally want to derive from the `IRepository` to inherit standard repository methods (while, you don't have to do). Repository interfaces are defined in the domain layer of a layered application. They are implemented in the data/infrastructure layer (`EntityFrameworkCore` project in a [startup template](https://abp.io/Templates)). - -Example implementation of the `IBookRepository` interface: - -````csharp -public class BookRepository - : EfCoreRepository, IBookRepository -{ - public BookRepository(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task DeleteBooksByType(BookType type) - { - var dbContext = await GetDbContextAsync(); - await dbContext.Database.ExecuteSqlRawAsync( - $"DELETE FROM Books WHERE Type = {(int)type}" - ); - } -} -```` - -Now, it's possible to [inject](Dependency-Injection.md) the `IBookRepository` and use the `DeleteBooksByType` method when needed. - -#### Override the Default Generic Repository - -Even if you create a custom repository, you can still inject the default generic repository (`IRepository` for this example). Default repository implementation will not use the class you have created. - -If you want to replace default repository implementation with your custom repository, do it inside the `AddAbpDbContext` options: - -````csharp -context.Services.AddAbpDbContext(options => -{ - options.AddDefaultRepositories(); - - //Replaces IRepository - options.AddRepository(); -}); -```` - -This is especially important when you want to **override a base repository method** to customize it. For instance, you may want to override `DeleteAsync` method to delete a specific entity in a more efficient way: - -````csharp -public async override Task DeleteAsync( - Guid id, - bool autoSave = false, - CancellationToken cancellationToken = default) -{ - //TODO: Custom implementation of the delete method -} -```` - -## Loading Related Entities - -Assume that you've an `Order` with a collection of `OrderLine`s and the `OrderLine` has a navigation property to the `Order`: - -````csharp -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Volo.Abp.Auditing; -using Volo.Abp.Domain.Entities; - -namespace MyCrm -{ - public class Order : AggregateRoot, IHasCreationTime - { - public Guid CustomerId { get; set; } - public DateTime CreationTime { get; set; } - - public ICollection Lines { get; set; } //Sub collection - - public Order() - { - Lines = new Collection(); - } - } - - public class OrderLine : Entity - { - public Order Order { get; set; } //Navigation property - public Guid OrderId { get; set; } - - public Guid ProductId { get; set; } - public int Count { get; set; } - public double UnitPrice { get; set; } - } -} - -```` - -And defined the database mapping as shown below: - -````csharp -builder.Entity(b => -{ - b.ToTable("Orders"); - b.ConfigureByConvention(); - - //Define the relation - b.HasMany(x => x.Lines) - .WithOne(x => x.Order) - .HasForeignKey(x => x.OrderId) - .IsRequired(); -}); - -builder.Entity(b => -{ - b.ToTable("OrderLines"); - b.ConfigureByConvention(); -}); -```` - -When you query an `Order`, you may want to **include** all the `OrderLine`s in a single query or you may want to **load them later** on demand. - -> Actually these are not directly related to the ABP Framework. You can follow the [EF Core documentation](https://docs.microsoft.com/en-us/ef/core/querying/related-data/) to learn all the details. This section will cover some topics related to the ABP Framework. - -### Eager Loading / Load With Details - -You have different options when you want to load the related entities while querying an entity. - -#### Repository.WithDetails - -`IRepository.WithDetailsAsync(...)` can be used to get an `IQueryable` by including one relation collection/property. - -**Example: Get an order with lines** - -````csharp -using System; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Domain.Services; - -namespace AbpDemo.Orders -{ - public class OrderManager : DomainService - { - private readonly IRepository _orderRepository; - - public OrderManager(IRepository orderRepository) - { - _orderRepository = orderRepository; - } - - public async Task TestWithDetails(Guid id) - { - //Get a IQueryable by including sub collections - var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines); - - //Apply additional LINQ extension methods - var query = queryable.Where(x => x.Id == id); - - //Execute the query and get the result - var order = await AsyncExecuter.FirstOrDefaultAsync(query); - } - } -} -```` - -> `AsyncExecuter` is used to execute async LINQ extensions without depending on the EF Core. If you add EF Core NuGet package reference to your project, then you can directly use `await query.FirstOrDefaultAsync()`. But, this time you depend on the EF Core in your domain layer. See the [repository document](Repositories.md) to learn more. - -**Example: Get a list of orders with their lines** - -````csharp -public async Task TestWithDetails() -{ - //Get a IQueryable by including sub collections - var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines); - - //Execute the query and get the result - var orders = await AsyncExecuter.ToListAsync(queryable); -} -```` - -> `WithDetailsAsync` method can get more than one expression parameter if you need to include more than one navigation property or collection. - -#### DefaultWithDetailsFunc - -If you don't pass any expression to the `WithDetailsAsync` method, then it includes all the details using the `DefaultWithDetailsFunc` option you provide. - -You can configure `DefaultWithDetailsFunc` for an entity in the `ConfigureServices` method of your [module](Module-Development-Basics.md) in your `EntityFrameworkCore` project. - -**Example: Include `Lines` while querying an `Order`** - -````csharp -Configure(options => -{ - options.Entity(orderOptions => - { - orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines); - }); -}); -```` - -> You can fully use the EF Core API here since this is located in the EF Core integration project. - -Then you can use the `WithDetails` without any parameter: - -````csharp -public async Task TestWithDetails() -{ - //Get a IQueryable by including all sub collections - var queryable = await _orderRepository.WithDetailsAsync(); - - //Execute the query and get the result - var orders = await AsyncExecuter.ToListAsync(queryable); -} -```` - -`WithDetailsAsync()` executes the expression you've setup as the `DefaultWithDetailsFunc`. - -#### Repository Get/Find Methods - -Some of the standard [Repository](Repositories.md) methods have optional `includeDetails` parameters; - -* `GetAsync` and `FindAsync` gets `includeDetails` with default value is `true`. -* `GetListAsync` and `GetPagedListAsync` gets `includeDetails` with default value is `false`. - -That means, the methods return a **single entity includes details** by default while list returning methods don't include details by default. You can explicitly pass `includeDetails` to change the behavior. - -> These methods use the `DefaultWithDetailsFunc` option that is explained above. - -**Example: Get an order with details** - -````csharp -public async Task TestWithDetails(Guid id) -{ - var order = await _orderRepository.GetAsync(id); -} -```` - -**Example: Get an order without details** - -````csharp -public async Task TestWithoutDetails(Guid id) -{ - var order = await _orderRepository.GetAsync(id, includeDetails: false); -} -```` - -**Example: Get list of entities with details** - -````csharp -public async Task TestWithDetails() -{ - var orders = await _orderRepository.GetListAsync(includeDetails: true); -} -```` - -#### Alternatives - -The repository pattern tries to encapsulate the EF Core, so your options are limited. If you need an advanced scenario, you can follow one of the options; - -* Create a custom repository method and use the complete EF Core API. -* Reference to the `Volo.Abp.EntityFrameworkCore` package from your project. In this way, you can directly use `Include` and `ThenInclude` in your code. - -See also [eager loading document](https://docs.microsoft.com/en-us/ef/core/querying/related-data/eager) of the EF Core. - -### Explicit / Lazy Loading - -If you don't include relations while querying an entity and later need to access to a navigation property or collection, you have different options. - -#### EnsurePropertyLoadedAsync / EnsureCollectionLoadedAsync - -Repositories provide `EnsurePropertyLoadedAsync` and `EnsureCollectionLoadedAsync` extension methods to **explicitly load** a navigation property or sub collection. - -**Example: Load Lines of an Order when needed** - -````csharp -public async Task TestWithDetails(Guid id) -{ - var order = await _orderRepository.GetAsync(id, includeDetails: false); - //order.Lines is empty on this stage - - await _orderRepository.EnsureCollectionLoadedAsync(order, x => x.Lines); - //order.Lines is filled now -} -```` - -`EnsurePropertyLoadedAsync` and `EnsureCollectionLoadedAsync` methods do nothing if the property or collection was already loaded. So, calling multiple times has no problem. - -See also [explicit loading document](https://docs.microsoft.com/en-us/ef/core/querying/related-data/explicit) of the EF Core. - -#### Lazy Loading with Proxies - -Explicit loading may not be possible in some cases, especially when you don't have a reference to the `Repository` or `DbContext`. Lazy Loading is a feature of the EF Core that loads the related properties / collections when you first access to it. - -To enable lazy loading; - -1. Install the [Microsoft.EntityFrameworkCore.Proxies](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Proxies/) package into your project (typically to the EF Core integration project) -2. Configure `UseLazyLoadingProxies` for your `DbContext` (in the `ConfigureServices` method of your module in your EF Core project). Example: - -````csharp -Configure(options => -{ - options.PreConfigure(opts => - { - opts.DbContextOptions.UseLazyLoadingProxies(); //Enable lazy loading - }); - - options.UseSqlServer(); -}); -```` - -3. Make your navigation properties and collections `virtual`. Examples: - -````csharp -public virtual ICollection Lines { get; set; } //virtual collection -public virtual Order Order { get; set; } //virtual navigation property -```` - -Once you enable lazy loading and arrange your entities, you can freely access to the navigation properties and collections: - -````csharp -public async Task TestWithDetails(Guid id) -{ - var order = await _orderRepository.GetAsync(id); - //order.Lines is empty on this stage - - var lines = order.Lines; - //order.Lines is filled (lazy loaded) -} -```` - -Whenever you access to a property/collection, EF Core automatically performs an additional query to load the property/collection from the database. - -> Lazy loading should be carefully used since it may cause performance problems in some specific cases. - -See also [lazy loading document](https://docs.microsoft.com/en-us/ef/core/querying/related-data/lazy) of the EF Core. - -## Read-Only Repositories - -ABP Framework provides read-only [repository](Repositories.md) interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`) to explicitly indicate that your purpose is to query data, but not change it. If so, you can inject these interfaces into your services. - -Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still use the [AsTracking()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.astracking) extension method on the LINQ expression, or `EnableTracking()` extension method on the repository object (See *Enabling / Disabling the Change Tracking* section in this document). - -> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) then casted it to a read-only repository interface. - -## Enabling / Disabling the Change Tracking - -In addition to the read-only repositories, ABP allows to manually control the change tracking behavior for querying objects. Please see the *Enabling / Disabling the Change Tracking* section of the [Repositories documentation](Repositories.md) to learn how to use it. - -## Access to the EF Core API - -In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example: - -````csharp -public async Task TestAsync() -{ - var dbContext = await _orderRepository.GetDbContextAsync(); - var dbSet = await _orderRepository.GetDbSetAsync(); - //var dbSet = dbContext.Set(); //Alternative, when you have the DbContext -} -```` - -* `GetDbContextAsync` returns a `DbContext` reference instead of `BookStoreDbContext`. You can cast it if you need. However, you don't need it in most cases. - -> Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the `DbContext`. This breaks encapsulation, but this is what you want in that case. - -## Extra Properties & Object Extension Manager - -Extra Properties system allows you to set/get dynamic properties to entities those implement the `IHasExtraProperties` interface. It is especially useful when you want to add custom properties to the entities defined in an [application module](Modules/Index.md), when you use the module as package reference. - -By default, all the extra properties of an entity are stored as a single `JSON` object in the database. - -Entity extension system allows you to to store desired extra properties in separate fields in the related database table. For more information about the extra properties & the entity extension system, see the following documents: - -* [Customizing the Application Modules: Extending Entities](Customizing-Application-Modules-Extending-Entities.md) -* [Entities](Entities.md) - -This section only explains the EF Core related usage of the `ObjectExtensionManager`. - -### ObjectExtensionManager.Instance - -`ObjectExtensionManager` implements the singleton pattern, so you need to use the static `ObjectExtensionManager.Instance` to perform all the operations. - -### MapEfCoreProperty - -`MapEfCoreProperty` is a shortcut extension method to define an extension property for an entity and map to the database. - -**Example**: Add `Title` property (database field) to the `IdentityRole` entity: - -````csharp -ObjectExtensionManager.Instance - .MapEfCoreProperty( - "Title", - (entityBuilder, propertyBuilder) => - { - propertyBuilder.HasMaxLength(64); - } - ); -```` - -### MapEfCoreEntity - -`MapEfCoreEntity` is a shortcut extension method to configure the `Entity`. - -**Example**: Set the max length of `Name` to the `IdentityRole` entity: - -````csharp -ObjectExtensionManager.Instance - .MapEfCoreEntity(builder => - { - builder.As>().Property(x => x.Name).HasMaxLength(200); - }); -```` - -### MapEfCoreDbContext - -`MapEfCoreDbContext` is a shortcut extension method to configure the `DbContext`. - -**Example**: Set the max length of `Name` to the `IdentityRole` entity of `IdentityDbContext`: - -````csharp -ObjectExtensionManager.Instance.MapEfCoreDbContext(b => -{ - b.Entity().Property(x => x.Name).HasMaxLength(200); -}); -```` - -If the related module has implemented this feature(explained below), then the new property is added to the model or the DbContext/Entity configure changed. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field. - -> The `MapEfCoreProperty`, `MapEfCoreEntity` and `MapEfCoreDbContext` methods must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEfCoreEntityExtensionMappings` class that is safe to use this method inside. - -### ConfigureEfCoreEntity, ApplyObjectExtensionMappings and TryConfigureObjectExtensions - -If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity`, `ApplyObjectExtensionMappings` and `TryConfigureObjectExtensions` extension methods in your entity mapping. - -**Example**: -````csharp -public static class QADbContextModelCreatingExtensions -{ - public static void ConfigureQA( - this ModelBuilder builder, - Action optionsAction = null) - { - Check.NotNull(builder, nameof(builder)); - - var options = new QAModelBuilderConfigurationOptions( - QADatabaseDbProperties.DbTablePrefix, - QADatabaseDbProperties.DbSchema - ); - - optionsAction?.Invoke(options); - - builder.Entity(b => - { - b.ToTable(options.TablePrefix + "Questions", options.Schema); - b.ConfigureByConvention(); - //... - - //Call this in the end of buildAction. - b.ApplyObjectExtensionMappings(); - }); - - //... - - //Call this in the end of ConfigureQA. - builder.TryConfigureObjectExtensions(); - } -} -```` - -> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` and `ConfigureEfCoreEntity` methods. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention. - -> The `Object Extension` feature need the `Change Tracking`, which means you can't use the read-only repositories for the entities that have `extension properties(MapEfCoreProperty)`, Please see the [Repositories documentation](Repositories.md) to learn the change tracking behavior. - -See the "*ConfigureByConvention Method*" section above for more information. - -## Advanced Topics - -### Controlling the Multi-Tenancy - -If your solution is [multi-tenant](Multi-Tenancy.md), tenants may have **separate databases**, you have **multiple** `DbContext` classes in your solution and some of your `DbContext` classes should be usable **only from the host side**, it is suggested to add `[IgnoreMultiTenancy]` attribute on your `DbContext` class. In this case, ABP guarantees that the related `DbContext` always uses the host [connection string](Connection-Strings.md), even if you are in a tenant context. - -**Example:** - -````csharp -[IgnoreMultiTenancy] -public class MyDbContext : AbpDbContext -{ - ... -} -```` - -Do not use the `[IgnoreMultiTenancy]` attribute if any one of your entities in your `DbContext` can be persisted in a tenant database. - -> When you use repositories, ABP already uses the host database for the entities don't implement the `IMultiTenant` interface. So, most of time you don't need to `[IgnoreMultiTenancy]` attribute if you are using the repositories to work with the database. - -### Set Default Repository Classes - -Default generic repositories are implemented by `EfCoreRepository` class by default. You can create your own implementation and use it for all the default repository implementations. - -First, define your default repository classes like that: - -```csharp -public class MyRepositoryBase - : EfCoreRepository - where TEntity : class, IEntity -{ - public MyRepositoryBase(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } -} - -public class MyRepositoryBase - : EfCoreRepository - where TEntity : class, IEntity -{ - public MyRepositoryBase(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } -} -``` - -First one is for [entities with composite keys](Entities.md), second one is for entities with single primary key. - -It's suggested to inherit from the `EfCoreRepository` class and override methods if needed. Otherwise, you will have to implement all the standard repository methods manually. - -Now, you can use `SetDefaultRepositoryClasses` option: - -```csharp -context.Services.AddAbpDbContext(options => -{ - options.SetDefaultRepositoryClasses( - typeof(MyRepositoryBase<,>), - typeof(MyRepositoryBase<>) - ); - - //... -}); -``` - -### Set Base DbContext Class or Interface for Default Repositories - -If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example: - -````csharp -public interface IBookStoreDbContext : IEfCoreDbContext -{ - DbSet Books { get; } -} -```` - -`IBookStoreDbContext` is implemented by the `BookStoreDbContext` class. Then you can use generic overload of the `AddDefaultRepositories`: - -````csharp -context.Services.AddAbpDbContext(options => -{ - options.AddDefaultRepositories(); - //... -}); -```` - -Now, your custom `BookRepository` can also use the `IBookStoreDbContext` interface: - -````csharp -public class BookRepository : EfCoreRepository, IBookRepository -{ - //... -} -```` - -One advantage of using an interface for a DbContext is then it will be replaceable by another implementation. - -### Replace Other DbContextes - -Once you properly define and use an interface for DbContext, then any other implementation can use the following ways to replace it: - -#### ReplaceDbContext Attribute - -```csharp -[ReplaceDbContext(typeof(IBookStoreDbContext))] -public class OtherDbContext : AbpDbContext, IBookStoreDbContext -{ - //... -} -``` - -#### ReplaceDbContext Option - -````csharp -context.Services.AddAbpDbContext(options => -{ - //... - options.ReplaceDbContext(); -}); -```` - -In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime. - -#### Replacing with Multi-Tenancy - -It is also possible to replace a DbContext based on the [multi-tenancy](Multi-Tenancy.md) side. `ReplaceDbContext` attribute and `ReplaceDbContext` method can get a `MultiTenancySides` option with a default value of `MultiTenancySides.Both`. - -**Example:** Replace DbContext only for tenants, using the `ReplaceDbContext` attribute - -````csharp -[ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)] -```` - -**Example:** Replace DbContext only for the host side, using the `ReplaceDbContext` method - -````csharp -options.ReplaceDbContext(MultiTenancySides.Host); -```` - -### Split Queries - -ABP enables [split queries](https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries) globally by default for better performance. You can change it as needed. - -**Example** - -````csharp -Configure(options => -{ - options.UseSqlServer(optionsBuilder => - { - optionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery); - }); -}); -```` - -### Customize Bulk Operations - -If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IEfCoreBulkOperationProvider`. - -- You may use example template below: - -```csharp -public class MyCustomEfCoreBulkOperationProvider - : IEfCoreBulkOperationProvider, ITransientDependency -{ - public async Task DeleteManyAsync( - IEfCoreRepository repository, - IEnumerable entities, - bool autoSave, - CancellationToken cancellationToken) - where TDbContext : IEfCoreDbContext - where TEntity : class, IEntity - { - // Your logic here. - } - - public async Task InsertManyAsync( - IEfCoreRepository repository, - IEnumerable entities, - bool autoSave, - CancellationToken cancellationToken) - where TDbContext : IEfCoreDbContext - where TEntity : class, IEntity - { - // Your logic here. - } - - public async Task UpdateManyAsync( - IEfCoreRepository repository, - IEnumerable entities, - bool autoSave, - CancellationToken cancellationToken) - where TDbContext : IEfCoreDbContext - where TEntity : class, IEntity - { - // Your logic here. - } -} -``` - -## See Also - -* [Entities](Entities.md) -* [Repositories](Repositories.md) -* [Video tutorial](https://abp.io/video-courses/essentials/abp-ef-core) \ No newline at end of file diff --git a/docs/en/Event-Bus.md b/docs/en/Event-Bus.md deleted file mode 100644 index a0e6859465..0000000000 --- a/docs/en/Event-Bus.md +++ /dev/null @@ -1,10 +0,0 @@ -# Event Bus - -An event bus is a mediator that transfers a message from a sender to a receiver. In this way, it provides a loosely coupled communication way between objects, services and applications. - -## Event Bus Types - -ABP Framework provides two type of event buses; - -* **[Local Event Bus](Local-Event-Bus.md)** is suitable for in-process messaging. -* **[Distributed Event Bus](Distributed-Event-Bus.md)** is suitable for inter-process messaging, like microservices publishing and subscribing to distributed events. \ No newline at end of file diff --git a/docs/en/Exception-Handling.md b/docs/en/Exception-Handling.md deleted file mode 100644 index 32f45b6b28..0000000000 --- a/docs/en/Exception-Handling.md +++ /dev/null @@ -1,343 +0,0 @@ -# Exception Handling - -ABP provides a built-in infrastructure and offers a standard model for handling exceptions. - -* Automatically **handles all exceptions** and sends a standard **formatted error message** to the client for an API/AJAX request. -* Automatically hides **internal infrastructure errors** and returns a standard error message. -* Provides an easy and configurable way to **localize** exception messages. -* Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map custom exceptions. - -## Automatic Exception Handling - -`AbpExceptionFilter` handles an exception if **any of the following conditions** are met: - -* Exception is thrown by a **controller action** which returns an **object result** (not a view result). -* The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`). -* Client explicitly accepts the `application/json` content type (via `accept` HTTP header). - -If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client. - -### Error Message Format - -Error Message is an instance of the `RemoteServiceErrorResponse` class. The simplest error JSON has a **message** property as shown below: - -````json -{ - "error": { - "message": "This topic is locked and can not add a new message" - } -} -```` - -There are **optional fields** those can be filled based upon the exception that has occurred. - -##### Error Code - -Error **code** is an optional and unique string value for the exception. Thrown `Exception` should implement the `IHasErrorCode` interface to fill this field. Example JSON value: - -````json -{ - "error": { - "code": "App:010042", - "message": "This topic is locked and can not add a new message" - } -} -```` - -Error code can also be used to localize the exception and customize the HTTP status code (see the related sections below). - -##### Error Details - -Error **details** in an optional field of the JSON error message. Thrown `Exception` should implement the `IHasErrorDetails` interface to fill this field. Example JSON value: - -```json -{ - "error": { - "code": "App:010042", - "message": "This topic is locked and can not add a new message", - "details": "A more detailed info about the error..." - } -} -``` - -##### Validation Errors - -**validationErrors** is a standard field that is filled if the thrown exception implements the `IHasValidationErrors` interface. - -````json -{ - "error": { - "code": "App:010046", - "message": "Your request is not valid, please correct and try again!", - "validationErrors": [{ - "message": "Username should be minimum length of 3.", - "members": ["userName"] - }, - { - "message": "Password is required", - "members": ["password"] - }] - } -} -```` - -`AbpValidationException` implements the `IHasValidationErrors` interface and it is automatically thrown by the framework when a request input is not valid. So, usually you don't need to deal with validation errors unless you have higly customised validation logic. - -### Logging - -Caught exceptions are automatically logged. - -#### Log Level - -Exceptions are logged with the `Error` level by default. The Log level can be determined by the exception if it implements the `IHasLogLevel` interface. Example: - -````C# -public class MyException : Exception, IHasLogLevel -{ - public LogLevel LogLevel { get; set; } = LogLevel.Warning; - - //... -} -```` - -#### Self Logging Exceptions - -Some exception types may need to write additional logs. They can implement the `IExceptionWithSelfLogging` if needed. Example: - -````C# -public class MyException : Exception, IExceptionWithSelfLogging -{ - public void Log(ILogger logger) - { - //...log additional info - } -} -```` - -> `ILogger.LogException` extension methods is used to write exception logs. You can use the same extension method when needed. - -## Business Exceptions - -Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception. - -`BusinessException` implements the `IBusinessException` interface in addition to the `IHasErrorCode`, `IHasErrorDetails` and `IHasLogLevel` interfaces. The default log level is `Warning`. - -Usually you have an error code related to a particular business exception. For example: - -````C# -throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer); -```` - -`QaErrorCodes.CanNotVoteYourOwnAnswer` is just a `const string`. The following error code format is recommended: - -```` -: -```` - -**code-namespace** is a **unique value** specific to your module/application. Example: - -```` -Volo.Qa:010002 -```` - -`Volo.Qa` is the code-namespace here. code-namespace is then will be used while **localizing** exception messages. - -* You can **directly throw** a `BusinessException` or **derive** your own exception types from it when needed. -* All properties are optional for the `BusinessException` class. But you generally set either `ErrorCode` or `Message` property. - -## Exception Localization - -One problem with throwing exceptions is how to localize error messages while sending it to the client. ABP offers two models and their variants. - -### User Friendly Exception - -If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client. - -`UserFriendlyException` class is the built-in implementation of the `IUserFriendlyException` interface. Example usage: - -````C# -throw new UserFriendlyException( - "Username should be unique!" -); -```` - -In this way, there is **no need for localization** at all. If you want to localize the message, you can inject and use the standard **string localizer** (see the [localization document](Localization.md)). Example: - -````C# -throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"]); -```` - -Then define it in the **localization resource** for each language. Example: - -````json -{ - "culture": "en", - "texts": { - "UserNameShouldBeUniqueMessage": "Username should be unique!" - } -} -```` - -String localizer already supports **parameterized messages**. For example: - -````C# -throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage", "john"]); -```` - -Then the localization text can be: - -````json -"UserNameShouldBeUniqueMessage": "Username should be unique! '{0}' is already taken!" -```` - -* The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class. - -### Using Error Codes - -`UserFriendlyException` is fine, but it has a few problems in advanced usages: - -* It requires you to **inject the string localizer** everywhere and always use it while throwing exceptions. -* However, in some of the cases, it may **not be possible** to inject the string localizer (in a static context or in an entity method). - -Instead of localizing the message while throwing the exception, you can separate the process using **error codes**. - -First, define the **code-namespace** to **localization resource** mapping in the module configuration: - -````C# -services.Configure(options => -{ - options.MapCodeNamespace("Volo.Qa", typeof(QaResource)); -}); -```` - -Then any of the exceptions with `Volo.Qa` namespace will be localized using their given localization resource. The localization resource should always have an entry with the error code key. Example: - -````json -{ - "culture": "en", - "texts": { - "Volo.Qa:010002": "You can not vote your own answer!" - } -} -```` - -Then a business exception can be thrown with the error code: - -````C# -throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer); -```` - -* Throwing any exception implementing the `IHasErrorCode` interface behaves the same. So, the error code localization approach is not unique to the `BusinessException` class. -* Defining localized string is not required for an error message. If it's not defined, ABP sends the default error message to the client. It does not use the `Message` property of the exception! if you want that, use the `UserFriendlyException` (or use an exception type that implements the `IUserFriendlyException` interface). - -#### Using Message Parameters - -If you have a parameterized error message, then you can set it with the exception's `Data` property. For example: - -````C# -throw new BusinessException("App:010046") -{ - Data = - { - {"UserName", "john"} - } -}; - -```` - -Fortunately there is a shortcut way to code this: - -````C# -throw new BusinessException("App:010046") - .WithData("UserName", "john"); -```` - -Then the localized text can contain the `UserName` parameter: - -````json -{ - "culture": "en", - "texts": { - "App:010046": "Username should be unique. '{UserName}' is already taken!" - } -} -```` - -* `WithData` can be chained with more than one parameter (like `.WithData(...).WithData(...)`). - -## HTTP Status Code Mapping - -ABP tries to automatically determine the most suitable HTTP status code for common exception types by following these rules: - -* For the `AbpAuthorizationException`: - * Returns `401` (unauthorized) if user has not logged in. - * Returns `403` (forbidden) if user has logged in. -* Returns `400` (bad request) for the `AbpValidationException`. -* Returns `404` (not found) for the `EntityNotFoundException`. -* Returns `403` (forbidden) for the `IBusinessException` (and `IUserFriendlyException` since it extends the `IBusinessException`). -* Returns `501` (not implemented) for the `NotImplementedException`. -* Returns `500` (internal server error) for other exceptions (those are assumed as infrastructure exceptions). - -The `IHttpExceptionStatusCodeFinder` is used to automatically determine the HTTP status code. The default implementation is the `DefaultHttpExceptionStatusCodeFinder` class. It can be replaced or extended as needed. - -### Custom Mappings - -Automatic HTTP status code determination can be overrided by custom mappings. For example: - -````C# -services.Configure(options => -{ - options.Map("Volo.Qa:010002", HttpStatusCode.Conflict); -}); -```` - -## Subscribing to the Exceptions - -It is possible to be informed when the ABP Framework **handles an exception**. It automatically **logs** all the exceptions to the standard [logger](Logging.md), but you may want to do more. - -In this case, create a class derived from the `ExceptionSubscriber` class in your application: - -````csharp -public class MyExceptionSubscriber : ExceptionSubscriber -{ - public async override Task HandleAsync(ExceptionNotificationContext context) - { - //TODO... - } -} -```` - -The `context` object contains necessary information about the exception occurred. - -> You can have multiple subscribers, each gets a copy of the exception. Exceptions thrown by your subscriber is ignored (but still logged). - -## Built-In Exceptions - -Some exception types are automatically thrown by the framework: - -- `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See [authorization](Authorization.md) for more. -- `AbpValidationException` is thrown if the input of the current request is not valid. See [validation](Validation.md) for more. -- `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](Repositories.md). - -You can also throw these type of exceptions in your code (although it's rarely needed). - -## AbpExceptionHandlingOptions - -`AbpExceptionHandlingOptions` is the main [options object](Options.md) to configure the exception handling system. You can configure it in the `ConfigureServices` method of your [module](Module-Development-Basics.md): - -````csharp -Configure(options => -{ - options.SendExceptionsDetailsToClients = true; - options.SendStackTraceToClients = false; -}); -```` - -Here, a list of the options you can configure: - -* `SendExceptionsDetailsToClients` (default: `false`): You can enable or disable sending exception details to the client. -* `SendStackTraceToClients` (default: `true`): You can enable or disable sending the stack trace of exception to the client. If you want to send the stack trace to the client, you must set both `SendStackTraceToClients` and `SendExceptionsDetailsToClients` options to `true` otherwise, the stack trace will not be sent to the client. - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/exception-handling) \ No newline at end of file diff --git a/docs/en/Extension-Methods-And-Helpers.md b/docs/en/Extension-Methods-And-Helpers.md deleted file mode 100644 index 8dac0a56ee..0000000000 --- a/docs/en/Extension-Methods-And-Helpers.md +++ /dev/null @@ -1,3 +0,0 @@ -# Extension Methods & Helpers - -TODO \ No newline at end of file diff --git a/docs/en/Features.md b/docs/en/Features.md deleted file mode 100644 index e38a8c2a5d..0000000000 --- a/docs/en/Features.md +++ /dev/null @@ -1,440 +0,0 @@ -# Features - -ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. - -The runtime value for a feature is generally a `boolean` value, like `true` (enabled) or `false` (disabled). However, you can get/set **any kind** of value for feature. - -Feature system was originally designed to control the tenant features in a **[multi-tenant](Multi-Tenancy.md)** application. However, it is **extensible** and capable of determining the features by any condition. - -> The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). - -## Checking for the Features - -Before explaining to define features, let's see how to check a feature value in your application code. - -### RequiresFeature Attribute - -`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is `true` (enabled) or not. It is a useful shortcut for the `boolean` features. - -**Example: Check if the "PDF Reporting" feature enabled** - -```csharp -public class ReportingAppService : ApplicationService, IReportingAppService -{ - [RequiresFeature("MyApp.PdfReporting")] - public async Task GetPdfReportAsync() - { - //TODO... - } -} -``` - -* `RequiresFeature(...)` simply gets a feature name to check if it is enabled or not. If not enabled, an authorization [exception](Exception-Handling.md) is thrown and a proper response is returned to the client side. -* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the methods of that class require the given feature. -* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if any of the features enabled. Use `RequiresAll` option, like `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to check all of the features to be enabled. -* Multiple usage of `[RequiresFeature]` attribute is supported for a method or class. ABP checks all of them in that case. - -> Feature name can be any arbitrary string. It should be unique for a feature. - -#### About the Interception - -ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class (application services, controllers...) that is injected from the [dependency injection](Dependency-Injection.md). - -However, there are **some rules should be followed** in order to make it working; - -* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work. -* Only `async` methods (methods returning a `Task` or `Task`) are intercepted. - -> There is an exception for the **controller and razor page methods**. They **don't require** the following the rules above, since ABP Framework uses the action/page filters to implement the feature checking in this case. - -### IFeatureChecker Service - -`IFeatureChecker` allows to check a feature in your application code. - -#### IsEnabledAsync - -Returns `true` if the given feature is enabled. So, you can conditionally execute your business flow. - -**Example: Check if the "PDF Reporting" feature enabled** - -```csharp -public class ReportingAppService : ApplicationService, IReportingAppService -{ - private readonly IFeatureChecker _featureChecker; - - public ReportingAppService(IFeatureChecker featureChecker) - { - _featureChecker = featureChecker; - } - - public async Task GetPdfReportAsync() - { - if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting")) - { - //TODO... - } - else - { - //TODO... - } - } -} -``` - -`IsEnabledAsync` has overloads to check multiple features in one method call. - -#### GetOrNullAsync - -Gets the current value for a feature. This method returns a `string`, so you store any kind of value inside it, by converting to or from `string`. - -**Example: Check the maximum product count allowed** - -```csharp -public class ProductController : AbpController -{ - private readonly IFeatureChecker _featureChecker; - - public ProductController(IFeatureChecker featureChecker) - { - _featureChecker = featureChecker; - } - - public async Task Create(CreateProductModel model) - { - var currentProductCount = await GetCurrentProductCountFromDatabase(); - - //GET THE FEATURE VALUE - var maxProductCountLimit = - await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount"); - - if (currentProductCount >= Convert.ToInt32(maxProductCountLimit)) - { - throw new BusinessException( - "MyApp:ReachToMaxProductCountLimit", - $"You can not create more than {maxProductCountLimit} products!" - ); - } - - //TODO: Create the product in the database... - } - - private async Task GetCurrentProductCountFromDatabase() - { - throw new System.NotImplementedException(); - } -} -``` - -This example uses a numeric value as a feature limit product counts for a user/tenant in a SaaS application. - -Instead of manually converting the value to `int`, you can use the generic overload of the `GetAsync` method: - -```csharp -var maxProductCountLimit = await _featureChecker.GetAsync("MyApp.MaxProductCount"); -``` - -#### Extension Methods - -There are some useful extension methods for the `IFeatureChecker` interface; - -* `Task GetAsync(string name, T defaultValue = default)`: Used to get a value of a feature with the given type `T`. Allows to specify a `defaultValue` that is returned when the feature value is `null`. -* `CheckEnabledAsync(string name)`: Checks if given feature is enabled. Throws an `AbpAuthorizationException` if the feature was not `true` (enabled). - -## Defining the Features - -A feature should be defined to be able to check it. - -### FeatureDefinitionProvider - -Create a class inheriting the `FeatureDefinitionProvider` to define features. - -**Example: Defining features** - -```csharp -using Volo.Abp.Features; - -namespace FeaturesDemo -{ - public class MyFeatureDefinitionProvider : FeatureDefinitionProvider - { - public override void Define(IFeatureDefinitionContext context) - { - var myGroup = context.AddGroup("MyApp"); - - myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false"); - myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10"); - } - } -} -``` - -> ABP automatically discovers this class and registers the features. No additional configuration required. - -> This class is generally created in the `Application.Contracts` project of your solution. - -* In the `Define` method, you first need to add a **feature group** for your application/module or get an existing group then add **features** to this group. -* First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value. -* Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value. - -Default value is used if there is no other value set for the current user/tenant. - -### Other Feature Properties - -While these minimal definitions are enough to make the feature system working, you can specify the **optional properties** for the features; - -* `DisplayName`: A localizable string that will be used to show the feature name on the user interface. -* `Description`: A longer localizable text to describe the feature. -* `ValueType`: Type of the feature value. Can be a class implementing the `IStringValueType`. Built-in types: - * `ToggleStringValueType`: Used to define `true`/`false`, `on`/`off`, `enabled`/`disabled` style features. A checkbox is shown on the UI. - * `FreeTextStringValueType`: Used to define free text values. A textbox is shown on the UI. - * `SelectionStringValueType`: Used to force the value to be selected from a list. A dropdown list is shown on the UI. -* `IsVisibleToClients` (default: `true`): Set false to hide the value of this feature from clients (browsers). Sharing the value with the clients helps them to conditionally show/hide/change the UI parts based on the feature value. -* `Properties`: A dictionary to set/get arbitrary key-value pairs related to this feature. This can be a point for customization. - -So, based on these descriptions, it would be better to define these features as shown below: - -```csharp -using FeaturesDemo.Localization; -using Volo.Abp.Features; -using Volo.Abp.Localization; -using Volo.Abp.Validation.StringValues; - -namespace FeaturesDemo -{ - public class MyFeatureDefinitionProvider : FeatureDefinitionProvider - { - public override void Define(IFeatureDefinitionContext context) - { - var myGroup = context.AddGroup("MyApp"); - - myGroup.AddFeature( - "MyApp.PdfReporting", - defaultValue: "false", - displayName: LocalizableString - .Create("PdfReporting"), - valueType: new ToggleStringValueType() - ); - - myGroup.AddFeature( - "MyApp.MaxProductCount", - defaultValue: "10", - displayName: LocalizableString - .Create("MaxProductCount"), - valueType: new FreeTextStringValueType( - new NumericValueValidator(0, 1000000)) - ); - } - } -} -``` - -* `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system. -* First feature is set to `ToggleStringValueType`, while the second one is set to `FreeTextStringValueType` with a numeric validator that allows to the values from `0` to `1,000,000`. - -Remember to define the localization the keys in your localization file: - -````json -"PdfReporting": "PDF Reporting", -"MaxProductCount": "Maximum number of products" -```` - -See the [localization document](Localization.md) for details about the localization system. - -### Feature Management Modal - -The [application startup template](Startup-Templates/Application.md) comes with the [tenant management](Modules/Tenant-Management.md) and the [feature management](Modules/Feature-Management.md) modules pre-installed. - -Whenever you define a new feature, it will be available on the **feature management modal**. To open this modal, navigate to the **tenant management page** and select the `Features` action for a tenant (create a new tenant if there is no tenant yet): - -![features-action](images/features-action.png) - -This action opens a modal to manage the feature values for the selected tenant: - -![features-modal](images/features-modal.png) - -So, you can enable, disable and set values for a tenant. These values will be used whenever a user of this tenant uses the application. - -See the *Feature Management* section below to learn more about managing the features. - -### Child Features - -A feature may have child features. This is especially useful if you want to create a feature that is selectable only if another feature was enabled. - -**Example: Defining child features** - -```csharp -using FeaturesDemo.Localization; -using Volo.Abp.Features; -using Volo.Abp.Localization; -using Volo.Abp.Validation.StringValues; - -namespace FeaturesDemo -{ - public class MyFeatureDefinitionProvider : FeatureDefinitionProvider - { - public override void Define(IFeatureDefinitionContext context) - { - var myGroup = context.AddGroup("MyApp"); - - var reportingFeature = myGroup.AddFeature( - "MyApp.Reporting", - defaultValue: "false", - displayName: LocalizableString - .Create("Reporting"), - valueType: new ToggleStringValueType() - ); - - reportingFeature.CreateChild( - "MyApp.PdfReporting", - defaultValue: "false", - displayName: LocalizableString - .Create("PdfReporting"), - valueType: new ToggleStringValueType() - ); - - reportingFeature.CreateChild( - "MyApp.ExcelReporting", - defaultValue: "false", - displayName: LocalizableString - .Create("ExcelReporting"), - valueType: new ToggleStringValueType() - ); - } - } -} -``` - -The example above defines a *Reporting* feature with two children: *PDF Reporting* and *Excel Reporting*. - -### Changing Features Definitions of a Depended Module - -A class deriving from the `FeatureDefinitionProvider` (just like the example above) can also get the existing feature definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. - -**Example: Manipulate an existing feature definition** - -```csharp -var someGroup = context.GetGroupOrNull("SomeModule"); -var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature"); -if (feature != null) -{ - feature.Description = ... - feature.CreateChild(...); -} -``` - -## Check a Feature in the Client Side - -A feature value is available at the client side too, unless you set `IsVisibleToClients` to `false` on the feature definition. The feature values are exposed from the [Application Configuration API](API/Application-Configuration.md) and usable via some services on the UI. - -See the following documents to learn how to check features in different UI types: - -* [ASP.NET Core MVC / Razor Pages / JavaScript API](UI/AspNetCore/JavaScript-API/Features.md) -* [Angular](UI/Angular/Features.md) - -**Blazor** applications can use the same `IFeatureChecker` service as explained above. - -## Feature Management - -Feature management is normally done by an admin user using the feature management modal: - -![features-modal](images/features-modal.png) - -This modal is available on the related entities, like tenants in a multi-tenant application. To open it, navigate to the **Tenant Management** page (for a multi-tenant application), click to the **Actions** button left to the Tenant and select the **Features** action. - -If you need to manage features by code, inject the `IFeatureManager` service. - -**Example: Enable PDF reporting for a tenant** - -```csharp -public class MyService : ITransientDependency -{ - private readonly IFeatureManager _featureManager; - - public MyService(IFeatureManager featureManager) - { - _featureManager = featureManager; - } - - public async Task EnablePdfReporting(Guid tenantId) - { - await _featureManager.SetForTenantAsync( - tenantId, - "MyApp.PdfReporting", - true.ToString() - ); - } -} -``` - -`IFeatureManager` is defined by the Feature Management module. It comes pre-installed with the application startup template. See the [feature management module documentation](Modules/Feature-Management.md) for more information. - -## Advanced Topics - -### Feature Value Providers - -Feature system is extensible. Any class derived from `FeatureValueProvider` (or implements `IFeatureValueProvider`) can contribute to the feature system. A value provider is responsible to **obtain the current value** of a given feature. - -Feature value providers are **executed one by one**. If one of them return a non-null value, then this feature value is used and the other providers are not executed. - -There are three pre-defined value providers, executed by the given order: - -* `TenantFeatureValueProvider` tries to get if the feature value is explicitly set for the **current tenant**. -* `EditionFeatureValueProvider` tries to get the feature value for the current edition. Edition Id is obtained from the current principal identity (`ICurrentPrincipalAccessor`) with the claim name `editionid` (a constant defined as`AbpClaimTypes.EditionId`). Editions are not implemented for the [tenant management](Modules/Tenant-Management.md) module. You can implement it yourself or consider to use the [SaaS module](https://commercial.abp.io/modules/Volo.Saas) of the ABP Commercial. -* `DefaultValueFeatureValueProvider` gets the default value of the feature. - -You can write your own provider by inheriting the `FeatureValueProvider`. - -**Example: Enable all features for a user with "SystemAdmin" as a "User_Type" claim value** - -```csharp -using System.Threading.Tasks; -using Volo.Abp.Features; -using Volo.Abp.Security.Claims; -using Volo.Abp.Validation.StringValues; - -namespace FeaturesDemo -{ - public class SystemAdminFeatureValueProvider : FeatureValueProvider - { - public override string Name => "SA"; - - private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; - - public SystemAdminFeatureValueProvider( - IFeatureStore featureStore, - ICurrentPrincipalAccessor currentPrincipalAccessor) - : base(featureStore) - { - _currentPrincipalAccessor = currentPrincipalAccessor; - } - - public override Task GetOrNullAsync(FeatureDefinition feature) - { - if (feature.ValueType is ToggleStringValueType && - _currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin") - { - return Task.FromResult("true"); - } - - return null; - } - } -} -``` - -If a provider returns `null`, then the next provider is executed. - -Once a provider is defined, it should be added to the `AbpFeatureOptions` as shown below: - -```csharp -Configure(options => -{ - options.ValueProviders.Add(); -}); -``` - -Use this code inside the `ConfigureServices` of your [module](Module-Development-Basics.md) class. - -### Feature Store - -`IFeatureStore` is the only interface that needs to be implemented to read the value of features from a persistence source, generally a database system. The Feature Management module implements it and pre-installed in the application startup template. See the [feature management module documentation](https://docs.abp.io/en/abp/latest/Modules/Feature-Management) for more information diff --git a/docs/en/FluentValidation.md b/docs/en/FluentValidation.md deleted file mode 100644 index c087f8a185..0000000000 --- a/docs/en/FluentValidation.md +++ /dev/null @@ -1,58 +0,0 @@ -# FluentValidation Integration - -ABP [Validation](Validation.md) infrastructure is extensible. [Volo.Abp.FluentValidation](https://www.nuget.org/packages/Volo.Abp.FluentValidation) NuGet package extends the validation system to work with the [FluentValidation](https://fluentvalidation.net/) library. - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.FluentValidation -```` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.FluentValidation](https://www.nuget.org/packages/Volo.Abp.FluentValidation) NuGet package to your project: - - ```` - Install-Package Volo.Abp.FluentValidation - ```` - -2. Add the `AbpFluentValidationModule` to the dependency list of your module: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpFluentValidationModule) //Add the FluentValidation module - )] -public class YourModule : AbpModule -{ -} -```` - -## Using the FluentValidation - -Follow [the FluentValidation documentation](https://fluentvalidation.net/) to create validator classes. Example: - -````csharp -public class CreateUpdateBookDtoValidator : AbstractValidator -{ - public CreateUpdateBookDtoValidator() - { - RuleFor(x => x.Name).Length(3, 10); - RuleFor(x => x.Price).ExclusiveBetween(0.0f, 999.0f); - } -} -```` - -ABP will automatically find this class and associate with the `CreateUpdateBookDto` on object validation. - -## See Also - -* [Validation System](Validation.md) \ No newline at end of file diff --git a/docs/en/Getting-Started-Angular-Template.md b/docs/en/Getting-Started-Angular-Template.md deleted file mode 100644 index 9beb84bcef..0000000000 --- a/docs/en/Getting-Started-Angular-Template.md +++ /dev/null @@ -1,8 +0,0 @@ -# Getting Started with the Startup Templates - -See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates: - -* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started?UI=MVC&DB=EF&Tiered=No) -* [Getting Started with the Angular UI](Getting-Started?UI=NG&DB=EF&Tiered=No) - - \ No newline at end of file diff --git a/docs/en/Getting-Started-AspNetCore-Application.md b/docs/en/Getting-Started-AspNetCore-Application.md deleted file mode 100644 index 0a6424ac3b..0000000000 --- a/docs/en/Getting-Started-AspNetCore-Application.md +++ /dev/null @@ -1,140 +0,0 @@ -# Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application - -This tutorial explains how to start ABP from scratch with minimal dependencies. You generally want to start with the **[startup template](Getting-Started-AspNetCore-MVC-Template.md)**. - -## Create a New Project - -1. Create a new AspNet Core Web Application with Visual Studio 2022 (17.0.0+): - -![](images/create-new-aspnet-core-application-v2.png) - -2. Configure your new project: - -![](images/select-empty-web-application-v2.png) - -3. Press the create button: - -![create-aspnet-core-application](images/create-aspnet-core-application.png) - -## Install Volo.Abp.AspNetCore.Mvc Package - -You can use the [ABP CLI](CLI.md) to install the Volo.Abp.AspNetCore.Mvc package to your project. Execute the following command in the folder of the .csproj file that you want to install the package on: - -````bash -abp add-package Volo.Abp.AspNetCore.Mvc -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc). - -## Create the First ABP Module - -ABP is a modular framework and it requires a **startup (root) module** class derived from ``AbpModule``: - -````C# -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Hosting; -using Volo.Abp; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.Modularity; - -namespace BasicAspNetCoreApplication -{ - [DependsOn(typeof(AbpAspNetCoreMvcModule))] - public class AppModule : AbpModule - { - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - var app = context.GetApplicationBuilder(); - var env = context.GetEnvironment(); - - // Configure the HTTP request pipeline. - if (env.IsDevelopment()) - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - app.UseRouting(); - app.UseConfiguredEndpoints(); - } - } -} -```` - -``AppModule`` is a good name for the startup module for an application. - -ABP packages define module classes and a module can depend on another. In the code above, the ``AppModule`` depends on the ``AbpAspNetCoreMvcModule`` (defined by the [Volo.Abp.AspNetCore.Mvc](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc) package). It's common to add a ``DependsOn`` attribute after installing a new ABP NuGet package. - -Instead of the Startup class, we are configuring an ASP.NET Core pipeline in this module class. - -## The Program Class - -Next step is to modify the Program class to integrate to the ABP module system: - -````C# -using BasicAspNetCoreApplication; - -var builder = WebApplication.CreateBuilder(args); - -await builder.AddApplicationAsync(); - -var app = builder.Build(); - -await app.InitializeApplicationAsync(); -await app.RunAsync(); -```` - -``builder.AddApplicationAsync();`` adds all services defined in all modules starting from the ``AppModule``. - -``app.InitializeApplicationAsync()`` initializes and starts the application. - -## Run the Application! - -That's all! Run the application, it will just work as expected. - -## Using Autofac as the Dependency Injection Framework - -While ASP.NET Core's Dependency Injection (DI) system is fine for basic requirements, [Autofac](https://autofac.org/) provides advanced features like Property Injection and Method Interception which are required by ABP to perform advanced application framework features. - -Replacing ASP.NET Core's DI system by Autofac and integrating to ABP is pretty easy. - -1. Install [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) package - -```` -Install-Package Volo.Abp.Autofac -```` - -2. Add the ``AbpAutofacModule`` Dependency - -````C# -[DependsOn(typeof(AbpAspNetCoreMvcModule))] -[DependsOn(typeof(AbpAutofacModule))] //Add dependency to ABP Autofac module -public class AppModule : AbpModule -{ - ... -} -```` - -3. Update `Program.cs` to use Autofac: - -````C# -using BasicAspNetCoreApplication; - -var builder = WebApplication.CreateBuilder(args); - -builder.Host.UseAutofac(); //Add this line - -await builder.AddApplicationAsync(); - -var app = builder.Build(); - -await app.InitializeApplicationAsync(); -await app.RunAsync(); -```` - -## Source Code - -Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication). diff --git a/docs/en/Getting-Started-AspNetCore-MVC-Template.md b/docs/en/Getting-Started-AspNetCore-MVC-Template.md deleted file mode 100644 index 9beb84bcef..0000000000 --- a/docs/en/Getting-Started-AspNetCore-MVC-Template.md +++ /dev/null @@ -1,8 +0,0 @@ -# Getting Started with the Startup Templates - -See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates: - -* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started?UI=MVC&DB=EF&Tiered=No) -* [Getting Started with the Angular UI](Getting-Started?UI=NG&DB=EF&Tiered=No) - - \ No newline at end of file diff --git a/docs/en/Getting-Started-Create-Solution-Single-Layer.md b/docs/en/Getting-Started-Create-Solution-Single-Layer.md deleted file mode 100644 index d2fbadd1b5..0000000000 --- a/docs/en/Getting-Started-Create-Solution-Single-Layer.md +++ /dev/null @@ -1,62 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Running the solution", - "Path": "Getting-Started-Running-Solution-Single-Layer" - }, - "Previous": { - "Name": "Setup Your Development Environment", - "Path": "Getting-Started-Setup-Environment-Single-Layer" - } -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -## Create a New Project - -We will use the ABP CLI to create a new ABP project. - -> You can also use the ABP CLI Command Generator on the [ABP Framework website](https://abp.io/get-started) by easily selecting all options from the page. - -Use the `new` command of the ABP CLI to create a new project: - -````shell -abp new Acme.BookStore -t app-nolayers{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{else if UI == "BlazorServer"}} -u blazor-server{{end}}{{if DB == "Mongo"}} -d mongodb{{end}} -```` - -*You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.* - -> [ABP CLI document](./CLI.md) covers all of the available commands and options. - -## The Solution Structure - -The solution structure is based on the [Single-Layer Startup Template](Startup-Templates/Application-Single-Layer.md) where everything is in one project instead of the [Domain Driven Design](Domain-Driven-Design.md). You can check its [documentation](Startup-Templates/Application-Single-Layer.md) for more details. - -{{ if DB == "Mongo" }} - -## MongoDB Transactions - -The [startup template](Startup-Templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable it in the *YourProjectModule* class's `ConfigureMongoDB` method: - - ```csharp -Configure(options => -{ - options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled; //or UnitOfWorkTransactionBehavior.Auto -}); - ``` - -> Or you can delete that code since `Auto` is already the default behavior. - -{{ end }} \ No newline at end of file diff --git a/docs/en/Getting-Started-Create-Solution.md b/docs/en/Getting-Started-Create-Solution.md deleted file mode 100644 index 96740abf89..0000000000 --- a/docs/en/Getting-Started-Create-Solution.md +++ /dev/null @@ -1,82 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"], - "Tiered": ["Yes", "No"] -} -```` -````json -//[doc-nav] -{ - "Next": { - "Name": "Running the solution", - "Path": "Getting-Started-Running-Solution" - }, - "Previous": { - "Name": "Setup Your Development Environment", - "Path": "Getting-Started-Setup-Environment" - } -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -## Create a New Project - -We will use the ABP CLI to create a new ABP project. - -> Alternatively, you can **create and download** projects from the [ABP Framework website](https://abp.io/get-started) by easily selecting all options from the page. - -Use the `new` command of the ABP CLI to create a new project: - -````shell -abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{else if UI == "BlazorServer"}} -u blazor-server{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC" || UI == "BlazorServer"}} --tiered{{else}} --separate-auth-server{{end}}{{end}} -```` - -*You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.* - -{{ if Tiered == "Yes" }} - -{{ if UI == "MVC" || UI == "BlazorServer" }} - -* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated. - -{{ else }} - -* `--separate-auth-server` argument is used to separate the Auth Server application from the API host application. If not specified, you will have a single endpoint on the server. - -{{ end }} - -{{ end }} - -> [ABP CLI document](./CLI.md) covers all of the available commands and options. - -### Mobile Development - -If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends. - -See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application. - -## The Solution Structure - -The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects. See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details. - -{{ if DB == "Mongo" }} - -## MongoDB Transactions - -The [startup template](Startup-Templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable it in the *YourProjectMongoDbModule* class's `ConfigureServices` method: - - ```csharp -Configure(options => -{ - options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto; -}); - ``` - -> Or you can delete that code since `Auto` is already the default behavior. - -{{ end }} \ No newline at end of file diff --git a/docs/en/Getting-Started-Overall.md b/docs/en/Getting-Started-Overall.md deleted file mode 100644 index ef88f5f4b5..0000000000 --- a/docs/en/Getting-Started-Overall.md +++ /dev/null @@ -1,8 +0,0 @@ -# Getting Started: Overall - -## Select the Solution Architecture - -This tutorial has multiple versions. Please select the one that fits you the best: - -* **[Single-Layer Solution](Getting-Started-Single-Layered.md)**: Creates a single-project solution. Recommended for building an application with a **simpler and easy to understand** architecture. -* **[Layered Solution Architecture](Getting-Started.md)**: A fully layered (multiple projects) solution based on [Domain Driven Design](Domain-Driven-Design.md) practices. Recommended for long-term projects that need a **maintainable and extensible** codebase. diff --git a/docs/en/Getting-Started-React-Native.md b/docs/en/Getting-Started-React-Native.md deleted file mode 100644 index 767aba6d7e..0000000000 --- a/docs/en/Getting-Started-React-Native.md +++ /dev/null @@ -1,129 +0,0 @@ -````json -//[doc-params] -{ - "Tiered": ["No", "Yes"] -} -```` - -# Getting Started with the React Native - -ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to your ABP based backends**. - -![React Native gif](./images/react-native-introduction.gif) - -## How to Prepare Development Environment - -Please follow the steps below to prepare your development environment for React Native. - -1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v16 or v18 installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system. -2. **[Optional] Install Yarn:** You may install Yarn v1 (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v6 and below. You may skip this step and work with npm, which is built-in in Node.js, instead. -3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to React Native projects. -4. **Install an Emulator:** React Native applications need an Android emulator or an iOS simulator to run on your OS. See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/) or [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) on expo.io documentation to learn how to set up an emulator. - -## How to Start a New React Native Project - -You have multiple options to initiate a new React Native project that works with ABP: - -### 1. Using ABP CLI - -ABP CLI is probably the most convenient and flexible way to initiate an ABP solution with a React Native application. Simply [install the ABP CLI](CLI.md) and run the following command in your terminal: - -```shell -abp new MyCompanyName.MyProjectName -csf -u -m react-native -``` - -> To see further options in the CLI, please visit the [CLI manual](CLI.md). - -This command will prepare a solution with an **Angular** or an **MVC** (depends on your choice), a **.NET Core**, and a **React Native** project in it. - -### 2. Generating a CLI Command from Get Started Page - -You can generate a CLI command on the [get started page of the abp.io website](https://abp.io/get-started). Then, use the command on your terminal to create a new [Startup Template](./Startup-Templates/Index.md). - -## How to Configure & Run the Backend - -> React Native application does not trust the auto-generated .NET HTTPS certificate. You should use **HTTP** during the development. - -> When you are using OpenIddict, You should remove 'clientSecret' on Environment.js (if exists) and disable "HTTPS-only" settings. (Openiddict has default since Version 6.0) - -A React Native application running on an Android emulator or a physical phone **can not connect to the backend** on `localhost`. To fix this problem, it is necessary to run the backend application on your **local IP address**. - -{{ if Tiered == "No"}} -![React Native host project local IP entry](images/rn-host-local-ip.png) - -- Open the `appsettings.json` file in the `.HttpApi.Host` folder. Replace the `localhost` address on the `SelfUrl` and `Authority` properties with your local IP address. -- Open the `launchSettings.json` file in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address. - -{{ else if Tiered == "Yes" }} - -![React Native tiered project local IP entry](images/rn-tiered-local-ip.png) - -- Open the `appsettings.json` file in the `.AuthServer` folder. Replace the `localhost` address on the `SelfUrl` property with your local IP address. -- Open the `launchSettings.json` file in the `.AuthServer/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address. -- Open the `appsettings.json` file in the `.HttpApi.Host` folder. Replace the `localhost` address on the `Authority` property with your local IP address. -- Open the `launchSettings.json` file in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address. - -{{ end }} - -Run the backend application as described in the [getting started document](Getting-Started.md). - -> You should turn off the "Https Restriction" if you're using OpenIddict as a central identity management solution. Because the IOS Simulator doesn't support self-signed certificates and OpenIddict is set to only work with HTTPS by default. - -## How to disable the Https-only settings of OpenIddict - -Open the {{ if Tiered == "No" }}`MyProjectNameHttpApiHostModule`{{ else if Tiered == "Yes" }}`MyProjectNameAuthServerModule`{{ end }} project and copy-paste the below code-block to the `PreConfigureServices` method: - -```csharp -#if DEBUG - PreConfigure(options => - { - options.UseAspNetCore() - .DisableTransportSecurityRequirement(); - }); -#endif -``` - -## How to Configure & Run the React Native Application - -1. Make sure the [database migration is complete](./Getting-Started?UI=NG&DB=EF&Tiered=No#create-the-database) and the [API is up and running](./Getting-Started?UI=NG&DB=EF&Tiered=No#run-the-application). -2. Open `react-native` folder and run `yarn` or `npm install` if you have not already. -3. Open the `Environment.js` in the `react-native` folder and replace the `localhost` address on the `apiUrl` and `issuer` properties with your local IP address as shown below: - -![react native environment local IP](images/rn-environment-local-ip.png) - -{{ if Tiered == "Yes" }} - -> Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project. - -{{else}} - -> Make sure that `issuer` and `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project. - -{{ end }} - -4. Run `yarn start` or `npm start`. Wait for the Expo CLI to print the opitons. - -> The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features. - -![expo-cli-options](images/rn-options.png) - -In the above image, you can start the application with an Android emulator, an iOS simulator or a physical phone by scanning the QR code with the [Expo Client](https://expo.io/tools#client) or choosing the option. - -### Expo - -![React Native login screen on iPhone 11](images/rn-login-iphone.png) - -### Android Studio - -1. Start the emulator in **Android Studio** before running the `yarn start` or `npm start` command. -2. Press **a** to open in Android Studio. - -![React Native login screen on iPhone 11](images/rn-login-android-studio.png) - -Enter **admin** as the username and **1q2w3E\*** as the password to login to the application. - -The application is up and running. You can continue to develop your application based on this startup template. - -## See Also - -- [React Native project structure](./Startup-Templates/Application#react-native) diff --git a/docs/en/Getting-Started-Running-Solution-Single-Layer.md b/docs/en/Getting-Started-Running-Solution-Single-Layer.md deleted file mode 100644 index 18b2316cf6..0000000000 --- a/docs/en/Getting-Started-Running-Solution-Single-Layer.md +++ /dev/null @@ -1,138 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Previous": { - "Name": "Creating a new solution", - "Path": "Getting-Started-Create-Solution-Single-Layer" - } -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -## Create the Database - -### Connection String - -Check the **connection string** in the `appsettings.json` file under the `YourProject` project. - -{{ if DB == "EF" }} - -````json -"ConnectionStrings": { - "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True" -} -```` - -> **About the Connection Strings and Database Management Systems** -> -> The solution is configured to use **Entity Framework Core** with **MS SQL Server** by default. However, if you've selected another DBMS using the `-dbms` parameter on the ABP CLI `new` command (like `-dbms MySQL`), the connection string might be different for you. -> -> EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers and you can use any supported DBMS. See [the Entity Framework integration document](Entity-Framework-Core.md) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md) if you need later. - -{{ else if DB == "Mongo" }} - -````json -"ConnectionStrings": { - "Default": "mongodb://localhost:27017/BookStore" -} -```` - -The solution is configured to use **MongoDB** in your local computer, so you need to have a MongoDB server instance up and running or change the connection string to another MongoDB server. - -{{ end }} - -### Seed Initial Data - -Before running the application, you need to create the database and seed the initial data. To do that, you can run the following command in the directory of your project (in the same folder of the `.csproj` file): - -```bash -dotnet run --migrate-database -``` - -## Before Running the Application - -### Installing the Client-Side Packages - -[ABP CLI](CLI.md) runs the `abp install-libs` command behind the scenes to install the required NPM packages for your solution while creating the application. - -However, sometimes this command might need to be manually run. For example, you need to run this command, if you have cloned the application, or the resources from *node_modules* folder didn't copy to *wwwroot/libs* folder, or if you have added a new client-side package dependency to your solution. - -For such cases, run the `abp install-libs` command on the root directory of your solution to install all required NPM packages: - -```bash -abp install-libs -``` - -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. - -{{if UI=="Blazor" || UI=="BlazorServer"}} - -### Bundling and Minification - -`abp bundle` command offers bundling and minification support for client-side resources (JavaScript and CSS files) for Blazor projects. This command automatically run when you create a new solution with the [ABP CLI](CLI.md). - -However, sometimes you might need to run this command manually. To update script & style references without worrying about dependencies, ordering, etc. in a project, you can run this command in the directory of your blazor application: - -```bash -abp bundle -``` - -> For more details about managing style and script references in Blazor or MAUI Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md). - -{{end}} - -## Run the Application - -{{if UI=="MVC" || UI=="BlazorServer"}} - -Running the application is pretty straight-forward, you can run the application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project: - -{{else if UI=="Blazor"}} - -Running the application is pretty straight-forward, you just need to run the `TodoApp.Host` application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project. - -> **Note:** The `host` application hosts and serves the `blazor` application. Therefore, you should run the `host` application only. - -After the application runs, open the application in your default browser. - -{{else if UI=="NG"}} - -The solution has two main applications: - -* `TodoApp` (in the .NET solution) hosts the server-side HTTP API, so the Angular application can consume it. (server-side application) -* `angular` folder contains the Angular application. (client-side application) - -Firstly, run the `TodoApp` project in your favorite IDE (or run the `dotnet run` CLI command on your project directory) to see the server-side HTTP API on [Swagger UI](https://swagger.io/tools/swagger-ui/). - -![swagger-ui](images/swagger-ui.png) - -You can explore and test your HTTP API with this UI. If it works, then we can run the Angular client application. - -You can run the application using the following (or `yarn start`) command: - -````bash -npm start -```` - -This command takes time, but eventually runs and opens the application in your default browser. - -{{end}} - -After running the project, the index page should be seen as below: - -![single-layer-index-page](images/single-layer-index-page.png) - -Enter **admin** as the username and **1q2w3E*** as the password to login to the application. The application is up and running. You can start developing your application based on this startup template. - -![bookstore-login-2](images/bookstore-login-2.png) \ No newline at end of file diff --git a/docs/en/Getting-Started-Running-Solution.md b/docs/en/Getting-Started-Running-Solution.md deleted file mode 100644 index f522016278..0000000000 --- a/docs/en/Getting-Started-Running-Solution.md +++ /dev/null @@ -1,271 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"], - "Tiered": ["Yes", "No"] -} -```` - -````json -//[doc-nav] -{ - "Previous": { - "Name": "Creating a new solution", - "Path": "Getting-Started-Create-Solution" - } -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -## Create the Database - -### Connection String - -Check the **connection string** in the `appsettings.json` file under the {{if Tiered == "Yes"}}`.AuthServer` and `.HttpApi.Host` projects{{else}}{{if UI=="MVC"}}`.Web` project{{else if UI=="BlazorServer"}}`.Blazor` project{{else}}`.HttpApi.Host` project{{end}}{{end}}. - -{{ if DB == "EF" }} - -````json -"ConnectionStrings": { - "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True" -} -```` - -> **About the Connection Strings and Database Management Systems** -> -> The solution is configured to use **Entity Framework Core** with **MS SQL Server** by default. However, if you've selected another DBMS using the `-dbms` parameter on the ABP CLI `new` command (like `-dbms MySQL`), the connection string might be different for you. -> -> EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers and you can use any supported DBMS. See [the Entity Framework integration document](Entity-Framework-Core.md) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md) if you need later. - -### Database Migrations - -The solution uses the [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). It comes with a `.DbMigrator` console application which **applies the migrations** and also **seeds the initial data**. It is useful on **development** as well as on **production** environment. - -> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one. - -### The Initial Migration - -`.DbMigrator` application automatically **creates the Initial migration** on first run. - -**If you are using Visual Studio, you can skip to the *Running the DbMigrator* section.** However, other IDEs (e.g. Rider) may have problems for the first run since it adds the initial migration and compiles the project. In this case, open a command line terminal in the folder of the `.DbMigrator` project and run the following command: - -````bash -dotnet run -```` - -For the next time, you can just run it in your IDE as you normally do. - -### Running the DbMigrator - -Right click to the `.DbMigrator` project and select **Set as StartUp Project** - -![set-as-startup-project](images/set-as-startup-project.png) - - Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below: - - ![db-migrator-output](images/db-migrator-output.png) - -> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database. - -{{ else if DB == "Mongo" }} - -````json -"ConnectionStrings": { - "Default": "mongodb://localhost:27017/BookStore" -} -```` - -The solution is configured to use **MongoDB** in your local computer, so you need to have a MongoDB server instance up and running or change the connection string to another MongoDB server. - -### Seed Initial Data - -The solution comes with a `.DbMigrator` console application which **seeds the initial data**. It is useful on **development** as well as on **production** environment. - -> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one. - -Right click to the `.DbMigrator` project and select **Set as StartUp Project** - -![set-as-startup-project](images/set-as-startup-project.png) - - Hit F5 (or Ctrl+F5) to run the application. It will have an output like shown below: - - ![db-migrator-output](images/db-migrator-output.png) - -> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database. - -{{ end }} - -## Before Running the Application - -### Installing the Client-Side Packages - -[ABP CLI](CLI.md) runs the `abp install-libs` command behind the scenes to install the required NPM packages for your solution while creating the application. - -However, sometimes this command might need to be manually run. For example, you need to run this command, if you have cloned the application, or the resources from *node_modules* folder didn't copy to *wwwroot/libs* folder, or if you have added a new client-side package dependency to your solution. - -For such cases, run the `abp install-libs` command on the root directory of your solution to install all required NPM packages: - -```bash -abp install-libs -``` - -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. - -{{if UI=="Blazor" || UI=="BlazorServer"}} - -### Bundling and Minification - -`abp bundle` command offers bundling and minification support for client-side resources (JavaScript and CSS files) for Blazor projects. This command automatically run when you create a new solution with the [ABP CLI](CLI.md). - -However, sometimes you might need to run this command manually. To update script & style references without worrying about dependencies, ordering, etc. in a project, you can run this command in the directory of your blazor application: - -```bash -abp bundle -``` - -> For more details about managing style and script references in Blazor or MAUI Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md). - -{{end}} - -## Run the Application - -{{ if UI == "MVC" || UI == "BlazorServer" }} - -> **Note**: Before starting the application, run `abp install-libs` command in your Web directory to restore the client-side libraries. This will populate the `libs` folder. - -{{ if UI == "BlazorServer" }} - -> **Important:** The `.AuthServer` application serves as the **Authentication Server** for the `.Blazor` application. It is essential to have the `.AuthServer` application running in the background to ensure the proper functioning of the `.Blazor` application. - -To do this, open terminal in `.AuthServer` project folder and run the following command. - -````bash -dotnet run -```` - -Once the `.AuthServer`application has started, it is time to run `.HttpApi.Host` application. - -> **Important:** Prior to launching the `.Blazor` project, it is essential to execute the `.HttpApi.Host` application as well. - -To do this, open terminal in `.HttpApi.Host` project folder and run the following command. - -````bash -dotnet run -```` -Once the `.AuthServer` and `.HttpApi.Host` applications has started, you can proceed to run the `.Blazor` project. - -{{ end # UI }} - -{{ if Tiered == "Yes" }} - -> Tiered solutions use **Redis** as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below. - -1. Ensure that the `.AuthServer` project is the startup project. Run this application that will open a **login** page in your browser. - -> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster. - -You can login, but you cannot enter to the main application here. This is **just the authentication server**. - -2. Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser. - -![swagger-ui](images/swagger-ui.png) - -This is the HTTP API that is used by the web application. - -3. Lastly, ensure that the {{if UI=="MVC"}}`.Web`{{else}}`.Blazor`{{end}} project is the startup project and run the application which will open a **welcome** page in your browser - -![mvc-tiered-app-home](images/bookstore-home-2.png) - -Click to the **login** button which will redirect you to the *authentication server* to login to the application: - -![bookstore-login](images/bookstore-login-2.png) - -{{ else # Tiered != "Yes" }} - -Ensure that the {{if UI=="MVC"}}`.Web`{{else}}`.Blazor`{{end}} project is the startup project. Run the application which will open the **login** page in your browser: - -> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster. - -![bookstore-login](images/bookstore-login-2.png) - -{{ end # Tiered }} - -{{ else # UI != MVC || BlazorServer }} - -### Running the HTTP API Host (Server Side) - -{{ if Tiered == "Yes" }} - -> Tiered solutions use Redis as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below. - -Ensure that the `.AuthServer` project is the startup project. Run the application which will open a **login** page in your browser. - -> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster. - -You can login, but you cannot enter to the main application here. This is **just the authentication server**. - -Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI: - -{{ else # Tiered == "No" }} - -Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI: - -> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster. - -{{ end # Tiered }} - -![swagger-ui](images/swagger-ui.png) - -You can see the application APIs and test them here. Get [more info](https://swagger.io/tools/swagger-ui/) about the Swagger UI. - -{{ end # UI }} - -{{ if UI == "Blazor" }} - -### Running the Blazor Application (Client Side) - -> **Important:** The `.HttpApi.Host` application serves as the **Authentication Server** for the `.Blazor` application. It is essential to have the `.HttpApi.Host` application running in the background to ensure the proper functioning of the `.Blazor` application. - -To do this, you can open terminal in `.HttpApi.Host` project folder and run the following command. - -````bash -dotnet run -```` - -Once the `.HttpApi.Host` application has started, you can proceed to run the `.Blazor` application. - -Ensure that the `.Blazor` project is the startup project and run the application. - -> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster. - -Once the application starts, click to the **Login** link on to header, which redirects you to the authentication server to enter a username and password: - -![bookstore-login](images/bookstore-login-2.png) - -{{ else if UI == "NG" }} - -### Running the Angular Application (Client Side) - -Go to the `angular` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com/) package manager while `npm install` will also work) - -```bash -yarn -``` - -Once all node modules are loaded, execute `yarn start` (or `npm start`) command: - -```bash -yarn start -``` - -It may take a longer time for the first build. Once it finishes, it opens the Angular UI in your default browser with the [localhost:4200](http://localhost:4200/) address. - -![bookstore-login](images/bookstore-login-2.png) - -{{ end }} - -Enter **admin** as the username and **1q2w3E*** as the password to login to the application. The application is up and running. You can start developing your application based on this startup template. \ No newline at end of file diff --git a/docs/en/Getting-Started-Setup-Environment-Single-Layer.md b/docs/en/Getting-Started-Setup-Environment-Single-Layer.md deleted file mode 100644 index 3652fc5701..0000000000 --- a/docs/en/Getting-Started-Setup-Environment-Single-Layer.md +++ /dev/null @@ -1,55 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Creating a new solution", - "Path": "Getting-Started-Create-Solution-Single-Layer" - } -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -## Setup Your Development Environment - -First things first! Let's setup your development environment before creating the project. - -### Pre-Requirements - -The following tools should be installed on your development machine: - -* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 8.0+](https://dotnet.microsoft.com/download/dotnet) development. - {{ if UI != "Blazor" }} -* [Node v16+](https://nodejs.org/) -* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [1](#f-yarn) or npm v6+ (already installed with Node) - {{ end }} - -{{ if UI != "Blazor" }} - -1 _Yarn v2 works differently and is not supported._ [↩](#a-yarn) - -{{ end }} - -### Install the ABP CLI - -[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions. First, you need to install the ABP CLI using the following command: - -````shell -dotnet tool install -g Volo.Abp.Cli -```` - -If you've already installed, you can update it using the following command: - -````shell -dotnet tool update -g Volo.Abp.Cli -```` \ No newline at end of file diff --git a/docs/en/Getting-Started-Setup-Environment.md b/docs/en/Getting-Started-Setup-Environment.md deleted file mode 100644 index 0d33e8abe2..0000000000 --- a/docs/en/Getting-Started-Setup-Environment.md +++ /dev/null @@ -1,58 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"], - "Tiered": ["Yes", "No"] -} -```` -````json -//[doc-nav] -{ - "Next": { - "Name": "Creating a new solution", - "Path": "Getting-Started-Create-Solution" - } -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -## Setup Your Development Environment - -First things first! Let's setup your development environment before creating the project. - -### Pre-Requirements - -The following tools should be installed on your development machine: - -* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 8.0+](https://dotnet.microsoft.com/download/dotnet) development. -{{ if UI != "Blazor" }} -* [Node v16+](https://nodejs.org/) -* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [1](#f-yarn) or npm v6+ (already installed with Node) -{{ end }} -{{ if Tiered == "Yes" }} -* [Redis](https://redis.io/) (the startup solution uses the Redis as the [distributed cache](Caching.md)). -{{ end }} - -{{ if UI != "Blazor" }} - -1 _Yarn v2 works differently and is not supported._ [↩](#a-yarn) - -{{ end }} - -### Install the ABP CLI - -[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions. First, you need to install the ABP CLI using the following command: - -````shell -dotnet tool install -g Volo.Abp.Cli -```` - -If you've already installed, you can update it using the following command: - -````shell -dotnet tool update -g Volo.Abp.Cli -```` \ No newline at end of file diff --git a/docs/en/Getting-Started-Single-Layered.md b/docs/en/Getting-Started-Single-Layered.md deleted file mode 100644 index 5ccad79649..0000000000 --- a/docs/en/Getting-Started-Single-Layered.md +++ /dev/null @@ -1,17 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -This tutorial explains how to **create and run** a new Single-Layered web application using the ABP Framework. Follow the steps below: - -1. [Setup your development environment](Getting-Started-Setup-Environment-Single-Layer.md) -2. [Creating a new solution](Getting-Started-Create-Solution-Single-Layer.md) -3. [Running the solution](Getting-Started-Running-Solution-Single-Layer.md) diff --git a/docs/en/Getting-Started-With-Startup-Templates.md b/docs/en/Getting-Started-With-Startup-Templates.md deleted file mode 100644 index add08b8ebd..0000000000 --- a/docs/en/Getting-Started-With-Startup-Templates.md +++ /dev/null @@ -1,3 +0,0 @@ -This document has been [moved to here](Getting-Started.md). - - diff --git a/docs/en/Getting-Started.md b/docs/en/Getting-Started.md deleted file mode 100644 index 6352f43bc7..0000000000 --- a/docs/en/Getting-Started.md +++ /dev/null @@ -1,18 +0,0 @@ -# Getting Started - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"], - "Tiered": ["Yes", "No"] -} -```` - -> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document. - -This tutorial explains how to **create and run** a new web application using the ABP Framework. Follow the steps below; - -1. [Setup your development environment](Getting-Started-Setup-Environment.md) -2. [Creating a new solution](Getting-Started-Create-Solution.md) -3. [Running the solution](Getting-Started-Running-Solution.md) \ No newline at end of file diff --git a/docs/en/Global-Features.md b/docs/en/Global-Features.md deleted file mode 100644 index 0029127eb0..0000000000 --- a/docs/en/Global-Features.md +++ /dev/null @@ -1,143 +0,0 @@ -# Global Features -Global Feature system is used to enable/disable an application feature on development time. It is done on the development time, because some **services** (e.g. controllers) are removed from the application model and **database tables** are not created for the disabled features, which is not possible on runtime. - -Global Features system is especially useful if you want to develop a reusable application module with optional features. If the final application doesn't want to use some of the features, it can disable these features. - -> If you are looking for a system to enable/disable features based on current tenant or any other condition, please see the [Features](Features.md) document. - -## Installation -> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.GlobalFeatures -``` - -## Defining a Global Feature - -A feature class is something like that: - -```csharp -[GlobalFeatureName("Shopping.Payment")] -public class PaymentFeature -{ - -} -``` - -## Enable/Disable Global Features - -Use `GlobalFeatureManager.Instance` to enable/disable a global feature. - -```csharp -// Able to Enable/Disable with generic type parameter. -GlobalFeatureManager.Instance.Enable(); -GlobalFeatureManager.Instance.Disable(); - -// Also able to Enable/Disable with string feature name. -GlobalFeatureManager.Instance.Enable("Shopping.Payment"); -GlobalFeatureManager.Instance.Disable("Shopping.Payment"); -``` - -> Global Features are disabled unless they are explicitly enabled. - -### Where to Configure Global Features? - -Global Features have to be configured before application startup. Since the `GlobalFeatureManager.Instance` is a singleton object, one-time, static configuration is enough. It is suggested to enable/disable global features in `PreConfigureServices` method of your module. You can use the `OneTimeRunner` utility class to make sure it runs only once: - -```csharp -private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - OneTimeRunner.Run(() => - { - GlobalFeatureManager.Instance.Enable(); - }); -} -``` - -## Check for a Global Feature - -```csharp -GlobalFeatureManager.Instance.IsEnabled() -GlobalFeatureManager.Instance.IsEnabled("Shopping.Payment") -``` - -Both methods return `bool`. So, you can write conditional logic as shown below: - -```csharp -if (GlobalFeatureManager.Instance.IsEnabled()) -{ - // Some strong payment codes here... -} -``` - -### RequiresGlobalFeature Attribute - -Beside the manual check, there is `[RequiresGlobalFeature]` attribute to check it declaratively for a controller or page. ABP returns HTTP Response `404` if the related feature was disabled. - -```csharp -[RequiresGlobalFeature(typeof(PaymentFeature))] -public class PaymentController : AbpController -{ - -} -``` - -## Grouping Features of a Module - -It is common to group global features of a module to allow the final application developer easily discover and configure the features. Following example shows how to group features of a module. - -Assume that we've defined a global feature for `Subscription` feature of an `Ecommerce` module: - -```csharp -[GlobalFeatureName("Ecommerce.Subscription")] -public class SubscriptionFeature : GlobalFeature -{ - public SubscriptionFeature(GlobalModuleFeatures module) - : base(module) - { - } -} -``` - -You can define as many features as you need in your module. Then define a class to group these features together: - -```csharp -public class GlobalEcommerceFeatures : GlobalModuleFeatures -{ - public const string ModuleName = "Ecommerce"; - - public SubscriptionFeature Subscription => GetFeature(); - - public GlobalEcommerceFeatures(GlobalFeatureManager featureManager) - : base(featureManager) - { - AddFeature(new SubscriptionFeature(this)); - } -} -``` - -Finally, you can create an extension method on `GlobalModuleFeaturesDictionary`: - -```csharp -public static class GlobalModuleFeaturesDictionaryEcommerceExtensions -{ - public static GlobalEcommerceFeatures Ecommerce( - this GlobalModuleFeaturesDictionary modules) - { - return modules.GetOrAdd( - GlobalEcommerceFeatures.ModuleName, - _ => new GlobalEcommerceFeatures(modules.FeatureManager) - ) as GlobalEcommerceFeatures; - } -``` - -Then `GlobalFeatureManager.Instance.Modules.Ecommerce()` can be used to access the global features of your module. Examples usages: - -```csharp -GlobalFeatureManager.Instance.Modules.Ecommerce().Subscription.Enable(); -GlobalFeatureManager.Instance.Modules.Ecommerce().EnableAll(); -``` - diff --git a/docs/en/Guid-Generation.md b/docs/en/Guid-Generation.md deleted file mode 100644 index 0990f5b6dd..0000000000 --- a/docs/en/Guid-Generation.md +++ /dev/null @@ -1,111 +0,0 @@ -# GUID Generation - -GUID is a common **primary key type** that is used in database management systems. ABP Framework prefers GUID as the primary for pre-built [application modules](Modules/Index.md). Also, `ICurrentUser.Id` property ([see](CurrentUser.md)) is type of GUID, that means the ABP Framework assumes that the User Id is always GUID. - -## Why Prefer GUID? - -GUID has advantages and disadvantages. You can find many articles on the web related to this topic, so we will not discuss all again, but will list the most fundamental advantages: - -* It is **usable** in all database providers. -* It allows to **determine the primary key** on the client side, without needing a **database round trip** to generate the Id value. This can be more performant while inserting new records to the database and allows us to know the PK before interacting to the database. -* GUIDs are **naturally unique** which has some advantages in the following situations if; - * You need to integrate to **external** systems. - * You need to **split or merge** different tables. - * You are creating **distributed systems**. -* GUIDs are impossible to guess, so they can be **more secure** compared to auto-increment Id values in some cases. - -While there are some disadvantages (just search it on the web), we found these advantages much more important while designing the ABP Framework. - -## IGuidGenerator - -The most important problem with GUID is that it is **not sequential by default**. When you use the GUID as the primary key and set it as the **clustered index** (which is default) for your table, it brings a significant **performance problem on insert** (because inserting new record may need to re-order the existing records). - -So, **never use `Guid.NewGuid()` to create Ids** for your entities! - -One good solution to this problem is to generate **sequential GUIDs**, which is provided by the ABP Framework out of the box. `IGuidGenerator` service creates sequential GUIDs (implemented by the `SequentialGuidGenerator` by default). Use `IGuidGenerator.Create()` when you need to manually set Id of an [entity](Entities.md). - -**Example: An entity with GUID primary key and creating the entity** - -Assume that you've a `Product` [entity](Entities.md) that has a `Guid` key: - -````csharp -using System; -using Volo.Abp.Domain.Entities; - -namespace AbpDemo -{ - public class Product : AggregateRoot - { - public string Name { get; set; } - - private Product() { /* This constructor is used by the ORM/database provider */ } - - public Product(Guid id, string name) - : base(id) - { - Name = name; - } - } -} -```` - -And you want to create a new product: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; - -namespace AbpDemo -{ - public class MyProductService : ITransientDependency - { - private readonly IRepository _productRepository; - private readonly IGuidGenerator _guidGenerator; - - public MyProductService( - IRepository productRepository, - IGuidGenerator guidGenerator) - { - _productRepository = productRepository; - _guidGenerator = guidGenerator; - } - - public async Task CreateAsync(string productName) - { - var product = new Product(_guidGenerator.Create(), productName); - - await _productRepository.InsertAsync(product); - } - } -} -```` - -This service injects the `IGuidGenerator` in the constructor. If your class is an [application service](Application-Services.md) or deriving from one of the other base classes, you can directly use the `GuidGenerator` base property which is a pre-injected `IGuidGenerator` instance. - -## Options - -### AbpSequentialGuidGeneratorOptions - -`AbpSequentialGuidGeneratorOptions` is the [option class](Options.md) that is used to configure the sequential GUID generation. It has a single property: - -* `DefaultSequentialGuidType` (`enum` of type `SequentialGuidType`): The strategy used while generating GUID values. - -Database providers behaves differently while processing GUIDs, so you should set it based on your database provider. `SequentialGuidType` has the following `enum` members: - -* `SequentialAtEnd` (**default**) works well with the [SQL Server](Entity-Framework-Core.md). -* `SequentialAsString` is used by [MySQL](Entity-Framework-Core-MySQL.md) and [PostgreSQL](Entity-Framework-Core-PostgreSQL.md). -* `SequentialAsBinary` is used by [Oracle](Entity-Framework-Core-Oracle.md). - -Configure this option in the `ConfigureServices` method of your [module](Module-Development-Basics.md), as shown below: - -````csharp -Configure(options => -{ - options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary; -}); -```` - -> EF Core [integration packages](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS) sets this option to a proper value for the related DBMS. So, most of the times, you don't need to set this option if you are using these integration packages. \ No newline at end of file diff --git a/docs/en/IdentityServer-Integration.md b/docs/en/IdentityServer-Integration.md deleted file mode 100644 index da82d4bc6a..0000000000 --- a/docs/en/IdentityServer-Integration.md +++ /dev/null @@ -1,3 +0,0 @@ -# IdentityServer Integration - -TODO \ No newline at end of file diff --git a/docs/en/Image-Manipulation.md b/docs/en/Image-Manipulation.md deleted file mode 100644 index 9d946e31dd..0000000000 --- a/docs/en/Image-Manipulation.md +++ /dev/null @@ -1,401 +0,0 @@ -# Image Manipulation -ABP Framework provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/) and [Magick.NET](https://github.com/dlemstra/Magick.NET) libraries. You can use these services in your reusable modules, libraries and applications, so you don't depend on a specific imaging library. - -> The image resizer/compressor system is designed to be extensible. You can implement your own image resizer/compressor contributor and use it in your application. - -## Installation - -You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach. - -### Using the ABP CLI - -Open a command line terminal in the folder of your project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.Imaging.Abstractions -``` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.Imaging.Abstractions](https://www.nuget.org/packages/Volo.Abp.Imaging.Abstractions) NuGet package to your project: - -``` -Install-Package Volo.Abp.Imaging.Abstractions -``` - -2. Add the `AbpImagingAbstractionsModule` to the dependency list of your module: - -```csharp -[DependsOn( - //...other dependencies - typeof(AbpImagingAbstractionsModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -``` - -## Providers - -ABP Framework provides two image resizer/compressor implementations out of the box: - -* [Magick.NET](#magick-net-provider) -* [ImageSharp](#imagesharp-provider) - -You should install one of these provides to make it actually working. - -> If none of the provider packages installed into your application, compress/resize operations return the untouched input image. - -## IImageResizer - -You can [inject](Dependency-Injection.md) the `IImageResizer` service and use it for image resize operations. Here is the available methods of the `IImageResizer` service: - -```csharp -public interface IImageResizer -{ - /* Works with a Stream object that represents an image */ - Task> ResizeAsync( - Stream stream, - ImageResizeArgs resizeArgs, - string mimeType = null, - CancellationToken cancellationToken = default - ); - - /* Works with a byte array that contains an image file */ - Task> ResizeAsync( - byte[] bytes, - ImageResizeArgs resizeArgs, - string mimeType = null, - CancellationToken cancellationToken = default - ); -} -``` - -**Example usage:** - -```csharp -var resizeResult = await _imageResizer.ResizeAsync( - imageStream, /* A stream object that represents an image */ - new ImageResizeArgs - { - Width = 100, - Height = 100, - Mode = ImageResizeMode.Crop - }, - mimeType: "image/jpeg" -); -``` - -> **Note:** If `resizeResult.State` returns 'Done', then it means that the resize operation was successful. However, if it returns any other state than 'Done', the stream you're using might be corrupted. Therefore, you can perform a check like the one below and assign the correct stream to the main stream: - -```csharp -if (resizeResult.Result is not null && imageStream != resizeResult.Result && resizeResult.Result.CanRead) -{ - await imageStream.DisposeAsync(); - imageStream = resizeResult.Result; -} -``` - -> You can use `MimeTypes.Image.Jpeg` constant instead of the `image/jpeg` magic string used in that example. - -### ImageResizeArgs - -The `ImageResizeArgs` is a class that is used to define the resize operation parameters. It has the following properties: - -* `Width`: The width of the resized image. -* `Height`: The height of the resized image. -* `Mode`: The resize mode (see the [ImageResizeMode](#imageresizemode) section for more information). - -### ImageResizeMode - -The `ImageResizeMode` is an enum that is used to define the resize mode. It has the following values: - -```csharp -public enum ImageResizeMode : byte -{ - None = 0, - Stretch = 1, - BoxPad = 2, - Min = 3, - Max = 4, - Crop = 5, - Pad = 6, - Default = 7 -} -``` - -> See the [ImageSharp documentation](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Processing.ResizeMode.html) for more information about the resize modes. - -### ImageResizeResult - -The `ImageResizeResult` is a generic class that is used to return the result of the image resize operations. It has the following properties: - -* `Result`: The resized image (stream or byte array). -* `State`: The result of the resize operation (type: `ImageProcessState`). - -### ImageProcessState - -The `ImageProcessState` is an enum that is used to return the the result of the image resize operations. It has the following values: - -```csharp -public enum ImageProcessState : byte -{ - Done = 1, - Canceled = 2, - Unsupported = 3, -} -``` - -### ImageResizeOptions - -`ImageResizeOptions` is an [options object](Options.md) that is used to configure the image resize system. It has the following properties: - -* `DefaultResizeMode`: The default resize mode. (Default: `ImageResizeMode.None`) - -## IImageCompressor - -You can [inject](Dependency-Injection.md) the `IImageCompressor` service and use it for image compression operations. Here is the available methods of the `IImageCompressor` service: - -```csharp -public interface IImageCompressor -{ - /* Works with a Stream object that represents an image */ - Task> CompressAsync( - Stream stream, - string mimeType = null, - CancellationToken cancellationToken = default - ); - - /* Works with a byte array that contains an image file */ - Task> CompressAsync( - byte[] bytes, - string mimeType = null, - CancellationToken cancellationToken = default - ); -} -``` - -**Example usage:** - -```csharp -var compressResult = await _imageCompressor.CompressAsync( - imageStream, /* A stream object that represents an image */ - mimeType: "image/jpeg" -); -``` - -> **Note:** If `compressResult.State` returns 'Done', then it means that the compression operation was successful. However, if it returns any other state than 'Done', the stream you're using might be corrupted. Therefore, you can perform a check like the one below and assign the correct stream to the main stream: - -```csharp - -if (compressResult.Result is not null && imageStream != compressResult.Result && compressResult.Result.CanRead) -{ - await imageStream.DisposeAsync(); - imageStream = compressResult.Result; -} -``` - -### ImageCompressResult - -The `ImageCompressResult` is a generic class that is used to return the result of the image compression operations. It has the following properties: - -* `Result`: The compressed image (stream or byte array). -* `State`: The result of the compress operation (type: `ImageProcessState`). - -### ImageProcessState - -The `ImageProcessState` is an enum that is used to return the the result of the image compress operations. It has the following values: - -```csharp -public enum ImageProcessState : byte -{ - Done = 1, - Canceled = 2, - Unsupported = 3, -} -``` - -## Magick.NET Provider - -`Volo.Abp.Imaging.MagickNet` NuGet package implements the image operations using the [Magick.NET](https://github.com/dlemstra/Magick.NET) library. - -## Installation - -You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach. - -### Using the ABP CLI - -Open a command line terminal in the folder of your project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.Imaging.MagickNet -``` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.Imaging.MagickNet](https://www.nuget.org/packages/Volo.Abp.Imaging.MagickNet) NuGet package to your project: - -``` -Install-Package Volo.Abp.Imaging.MagickNet -``` - -2. Add `AbpImagingMagickNetModule` to your [module](Module-Development-Basics.md)'s dependency list: - -```csharp -[DependsOn(typeof(AbpImagingMagickNetModule))] -public class MyModule : AbpModule -{ - //... -} -``` - -### Configuration - -`MagickNetCompressOptions` is an [options object](Options.md) that is used to configure the Magick.NET image compression system. It has the following properties: - -* `OptimalCompression`: Indicates whether the optimal compression is enabled or not. (Default: `false`) -* `IgnoreUnsupportedFormats`: Indicates whether the unsupported formats are ignored or not. (Default: `false`) -* `Lossless`: Indicates whether the lossless compression is enabled or not. (Default: `false`) - -## ImageSharp Provider - -`Volo.Abp.Imaging.ImageSharp` NuGet package implements the image operations using the [ImageSharp](https://github.com/SixLabors/ImageSharp) library. - -## Installation - -You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach. - -### Using the ABP CLI - -Open a command line terminal in the folder of your project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.Imaging.ImageSharp -``` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.Imaging.ImageSharp](https://www.nuget.org/packages/Volo.Abp.Imaging.ImageSharp) NuGet package to your project: - -``` -Install-Package Volo.Abp.Imaging.ImageSharp -``` - -2. Add `AbpImagingImageSharpModule` to your [module](Module-Development-Basics.md)'s dependency list: - - -```csharp -[DependsOn(typeof(AbpImagingImageSharpModule))] -public class MyModule : AbpModule -{ - //... -} -``` - -### Configuration - -`ImageSharpCompressOptions` is an [options object](Options.md) that is used to configure the ImageSharp image compression system. It has the following properties: - -* `DefaultQuality`: The default quality of the JPEG and WebP encoders. (Default: `75`) -* [`JpegEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder.html): The JPEG encoder. (Default: `JpegEncoder` with `Quality` set to `DefaultQuality`) -* [`PngEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Png.PngEncoder.html): The PNG encoder. (Default: `PngEncoder` with `IgnoreMetadata` set to `true` and `CompressionLevel` set to `PngCompressionLevel.BestCompression`) -* [`WebPEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Webp.WebpEncoder.html): The WebP encoder. (Default: `WebPEncoder` with `Quality` set to `DefaultQuality`) - -**Example usage:** - -```csharp -Configure(options => -{ - options.JpegEncoder = new JpegEncoder - { - Quality = 60 - }; - options.PngEncoder = new PngEncoder - { - CompressionLevel = PngCompressionLevel.BestCompression - }; - options.WebPEncoder = new WebPEncoder - { - Quality = 65 - }; -}); -``` - -## ASP.NET Core Integration - -`Volo.Abp.Imaging.AspNetCore` NuGet package defines attributes for controller actions that can automatically compress and/or resize uploaded files. - -## Installation - -You can add this package to your application by either using the [ABP CLI](CLI.md) or manually installing it. Using the [ABP CLI](CLI.md) is the recommended approach. - -### Using the ABP CLI - -Open a command line terminal in the folder of your project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.Imaging.AspNetCore -``` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.Imaging.AspNetCore](https://www.nuget.org/packages/Volo.Abp.Imaging.AspNetCore) NuGet package to your project: - -``` -Install-Package Volo.Abp.Imaging.AspNetCore -``` - -2. Add `AbpImagingAspNetCoreModule` to your [module](Module-Development-Basics.md)'s dependency list: - -```csharp -[DependsOn(typeof(AbpImagingAspNetCoreModule))] -public class MyModule : AbpModule -{ - //... -} -``` - -### CompressImageAttribute - -The `CompressImageAttribute` is used to compress the image before. `IFormFile`, `IRemoteStreamContent`, `Stream` and `IEnumrable` types are supported. It has the following properties: - -* `Parameters`: Names of the the parameters that are used to configure the image compression system. This is useful if your action has some non-image parameters. If you don't specify the parameters names, all of the method parameters are considered as image. - -**Example usage:** - -```csharp -[HttpPost] -[CompressImage] /* Compresses the given file (automatically determines the file mime type) */ -public async Task Upload(IFormFile file) -{ - //... -} -``` - -### ResizeImageAttribute - -The `ResizeImageAttribute` is used to resize the image before requesting the action. `IFormFile`, `IRemoteStreamContent`, `Stream` and `IEnumrable` types are supported. It has the following properties: - -* `Parameters`: Names of the the parameters that are used to configure the image resize system. This is useful if your action has some non-image parameters. If you don't specify the parameters names, all of the method parameters are considered as image. -* `Width`: Target width of the resized image. -* `Height`: Target height of the resized image. -* `Mode`: The resize mode (see the [ImageResizeMode](#imageresizemode) section for more information). - -**Example usage:** - -```csharp -[HttpPost] -[ResizeImage(Width = 100, Height = 100, Mode = ImageResizeMode.Crop)] -public async Task Upload(IFormFile file) -{ - //... -} -``` diff --git a/docs/en/Index.md b/docs/en/Index.md deleted file mode 100644 index bcb370677f..0000000000 --- a/docs/en/Index.md +++ /dev/null @@ -1,97 +0,0 @@ -# ABP Documentation - -ABP Framework offers an **opinionated architecture** to build enterprise software solutions with **best practices** on top of the **.NET** and the **ASP.NET Core** platforms. It provides the fundamental infrastructure, production-ready startup templates, modules, themes, tooling, guides and documentation to implement that architecture properly and **automate the details** and repetitive works as much as possible. - -## Getting Started - -* [Quick Start](Tutorials/Todo/Overall.md) is a single-part, quick-start tutorial to build a simple application with the ABP Framework. Start with this tutorial if you want to quickly understand how ABP works. -* [Getting Started](Getting-Started.md) guide can be used to create and run ABP based solutions with different options and details. -* [Web Application Development Tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack web application with all aspects of a real-life solution. - -### UI Framework Options - -ABP Framework can work with any UI framework, while the following frameworks are supported out of the box: - -ui options - -### Database Provider Options - -ABP Framework can work with any database provider, while the following providers are supported out of the box: - -ABP Database Providers - -## Exploring the Documentation - -ABP has a **comprehensive documentation** that not only explains the ABP Framework, but also includes **guides** and **samples** to help you on creating a **maintainable solution** by introducing and discussing common **software development principle and best practices**. - -### Architecture - -ABP offers a complete, modular and layered software architecture based on [Domain Driven Design](Domain-Driven-Design.md) principles and patterns. It also provides the necessary infrastructure to implement this architecture. - -* See the [Modularity](Module-Development-Basics.md) document to understand the module system. -* [Implementing Domain Driven Design book](https://abp.io/books/implementing-domain-driven-design?ref=doc) is an ultimate guide for who want to understand and implement the DDD with the ABP Framework. -* [Microservice Architecture](Microservice-Architecture.md) document explains how ABP helps to create a microservice solution. -* [Multi-Tenancy](Multi-Tenancy.md) document introduces multi-tenancy and explores the ABP multi-tenancy infrastructure. - -### Infrastructure - -There are a lot of features provided by the ABP Framework to achieve real world scenarios easier, like [Event Bus](Event-Bus.md), [Background Job System](Background-Jobs.md), [Audit Logging](Audit-Logging.md), [BLOB Storing](Blob-Storing.md), [Data Seeding](Data-Seeding.md), [Data Filtering](Data-Filtering.md). - -### Cross Cutting Concerns - -ABP also simplifies (and even automates wherever possible) cross cutting concerns and common non-functional requirements like [Exception Handling](Exception-Handling.md), [Validation](Validation.md), [Authorization](Authorization.md), [Localization](Localization.md), [Caching](Caching.md), [Dependency Injection](Dependency-Injection.md), [Setting Management](Settings.md), etc. - -### Application Modules - -Application Modules provides pre-built application functionalities; - -* [**Account**](Modules/Account.md): Provides UI for the account management and allows user to login/register to the application. -* **[Identity](Modules/Identity.md)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library. -* [**OpenIddict**](Modules/OpenIddict.md): Integrates to OpenIddict. -* [**Tenant Management**](Modules/Tenant-Management.md): Manages tenants for a [multi-tenant](Multi-Tenancy.md) (SaaS) application. - -See the [Application Modules](Modules/Index.md) document for all pre-built modules. - -### Startup Templates - -The [Startup templates](Startup-Templates/Index.md) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**. - -## Books - -### Mastering ABP Framework - -Mastering ABP Framework - Halil İbrahim Kalkan - -*Mastering ABP Framework* is an ultimate guide to get started and expertise with the ABP Framework. It is authored by Halil İbrahim Kalkan, the creator and the lead developer of the ABP Framework. - -**[You can order it from Amazon now](https://www.amazon.com/Mastering-ABP-Framework-maintainable-implementing-dp-1801079242/dp/1801079242)!** - -### Free E-Book: Implementing Domain Driven Design - -Implementing Domain Driven Design - -A **practical guide** for implementing Domain Driven Design (DDD). While the implementation details are **based on the ABP Framework** infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution. - -**[Click here to get your free copy](https://abp.io/books/implementing-domain-driven-design?ref=doc).** - -## ABP Community - -### The Source Code - -ABP is hosted on GitHub. See [the source code](https://github.com/abpframework). - -### ABP Community Web Site - -The [ABP Community](https://community.abp.io/) is a website to publish articles and share knowledge about the ABP Framework. You can also create content for the community! - -### Blog - -Follow the [ABP Blog](https://blog.abp.io/) to learn the latest happenings in the ABP Framework. - -### Samples - -See the [sample projects](Samples/Index.md) built with the ABP Framework. - -### Want to Contribute? - -ABP is a community-driven open source project. See [the contribution guide](Contribution/Index.md) if you want to be a part of this project. diff --git a/docs/en/Integration-Services.md b/docs/en/Integration-Services.md deleted file mode 100644 index 6c31fb69bf..0000000000 --- a/docs/en/Integration-Services.md +++ /dev/null @@ -1,116 +0,0 @@ -# Integration Services - -The *Integration Service* concept was created to distinguish the [application services](Application-Services.md) that are built for inter-module (or inter-microservice) communication from the application services that are intended to be consumed from a UI or a client application. - -The following figure shows a few microservices behind an API Gateway that is consumed by a UI application and 3rd-party client applications: - -![integration-services](images/integration-services.png) - -HTTP requests coming from out of the API Gateway can be called as *external request*, while the HTTP requests performed between microservices can be considered as *internal requests*. The application services that are designed to respond to these internal requests are called as *integration services*, because their purpose is to integrate microservices in the system, rather than respond to user requests. - -## Marking an Application Service as Integration Service - -Assume that you have an application service named `ProductAppService`, and you want to use that application service as an integration service. In that case, you can use the `[IntegrationService]` attribute on top of the application service class as shown below: - -```csharp -[IntegrationService] -public class ProductAppService : ApplicationService, IProductAppService -{ - // ... -} -``` - -If your application service has an interface, like `IProductService` in this example, you can use it on the service interface: - -```csharp -[IntegrationService] -public interface IProductAppService : IApplicationService -{ - // ... -} -``` - -> If you've used the `[IntegrationService]` on top of your service interface, it is *not needed* to use on the service class too. - -That's all. From now, ABP will handle your application service as integration service and implement the followings by convention: - -* That service is **not exposed** by default, unless you explicitly set `ExposeIntegrationServices` options (see the *Exposing Integration Services* section). -* If you are using the [Auto API Controllers](API/Auto-API-Controllers.md) feature in your application, the **URL prefix** will be `/integration-api` instead of `/api` for your integration services. Thus, you can distinguish internal and external service communications and take additional actions, such as preventing REST API calls for integration services out of API Gateway. -* **Audit logging** is disabled by default for the integration services. See the next section if you want to enable it. - -## Marking an MVC Controller as Integration Service - -In addition to application services, you can mark a regular MVC Controller as integration service, using the same `IntegrationService` attribute, or inheriting an interface that has the `IntegrationService` attribute. - -**Example:** - -````csharp -[IntegrationService] // Mark as integration service -[Route("integration-api/products")] -public class ProductController : AbpControllerBase -{ - //... -} -```` - -When you use the `IntegrationService` attribute, ABP will handle your controller as integration service and implement the followings by convention: - -* That controller is **not exposed** to clients by default, unless you explicitly set `ExposeIntegrationServices` options (see the *Exposing Integration Services* section). -* **Audit logging** is disabled by default for controller. See the next section if you want to enable it. - -## Configuration - -### Exposing Integration Services - -Integration services and controllers are not exposed by default for security reasons. They typically don't require authorization, so you should **carefully and explicitly** allow them to be visible and usable to client applications. - -To expose integration services and controllers, set `AbpAspNetCoreMvcOptions.ExposeIntegrationServices` to `true` in the `ConfigureServices` method of your [module class](Module-Development-Basics.md): - -````csharp -Configure(options => -{ - options.ExposeIntegrationServices = true; -}); -```` - -> Hiding integration services is useful when you are building reusable application modules, where they may be used in a monolith application or in a microservice system. In a monolith application, integration services don't need to be exposed outside since the modules may in-process communicate with each other. On the other hand, if you build a microservice solution and use that module as a service, it will be proper to expose the integration services, so other microservices can consume them remotely inside your private network (or Kubernetes cluster). In that case, be careful to not accidently expose the integration services out of your private network. Configuring your API Gateway so that it blocks requests to `integration-api` prefixed URLs from outside of your network will be a good option. - -### Enabling/Disabling the Audit Logging - -Audit Logging is disabled by default for integration services but it can be enabled by configuring the `AbpAuditingOptions` [options class](Options.md) in the `ConfigureServices` method of your [module class](Module-Development-Basics.md): - -```csharp -Configure(options => -{ - options.IsEnabledForIntegrationService = true; -}); -``` - -> Please refer to the [audit logging document](Audit-Logging.md) for other options and details. - -### Filtering Auto API Controllers - -You can filter integration services (or non-integration services) while creating [Auto API Controllers](API/Auto-API-Controllers.md), using the `ApplicationServiceTypes` option of the `ConventionalControllerSetting` by configuring the `AbpAspNetCoreMvcOptions` as shown below: - -```csharp -PreConfigure(options => -{ - options.ConventionalControllers.Create( - typeof(MyApplicationModule).Assembly, - conventionalControllerSetting => - { - conventionalControllerSetting.ApplicationServiceTypes = - ApplicationServiceTypes.IntegrationServices; - }); -}); -``` - -Tip: You can call the `options.ConventionalControllers.Create` multiple times to configure regular application services and integration services with different options. - -> Please refer to the [Auto API Controllers document](API/Auto-API-Controllers.md) for more information about the Auto API Controller system. - -## See Also - -* [Application Services](Application-Services.md) -* [Auto API Controllers](API/Auto-API-Controllers.md) -* [Audit Logging](Audit-Logging.md) diff --git a/docs/en/Integration-Tests.md b/docs/en/Integration-Tests.md deleted file mode 100644 index 9ad4741822..0000000000 --- a/docs/en/Integration-Tests.md +++ /dev/null @@ -1 +0,0 @@ -This document has been [moved to here](Testing.md). \ No newline at end of file diff --git a/docs/en/JSON.md b/docs/en/JSON.md deleted file mode 100644 index d4fc550982..0000000000 --- a/docs/en/JSON.md +++ /dev/null @@ -1,74 +0,0 @@ -# JSON -The ABP Framework provides an abstraction to work with JSON. Having such an abstraction has some benefits; - -* You can write library independent code. Therefore, you can change the underlying library with the minimum effort and code change. -* You can use the predefined converters defined in the ABP without worrying about the underlying library's internal details. - -> The JSON serialization system is implemented with the [Volo.Abp.Json](https://www.nuget.org/packages/Volo.Abp.Json) NuGet package([Volo.Abp.Json.SystemTextJson](https://www.nuget.org/packages/Volo.Abp.Json.SystemTextJson) is the default implementation). Most of the time, you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Json) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). - -## IJsonSerializer - -You can inject `IJsonSerializer` and use it for JSON operations. Here is the available operations in the `IJsonSerializer` interface. - -```csharp -public interface IJsonSerializer -{ - string Serialize(object obj, bool camelCase = true, bool indented = false); - T Deserialize(string jsonString, bool camelCase = true); - object Deserialize(Type type, string jsonString, bool camelCase = true); -} -``` -Usage Example: - -```csharp -public class ProductManager -{ - public IJsonSerializer JsonSerializer { get; } - - public ProductManager(IJsonSerializer jsonSerializer) - { - JsonSerializer = jsonSerializer; - } - - public void SendRequest(Product product) - { - var json= JsonSerializer.Serialize(product); - // Left blank intentionally for demo purposes... - } -} -``` - -## Configuration - -### AbpJsonOptions - -`AbpJsonOptions` type provides options for the JSON operations in the ABP Framework. - -Properties: -* **InputDateTimeFormats(`List`)**: Formats of input JSON date, Empty string means default format. You can provide multiple formats to parse the date. -* **OutputDateTimeFormat(`string`)**: Format of output json date, Null or empty string means default format. - -## System Text Json - -### AbpSystemTextJsonSerializerOptions - -- **JsonSerializerOptions(`System.Text.Json.JsonSerializerOptions`)**: Global options for System.Text.Json library operations. See [here](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions) for reference. - -### AbpSystemTextJsonSerializerModifiersOptions - -- **Modifiers(`List>`)**: Configure `Modifiers` of `DefaultJsonTypeInfoResolver`. See [here](https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-6/#json-contract-customization) for reference. - - -## Newtonsoft - -Add [Volo.Abp.Json.Newtonsoft](https://www.nuget.org/packages/Volo.Abp.Json.Newtonsoft) package and depends on `AbpJsonNewtonsoftModule` to replace the `System Text Json`. - -#### AbpNewtonsoftJsonSerializerOptions - -- **JsonSerializerSettings(`Newtonsoft.Json.JsonSerializerSettings`)**: Global options for Newtonsoft library operations. See [here](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonSerializerSettings.htm) for reference. - -## Configuring JSON options in ASP.NET Core - -You can change the JSON behavior in ASP.NET Core by configuring [JsonOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.jsonoptions) or -[MvcNewtonsoftJsonOptions](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.mvcnewtonsoftjsonoptions)(if you use `Newtonsoft.Json`) - diff --git a/docs/en/Json-Serialization.md b/docs/en/Json-Serialization.md deleted file mode 100644 index 7d6072c041..0000000000 --- a/docs/en/Json-Serialization.md +++ /dev/null @@ -1,3 +0,0 @@ -# JSON - -TODO \ No newline at end of file diff --git a/docs/en/KB/Windows-Path-Too-Long-Fix.md b/docs/en/KB/Windows-Path-Too-Long-Fix.md deleted file mode 100644 index 6a822c1402..0000000000 --- a/docs/en/KB/Windows-Path-Too-Long-Fix.md +++ /dev/null @@ -1,39 +0,0 @@ -# How to Fix "Filename too long" Error on Windows - -If you encounter the "filename too long" or "unzip" error on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 255 characters. - -## Solution 1 -Try [enabling the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). - -If you face long path errors related to Git, try the following command to enable long paths in Windows. -``` -git config --system core.longpaths true -``` - -See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path - - -## Solution 2 - -You may encounter a "DirectoryNotFoundException - Could not find a part of the path" exception in Windows while using certain .NET MAUI build tools. This is related to some 32 bit .NET MAUI build tools. To resolve this issue, you can try placing the solution in the root directory of your drive, such as `C:\Projects\`. However, please note that this solution is specific to this particular exception and may not be applicable to all cases of the Windows long path issue. - - -## Solution 3 - -You can define an alias for a path in Windows by creating a symbolic link using the `mklink` command in the command prompt. Here's an example: - -``` -mklink /D C:\MyProject C:\my\long\path\to\solution\ -``` - -> Your **solution (.sln)** file should be in `C:\my\long\path\to\solution\`. Keep in mind that, if you have relative paths in your .csproj file, it will not work! - -This command creates a symbolic link named `MyProject` in the root of the `C:` drive that points to the `C:\my\long\path\to\solution\` directory. You can then use `C:\MyProject` to access the contents of the `C:\my\long\path\to\solution\` directory. - -> Note that you need to run the command prompt as an administrator to the create symbolic links. - -Then you can try building your project with `dotnet build` command. - -``` -dotnet build C:\MyProject\MyProjectName.sln -``` diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md deleted file mode 100644 index 5c5d967f5f..0000000000 --- a/docs/en/Local-Event-Bus.md +++ /dev/null @@ -1,247 +0,0 @@ -# Local Event Bus - -The Local Event Bus allows services to publish and subscribe to **in-process events**. That means it is suitable if two services (publisher and subscriber) are running in the same process. - -## Publishing Events - -There are two ways of publishing local events explained in the following sections. - -### Publishing Events Using the ILocalEventBus - -`ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event. - -**Example: Publish a local event when the stock count of a product changes** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus.Local; - -namespace AbpDemo -{ - public class MyService : ITransientDependency - { - private readonly ILocalEventBus _localEventBus; - - public MyService(ILocalEventBus localEventBus) - { - _localEventBus = localEventBus; - } - - public virtual async Task ChangeStockCountAsync(Guid productId, int newCount) - { - //TODO: IMPLEMENT YOUR LOGIC... - - //PUBLISH THE EVENT - await _localEventBus.PublishAsync( - new StockCountChangedEvent - { - ProductId = productId, - NewCount = newCount - } - ); - } - } -} -```` - -`PublishAsync` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class: - -````csharp -using System; - -namespace AbpDemo -{ - public class StockCountChangedEvent - { - public Guid ProductId { get; set; } - - public int NewCount { get; set; } - } -} -```` - -Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). - -### Publishing Events Inside Entity / Aggregate Root Classes - -[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish local events inside entity / aggregate root classes. - -**Example: Publish a local event inside an aggregate root method** - -````csharp -using System; -using Volo.Abp.Domain.Entities; - -namespace AbpDemo -{ - public class Product : AggregateRoot - { - public string Name { get; set; } - - public int StockCount { get; private set; } - - private Product() { } - - public Product(Guid id, string name) - : base(id) - { - Name = name; - } - - public void ChangeStockCount(int newCount) - { - StockCount = newCount; - - //ADD an EVENT TO BE PUBLISHED - AddLocalEvent( - new StockCountChangedEvent - { - ProductId = Id, - NewCount = newCount - } - ); - } - } -} -```` - -`AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created, updated or deleted) into the database. - -> Tip: If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. - -#### IGeneratesDomainEvents Interface - -Actually, adding local events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you. - -> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example. - -#### How It Was Implemented? - -Calling the `AddLocalEvent` doesn't immediately publish the event. The event is published when you save changes to the database; - -* For EF Core, it is published on `DbContext.SaveChanges`. -* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). - -## Subscribing to Events - -A service can implement the `ILocalEventHandler` to handle the event. - -**Example: Handle the `StockCountChangedEvent` defined above** - -````csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus; - -namespace AbpDemo -{ - public class MyHandler - : ILocalEventHandler, - ITransientDependency - { - public async Task HandleEventAsync(StockCountChangedEvent eventData) - { - //TODO: your code that does something on the event - } - } -} -```` - -That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic in your handler class. - -* **One or more handlers** can subscribe to the same event. -* A single event handler class can **subscribe to multiple events** by implementing the `ILocalEventHandler` interface for each event type. - -If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope. - -> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. - -### LocalEventHandlerOrder Attribute - -`LocalEventHandlerOrder` attribute can be used to set the execution order for the event handlers, which can be helpful if you want to handle your event handlers in a specific order. - -````csharp -[LocalEventHandlerOrder(-1)] -public class MyHandler - : ILocalEventHandler, - ITransientDependency -{ - public async Task HandleEventAsync(StockCountChangedEvent eventData) - { - //TODO: your code that does something on the event - } -} -```` - -> By default, all event handlers have an order value of 0. Thus, if you want to take certain event handlers to be executed before other event handlers, you can set the order value as a negative value. - -#### LocalEventHandlerOrderAttribute Properties - -* `Order` (`int`): Used to set the execution order for a certain event handler. - -### Transaction & Exception Behavior - -Event handlers are always executed in the same [unit of work](Unit-Of-Work.md) scope, that means in the same database transaction with the code that published the event. If an event handler throws an exception, the unit of work (database transaction) is rolled back. So, **use try-catch yourself** in the event handler if you want to hide the error. - -When you call `ILocalEventBus.PublishAsync`, the event handlers are not immediately executed. Instead, they are executed just before the current unit of work completed (an unhandled exception in the handler still rollbacks the current unit of work). If you want to immediately execute the handlers, set the optional `onUnitOfWorkComplete` parameter to `false`. - -> Keeping the default behavior is recommended unless you don't have a unique requirement. `onUnitOfWorkComplete` option is not available when you publish events inside entity / aggregate root classes (see the *Publishing Events Inside Entity / Aggregate Root Classes* section). - -## Pre-Built Events - -It is very common to **publish events on entity create, update and delete** operations. ABP Framework **automatically** publish these events for all entities. You can just subscribe to the related event. - -**Example: Subscribe to an event that published when a user was created** - -````csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Entities.Events; -using Volo.Abp.EventBus; - -namespace AbpDemo -{ - public class MyHandler - : ILocalEventHandler>, - ITransientDependency - { - public async Task HandleEventAsync( - EntityCreatedEventData eventData) - { - var userName = eventData.Entity.UserName; - var email = eventData.Entity.Email; - //... - } - } -} -```` - -This class subscribes to the `EntityCreatedEventData`, which is published just after a user was created (but before the current transaction is completed). For example, you may want to send a "Welcome" email to the new user. - -The pre-built event types are; - -* `EntityCreatedEventData` is published just after an entity was successfully created. -* `EntityUpdatedEventData` is published just after an entity was successfully updated. -* `EntityDeletedEventData` is published just after an entity was successfully deleted. -* `EntityChangedEventData` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. - -### How It Was Implemented? - -Pre-build events are published when you save changes to the database; - -* For EF Core, they are published on `DbContext.SaveChanges`. -* For MongoDB, they are published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). - -#### AbpEntityChangeOptions - -There is a `PublishEntityUpdatedEventWhenNavigationChanges` option in the `AbpEntityChangeOptions` class with a default value of `true`. -If you set it to `false`, the `EntityUpdatedEventData` will not be published when a navigation property changes. - -> This option is only used for the EF Core. - -## See Also - -* [Distributed Event Bus](Distributed-Event-Bus.md) diff --git a/docs/en/Localization.md b/docs/en/Localization.md deleted file mode 100644 index 5b04940025..0000000000 --- a/docs/en/Localization.md +++ /dev/null @@ -1,258 +0,0 @@ -# Localization - -ABP's localization system is seamlessly integrated to the `Microsoft.Extensions.Localization` package and compatible with the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization). It adds some useful features and enhancements to make it easier to use in real life application scenarios. - -## Installation - -> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. - -You can use the [ABP CLI](CLI.md) to install the Volo.Abp.Localization package to your project. Execute the following command in the folder of the .csproj file that you want to install the package on: - -```bash -abp add-package Volo.Abp.Localization -``` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Localization). - -Then you can add **AbpLocalizationModule** dependency to your module: - -```c# -using Volo.Abp.Modularity; -using Volo.Abp.Localization; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpLocalizationModule))] - public class MyModule : AbpModule - { - //... - } -} -``` - -## Creating A Localization Resource - -A localization resource is used to group related localization strings together and separate them from other localization strings of the application. A [module](Module-Development-Basics.md) generally defines its own localization resource. Localization resource is just a plain class. Example: - -````C# -public class TestResource -{ -} -```` - -Then it should be added using `AbpLocalizationOptions` as shown below: - -````C# -[DependsOn(typeof(AbpLocalizationModule))] -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - // "YourRootNameSpace" is the root namespace of your project. It can be empty if your root namespace is empty. - options.FileSets.AddEmbedded("YourRootNameSpace"); - }); - - Configure(options => - { - //Define a new localization resource (TestResource) - options.Resources - .Add("en") - .AddVirtualJson("/Localization/Resources/Test"); - }); - } -} -```` - -In this example; - -* Added a new localization resource with "en" (English) as the default culture. -* Used JSON files to store the localization strings. -* JSON files are embedded into the assembly using `AbpVirtualFileSystemOptions` (see [virtual file system](Virtual-File-System.md)). - -JSON files are located under "/Localization/Resources/Test" project folder as shown below: - -![localization-resource-json-files](images/localization-resource-json-files.png) - -A JSON localization file content is shown below: - -````json -{ - "culture": "en", - "texts": { - "HelloWorld": "Hello World!" - } -} -```` - -* Every localization file should define the `culture` code for the file (like "en" or "en-US"). -* `texts` section just contains key-value collection of the localization strings (keys may have spaces too). - -> ABP will ignore (skip) the JSON file if the `culture` section is missing. - -### Default Resource - -`AbpLocalizationOptions.DefaultResourceType` can be set to a resource type, so it is used when the localization resource was not specified: - -````csharp -Configure(options => -{ - options.DefaultResourceType = typeof(TestResource); -}); -```` - -> The [application startup template](Startup-Templates/Application.md) sets `DefaultResourceType` to the localization resource of the application. - -### Short Localization Resource Name - -Localization resources are also available in the client (JavaScript) side. So, setting a short name for the localization resource makes it easy to use localization texts. Example: - -````C# -[LocalizationResourceName("Test")] -public class TestResource -{ -} -```` - -See the Getting Localized Test / Client Side section below. - -### Inherit From Other Resources - -A resource can inherit from other resources which makes possible to re-use existing localization strings without referring the existing resource. Example: - -````C# -[InheritResource(typeof(AbpValidationResource))] -public class TestResource -{ -} -```` - -Alternative inheritance by configuring the `AbpLocalizationOptions`: - -````C# -services.Configure(options => -{ - options.Resources - .Add("en") //Define the resource by "en" default culture - .AddVirtualJson("/Localization/Resources/Test") //Add strings from virtual json files - .AddBaseTypes(typeof(AbpValidationResource)); //Inherit from an existing resource -}); -```` - -* A resource may inherit from multiple resources. -* If the new resource defines the same localized string, it overrides the string. - -### Extending Existing Resource - -Inheriting from a resource creates a new resource without modifying the existing one. In some cases, you may want to not create a new resource but directly extend an existing resource. Example: - -````C# -services.Configure(options => -{ - options.Resources - .Get() - .AddVirtualJson("/Localization/Resources/Test/Extensions"); -}); -```` - -* If an extension file defines the same localized string, it overrides the string. - -## Getting the Localized Texts - -Getting the localized text is pretty standard. - -### Simplest Usage In A Class - -Just inject the `IStringLocalizer` service and use it like shown below: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IStringLocalizer _localizer; - - public MyService(IStringLocalizer localizer) - { - _localizer = localizer; - } - - public void Foo() - { - var str = _localizer["HelloWorld"]; - } -} -```` - -##### Format Arguments - -Format arguments can be passed after the localization key. If your message is `Hello {0}, welcome!`, then you can pass the `{0}` argument to the localizer like `_localizer["HelloMessage", "John"]`. - -> Refer to the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) for details about using the localization. - -### Using In A Razor View/Page - -Use `IHtmlLocalizer` in razor views/pages; - -````c# -@inject IHtmlLocalizer Localizer - -

    @Localizer["HelloWorld"]

    -```` - -### Special Base Classes - -Some ABP Framework base classes provide a `L` property to use the localizer even easier. - -**Example: Localize a text in an application service method** - -```csharp -using System.Threading.Tasks; -using MyProject.Localization; -using Volo.Abp.Application.Services; - -namespace MyProject -{ - public class TestAppService : ApplicationService - { - public TestAppService() - { - LocalizationResource = typeof(MyProjectResource); - } - - public async Task DoIt() - { - var str = L["HelloWorld"]; - } - } -} -``` - -When you set the `LocalizationResource` in the constructor, the `ApplicationService` class uses that resource type when you use the `L` property, just like in the `DoIt()` method. - -Setting `LocalizationResource` in every application service can be tedious. You can create an abstract base application service class, set it there and derive your application services from that base class. This is already implemented when you create a new project with the [startup templates](Startup-Templates/Application.md). So, you can simply inherit from the base class directly use the `L` property: - -```csharp -using System.Threading.Tasks; - -namespace MyProject -{ - public class TestAppService : MyProjectAppService - { - public async Task DoIt() - { - var str = L["HelloWorld"]; - } - } -} -``` - -The `L` property is also available for some other base classes like `AbpController` and `AbpPageModel`. - -## The Client Side - -See the following documents to learn how to reuse the same localization texts in the JavaScript side; - -* [Localization for the MVC / Razor Pages UI](UI/AspNetCore/JavaScript-API/Localization.md) -* [Localization for the Blazor UI](UI/Blazor/Localization.md) -* [Localization for the Angular UI](UI/Angular/Localization.md) -* [Video tutorial](https://abp.io/video-courses/essentials/localization) \ No newline at end of file diff --git a/docs/en/Logging.md b/docs/en/Logging.md deleted file mode 100644 index 7fa45c03b9..0000000000 --- a/docs/en/Logging.md +++ /dev/null @@ -1,6 +0,0 @@ -# Logging - -ABP Framework doesn't implement any logging infrastructure. It uses the [ASP.NET Core's logging system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging). - -> .NET Core's logging system is actually independent from the ASP.NET Core. It is usable in any type of application. - diff --git a/docs/en/MailKit.md b/docs/en/MailKit.md deleted file mode 100644 index b05a28f9ba..0000000000 --- a/docs/en/MailKit.md +++ /dev/null @@ -1,48 +0,0 @@ -# MailKit Integration - -[MailKit](http://www.mimekit.net/) is a cross-platform, popular open source mail client library for .net. ABP Framework provides an integration package to use the MailKit as the [email sender](Emailing.md). - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.MailKit -```` - -If you haven't done it yet, you first need to install the ABP CLI. For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.MailKit). - -## Sending Emails - -### IEmailSender - -[Inject](Dependency-Injection.md) the standard `IEmailSender` into any service and use the `SendAsync` method to send emails. See the [email sending document](Emailing.md) for details. - -> `IEmailSender` is the suggested way to send emails even if you use MailKit, since it makes your code provider independent. - -### IMailKitSmtpEmailSender - -MailKit package also exposes the `IMailKitSmtpEmailSender` service that extends the `IEmailSender` by adding the `BuildClientAsync()` method. This method can be used to obtain a `MailKit.Net.Smtp.SmtpClient` object that can be used to perform MailKit specific operations. - -## Configuration - -MailKit integration package uses the same settings defined by the email sending system. So, refer to the [email sending document](Emailing.md) for the settings. - -In addition to the standard settings, this package defines `AbpMailKitOptions` as a simple [options](Options.md) class. This class defines only one options: - -* **SecureSocketOption**: Used to set one of the `SecureSocketOptions`. Default: `null` (uses the defaults). - -**Example: Use *SecureSocketOptions.SslOnConnect*** - -````csharp -Configure(options => -{ - options.SecureSocketOption = SecureSocketOptions.SslOnConnect; -}); -```` - -Refer to the [MailKit documentation](http://www.mimekit.net/) to learn more about this option. - -## See Also - -* [Email sending](Emailing.md) \ No newline at end of file diff --git a/docs/en/Microservice-Architecture.md b/docs/en/Microservice-Architecture.md deleted file mode 100644 index b98323908d..0000000000 --- a/docs/en/Microservice-Architecture.md +++ /dev/null @@ -1,30 +0,0 @@ -# Microservice Architecture - -*"Microservices are a software development technique—a variant of the **service-oriented architecture** (SOA) architectural style that structures an application as a collection of **loosely coupled services**. In a microservices architecture, services are **fine-grained** and the protocols are **lightweight**. The benefit of decomposing an application into different smaller services is that it improves **modularity**. This makes the application easier to understand, develop, test, and become more resilient to architecture erosion. It **parallelizes development** by enabling small autonomous teams to **develop, deploy and scale** their respective services independently. It also allows the architecture of an individual service to emerge through **continuous refactoring**. Microservices-based architectures enable **continuous delivery and deployment**."* - -— [Wikipedia](https://en.wikipedia.org/wiki/Microservices) - -## Introduction - -One of the major goals of the ABP framework is to provide a convenient infrastructure to create microservice solutions. To make this possible, - -* Provides a [module system](Module-Development-Basics.md) that allows you to split your application into modules where each module may have its own database, entities, services, APIs, UI components/pages... etc. -* Offers an [architectural model](Best-Practices/Module-Architecture.md) to develop your modules to be compatible to microservice development and deployment. -* Provides [best practices guide](Best-Practices/Index.md) to develop your module standards-compliance. -* Provides base infrastructure to implement [Domain Driven Design](Domain-Driven-Design.md) in your microservices. -* Provide services to [automatically create REST-style APIs](API/Auto-API-Controllers.md) from your application services. -* Provide services to [automatically create C# API clients](API/Dynamic-CSharp-API-Clients.md) that makes easy to consume your services from another service/application. -* Provides a [distributed event bus](Event-Bus.md) to communicate your services. -* Provides many other services to make your daily development easier. - -## Microservice for New Applications - -One common advise to start a new solution is **always to start with a monolith**, keep it modular and split into microservices once the monolith becomes a problem. This makes your progress fast in the beginning especially if your team is small and you don't want to deal with challenges of the microservice architecture. - -However, developing such a well-modular application can be a problem since it is **hard to keep modules isolated** from each other as you would do it for microservices (see [Stefan Tilkov's article](https://martinfowler.com/articles/dont-start-monolith.html) about that). Microservice architecture naturally forces you to develop well isolated services, but in a modular monolithic application it's easy to tight couple modules to each other and design **weak module boundaries** and API contracts. - -ABP can help you in that point by offering a **microservice-compatible, strict module architecture** where your module is split into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application. See the [module development best practice guide](Best-Practices/Index.md) that offers a **microservice-first module design**. All [standard ABP modules](https://github.com/abpframework/abp/tree/master/modules) are developed based on this guide. So, you can use these modules by embedding into your monolithic solution or deploy them separately and use via remote APIs. They can share a single database or can have their own database based on your simple configuration. - -## Microservice Demo Solution: eShopOnAbp - -The [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) demonstrates a complete microservice solution based on the ABP framework. diff --git a/docs/en/Migration-Guides/Abp-4_0-Angular.md b/docs/en/Migration-Guides/Abp-4_0-Angular.md deleted file mode 100644 index 6095a0f447..0000000000 --- a/docs/en/Migration-Guides/Abp-4_0-Angular.md +++ /dev/null @@ -1,121 +0,0 @@ -# Angular UI 3.3 to 4.0 Migration Guide - -## Angular v11 - -The new ABP Angular UI is based on Angular v11 and TypeScript v4. The difference between v10 and v11 is non-breaking so you do not have to update right away but it is recommended. Nevertheless, ABP modules will keep working with Angular v10. Therefore, if your project is Angular v10, you do not need to update to Angular 11. The update is usually very easy though. - -You can read more about Angular v11 [here](https://blog.angular.io/version-11-of-angular-now-available-74721b7952f7) - -## **Breaking Changes** - -### **Localization** - -Prior to ABP 4.x, we'd handled what locale files of Angular should be created to load them lazily. However, this made it impossible to add new locale files (to be lazily loaded) for our users. With ABP 4.x, we enabled an option to pass a function to `CoreModule`. - -The quickest solution is as follows: - -```typescript -// app.module.ts - -import { registerLocale } from '@abp/ng.core/locale'; -// or -// import { registerLocale } from '@volo/abp.ng.language-management/locale'; -// if you have commercial license - - -@NgModule({ -imports: [ - // ... - CoreModule.forRoot({ - // ...other options, - registerLocaleFn: registerLocale() - }), - //... -] -export class AppModule {} -``` - -You can find the related issue [here](https://github.com/abpframework/abp/issues/6066) -Also, please refer to [the docs](https://docs.abp.io/en/abp/latest/UI/Angular/Localization#registering-a-new-locale) for more information. - -### **Removed the Angular Account Module Public UI** - -With ABP 4.x, we have retired `@abp/ng.account`, it is no longer a part of our framework. There won't be any newer versions of this package as well. Therefore, you can delete anything related to this package. -There should be a config in `app-routing.module` for path `account` and `AccountConfigModule` import in `app.module` - -However, if you are using the commercial version of this package, a.k.a `@volo/abp.ng.account`, this package will continue to exist because it contains `AccountAdminModule` which is still being maintained and developed. You only need to delete the route config from `app-routing.module` - -You can find the related issue [here](https://github.com/abpframework/abp/issues/5652) - -Angular UI is using the Authorization Code Flow to authenticate since version 3.1.0 by default. Starting from version 4.0, this is becoming the only option, because it is the recommended way of authenticating SPAs. - -If you haven't done it yet, see [this post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) to change the authentication of your application. - -### State Management - -In the ABP Angular UI, we've been using `NGXS` for state management. However, we've decided that the Angular UI should be agnostic with regard to state management. Our users should be able to handle the state in any way they prefer. They should be able to use any library other than `NGXS` or no library at all. That's why we have created our internal store in version 3.2. It is a simple utility class that employs `BehaviorSubject` internally. - -You can examine it [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/utils/internal-store-utils.ts) - -With version 4.0, we will keep utilizing our `InternalStore` instead of `@ngxs/store` in our services and move away from `@ngxs/store`. We plan to remove any dependency of `NGXS` by version 5.0. - -With this in mind, we've already deprecated some services and implemented some breaking changes. - -#### Removed the `SessionState` - -Use `SessionStateService` instead of the `SessionState`. See [this issue](https://github.com/abpframework/abp/issues/5606) for details. - -#### Deprecated the `ConfigState` - -`ConfigState` is now deprecated and should not be used. - -`ConfigState` reference removed from `CoreModule`. If you want to use the `ConfigState` (not recommended), you should pass the state to `NgxsModule` as shown below: - -```typescript -//app.module.ts - -import { ConfigState } from '@abp/ng.core'; - -// ... - -imports: [ - NgxsModule.forRoot([ConfigState]), -// ... -``` - -Moving away from the global store, we create small services with a single responsibility. There are two new services available in version 4.0 which are `EnvironmentService` and `PermissionService`. - -See [the related issue](https://github.com/abpframework/abp/issues/6154) - -Please refer to the following docs for detail information and examples -- [`ConfigStateService`](../UI/Angular/Config-State-Service) -- [`EnvironmentService`](../UI/Angular/Environment#EnvironmentService) -- [`PermissionService`](../UI/Angular/Permission-Management#) - -### Deprecated Interfaces - - Some interfaces have long been marked as deprecated and now they are removed. - -- Removed replaceable components state. -- Removed legacy identity types and service. -- Removed legacy tenant management types and service. -- Removed legacy feature management types and services. -- Removed legacy permission management types and service. - -### Deprecated commercial interfaces -- Removed legacy audit logging types and services. -- Removed legacy identity types and services. -- Removed legacy language management types and services. -- Removed legacy saas types and services. - -### Identity Server [COMMERCIAL] - -With the new version of Identity Server, there happened some breaking changes in the backend (also in the database). We've implemented those in the Angular UI. -If you are just using the package `@volo/abp.ng.identity-server` as is, you will not need to do anything. -However, there are a couple of breaking changes we need to mention. - -- As we have stated above, we want to remove the dependency of `NGXS`. Thus, we have deleted all of the actions defined in `identity-server.actions`. Those actions are not needed anymore and the state is managed locally. With the actions gone, `IdentityServerStateService` became unused and got deleted as well. - -- `ApiScope` is also available as a new entity (It was part of `ApiResource` before). It provides tokens for entity prop, entity actions, toolbar, edit and create form contributors like the existing ones which are `Client`, `IdentityResource` and `ApiResource` - -- There were some deprecated interfaces within the `IdentityServer` namespace. Those are no longer being used, instead, their replacements were generated by `ABP Cli` using the `generate-proxy` command. diff --git a/docs/en/Migration-Guides/Abp-4_0-Blazor.md b/docs/en/Migration-Guides/Abp-4_0-Blazor.md deleted file mode 100644 index 7f00d6de75..0000000000 --- a/docs/en/Migration-Guides/Abp-4_0-Blazor.md +++ /dev/null @@ -1,88 +0,0 @@ -# Blazor UI 3.3 to 4.0 Migration Guide - -## Startup Template Changes - -These changes are required to manually applied in your own solution. It would be easier if you create a new solution based on 4.0 with the same name of your current solution then compare the files. - -### Csproj File / Dependencies - -* Add `true` to the `PropertyGroup` section of your project (`.csproj`) file. -* Update the `Blazorise.*` packages to the latest version (to the latest RC for the ABP 4.0 preview). - -### `wwwroot/index.html` - -There are some changes made in the index.html file; - -* Removed JQuery & Bootstrap JavaScript dependencies -* Replaced Bootstrap and FontAwesome imports with local files instead of CDN usages. -* Re-arranged some ABP CSS file locations. -* Introduced the `abp bundle` CLI command to manage global Style/Script file imports. - -Follow the steps below to apply the changes; - -1. Add the bundle contributor class into your project (it will be slightly different based on your solution namespaces): - -````csharp -using Volo.Abp.Bundling; - -namespace MyCompanyName.MyProjectName.Blazor -{ - public class MyProjectNameBundleContributor : IBundleContributor - { - public void AddScripts(BundleContext context) - { - } - - public void AddStyles(BundleContext context) - { - context.Add("main.css"); - } - } -} -```` - -If you are using another global style/script files, add them here. - -2. Remove all the `` elements and replace with the following comment tags: - -````html - - -```` - -3. Remove all the `` elements and replace with the following comment tags: - -````html - - -```` - -4. Execute the following command in a terminal in the root folder of the Blazor project (`.csproj`) file (ensure that you're using the ABP CLI version 4.0): - -````bash -abp bundle -```` - -This will fill in the `Styles` and `Scripts` tags based on the dependencies. - -5. You can clean the `blazor-error-ui` related sections from your `main.css` file since they are not needed anymore. - -### The Root Element - -This change is optional but recommended. - -* Change `...` to `
    ...
    ` in the `wwwroot/index.html`. -* Change `builder.RootComponents.Add("app");` to `builder.RootComponents.Add("#ApplicationContainer");` in the *YourProjectBlazorModule.cs*. - -## AbpCrudPageBase Changes - -If you've derived your pages from the `AbpCrudPageBase` class, then you may need to apply the following changes; - -- `OpenEditModalAsync` method gets `EntityDto` instead of id (`Guid`) parameter. Pass `context` instead of `context.Id`. -- `DeleteEntityAsync` method doesn't display confirmation dialog anymore. You can use the new `EntityActions` component in Data Grids to show confirmation messages. You can also inject `IUiMessageService` to your page or component and call the `ConfirmAsync` explicitly. -- Added `GetListInput` as a base property that is used to filter while getting the entities from the server. - -## Others - -- Refactored namespaces for some Blazor components ([#6015](https://github.com/abpframework/abp/issues/6015)). -- Removed Async Suffix from IUiMessageService methods ([#6123](https://github.com/abpframework/abp/pull/6123)). \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-4_0-MVC-Razor-Pages.md b/docs/en/Migration-Guides/Abp-4_0-MVC-Razor-Pages.md deleted file mode 100644 index d2fa97ec5e..0000000000 --- a/docs/en/Migration-Guides/Abp-4_0-MVC-Razor-Pages.md +++ /dev/null @@ -1,6 +0,0 @@ -# MVC / Razor Pages UI 3.3 to 4.0 Migration Guide - -## Use IBrandingProvider in the Volo.Abp.UI Package - -This will be a breaking change for MVC UI, but very easy to fix. `IBrandingProvider` is being moved from `Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components` to `Volo.Abp.Ui.Branding` namespace. So, just update the namespace imports. - diff --git a/docs/en/Migration-Guides/Abp-4_0.md b/docs/en/Migration-Guides/Abp-4_0.md deleted file mode 100644 index 9e1bd4e816..0000000000 --- a/docs/en/Migration-Guides/Abp-4_0.md +++ /dev/null @@ -1,280 +0,0 @@ -# ABP Framework 3.3 to 4.0 Migration Guide - -This document introduces the breaking changes done in the ABP Framework 4.0 and explains how to fix your 3.x based solutions while upgrading to the ABP Framework 4.0. - -> See [the blog post](https://blog.abp.io/abp/ABP.IO-Platform-v4.0-RC-Has-Been-Released-based-on-.NET-5.0) to learn what's new with the ABP Framework 4.0. This document only focuses on the breaking changes. - -## Overall - -Here, the overall list of the changes; - -* Upgraded to the .NET 5.0 [(#6118](https://github.com/abpframework/abp/issues/6118)). -* Moved from Newtonsoft.Json to System.Text.Json [(#1198](https://github.com/abpframework/abp/issues/1198)). -* Upgraded to the Identity Server 4.1.1 ([#4461](https://github.com/abpframework/abp/issues/4461)). -* Switched to `kebab-case` for conventional URLs for the auto API controller routes ([#5325](https://github.com/abpframework/abp/issues/5325)). -* Removed Retry for the Dynamic HTTP Client Proxies ([#6090](https://github.com/abpframework/abp/issues/6090)). -* Creation audit properties of the entities made read-only ([#6020](https://github.com/abpframework/abp/issues/6020)). -* Changed type of the IHasExtraProperties.ExtraProperties ([#3751](https://github.com/abpframework/abp/issues/3751)). -* Use IBrandingProvider in the Volo.Abp.UI package and remove the one in the Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared ([#5375](https://github.com/abpframework/abp/issues/5375)). -* Removed the Angular Account Module Public UI (login, register... pages) since they are not being used in the default (authorization code) flow ([#5652](https://github.com/abpframework/abp/issues/5652)). -* Removed the SessionState in the @abp/ng.core package ([#5606](https://github.com/abpframework/abp/issues/5606)). -* Made some API revisions & startup template changes for the Blazor UI. - -## Upgraded to .NET 5.0 - -ABP Framework has been moved to .NET 5.0. So, if you want to upgrade to the ABP Framework 4.0, you also need to upgrade to .NET 5.0. - -See the [Migrate from ASP.NET Core 3.1 to 5.0](https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50) document to learn how to upgrade your solution to .NET 5.0. - -## Moved to System.Text.Json - -ABP Framework 4.0 uses the System.Text.Json by default as the JSON serialization library. It, actually, using a hybrid approach: Continues to use the Newtonsoft.Json when it needs to use features not supported by the System.Text.Json. - -### Unsupported Types - -If you want to use the Newtonsoft.Json to serialize/deserialize for some specific types, you can configure the `AbpSystemTextJsonSerializerOptions` in your module's `ConfigureServices` method. - -**Example: Use Newtonsoft.Json for `MySpecialClass`** - -````csharp -Configure(options => -{ - options.UnsupportedTypes.AddIfNotContains(typeof(MySpecialClass)); -}); -```` - -### Always Use the Newtonsoft.Json - -If you want to continue to use the Newtonsoft.Json library for all the types, you can set `UseHybridSerializer` to false in the `PreConfigureServices` method of your module class: - -````csharp -PreConfigure(options => -{ - options.UseHybridSerializer = false; -}); -```` - -## Upgraded to Identity Server 4.1.1 - -ABP Framework upgrades the [IdentityServer4](https://www.nuget.org/packages/IdentityServer4) library from 3.x to 4.1.1 with the ABP Framework version 4.0. IdentityServer 4.x has a lot of changes. Some of them are **breaking changes in the data structure**. - -### Entity Changes - -Entity changes don't directly affect your application; however, it is good to know. - -#### ApiScope - -As the **most critical breaking change**; Identity Server 4.x defines the `ApiScope` as an independent aggregate root. Previously, it was the child entity of the `ApiResource`. This change requires manual operation. See the _Database Changes_ section. - -Also, added `Enabled(string)` and `Description(bool,true)` properties. - -#### ApiResource - -- Added `AllowedAccessTokenSigningAlgorithms (string)` and `ShowInDiscoveryDocument(bool, default: true)` properties - -#### Client - -- Added `RequireRequestObject ` and `AllowedIdentityTokenSigningAlgorithms ` properties. -- Changed the default value of `RequireConsent` from `true` to `false`. -- Changed the default value of `RequirePkce` from `false` to `true`. - -#### DeviceFlowCodes - -- Added `SessionId ` and `Description ` properties. - -#### PersistedGrant - -- Added `SessionId `, `Description ` and `ConsumedTime ` properties - -### Database Changes - -> Attention: **Please backup your database** before the migration! - -**If you are upgrading from 3.x, then there are some steps should be done in your database.** - -#### Database Schema Migration - -If you are using **Entity Framework Core**, you need to add a new database migration, using the `Add-Migration` command, and apply changes to the database. Please **review the migration** script and read the sections below to understand if it affects your existing data. Otherwise, you may **lose some of your configuration**, which may not be easy to remember and re-configure. - -#### Seed Code - -If you haven't customized the `IdentityServerDataSeedContributor` and haven't customized the initial data inside the `IdentityServer*` tables; - -1. Update `IdentityServerDataSeedContributor` class by comparing to [the latest code](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs). You probably only need to add the `CreateApiScopesAsync` method and the code related to it. -2. Then you can simply clear all the **data** in these tables then execute the `DbMigrator` application to fill it with the new configuration. - -#### Migrating the Configuration Data - -If you've customized your IdentityServer configuration in the database or in the seed data, you should understand the changes and upgrade your code/data accordingly. Especially, the following changes will affect your application: - -- `IdentityServerApiScopes` table's `Enabled` field is dropped and re-created. So, you need to enable the API scopes again manually. -- `IdentityServerApiResourceScopes` table is dropped and recreated. So, you need to backup and move your current data to the new table. -- `IdentityServerIdentityResourceClaims` table is dropped and recreated. So, you need to backup and move your current data to the new table. - -You may need to perform additional steps based on how much you made custom configurations. - -### Other IdentityServer Changes - -IdentityServer has removed the [public origin option](https://github.com/IdentityServer/IdentityServer4/pull/4335). It was resolving HTTP/HTTPS conversion issues, but they decided to leave this to the developer. This is especially needed if you use a reverse proxy where your external protocol is HTTPS but internal protocol is HTTP. - -One simple solution is to add such a middleware at the begingning of your ASP.NET Core pipeline. - -```csharp -app.Use((httpContext, next) => -{ - httpContext.Request.Scheme = "https"; - return next(); -}); -``` - -> This sample is obtained from the [ASP.NET Core documentation](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer#scenarios-and-use-cases). You can use it if you always use HTTPS in all environments. - -### Related Resources - -- https://leastprivilege.com/2020/06/19/announcing-identityserver4-v4-0/ -- https://github.com/IdentityServer/IdentityServer4/issues/4592 - -## Auto API Controller Route Changes - -The route calculation for the [Auto API Controllers](https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers) is changing with the ABP Framework version 4.0 ([#5325](https://github.com/abpframework/abp/issues/5325)). Before v4.0 the route paths were **camelCase**. After version 4.0, it's changed to **kebab-case** route paths where it is possible. - -**A typical auto API before v4.0** - -![route-before-4](images/route-before-4.png) - -**camelCase route parts become kebab-case with 4.0** - -![route-4](images/route-4.png) - -### How to Fix? - -You may not take any action for the MVC & Blazor UI projects. - -For the Angular UI, this change may effect your client UI. If you have used the [ABP CLI Service Proxy Generation](../UI/Angular/Service-Proxies.md), you can run the server side and re-generate the service proxies. If you haven't used this tool, you should manually update the related URLs in your application. - -If there are other type of clients (e.g. 3rd-party companies) using your APIs, they also need to update the URLs. - -### Use the v3.x style URLs - -If it is hard to change it in your application, you can still to use the version 3.x route strategy, by following one of the approaches; - -- Set `UseV3UrlStyle` to `true` in the options of the `options.ConventionalControllers.Create(...)` method. Example: - -```csharp -options.ConventionalControllers - .Create(typeof(BookStoreApplicationModule).Assembly, opts => - { - opts.UseV3UrlStyle = true; - }); -``` - -This approach affects only the controllers for the `BookStoreApplicationModule`. - -- Set `UseV3UrlStyle` to `true` for the `AbpConventionalControllerOptions` to set it globally. Example: - -```csharp -Configure(options => -{ - options.UseV3UrlStyle = true; -}); -``` - -Setting it globally affects all the modules in a modular application. - -## Removed Retry for the Dynamic HTTP Client Proxies - -[Dynamic C# HTTP Client Proxies](../API/Dynamic-CSharp-API-Clients.md) were trying up to 3 times if a request fails using the [Polly](https://github.com/App-vNext/Polly) library. Starting from the version 4.0, this logic has been removed. If you need it, you should configure it in your own application, by configuring the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module. - -**Example: Retry 3 times on failure by incremental waiting between tries** - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => - { - clientBuilder.AddTransientHttpErrorPolicy( - policyBuilder => policyBuilder - .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i))) - ); - }); - }); -} -```` - -This example uses the Microsoft.Extensions.Http.Polly NuGet package. - -If you create a new solution, you can find the same configuration in the `.HttpApi.Client.ConsoleTestApp` project's module class, as an example. - -## Creation Audit Properties Made Read-Only - -Removed setters from the `IHasCreationTime.CreationTime`, ` IMustHaveCreator.CreatorId` and `IMayHaveCreator.CreatorId` properties to accidently set the creation properties while updating an existing entity. - -Since the ABP Framework automatically sets these properties, you normally don't need to directly set them. If you want to set them, as a best practice, it is suggested to make it in the constructor to not provide a way to change it later. - -These properties implemented with `protected set` in the `Entity` and `AggregateRoot` base classes. That means you can still set in a derived class, if you need it. Alternatively, you can use reflection to set them (Or use `ObjectHelper.TrySetProperty` which internally uses reflection) out of the class if you have to do. - -## Changed type of the IHasExtraProperties.ExtraProperties - -`IHasExtraProperties.ExtraProperties` was a regular `Dictionary`. With the version 4.0, it is replaced with `ExtraPropertyDictionary` class which inherits the `Dictionary`. - -Most of the applications don't be affected by this change. If you've directly implemented this interface, replace the standard dictionary to the `ExtraPropertyDictionary`. - -## Other Changes - -### IdentityOptions Usage - -Previously, when you inject `IOptions`, you get a dynamically overridden options value. For example, when you get `IdentityOptions.Password.RequiredLength`, the value is being changed based on the setting (`IdentitySettingNames.Password.RequiredLength`) of the current tenant. That means `IdentityOptions` changes per tenant. However, this caused an [issue](https://github.com/abpframework/abp/issues/6318) and we [had to change](https://github.com/abpframework/abp/pull/6333) the usage. - -With the version 4.0, you need to inject `IOptions` and call the new `SetAsync` method before using it, to be able to override the options by the settings. Otherwise, you get the default (statically configured) values of the options. - -Example usage: - -````csharp -public class MyService : ITransientDependency -{ - private readonly IOptions _options; - - public MyService(IOptions options) - { - _options = options; - } - - public async Task DoItAsync() - { - await _options.SetAsync(); - - var requiredLength = _options.Value.Password.RequiredLength; - } -} -```` - -Pre-built modules already handles this. However, if you have used `IdentityOptions` directly in your code, you also need to follow this new pattern. -Please make sure that the injected `IOptions` service and the service consuming it are in the same scope of dependency injection container. - -### LDAP module full async - -In order to solve the problem of async over sync, `ILdapManager` uses async method instead of sync. And use [`ldap4net`](https://github.com/flamencist/ldap4net) to replace [`Novell.Directory.Ldap.NETStandard`](https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard) package. - -### Dynamic external login provider system - -You need to change the `WithDynamicOptions` method and pass the `Handler` class of the external login provider. -Use the `goto definition` function in Visual Studio or Rider to check `Handler` in the extension method like `AddGoogle`. - -```csharp -- WithDynamicOptions() -+ WithDynamicOptions() -```` - -## ASP.NET Core MVC / Razor Pages UI - -See the [ASP.NET Core MVC / Razor Pages UI Migration Guide](Abp-4_0-MVC-Razor-Pages.md). - -## Angular UI - -See the [Angular UI Migration Guide](Abp-4_0-Angular.md). - -## Blazor UI - -See the [Blazor UI Migration Guide](Abp-4_0-Blazor.md). diff --git a/docs/en/Migration-Guides/Abp-4_2.md b/docs/en/Migration-Guides/Abp-4_2.md deleted file mode 100644 index 084ae24caa..0000000000 --- a/docs/en/Migration-Guides/Abp-4_2.md +++ /dev/null @@ -1,101 +0,0 @@ -# ABP version 4.2 Migration Guide - -This version has no breaking changes but there is an important change on the repositories that should be applied for your application for an important performance and scalability gain. - -## IRepository.GetQueryableAsync - -`IRepository` interface inherits `IQueryable`, so you can directly use the standard LINQ extension methods, like `Where`, `OrderBy`, `First`, `Sum`... etc. - -**Example: Using LINQ directly over the repository object** - -````csharp -public class BookAppService : ApplicationService, IBookAppService -{ - private readonly IRepository _bookRepository; - - public BookAppService(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task DoItInOldWayAsync() - { - //Apply any standard LINQ extension method - var query = _bookRepository - .Where(x => x.Price > 10) - .OrderBy(x => x.Name); - - //Execute the query asynchronously - var books = await AsyncExecuter.ToListAsync(query); - } -} -```` - -*See [the documentation](https://docs.abp.io/en/abp/4.2/Repositories#iqueryable-async-operations) if you wonder what is the `AsyncExecuter`.* - -**Beginning from the version 4.2, the recommended way is using `IRepository.GetQueryableAsync()` to obtain an `IQueryable`, then use the LINQ extension methods over it.** - -**Example: Using the new GetQueryableAsync method** - -````csharp -public async Task DoItInNewWayAsync() -{ - //Use GetQueryableAsync to obtain the IQueryable first - var queryable = await _bookRepository.GetQueryableAsync(); - - //Then apply any standard LINQ extension method - var query = queryable - .Where(x => x.Price > 10) - .OrderBy(x => x.Name); - - //Finally, execute the query asynchronously - var books = await AsyncExecuter.ToListAsync(query); -} -```` - -ABP may start a database transaction when you get an `IQueryable` (If current [Unit Of Work](https://docs.abp.io/en/abp/latest/Unit-Of-Work) is transactional). In this new way, it is possible to **start the database transaction in an asynchronous way**. Previously, we could not get the advantage of asynchronous while starting the transactions. - -> **The new way has a significant performance and scalability gain. The old usage (directly using LINQ over the repositories) will be removed in the next major version (5.0).** You have a lot of time for the change, but we recommend to immediately take the action since the old usage has a big **scalability problem**. - -### Actions to Take - -* Use the repository's queryable feature as explained before. -* If you've overridden `CreateFilteredQuery` in a class derived from `CrudAppService`, you should override the `CreateFilteredQueryAsync` instead and remove the `CreateFilteredQuery` in your class. -* If you've overridden `WithDetails` in your custom repositories, remove it and override `WithDetailsAsync` instead. -* If you've used `DbContext` or `DbSet` properties in your custom repositories, use `GetDbContextAsync()` and `GetDbSetAsync()` methods instead of them. - -You can re-build your solution and check the `Obsolete` warnings to find some of the usages need to change. - -#### About IRepository Async Extension Methods - -Using IRepository Async Extension Methods has no such a problem. The examples below are pretty fine: - -````csharp -var countAll = await _personRepository - .CountAsync(); - -var count = await _personRepository - .CountAsync(x => x.Name.StartsWith("A")); - -var book1984 = await _bookRepository - .FirstOrDefaultAsync(x => x.Name == "John"); -```` - -See the [repository documentation](https://docs.abp.io/en/abp/4.2/Repositories#iqueryable-async-operations) to understand the relation between `IQueryable` and asynchronous operations. - -## .NET Package Upgrades - -ABP uses the latest 5.0.* .NET packages. If your application is using 5.0.0 packages, you may get an error on build. We recommend to depend on the .NET packages like `5.0.*` in the `.csproj` files to use the latest patch versions. - -Example: - -````xml - -```` - -## Blazorise Library Upgrade - -If you are upgrading to 4.2, you also need also upgrade the following packages in your Blazor application; - -* `Blazorise.Bootstrap` to `0.9.3-preview6` -* `Blazorise.Icons.FontAwesome` to `0.9.3-preview6` \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-4_3.md b/docs/en/Migration-Guides/Abp-4_3.md deleted file mode 100644 index 22d1282715..0000000000 --- a/docs/en/Migration-Guides/Abp-4_3.md +++ /dev/null @@ -1,43 +0,0 @@ -# ABP Framework 4.x to 4.3 Migration Guide - -This version comes with some changes in the startup template, mostly related to Blazor UI. This document explains the breaking changes. However, **it is suggested to [compare the startup templates manually](Upgrading-Startup-Template.md) to see all the changes** and apply to your solution. - -## Common - -* `app.UseVirtualFiles()` has been marked as **obsolete**. Use `app.UseStaticFiles()` instead. ABP will handle the virtual file system integrated to the static files middleware. - -## Blazor UI - -Implemented the Blazor Server Side support with this release. It required some packages and namespaces arrangements. **Existing Blazor (WebAssembly) applications should done the changes explained in this section**. - -### Namespace Changes - -- `AbpBlazorMessageLocalizerHelper` -> moved to Volo.Abp.AspNetCore.Components.Web -- `AbpRouterOptions` -> moved to Volo.Abp.AspNetCore.Components.Web.Theming.Routing -- `AbpToolbarOptions` and `IToolbarContributor` -> moved to Volo.Abp.AspNetCore.Components.Web.Theming.Toolbars -- `IAbpUtilsService` -> moved to Volo.Abp.AspNetCore.Components.Web -- `PageHeader` -> moved to `Volo.Abp.AspNetCore.Components.Web.Theming.Layout`. - -In practice, if your application is broken because of the `Volo.Abp.AspNetCore.Components.WebAssembly.*` namespace, please try to switch to `Volo.Abp.AspNetCore.Components.Web.*` namespace. - -Remember to change namespaces in the `_Imports.razor` files. - -### Package Changes - -No change on the framework packages, but **module packages are separated as Web Assembly & Server**; - -* Use `Volo.Abp.Identity.Blazor.WebAssembly` NuGet package instead of `Volo.Abp.Identity.Blazor` package. Also, change `AbpIdentityBlazorModule` usage to `AbpIdentityBlazorWebAssemblyModule` in the `[DependsOn]` attribute on your module class. -* Use `Volo.Abp.TenantManagement.Blazor.WebAssembly` NuGet package instead of `Volo.Abp.TenantManagement.Blazor` package. Also, change `AbpTenantManagementBlazorModule` usage to `AbpTenantManagementBlazorWebAssemblyModule` in the `[DependsOn]` attribute on your module class. -* Use `Volo.Abp.PermissionManagement.Blazor.WebAssembly` NuGet package instead of `Volo.Abp.PermissionManagement.Blazor` package. Also, change `AbpPermissionManagementBlazorModule` usage to `AbpPermissionManagementBlazorWebAssemblyModule` in the `[DependsOn]` attribute on your module class. -* Use `Volo.Abp.SettingManagement.Blazor.WebAssembly` NuGet package instead of `Volo.Abp.SettingManagement.Blazor` package. Also, change `AbpSettingManagementBlazorModule` usage to `AbpSettingManagementBlazorWebAssemblyModule` in the `[DependsOn]` attribute on your module class. -* Use `Volo.Abp.FeatureManagement.Blazor.WebAssembly` NuGet package instead of `Volo.Abp.FeatureManagement.Blazor` package. Also, change `AbpFeatureManagementBlazorModule` usage to `AbpFeatureManagementBlazorWebAssemblyModule` in the `[DependsOn]` attribute on your module class. - -### Other Changes - -* `EntityAction.RequiredPermission` has been marked as **obsolete**, because of performance reasons. It is suggested to use the `Visible` property by checking the permission/policy yourself and assigning to a variable. - -### Resource Reference Changes - -Open `BundleContributor.cs` and replace `context.Add("main.css");` to `context.Add("main.css", true);` - -Then run `abp bundle` command in the `blazor` folder to update resource references. \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-5-0-Blazor.md b/docs/en/Migration-Guides/Abp-5-0-Blazor.md deleted file mode 100644 index 09d0f8e682..0000000000 --- a/docs/en/Migration-Guides/Abp-5-0-Blazor.md +++ /dev/null @@ -1,19 +0,0 @@ -# ABP Blazor UI v4.x to v5.0 Migration Guide - -> This document is for the Blazor UI. See also [the main migration guide](Abp-5_0.md). - -## Upgrading to the latest Blazorise - -ABP 5.0 uses the latest version of the [Blazorise](https://blazorise.com/) library. Please upgrade the Blazorise NuGet packages in your solution. - -## Bootstrap 5 - -ABP 5.0 now works with Bootstrap 5. For details, please refer to the official [migration guide](https://getbootstrap.com/docs/5.0/migration/) provided by Bootstrap. - -Replace `Blazorise.Bootstrap` with `Blazorise.Bootstrap5` package. - -Replace `AddBootstrapProviders()` with `AddBootstrap5Providers()`. - -## Update Bundle - -Use `abp bundle` command to update bundles if you are using Blazor WebAssembly. diff --git a/docs/en/Migration-Guides/Abp-5-0-MVC.md b/docs/en/Migration-Guides/Abp-5-0-MVC.md deleted file mode 100644 index f2c863c634..0000000000 --- a/docs/en/Migration-Guides/Abp-5-0-MVC.md +++ /dev/null @@ -1,12 +0,0 @@ -# ABP MVC / Razor Pages UI v4.x to v5.0 Migration Guide - -> This document is for the ABP MVC / Razor Pages UI. See also [the main migration guide](Abp-5_0.md). - -## Use install-libs by default - -Removed the Gulp dependency from the MVC / Razor Pages UI projects in favor of `abp install-libs` command ([see](https://docs.abp.io/en/abp/5.0/UI/AspNetCore/Client-Side-Package-Management#install-libs-command)) of the ABP CLI. You should run this command whenever you change/upgrade your client-side package dependencies via `package.json`. - -## Switched to SweetAlert2 - -Switched from SweetAlert to SweetAlert2. Run the `abp install-libs` command (in the root directory of the web project) after upgrading your solution. See [#9607](https://github.com/abpframework/abp/pull/9607). - diff --git a/docs/en/Migration-Guides/Abp-5_0-Angular.md b/docs/en/Migration-Guides/Abp-5_0-Angular.md deleted file mode 100644 index b7e55dad38..0000000000 --- a/docs/en/Migration-Guides/Abp-5_0-Angular.md +++ /dev/null @@ -1,154 +0,0 @@ -# Angular UI v4.x to v5.0 Migration Guide - -> This document is for the Angular UI. See also [the main migration guide](Abp-5_0.md). - -## Overall - -See the overall list of breaking changes: - -- Bootstrap 5 implementation [#10067](https://github.com/abpframework/abp/issues/10067) -- Remove NGXS dependency & states [#9952](https://github.com/abpframework/abp/issues/9952) -- Install @angular/localize package to startup templates [#10099](https://github.com/abpframework/abp/issues/10099) -- Create new secondary entrypoints and move the related proxies to there [#10060](https://github.com/abpframework/abp/issues/10060) -- Move SettingTabsService to @abp/ng.setting-management/config package from @abp/ng.core [#10061](https://github.com/abpframework/abp/issues/10061) -- Make the @abp/ng.account dependent on @abp/ng.identity [#10059](https://github.com/abpframework/abp/issues/10059) -- Set default abp-modal size medium [#10118](https://github.com/abpframework/abp/issues/10118) -- Update all dependency versions to the latest [#9806](https://github.com/abpframework/abp/issues/9806) -- Chart.js big include with CommonJS warning [#7472](https://github.com/abpframework/abp/issues/7472) - -## Angular v12 - -The new ABP Angular UI is based on Angular v12. We started to compile Angular UI packages with the Ivy compilation. Therefore, **new packages only work with Angular v12**. If you are still on the older version of Angular v12, you have to update to Angular v12. The update is usually very easy. See [Angular Update Guide](https://update.angular.io/?l=2&v=11.0-12.0) for further information. - -> **ABP Angular UI is not yet compatible with Angular v13 due to some issues.** - -## Bootstrap 5 - -ABP 5.0 now works with Bootstrap 5. For details, please refer to the official [migration guide](https://getbootstrap.com/docs/5.0/migration/) provided by Bootstrap. - -We have updated dependencies of the `ThemeShared` package, therefore when you update `@abp/ng.theme.shared`, it will install the necessary dependencies. - -### RTL - -Bootstrap 5 provides its own CSS file for RTL(right-to-left) languages. Therefore, we have removed `bootstrap-rtl.min.css` from `@abp/ng.theme.shared`. - -In angular.json, make the following change: - -Replace - -`"node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css"` with - -`"node_modules/bootstrap/dist/css/bootstrap.rtl.min.css"` - -```js -{ - // ... - "styles": [{ - "input": "node_modules/bootstrap/dist/css/bootstrap.rtl.min.css", - "inject": false, - "bundleName": "bootstrap-rtl.min" - }] -} -``` - -That's it for open source templates. - -### Commercial - -Starting from version 5.0, Lepton styles get bundled with Bootstrap. That's why you don't need to provide `bootstrap` styles in `angular.json` anymore. - -Remove the following two objects from the `styles` array in `angular.json` - -```js -{ - // ... - "styles": [{ - "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", - "inject": false, - "bundleName": "bootstrap-rtl.min" - }, - { - "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", - "inject": true, - "bundleName": "bootstrap-ltr.min" - }] -} -``` - -After you have implemented the necessary changes explained by Bootstrap, it should be good to go. - -## NGXS has been removed - -We aim to make the ABP Framework free of any state-management solutions. ABP developers should be able to use the ABP Framework with any library/framework of their choice. So, we decided to remove NGXS from ABP packages. - -If you'd like to use NGXS after upgrading to v5.0, you have to install the NGXS to your project. The package can be installed with the following command: - -```bash -npm install @ngxs/store - -# or - -yarn add @ngxs/store -``` - -NGXS states and actions, some namespaces have been removed. See [this issue](https://github.com/abpframework/abp/issues/9952) for the details. - -If you don't want to use the NGXS, you should remove all NGXS related imports, injections, etc., from your project. - -## @angular/localize package - -[`@angular/localize`](https://angular.io/api/localize) dependency has been removed from `@abp/ng.core` package. The package must be installed in your app. Run the following command to install: - -```bash -npm install @angular/localize@12 - -# or - -yarn add @angular/localize@12 -``` - -> ABP Angular UI packages are not dependent on the `@angular/localize` package. However, some packages (like `@ng-bootstrap/ng-bootstrap`) depend on the package. Thus, this package needs to be installed in your project. - -## Proxy endpoints - -New endpoints named proxy have been created, related proxies have moved. -For example; before v5.0, `IdentityUserService` could be imported from `@abp/ng.identity`. As of v5.0, the service can be imported from `@abp/ng.identity/proxy`. See an example: - -```ts -import { IdentityUserService } from '@abp/ng.identity/proxy'; - -@Component({}) -export class YourComponent { - constructor(private identityUserService: IdentityUserService) {} -} -``` - -Following proxies have been affected: - -- `@abp/ng.account` to `@abp/ng.account.core/proxy` -- `@abp/ng.feature-management` to `@abp/ng.feature-management/proxy` -- `@abp/ng.identity` to `@abp/ng.identity/proxy` -- `@abp/ng.permission-management` to `@abp/ng.permission-management/proxy` -- `@abp/ng.tenant-management` to `@abp/ng.tenant-management/proxy` -- **ProfileService** is deleted from `@abp/ng.core`. Instead, you can import it from `@abp/ng.identity/proxy` - -## SettingTabsService - -**SettingTabsService** has moved from `@abp/ng.core` to `@abp/ng.setting-management/config`. - -## ChartComponent - -[`ChartComponent`](../UI/Angular/Chart-Component.md) has moved from `@abp/ng.theme.shared` to `@abp/ng.components/chart.js`. To use the component, you need to import the `ChartModule` to your module as follows: - -```ts -import { ChartModule } from '@abp/ng.components/chart.js'; - -@NgModule({ - imports: [ - ChartModule, - // ... - ], - // ... -}) -export class YourFeatureModule {} -``` diff --git a/docs/en/Migration-Guides/Abp-5_0.md b/docs/en/Migration-Guides/Abp-5_0.md deleted file mode 100644 index 758f2b91ff..0000000000 --- a/docs/en/Migration-Guides/Abp-5_0.md +++ /dev/null @@ -1,121 +0,0 @@ -# ABP Framework v4.x to v5.0 Migration Guide - -This document is a guide for upgrading ABP 4.x solutions to ABP 5.0. Please read them all since 5.0 has some important breaking changes. - -## .NET 6.0 - -ABP 5.0 runs on .NET 6.0. So, please upgrade your solution to .NET 6.0 if you want to use ABP 5.0. You can see [Microsoft's migration guide](https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60). - -## Bootstrap 5 - -ABP 5.0 uses the Bootstrap 5 as the fundamental HTML/CSS framework. We've migrated all the UI themes, tag helpers, UI components and the pages of the pre-built application modules. You may need to update your own pages by following the [Bootstrap's migration guide](https://getbootstrap.com/docs/5.0/migration/). - -## Startup Template Changes - -The startup template has changed. You don't need to apply all the changes, but it is strongly suggested to follow [this guide](Upgrading-Startup-Template.md) and make the necessary changes for your solution. - -## ABP Framework - -This section contains breaking changes in the ABP Framework. - -### MongoDB - -ABP Framework will serialize the datetime based on [AbpClockOptions](https://docs.abp.io/en/abp/latest/Timing#clock-options) starting from ABP v5.0. It was saving `DateTime` values as UTC in MongoDB. Check out [MongoDB Datetime Serialization Options](https://mongodb.github.io/mongo-csharp-driver/2.13/reference/bson/mapping/#datetime-serialization-options). - -If you want to revert back this feature, set `UseAbpClockHandleDateTime` to `false` in `AbpMongoDbOptions`: - -```cs -Configure(x => x.UseAbpClockHandleDateTime = false); -``` - -### Publishing Auto-Events in the Same Unit of Work - -Local and distributed auto-events are handled in the same unit of work now. That means the event handles are executed in the same database transaction and they can rollback the transaction if they throw any exception. The new behavior may affect your previous assumptions. See [#9896](https://github.com/abpframework/abp/issues/9896) for more. - -#### Deprecated EntityCreatingEventData, EntityUpdatingEventData, EntityDeletingEventData and EntityChangingEventData - -As a side effect of the previous change, `EntityCreatingEventData`, `EntityUpdatingEventData`, `EntityDeletingEventData` and `EntityChangingEventData` is not necessary now, because `EntityCreatedEventData`, `EntityUpdatedEventData`, `EntityDeletedEventData` and `EntityChangedEventData` is already taken into the current unit of work. Please switch to `EntityCreatedEventData`, `EntityUpdatedEventData`, `EntityDeletedEventData` and `EntityChangedEventData` if you've used the deprecated events. See [#9897](https://github.com/abpframework/abp/issues/9897) to learn more. - -### Removed ModelBuilderConfigurationOptions classes - -If you've used these classes, please remove their usages and use the static properties to customize the module's database mappings. See [#8887](https://github.com/abpframework/abp/issues/8887) for more. - -### Removed Obsolete APIs - -* `IRepository` doesn't inherit from `IQueryable` anymore. It was [made obsolete in 4.2](https://docs.abp.io/en/abp/latest/Migration-Guides/Abp-4_2#irepository-getqueryableasync). - -### Automatically Setting the TenantId for New Entities - -Beginning from the version 5.0, ABP automatically sets the `TenantId` for you when you create a new entity object (that implements the `IMultiTenant` interface). It is done in the constructor of the base `Entity` class (all other base entity and aggregate root classes are derived from the `Entity` class). The `TenantId` is set from the current value of the `ICurrentTenant.Id` property. - -This can be a breaking change in rare cases (for example, if you create host side entities from a tenant context and do not explicitly set host entity's `TenantId` to `null`). - -### Other Breaking Changes - -* [#9549](https://github.com/abpframework/abp/pull/9549) `IObjectValidator` methods have been changed to asynchronous. -* [#9940](https://github.com/abpframework/abp/pull/9940) Use ASP NET Core's authentication scheme to handle `AbpAuthorizationException`. -* [#9180](https://github.com/abpframework/abp/pull/9180) Use `IRemoteContentStream` without form content headers. - -## UI Providers - -* [Angular UI 4.x to 5.0 Migration Guide](Abp-5_0-Angular.md) -* [ASP.NET Core MVC / Razor Pages UI 4.x to 5.0 Migration Guide](Abp-5-0-MVC.md) -* [Blazor UI 4.x to 5.0 Migration Guide](Abp-5-0-Blazor.md) - -## Modules - -This section contains breaking and important changes in the application modules. - -### Identity - -#### User Active/Passive - -An `IsActive` (`bool`) property is added to the `IdentityUser` entity. This flag will be checked during the authentication of the users. EF Core developers need to add a new database migration and update their databases. - -**After the database migration, set this property to `true` for the existing users: `UPDATE AbpUsers SET IsActive=1`**. Otherwise, none of the users can login to the application. - -Alternatively, you can set `defaultValue` to `true` in the migration class (after adding the migration). -This will add the column with `true` value for the existing records. - -```cs -public partial class AddIsActiveToIdentityUser : Migration -{ - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "IsActive", - table: "AbpUsers", - type: "bit", - nullable: false, - defaultValue: true); // Default is false, change it to true. - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "IsActive", - table: "AbpUsers"); - } -} -``` - -For MongoDB, you need to update the `IsActive` field for the existing users in the database. - -You can use following script in MongoShell: -```js -db.AbpUsers.updateMany({},{$set:{ IsActive : true }}) -``` - -#### Identity -> Account API Changes - -`IProfileAppService` (and the implementation and the related DTOs) are moved to the Account module from the Identity module (done with [this PR](https://github.com/abpframework/abp/pull/10370/files)). - -### IdentityServer - -`IApiScopeRepository.GetByNameAsync` method renamed as `FindByNameAsync`. - -## See Also - -* [Angular UI 4.x to 5.0 Migration Guide](Abp-5_0-Angular.md) -* [ASP.NET Core MVC / Razor Pages UI 4.x to 5.0 Migration Guide](Abp-5-0-MVC.md) -* [Blazor UI 4.x to 5.0 Migration Guide](Abp-5-0-Blazor.md) diff --git a/docs/en/Migration-Guides/Abp-5_2.md b/docs/en/Migration-Guides/Abp-5_2.md deleted file mode 100644 index aeb79e22a6..0000000000 --- a/docs/en/Migration-Guides/Abp-5_2.md +++ /dev/null @@ -1,52 +0,0 @@ -# ABP Version 5.2 Migration Guide - -This document is a guide for upgrading ABP v5.x solutions to ABP v5.2. Please read them all since v5.2 has some changes you should take care. - - -## MongoDB - -- `IMongoDbRepositoryFilterer.AddGlobalFilters()` method is replaced with async one `IMongoDbRepositoryFilterer.AddGlobalFiltersAsync()` - -## Blazor UI -If you use Blazor WASM or Blazor Server UI, you should follow this section. - -### Blazorise 1.0 -We've upgraded to Blazorise 1.0 stable version. So there is some breaking changes that you have to apply in your project. - -Also You can review that pull request [#11649 - Blazorise 1.0 Migration](https://github.com/abpframework/abp/pull/11649) - -- `NumericEdit` is now made around the native `input type="number"` so a lot of its formatting features are moved to the new `NumericPicker` component. Replace NumericEdit with NumericPicker. -- Rename `DecimalsSeparator` to `DecimalSeparator` on the `DataGridColumn` and `NumericPicker`. -- Rename `MaxMessageSize` to `MaxChunkSize`. -- Remove `Fullscreen` parameter on `` and replace it with `Size="ModalSize.Fullscreen"` parameter. -- Remove `NotificationType`, `Message`, and `Title` parameter from `` component. -- Move `RightAligned` parameter from `` to `` component. -- Rename any usage of the `ChangeTextOnKeyPress` parameter into `Immediate`. -- Rename any usage of `DelayTextOnKeyPress` parameter into `Debounce` and `DelayTextOnKeyPressInterval` into DebounceInterval. -- Replace all `Left` and `Right` enums with `Start` and `End` for the following enum types: `Direction`, `Float`, `Placement`, `NotificationLocation`, `Side`, `SnackbarLocation`, `SnackbarStackLocation`, `TabPosition`, and `TextAlignment`. -- Replace all `FromLeft`, `FromRight`, `RoundedLeft`, and `RoundedRight` enums with `FromStart`, `FromEnd`, `RoundedStart`, and `RoundedEnd` for the `Border` utilities. -- Replace all `FromLeft` and `FromRight` with `FromStart`, `FromEnd` for the Margin and `Padding` utilities. -- Replace all `AddLabel` with `AddLabels` method on chart instance. -- Change enum value from `None` to `Default` for the following enum types: `Color`, `Background`, `TextColor`, `Alignment`, `BorderRadius`, `BorderSize`, `Direction`, `DisplayDirection`, `FigureSize`, `IconSize`, `JustifyContent`, `OverflowType`, `SnackbarColor`, `Target`, `TextAlignment`, `TextOverflow`, `TextTransform`, `TextWeight`, `VerticalAlignment`, `Visibility`, `Size`, and `SnackbarLocation`. -- Obsolete typography parameters `Alignment`, `Color`, `Transform`, and `Weight` are removed in favor of `TextAlignment`, `TextColor`, `TextTransform`, and `TextWeight`. -- Remove any use of an obsolete component ``. -- The Datagrid's obsolete `Direction` parameter has now been removed. Instead, please use the `SortDirection` parameter if you weren't already.. -- Rename `` `Mode` parameter into `RenderMode`. - -> _Check out [Blazorise Release Notes](https://preview.blazorise.com/news/release-notes/100) for more information._ - -## MVC - Razor Pages UI - -If you use MVC Razor Pages UI, you should follow this section. - -### Client libraries -The `libs` folder no longer exists in templates after v5.2. That change greatly reduced the size of templates and brought some other advantages. - -You can use `abp install-libs` command for installing or updating client libraries. You should run this command after updating v5.2. - -> If you're creating a new project, you don't have to be concerned about it, ABP CLI installs client libraries after automatically. - -## See Also - -* [Official blog post for the 5.2 release](https://blog.abp.io/abp/ABP.IO-Platform-5-2-RC-Has-Been-Published) - diff --git a/docs/en/Migration-Guides/Abp-5_3.md b/docs/en/Migration-Guides/Abp-5_3.md deleted file mode 100644 index acc69504f0..0000000000 --- a/docs/en/Migration-Guides/Abp-5_3.md +++ /dev/null @@ -1,11 +0,0 @@ -# ABP Version 5.3 Migration Guide - -This document is a guide for upgrading ABP v5.2 solutions to ABP v5.3. There is a change in this version that may effect your applications, please read it carefully and apply the necessary changes to your application. - -## AutoMapper Upgraded to v11.0.1 - -AutoMapper library upgraded to **v11.0.1** in this version. So, you need to change your project's target SDK that use the **AutoMapper** library (typically your `*.Application` project). You can change it from `netstandard2.0` to `netstandard2.1` or `net6` if needed. Please see [#12189](https://github.com/abpframework/abp/pull/12189) for more info. - -## See Also - -* [Official blog post for the 5.3 release](https://blog.abp.io/abp/ABP.IO-Platform-5.3-RC-Has-Been-Published) diff --git a/docs/en/Migration-Guides/Abp-6_0.md b/docs/en/Migration-Guides/Abp-6_0.md deleted file mode 100644 index 21eb6023b8..0000000000 --- a/docs/en/Migration-Guides/Abp-6_0.md +++ /dev/null @@ -1,29 +0,0 @@ -# ABP Version 6.0 Migration Guide - -This document is a guide for upgrading ABP v5.3 solutions to ABP v6.0. There is a change in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -## The IsActive property is Added - -`IsActive` property is added to `IUserData`. This property is set to **true** by default. **Cmskit** and **Blog** modules are affected by this change. You need to add a new migration to your existing application if you are using any of these modules. Please see [#11417](https://github.com/abpframework/abp/pull/11417) for more info. - -## Default behavior change in MultiTenancyMiddlewareErrorPageBuilder - -If you have customized the `MultiTenancyMiddlewareErrorPageBuilder` of the `AbpMultiTenancyOptions`, the pipeline now returns **true** to stop the pipeline as the default behavior. See [AbpMultiTenancyOptions: Handle inactive and non-existent tenants](https://github.com/abpframework/abp/blob/dev/docs/en/Multi-Tenancy.md#abpmultitenancyoptions-handle-inactive-and-non-existent-tenants) for more info. - -## Migrating to LeptonX Lite - -LeptonX Lite is now being introduced and you can follow the guides below to migrate your existing applications: - -- [Migrating to LeptonX MVC UI](../Themes/LeptonXLite/AspNetCore.md) -- [Migrating to LeptonX Angular UI](../Themes/LeptonXLite/Angular.md) -- [Migrating to LeptonX Blazor UI](../Themes/LeptonXLite/Blazor.md) - -## Migrating to OpenIddict - -After the [announcement of plan to replace the IdentityServer](https://github.com/abpframework/abp/issues/11989), we have successfully implemented [Openiddict](https://github.com/openiddict/openiddict-core) as a replacement for IdentityServer4 as an OpenID-Provider. - -You can follow the [IdentityServer to OpenIddict Step by Step Guide](OpenIddict-Step-by-Step.md) for migrating your existing application in detail with a sample project. - -## See Also - -* [Official blog post for the 6.0 release](https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published) diff --git a/docs/en/Migration-Guides/Abp-7_0.md b/docs/en/Migration-Guides/Abp-7_0.md deleted file mode 100644 index 65a1614603..0000000000 --- a/docs/en/Migration-Guides/Abp-7_0.md +++ /dev/null @@ -1,213 +0,0 @@ -# ABP Version 7.0 Migration Guide - -This document is a guide for upgrading ABP v6.x solutions to ABP v7.0. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -> ABP Framework upgraded to .NET 7.0, so you need to move your solutions to .NET 7.0 if you want to use the ABP 7.0. You can check the [Migrate from ASP.NET Core 6.0 to 7.0](https://learn.microsoft.com/en-us/aspnet/core/migration/60-70?view=aspnetcore-7.0) documentation. - -## `FormTenantResolveContributor` Removed from the `AbpTenantResolveOptions` - -`FormTenantResolveContributor` has been removed from the `AbpTenantResolveOptions`. Thus, if you need to get tenant info from `HTTP Request From`, please add a custom `TenantResolveContributor` to implement it. - -## `IHybridServiceScopeFactory` Removed - -`IHybridServiceScopeFactory` has been removed. Please use the `IServiceScopeFactory` instead. - -## Hybrid JSON was removed. - -Since [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) library supports more custom features in NET 7, ABP no longer need the hybrid Json feature. - -### Previous Behavior - -There is a `Volo.Abp.Json` package which contains the `AbpJsonModule` module. -`Serialization/deserialization` features of [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) and [Newtonsoft](https://www.newtonsoft.com/json/help/html/SerializingJSON.htm) are implemented in this module. - -We use [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) first, More custom cases can be handled with [Newtonsoft](https://www.newtonsoft.com/json/help/html/SerializingJSON.htm) by configuring `UnsupportedTypes` of `AbpSystemTextJsonSerializerOptions`. - -### New Behavior - -We created `Volo.Abp.Json.SystemTextJson` and `Volo.Abp.Json.Newtonsoft` as separate packages, which means you can only use one of them in your project. The default is to use `SystemTextJson`. If you want `Newtonsoft`, please also use `Volo.Abp.AspNetCore.Mvc.NewtonsoftJson` in your web project. - -* Volo.Abp.Json.Abstractions -* Volo.Abp.Json.Newtonsoft -* Volo.Abp.Json.SystemTextJson -* Volo.Abp.Json (Depends on `Volo.Abp.Json.SystemTextJson` by default to prevent breaking) -* Volo.Abp.AspNetCore.Mvc.NewtonsoftJson - -The `AbpJsonOptions` now has only two properties, which are - -* `InputDateTimeFormats(List)`: Formats of input JSON date, Empty string means default format. You can provide multiple formats to parse the date. -* `OutputDateTimeFormat(string)`: Format of output json date, Null or empty string means default format. - -Please remove all `UnsupportedTypes` add custom `Modifiers` to control serialization/deserialization behavior. - -Check the docs to see the more info: https://github.com/abpframework/abp/blob/dev/docs/en/JSON.md#configuration - -Check the docs to see how to customize a JSON contract: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/custom-contracts - -## "Manage Host Features" Moved to the Settings Page - -"Manage Host Features" button has been moved from Tenants page to Settings page. - -See https://github.com/abpframework/abp/pull/13359 for more info. - -## Removed the `setter` from the Auditing Interfaces - -`AuditedEntity` and other base entity classes will continue to have public setters. If you want to make them private, don't derive from these base classes, but implement the interfaces yourself. - -See https://github.com/abpframework/abp/issues/12229#issuecomment-1191384798 for more info. - -## Added Abp prefix to DbProperties Classes - -Please update the database migration and related connection string names. - -## `EntityCreatingEventData`, `EntityUpdatingEventData`, `EntityDeletingEventData` and `EntityChangingEventData` has been removed. - -They are deprecated don't use them anymore. - -## LayoutHookInfo.cs, LayoutHookViewModel.cs, LayoutHooks.cs, AbpLayoutHookOptions.cs classes have been moved under the Volo.Abp.Ui.LayoutHooks namespace. - -See https://github.com/abpframework/abp/pull/13903 for more info. - -## Removed `abp.auth.policies` - -`abp.auth.polices` has been removed, use `abp.auth.grantedPolicies` instead. - -## Static C# Proxy Generation - -The `abp generate-proxy -t csharp ..` command will generate all the `classes/enums/other types` in the client side (including application service interfaces) behalf of you. - -If you have reference to the target contracts package, then you can pass a parameter `--without-contracts (shortcut: -c)`. - -See https://github.com/abpframework/abp/issues/13613#issue-1333088953 for more info. - -## Dynamic Permissions - -* `IPermissionDefinitionManager` methods are converted to asynchronous, and renamed (added Async postfix). -* Removed `MultiTenancySides` from permission groups. -* Inherit `MultiTenancySides` enum from byte (default was int). -* Needs to add migration for new entities in the Permission Management module. - -See https://github.com/abpframework/abp/pull/13644 for more info. - -## External Localization Infrastructure - -* Introduced `LocalizationResourceBase` that is base for localization resources. `LocalizationResource` inherits from it for typed (static) localization resources (like before). Also introduced `NonTypedLocalizationResource` that inherits from `LocalizationResourceBase` for dynamic/external localization resources. We are using `LocalizationResourceBase` for most of the places where we were using `LocalizationResource` before and that can be a breaking change for some applications. -* All layouts in all MVC UI themes should add this line just before the **ApplicationConfigurationString** line: - -```html - -``` - -We've already done this for our themes. - -See https://github.com/abpframework/abp/pull/13845 for more info. - -## Replaced `BlogPostPublicDto` with `BlogPostCommonDto` - -- In the CMS Kit Module, `BlogPostPublicDto` has been moved to `Volo.CmsKit.Common.Application.Contracts` from `Volo.CmsKit.Public.Application.Contracts` and renamed to `BlogPostCommonDto`. - -- See the [PR#13499](https://github.com/abpframework/abp/pull/13499) for more information. - -> You can ignore this if you don't use CMS Kit Module. - -## Data migration environment - -Please call `AddDataMigrationEnvironment` method in the migration project. - -```cs -using (var application = await AbpApplicationFactory.CreateAsync(options => -{ - //... - options.AddDataMigrationEnvironment(); -})) -{ - //... -} -``` - -```cs -var builder = WebApplication.CreateBuilder(args); -builder.Services.AddDataMigrationEnvironment(); -// Call AddDataMigrationEnvironment before AddApplicationAsync -await builder.AddApplicationAsync(); -//... -``` - -See https://github.com/abpframework/abp/pull/13985 for more info. - -## Devart.Data.Oracle.EFCore - -The `Devart.Data.Oracle.EFCore` package do not yet support EF Core 7.0, If you use `AbpEntityFrameworkCoreOracleDevartModule(Volo.Abp.EntityFrameworkCore.Oracle.Devart)` may not work as expected, We will release new packages as soon as they are updated. - -See https://github.com/abpframework/abp/issues/14412 for more info. -# Changes on Angular Apps -## Added a new package `@abp/ng.oauth` -OAuth Functionality moved to a seperate package named `@abp/ng.oauth`, so ABP users should add the `@abp/ng.oauth` packages on app.module.ts. -Add the new npm package to your app. -``` -yarn add @abp/ng.oauth -// or npm i ---save @abp/ng.oauth -``` - -```typescript -// app.module.ts -import { AbpOAuthModule } from "@abp/ng.oauth"; -// ... -@NgModule({ - // ... - imports: [ - AbpOAuthModule.forRoot(), // <-- Add This - // ... - ], - // ... -}) -export class AppModule {} - -``` -## Lepton X Google-Font -If you are using LeptonX that has google fonts, the fonts were built-in the Lepton file. It's been moved to a seperate file. So the ABP user should add font-bundle in angular.json. ( under the 'yourProjectName' > 'architect' > 'build' > 'options' >'styles' ) - -// for LeptonX Lite -```json - { - input: 'node_modules/@volo/ngx-lepton-x.lite/assets/css/font-bundle.rtl.css', - inject: false, - bundleName: 'font-bundle.rtl', - }, - { - input: 'node_modules/@volo/ngx-lepton-x.lite/assets/css/font-bundle.css', - inject: false, - bundleName: 'font-bundle', - }, -``` - -// for LeptonX -```json - { - input: 'node_modules/@volosoft/ngx-lepton-x/assets/css/font-bundle.css', - inject: false, - bundleName: 'font-bundle', - }, - { - input: 'node_modules/@volosoft/ngx-lepton-x/assets/css/font-bundle.rtl.css', - inject: false, - bundleName: 'font-bundle.rtl', - }, -``` - -## Updated Side Menu Layout - -In side menu layout, eThemeLeptonXComponents.Navbar has been changed to eThemeLeptonXComponents.Toolbar, and -eThemeLeptonXComponents.Sidebar to eThemeLeptonXComponents.Navbar. - -And also added new replaceable component like Logo Component, Language Component etc. - -If you are using replaceable component system you can check [documentation](https://docs.abp.io/en/commercial/latest/themes/lepton-x/angular#customization). - - -## ng-zorro-antd-tree.css - -ng-zorro-antd-tree.css file should be in angular.json if the user uses AbpTree component or Abp-commercial. The ABP User should add this style definition on angular.json. ( under the 'yourProjectName' > 'architect' > 'build' > 'options' >'styles' ) - -{ "input": "node_modules/ng-zorro-antd/tree/style/index.min.css", "inject": false, "bundleName": "ng-zorro-antd-tree" }, - diff --git a/docs/en/Migration-Guides/Abp-7_1.md b/docs/en/Migration-Guides/Abp-7_1.md deleted file mode 100644 index 711f520800..0000000000 --- a/docs/en/Migration-Guides/Abp-7_1.md +++ /dev/null @@ -1,21 +0,0 @@ -# ABP Version 7.1 Migration Guide - -This document is a guide for upgrading ABP v7.0 solutions to ABP v7.1. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -> **Note**: Entity Framework developers may need to add a new code-first database migration to their projects since we made some improvements to the existing entities of some application modules. - -## Navigation Menu - `CustomData` type changed to `Dictionary` - -`ApplicationMenu` and `ApplicationMenuItem` classes' `CustomData` property type has been changed to `Dictionary`. So, if you use the optional `CustomData` property of these classes, change it accordingly. See [#15608](https://github.com/abpframework/abp/pull/15608) for more information. - -*Old usage:* - -```csharp -var menu = new ApplicationMenu("Home", L["Home"], "/", customData: new MyCustomData()); -``` - -*New usage:* - -```csharp -var menu = new ApplicationMenu("Home", L["Home"], "/").WithCustomData("CustomDataKey", new MyCustomData()); -``` diff --git a/docs/en/Migration-Guides/Abp-7_2.md b/docs/en/Migration-Guides/Abp-7_2.md deleted file mode 100644 index 5c99e8e604..0000000000 --- a/docs/en/Migration-Guides/Abp-7_2.md +++ /dev/null @@ -1,13 +0,0 @@ -# ABP Version 7.2 Migration Guide - -This document is a guide for upgrading ABP v7.1 solutions to ABP v7.2. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -## `LastPasswordChangeTime` and `ShouldChangePasswordOnNextLogin` Properties Added to the `IdentityUser` Class - -In this version, two new properties, which are `LastPasswordChangeTime` and `ShouldChangePasswordOnNextLogin` have been added to the `IdentityUser` class and to the corresponding entity. Therefore, you may need to create a new migration and apply it to your database. - -## Renamed `OnRegistered` Method - -There was a typo in an extension method, named as `OnRegistred`. In this version, we have fixed the typo and renamed the method as `OnRegistered`. Also, we have updated the related places in our modules that use this method. - -However, if you have used this method in your projects, you need to rename it as `OnRegistered` in your code. \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-7_3.md b/docs/en/Migration-Guides/Abp-7_3.md deleted file mode 100644 index 1d8e5b1e36..0000000000 --- a/docs/en/Migration-Guides/Abp-7_3.md +++ /dev/null @@ -1,31 +0,0 @@ -# ABP Version 7.3 Migration Guide - -This document is a guide for upgrading ABP v7.2 solutions to ABP v7.3. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -## OpenIddict - Refactoring of `ClaimsPrincipal` - -There are some changes that you might need to fix in your code. You can see the following list of the required changes: - -* `AbpOpenIddictClaimDestinationsManager` was renamed as `AbpOpenIddictClaimsPrincipalManager`. -* Use `AbpOpenIddictClaimsPrincipalManager.HandleAsync` instead of `AbpOpenIddictClaimDestinationsManager.SetAsync`, which is removed. -* `AbpDefaultOpenIddictClaimDestinationsProvider` was renamed as `AbpDefaultOpenIddictClaimsPrincipalHandler`. -* `IAbpOpenIddictClaimDestinationsProvider` was renamed as `IAbpOpenIddictClaimsPrincipalHandler`. -* Use `IAbpOpenIddictClaimsPrincipalHandler.HandleAsync` instead of `IAbpOpenIddictClaimDestinationsProvider.SetAsync`, which is removed. -* `AbpOpenIddictClaimDestinationsOptions` was renamed as `AbpOpenIddictClaimsPrincipalOptions`. - -Please check [this PR](https://github.com/abpframework/abp/pull/16537) if you encounter any problems related to OpenIddict Module. - -## Nonce attribute support for Content Security Policy (CSP) - -ABP Framework supports adding unique value to nonce attribute for script tags which can be used by Content Security Policy to determine whether or not a given fetch will be allowed to proceed for a given element. In other words, it provides a mechanism to execute only correct script tags with the correct nonce value. - -> See the [Security Headers](../UI/AspNetCore/Security-Headers.md) documentation for more information. - -This feature comes with a small restriction. If you use any C# code used inside the script tag, it may cause errors (Because a new `NonceScriptTagHelper` has been added, and it replaces script tags in the HTML contents). - -For example, `` will no longer work. However, you can use the C# code for an attribute of script tag, for example, `` is completely valid and won't cause any problem. - -> Note: You should not use any C# code used inside the script tag, even if you don't use this feature. Because it might cause errors. - -## Angular UI -We would like to inform you that ABP Framework version 7.3 uses Angular version 16. Please migrate your applications to Angular 16. [Update angular](https://update.angular.io/) \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-7_4.md b/docs/en/Migration-Guides/Abp-7_4.md deleted file mode 100644 index ee72da812f..0000000000 --- a/docs/en/Migration-Guides/Abp-7_4.md +++ /dev/null @@ -1,119 +0,0 @@ -# ABP Version 7.4 Migration Guide - -This document is a guide for upgrading ABP v7.3 solutions to ABP v7.4. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -## Bumped the `Microsoft.Extensions.FileProviders.Embedded` Package Version To v7.0.10 - -In this version, the `Microsoft.Extensions.FileProviders.Embedded` (and other `Microsoft.*` packages) upgraded to the latest version, which is v7.0.10. Therefore, in your solution, you should update the `Microsoft.Extensions.FileProviders.Embedded` package (and other `Microsoft.*` packages) version to v7.0.10. This package typically would be in your `Domain.Shared` project and other projects that have embedded resource(s). So, search this package through your solution and update it accordingly. - -> You can check [this issue](https://github.com/abpframework/abp/pull/17516) to see the updated package versions. - -## Renamed the `AddGlobalFilters<>` method as `FilterQueryable<>` in `IMongoDbRepositoryFilterer` - -ABP Framework provides services to automatically filter data on querying from a database. Prior to this version, creating a new class that derives from the `MongoDbRepositoryFilterer` and overriding its `AddGlobalFilters` method was needed for implementing a data filter for [MongoDB](../MongoDB.md). - -In this version, the `AddGlobalFilters<>` method is renamed as `FilterQueryable<>`. Therefore, you need to update the method name if you have used data filtering for MongoDB, in your application. - -## Exposing Integration Services - -[Integration Services](../Integration-Services.md) are now not being exposed by default. In a monolith application, integration services don't need to be exposed outside since the modules would probably be in-process communication with each other. Therefore, they don't need to be exposed for most of the time. - -If you build a microservice solution or you need to access an integration service via a network call from any other application, you will probably need to expose the integration services so the other applications can consume them. - -To expose integration services and controllers, you can configure the `AbpAspNetCoreMvcOptions` and set the `ExposeIntegrationServices` property as *true* in the `ConfigureServices` method of your [module class](../Module-Development-Basics.md): - -```csharp -Configure(options => -{ - options.ExposeIntegrationServices = true; -}); -``` - -## `LocalizationResource` property removed from the `TemplateDefinition` class - -In this version, the `LocalizationResource` property was removed from the `TemplateDefinition` class and instead, the `LocalizationResourceName` property has been added. - -```diff -- public Type LocalizationResource { get; set; } -+ public string LocalizationResourceName { get; set; } -``` - -## Changed the method signature for `ICorrelationIdProvider.Get()` - -Prior to this version, the `ICorrelationIdProvider.Get()` method used to return a non nullable string that represented a *correlationId* (a unique key that is used in distributed applications to trace requests across multiple services/operations). In this version, this method may return `null` if it hasn't been generated by `AbpCorrelationIdMiddleware` before. - -```diff -public interface ICorrelationIdProvider -{ - -- [NotNull] string Get(); -+ string? Get(); - - //other methods - -} -``` - -Therefore, if you've used this method in your application, you might want to make a null check and update the method signature where it's used. - -> See [#16795](https://github.com/abpframework/abp/pull/16795) for more information. - -## Dynamic Setting Store - Setting Management Module - -In this version, ABP Framework introduces Dynamic Setting Store, which is an important feature that allows us to collect and get all setting definitions from a single point. This feature requires some actions that need to be taken care of as the following: - -* You need to create a new migration and apply it to your database because a new database table has been added. -* `ISettingDefinitionManager`'s sync methods have been removed and instead, asynchronous versions of the existing methods have been added. - -```diff -public interface ISettingDefinitionManager -{ -- SettingDefinition Get([NotNull] string name); -+ Task GetAsync([NotNull] string name); - -- IReadOnlyList GetAll(); -+ Task> GetAllAsync(); - -- SettingDefinition? GetOrNull(string name); -+ Task GetOrNullAsync([NotNull] string name); -} -``` - -## `IdentityUserIntegrationService` - Identity Module - -In this version, ABP Framework introduces the `IdentityUserIntegrationService`, which is designed to get the current user's information, such as his/her role names within a non-authorized integration service. - -> For more information, see the related PR: [#16962](https://github.com/abpframework/abp/pull/16962) - -This is a breaking change for microservice solutions because of the following two reasons and it should be considered: - -* `IdentityUserIntegrationService` provides non-authorized services. This is not breaking the application but should be taken care of. Since, everyone can use the service to retrieve some information for a certain user (for example, the role names of a user). -* Secondly, since integration services are not exposed by default anymore as explained in the *Exposing Integration Services* section above, you should explicitly enable exposing integration services. Otherwise, the operation will fail and you'll get a `404` error from the identity microservice. - -To expose integration services and controllers, you can configure the `AbpAspNetCoreMvcOptions` and set the `ExposeIntegrationServices` property as *true* in the `ConfigureServices` method of your [module class](../Module-Development-Basics.md): - -```csharp -Configure(options => -{ - options.ExposeIntegrationServices = true; -}); -``` - -## Blazor UI -If you use Blazor WASM or Blazor Server UI, you should follow this section. - -### Bumped the `Blazorise` dependencies to `1.3.1` -In this version, the `Blazorise` dependencies are upgraded to the `1.3.1` version. you should upgrade Blazorise packages to `1.3.1` in your `Blazor.csproj` file. -The following packages are included in the templates by default: -- `Blazorise.Bootstrap5` -- `Blazorise.Icons.FontAwesome` -- `Blazorise.Components` -> _If your project depends on more blazorise packages, then you should upgrade all of them._ -> You should execute `dotnet build` & `abp bundle` commands in the Blazor project if you are using the Blazor WebAssembly. - -### Bumped the `Microsoft.AspNetCore.Components.*` dependency to `7.0.10` - -In this version, the `Microsoft.AspNetCore.Components.*` dependencies are upgraded to the `7.0.10` version. Therefore, you should upgrade the `Microsoft.AspNetCore.Components.Web` and `Microsoft.AspNetCore.Components.WebAssembly` packages to `7.0.10` in your `Blazor.csproj` file. - -## Angular UI -We would like to inform you that ABP Framework version 7.4 uses Angular version 16. Please migrate your applications to Angular 16. [Update angular](https://update.angular.io/) \ No newline at end of file diff --git a/docs/en/Migration-Guides/Abp-8-2-Blazor-Web-App.md b/docs/en/Migration-Guides/Abp-8-2-Blazor-Web-App.md deleted file mode 100644 index 239bbf70c8..0000000000 --- a/docs/en/Migration-Guides/Abp-8-2-Blazor-Web-App.md +++ /dev/null @@ -1,223 +0,0 @@ -# Migrating to Blazor Web App - -ASP.NET Blazor in .NET 8 allows you to use a single powerful component model to handle all of your web UI needs, including server-side rendering, client-side rendering, streaming rendering, progressive enhancement, and much more! - -ABP v8.2.x supports the new Blazor Web App template, in this guide, we will introduce some new changes and features in the new Blazor Web App template. - -## Create a new Blazor Web App - -> Please make sure you have installed the 8.2.x version of the ABP CLI. - -You can create a new Blazor Web App using the `abp new BookStore -t app -u blazor-webapp` command. The `-u blazor-webapp` option is used to select the Blazor Web App template. - -Of course, you can also create Blazor WASM and Blazor Server applications. We have changed them to use the new Blazor Web App mode: - -````csharp -abp new BookStore -t app -u blazor -abp new BookStore -t app -u blazor-server -```` - -## Render modes - -The template project use different render modes for different types of projects in the `App.razor` component. - -| Type | Render mode -|----------------|------------------ -| WASM | InteractiveWebAssembly(prerender: false) -| Server | InteractiveServer -| WebApp | InteractiveAuto - -## The key changes of the new Blazor Web App template - -The new Web App template has two projects, each containing a system of [ABP modules](https://docs.abp.io/en/abp/latest/Modules/Index). - -- MyCompanyName.MyProjectName.Blazor.WebApp -- MyCompanyName.MyProjectName.Blazor.WebApp.Client - -### MyCompanyName.MyProjectName.Blazor.WebApp - -The `Blazor.WebApp` is the startup project, and there is an `App.razor` component in the `Blazor.WebApp` project, which is the root component of the Blazor application. - -The main differences between it, and a regular Blazor server project are: - -1. You need to `PreConfigure` the `IsBlazorWebApp` to `true` in `AbpAspNetCoreComponentsWebOptions`: - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.IsBlazorWebApp = true; - }); -} -```` - -2. Add related services to the container. Add assembly of `MyProjectNameBlazorClientModule` to the `AdditionalAssemblies` by configuring `AbpRouterOptions`: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - // Add services to the container. - context.Services.AddRazorComponents() - .AddInteractiveServerComponents() - .AddInteractiveWebAssemblyComponents(); - - Configure(options => - { - options.AppAssembly = typeof(MyProjectNameBlazorModule).Assembly; - options.AdditionalAssemblies.Add(typeof(MyProjectNameBlazorClientModule).Assembly); - }); -} -```` - -3. Add `UseAntiforgery` middleware and `MapRazorComponents/AddInteractiveServer/WebAssemblyRenderMode/AddAdditionalAssemblies` in the `OnApplicationInitialization` method. - -````csharp -public override void OnApplicationInitialization(ApplicationInitializationContext context) -{ - var env = context.GetEnvironment(); - var app = context.GetApplicationBuilder(); - // ... - - app.UseAntiforgery(); - app.UseAuthorization(); - - app.UseConfiguredEndpoints(builder => - { - builder.MapRazorComponents() - .AddInteractiveServerRenderMode() - .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(builder.ServiceProvider.GetRequiredService>().Value.AdditionalAssemblies.ToArray()); - }); -} -```` - -### MyCompanyName.MyProjectName.Blazor.WebApp.Client - -There is a `Routers.razor` component in the `Blazor.WebApp.Client` project, which is used by the `App.razor` component. - -The main differences between it and a regular Blazor WASM project are: - -1. You need to `PreConfigure` the `IsBlazorWebApp` to `true` in `AbpAspNetCoreComponentsWebOptions`: - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.IsBlazorWebApp = true; - }); -} -```` - -2. Use `AddBlazorWebAppServices` to replace `Authentication` code: - -````csharp -private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) -{ - builder.Services.AddBlazorWebAppServices(); -} -```` - -3. Remove the `builder.RootComponents.Add("#ApplicationContainer");` code. - -### MyCompanyName.MyProjectName.Blazor.WebApp.Tiered and MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client - -The tiered projects are the same as the WebApp projects, but the authentication configuration is different. - -We need share the `access_token` to `Client` project. - -Add code block to `App.razor` of `MyCompanyName.MyProjectName.Blazor.WebApp.Tiered` as below: - -````csharp -@code{ - [CascadingParameter] - private HttpContext HttpContext { get; set; } = default!; - - [Inject] - private PersistentComponentState PersistentComponentState { get; set; } - - private string? Token { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - if (HttpContext.User?.Identity?.IsAuthenticated == true) - { - Token = await HttpContext.GetTokenAsync("access_token"); - } - - PersistentComponentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); - } - - async Task OnPersistingAsync() - { - if (!Token.IsNullOrWhiteSpace()) - { - PersistentComponentState.PersistAsJson(PersistentAccessToken.Key, new PersistentAccessToken - { - AccessToken = Token - }); - } - - await Task.CompletedTask; - } -} -```` - -Add `ConfigureAuthentication` to `MyProjectNameBlazorClientModule` of `MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client` as below: - -````csharp -private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) -{ - builder.Services.AddBlazorWebAppTieredServices(); -} -```` - -## ABP Bundle - -You need set `IsBlazorWebApp` and `InteractiveAuto` to `true` in the `appsettings.json` file of the `MyCompanyName.MyProjectName.Blazor.WebApp.Client` project: - -````json -{ - "AbpCli": { - "Bundle": { - "Mode": "BundleAndMinify", /* Options: None, Bundle, BundleAndMinify */ - "Name": "global", - "IsBlazorWebApp": true, - "InteractiveAuto": true, - "Parameters": { - - } - } - } -} -```` - -For Blazor WASM and Blazor Server applications, you need to set `IsBlazorWebApp` to `true` and not need to change the `InteractiveAuto`: - -````json -{ - "AbpCli": { - "Bundle": { - "Mode": "BundleAndMinify", /* Options: None, Bundle, BundleAndMinify */ - "Name": "global", - "IsBlazorWebApp": true, - "Parameters": { - - } - } - } -} -```` - -Then run the `abp bundle` command to under the `MyCompanyName.MyProjectName.Blazor.WebApp.Client` project to generate the `global.css` and `global.js` files. - -## Troubleshooting - -If you encounter any problems during the migration, please create a new template project and compare the differences between the new and old projects. - -# References - -- [ASP.NET Core Blazor render modes](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0) -- [Migrate from ASP.NET Core Blazor 7.0 to 8.0](https://learn.microsoft.com/en-us/aspnet/core/migration/70-80?view=aspnetcore-8.0&tabs=visual-studio#blazor) -- [Full stack web UI with Blazor in .NET 8 | .NET Conf 2023](https://www.youtube.com/watch?v=YwZdtLEtROA) diff --git a/docs/en/Migration-Guides/Abp-8_0.md b/docs/en/Migration-Guides/Abp-8_0.md deleted file mode 100644 index 0cba42d759..0000000000 --- a/docs/en/Migration-Guides/Abp-8_0.md +++ /dev/null @@ -1,303 +0,0 @@ -# ABP Version 8.0 Migration Guide - -This document is a guide for upgrading ABP v7.x solutions to ABP v8.0. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -> ABP Framework upgraded to .NET 8.0, so you need to move your solutions to .NET 8.0 if you want to use the ABP 8.0. You can check the [Migrate from ASP.NET Core 7.0 to 8.0](https://learn.microsoft.com/en-us/aspnet/core/migration/70-80) documentation. - -## Upgraded to OpenIddict to 5.0.0 - -The 5.0 release of OpenIddict is a major release that introduces breaking changes. - -See [OpenIddict 4.x to 5.x Migration Guide](OpenIddict4-to-5.md) for more information. - -## Injected the `IDistributedEventBus` Dependency into the `IdentityUserManager` - -In this version, `IDistributedEventBus` service has been injected to the `IdentityUserManager` service, to publish a distributed event when the email or username is changed for a user, this was needed because sometimes there may be scenarios where the old email/username is needed for the synchronization purposes. - -Therefore, you might need to update the `IdentityUserManager`'s constructor if you have overridden the class and are using it. - -> See the issue for more information: https://github.com/abpframework/abp/pull/17990 - -## Updated Method Signatures in the Bundling System - -In this version, ABP Framework introduced the CDN support for bundling. During the development, we have made some improvements on the bundling system and changed some method signatures. - -See https://github.com/abpframework/abp/issues/17864 for more information. - -## Replaced `IdentityUserLookupAppService` with the `IIdentityUserIntegrationService` - -[Integration Services](../Integration-Services.md) are built for module-to-module (or microservice-to-microservice) communication rather than consumed from a UI or a client application as [Application Services](../Application-Services.md) are intended to do. - -In that regard, we are discarding the `IIdentityUserLookupAppService` in the Identity Module and moving its functionality to the `IIdentityUserIntegrationService`. Therefore, if you have used that application service directly, use the integration service (`IIdentityUserIntegrationService`) instead. `IIdentityUserLookupAppService` will be removed in thes next versions, so you may need to create a similar service in your application. - -> Notice that integration services have no authorization and are not exposed as HTTP API by default. -Also, if you have overridden the `IdentityUserLookupAppService` and `IdentityUserIntegrationService` classes in your application, you should update these classes' constructors as follows: - -*IdentityUserLookupAppService.cs* -```csharp - public IdentityUserLookupAppService(IIdentityUserIntegrationService identityUserIntegrationService) - { - IdentityUserIntegrationService = identityUserIntegrationService; - } -``` - -*IdentityUserIntegrationService.cs* - -```diff - public IdentityUserIntegrationService( - IUserRoleFinder userRoleFinder, -+ IdentityUserRepositoryExternalUserLookupServiceProvider userLookupServiceProvider) - { - UserRoleFinder = userRoleFinder; -+ UserLookupServiceProvider = userLookupServiceProvider; - } -``` - -## MongoDB Event Bus Enhancements - -In this version, we have made some enhancements in the transactional inbox/outbox pattern implementation and defined two new methods: `ConfigureEventInbox` and `ConfigureEventOutbox` for MongoDB Event Box collections. - -If you call one of these methods in your DbContext class, then this introduces a breaking-change because if you do it, MongoDB collection names will be changed. Therefore, it should be carefully done since existing (non-processed) event records are not automatically moved to new collection and they will be lost. Existing applications with event records should rename the collection manually while deploying their solutions. - -See https://github.com/abpframework/abp/pull/17723 for more information. Also, check the documentation for the related configurations: [Distributed Event Bus](../Distributed-Event-Bus.md) - -## Moved the CMS Kit Pages Feature's Routing to a `DynamicRouteValueTransformer` - -In this version, we have made some improvements in the [CMS Kit's Pages Feature](../Modules/Cms-Kit/Pages.md), such as moving the routing logic to a `DynamicRouteValueTransformer` and etc... - -These enhancements led to some breaking changes as listed below that should be taken care of: - -* Page routing has been moved to **DynamicRouteValueTransformer**. If you use `{**slug}` pattern in your routing, it might conflict with new CMS Kit routing. -* `PageConsts.UrlPrefix` has been removed, instead, the default prefix is *pages* for now. Still `/pages/{slug}` route works for backward compatibility alongside with `/{slug}` route. - -* **Endpoints changed:** - * `api/cms-kit-public/pages/{slug}` endpoint is changed to `api/cms-kit-public/pages/by-slug?slug={slug}`. Now multiple level of page URLs can be used and `/` characters will be transferred as URL Encoded in querysting to the HTTP API. - * `api/cms-kit-public/pages` changed to `api/cms-kit-public/pages/home` - ->_CmsKit Client Proxies are updated. If you don't send a **custom request** to this endpoint, **you don't need to take an action**_ - -## Added Integration Postfix for Auto Controllers - -With this version on, the `Integration` suffix from controller names while generating [auto controllers](../API/Auto-API-Controllers.md) are not going to be removed, to differ the integration services from application services in the OpenAPI specification: - -![](./images/integration-postfix-not-removed.png) - -> This should not affect most of the applications since you normally do not depend on the controller names in the client side. - -See https://github.com/abpframework/abp/issues/17625 for more information (how to preserve the existing behaviour, etc...). - -## Revised the reCaptcha Generator for CMS Kit's Comment Feature - -In this version, we have made improvements on the [CMS Kit's Comment Feature](../Modules/Cms-Kit/Comments.md) and revised the reCaptcha generation process, and made a performance improvement. - -This introduced some breaking changes that you should aware of: - -* Lifetime of the `SimpleMathsCaptchaGenerator` changed from singleton to transient, -* Changed method signatures for `SimpleMathsCaptchaGenerator` class. (all of its methods are now async) - -If you haven't override the comment view component, then you don't need to make any changes, however if you have overriden the component and used the `SimpleMathsCaptchaGenerator` class, then you should make the required changes as described. - -## Disabled Logging for `HEAD` HTTP Methods - -HTTP GET requests should not make any change in the database normally and audit log system of ABP Framework doesn't save audit log objects for GET requests by default. You can configure the `AbpAuditingOptions` and set the `IsEnabledForGetRequests` to **true** if you want to record _GET_ requests as described in [the documentation](../Audit-Logging.md). - -Prior to this version, only the _GET_ requests were not saved as audit logs. From this version on, also the _HEAD_ requests will not be saved as audit logs, if the `IsEnabledForGetRequests` explicitly set as **true**. - -You don't need to make any changes related to that, however it's important to know this change. - -## Obsolete the `AbpAspNetCoreIntegratedTestBase` Class - -In this version, `AbpAspNetCoreAsyncIntegratedTestBase` class has been set as `Obsolete` and it's recommended to use `AbpWebApplicationFactoryIntegratedTest` instead. - -## Use NoTracking for Readonly Repositories for EF Core - -In this version, ABP Framework provides read-only [repository](Repositories.md) interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`) to explicitly indicate that your purpose is to query data, but not change it. If so, you can inject these interfaces into your services. - -Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. - -> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) then casted it to a read-only repository interface. - -> See the issue for more information: https://github.com/abpframework/abp/pull/17421 - -## Use `IAbpDaprClientFactory` to Obtain `DaprClient` - -From this version on, instead of injecting the `DaprClient` directly, using the `IAbpDaprClientFactory.CreateAsync` method to create `DaprClient` or `HttpClient` objects to perform operations on Dapr is recommended. - -The documentation is already updated according to this suggestion and can be found at https://docs.abp.io/en/abp/8.0/Dapr/Index. So, if you want to learn more you can check the documentation or see the PR: https://github.com/abpframework/abp/pull/18117. - -## Use Newer Versions of the SQL Server (SQL Server 14+) - -Starting with EF Core 8.0, EF now generates SQL that is more efficient, but is unsupported on SQL Server 2014 and below. Therefore, if your database provider is SQL Server, then ensure that it's newer than SQL Server 2014. Otherwise, you may get errors due to database creation or while seeding initial data. - -The error is similar to: `Microsoft.Data.SqlClient.SqlException (0x80131904)`. - -> Check the [Entity Framework Core's Breaking Changes](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/breaking-changes#high-impact-changes) documentation for more info. - -## Angular UI - -### Guards - -From Angular Documentation; - -> Class-based **`Route`** guards are deprecated in favor of functional guards. - -- Angular has been using functional guards since version 14. According to this situation we have moved our guards to functional guards. - -We have modified our modules to adaptate functional guards. - -```diff -- import {AuthGuard, PermissionGuard} from '@abp/ng.core'; -+ import {authGuard, permissionGuard} from '@abp/ng.core'; - -- canActivate: mapToCanActivate([AuthGuard, PermissionGuard]) -+ canActivate: [authGuard, permissionGuard] -``` - -You can still use class based guards but we recommend it to use functional guards like us :) - -## Upgraded NuGet Dependencies - -You can see the following list of NuGet libraries that have been upgraded with .NET 8.0 upgrade, if you are using one of these packages explicitly, you may consider upgrading them in your solution: - -| Package | Old Version | New Version | -| ------------------- | ----------- | ----------- | -| aliyun-net-sdk-sts | 3.1.0 | 3.1.2 | -| AsyncKeyedLock | 6.2.1 | 6.2.2 | -| Autofac | 7.0.0 | 8.0.0 | -| Autofac.Extensions.DependencyInjection | 8.0.0 | 9.0.0 | -| Autofac.Extras.DynamicProxy | 6.0.1 | 7.1.0 | -| AutoMapper | 12.0.0 | 12.0.1 | -| AWSSDK.S3 | 3.7.9.2 | 3.7.300.2 | -| AWSSDK.SecurityToken | 3.7.1.151 | 3.7.300.2 | -| Azure.Messaging.ServiceBus | 7.8.1 | 7.17.0 | -| Azure.Storage.Blobs | 12.15.0 | 12.19.1 | -| Blazorise | 1.3.1 | 1.4.1 | -| Blazorise.Bootstrap5 | 1.3.1 | 1.4.1 | -| Blazorise.Icons.FontAwesome | 1.3.1 | 1.4.1 | -| Blazorise.Components | 1.3.1 | 1.4.1 | -| Blazorise.DataGrid | 1.3.1 | 1.4.1 | -| Blazorise.Snackbar | 1.3.1 | 1.4.1 | -| Confluent.Kafka | 1.8.2 | 2.3.0 | -| Dapper | 2.0.123 | 2.1.21 | -| Dapr.AspNetCore | 1.9.0 | 1.12.0 | -| Dapr.Client | 1.9.0 | 1.12.0 | -| Devart.Data.Oracle.EFCore | 10.1.134.7 | 10.3.10.8 | -| DistributedLock.Core | 1.0.4 | 1.0.5 | -| DistributedLock.Redis | 1.0.1 | 1.0.2 | -| EphemeralMongo.Core | 1.1.0 | 1.1.3 | -| EphemeralMongo6.runtime.linux-x64 | 1.1.0 | 1.1.3 | -| EphemeralMongo6.runtime.osx-x64 | 1.1.0 | 1.1.3 | -| EphemeralMongo6.runtime.win-x64 | 1.1.0 | 1.1.3 | -| FluentValidation | 11.0.1 | 11.8.0 | -| Hangfire.AspNetCore | 1.8.2 | 1.8.6 | -| Hangfire.SqlServer | 1.8.2 | 1.8.6 | -| HtmlSanitizer | 5.0.331 | 8.0.746 | -| HtmlAgilityPack | 1.11.42 | 1.11.54 | -| IdentityModel | 6.0.0 | 6.2.0 | -| IdentityServer4.AspNetIdentity | 4.1.1 | 4.1.2 | -| JetBrains.Annotations | 2022.1.0 | 2023.3.0 | -| LibGit2Sharp | 0.26.2 | 0.28.0 | -| Magick.NET-Q16-AnyCPU | 13.2.0 | 13.4.0 | -| MailKit | 3.2.0 | 4.3.0 | -| Markdig.Signed | 0.26.0 | 0.33.0 | -| Microsoft.AspNetCore.Authentication.JwtBearer | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Authentication.OpenIdConnect | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Authorization | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Components | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Components.Authorization | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Components.Web | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Components.WebAssembly | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Components.WebAssembly.Authentication | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Components.WebAssembly.DevServer | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Components.WebAssembly.Server | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.DataProtection.StackExchangeRedis | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Mvc.NewtonsoftJson | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.Mvc.Versioning | 5.0.0 | 5.1.0 | -| Microsoft.AspNetCore.Razor.Language | 6.0.8 | 6.0.25 | -| Microsoft.AspNetCore.TestHost | 7.0.10 | 8.0.0 | -| Microsoft.AspNetCore.WebUtilities | 2.2.0 | 8.0.0 | -| Microsoft.Bcl.AsyncInterfaces | 7.0.0 | 8.0.0 | -| Microsoft.CodeAnalysis.CSharp | 4.2.0 | 4.5.0 | -| Microsoft.Data.Sqlite | 7.0.0 | 8.0.0 | -| Microsoft.EntityFrameworkCore | 7.0.10 | 8.0.0 | -| Microsoft.EntityFrameworkCore.Design | 7.0.0 | 8.0.0 | -| Microsoft.EntityFrameworkCore.InMemory | 7.0.10 | 8.0.0 | -| Microsoft.EntityFrameworkCore.Proxies | 7.0.10 | 8.0.0 | -| Microsoft.EntityFrameworkCore.Relational | 7.0.10 | 8.0.0 | -| Microsoft.EntityFrameworkCore.Sqlite | 7.0.10 | 8.0.0 | -| Microsoft.EntityFrameworkCore.SqlServer | 7.0.0 | 8.0.0 | -| Microsoft.EntityFrameworkCore.Tools | 7.0.1 | 8.0.0 | -| Microsoft.Extensions.Caching.Memory | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Caching.StackExchangeRedis | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Configuration.Binder | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Configuration.CommandLine | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Configuration.EnvironmentVariables | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Configuration.UserSecrets | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.DependencyInjection | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.DependencyInjection.Abstractions | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.FileProviders.Composite | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.FileProviders.Embedded | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.FileProviders.Physical | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.FileSystemGlobbing | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Hosting | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Hosting.Abstractions | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Http | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Http.Polly| 7.0.10 | 8.0.0 | -| Microsoft.Extensions.Identity.Core | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Localization | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Logging | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Logging.Console | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Options | 7.0.0 | 8.0.0 | -| Microsoft.Extensions.Options.ConfigurationExtensions | 7.0.0 | 8.0.0 | -| Microsoft.NET.Test.Sdk | 17.2.0 | 17.8.0 | -| Microsoft.VisualStudio.Web.CodeGeneration.Design | 7.0.0 | 8.0.0 | -| Minio | 4.0.6 | 6.0.1 | -| MongoDB.Driver | 2.19.1 | 2.22.0 | -| NEST | 7.14.1 | 7.17.5 | -| Newtonsoft.Json | 13.0.1 | 13.0.3 | -| NSubstitute | 4.3.0 | 5.1.0 | -| NuGet.Versioning | 5.11.0 | 6.7.0 | -| NUglify | 1.20.0 | 1.21.0 | -| Npgsql.EntityFrameworkCore.PostgreSQL | 7.0.0 | 8.0.0 | -| NSubstitute.Analyzers.CSharp | 1.0.15 | 1.0.16 | -| Octokit | 0.50.0 | 9.0.0 | -| OpenIddict.Abstractions | 4.8.0 | 5.0.0 | -| OpenIddict.Core | 4.8.0 | 5.0.0 | -| OpenIddict.Server.AspNetCore | 4.8.0 | 5.0.0 | -| OpenIddict.Validation.AspNetCore | 4.8.0 | 5.0.0 | -| OpenIddict.Validation.ServerIntegration | 4.8.0 | 5.0.0 | -| Oracle.EntityFrameworkCore | 7.21.8 | 8.21.121 | -| Polly | 7.2.3 | 8.2.0 | -| Pomelo.EntityFrameworkCore.MySql | 7.0.0 | 8.0.0 | -| Quartz | 3.4.0 | 3.7.0 | -| Quartz.Extensions.DependencyInjection | 3.4.0 | 3.7.0 | -| Quartz.Plugins.TimeZoneConverter | 3.4.0 | 3.7.0 | -| Quartz.Serialization.Json | 3.3.3 | 3.7.0 | -| RabbitMQ.Client | 6.3.0 | 6.6.0 | -| Rebus | 6.6.5 | 8.0.1 | -| Rebus.ServiceProvider | 7.0.0 | 10.0.0 | -| Scriban | 5.4.4 | 5.9.0 | -| Serilog | 2.11.0 | 3.1.1 | -| Serilog.AspNetCore | 5.0.0 | 8.0.0 | -| Serilog.Extensions.Hosting | 3.1.0 | 8.0.0 | -| Serilog.Extensions.Logging | 3.1.0 | 8.0.0 | -| Serilog.Sinks.Async | 1.4.0 | 1.5.0 | -| Serilog.Sinks.Console | 3.1.1 | 5.0.0 | -| Serilog.Sinks.File | 4.1.0 | 5.0.0 | -| SharpZipLib | 1.3.3 | 1.4.2 | -| Shouldly | 4.0.3 | 4.2.1 | -| SixLabors.ImageSharp.Drawing | 2.0.0 | 2.0.1 | -| Slugify.Core | 3.0.0 | 4.0.1 | -| StackExchange.Redis | 2.6.122 | 2.7.4 | -| Swashbuckle.AspNetCore | 6.2.1 | 6.5.0 | -| System.Collections.Immutable | 7.0.0 | 8.0.0 | -| System.Linq.Dynamic.Core | 1.3.3 | 1.3.5 | -| System.Security.Permissions | 7.0.0 | 8.0.0 | -| System.Text.Encoding.CodePages | 7.0.0 | 8.0.0 | -| System.Text.Encodings.Web | 7.0.0 | 8.0.0 | -| System.Text.Json | 7.0.0 | 8.0.0 | -| TimeZoneConverter | 5.0.0 | 6.1.0 | -| xunit | 2.4.1 | 2.6.1 | -| xunit.extensibility.execution | 2.4.1 | 2.6.1 | -| xunit.runner.visualstudio | 2.4.5 | 2.5.3 | diff --git a/docs/en/Migration-Guides/Abp-8_1.md b/docs/en/Migration-Guides/Abp-8_1.md deleted file mode 100644 index f13ac8d209..0000000000 --- a/docs/en/Migration-Guides/Abp-8_1.md +++ /dev/null @@ -1,146 +0,0 @@ -# ABP Version 8.1 Migration Guide - -This document is a guide for upgrading ABP v8.0 solutions to ABP v8.1. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -## Added `NormalizedName` property to `Tenant` - -The `Tenant` entity has a new property called `NormalizedName`. It is used to find/cache a tenant by its name in a case-insensitive way. -This property is automatically set when a tenant is created or updated. It gets the normalized name of the tenant name by `UpperInvariantTenantNormalizer(ITenantNormalizer)` service. You can implement this service to change the normalization logic. - -### `ITenantStore` - -The `ITenantStore` will use the `NormalizedName` parameter to get tenants, Please use the `ITenantNormalizer` to normalize the tenant name before calling the `ITenantStore` methods. - -### Update `NormalizedName` in `appsettings.json` - -If your tenants defined in the `appsettings.json` file, you should add the `NormalizedName` property to your tenants. - -````json -"Tenants": [ - { - "Id": "446a5211-3d72-4339-9adc-845151f8ada0", - "Name": "tenant1", - "NormalizedName": "TENANT1" // <-- Add this property - }, - { - "Id": "25388015-ef1c-4355-9c18-f6b6ddbaf89d", - "Name": "tenant2", - "NormalizedName": "TENANT2", // <-- Add this property - "ConnectionStrings": { - "Default": "...tenant2's db connection string here..." - } - } - ] -```` - -### Update `NormalizedName` in the database - -Please add a sql script to your migration to set the `NormalizedName` property of the existing tenants. You can use the following script: - -> This script is for the SQL Server database. You can change it for your database. - -> The table name `SaasTenants` is used for ABP commercial Saas module. `AbpTenants` is for the ABP open-source Tenant Management module. - -```sql -UPDATE SaasTenants SET NormalizedName = UPPER(Name) WHERE NormalizedName IS NULL OR NormalizedName = '' -``` - -```csharp -/// -public partial class Add_NormalizedName : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "NormalizedName", - table: "SaasTenants", - type: "nvarchar(64)", - nullable: false, - defaultValue: ""); - - migrationBuilder.Sql("UPDATE SaasTenants SET NormalizedName = UPPER(Name) WHERE NormalizedName IS NULL OR NormalizedName = ''"); - - migrationBuilder.CreateIndex( - name: "IX_SaasTenants_NormalizedName", - table: "SaasTenants", - column: "NormalizedName"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_SaasTenants_NormalizedName", - table: "SaasTenants"); - - migrationBuilder.DropColumn( - name: "NormalizedName", - table: "SaasTenants"); - } -} -``` - -See https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/managing?tabs=dotnet-core-cli#adding-raw-sql to learn how to add raw SQL to migrations. - -## Use `Asp.Versioning.Mvc` to replace `Microsoft.AspNetCore.Mvc.Versioning` - -The Microsoft.AspNetCore.Mvc.Versioning packages are now deprecated and superseded by Asp.Versioning.Mvc. -See the announcement here: https://github.com/dotnet/aspnet-api-versioning/discussions/807 - -The namespace of the `[ControllerName]` attribute has changed to `using Asp.Versioning`, Please update your code to use the new namespace. - -Related PR: https://github.com/abpframework/abp/pull/18380 - -## New asynchronous methods for `IAppUrlProvider` - -The `IsRedirectAllowedUrl` method of `IAppUrlProvider` has been changed to `IsRedirectAllowedUrlAsync` and it is now an async method. -You should update your usage of `IAppUrlProvider` to use the new method. - -Related PR: https://github.com/abpframework/abp/pull/18492 - -## New attribute: `ExposeKeyedServiceAttribute` - -The new `ExposeKeyedServiceAttribute` is used to control which keyed services are provided by the related class. Example: - -````C# -[ExposeKeyedService("taxCalculator")] -[ExposeKeyedService("calculator")] -public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency -{ -} -```` - -In the example above, the `TaxCalculator` class exposes the `ITaxCalculator` interface with the key `taxCalculator` and the `ICalculator` interface with the key `calculator`. That means you can get keyed services from the `IServiceProvider` as shown below: - -````C# -var taxCalculator = ServiceProvider.GetRequiredKeyedService("taxCalculator"); -var calculator = ServiceProvider.GetRequiredKeyedService("calculator"); -```` - -Also, you can use the [`FromKeyedServicesAttribute`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.fromkeyedservicesattribute?view=dotnet-plat-ext-8.0) to resolve a certain keyed service in the constructor: - -```csharp -public class MyClass -{ - //... - public MyClass([FromKeyedServices("taxCalculator")] ITaxCalculator taxCalculator) - { - TaxCalculator = taxCalculator; - } -} -``` - -> Notice that the `ExposeKeyedServiceAttribute` only exposes the keyed services. So, you can not inject the `ITaxCalculator` or `ICalculator` interfaces in your application without using the `FromKeyedServicesAttribute` as shown in the example above. If you want to expose both keyed and non-keyed services, you can use the `ExposeServicesAttribute` and `ExposeKeyedServiceAttribute` attributes together as shown below: -````C# -[ExposeKeyedService("taxCalculator")] -[ExposeKeyedService("calculator")] -[ExposeServices(typeof(ITaxCalculator), typeof(ICalculator))] -public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency -{ -} -```` - -This is a small **Breaking Change** because `IOnServiceExposingContext` has changed. You should update your usage of `IOnServiceExposingContext` if you have related code. - -Related PR: https://github.com/abpframework/abp/pull/18819 diff --git a/docs/en/Migration-Guides/Abp-8_2.md b/docs/en/Migration-Guides/Abp-8_2.md deleted file mode 100644 index 5412fe47c0..0000000000 --- a/docs/en/Migration-Guides/Abp-8_2.md +++ /dev/null @@ -1,61 +0,0 @@ -# ABP Version 8.2 Migration Guide - -This document is a guide for upgrading ABP v8.x solutions to ABP v8.2. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. - -## Updated target frameworks to net8.0 for template projects - -`TargetFrameworks` of the following template projects are upgraded to **net8.0**: - -* `*.Application.Contracts` -* `*.Domain.Shared` -* `*.Domain` -* `*.MongoDB` -* `*.HttpApi.Client` -* `*.Host.Shared` (for module template) - -Before this version, all of the projects above were targeting multiple frameworks (**netstandard2.0**, **netstandard2.1** and **net8.0**), with this version, we started to only target **net8.0** for these template projects. Note that, all other shared libraries still target multiple frameworks. - -> This change should not affect your pre-existing solutions and you don't need to make any changes in your application. See the PR for more info: https://github.com/abpframework/abp/pull/19565 - -## Upgraded AutoMapper to 13.0.1 - -In this version, **AutoMapper** library version upgraded to 13.0.1. See [the release notes of AutoMapper v13.0.1](https://github.com/AutoMapper/AutoMapper/releases/tag/v13.0.1) for more information. - -## Added default padding to `.tab-content` class for Basic Theme - -In this version, default padding (padding-top: 1.5rem and padding-bottom: 1.5rem) has been added to the tab-content for the Basic Theme. See [#19475](https://github.com/abpframework/abp/pull/19475) for more information. - -## Moved members page directory for Blogging Module - -With this version on, ABP Framework allows you to use single blog mode, without needing to define a blog and a prefix. With this change, the following namespace changes were done: -* `Volo.Blogging.Pages.Blog` -> `Volo.Blogging.Pages.Blogs` -* `Volo.Blogging.Pages.Members` -> `Volo.Blogging.Pages.Blogs.Members` (members folder) - -> If you haven't overridden the pages above, then you don't need to make any additional changes. See [#19418](https://github.com/abpframework/abp/pull/19418) for more information. - -## Removed `FlagIcon` property from the `ILanguageInfo` - -The `FlagIcon` property has been removed from the `ILanguageInfo` interface since we removed the flag icon library in the earlier versions from all of our themes and none of them using it now. - -If the flag icon has been specified while defining the localization languages, then it should be removed: - -```diff - Configure(options => - { -- options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi", "in")); -+ options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi")); - //... - } -``` - -## Blazor Full-Stack Web UI - -In this version, ABP Framework provides a new UI option called **Blazor Full-Stack WebApp**. We have already created an introduction/migration guide for you to check it: [Migrating to Blazor Web App](Abp-8-2-Blazor-Web-App.md) - -> Please read the documentation carefully if you are considering migrating your existing **Blazor** project to **Blazor WebApp**. - -## Session Management Infrastructure - -The **Session Management** feature allows you to prevent concurrent login and manage user sessions. - -In this version, a new entity called `IdentitySession` has been added to the framework and you should create a new migration and apply it to your database. \ No newline at end of file diff --git a/docs/en/Migration-Guides/BlazorUI-3_3.md b/docs/en/Migration-Guides/BlazorUI-3_3.md deleted file mode 100644 index bb3d07f615..0000000000 --- a/docs/en/Migration-Guides/BlazorUI-3_3.md +++ /dev/null @@ -1,14 +0,0 @@ -# Migration Guide for the Blazor UI from the v3.2 to the v3.3 - -## Startup Template Changes - -* Remove `Volo.Abp.Account.Blazor` NuGet package from your `.Blazor.csproj` and add `Volo.Abp.TenantManagement.Blazor` NuGet package. -* Remove the ``typeof(AbpAccountBlazorModule)`` from the dependency list of *YourProjectBlazorModule* class and add the `typeof(AbpTenantManagementBlazorModule)`. -* Add `@using Volo.Abp.BlazoriseUI` and `@using Volo.Abp.BlazoriseUI.Components` into the `_Imports.razor` file. -* Remove the `div` with `id="blazor-error-ui"` (with its contents) from the `wwwroot/index.html ` file, since the ABP Framework now shows error messages as a better message box. -* Update the`AddOidcAuthentication` options in your *YourProjectBlazorModule* class as described in the issue [#5913](https://github.com/abpframework/abp/issues/5913). - -## BlazoriseCrudPageBase to AbpCrudPageBase - -Renamed `BlazoriseCrudPageBase` to `AbpCrudPageBase`. Just update the usages. It also has some changes, you may need to update method calls/usages manually. - diff --git a/docs/en/Migration-Guides/IdentityServer4-Step-by-Step.md b/docs/en/Migration-Guides/IdentityServer4-Step-by-Step.md deleted file mode 100644 index aff389ccf8..0000000000 --- a/docs/en/Migration-Guides/IdentityServer4-Step-by-Step.md +++ /dev/null @@ -1,230 +0,0 @@ -# Migrating from OpenIddict to IdentityServer4 Step by Step Guide - -ABP startup templates use `OpenIddict` OpenID provider from v6.0.0 by default and `IdentityServer` projects are renamed to `AuthServer` in tiered/separated solutions. Since OpenIddict is the default OpenID provider library for ABP templates since v6.0, you may want to keep using [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) library, even it is **archived and no longer maintained by the owners**. ABP doesn't provide support for newer versions of IdentityServer. This guide provides layer-by-layer guidance for migrating your existing [OpenIddict](https://github.com/openiddict/openiddict-core) application to IdentityServer4. - -## IdentityServer4 Migration Steps - -Use the `abp update` command to update your existing application. See [Upgrading docs](../Upgrading.md) for more info. Apply required migrations by following the [Migration Guides](Index.md) based on your application version. - -### Domain.Shared Layer - -- In **MyApplication.Domain.Shared.csproj** replace **project reference**: - -```csharp - -``` - - with - -```csharp - -``` - -- In **MyApplicationDomainSharedModule.cs** replace usings and **module dependencies:** - -```csharp -using Volo.Abp.OpenIddict; -... -typeof(AbpOpenIddictDomainSharedModule) -``` - - with - -```csharp -using Volo.Abp.IdentityServer; -... -typeof(AbpIdentityServerDomainSharedModule) -``` - -### Domain Layer - -- In **MyApplication.Domain.csproj** replace **project references**: - -```csharp - - -``` - - with - -```csharp - - -``` - -- In **MyApplicationDomainModule.cs** replace usings and **module dependencies**: - -```csharp -using Volo.Abp.OpenIddict; -using Volo.Abp.PermissionManagement.OpenIddict; -... -typeof(AbpOpenIddictDomainModule), -typeof(AbpPermissionManagementDomainOpenIddictModule), -``` - - with - -```csharp -using Volo.Abp.IdentityServer; -using Volo.Abp.PermissionManagement.IdentityServer; -... -typeof(AbpIdentityServerDomainModule), -typeof(AbpPermissionManagementDomainIdentityServerModule), -``` - -#### OpenIddictDataSeedContributor - -DataSeeder is the most important part for starting the application since it seeds the initial data for both OpenID providers. - -- Create a folder named *IdentityServer* under the Domain project and copy the [IdentityServerDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/IdentityServer/IdentityServerDataSeedContributor.cs) under this folder. **Rename** all the `OpenId2Ids` with your project name. -- Delete *OpenIddict* folder that contains `OpenIddictDataSeedContributor.cs` which is no longer needed. - -### EntityFrameworkCore Layer - -If you are using MongoDB, skip this step and check the *MongoDB* layer section. - -- In **MyApplication.EntityFrameworkCore.csproj** replace **project reference**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In **MyApplicationEntityFrameworkCoreModule.cs** replace usings and **module dependencies**: - -```csharp -using Volo.Abp.OpenIddict.EntityFrameworkCore; -... -typeof(AbpOpenIddictEntityFrameworkCoreModule), -``` - - with - -```csharp -using Volo.Abp.IdentityServer.EntityFrameworkCore; -... -typeof(AbpIdentityServerEntityFrameworkCoreModule), -``` - -- In **MyApplicationDbContext.cs** replace usings and **fluent api configurations**: - - ```csharp - using Volo.Abp.OpenIddict.EntityFrameworkCore; - ... - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - ... - builder.ConfigureOpenIddict(); - ``` - - with - - ```csharp - using Volo.Abp.IdentityServer.EntityFrameworkCore; - ... - using Volo.Abp.OpenIddict.EntityFrameworkCore; - ... - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - ... - builder.ConfigureIdentityServer(); - ``` - -> Not: You need to create new migration after updating the fluent api. Navigate to *EntityFrameworkCore* folder and add a new migration. Ex, `dotnet ef migrations add Updated_To_IdentityServer ` - -### MongoDB Layer - -If you are using EntityFrameworkCore, skip this step and check the *EntityFrameworkCore* layer section. - -- In **MyApplication.MongoDB.csproj** replace **project reference**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In **MyApplicationMongoDbModule.cs** replace usings and **module dependencies**: - -```csharp -using Volo.Abp.OpenIddict.MongoDB; -... -typeof(AbpOpenIddictMongoDbModule), -``` - - with - -```csharp -using Volo.Abp.IdentityServer.MongoDB; -... -typeof(AbpIdentityServerMongoDbModule), -``` - -### DbMigrator Project - -- In `appsettings.json` **replace OpenIddict section with IdentityServer** since IdentityServerDataSeeder will be using these information for initial data seeding: - - ```json - "IdentityServer": { // Rename OpenIddict to IdentityServer - "Clients ": { // Rename Applications to Clients - ... - } - } - ``` - - -### Test Project - -- In **MyApplicationTestBaseModule.cs** **add** the IdentityServer related using and PreConfigurations: - - ```csharp - using Volo.Abp.IdentityServer; - ``` - - and - - ```csharp - PreConfigure(options => - { - options.AddDeveloperSigningCredential = false; - }); - - PreConfigure(identityServerBuilder => - { - identityServerBuilder.AddDeveloperSigningCredential(false, System.Guid.NewGuid().ToString()); - }); - ``` - - to `PreConfigureServices` to run authentication related unit tests. - -### UI Layer - -You can follow the migrations guides from IdentityServer to OpenIddict in **reverse order** to update your UIs. You can also check the source-code for [Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml) and [Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml.cs) files for **AuthServer** project. - -- [Angular UI Migration](OpenIddict-Angular.md) -- [MVC/Razor UI Migration](OpenIddict-Mvc.md) -- [Blazor-Server UI Migration](OpenIddict-Blazor-Server.md) -- [Blazor-Wasm UI Migration](OpenIddict-Blazor.md) - -## Source code of samples and module - -* [Open source tiered & separate auth server application migrate OpenIddict to Identity Server](https://github.com/abpframework/abp-samples/tree/master/OpenId2Ids) -* [IdentityServer module document](https://docs.abp.io/en/abp/6.0/Modules/IdentityServer) -* [IdentityServer module source code](https://github.com/abpframework/abp/tree/rel-6.0/modules/identityserver) diff --git a/docs/en/Migration-Guides/IdentityServer_To_OpenIddict.md b/docs/en/Migration-Guides/IdentityServer_To_OpenIddict.md deleted file mode 100644 index d60493be7a..0000000000 --- a/docs/en/Migration-Guides/IdentityServer_To_OpenIddict.md +++ /dev/null @@ -1,78 +0,0 @@ -# Migration Identity Server to OpenIddict Guide - -This document explains how to migrate to [OpenIddict](https://github.com/openiddict/openiddict-core) from Identity Server. From now on the ABP startup templates uses `OpenIddict` as the auth server by default since version v6.0.0. - -## History -We are not removing Identity Server packages and we will continue to release new versions of Identity Server related NuGet/NPM packages. That means you won't have an issue while upgrading to v6.0 when the stable version releases. We will continue to fix bugs in our packages for a while. ABP 7.0 will be based on .NET 7. If Identity Server continues to work with .NET 7, we will also continue to ship NuGet packages for our IDS integration. - -On the other hand, Identity Server ends support for the open-source Identity Server in the end of 2022. The Identity Server team has decided to move to Duende IDS and ABP will not be migrated to the commercial Duende IDS. You can see the Duende Identity Server announcement from [this link](https://blog.duendesoftware.com/posts/20220111_fair_trade). - -## OpenIddict Migration Steps - -* Update all `Volo's` packages to `6.x`. -* Replace all `Volo's` `IdentityServer.*` packages with corresponding `OpenIddict.*` packages. eg `Volo.Abp.IdentityServer.Domain` to `Volo.Abp.OpenIddict.Domain`, `Volo.Abp.Account.Web.IdentityServer` to `Volo.Abp.Account.Web.OpenIddict`. -* Replace all `IdentityServer` modules with corresponding `OpenIddict` modules. eg `AbpIdentityServerDomainModule` to `AbpOpenIddictDomainModule`, `AbpAccountWebIdentityServerModule` to `AbpAccountWebOpenIddictModule`. -* Rename the `ConfigureIdentityServer` to `ConfigureOpenIddict` in your `ProjectNameDbContext` class. -* Remove the `UseIdentityServer` and add `UseAbpOpenIddictValidation` after `UseAuthentication`. -* Add follow code to your startup module. - -```cs -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("ProjectName"); // Change ProjectName to your project name. - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); -} -``` - -* If your project is not separate AuthServer please also add `ForwardIdentityAuthenticationForBearer` - -```cs -private void ConfigureAuthentication(ServiceConfigurationContext context) -{ - context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); -} -``` - -* Remove the `IdentityServerDataSeedContributor` from the `Domain` project. -* Create a new version of the project, with the same name as your existing project. -* Copy the `ProjectName.Domain\OpenIddict\OpenIddictDataSeedContributor.cs` of new project into your project and update `appsettings.json` base on `ProjectName.DbMigrator\appsettings.json`, Be careful to change the port number. -* Copy the `Index.cshtml.cs` and `Index.cs` of new project to your project if you're using `IClientRepository` in `IndexModel`. -* Update the scope name from `role` to `roles` in `AddAbpOpenIdConnect` method. -* Remove `options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` from `HttpApi.Host` project. -* AuthServer no longer requires `JWT bearer authentication`. Please remove it. eg `AddJwtBearer` and `UseJwtTokenMiddleware`. -* Try compiling the project in the IDE and following the errors to remove and reference the code and namespaces. -* Add migrations and update the database if you are using EF Core as the database provider. - -## Module packages -### Open source side -* Volo.Abp.OpenIddict.Domain (`AbpOpenIddictDomainModule`) -* Volo.Abp.OpenIddict.Domain.Shared (`AbpOpenIddictDomainSharedModule`) -* Volo.Abp.OpenIddict.EntityFrameworkCore (`AbpOpenIddictEntityFrameworkCoreModule`) -* Volo.Abp.OpenIddict.AspNetCore (`AbpOpenIddictAspNetCoreModule`) -* Volo.Abp.OpenIddict.MongoDB (`AbpOpenIddictMongoDbModule`) -* Volo.Abp.Account.Web.OpenIddict (`AbpAccountWebOpenIddictModule`) -* Volo.Abp.PermissionManagement.Domain.OpenIddict (`AbpPermissionManagementDomainOpenIddictModule`) - -### Commercial side -* Volo.Abp.OpenIddict.Pro.Application.Contracts (`AbpOpenIddictProApplicationContractsModule`) -* Volo.Abp.OpenIddict.Pro.Application (`AbpOpenIddictProApplicationModule`) -* Volo.Abp.OpenIddict.Pro.HttpApi.Client (`AbpOpenIddictProHttpApiClientModule`) -* Volo.Abp.OpenIddict.Pro.HttpApi (`AbpOpenIddictProHttpApiModule`) -* Volo.Abp.OpenIddict.Pro.Blazor(`AbpOpenIddictProBlazorModule`) -* Volo.Abp.OpenIddict.Pro.Blazor.Server (`AbpOpenIddictProBlazorServerModule`) -* Volo.Abp.OpenIddict.Pro.Blazor.WebAssembly (`AbpOpenIddictProBlazorWebAssemblyModule`) -* Volo.Abp.OpenIddict.Pro.Web (`AbpOpenIddictProWebModule`) - -## Source code of samples and module - -* [Open source tiered & separate auth server application migrate Identity Server to OpenIddict](https://github.com/abpframework/abp-samples/tree/master/Ids2OpenId) -* [Commercial tiered & separate auth server application migrate Identity Server to OpenIddict](https://abp.io/Account/Login?returnUrl=/api/download/samples/Ids2OpenId) -* [OpenIddict module document](https://docs.abp.io/en/abp/6.0/Modules/OpenIddict) -* [OpenIddict module source code](https://github.com/abpframework/abp/tree/rel-6.0/modules/openiddict) diff --git a/docs/en/Migration-Guides/Index.md b/docs/en/Migration-Guides/Index.md deleted file mode 100644 index 2718bc80fb..0000000000 --- a/docs/en/Migration-Guides/Index.md +++ /dev/null @@ -1,20 +0,0 @@ -# ABP Framework Migration Guides - -The following documents explain how to migrate your existing ABP applications. We write migration documents only if you need to take an action while upgrading your solution. Otherwise, you can easily upgrade your solution using the [abp update command](../Upgrading.md). - -- [8.x to 8.2](Abp-8_2.md) -- [8.0 to 8.1](Abp-8_1.md) -- [7.x to 8.0](Abp-8_0.md) -- [7.3 to 7.4](Abp-7_4.md) -- [7.2 to 7.3](Abp-7_3.md) -- [7.1 to 7.2](Abp-7_2.md) -- [7.0 to 7.1](Abp-7_1.md) -- [6.0 to 7.0](Abp-7_0.md) -- [5.3 to 6.0](Abp-6_0.md) -- [5.2 to 5.3](Abp-5_3.md) -- [5.1 to 5.2](Abp-5_2.md) -- [4.x to 5.0](Abp-5_0.md) -- [4.2 to 4.3](Abp-4_3.md) -- [4.x to 4.2](Abp-4_2.md) -- [3.3.x to 4.0](Abp-4_0.md) -- [2.9.x to 3.0](../UI/Angular/Migration-Guide-v3.md) diff --git a/docs/en/Migration-Guides/OpenIddict-Angular.md b/docs/en/Migration-Guides/OpenIddict-Angular.md deleted file mode 100644 index fe0b3986ac..0000000000 --- a/docs/en/Migration-Guides/OpenIddict-Angular.md +++ /dev/null @@ -1,170 +0,0 @@ -# OpenIddict Angular UI Migration Guide - -## Angular Project - -- In `environment.ts` and `environment.prod.ts` **add a trailing slash at the end of the issuer**: - - ```typescript - oAuthConfig: { - issuer: 'https://localhost:44377/', - ... - }, - ``` - -## Http.Api.Host (Non-Separated IdentityServer) - -- In **MyApplication.HttpApi.Host.csproj** replace **project references**: - - ```csharp - - - ``` - - with - - ```csharp - - ``` - -- In the **MyApplicationHttpApiHostModule.cs** replace usings and **module dependencies**: - - ```csharp - using Volo.Abp.AspNetCore.Authentication.JwtBearer; - ... - typeof(AbpAspNetCoreAuthenticationJwtBearerModule), - typeof(AbpAccountWebIdentityServerModule), - ``` - - with - - ```csharp - using OpenIddict.Validation.AspNetCore; - ... - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In the **MyApplicationHttpApiHostModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In the **MyApplicationHttpApiHostModule.cs** `ConfigureServices` method, **replace the method call**: - - From `ConfigureAuthentication(context, configuration);` to `ConfigureAuthentication(context);` and update the method as: - - ```csharp - private void ConfigureAuthentication(ServiceConfigurationContext context) - { - context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); - } - ``` - -- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, **replace the midware**: - - ```csharp - app.UseJwtTokenMiddleware(); - app.UseIdentityServer(); - ``` - - with - - ```csharp - app.UseAbpOpenIddictValidation(); - ``` - -- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. - -- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: - - ```json - "AuthServer": { - "Authority": "https://localhost:44345", - "RequireHttpsMetadata": "false", - "SwaggerClientId": "MyApplication_Swagger" - }, - ``` - -## Http.Api.Host (Separated IdentityServer) - -- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. - -- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: - - ```json - "AuthServer": { - "Authority": "https://localhost:44345", - "RequireHttpsMetadata": "false", - "SwaggerClientId": "MyApplication_Swagger" - }, - ``` - -## IdentityServer - -This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future. - -- In **MyApplication.IdentityServer.csproj** replace **project references**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In the **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: - - ```csharp - typeof(AbpAccountWebIdentityServerModule), - ``` - - with - - ```csharp - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In the **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method, **remove the midware**: - - ```csharp - app.UseIdentityServer(); - ``` - -- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. - - > Note: It can be found under the *Pages* folder. - -## See Also - -* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) diff --git a/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md b/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md deleted file mode 100644 index 93c9562a4d..0000000000 --- a/docs/en/Migration-Guides/OpenIddict-Blazor-Server.md +++ /dev/null @@ -1,175 +0,0 @@ -# OpenIddict Blazor-Server UI Migration Guide - -## Blazor Project (Non-Tiered Solution) - -- In the **MyApplication.Blazor.csproj** replace **project references**: - - ```csharp - - - ``` - - with - - ```csharp - - ``` - -- In the **MyApplicationBlazorModule.cs** replace usings and **module dependencies**: - - ```csharp - using System; - using System.Net.Http; - using Volo.Abp.AspNetCore.Authentication.JwtBearer; - ... - typeof(AbpAspNetCoreAuthenticationJwtBearerModule), - typeof(AbpAccountWebIdentityServerModule), - ``` - - with - - ```csharp - using OpenIddict.Validation.AspNetCore; - ... - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In the **MyApplicationBlazorModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In the **MyApplicationBlazorModule.cs** `ConfigureServices` method, **replace the method call**: - - From `ConfigureAuthentication(context, configuration);` to `ConfigureAuthentication(context);` and update the method as: - - ```csharp - private void ConfigureAuthentication(ServiceConfigurationContext context) - { - context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); - } - ``` - -- In the **MyApplicationBlazorModule.cs** `OnApplicationInitialization` method, **replace the midware**: - - ```csharp - app.UseJwtTokenMiddleware(); - app.UseIdentityServer(); - ``` - - with - - ```csharp - app.UseAbpOpenIddictValidation(); - ``` - -## Blazor Project (Tiered Solution) - -- In the **MyApplicationWebModule.cs** update the `AddAbpOpenIdConnect` configurations: - - ```csharp - .AddAbpOpenIdConnect("oidc", options => - { - options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - - options.ClientId = configuration["AuthServer:ClientId"]; - options.ClientSecret = configuration["AuthServer:ClientSecret"]; - - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - - options.Scope.Add("roles"); // Replace "role" with "roles" - options.Scope.Add("email"); - options.Scope.Add("phone"); - options.Scope.Add("MyApplication"); - }); - ``` - - Replace **role** scope with **roles**. - -## IdentityServer - -This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future. - -- In **MyApplication.IdentityServer.csproj** replace **project references**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: - - ```csharp - typeof(AbpAccountWebIdentityServerModule), - ``` - - with - - ```csharp - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method **remove IdentityServer midware**: - - ```csharp - app.UseIdentityServer(); - ``` - -## Http.Api.Host - -- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. - -- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: - -```json -"AuthServer": { - "Authority": "https://localhost:44345", - "RequireHttpsMetadata": "false", - "SwaggerClientId": "MyApplication_Swagger" -}, -``` - -- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. - - > Note: It can be found under the *Pages* folder. - -## See Also - -* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) diff --git a/docs/en/Migration-Guides/OpenIddict-Blazor.md b/docs/en/Migration-Guides/OpenIddict-Blazor.md deleted file mode 100644 index 09e137a0a6..0000000000 --- a/docs/en/Migration-Guides/OpenIddict-Blazor.md +++ /dev/null @@ -1,189 +0,0 @@ -# OpenIddict Blazor Wasm UI Migration Guide - -## Blazor Project - -- In the **MyApplicationBlazorModule.cs** update the `ConfigureAuthentication` method: - - ```csharp - builder.Services.AddOidcAuthentication(options => - { - ... - options.UserOptions.RoleClaim = JwtClaimTypes.Role; - - options.ProviderOptions.DefaultScopes.Add("role"); - ... - }); - ``` - - Update **UserOptions** and **role scope** as below - - ```csharp - builder.Services.AddOidcAuthentication(options => - { - ... - options.UserOptions.NameClaim = OpenIddictConstants.Claims.Name; - options.UserOptions.RoleClaim = OpenIddictConstants.Claims.Role; - - options.ProviderOptions.DefaultScopes.Add("roles"); - ... - }); - ``` - -## Http.Api.Host (Non-Separated IdentityServer) - -- In the **MyApplication.HttpApi.Host.csproj** replace **project references**: - - ```csharp - - - ``` - - with - - ```csharp - - ``` - -- In the **MyApplicationHttpApiHostModule.cs** replace usings and **module dependencies**: - - ```csharp - using System.Net.Http; - using Volo.Abp.AspNetCore.Authentication.JwtBearer; - ... - typeof(AbpAspNetCoreAuthenticationJwtBearerModule), - typeof(AbpAccountWebIdentityServerModule), - ``` - - with - - ```csharp - using OpenIddict.Validation.AspNetCore; - ... - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In the **MyApplicationHostModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In the **MyApplicationHostModule.cs** `ConfigureServices` method, **replace the method call**: - - From `ConfigureAuthentication(context, configuration);` to `ConfigureAuthentication(context);` and update the method as: - - ```csharp - private void ConfigureAuthentication(ServiceConfigurationContext context) - { - context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); - } - ``` - -- In the **MyApplicationHostModule.cs** `OnApplicationInitialization` method, **replace the midware**: - - ```csharp - app.UseJwtTokenMiddleware(); - app.UseIdentityServer(); - ``` - - with - - ```csharp - app.UseAbpOpenIddictValidation(); - ``` - -- Delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. - -- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: - - ```json - "AuthServer": { - "Authority": "https://localhost:44345", - "RequireHttpsMetadata": "false", - "SwaggerClientId": "MyApplication_Swagger" - }, - ``` - -## Http.Api.Host (Separated IdentityServer) - -- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. - -- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: - - ```json - "AuthServer": { - "Authority": "https://localhost:44345", - "RequireHttpsMetadata": "false", - "SwaggerClientId": "MyApplication_Swagger" - }, - ``` - -## IdentityServer - -This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future. - -- In **MyApplication.IdentityServer.csproj** replace **project references**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In the **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: - - ```csharp - typeof(AbpAccountWebIdentityServerModule), - ``` - - with - - ```csharp - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In the **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method, **remove the midware**: - - ```csharp - app.UseIdentityServer(); - ``` - -- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. - - > Note: It can be found under the *Pages* folder. - -## See Also - -* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) diff --git a/docs/en/Migration-Guides/OpenIddict-Mvc.md b/docs/en/Migration-Guides/OpenIddict-Mvc.md deleted file mode 100644 index 8dd5ec6c94..0000000000 --- a/docs/en/Migration-Guides/OpenIddict-Mvc.md +++ /dev/null @@ -1,183 +0,0 @@ -# OpenIddict MVC/Razor UI Migration Guide - -## Web Project (Non-Tiered Solution) - -- In **MyApplication.Web.csproj** replace **project references**: - - ```csharp - - - ``` - - with - - ```csharp - - ``` - -- In **MyApplicationWebModule.cs** replace usings and **module dependencies**: - - ```csharp - using Volo.Abp.AspNetCore.Authentication.JwtBearer; - ... - typeof(AbpAccountWebIdentityServerModule), - typeof(AbpAspNetCoreAuthenticationJwtBearerModule), - ``` - - with - - ```csharp - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In **MyApplicationWebModule.cs** `ConfigureServices` method **update authentication configuration**: - - ```csharp - ConfigureAuthentication(context, configuration); - ``` - - with - - ```csharp - ConfigureAuthentication(context); - ``` - - and update the `ConfigureAuthentication` private method to: - - ```csharp - private void ConfigureAuthentication(ServiceConfigurationContext context) - { - context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); - } - ``` - - - In the **MyApplicationWebModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In **MyApplicationWebModule.cs** `OnApplicationInitialization` method **replace IdentityServer and JwtToken midwares**: - - ```csharp - app.UseJwtTokenMiddleware(); - app.UseIdentityServer(); - ``` - - with - - ```csharp - app.UseAbpOpenIddictValidation(); - ``` - - -## Web Project (Tiered Solution) - -- In the **MyApplicationWebModule.cs** update the `AddAbpOpenIdConnect` configurations: - - ```csharp - .AddAbpOpenIdConnect("oidc", options => - { - options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - - options.ClientId = configuration["AuthServer:ClientId"]; - options.ClientSecret = configuration["AuthServer:ClientSecret"]; - - options.UsePkce = true; // Add this line - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true - - options.Scope.Add("roles"); // Replace "role" with "roles" - options.Scope.Add("email"); - options.Scope.Add("phone"); - options.Scope.Add("MyApplication"); - }); - ``` - -Replace role scope to **roles** and add **UsePkce** and **SignoutScheme** options. - -## IdentityServer - -This project is renamed to **AuthServer** after v6.0.0. You can also refactor and rename your project to *AuthServer* for easier updates in the future. - -- In **MyApplication.IdentityServer.csproj** replace **project references**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: - - ```csharp - typeof(AbpAccountWebIdentityServerModule), - ``` - - with - - ```csharp - typeof(AbpAccountWebOpenIddictModule), - ``` - -- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(builder => - { - builder.AddValidation(options => - { - options.AddAudiences("MyApplication"); // Replace with your application name - options.UseLocalServer(); - options.UseAspNetCore(); - }); - }); - } - ``` - -- In **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method **remove IdentityServer midware**: - - ```csharp - app.UseIdentityServer(); - ``` - -- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. - - > Note: It can be found under the *Pages* folder. - -## Http.Api.Host - -- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. - -- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: - - ```json - "AuthServer": { - "Authority": "https://localhost:44345", - "RequireHttpsMetadata": "false", - "SwaggerClientId": "MyApplication_Swagger" - }, - ``` - -## See Also - -* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) diff --git a/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md b/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md deleted file mode 100644 index 8aff29c84d..0000000000 --- a/docs/en/Migration-Guides/OpenIddict-Step-by-Step.md +++ /dev/null @@ -1,266 +0,0 @@ -# Migrating from IdentityServer to OpenIddict Step by Step Guide - -This guide provides layer-by-layer guidance for migrating your existing application to [OpenIddict](https://github.com/openiddict/openiddict-core) from IdentityServer. ABP startup templates use `OpenIddict` OpenId provider from v6.0.0 by default and `IdentityServer` projects are renamed to `AuthServer` in tiered/separated solutions. Since OpenIddict is only available with ABP v6.0, you will need to update your existing application in order to apply OpenIddict changes. - -## History -We are not removing Identity Server packages and we will continue to release new versions of IdentityServer-related NuGet/NPM packages. That means you won't have an issue while upgrading to v6.0 when the stable version releases. We will continue to fix bugs in our packages for a while. ABP 7.0 will be based on .NET 7. If Identity Server continues to work with .NET 7, we will also continue to ship NuGet packages for our IDS integration. - -On the other hand, Identity Server ends support for the open-source Identity Server at the end of 2022. The Identity Server team has decided to move to Duende IDS and ABP will not be migrated to the commercial Duende IDS. You can see the Duende Identity Server announcement from [this link](https://blog.duendesoftware.com/posts/20220111_fair_trade). - -## Commercial Template - -If you are using a commercial template, please check [Migrating from IdentityServer to OpenIddict for the Commercial Templates](https://docs.abp.io/en/commercial/6.0/migration-guides/openIddict-step-by-step) guide. -If you are using the microservice template, please check [Migrating the Microservice Template from IdentityServer to OpenIddict](https://docs.abp.io/en/commercial/6.0/migration-guides/openIddict-microservice) guide. - -## OpenIddict Migration Steps - -Use the `abp update` command to update your existing application. See [Upgrading docs](../Upgrading.md) for more info. Apply required migrations by following the [Migration Guides](Index.md) based on your application version. - -### Domain.Shared Layer - -- In **MyApplication.Domain.Shared.csproj** replace **project reference**: - -```csharp - -``` - - with - -```csharp - -``` - -- In **MyApplicationDomainSharedModule.cs** replace usings and **module dependencies:** - -```csharp -using Volo.Abp.IdentityServer; -... -typeof(AbpIdentityServerDomainSharedModule) -``` - - with - -```csharp -using Volo.Abp.OpenIddict; -... -typeof(AbpOpenIddictDomainSharedModule) -``` - -### Domain Layer - -- In **MyApplication.Domain.csproj** replace **project references**: - -```csharp - - -``` - - with - -```csharp - - -``` - -- In **MyApplicationDomainModule.cs** replace usings and **module dependencies**: - -```csharp -using Volo.Abp.IdentityServer; -using Volo.Abp.PermissionManagement.IdentityServer; -... -typeof(AbpIdentityServerDomainModule), -typeof(AbpPermissionManagementDomainIdentityServerModule), -``` - - with - -```csharp -using Volo.Abp.OpenIddict; -using Volo.Abp.PermissionManagement.OpenIddict; -... -typeof(AbpOpenIddictDomainModule), -typeof(AbpPermissionManagementDomainOpenIddictModule), -``` - -#### OpenIddictDataSeedContributor - -- Create a folder named *OpenIddict* under the Domain project and copy the [OpenIddictDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/OpenIddict/OpenIddictDataSeedContributor.cs) under this folder. **Rename** all the `Ids2OpenId` with your project name. -- Delete *IdentityServer* folder that contains `IdentityServerDataSeedContributor.cs` which is no longer needed. - -You can also create a project with the same name and copy the `OpenIddict` folder of the new project into your project. - -### EntityFrameworkCore Layer - -If you are using MongoDB, skip this step and check the *MongoDB* layer section. - -- In **MyApplication.EntityFrameworkCore.csproj** replace **project reference**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In **MyApplicationEntityFrameworkCoreModule.cs** replace usings and **module dependencies**: - -```csharp -using Volo.Abp.IdentityServer.EntityFrameworkCore; -... -typeof(AbpIdentityServerEntityFrameworkCoreModule), -``` - - with - -```csharp -using Volo.Abp.OpenIddict.EntityFrameworkCore; -... -typeof(AbpOpenIddictEntityFrameworkCoreModule), -``` - -- In **MyApplicationDbContext.cs** replace usings and **fluent api configurations**: - - ```csharp - using Volo.Abp.IdentityServer.EntityFrameworkCore; - ... - using Volo.Abp.OpenIddict.EntityFrameworkCore; - ... - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - ... - builder.ConfigureIdentityServer(); - ``` - - with - - ```csharp - using Volo.Abp.OpenIddict.EntityFrameworkCore; - ... - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - ... - builder.ConfigureOpenIddict(); - ``` - -### MongoDB Layer - -If you are using EntityFrameworkCore, skip this step and check the *EntityFrameworkCore* layer section. - -- In **MyApplication.MongoDB.csproj** replace **project reference**: - - ```csharp - - ``` - - with - - ```csharp - - ``` - -- In **MyApplicationMongoDbModule.cs** replace usings and **module dependencies**: - -```csharp -using Volo.Abp.IdentityServer.MongoDB; -... -typeof(AbpIdentityServerMongoDbModule), -``` - - with - -```csharp -using Volo.Abp.OpenIddict.MongoDB; -... -typeof(AbpOpenIddictMongoDbModule), -``` - -### DbMigrator Project - -- In **MyApplication.DbMigrator.csproj** **add project reference**: - - ```csharp - - ``` - -for creating the host builder. - -- In `appsettings.json` **replace IdentityServer section with OpenIddict:** - - ```json - "OpenIddict": { - "Applications": { - "MyApplication_Web": { - "ClientId": "MyApplication_Web", - "ClientSecret": "1q2w3e*", - "RootUrl": "https://localhost:44384" - }, - "MyApplication_App": { - "ClientId": "MyApplication_App", - "RootUrl": "http://localhost:4200" - }, - "MyApplication_BlazorServerTiered": { - "ClientId": "MyApplication_BlazorServerTiered", - "ClientSecret": "1q2w3e*", - "RootUrl": "https://localhost:44346" - }, - "MyApplication_Swagger": { - "ClientId": "MyApplication_Swagger", - "RootUrl": "https://localhost:44391" - } - } - } - ``` - - Replace **MyApplication** with your application name. - -### Test Project - -- In **MyApplicationTestBaseModule.cs** **remove** the IdentityServer related using and PreConfigurations: - - ```csharp - using Volo.Abp.IdentityServer; - ``` - - and - - ```csharp - PreConfigure(options => - { - options.AddDeveloperSigningCredential = false; - }); - - PreConfigure(identityServerBuilder => - { - identityServerBuilder.AddDeveloperSigningCredential(false, System.Guid.NewGuid().ToString()); - }); - ``` - - from `PreConfigureServices`. - -### UI Layer - -- [Angular UI Migration](OpenIddict-Angular.md) -- [MVC/Razor UI Migration](OpenIddict-Mvc.md) -- [Blazor-Server UI Migration](OpenIddict-Blazor-Server.md) -- [Blazor-Wasm UI Migration](OpenIddict-Blazor.md) - -## Source code of samples and module - -* [Open source tiered & separate auth server application migrate Identity Server to OpenIddict](https://github.com/abpframework/abp-samples/tree/master/Ids2OpenId) -* [OpenIddict module document](https://docs.abp.io/en/abp/6.0/Modules/OpenIddict) -* [OpenIddict module source code](https://github.com/abpframework/abp/tree/rel-6.0/modules/openiddict) - -## See Also - -* [ABP Version 6.0 Migration Guide](Abp-6_0.md) diff --git a/docs/en/Migration-Guides/OpenIddict4-to-5.md b/docs/en/Migration-Guides/OpenIddict4-to-5.md deleted file mode 100644 index b8eb1ffb87..0000000000 --- a/docs/en/Migration-Guides/OpenIddict4-to-5.md +++ /dev/null @@ -1,108 +0,0 @@ -# OpenIddict 4.x to 5.x Migration Guide - -The 5.0 release of OpenIddict is a major release that introduces breaking changes. - -Check this blog [OpenIddict 5.0 general availability](https://kevinchalet.com/2023/12/18/openiddict-5-0-general-availability/) for the new features introduced in OpenIddict 5.0. - -I will show the changes you need to make to do the migration. - -> Please backup your database before doing the migration. - -## OpenIddictApplication changes - -1. The `Type(string)` of the `OpenIddictApplication` has been renamed to `ClientType(string)`. -2. The `ApplicationType(string)` has been added to the `OpenIddictApplication` entity. -3. The `JsonWebKeySet(string)` has been added to the `OpenIddictApplication` entity. -4. The `Settings(string)` has been added to the `OpenIddictApplication` entity. - -The new migration looks like this: - -````csharp -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace OpenIddict.Demo.Server.Migrations -{ - /// - public partial class openiddict5 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.RenameColumn( - name: "Type", - table: "OpenIddictApplications", - newName: "ClientType"); - - migrationBuilder.AddColumn( - name: "ApplicationType", - table: "OpenIddictApplications", - type: "nvarchar(50)", - maxLength: 50, - nullable: true); - - migrationBuilder.AddColumn( - name: "JsonWebKeySet", - table: "OpenIddictApplications", - type: "nvarchar(max)", - nullable: true); - - migrationBuilder.AddColumn( - name: "Settings", - table: "OpenIddictApplications", - type: "nvarchar(max)", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ApplicationType", - table: "OpenIddictApplications"); - - migrationBuilder.DropColumn( - name: "JsonWebKeySet", - table: "OpenIddictApplications"); - - migrationBuilder.DropColumn( - name: "Settings", - table: "OpenIddictApplications"); - - migrationBuilder.RenameColumn( - name: "ClientType", - table: "OpenIddictApplications", - newName: "Type"); - } - } -} -```` - -## OpenIddictApplicationModel changes - -1. The `Type(string)` of the `OpenIddictApplicationModel` has been renamed to `ClientType(string)`. -2. The `ApplicationType(string)` has been added to the `OpenIddictApplicationModel` entity. -3. The `JsonWebKeySet`([JsonWebKeySet](https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.jsonwebkeyset)) has been added to the `OpenIddictApplicationModel` entity. -4. The `Settings(string)` has been added to the `OpenIddictApplicationModel` entity. - -## OpenIddictApplicationDescriptor changes - -You have to change the `Type` to `ClientType` when creating a new `AbpApplicationDescriptor` or `OpenIddictApplicationDescriptor`. - -````csharp -var application = new AbpApplicationDescriptor { - ClientId = name, -- Type = type, -+ ClientType = type, - ClientSecret = secret, - ConsentType = consentType, - DisplayName = displayName, -```` - -## OpenIddict Pro module UI changes - -You can change the `ApplicationType` when creating/editing a OpenIddict's application, also set time life of the tokens for each application. - -![ropeniddict-pro-application-modal](images/openiddict-pro-application-modal.png) -![openiddict-pro-application-timelife-modal](images/openiddict-pro-application-timelife-modal.png) diff --git a/docs/en/Migration-Guides/Upgrading-Startup-Template.md b/docs/en/Migration-Guides/Upgrading-Startup-Template.md deleted file mode 100644 index 0b7bbe8aa0..0000000000 --- a/docs/en/Migration-Guides/Upgrading-Startup-Template.md +++ /dev/null @@ -1,88 +0,0 @@ -# Upgrading the Startup Template - -Sometimes we introduce new features/changes that requires to **make changes in the startup template**. We already implement the changes in the startup template for new applications. However, in some cases you need to manually make some minor changes in your existing solution. - -This guide explains a suggested way of upgrading your solution templates, using the WinMerge tool. - -> See also the [Upgrading document](../Upgrading.md) for an overall progress of upgrading. This document focuses on upgrading the startup template. - -## 1) Create Dummy Solutions - -We will create two solutions to compare the changes; - -* The first solution is with your existing version -* The second solution is the version you want to upgrade - -Assume that we are upgrading from the version **4.2.2** to version **4.3.0-rc.1**. First, create two empty folders: - -![upgrade-diff-empty-folders](../images/upgrade-diff-empty-folders.png) - -**A)** Open a command-line terminal inside the `4_2_2` folder and create a new solution with the version `4.2.2` using the ABP [CLI](../CLI.md) (install it if you haven't installed before). - -**Example:** - -````bash -abp new MyCompareApp -u blazor -v 4.2.2 -```` - -> Important: You need to create the solution with the exact configuration of your solution. If your application has Angular UI and MongoDB, you should use the same options here. - -**B)** Then open a command-line terminal inside the `4_3_0-rc1` folder and create a new solution with the version `4.3.0-rc.1` using the ABP [CLI](../CLI.md). - -**Example:** - -````bash -abp new MyCompareApp -u blazor -v 4.3.0-rc.1 -```` - -Now, we have the same application with different versions. - -## 2) Upgrade the Old Application - -If we compare two folders now, we will see unnecessary differences because of NuGet & NPM package differences. It is better to upgrade the old application to the new version before comparing them. - -Open a command-line terminal inside the `4_2_2` folder and type the following command: - -````bash -abp update -v 4.3.0-rc.1 -```` - -This will update all NuGet & NPM packages in your solution. We are ready to compare the folders to see the differences. - -## 3) Compare the Folders - -We will use the [WinMerge](https://winmerge.org/) utility for the comparison. So, please install it if it wasn't installed before. After installation, open the WinMerge application, select the the *File > Open* menu item, select the folders you want to compare: - -![winmerge-open-folders](../images/winmerge-open-folders.png) - -Now, we can click to the *Compare* button to see all the differences. Here, a screenshot from the comparison: - -![winmerge-comparison-result](../images/winmerge-comparison-result.png) - -See the *Comparison result* column or the yellow coloring to understand if two files or folder are different. It shows almost all folders are different. However, don't worry. Generally a few files will be different in a folder and a few lines will be different in a file comparison. - -For example, I select the `MyCompareApp.Blazor.csproj` to understand what's changed in this file: - -![winmerge-file-diff](../images/winmerge-file-diff.png) - -We see that; - -* `Blazorise.Bootstrap` package is upgraded from version `0.9.3-preview6` to version `0.9.3.3`. -* `Blazorise.Icons.FontAwesome` package is upgraded from version `0.9.3-preview6` to version `0.9.3.3`. -* `Volo.Abp.Identity.Blazor` package is replaced by `Volo.Abp.Identity.Blazor.WebAssembly`. -* `Volo.Abp.TenantManagement.Blazor` package is replaced by `Volo.Abp.TenantManagement.Blazor.WebAssembly`. -* `Volo.Abp.SettingManagement.Blazor.WebAssembly` package is newly added. - -In this way, we can understand all the changes. - -## 4) Apply Changes on Your Solution - -Comparison result clearly shows the necessary changes should be done on upgrade. All you need to do is to apply the same changes in your own solution. - -> **It is important you first upgrade your own solution to the new version, using the `abp update` command. Then you can apply the manual changes** - -## Notes - -* Sometimes, you may find some changes are unnecessary for your own solution. You may deleted these or already customized. In these cases, you can just ignore it. -* If you do not upgrade your solution as described in this document, your application will continue to work as long as you implement the breaking changes documented in the [migration guide](Index.md). However, you may not get benefit of some new features those require changes in your solution files. -* Most of the times, there will be a few or no differences on the startup templates. When there are important changes, we write a note to the related migration guide, so you apply them manually. \ No newline at end of file diff --git a/docs/en/Migration-Guides/images/integration-postfix-not-removed.png b/docs/en/Migration-Guides/images/integration-postfix-not-removed.png deleted file mode 100644 index 6ec19d7121..0000000000 Binary files a/docs/en/Migration-Guides/images/integration-postfix-not-removed.png and /dev/null differ diff --git a/docs/en/Migration-Guides/images/openiddict-pro-application-modal.png b/docs/en/Migration-Guides/images/openiddict-pro-application-modal.png deleted file mode 100644 index 12ac682084..0000000000 Binary files a/docs/en/Migration-Guides/images/openiddict-pro-application-modal.png and /dev/null differ diff --git a/docs/en/Migration-Guides/images/openiddict-pro-application-timelife-modal.png b/docs/en/Migration-Guides/images/openiddict-pro-application-timelife-modal.png deleted file mode 100644 index 4c0523325b..0000000000 Binary files a/docs/en/Migration-Guides/images/openiddict-pro-application-timelife-modal.png and /dev/null differ diff --git a/docs/en/Migration-Guides/images/route-4.png b/docs/en/Migration-Guides/images/route-4.png deleted file mode 100644 index 8723e7d0d6..0000000000 Binary files a/docs/en/Migration-Guides/images/route-4.png and /dev/null differ diff --git a/docs/en/Migration-Guides/images/route-before-4.png b/docs/en/Migration-Guides/images/route-before-4.png deleted file mode 100644 index b27dd18476..0000000000 Binary files a/docs/en/Migration-Guides/images/route-before-4.png and /dev/null differ diff --git a/docs/en/Module-Development-Basics.md b/docs/en/Module-Development-Basics.md deleted file mode 100644 index 785f631563..0000000000 --- a/docs/en/Module-Development-Basics.md +++ /dev/null @@ -1,194 +0,0 @@ -# Modularity - -## Introduction - -ABP Framework was designed to support to build fully modular applications and systems where every module may have entities, services, database integration, APIs, UI components and so on; - -* This document introduces the basics of the module system. -* [Module development best practice guide](Best-Practices/Index.md) explains some **best practices** to develop **re-usable application modules** based on **DDD** principles and layers. A module designed based on this guide will be **database independent** and can be deployed as a **microservice** if needed. -* [Pre-built application modules](Modules/Index.md) are **ready to use** in any kind of application. -* [Module startup template](Startup-Templates/Module.md) is a jump start way to **create a new module**. -* [ABP CLI](CLI.md) has commands to support modular development. -* All other framework features are compatible to the modularity system. - -## Module Class - -Every module should define a module class. The simplest way of defining a module class is to create a class derived from ``AbpModule`` as shown below: - -````C# -public class BlogModule : AbpModule -{ - -} -```` - -### Configuring Dependency Injection & Other Modules - -#### ConfigureServices Method - -``ConfigureServices`` is the main method to add your services to the dependency injection system and configure other modules. Example: - -> These methods have Async versions too, and if you want to make asynchronous calls inside these methods, override the asynchronous versions instead of the synchronous ones. - -````C# -public class BlogModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //... - } -} -```` - -You can register dependencies one by one as stated in Microsoft's [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection). But ABP has a **conventional dependency registration system** which automatically register all services in your assembly. See the [dependency Injection](Dependency-Injection.md) documentation for more about the dependency injection system. - -You can also configure other services and modules in this way. Example: - -````C# -public class BlogModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //Configure default connection string for the application - Configure(options => - { - options.ConnectionStrings.Default = "......"; - }); - } -} -```` - -> `ConfigureServices` method has an asynchronous version too: `ConfigureServicesAsync`. If you want to make asynchronous calls (use the `await` keyword) inside this method, override the asynchronous version instead of the synchronous one. If you override both asynchronous and synchronous versions, only the asynchronous version will be executed. - -See the [Configuration](Configuration.md) document for more about the configuration system. - -#### Pre & Post Configure Services - -``AbpModule`` class also defines ``PreConfigureServices`` and ``PostConfigureServices`` methods to override and write your code just before and just after ``ConfigureServices``. Notice that the code you have written into these methods will be executed before/after the ``ConfigureServices`` methods of all other modules. - -> These methods have asynchronous versions too. If you want to make asynchronous calls inside these methods, override the asynchronous versions instead of the synchronous ones. - -### Application Initialization - -Once all the services of all modules are configured, the application starts by initializing all modules. In this phase, you can resolve services from ``IServiceProvider`` since it's ready and available. - -#### OnApplicationInitialization Method - -You can override ``OnApplicationInitialization`` method to execute code while application is being started. - -**Example:** - -````C# -public class BlogModule : AbpModule -{ - public override void OnApplicationInitialization( - ApplicationInitializationContext context) - { - var myService = context.ServiceProvider.GetService(); - myService.DoSomething(); - } -} -```` - -`OnApplicationInitialization` method has an asynchronous version too. If you want to make asynchronous calls (use the `await` keyword) inside this method, override the asynchronous version instead of the synchronous one. - -**Example:** - -````csharp -public class BlogModule : AbpModule -{ - public override Task OnApplicationInitializationAsync( - ApplicationInitializationContext context) - { - var myService = context.ServiceProvider.GetService(); - await myService.DoSomethingAsync(); - } -} -```` - -> If you override both asynchronous and synchronous versions, only the asynchronous version will be executed. - -``OnApplicationInitialization`` is generally used by the startup module to construct the middleware pipeline for ASP.NET Core applications. - -**Example:** - -````C# -[DependsOn(typeof(AbpAspNetCoreMvcModule))] -public class AppModule : AbpModule -{ - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - var app = context.GetApplicationBuilder(); - var env = context.GetEnvironment(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseMvcWithDefaultRoute(); - } -} -```` - -You can also perform startup logic if your module requires it - -#### Pre & Post Application Initialization - -``AbpModule`` class also defines ``OnPreApplicationInitialization`` and ``OnPostApplicationInitialization`` methods to override and write your code just before and just after ``OnApplicationInitialization``. Notice that the code you have written into these methods will be executed before/after the ``OnApplicationInitialization`` methods of all other modules. - -> These methods have asynchronous versions too, and if you want to make asynchronous calls inside these methods, override the asynchronous versions instead of the synchronous ones. - -### Application Shutdown - -Lastly, you can override ``OnApplicationShutdown`` method if you want to execute some code while application is being shutdown. - -> This methods has asynchronous version too. If you want to make asynchronous calls inside this method, override the asynchronous version instead of the synchronous one. - -## Module Dependencies - -In a modular application, it's not unusual for one module to depend upon another module(s). An ABP module must declare a ``[DependsOn]`` attribute if it does have a dependency upon another module, as shown below: - -````C# -[DependsOn(typeof(AbpAspNetCoreMvcModule))] -[DependsOn(typeof(AbpAutofacModule))] -public class BlogModule -{ - //... -} -```` - -You can use multiple ``DependsOn`` attribute or pass multiple module types to a single ``DependsOn`` attribute depending on your preference. - -A depended module may depend on another module, but you only need to define your direct dependencies. ABP investigates the dependency graph for the application at startup and initializes/shutdowns modules in the correct order. - -## Additional Module Assemblies - -ABP automatically registers all the services of your module to the [dependency injection](Dependency-Injection.md) system. It finds the service types by scanning types in the assembly that defines your module class. That assembly is considered as the main assembly of your module. - -Typically, every assembly contains a separate module class definition. Then modules depend on each other using the `DependsOn` attribute as explained in the previous section. However, in some rare cases, your module may consist of multiple assemblies, and only one of them defines a module class, and you want to make the other assemblies parts of your module. In that case, you can use the `AdditionalAssembly` attribute as shown below: - -````csharp -[DependsOn(...)] // Your module dependencies as you normally do -[AdditionalAssembly(typeof(BlogService))] // A type in the target assembly -public class BlogModule -{ - //... -} -```` - -In this example, we assume that the `BlogService` class is inside one assembly (`csproj`) and the `BlogModule` class is inside another assembly (`csproj`). With the `AdditionalAssembly` definition, ABP will load the assembly containing the `BlogService` class as a part of the blog module. - -Notice that `BlogService` is only an arbitrary selected type in the target assembly. It is just used to indicate the related assembly. You could use any type in the assembly. - -> WARNING: If you need to use the `AdditionalAssembly`, be sure that you don't design your system in a wrong way. With this example above, the `BlogService` class' assembly should normally have its own module class and the `BlogModule` should depend on it using the `DependsOn` attribute. Do not use the `AdditionalAssembly` attribute when you can already use the `DependsOn` attribute. - -## Framework Modules vs Application Modules - -There are **two types of modules.** They don't have any structural difference but categorized by functionality and purpose: - -- **Framework modules**: These are **core modules of the framework** like caching, emailing, theming, security, serialization, validation, EF Core integration, MongoDB integration... etc. They do not have application/business functionalities but makes your daily development easier by providing common infrastructure, integration and abstractions. -- **Application modules**: These modules implement **specific application/business functionalities** like blogging, document management, identity management, tenant management... etc. They generally have their own entities, services, APIs and UI components. See [pre-built application modules](Modules/Index.md). - -## See Also -* [Video tutorial](https://abp.io/video-courses/essentials/modularity) diff --git a/docs/en/Module-Entity-Extensions.md b/docs/en/Module-Entity-Extensions.md deleted file mode 100644 index 93c79120cd..0000000000 --- a/docs/en/Module-Entity-Extensions.md +++ /dev/null @@ -1,512 +0,0 @@ -# Module Entity Extensions - -Module entity extension system is a **high level** extension system that allows you to **define new properties** for existing entities of the depended modules. It automatically **adds properties to the entity, database, HTTP API and the user interface** in a single point. - -> The module must be developed the *Module Entity Extensions* system in mind. All the **official modules** supports this system wherever possible. - -## Quick Example - -Open the `YourProjectNameModuleExtensionConfigurator` class inside the `Domain.Shared` project of your solution and change the `ConfigureExtraProperties`method as shown below to add a `SocialSecurityNumber` property to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). - -````csharp -public static void ConfigureExtraProperties() -{ - OneTimeRunner.Run(() => - { - ObjectExtensionManager.Instance.Modules() - .ConfigureIdentity(identity => - { - identity.ConfigureUser(user => - { - user.AddOrUpdateProperty( //property type: string - "SocialSecurityNumber", //property name - property => - { - //validation rules - property.Attributes.Add(new RequiredAttribute()); - property.Attributes.Add( - new StringLengthAttribute(64) { - MinimumLength = 4 - } - ); - - //...other configurations for this property - } - ); - }); - }); - }); -} -```` - ->This method is called inside the `YourProjectNameDomainSharedModule` at the beginning of the application. `OneTimeRunner` is a utility class that guarantees to execute this code only one time per application, since multiple calls are unnecessary. - -* `ObjectExtensionManager.Instance.Modules()` is the starting point to configure a module. `ConfigureIdentity(...)` method is used to configure the entities of the Identity Module. -* `identity.ConfigureUser(...)` is used to configure the user entity of the identity module. Not all entities are designed to be extensible (since it is not needed). Use the intellisense to discover the extensible modules and entities. -* `user.AddOrUpdateProperty(...)` is used to add a new property to the user entity with the `string` type (`AddOrUpdateProperty` method can be called multiple times for the same property of the same entity. Each call can configure the options of the same property, but only one property is added to the entity with the same property name). You can call this method with different property names to add more properties. -* `SocialSecurityNumber` is the name of the new property. -* `AddOrUpdateProperty` gets a second argument (the `property =>` lambda expression) to configure additional options for the new property. - * We can add data annotation attributes like shown here, just like adding a data annotation attribute to a class property. - -#### Create & Update Forms - -Once you define a property, it appears in the create and update forms of the related entity: - -![add-new-property-to-user-form](images/add-new-property-to-user-form.png) - -`SocialSecurityNumber` field comes into the form. Next sections will explain the localization and the validation for this new property. - -### Data Table - -New properties also appear in the data table of the related page: - -![add-new-property-to-user-form](images/add-new-property-to-user-table.png) - -`SocialSecurityNumber` column comes into the table. Next sections will explain the option to hide this column from the data table. - -## Property Options - -There are some options that you can configure while defining a new property. - -### Display Name - -You probably want to set a different (human readable) display name for the property that is shown on the user interface. - -#### Don't Want to Localize? - -If your application is not localized, you can directly set the `DisplayName` for the property to a `FixedLocalizableString` object. Example: - -````csharp -property => -{ - property.DisplayName = new FixedLocalizableString("Social security no"); -} -```` - -#### Localizing the Display Name - -If you want to localize the display name, you have two options. - -##### Localize by Convention - -Instead of setting the `property.DisplayName`, you can directly open your localization file (like `en.json`) and add the following entry to the `texts` section: - -````json -"SocialSecurityNumber": "Social security no" -```` - -Define the same `SocialSecurityNumber` key (the property name you've defined before) in your localization file for each language you support. That's all! - -In some cases, the localization key may conflict with other keys in your localization files. In such cases, you can use the `DisplayName:` prefix for display names in the localization file (`DisplayName:SocialSecurityNumber` as the localization key for this example). Extension system looks for prefixed version first, then fallbacks to the non prefixed name (it then fallbacks to the property name if you haven't localized it). - -> This approach is recommended since it is simple and suitable for most scenarios. - -##### Localize using the `DisplayName` Property - -If you want to specify the localization key or the localization resource, you can still set the `DisplayName` option: - -````csharp -property => -{ - property.DisplayName = - LocalizableString.Create( - "UserSocialSecurityNumberDisplayName" - ); -} -```` - -* `MyProjectNameResource` is the localization resource and `UserSocialSecurityNumberDisplayName` is the localization key in the localization resource. - -> See [the localization document](Localization.md) if you want to learn more about the localization system. - -#### Default Value - -A default value is automatically set for the new property, which is the natural default value for the property type, like `null` for `string`, `false` for `bool` or `0` for `int`. - -There are two ways to override the default value: - -##### DefaultValue Option - -`DefaultValue` option can be set to any value: - -````csharp -property => -{ - property.DefaultValue = 42; -} -```` - -##### DefaultValueFactory Options - -`DefaultValueFactory` can be set to a function that returns the default value: - -````csharp -property => -{ - property.DefaultValueFactory = () => DateTime.Now; -} -```` - -`options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` . - -> Tip: Use `DefaultValueFactory` option only if the default value may change over the time (like `DateTime.Now` in this example). If it is a constant value, then use the `DefaultValue` option. - -### Validation - -Entity extension system allows you to define validation for extension properties in a few ways. - -#### Data Annotation Attributes - -`Attributes` is a list of attributes associated to this property. The example code below adds two [data annotation validation attributes](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation) to the property: - -````csharp -property => -{ - property.Attributes.Add(new RequiredAttribute()); - property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); -} -```` - -When you run the application, you see that the validation works out of the box: - -![add-new-propert-to-user-form](images/add-new-property-to-user-form-validation-error.png) - -Since we've added the `RequiredAttribute`, it doesn't allow to left it blank. The validation system works; - -* On the user interface (with automatic localization). -* On the HTTP API. Even if you directly perform an HTTP request, you get validation errors with a proper HTTP status code. -* On the `SetProperty(...)` method on the entity (see [the document](Entities.md) if you wonder what is the `SetProperty()` method). - -So, it automatically makes a full stack validation. - -> See the [ASP.NET Core MVC Validation document](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation) to learn more about the attribute based validation. - -##### Default Validation Attributes - -There are some attributes **automatically added** when you create certain type of properties; - -* `RequiredAttribute` is added for **non nullable** primitive property types (e.g. `int`, `bool`, `DateTime`...) and `enum` types. If you want to allow nulls, make the property nullable (e.g. `int?`). -* `EnumDataTypeAttribute` is added for **enum types**, to prevent to set invalid enum values. - -Use `property.Attributes.Clear();` if you don't want these attributes. - -#### Validation Actions - -Validation actions allows you to execute a custom code to perform the validation. The example below checks if the `SocialSecurityNumber` starts with `B` and adds a validation error if so: - -````csharp -property => -{ - property.Attributes.Add(new RequiredAttribute()); - property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); - - property.Validators.Add(context => - { - if (((string) context.Value).StartsWith("B")) - { - context.ValidationErrors.Add( - new ValidationResult( - "Social security number can not start with the letter 'B', sorry!", - new[] {"extraProperties.SocialSecurityNumber"} - ) - ); - } - }); - -} -```` - -Using a `RegularExpressionAttribute` might be better in this case, but this is just an example. Anyway, if you enter a value starts with the letter `B` you get the following error **while saving the form**: - -![add-new-propert-to-user-form](images/add-new-property-to-user-form-validation-error-custom.png) - -##### The Context Object - -The `context` object has useful properties that can be used in your custom validation action. For example, you can use the `context.ServiceProvider` to resolve services from the [dependency injection system](Dependency-Injection.md). The example below gets the localizer and adds a localized error message: - -````csharp -if (((string) context.Value).StartsWith("B")) -{ - var localizer = context.ServiceProvider - .GetRequiredService>(); - - context.ValidationErrors.Add( - new ValidationResult( - localizer["SocialSecurityNumberCanNotStartWithB"], - new[] {"extraProperties.SocialSecurityNumber"} - ) - ); -} -```` - ->`context.ServiceProvider` is nullable! It can be `null` only if you use the `SetProperty(...)` method on the object. Because DI system is not available on this time. While this is a rare case, you should perform a fallback logic when `context.ServiceProvider` is `null`. For this example, you would add a non-localized error message. This is not a problem since setting an invalid value to a property generally is a programmer mistake and you mostly don't need to localization in this case. In any way, you would not be able to use localization even in a regular property setter. But, if you are serious about localization, you can throw a business exception (see the [exception handling document](https://docs.abp.io/en/abp/latest/Exception-Handling) to learn how to localize a business exception). - -### UI Visibility - -When you define a property, it appears on the data table, create and edit forms on the related UI page. However, you can control each one individually. Example: - -````csharp -property => -{ - property.UI.OnTable.IsVisible = false; - //...other configurations -} -```` - -Use `property.UI.OnCreateForm` and `property.UI.OnEditForm` to control forms too. If a property is required, but not added to the create form, you definitely get a validation exception, so use this option carefully. But a required property may not be in the edit form if that's your requirement. - -### UI Order - -When you define a property, it appears on the data table, create and edit forms on the related UI page. However, you can control its order. Example: - -````csharp -property => -{ - property.UI.Order = 1; - //...other configurations -} -```` - -Use `property.UI.OnCreateForm` and `property.UI.OnEditForm` to control forms too. If a property is required, but not added to the create form, you definitely get a validation exception, so use this option carefully. But a required property may not be in the edit form if that's your requirement. - -### HTTP API Availability - -Even if you disable a property on UI, it can be still available through the HTTP API. By default, a property is available on all APIs. - -Use the `property.Api` options to make a property unavailable in some API endpoints. - -````csharp -property => -{ - property.Api.OnUpdate.IsAvailable = false; -} -```` - -In this example, Update HTTP API will not allow to set a new value to this property. In this case, you also want to disable this property on the edit form: - -````csharp -property => -{ - property.Api.OnUpdate.IsAvailable = false; - property.UI.OnEditForm.IsVisible = false; -} -```` - -In addition to the `property.Api.OnUpdate`, you can set `property.Api.OnCreate` and `property.Api.OnGet` for a fine control the API endpoint. - -## Special Types - -### Enum - -Module extension system naturally supports the `enum` types. - -An example enum type: - -````csharp -public enum UserType -{ - Regular, - Moderator, - SuperUser -} -```` - -You can add enum properties just like others: - -````csharp -user.AddOrUpdateProperty("Type"); -```` - -An enum properties is shown as combobox (select) in the create/edit forms: - -![add-new-property-enum](images/add-new-property-enum.png) - -#### Localization - -Enum member name is shown on the table and forms by default. If you want to localize it, just create a new entry on your [localization](https://docs.abp.io/en/abp/latest/Localization) file: - -````json -"Enum:UserType.0": "Super user" -```` - -One of the following names can be used as the localization key: - -* `Enum:UserType.0` -* `Enum:UserType.SuperUser` -* `UserType.0` -* `UserType.SuperUser` -* `SuperUser` - -Localization system searches for the key with the given order. Localized text are used on the table and the create/edit forms. - -### Navigation Properties / Foreign Keys - -It is supported to add an extension property to an entity that is Id of another entity (foreign key). - -#### Example: Associate a department to a user - -````csharp -ObjectExtensionManager.Instance.Modules() - .ConfigureIdentity(identity => - { - identity.ConfigureUser(user => - { - user.AddOrUpdateProperty( - "DepartmentId", - property => - { - property.UI.Lookup.Url = "/api/departments"; - property.UI.Lookup.DisplayPropertyName = "name"; - } - ); - }); - }); -```` - -`UI.Lookup.Url` option takes a URL to get list of departments to select on edit/create forms. This endpoint can be a typical controller, an [auto API controller](API/Auto-API-Controllers.md) or any type of endpoint that returns a proper JSON response. - -An example implementation that returns a fixed list of departments (in real life, you get the list from a data source): - -````csharp -[Route("api/departments")] -public class DepartmentController : AbpController -{ - [HttpGet] - public async Task> GetAsync() - { - return new ListResultDto( - new[] - { - new DepartmentDto - { - Id = Guid.Parse("6267f0df-870f-4173-be44-d74b4b56d2bd"), - Name = "Human Resources" - }, - new DepartmentDto - { - Id = Guid.Parse("21c7b61f-330c-489e-8b8c-80e0a78a5cc5"), - Name = "Production" - } - } - ); - } -} -```` - -This API returns such a JSON response: - -````json -{ - "items": [{ - "id": "6267f0df-870f-4173-be44-d74b4b56d2bd", - "name": "Human Resources" - }, { - "id": "21c7b61f-330c-489e-8b8c-80e0a78a5cc5", - "name": "Production" - }] -} -```` - -ABP can now show an auto-complete select component to pick the department while creating or editing a user: - -![extension-navigation-property-form](images/extension-navigation-property-form.png) - -And shows the department name on the data table: - -![extension-navigation-property-form](images/extension-navigation-property-table.png) - -#### Lookup Options - -`UI.Lookup` has the following options to customize how to read the response returned from the `Url`: - -* `Url`: The endpoint to get the list of target entities. This is used on edit and create forms. -* `DisplayPropertyName`: The property in the JSON response to read the display name of the target entity to show on the UI. Default: `text`. -* `ValuePropertyName`: The property in the JSON response to read the Id of the target entity. Default: `id`. -* `FilterParamName`: ABP allows to search/filter the entity list on edit/create forms. This is especially useful if the target list contains a lot of items. In this case, you can return a limited list (top 100, for example) and allow user to search on the list. ABP sends filter text to the server (as a simple query string) with the name of this option. Default: `filter`. -* `ResultListPropertyName`: By default, returned JSON result should contain the entity list in an `items` array. You can change the name of this field. Default: `items`. - -#### Lookup Properties: How Display Name Works? - -You may wonder how ABP shows the department name on the data table above. - -It is easy to understand how to fill the dropdown on edit and create forms: ABP makes an AJAX request to the given URL. It re-requests whenever user types to filter the items. - -However, for the data table, multiple items are shown on the UI and performing a separate AJAX call to get display name of the department for each row would not be so efficient. - -Instead, the display name of the foreign entity is also saved as an extra property of the entity (see *Extra Properties* section of the [Entities](Entities.md) document) in addition to Id of the foreign entity. If you check the database, you can see the `DepartmentId_Text` in the `ExtraProperties` field in the database table: - -````json -{"DepartmentId":"21c7b61f-330c-489e-8b8c-80e0a78a5cc5","DepartmentId_Text":"Production"} -```` - -So, this is a type of *data duplication*. If your target entity's name changes in the database later, there is no automatic synchronization system. The system works as expected, but you see the old name on the data tables. If that's a problem for you, you should care yourself to update this information when display name of your entity changes. - -## Database Mapping - -For relational databases, all extension property values are stored in a single field in the table: - -![add-new-propert-to-user-database-extra-properties](images/add-new-propert-to-user-database-extra-properties.png) - -`ExtraProperties` field stores the properties as a JSON object. While that's fine for some scenarios, you may want to create a dedicated field for your new property. Fortunately, it is very easy to configure. - -If you are using the Entity Framework Core database provider, you can configure the database mapping as shown below: - -````csharp -ObjectExtensionManager.Instance - .MapEfCoreProperty( - "SocialSecurityNumber", - (entityBuilder, propertyBuilder) => - { - propertyBuilder.HasMaxLength(64); - } - ); -```` - -Write this inside the `YourProjectNameEfCoreEntityExtensionMappings` class in your `.EntityFrameworkCore` project. Then you need to use the standard `Add-Migration` and `Update-Database` commands to create a new database migration and apply the change to your database. - -Add-Migration create a new migration as shown below: - -````csharp -public partial class Added_SocialSecurityNumber_To_IdentityUser : Migration -{ - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "SocialSecurityNumber", - table: "AbpUsers", - maxLength: 128, - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "SocialSecurityNumber", - table: "AbpUsers"); - } -} -```` - -Once you update your database, you will see that the `AbpUsers` table has the new property as a standard table field: - -![add-new-propert-to-user-database-extra-properties](images/add-new-propert-to-user-database-field.png) - -> If you first created a property without a database table field, then you later needed to move this property to a database table field, it is suggested to execute an SQL command in your migration to copy the old values to the new field. -> -> However, if you don't make it, the ABP Framework seamlessly manages it. It uses the new database field, but fallbacks to the `ExtraProperties` field if it is null. When you save the entity, it moves the value to the new field. - -See the [Extending Entities](Customizing-Application-Modules-Extending-Entities.md) document for more. - -## More - -See the [Customizing the Modules](Customizing-Application-Modules-Guide.md) guide for an overall index for all the extensibility options. - -Here, a few things you can do: - -* You can create a second entity that maps to the same database table with the extra property as a standard class property (if you've defined the EF Core mapping). For the example above, you can add a `public string SocialSecurityNumber {get; set;}` property to the `AppUser` entity in your application, since the `AppUser` entity is mapped to the same `AbpUser` table. Do this only if you need it, since it brings more complexity to your application. -* You can override a domain or application service to perform custom logics with your new property. -* You can low level control how to add/render a field in the data table on the UI. - -## See Also - -* [Angular UI Extensions](UI/Angular/Extensions-Overall.md) diff --git a/docs/en/Modules/Account.md b/docs/en/Modules/Account.md deleted file mode 100644 index 2ae77d532d..0000000000 --- a/docs/en/Modules/Account.md +++ /dev/null @@ -1,80 +0,0 @@ -# Account Module - -Account module implements the basic authentication features like **login**, **register**, **forgot password** and **account management**. - -This module is based on [Microsoft's Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity) and the [Identity Module](Identity.md). It has [IdentityServer](https://github.com/IdentityServer) integration (based on the [IdentityServer Module](IdentityServer.md)) and [OpenIddict](https://github.com/openiddict) integration (based on the [OpenIddict Module](OpenIddict.md)) to provide **single sign-on**, access control and other advanced authentication features. - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/account). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## User Interface - -This section introduces the main pages provided by this module. - -### Login - -`/Account/Login` page provides the login functionality. - -![account-module-login](../images/account-module-login.png) - -Social/external login buttons becomes visible if you setup it. See the *Social/External Logins* section below. Register and Forgot password and links redirect to the pages explained in the next sections. - -### Register - -`/Account/Register` page provides the new user registration functionality. - -![account-module-register](../images/account-module-register.png) - -### Forgot Password & Reset Password - -`/Account/ForgotPassword` page provides a way of sending password reset link to user's email address. The user then clicks to the link and determines a new password. - -![account-module-forgot-password](../images/account-module-forgot-password.png) - -### Account Management - -`/Account/Manage` page is used to change password and personal information of the user. - -![account-module-manage-account](../images/account-module-manage-account.png) - -## OpenIddict Integration - -[Volo.Abp.Account.Web.OpenIddict](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) package provides integration for the [OpenIddict](https://github.com/openiddict). This package comes as installed with the [application startup template](../Startup-Templates/Application.md). See the [OpenIddict Module](OpenIddict.md) documentation. - -## IdentityServer Integration - -[Volo.Abp.Account.Web.IdentityServer](https://www.nuget.org/packages/Volo.Abp.Account.Web.IdentityServer) package provides integration for the [IdentityServer](https://github.com/IdentityServer). This package comes as installed with the [application startup template](../Startup-Templates/Application.md). See the [IdentityServer Module](IdentityServer.md) documentation. - -## Social/External Logins - -The Account Module has already configured to handle social or external logins out of the box. You can follow the ASP.NET Core documentation to add a social/external login provider to your application. - -### Example: Facebook Authentication - -Follow the [ASP.NET Core Facebook integration document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to support the Facebook login for your application. - -#### Add the NuGet Package - -Add the [Microsoft.AspNetCore.Authentication.Facebook](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Facebook) package to your project. Based on your architecture, this can be `.Web`, `.IdentityServer` (for tiered setup) or `.Host` project. - -#### Configure the Provider - -Use the `.AddFacebook(...)` extension method in the `ConfigureServices` method of your [module](../Module-Development-Basics.md), to configure the client: - -````csharp -context.Services.AddAuthentication() - .AddFacebook(facebook => - { - facebook.AppId = "..."; - facebook.AppSecret = "..."; - facebook.Scope.Add("email"); - facebook.Scope.Add("public_profile"); - }); -```` - -> It would be a better practice to use the `appsettings.json` or the ASP.NET Core User Secrets system to store your credentials, instead of a hard-coded value like that. Follow the [Microsoft's document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to learn the user secrets usage. diff --git a/docs/en/Modules/Audit-Logging.md b/docs/en/Modules/Audit-Logging.md deleted file mode 100644 index 93a50c8387..0000000000 --- a/docs/en/Modules/Audit-Logging.md +++ /dev/null @@ -1,60 +0,0 @@ -# Audit Logging Module - -The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. - -> This document covers only the audit logging module which persists audit logs to a database. See [the audit logging](../Audit-Logging.md) document for more about the audit logging system. - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/audit-logging). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## Internals - -### Domain Layer - -#### Aggregates - -- `AuditLog` (aggregate root): Represents an audit log record in the system. - - `EntityChange` (collection): Changed entities of audit log. - - `AuditLogAction` (collection): Executed actions of audit log. - -#### Repositories - -Following custom repositories are defined for this module: - -- `IAuditLogRepository` - -### Database providers - -#### Common - -##### Table / collection prefix & schema - -All tables/collections use the `Abp` prefix by default. Set static properties on the `AbpAuditLoggingDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection string - -This module uses `AbpAuditLogging` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- **AbpAuditLogs** - - AbpAuditLogActions - - AbpEntityChanges - - AbpEntityPropertyChanges - -#### MongoDB - -##### Collections - -- **AbpAuditLogs** - -## See Also - -* [Audit logging system](../Audit-Logging.md) \ No newline at end of file diff --git a/docs/en/Modules/Background-Jobs.md b/docs/en/Modules/Background-Jobs.md deleted file mode 100644 index 5fd57d812c..0000000000 --- a/docs/en/Modules/Background-Jobs.md +++ /dev/null @@ -1,55 +0,0 @@ -# Background Jobs Module - -The Background Jobs module implements the `IBackgroundJobStore` interface and makes possible to use the default background job manager of the ABP Framework. If you don't want to use this module, then you should implement the `IBackgroundJobStore` interface yourself. - -> This document covers only the background jobs module which persists background jobs to a database. See [the background jobs](../Background-Jobs.md) document for more about the background jobs system. - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/background-jobs). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## Internals - -### Domain Layer - -#### Aggregates - -- `BackgroundJobRecord` (aggregate root): Represents a background job record. - -#### Repositories - -Following custom repositories are defined for this module: - -- `IBackgroundJobRepository` - -### Database providers - -#### Common - -##### Table / collection prefix & schema - -All tables/collections use the `Abp` prefix by default. Set static properties on the `BackgroundJobsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection string - -This module uses `AbpBackgroundJobs` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- **AbpBackgroundJobs** - -#### MongoDB - -##### Collections - -- **AbpBackgroundJobs** - -## See Also - -* [Background job system](../Background-Jobs.md) \ No newline at end of file diff --git a/docs/en/Modules/Cms-Kit/Blogging.md b/docs/en/Modules/Cms-Kit/Blogging.md deleted file mode 100644 index eb6619a4bc..0000000000 --- a/docs/en/Modules/Cms-Kit/Blogging.md +++ /dev/null @@ -1,134 +0,0 @@ -# CMS Kit: Blogging - -The blogging feature provides the necessary UI to manage and render blogs and blog posts. - -## Enabling the Blogging Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## User Interface - -### Menu Items - -The following menu items are added by the blogging feature to the admin application: - -* **Blogs**: Blog management page. -* **Blog Posts**: Blog post management page. - -## Pages - -### Blogs - -Blogs page is used to create and manage blogs in your system. - -![blogs-page](../../images/cmskit-module-blogs-page.png) - -A screenshot from the new blog creation modal: - -![blogs-edit](../../images/cmskit-module-blogs-edit.png) - -**Slug** is the URL part of the blog. For this example, the root URL of the blog becomes `your-domain.com/blogs/technical-blog/`. - -- You can change the default slug by using `CmsBlogsWebConsts.BlogRoutePrefix` constant. For example, if you set it to `foo`, the root URL of the blog becomes `your-domain.com/foo/technical-blog/`. - - ```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - CmsBlogsWebConsts.BlogsRoutePrefix = "foo"; - } - ``` - -#### Blog Features - -Blog feature uses some of the other CMS Kit features. You can enable or disable the features by clicking the features action for a blog. - -![blogs-feature-action](../../images/cmskit-module-blogs-feature-action.png) - -You can select/deselect the desired features for blog posts. - -![features-dialog](../../images/cmskit-module-features-dialog-2.png) - -##### Quick Navigation Bar In Blog Post -If you enable "Quick navigation bar in blog posts", it will enabled scroll index as seen below. - -![scroll-index](../../images/cmskit-module-features-scroll-index.png) - -### Blog Post Management - -When you create blogs, you can manage blog posts on this page. - -![blog-posts-page](../../images/cmskit-module-blog-posts-page.png) - -You can create and edit an existing blog post on this page. If you enable specific features such as tags, you can set tags for the blog post on this page. - -![blog-post-edit](../../images/cmskit-module-blog-post-edit.png) - -## Internals - -### Domain Layer - -#### Aggregates - -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. - -- `Blog` _(aggregate root)_: Presents blogs of application. -- `BlogPost`_(aggregate root)_: Presents blog posts in blogs. -- `BlogFeature`:_(aggregate root)_: Presents blog features enabled/disabled state. Such as reactions, ratings, comments, etc. - -#### Repositories - -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. The following repositories are defined for this feature: - -- `IBlogRepository` -- `IBlogPostRepository` -- `IBlogFeatureRepository` - -#### Domain services - -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. - -- `BlogManager` -- `BlogPostManager` -- `BlogFeatureManager` - -### Application layer - -#### Application Services - -##### Common - -- `BlogFeatureAppService` _(implements `IBlogFeatureAppService`)_ - -##### Admin - -- `BlogAdminAppService` _(implements `IBlogAdminAppService`)_ -- `BlogFeatureAdminAppService` _(implements `IBlogFeatureAdminAppService`)_ -- `BlogPostAdminAppService` _(implements `IBlogPostAdminAppService`)_ - -##### Public - -- `BlogPostPublicAppService` _(implements `IBlogPostPublicAppService`)_ - -### Database providers - -#### Entity Framework Core - -##### Tables - -- CmsBlogs -- CmsBlogPosts -- CmsBlogFeatures - -#### MongoDB - -##### Collections - -- CmsBlogs -- CmsBlogPosts -- CmsBlogFeatures - -## Entity Extensions - -Check the ["Entity Extensions" section of the CMS Kit Module documentation](Index.md#entity-extensions) to see how to extend entities of the Blogging Feature of the CMS Kit module. \ No newline at end of file diff --git a/docs/en/Modules/Cms-Kit/Comments.md b/docs/en/Modules/Cms-Kit/Comments.md deleted file mode 100644 index ddd39fc675..0000000000 --- a/docs/en/Modules/Cms-Kit/Comments.md +++ /dev/null @@ -1,145 +0,0 @@ -# CMS Kit: Comments - -CMS kit provides a **comment** system to add the comment feature to any kind of resource, like blog posts, products, etc. - -## Enabling the Comment Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## Options - -The comment system provides a mechanism to group comment definitions by entity types. For example, if you want to use the comment system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product`, and add comments under these entity types. - -`CmsKitCommentOptions` can be configured in the domain layer, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: - -```csharp -Configure(options => -{ - options.EntityTypes.Add(new CommentEntityTypeDefinition("Product")); - options.IsRecaptchaEnabled = true; //false by default - options.AllowedExternalUrls = new Dictionary> - { - { - "Product", - new List - { - "https://abp.io/" - } - } - }; -}); -``` - -> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above. - -`CmsKitCommentOptions` properties: - -- `EntityTypes`: List of defined entity types(`CmsKitCommentOptions`) in the comment system. -- `IsRecaptchaEnabled`: This flag enables or disables the reCaptcha for the comment system. You can set it as **true** if you want to use reCaptcha in your comment system. -- `AllowedExternalUrls`: Indicates the allowed external URLs by entity types, which can be included in a comment. If it's specified for a certain entity type, then only the specified external URLs are allowed in the comments. - -`CommentEntityTypeDefinition` properties: - -- `EntityType`: Name of the entity type. - -## The Comments Widget - -The comment system provides a commenting [widget](../../UI/AspNetCore/Widgets.md) to allow users to send comments to resources on public websites. You can simply place the widget on a page like below. - -```csharp -@await Component.InvokeAsync(typeof(CommentingViewComponent), new -{ - entityType = "Product", - entityId = "...", - isReadOnly = false, - referralLinks = new [] {"nofollow"} -}) -``` - -`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. `referralLinks` is an optional parameter. You can use this parameter to add values (such as "nofollow", "noreferrer", or any other values) to the [rel attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel) of links. - -## User Interface - -### Menu Items - -The following menu items are added by the commenting feature to the admin application: - -* **Comments**: Opens the comment management page. - -### Pages - -#### Comment Management - -You can view and manage comments on this page. - -![comment-page](../../images/cmskit-module-comment-page.png) - -You can also view and manage replies on this page. - -![comments-detail](../../images/cmskit-module-comments-detail.png) - -## Internals - -### Domain Layer - -#### Aggregates - -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. - -##### Comment - -A comment represents a written comment from a user. - -- `Comment` (aggregate root): Represents a written comment in the system. - -#### Repositories - -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. - -The following custom repositories are defined for this feature: - -- `ICommentRepository` - -#### Domain services - -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. - -##### Comment Manager - -`CommentManager` is used to perform some operations for the `Comment` aggregate root. - -### Application layer - -#### Application services - -- `CommentAdminAppService` (implements `ICommentAdminAppService`): Implements the use cases of the comment management system, like listing or removing comments etc. -- `CommentPublicAppService` (implements `ICommentPublicAppService`): Implements the use cases of the comment management on the public websites, like listing comments, adding comments etc. - -### Database providers - -#### Common - -##### Table / collection prefix & schema - -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection string - -This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- CmsComments - -#### MongoDB - -##### Collections - -- **CmsComments** - diff --git a/docs/en/Modules/Cms-Kit/Dynamic-Widget.md b/docs/en/Modules/Cms-Kit/Dynamic-Widget.md deleted file mode 100644 index 4ca764b7ea..0000000000 --- a/docs/en/Modules/Cms-Kit/Dynamic-Widget.md +++ /dev/null @@ -1,137 +0,0 @@ -# Dynamic Widget - -CMS kit provides a dynamic [widget](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Widgets) used to render the components previously developed by the software in the content of the pages and blog posts. Its means, that in static content you can use dynamic content. We will mention how you can do it. You have two choices to define the widget in the system: Writing and UI. - -### Adding the widget -Firstly we will show how to use the widget system via writing manually in the page and blog post contents. - -Let's define the view component - -```csharp -[Widget] -[ViewComponent(Name = "CmsToday")] -public class TodayViewComponent : AbpViewComponent -{ - public IViewComponentResult Invoke() - { - return View("~/ViewComponents/Today.cshtml", - new TodayViewComponent()); - } -} -``` - -```html -@model Volo.CmsKit.ViewComponents.TodayViewComponent - -

    Welcome Today Component

    -

    @DateTime.Now.ToString()

    - -``` - -Now configuration time on YourModule.cs -```csharp -Configure(options => - { - options.AddWidget("Today","CmsToday"); - }); -``` - -Now you're ready to add your widget by writing. -[Widget Type="Today"] - -After completing the above steps, you can see the output at the right of the below screenshot. -![cmskit-without-parameter.png](../../images/cmskit-without-parameter.png) - -### Adding by using UI -Now we will mention the second option, using UI. -Once writing these definitions can make some mistakes hence we added a new feature to use the widget system easily. To the right of the editor, you will see the customized `W` button to add a dynamic widget like the below image. Don't forget please this is design mode and you need to view your page in view mode after saving. Also `Preview` tab on the editor will be ready to check your output easily for widget configurations in the next features. - -![cms-kit-page-editor](../../images/cms-kit-page-editor.png) - -### Adding by using UI with parameters -Let's improve the above example by adding a new parameter named format. Via this feature, we can use the widget system with many different scenarios but not prolong the document. Also, these examples can be expandable with dependency injection and getting values from the database, but we will use a basic example. We will add the format parameter to customize the date. - -```csharp -[Widget] -[ViewComponent(Name = "CmsToday")] -public class TodayViewComponent : AbpViewComponent -{ - public string Format { get; set; } - - public IViewComponentResult Invoke(string format) - { - return View("~/ViewComponents/Today.cshtml", - new TodayViewComponent() { Format = format }); - } -} -``` - -```html -@model Volo.CmsKit.ViewComponents.TodayViewComponent - -

    Welcome Today Component

    -

    @DateTime.Now.ToString(Format)

    - -``` - -Let's define the format component. -```csharp -[Widget] -[ViewComponent(Name = "Format")] -public class FormatViewComponent : AbpViewComponent -{ - public IViewComponentResult Invoke() - { - return View("~/ViewComponents/Format.cshtml", - new FormatViewModel()); - } -} - -public class FormatViewModel -{ - [DisplayName("Format your date in the component")] - public string Format { get; set; } -} -``` -> Important Note: To get properties properly you should set the `name` property on the razor page or you may use the ABP component. ABP handles that automatically. - -```html -@using Volo.CmsKit.ViewComponents -@model FormatViewModel - -
    - -
    -``` - -```csharp -Configure(options => - { - options.AddWidget("Today", "CmsToday", "Format"); - }); -``` - -![cmskit-module-editor-parameter](../../images/cmskit-module-editor-parameter.png) - -In this image, after choosing your widget (on the other case, it changes automatically up to your configuration, mine is `Today`. Its parameter name `parameterWidgetName` and its value is `Format`) you will see the next widget. Enter input values or choose them and click `Add`. You will see the underlined output in the editor. Right of the image, also you can see its previewed output. - -You can edit this output manually if do any wrong coding for that (wrong value or typo) you won't see the widget, even so, your page will be viewed successfully. - -## Options -To configure the widget, you should define the below code in YourModule.cs - -```csharp -Configure(options => - { - options.AddWidget(widgetType: "Today", widgetName: "CmsToday", parameterWidgetName: "Format"); - }); -``` - -Let's look at these parameters in detail -* `widgetType` is used for end-user and more readable names. The following bold word represents widgetType. -[Widget Type="**Today**" Format="yyyy-dd-mm HH:mm:ss"]. - -* `widgetName` is used for your widget name used in code for the name of the `ViewComponent`. - -* `parameterWidgetName` is used the for editor component side to see on the `Add Widget` modal. -After choosing the widget type from listbox (now just defined `Format`) and renders this widget automatically. It's required only to see UI once using parameters \ No newline at end of file diff --git a/docs/en/Modules/Cms-Kit/Global-Resources.md b/docs/en/Modules/Cms-Kit/Global-Resources.md deleted file mode 100644 index 5e0e0154a0..0000000000 --- a/docs/en/Modules/Cms-Kit/Global-Resources.md +++ /dev/null @@ -1,72 +0,0 @@ -# CMS Kit: Global Resources - -CMS Kit Global Resources system allows to add global styles and scripts dynamically. - -## Enabling the Global Resources Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## The User Interface - -### Menu items - -CMS Kit module admin side adds the following items to the main menu, under the *Global Resources* menu item: - -* **Global Resources**: Global resources management page. - -`CmsKitAdminMenus` class has the constants for the menu item names. - -### Global Resources Page - -Global Resources page is used to manage global styles and scripts in the system. - -![cms-kit-global-resources-page](../../images/cmskit-module-global-resources-page.png) - -# Internals - -## Domain Layer - -#### Aggregates - -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. - -- `GlobalResource` (aggregate root): Stores a resource. - -#### Repositories - -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. - -Following custom repositories are defined for this feature: - -- `IGlobalResourceRepository` - -#### Domain services - -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. - -##### Global Resource Manager - -`GlobalResourceManager` is used to perform operations for the `GlobalResource` aggregate root. - -### Application layer - -#### Application services - -- `GlobalResourceAdminAppService` (implements `IGlobalResourceAdminAppService`): Implements the management operations of global resources system. -- `GlobalResourcePublicAppService` (implements `IGlobalResourcePublicAppService`): Implements the public use cases of global resources system. - -#### Database - -#### Entity Framework Core - -##### Tables - -- CmsGlobalResources - -#### MongoDB - -##### Collections - -- CmsGlobalResources \ No newline at end of file diff --git a/docs/en/Modules/Cms-Kit/Index.md b/docs/en/Modules/Cms-Kit/Index.md deleted file mode 100644 index 6c3201d0f9..0000000000 --- a/docs/en/Modules/Cms-Kit/Index.md +++ /dev/null @@ -1,146 +0,0 @@ -# CMS Kit Module - -This module provides CMS (Content Management System) capabilities for your application. It provides **core building blocks** and fully working **sub-systems** to create your own website with CMS features enabled, or use the building blocks in your web sites with any purpose. - -> You can see the live demo at [cms-kit-demo.abpdemo.com](https://cms-kit-demo.abpdemo.com/). - -> **This module currently available only for the MVC / Razor Pages UI**. While there is no official Blazor package, it can also work in a Blazor Server UI since a Blazor Server UI is actually a hybrid application that runs in an ASP.NET Core MVC / Razor Pages application. - -The following features are currently available: - -* Provides a [**page**](Pages.md) management system to manage dynamic pages with dynamic URLs. -* Provides a [**blogging**](Blogging.md) system to create publish blog posts with multiple blog support. -* Provides a [**tagging**](Tags.md) system to tag any kind of resource, like a blog post. -* Provides a [**comment**](Comments.md) system to add comments feature to any kind of resource, like blog post or a product review page. -* Provides a [**reaction**](Reactions.md) system to add reactions (smileys) feature to any kind of resource, like a blog post or a comment. -* Provides a [**rating**](Ratings.md) system to add rating feature to any kind of resource. -* Provides a [**menu**](Menus.md) system to manage public menus dynamically. -* Provides a [**global resources**](Global-Resources.md) system to add global styles and scripts dynamically. -* Provides a [**Dynamic Widget**](Dynamic-Widget.md) system to create dynamic widgets for page and blog posts. - -> You can click on the any feature links above to understand and learn how to use it. - -All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, with the help of the [Global Features](../../Global-Features.md) system. - -## Pre Requirements - -- This module depends on [BlobStoring](../../Blob-Storing.md) module for keeping media content. -> Make sure `BlobStoring` module is installed and at least one provider is configured properly. For more information, check the [documentation](../../Blob-Storing.md). - -- CMS Kit uses [distributed cache](../../Caching.md) for responding faster. -> Using a distributed cache, such as [Redis](../../Redis-Cache.md), is highly recommended for data consistency in distributed/clustered deployments. - -## How to Install - -[ABP CLI](../../CLI.md) allows installing a module to a solution using the `add-module` command. You can install the CMS Kit module in a command-line terminal with the following command: - -```bash -abp add-module Volo.CmsKit --skip-db-migrations -``` - -> By default, Cms-Kit is disabled by `GlobalFeature`. Because of that the initial migration will be empty. So you can skip the migration by adding `--skip-db-migrations` to command when installing if you are using Entity Framework Core. After enabling Cms-Kit global feture, please add new migration. - -After the installation process, open the `GlobalFeatureConfigurator` class in the `Domain.Shared` project of your solution and place the following code into the `Configure` method to enable all the features in the CMS Kit module. - -```csharp -GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit => -{ - cmsKit.EnableAll(); -}); -``` - -Instead of enabling all, you may prefer to enable the features one by one. The following example enables only the [tags](Tags.md) and [comments](Comments.md) features: - -````csharp -GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit => -{ - cmsKit.Tags.Enable(); - cmsKit.Comments.Enable(); -}); -```` - -> If you are using Entity Framework Core, do not forget to add a new migration and update your database. - -## The Packages - -This module follows the [module development best practices guide](https://docs.abp.io/en/abp/latest/Best-Practices/Index) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them. - -CMS kit packages are designed for various usage scenarios. If you check the [CMS kit packages](https://www.nuget.org/packages?q=Volo.CmsKit), you will see that some packages have `Admin` and `Public` suffixes. The reason is that the module has two application layers, considering they might be used in different type of applications. These application layers uses a single domain layer: - - - `Volo.CmsKit.Admin.*` packages contain the functionalities required by admin (back office) applications. - - `Volo.CmsKit.Public.*` packages contain the functionalities used in public websites where users read blog posts or leave comments. - - `Volo.CmsKit.*` (without Admin/Public suffix) packages are called as unified packages. Unified packages are shortcuts for adding Admin & Public packages (of the related layer) separately. If you have a single application for administration and public web site, you can use these packages. - -## Internals - -### Table / collection prefix & schema - -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -### Connection string - -This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -## Entity Extensions - -[Module entity extension](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) system is a **high-level** extension system that allows you to **define new properties** for existing entities of the dependent modules. It automatically **adds properties to the entity**, **database**, **HTTP API, and user interface** in a single point. - -To extend entities of the CMS Kit module, open your `YourProjectNameModuleExtensionConfigurator` class inside of your `DomainShared` project and change the `ConfigureExtraProperties` method like shown below. - -```csharp -public static void ConfigureExtraProperties() -{ - OneTimeRunner.Run(() => - { - ObjectExtensionManager.Instance.Modules() - .ConfigureCmsKit(cmsKit => - { - cmsKit.ConfigureBlog(plan => // extend the Blog entity - { - plan.AddOrUpdateProperty( //property type: string - "BlogDescription", //property name - property => { - //validation rules - property.Attributes.Add(new RequiredAttribute()); //adds required attribute to the defined property - - //...other configurations for this property - } - ); - }); - - cmsKit.ConfigureBlogPost(blogPost => // extend the BlogPost entity - { - blogPost.AddOrUpdateProperty( //property type: string - "BlogPostDescription", //property name - property => { - //validation rules - property.Attributes.Add(new RequiredAttribute()); //adds required attribute to the defined property - property.Attributes.Add( - new StringLengthAttribute(MyConsts.MaximumDescriptionLength) { - MinimumLength = MyConsts.MinimumDescriptionLength - } - ); - - //...other configurations for this property - } - ); - }); - }); - }); -} -``` - -* `ConfigureCmsKit(...)` method is used to configure the entities of the CMS Kit module. - -* `cmsKit.ConfigureBlog(...)` is used to configure the **Blog** entity of the CMS Kit module. You can add or update your extra properties on the **Blog** entity. - -* `cmsKit.ConfigureBlogPost(...)` is used to configure the **BlogPost** entity of the CMS Kit module. You can add or update your extra properties of the **BlogPost** entity. - -* You can also set some validation rules for the property that you defined. In the above sample, `RequiredAttribute` and `StringLengthAttribute` were added for the property named **"BlogPostDescription"**. - -* When you define the new property, it will automatically add to **Entity**, **HTTP API**, and **UI** for you. - * Once you define a property, it appears in the create and update forms of the related entity. - * New properties also appear in the datatable of the related page. - diff --git a/docs/en/Modules/Cms-Kit/Menus.md b/docs/en/Modules/Cms-Kit/Menus.md deleted file mode 100644 index b469c322ab..0000000000 --- a/docs/en/Modules/Cms-Kit/Menus.md +++ /dev/null @@ -1,90 +0,0 @@ -# CMS Kit: Menus - -CMS Kit Menu system allows to manage public menus dynamically. - -## Enabling the Menu Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## The User Interface - -### Menu items - -CMS Kit module admin side adds the following items to the main menu, under the *CMS* menu item: - -* **Menus**: Menu management page. - -`CmsKitAdminMenus` class has the constants for the menu item names. - -### Menus - -#### Menu Management - -Menus page is used to manage dynamic public menus in the system. - -![cms-kit-menus-page](../../images/cmskit-module-menus-page.png) - -The created menu items will be visible on the public-web side, as shown below: - -![cms-kit-public-menus](../../images//cmskit-module-menus-public.png) - -## Internals - -### Domain Layer - -#### Aggregates - -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. - -- `MenuItem` (aggregate root): A Menu Item presents a single node at menu tree. - -#### Repositories - -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. - -Following custom repositories are defined for this feature: - -- `IMenuItemRepository` - -#### Domain services - -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. - -##### Menu Item Manager - -`MenuItemManager` is used to perform some operations for the `MenuItemManager` aggregate root. - -### Application layer - -#### Application services - -- `MenuItemAdminAppService` (implements `IMenuItemAdminAppService`): Implements the management operations of menu system. -- `MenuItemPublicAppService` (implements `IMenuItemPublicAppService`): Implements the public use cases of menu system. - -### Database providers - -#### Common - -##### Table / collection prefix & schema - -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection string - -This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- CmsMenuItems - -#### MongoDB - -##### Collections - -- CmsMenuItems \ No newline at end of file diff --git a/docs/en/Modules/Cms-Kit/Pages.md b/docs/en/Modules/Cms-Kit/Pages.md deleted file mode 100644 index 04614dd74a..0000000000 --- a/docs/en/Modules/Cms-Kit/Pages.md +++ /dev/null @@ -1,34 +0,0 @@ -# CMS Kit: Pages - -CMS Kit Page system allows you to create dynamic pages by specifying URLs, which is the fundamental feature of a CMS. - -## Enabling the Pages Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## The User Interface - -### Menu items - -CMS Kit module admin side adds the following items to the main menu, under the *CMS* menu item: - -* **Pages**: Page management page. - -`CmsKitAdminMenus` class has the constants for the menu item names. - -### Pages - -#### Page Management - -**Pages** page is used to manage dynamic pages in the system. You can create/edit pages with dynamic routes and contents on this page: - -![pages-edit](../../images/cmskit-module-pages-edit.png) - -After you have created pages, you can set one of them as a *home page*. Then, whenever anyone navigates to your application's homepage, they see the dynamic content of the page that you have defined on this page. - -![pages-page](../../images/cmskit-module-pages-page.png) - -Also when you create a page, you can access the created page via `/{slug}` URL. - diff --git a/docs/en/Modules/Cms-Kit/Ratings.md b/docs/en/Modules/Cms-Kit/Ratings.md deleted file mode 100644 index e6a2ba97fd..0000000000 --- a/docs/en/Modules/Cms-Kit/Ratings.md +++ /dev/null @@ -1,111 +0,0 @@ -# Rating System - -CMS kit provides a **rating** system to to add ratings feature to any kind of resource like blog posts, comments, etc. Here how the rating component looks like on a sample page: - -![ratings](../../images/cmskit-module-ratings.png) - -## Enabling the Rating Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## Options - -The rating system provides a mechanism to group ratings by entity types. For example, if you want to use the rating system for products, you need to define an entity type named `Product` and then add ratings under the defined entity type. - -`CmsKitRatingOptions` can be configured in the domain layer, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: - -```csharp -Configure(options => -{ - options.EntityTypes.Add(new RatingEntityTypeDefinition("Product")); -}); -``` - -> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above. - -`CmsKitRatingOptions` properties: - -- `EntityTypes`: List of defined entity types(`RatingEntityTypeDefinition`) in the rating system. - -`RatingEntityTypeDefinition` properties: - -- `EntityType`: Name of the entity type. - -## The Rating Widget - -The ratings system provides a rating widget to allow users send ratings to resources in public websites. You can simply place the widget on a page like below. - -```csharp -@await Component.InvokeAsync(typeof(RatingViewComponent), new -{ - entityType = "Product", - entityId = "entityId", - isReadOnly = false -}) -``` - -`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. - -# Internals - -## Domain Layer - -#### Aggregates - -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. - -##### Rating - -A rating represents a given rating from a user. - -- `Rating` (aggregate root): Represents a given rating in the system. - -#### Repositories - -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. - -Following custom repositories are defined for this feature: - -- `IRatingRepository` - -#### Domain services - -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. - -##### Reaction Manager - -`RatingManager` is used to perform some operations for the `Rating` aggregate root. - -### Application layer - -#### Application services - -- `RatingPublicAppService` (implements `IRatingPublicAppService`): Implements the use cases of rating system. - -### Database providers - -#### Common - -##### Table / collection prefix & schema - -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection string - -This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- CmsRatings - -#### MongoDB - -##### Collections - -- **CmsRatings** \ No newline at end of file diff --git a/docs/en/Modules/Cms-Kit/Reactions.md b/docs/en/Modules/Cms-Kit/Reactions.md deleted file mode 100644 index 457d5e0f26..0000000000 --- a/docs/en/Modules/Cms-Kit/Reactions.md +++ /dev/null @@ -1,126 +0,0 @@ -# Reaction System - -CMS kit provides a **reaction** system to add reactions feature to any kind of resource, like blog posts or comments. - -Reaction component allows users to react to your content via pre-defined icons/emojis. Here how the reactions component may looks like: - -![reactions](../../images/cmskit-module-reactions.png) - -You can also customize the reaction icons shown in the reaction component. - -## Enabling the Reaction Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## Options - -Reaction system provides a mechanism to group reactions by entity types. For example, if you want to use the reaction system for products, you need to define an entity type named `Product`, and then add reactions under the defined entity type. - -`CmsKitReactionOptions` can be configured in the domain layer, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: - -```csharp -Configure(options => -{ - options.EntityTypes.Add( - new ReactionEntityTypeDefinition( - "Product", - reactions: new[] - { - new ReactionDefinition(StandardReactions.Smile), - new ReactionDefinition(StandardReactions.ThumbsUp), - new ReactionDefinition(StandardReactions.ThumbsDown), - new ReactionDefinition(StandardReactions.Confused), - new ReactionDefinition(StandardReactions.Eyes), - new ReactionDefinition(StandardReactions.Heart) - })); -}); -``` - -> If you're using the [Comment](Comments.md) or [Blogging](Blogging.md) features, the ABP framework defines predefined reactions for these features automatically. - -`CmsKitReactionOptions` properties: - -- `EntityTypes`: List of defined entity types (`CmsKitReactionOptions`) in the reaction system. - -`ReactionEntityTypeDefinition` properties: - -- `EntityType`: Name of the entity type. -- `Reactions`: List of defined reactions (`ReactionDefinition`) in the entity type. - -## The Reactions Widget - -The reaction system provides a reaction widget to allow users to send reactions to resources. You can place the widget on a page like below: - -```csharp -@await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new -{ - entityType = "Product", - entityId = "..." -}) -``` - -`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. - -# Internals - -## Domain Layer - -#### Aggregates - -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. - -##### UserReaction - -A user reaction represents a given reaction from a user. - -- `UserReaction` (aggregate root): Represents a given reaction in the system. - -#### Repositories - -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. - -Following custom repositories are defined for this feature: - -- `IUserReactionRepository` - -#### Domain services - -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. - -##### Reaction Manager - -`ReactionManager` is used to perform some operations for the `UserReaction` aggregate root. - -### Application layer - -#### Application services - -- `ReactionPublicAppService` (implements `IReactionPublicAppService`): Implements the use cases of reaction system. - -### Database providers - -#### Common - -##### Table / collection prefix & schema - -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection string - -This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- CmsUserReactions - -#### MongoDB - -##### Collections - -- **CmsUserReactions** diff --git a/docs/en/Modules/Cms-Kit/Tags.md b/docs/en/Modules/Cms-Kit/Tags.md deleted file mode 100644 index 9f85faeff2..0000000000 --- a/docs/en/Modules/Cms-Kit/Tags.md +++ /dev/null @@ -1,165 +0,0 @@ -# Tag Management - -CMS kit provides a **tag** system to tag any kind of resources, like a blog post. - -## Enabling the Tag Management Feature - -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. - -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. - -## Options - -The tag system provides a mechanism to group tags by entity types. For example, if you want to use the tag system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product` and add tags under these entity types. - -`CmsKitTagOptions` can be configured in the domain layer, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics) class. - -**Example: Adding tagging support for products** - -```csharp -Configure(options => -{ - options.EntityTypes.Add(new TagEntityTypeDefiniton("Product")); -}); -``` - -> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. - -`CmsKitTagOptions` properties: - -- `EntityTypes`: List of defined entity types(`TagEntityTypeDefiniton`) in the tag system. - -`TagEntityTypeDefiniton` properties: - -- `EntityType`: Name of the entity type. -- `DisplayName`: The display name of the entity type. You can use a user-friendly display name to show entity type definition on the admin website. -- `CreatePolicies`: List of policy/permission names allowing users to create tags under the entity type. -- `UpdatePolicies`: List of policy/permission names allowing users to update tags under the entity type. -- `DeletePolicies`: List of policy/permission names allowing users to delete tags under the entity type. - -## The Tag Widget - -The tag system provides a tag [widget](../../UI/AspNetCore/Widgets.md) to display associated tags of a resource that was configured for tagging. You can simply place the widget on a page like the one below: - -```csharp -@await Component.InvokeAsync(typeof(TagViewComponent), new -{ - entityType = "Product", - entityId = "...", - urlFormat = "/products?tagId={TagId}&tagName={TagName}" -}) -``` - -`entityType` was explained in the previous section. In this example, the `entityId` should be the unique id of the product. If you have a `Product` entity, you can use its Id here. `urlFormat` is the string format of the URL which will be generated for each tag. You can use the `{TagId}` and `{TagName}` placeholders to populate the URL. For example, the above URL format will populate URLs like `/products?tagId=1&tagName=tag1`. - -## The Popular Tags Widget - -The tag system provides a popular tags [widget](../../UI/AspNetCore/Widgets.md) to display popular tags of a resource that was configured for tagging. You can simply place the widget on a page as below: - -```csharp -@await Component.InvokeAsync(typeof(PopularTagsViewComponent), new -{ - entityType = "Product", - urlFormat = "/products?tagId={TagId}&tagName={TagName}", - maxCount = 10 -}) -``` - -`entityType` was explained in the previous section. `urlFormat` was explained in the previous section. `maxCount` is the maximum number of tags to be displayed. - -## User Interface - -### Menu Items - -The following menu items are added by the tagging feature to the admin application: - -* **Tags**: Opens the tag management page. - -### Pages - -#### Tag Management - -This page can be used to create, edit and delete tags for the entity types. - -![tags-page](../../images/cmskit-module-tags-page.png) - -You can create or edit an existing tag on this page. - -![tag-edit](../../images/cmskit-module-tag-edit.png) - -## Internals - -### Domain Layer - -#### Aggregates - -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. - -##### Tag - -A tag represents a tag under the entity type. - -- `Tag` (aggregate root): Represents a tag in the system. - -##### EntityTag - -An entity tag represents a connection between the tag and the tagged entity. - -- `EntityTag`(entity): Represents a connection between the tag and the tagged entity. - -#### Repositories - -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. - -The following custom repositories are defined for this feature: - -- `ITagRepository` -- `IEntityTagRepository` - -#### Domain services - -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. - -##### Tag Manager - -`TagManager` performs some operations for the `Tag` aggregate root. - -##### Entity Tag Manager - -`EntityTagManager` performs some operations for the `EntityTag` entity. - -### Application layer - -#### Application services - -- `TagAdminAppService` (implements `ITagAdminAppService`). -- `EntityTagAdminAppService` (implements `IEntityTagAdminAppService`). -- `TagAppService` (implements `ITagAppService`). - -### Database providers - -#### Common - -##### Table / Collection prefix & schema - -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection string - -This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- CmsTags -- CmsEntityTags - -#### MongoDB - -##### Collections - -- **CmsTags** -- **CmsEntityTags** diff --git a/docs/en/Modules/Database-Tables.md b/docs/en/Modules/Database-Tables.md deleted file mode 100644 index 0f00055d13..0000000000 --- a/docs/en/Modules/Database-Tables.md +++ /dev/null @@ -1,575 +0,0 @@ -# Database Tables - -This documentation describes all database tables and their purposes. You can read this documentation to get general knowledge of the database tables that come from each module. - -## [Audit Logging Module](Audit-Logging.md) - -### AbpAuditLogs - -This table stores information about the audit logs in the application. Each record represents an audit log and tracks the actions performed in the application. - -### AbpAuditLogActions - -This table stores information about the actions performed in the application, which are logged for auditing purposes. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpAuditLogs](#abpauditlogs) | Id | Links each action to a specific audit log. | - -### AbpEntityChanges - -This table stores information about entity changes in the application, which are logged for auditing purposes. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpAuditLogs](#abpauditlogs) | Id | Links each entity change to a specific audit log. | - -### AbpEntityPropertyChanges - -This table stores information about property changes to entities in the application, which are logged for auditing purposes. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpEntityChanges](#abpentitychanges) | Id | Links each property change to a specific entity change. | - -## [Background Jobs Module](Background-Jobs.md) - -### AbpBackgroundJobs - -This table stores information about the background jobs in the application and facilitates their efficient management and tracking. Each entry in the table contains details of a background job, including the job name, arguments, try count, next try time, last try time, abandoned status, and priority. - -## [Tenant Management Module](Tenant-Management.md) - -### AbpTenants - -This table stores information about the tenants. Each record represents a tenant and contains information about the tenant, such as name and other details. - -### AbpTenantConnectionStrings - -This table stores information about the tenant database connection strings. When you define a connection string for a tenant, a new record will be added to this table. You can query this database to get connection strings by tenants. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpTenants](#abptenants) | Id | The `Id` column in the `AbpTenants` table is used to associate the tenant connection string with the corresponding tenant. | - -## Blogging Module - -### BlgUsers - -This table stores information about the blog users. When a new identity user is created, a new record will be added to this table. - -### BlgBlogs - -This table serves to store blog information and semantically separates the posts of each blog. - -### BlgPosts - -This table stores information about the blog posts. You can query this table to get blog posts by blogs. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [BlgBlogs](#blgblogs) | Id | To associate the blog post with the corresponding blog. | -### BlgComments - -This table stores information about the comments made on blog posts. You can query this table to get comments by posts. -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [BlgPosts](#blgposts) | Id | Links the comment to the corresponding blog post. | -| [BlgComments](#blgcomments) | Id | Links the comment to the parent comment. | - -### BlgTags - -This table stores information about the tags. When a new tag is used, a new record will be added to this table. You can query this table to get tags by blogs. - -### BlgPostTags - -This table is used to associate tags with blog posts in order to categorize and organize the content. You can query this table to get post tags by posts. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [BlgTags](#blgtags) | Id | Links the post tag to the corresponding tag. | -| [BlgPosts](#blgposts) | Id | Links the post tag to the corresponding blog post. | - -## [CMS Kit Module](Cms-Kit/Index.md) - -### CmsUsers - -This table stores information about the CMS Kit module users. When a new identity user is created, a new record will be added to this table. - -### CmsBlogs - -This table serves to store blog information and semantically separates the posts of each blog. - -### CmsBlogPosts - -This table stores information about the blog posts. You can query this table to get blog posts by blogs. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [CmsUsers](#cmsusers) | Id | Links the blog post to the corresponding author. | - -### CmsBlogFeatures - -This table stores information about the blog features. You can query this table to get blog features by blogs. - -### CmsComments - -This table is utilized by the [CMS Kit Comment system](Cms-Kit/Comments.md) to store comments made on the blog posts. You can query this table to get comments by posts. - -### CmsTags - -This table stores information about the tags. When a new tag is used, a new record will be added to this table. You can query this table to get tags by blogs. - -### CmsEntityTags - -This table is utilized by the [Tag Management system](Cms-Kit/Tags.md) to store tags and their relationship with various entities, thus enabling efficient categorization and organization of content. You can query this table to get entity tags by entities. - -### CmsGlobalResources - -This table is a database table for the [CMS Kit Global Resources system](Cms-Kit/Global-Resources.md), allowing dynamic addition of global styles and scripts. - -### CmsMediaDescriptors - -This table is utilized by the CMS kit module to manage media files by using the [BlobStoring](../Blob-Storing.md) module. - -### CmsMenuItems - -This table is used by the [CMS Kit Menu system](Cms-Kit/Menus.md) to manage and store information about dynamic public menus, including details such as menu item display names, URLs, and hierarchical relationships. - -### CmsPages - -This table is utilized by the [CMS Kit Page system](Cms-Kit/Pages.md) to store dynamic pages within the application, including information such as page URLs, titles, and content. - -### CmsRatings - -This table is utilized by the [CMS Kit Rating system](Cms-Kit/Ratings.md) to store ratings made on blog posts. You can query this table to get ratings by posts. - -### CmsUserReactions - -This table is utilized by the [CMS Kit Reaction system](Cms-Kit/Reactions.md) to store reactions made on blog posts. You can query this table to get reactions by posts. - -## [Docs Module](Docs.md) - -### DocsProjects - -This table stores project information to categorize documents according to different projects. - -### DocsDocuments - -This table retrieves the document if it's not found in the cache. The documentation is being updated when the content is retrieved from the database. - -### DocsDocumentContributors - -This table stores information about the contributors of the documents. You can query this table to get document contributors by documents. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [DocsDocuments](#docsdocuments) | Id | Links the document contributor to the corresponding document. | - -## [Feature Management Module](Feature-Management.md) - -### AbpFeatureGroups - -This table stores information about the feature groups in the application. For example, you can group all the features in the [`AbpFeatures`](#abpfeatures) table related to the `Identity` module under the `Identity` group. - -### AbpFeatures - -This table stores information about the features in the application. You can use the `Name` column to link each feature with its corresponding feature value in the [`AbpFeatureValues`](#abpfeaturevalues) table, so that you can easily manage and organize the features. - -### AbpFeatureValues - -This table stores the values of the features for different providers. You can use the `Name` column to link each feature value with its corresponding feature in the [`AbpFeatures`](#abpfeatures) table, so that you can easily manage and organize the features. - -## [Identity Module](Identity.md) - -### AbpUsers - -This table stores information about the identity users in the application. - -### AbpRoles - -This table stores information about the roles in the application. Roles are used to manage and control access to different parts of the application by assigning permissions and claims to roles and then assigning those roles to users. This table is important for managing and organizing the roles in the application, and for defining the access rights of the users. - -### AbpClaimTypes - -This table stores information about the claim types used in the application. You can use the `Name`, `Regex` columns to filter the claim types by name, and regex pattern respectively, so that you can easily manage and track the claim types in the application. - -### AbpLinkUsers - -This table is useful for linking multiple user accounts across different tenants or applications to a single user, allowing them to easily switch between their accounts. - -### AbpUserClaims - -This table can manage user-based access control by allowing to assign claims to users, which describes the access rights of the individual user. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpUsers](#abpusers) | Id | Links the user claim to the corresponding user. | - -### AbpUserLogins - -This table can store information about the user's external logins such as login with Facebook, Google, etc. and it can also be used to track the login history of users. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpUsers](#abpusers) | Id | Links the user login to the corresponding user. | - -### AbpUserRoles - -This table can manage user-based access control by allowing to assign roles to users, which describe the access rights of the individual user. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpUsers](#abpusers) | Id | Links the user role to the corresponding user. | -| [AbpRoles](#abproles) | Id | Links the user role to the corresponding role. | - -### AbpUserTokens - -This table can store information about user's refresh tokens, access tokens and other tokens used in the application. It can also be used to invalidate or revoke user tokens. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpUsers](#abpusers) | Id | Links the user token to the corresponding user. | - -### AbpOrganizationUnits - -This table is useful for creating and managing a hierarchical structure of the organization, allowing to group users and assign roles based on the organization structure. You can use the `Code`, `ParentId` columns to filter the organization units by code and parent id respectively, so that you can easily manage and track the organization units in the application. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpOrganizationUnits](#abporganizationunits) | ParentId | Links the organization unit to its parent organization unit. | - -### AbpOrganizationUnitRoles - -This table is useful for managing role-based access control at the level of organization units, allowing to assign different roles to different parts of the organization structure. You can use the `OrganizationUnitId`, `RoleId` columns to filter the roles by organization unit id and role id respectively, so that you can easily manage and track the roles assigned to organization units in the application. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpOrganizationUnits](#abporganizationunits) | Id | Links the organization unit role to the corresponding organization unit. | -| [AbpRoles](#abproles) | Id | Links the organization unit role to the corresponding role. | - -### AbpUserOrganizationUnits - -This table stores information about the organization units assigned to the users in the application. This table can manage user-organization unit relationships, and to group users based on the organization structure. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpUsers](#abpusers) | Id | Links the user organization unit to the corresponding user. | -| [AbpOrganizationUnits](#abporganizationunits) | Id | Links the user organization unit to the corresponding organization unit. | - -### AbpRoleClaims - -This table is useful for managing role-based access control by allowing to assign claims to roles, which describes the access rights of the users that belong to that role. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpRoles](#abproles) | Id | Links the role claim to the corresponding role. | - -### AbpSecurityLogs - -This table logs important operations and changes related to user accounts, allowing users to save the security logs for future reference. - -## [Permission Management](Permission-Management.md) - -### AbpPermissionGroups - -This table is important for managing and organizing the permissions in the application, by grouping them into logical categories. - -### AbpPermissions - -This table is important for managing and controlling access to different parts of the application and for defining the granular permissions that make up the larger permissions or roles. - -### AbpPermissionGrants - -The table stores and manage the permissions in the application and to keep track of permissions that are granted, to whom and when. Columns such as `Name`, `ProviderName`, `ProviderKey`, `TenantId` can be used to filter the granted permissions by name, provider name, provider key, and tenant id respectively, so that you can easily manage and track the granted permissions in the application. - -## [Setting Management](Setting-Management.md) - -### AbpSettings - -This table stores key-value pairs of settings for the application, and it allows dynamic configuration of the application without the need for recompilation. - -## [OpenIddict](OpenIddict.md) - -### OpenIddictApplications - -This table can store information about the OpenID Connect applications, including the client id, client secret, redirect URI, and other relevant information. It can also be used to authenticate and authorize clients using OpenID Connect protocol. - -### OpenIddictAuthorizations - -This table stores the OpenID Connect authorization data in the application. It can also be used to manage and validate the authorization grants issued to clients and users. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [OpenIddictApplications](#openiddictapplications) | Id | Links the authorization to the corresponding application. | - -### OpenIddictTokens - -This table can store information about the OpenID Connect tokens, including the token payload, expiration, type, and other relevant information. It can also be used to manage and validate the tokens issued to clients and users, such as access tokens and refresh tokens, and to control access to protected resources. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [OpenIddictApplications](#openiddictapplications) | Id | Links the token to the corresponding application. | -| [OpenIddictAuthorizations](#openiddictauthorizations) | Id | Links the token to the corresponding authorization. | - -### OpenIddictScopes - -This table can store information about the OpenID Connect scopes, including the name and description of the scope. It can also be used to define the permissions or access rights associated with the scopes, which are then used to control access to protected resources. - -## [IdentityServer](IdentityServer.md) - -### IdentityServerApiResources - -This table can store information about the API resources, including the resource name, display name, description, and other relevant information. It can also be used to define the scopes, claims, and properties associated with the API resources, which are then used to control access to protected resources. - -### IdentityServerIdentityResources - -This table can store information about the identity resources, including the name, display name, description, and enabled status. - -### IdentityServerClients - -This table can store information about the clients, including the client id, client name, client URI and other relevant information. It can also be used to define the scopes, claims, and properties associated with the clients, which are then used to control access to protected resources. - -### IdentityServerApiScopes - -This table can store information about the API scopes, including the scope name, display name, description, and other relevant information. It can also be used to define the claims and properties associated with the API scopes, which are then used to control access to protected resources. - -### IdentityServerApiResourceClaims - -This table can store information about the claims of an API resource, including the claim type and API resource id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the claim to the corresponding API resource. | - -### IdentityServerIdentityResourceClaims - -This table can store information about the claims of an identity resource, including the claim type and identity resource id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerIdentityResources](#identityserveridentityresources) | Id | Links the claim to the corresponding identity resource. | - -### IdentityServerClientClaims - -This table can store information about the claims of a client, including the claim type, claim value and client id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the claim to the corresponding client. | - -### IdentityServerApiScopeClaims - -This table can store information about the claims of an API scope, including the claim type and API scope id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerApiScopes](#identityserverapiscopes) | Id | Links the claim to the corresponding API scope. | - -### IdentityServerApiResourceProperties - -This table can store information about properties, including the property key and value, and the associated API resource. These properties can store additional metadata or configuration information related to the API resources. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the property to the corresponding API resource. | - -### IdentityServerIdentityResourceProperties - -This table can store information about properties, including the property key and value, and the associated identity resource. These properties can store additional metadata or configuration information related to the identity resources. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerIdentityResources](#identityserveridentityresources) | Id | Links the property to the corresponding identity resource. | - -### IdentityServerClientProperties - -This table can be store information about the properties of a client, including the key, value and client id. These properties can store additional metadata or configuration information related to the clients. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the property to the corresponding client. | - -### IdentityServerApiScopeProperties - -This table can store information about the properties of an API scope, including the key, value and API scope id. These properties can store additional metadata or configuration information related to the API scopes. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerApiScopes](#identityserverapiscopes) | Id | Links the property to the corresponding API scope. | - -### IdentityServerApiResourceScopes - -This table can store information about the scopes of an API resource, including the scope name and API resource id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the scope to the corresponding API resource. | - -### IdentityServerClientScopes - - This table can store information about the scopes of a client, including the scope and client id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the scope to the corresponding client. | - -### IdentityServerApiResourceSecrets - -This table can store information about the secrets of an API resource, including the secret value, expiration date, and API resource id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerApiResources](#identityserverapiresources) | Id | Links the secret to the corresponding API resource. | - -### IdentityServerClientSecrets - -This table can store information about the secrets of a client, including the secret value, expiration date, and client id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the secret to the corresponding client. | - -### IdentityServerClientCorsOrigins - -This table can store information about the CORS origins of a client, including the origin and client id. It can also be used to manage and validate the CORS origins of a client. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the CORS origin to the corresponding client. | - -### IdentityServerClientGrantTypes - -This table can store information about the grant types of a client, including the grant type and client id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the grant type to the corresponding client. | - -### IdentityServerClientIdPRestrictions - -This table can store information about the identity provider restrictions of a client, including the identity provider and client id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the identity provider restriction to the corresponding client. | - -### IdentityServerClientPostLogoutRedirectUris - -This table can store information about the post logout redirect URIs of a client, including the post logout redirect URI and client id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the post logout redirect URI to the corresponding client. | - -### IdentityServerClientRedirectUris - -This table can store information about the redirect URIs of a client, including the redirect URI and client id. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [IdentityServerClients](#identityserverclients) | Id | Links the redirect URI to the corresponding client. | - -### IdentityServerDeviceFlowCodes - -This table can store information about the device flow codes, including the user code, device code, subject id, client id, creation time, expiration, data and session id. - -### IdentityServerPersistedGrants - -This table can store information about the persisted grants, including the key, type, subject id, client id, creation time, expiration, and data. - -## Others - -### AbpBlobContainers - -This table is important for providing a better user experience by allowing the application to support multiple containers and providing BLOB-specific features. - -### AbpBlobs - -This table stores the binary data of BLOBs (binary large objects) in the application. Each BLOB is related to a container in the [AbpBlobContainers](#abpblobcontainers) table, where the container name, tenant id and other properties of the container can be found. - -#### Foreign Keys - -| Table | Column | Description | -| --- | --- | --- | -| [AbpBlobContainers](#abpblobcontainers) | Id | Links the BLOB to the corresponding container. | - -### AbpLocalizationResources - -This table stores the localization resources for the application. This table is important for providing a better user experience by allowing the application to support multiple resources and providing localized text and other localization-specific features. - -### AbpLocalizationTexts - -The table contains the resource name, culture name, and a json encoded value which holds the key-value pair of localization text. It allows for efficient storage and management of localization texts and allows for easy update or addition of new translations for specific resources and cultures. diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md deleted file mode 100644 index e52db93a23..0000000000 --- a/docs/en/Modules/Docs.md +++ /dev/null @@ -1,675 +0,0 @@ -# Docs Module - -## What is Docs Module? - -Docs module is an application module for ABP framework. It simplifies software documentation. This module is free and open-source. - -### Integration - -Currently docs module provides you to store your docs both on GitHub and file system. - -### Hosting - -Docs module is an application module and does not offer any hosting solution. You can host your docs on-premise or on cloud. - -### Versioning - -When you use GitHub to store your docs, Docs Module supports versioning. If you have multiple versions for your docs, there will be a combo-box on the UI to switch between versions. If you choose file system to store your docs, it does not support multiple versions. - -[The documents](https://docs.abp.io/) for ABP framework is also using this module. - -> Docs module follows the [module architecture best practices](../Best-Practices/Module-Architecture.md) guide. - -## Installation - -This document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider. - -### 1- Creating an application - -If you do not have an existing ABP project, you can either [generate a CLI command from the get started page of the abp.io website](https://abp.io/get-started) and runs it or run the command below: - -```bash -abp new Acme.MyProject -``` - -### 2- Running The Empty Application - -After you created the project, open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**. - -![Create a new project](../images/docs-module_solution-explorer.png) - -The database connection string is located in `appsettings.json` of your `Acme.MyProject.Web` project. If you have a different database configuration, change the connection string. - -```json -{ - "ConnectionStrings": { - "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProject;Trusted_Connection=True" - } -} -``` - -Run `Acme.MyProject.DbMigrator` project, it will be responsible for applying database migration and seed data. The database `MyProject` will be created in your database server. - -Now an empty ABP project has been created! You can now run your project and see the empty website. - -To login your website enter `admin` as the username and `1q2w3E*` as the password. - -### 3- Installation Module - -Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project. - -#### 3.1- Use ABP CLI - -It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: - -```bash -abp add-module Volo.Docs -``` - -#### 3.2- Manually install - -Or you can also manually install nuget package to each project: - -* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project. - - ```bash - Install-Package Volo.Docs.Domain - ``` - -* Install [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget package to `Acme.MyProject.EntityFrameworkCore` project. - - ```bash - Install-Package Volo.Docs.EntityFrameworkCore - ``` - -* Install [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget package to `Acme.MyProject.Application` project. - - ```bash - Install-Package Volo.Docs.Application - ``` - -* Install [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Web` project. - - ```bash - Install-Package Volo.Docs.Web - ``` - -##### 3.2.1- Adding Module Dependencies - -An ABP module must declare `[DependsOn]` attribute if it has a dependency upon another module. Each module has to be added in`[DependsOn]` attribute to the relevant project. - -* Open `MyProjectDomainModule.cs`and add `typeof(DocsDomainModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsDomainModule), - typeof(AbpIdentityDomainModule), - typeof(AbpAuditingModule), - typeof(BackgroundJobsDomainModule), - typeof(AbpAuditLoggingDomainModule) - )] - public class MyProjectDomainModule : AbpModule - { - //... - } - ``` - -* Open `MyProjectEntityFrameworkCoreModule.cs`and add `typeof(DocsEntityFrameworkCoreModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsEntityFrameworkCoreModule), - typeof(MyProjectDomainModule), - typeof(AbpIdentityEntityFrameworkCoreModule), - typeof(AbpPermissionManagementEntityFrameworkCoreModule), - typeof(AbpSettingManagementEntityFrameworkCoreModule), - typeof(AbpEntityFrameworkCoreSqlServerModule), - typeof(BackgroundJobsEntityFrameworkCoreModule), - typeof(AbpAuditLoggingEntityFrameworkCoreModule) - )] - public class MyProjectEntityFrameworkCoreModule : AbpModule - { - //... - } - ``` - -* Open `MyProjectApplicationModule.cs`and add `typeof(DocsApplicationModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsApplicationModule), - typeof(MyProjectDomainModule), - typeof(AbpIdentityApplicationModule))] - public class MyProjectApplicationModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.DefinitionProviders.Add(); - }); - - Configure(options => - { - options.AddProfile(); - }); - } - } - ``` - -* Open `MyProjectWebModule.cs`and add `typeof(DocsWebModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsWebModule), - typeof(MyProjectApplicationModule), - typeof(MyProjectEntityFrameworkCoreModule), - typeof(AbpAutofacModule), - typeof(AbpIdentityWebModule), - typeof(AbpAccountWebModule), - typeof(AbpAspNetCoreMvcUiBasicThemeModule) - )] - public class MyProjectWebModule : AbpModule - { - //... - } - ``` - -##### 3.2.2- Adding NPM Package - -Open `package.json` and add `@abp/docs": "^5.0.0` as shown below: - - ```json - { - "version": "1.0.0", - "name": "my-app", - "private": true, - "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^5.0.0", - "@abp/docs": "^5.0.0" - } - } - ``` - -Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command: - -````bash -abp install-libs -```` - -### 4- Database Integration - -#### 4.1- Entity Framework Integration - -If you choose Entity Framework as your database provider, you need to configure the Docs Module. To do this; - -- Open `MyProjectMigrationsDbContext.cs` and add `builder.ConfigureDocs()` to the `OnModelCreating()`. - - ```csharp - public class MyProjectMigrationsDbContext : AbpDbContext - { - public MyProjectMigrationsDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.ConfigurePermissionManagement(); - builder.ConfigureSettingManagement(); - builder.ConfigureBackgroundJobs(); - builder.ConfigureAuditLogging(); - builder.ConfigureIdentity(); - builder.ConfigureIdentityServer(); - builder.ConfigureFeatureManagement(); - builder.ConfigureTenantManagement(); - builder.ConfigureDocs(); //Add this line to configure the Docs Module - - /* Configure customizations for entities from the modules included */ - - builder.Entity(b => - { - b.ConfigureCustomUserProperties(); - }); - - /* Configure your own tables/entities inside the ConfigureQaDoc method */ - - builder.ConfigureMyProject(); - } - } - ``` - -* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore` as default project. Then write the below command to add the migration for Docs Module. - - ```csharp - add-migration Added_Docs_Module - ``` - - When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore\Migrations`. - - Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore` is still default project. - - ```csharp - update-database - ``` - - Finally, you can check your database to see the newly created tables. For example you can see `DocsProjects` table must be added to your database. - -### 5- Linking Docs Module - -The default route for Docs module is; - -```txt -/Documents -``` - -To add Docs module link to your application menu; - -* Open `MyProjectMenuContributor.cs` and add the below line to the method `ConfigureMainMenuAsync()`. - - ```csharp - context.Menu.Items.Add(new ApplicationMenuItem("MyProject.Docs", l["Menu:Docs"], "/Documents")); - ``` - - Final look of **MyProjectMenuContributor.cs** - - ```csharp - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - var l = context.ServiceProvider.GetRequiredService>(); - - context.Menu.Items.Insert(0, new ApplicationMenuItem("MyProject.Home", l["Menu:Home"], "/")); - - context.Menu.Items.Add(new ApplicationMenuItem("MyProject.Docs", l["Menu:Docs"], "/Documents")); - } - ``` - -The `Menu:Docs` keyword is a localization key. To localize the menu text, open `Localization\MyProject\en.json` in the project `Acme.MyProject.Domain`. And add the below line - -```json -"Menu:Docs": "Documents" -``` - -Final look of **en.json** - -```json -{ - "culture": "en", - "texts": { - "Menu:Home": "Home", - "Welcome": "Welcome", - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", - "Menu:Docs": "Documents" - } -} -``` - -The new menu item for Docs Module is added to the menu. Run your web application and browse to `http://localhost:YOUR_PORT_NUMBER/documents` URL. - -You will see a warning says; - -```txt -There are no projects yet! -``` - -As we have not added any projects yet, this warning is normal. - -### 6- Adding New Docs Project - -Open `DocsProjects` in your database, and insert a new record with the following field information; - -* **Name**: The display name of the document name which will be shown on the web page. -* **ShortName**: A short and URL friendly name that will be used in your docs URL. -* **Format**: The format of the document (for Markdown: `md`, for HTML: `html`) -* **DefaultDocumentName**: The document for the initial page. -* **NavigationDocumentName**: The document to be used for the navigation menu (Index). -* **MinimumVersion**: The minimum version to show the docs. Below version will not be listed. -* **DocumentStoreType**: The source of the documents (for GitHub:`GitHub`, for file system`FileSystem`) -* **ExtraProperties**: A serialized `JSON` that stores special configuration for the selected `DocumentStoreType`. -* **MainWebsiteUrl**: The URL when user clicks to the logo of the Docs module page. You can simply set as `/` to link to your website root address. -* **LatestVersionBranchName**: This is a config for GitHub. It's the branch name which to retrieve the docs. You can set it as `dev`. - -#### Sample Project Record for "GitHub" - -You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store. - -- Name: `ABP framework (GitHub)` - -- ShortName: `abp` - -- Format: `md` - -- DefaultDocumentName: `Index` - -- NavigationDocumentName: `docs-nav.json` - -- MinimumVersion: `` (no minimum version) - -- DocumentStoreType: `GitHub` - -- ExtraProperties: - - ```json - {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""} - ``` - - Note that `GitHubAccessToken` is masked with `***`. It's a private token that you must get it from GitHub. See https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ - -- MainWebsiteUrl: `/` - -- LatestVersionBranchName: `dev` - -For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table: - -```mssql -INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"","GitHubUserAgent":""}', N'/', N'dev', N'', N'12f21123e08e4f15bedbae0b2d939659') -``` - -Be aware that `GitHubAccessToken` is masked. It's a private token and you must get your own token and replace the `***` string. - -Now you can run the application and navigate to `/Documents`. - -#### Sample Project Record for "FileSystem" - -You can use [ABP Framework](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store. - -- Name: `ABP framework (FileSystem)` - -- ShortName: `abp` - -- Format: `md` - -- DefaultDocumentName: `Index` - -- NavigationDocumentName: `docs-nav.json` - -- MinimumVersion: `` (no minimum version) - -- DocumentStoreType: `FileSystem` - -- ExtraProperties: - - ```json - {"Path":"C:\\Github\\abp\\docs"} - ``` - - Note that `Path` must be replaced with your local docs directory. You can fetch the ABP Framework's documents from https://github.com/abpframework/abp/tree/master/docs and copy to the directory `C:\\Github\\abp\\docs` to get it work. - -- MainWebsiteUrl: `/` - -- LatestVersionBranchName: `latest` - -For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table: - -```mssql -INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', N'latest', N'', N'12f21123e08e4f15bedbae0b2d939659') -``` - -Add one of the sample projects above and run the application. In the menu you will see `Documents` link, click the menu link to open the documents page. - -So far, we have created a new application from abp.io website and made it up and ready for Docs module. - -### 7- Creating a New Document - -In the sample Project records, you see that `Format` is specified as `md` which refers to [Mark Down](https://en.wikipedia.org/wiki/Markdown). You can see the mark down cheat sheet following the below link; - -https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet - -ABP Docs Module can render mark down to HTML. - -Now let's have a look a sample document in markdown format. - -~~~markdown -# This is a header - -Welcome to Docs Module. - -## This is a sub header - - [This is a link](https://abp.io) - -![This is an image](https://abp.io/assets/my-image.png) - -## This is a code block - -```csharp -public class Person -{ - public string Name { get; set; } - - public string Address { get; set; } -} -``` -~~~ - -As an example you can see ABP Framework documentation: - -[https://github.com/abpframework/abp/blob/master/docs/en/](https://github.com/abpframework/abp/blob/master/docs/en/) - -#### Conditional sections feature (Using Scriban) - -Docs module uses [Scriban](https://github.com/lunet-io/scriban/tree/master/doc) for conditionally show or hide some parts of a document. In order to use that feature, you have to create a JSON file as **Parameter document** per every language. It will contain all the key-values, as well as their display names. - -For example, [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/blob/master/en/docs-params.json): - -```json -{ - "parameters": [{ - "name": "UI", - "displayName": "UI", - "values": { - "MVC": "MVC / Razor Pages", - "NG": "Angular" - } - }, - { - "name": "DB", - "displayName": "Database", - "values": { - "EF": "Entity Framework Core", - "Mongo": "MongoDB" - } - }, - { - "name": "Tiered", - "displayName": "Tiered", - "values": { - "No": "Not Tiered", - "Yes": "Tiered" - } - }] -} -``` - -Since not every single document in your projects may not have sections or may not need all of those parameters, you have to declare which of those parameters will be used for sectioning the document, as a JSON block anywhere on the document. - -For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md): - -``` -..... - -​```json -//[doc-params] -{ - "UI": ["MVC","NG"], - "DB": ["EF", "Mongo"], - "Tiered": ["Yes", "No"] -} -​``` - -........ -``` - -This section will be automatically deleted during render. And f course, those key values must match with the ones in **Parameter document**. - -![Interface](../images/docs-section-ui.png) - -Now you can use **Scriban** syntax to create sections in your document. - -For example: - -```` -{{ if UI == "NG" }} - -* `-u` argument specifies the UI framework, `angular` in this case. - -{{ end }} - -{{ if DB == "Mongo" }} - -* `-d` argument specifies the database provider, `mongodb` in this case. - -{{ end }} - -{{ if Tiered == "Yes" }} - -* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated. - -{{ end }} - -```` - -You can also use variables in a text, adding **_Value** postfix to its key: - -```txt -This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. -``` - -Also, **Document_Language_Code** and **Document_Version** keys are pre-defined if you want to get the language code or the version of the current document (This may be useful for creating links that redirects to another documentation system in another domain). - ------- - -**IMPORTANT NOTICE**: Scriban uses "{{" and "}}" for syntax. Therefore, you must use escape blocks if you are going to use those in your document (an Angular document, for example). See [Scriban docs](https://github.com/lunet-io/scriban/blob/master/doc/language.md#13-escape-block) for more information. - -### 8- Creating the Navigation Document - -Navigation document is the main menu of the documents page. It is located on the left side of the page. It is a `JSON` file. Take a look at the below sample navigation document to understand the structure. - -```json -{ - "items":[ - { - "text":"Sample Menu Item - 1", - "items":[ - { - "text":"Sample Menu Item - 1.1", - "items":[ - { - "text":"Sample Menu Item - 1.1.1", - "path":"SampleMenuItem_1_1_1.md" - } - ] - }, - { - "text":"Sample Menu Item - 1.2", - "items":[ - { - "text":"Sample Menu Item - 1.2.1", - "path":"SampleMenuItem_1_2_1.md" - }, - { - "text":"Sample Menu Item - 1.2.2", - "path":"SampleMenuItem_1_2_2.md" - } - ] - } - ] - }, - { - "text":"Sample Menu Item - 2", - "items":[ - { - "text":"Sample Menu Item - 2.1", - "items":[ - { - "text":"Sample Menu Item - 2.1.1", - "path":"SampleMenuItem_2_1_1.md" - } - ] - } - ] - } - ] -} -``` - -The upper sample `JSON` file renders the below navigation menu as `HTML`. - -![Navigation menu](../images/docs-module_download-sample-navigation-menu.png) - -Finally a new Docs Module is added to your project which is feeded with GitHub. - - -## Full-Text Search(Elastic Search) - -The Docs module supports full-text search using Elastic Search. It is not enabled by default. You can configure `DocsElasticSearchOptions` to enable it. - -```csharp -Configure(options => -{ - options.Enable = true; - options.IndexName = "your_index_name"; //default IndexName is abp_documents -}); -``` - -The `Index` is automatically created after the application starts if the `Index` does not exist. - -`DefaultElasticClientProvider` is responsible for creating `IElasticClient`. By default, it reads Elastic Search's `Url` from `IConfiguration`. -If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](../Dependency-Injection.md) system. - -```json -{ - "ElasticSearch": { - "Url": "http://localhost:9200" - } -} -``` - - -## Row Highlighting - -You can apply highlight to specific code lines or a range of sequential lines. -See the following examples: - -``` - ```C# {3, 5} - public class Book : Entity - { - public string Name { get; set; } - public string Surname { get; set; } - } - ``` -``` - -``` - ```C# {2-4} - public class Book : Entity - { - public string Name { get; set; } - public string Surname { get; set; } - } - ``` -``` - -``` - ```C# {1, 2-4} - public class Book : Entity - { - public string Name { get; set; } - public string Surname { get; set; } - } - ``` -``` - ---- - - - -## Next - -Docs Module is also available as a standalone application. Check out [VoloDocs](../Apps/VoloDocs). diff --git a/docs/en/Modules/Feature-Management.md b/docs/en/Modules/Feature-Management.md deleted file mode 100644 index d6cf470391..0000000000 --- a/docs/en/Modules/Feature-Management.md +++ /dev/null @@ -1,106 +0,0 @@ -# Feature Management Module - -The Feature Management module implements the `IFeatureManagementStore` interface defined by the [Feature System](../Features.md). - -> This document covers only the feature management module which persists feature values to a database. See [the features](../Features.md) document for more about the feature system. - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/feature-management). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## User Interface - -### Feature Management Dialog - -Feature management module provides a reusable dialog to manage features related to an object. For example, the [Tenant Management Module](Tenant-Management.md) uses it to manage features of tenants in the Tenant Management page. - -![features-module-opening](../images/features-module-opening.png) - -When you click *Actions* -> *Features* for a tenant, the feature management dialog is opened. An example screenshot from this dialog with two features defined: - -![features-modal](../images/features-modal.png) - -In this dialog, you can enable, disable or set values for the features for a tenant. - -## IFeatureManager - -`IFeatureManager` is the main service provided by this module. It is used to read and change the setting values for the tenants in a multi-tenant application. `IFeatureManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a feature value. - -> If you just want to read feature values, use the `IFeatureChecker` as explained in the [Features document](../Features.md). - -**Example: Get/set a feature's value for a tenant** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.FeatureManagement; - -namespace Demo -{ - public class MyService : ITransientDependency - { - private readonly IFeatureManager _featureManager; - - public MyService(IFeatureManager featureManager) - { - _featureManager = featureManager; - } - - public async Task SetFeatureDemoAsync(Guid tenantId, string value) - { - await _featureManager - .SetForTenantAsync(tenantId, "Feature1", value); - - var currentValue = await _featureManager - .GetOrNullForTenantAsync("Feature1", tenantId); - } - } -} -```` - -## Feature Management Providers - -Features Management Module is extensible, just like the [features system](../Features.md). You can extend it by defining feature management providers. There are 3 pre-built feature management providers registered it the following order: - -* `DefaultValueFeatureManagementProvider`: Gets the value from the default value of the feature definition. It can not set the default value since default values are hard-coded on the feature definition. -* `EditionFeatureManagementProvider`: Gets or sets the feature values for an edition. Edition is a group of features assigned to tenants. Edition system has not implemented by the Tenant Management module. You can implement it yourself or purchase the ABP Commercial [SaaS Module](https://commercial.abp.io/modules/Volo.Saas) which implements it and also provides more SaaS features, like subscription and payment. -* `TenantFeatureManagementProvider`: Gets or sets the features values for tenants. - -`IFeatureManager` uses these providers on get/set methods. Typically, every feature management provider defines extension methods on the `IFeatureManager` service (like `SetForTenantAsync` defined by the tenant feature management provider). - -If you want to create your own provider, implement the `IFeatureManagementProvider` interface or inherit from the `FeatureManagementProvider` base class: - -````csharp -public class CustomFeatureProvider : FeatureManagementProvider -{ - public override string Name => "Custom"; - - public CustomFeatureProvider(IFeatureManagementStore store) - : base(store) - { - } -} -```` - -`FeatureManagementProvider` base class makes the default implementation (using the `IFeatureManagementStore`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each feature value record). - -Once you create your provider class, you should register it using the `FeatureManagementOptions` [options class](../Options.md): - -````csharp -Configure(options => -{ - options.Providers.Add(); -}); -```` - -The order of the providers are important. Providers are executed in the reverse order. That means the `CustomFeatureProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. - -## See Also - -* [Features](../Features.md) - diff --git a/docs/en/Modules/Identity.md b/docs/en/Modules/Identity.md deleted file mode 100644 index f73e8073b2..0000000000 --- a/docs/en/Modules/Identity.md +++ /dev/null @@ -1,322 +0,0 @@ -# Identity Management Module - -Identity module is used to manage roles, users and their permissions, based on the [Microsoft Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity). - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/identity). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## User Interface - -This module provides [Blazor](../UI/Blazor/Overall.md), [Angular](../UI/Angular/Quick-Start.md) and [MVC / Razor Pages](../UI/AspNetCore/Overall.md) UI options. - -### Menu Items - -This module adds an *Identity management* menu item under the *Administration* menu: - -![identity-module-menu](../images/identity-module-menu.png) - -The menu items and the related pages are authorized. That means the current user must have the related permissions to make them visible. The `admin` role (and the users with this role - like the `admin` user) already has these permissions. If you want to enable permissions for other roles/users, open the *Permissions* dialog on the *Roles* or *Users* page and check the permissions as shown below: - -![identity-module-permissions](../images/identity-module-permissions.png) - -See the [Authorization document](../Authorization.md) to understand the permission system. - -### Pages - -This section introduces the main pages provided by this module. - -#### Users - -This page is used to see the list of users. You can create/edit and delete users, assign users to roles. - -![identity-module-users](../images/identity-module-users.png) - -A user can have zero or more roles. Users inherit permissions from their roles. In addition, you can assign permissions directly to the users (by clicking the *Actions* button, then selecting the *Permissions*). - -#### Roles - -Roles are used to group permissions assign them to users. - -![identity-module-roles](../images/identity-module-roles.png) - -Beside the role name, there are two properties of a role: - -* `Default`: If a role is marked as "default", then that role is assigned to new users by default when they register to the application themselves (using the [Account Module](Account.md)). -* `Public`: A public role of a user can be seen by other users in the application. This feature has no usage in the Identity module, but provided as a feature that you may want to use in your own application. - -## Other Features - -This section covers some other features provided by this module which don't have the UI pages. - -### Organization Units - -Organization Units (OU) can be used to **hierarchically group users and entities**. - -#### OrganizationUnit Entity - -An OU is represented by the **OrganizationUnit** entity. The fundamental properties of this entity are: - -- **TenantId**: Tenant's Id of this OU. Can be null for host OUs. -- **ParentId**: Parent OU's Id. Can be null if this is a root OU. -- **Code**: A hierarchical string code that is unique for a tenant. -- **DisplayName**: Shown name of the OU. - -#### Organization Tree - -Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. There are some rules for this tree; - -- There can be more than one root (where the `ParentId` is `null`). -- There is a limit for the first-level children count of an OU (because of the fixed OU Code unit length explained below). - -#### OU Code - -OU code is automatically generated and maintained by the `OrganizationUnitManager` service. It's a string that looks something like this: - -"**00001.00042.00005**" - -This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code (automatically applied when you use `OrganizationUnitManager`): - -- It is **unique** for a [tenant](../Multi-Tenancy.md). -- All the children of the same OU have codes that **start with the parent OU's code**. -- It's **fixed length** and based on the level of the OU in the tree, as shown in the sample. -- While the OU code is unique, it can be **changed** if you move the related OU. - -Notice that you must reference an OU by Id, not Code, because the Code can be changed later. - -#### OrganizationUnit Manager - -The `OrganizationUnitManager` class can be [injected](../Dependency-Injection.md) and used to manage OUs. Common use cases are: - -- Create, Update or Delete an OU -- Move an OU in the OU tree. -- Getting information about the OU tree and its items. - -### Identity Security Log - -The security log system records some important operations or changes about your account (like *login* and *change password*). You can also save the security log if needed. - -You can inject and use `IdentitySecurityLogManager` or `ISecurityLogManager` to write security logs. It will create a log object by default and fill in some common values, such as `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`, etc. Of course, you can override them. - -```cs -await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() -{ - Identity = "IdentityServer", - Action = "ChangePassword" -}); -``` - -Configure `AbpSecurityLogOptions` to provide the application name (in case of you have multiple applications and want to distinguish the applications in the logs) for the log or disable this feature. - -```cs -Configure(options => -{ - options.ApplicationName = "AbpSecurityTest"; -}); -``` - -## Options - -`IdentityOptions` is the standard [options class](../Options.md) provided by the Microsoft [Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity). So, you can set these options in the `ConfigureServices` method of your [module](../Module-Development-Basics.md) class. - -**Example: Set minimum required length of passwords** - -````csharp -Configure(options => -{ - options.Password.RequiredLength = 5; -}); -```` - -ABP takes these options one step further and allows you to change them on runtime by using the [setting system](../Settings.md). You can [inject](../Dependency-Injection.md) `ISettingManager` and use one of the `Set...` methods to change the option values for a user, a tenant or globally for all users. - -**Example: Change minimum required length of passwords for the current tenant** - -````csharp -public class MyService : ITransientDependency -{ - private readonly ISettingManager _settingManager; - - public MyService(ISettingManager settingManager) - { - _settingManager = settingManager; - } - - public async Task ChangeMinPasswordLength(int minLength) - { - await _settingManager.SetForCurrentTenantAsync( - IdentitySettingNames.Password.RequiredLength, - minLength.ToString() - ); - } -} -```` - -`IdentitySettingNames` class (in the `Volo.Abp.Identity.Settings` namespace) defines constants for the setting names. - -## Distributed Events - -This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module; - -* `UserEto` is published on changes done on an `IdentityUser` entity. -* `IdentityRoleEto` is published on changes done on an `IdentityRole` entity. -* `IdentityClaimTypeEto` is published on changes done on an `IdentityClaimType` entity. -* `OrganizationUnitEto` is published on changes done on an `OrganizationUnit` entity. - -**Example: Get notified when a new user has been created** - -````csharp -public class MyHandler : - IDistributedEventHandler>, - ITransientDependency -{ - public async Task HandleEventAsync(EntityCreatedEto eventData) - { - UserEto user = eventData.Entity; - // TODO: ... - } -} -```` - -`UserEto` and `IdentityRoleEto` are configured to automatically publish the events. You should configure yourself for the others. See the [Distributed Event Bus document](../Distributed-Event-Bus.md) to learn details of the pre-defined events. - -> Subscribing to the distributed events is especially useful for distributed scenarios (like microservice architecture). If you are building a monolithic application, or listening events in the same process that runs the Identity Module, then subscribing to the [local events](../Local-Event-Bus.md) can be more efficient and easier. - -## Internals - -This section covers some internal details of the module that you don't need much, but may need to use in some cases. - -### Domain layer - -#### Aggregates - -##### User - -A user is generally a person logins to and uses the application. - -* `IdentityUser` (aggregate root): Represents a user in the system. - * `IdentityUserRole` (collection): Roles to the user. - * `IdentityUserClaim` (collection): Custom claims of the user. - * `IdentityUserLogin` (collection): External logins of the user. - * `IdentityUserToken` (collection): Tokens of the user (used by the Microsoft Identity services). - -##### Role - -A role is typically a group of permissions to assign to the users. - -* `IdentityRole` (aggregate root): Represents a role in the system. - * `IdentityRoleClaim` (collection): Custom claims of the role. - -##### Claim Type - -A claim type is a definition of a custom claim that can be assigned to other entities (like roles and users) in the system. - -* `IdentityClaimType` (aggregate root): Represents a claim type definition. It contains some properties (e.g. Required, Regex, Description, ValueType) to define the claim type and the validation rules. - -##### Identity Security Log - -A `IdentitySecurityLog` object represents an authentication related operation (like *login*) in the system. - -* `IdentitySecurityLog` (aggregate root): Represents a security log in the system. - -##### OrganizationUnit - -An Organization unit is a entity in a hierarchical structure. - -* ```OrganizationUnit``` (aggregate root): Represents an organization unit in the system. - * ```Roles``` (collection): Roles of the organization unit. - -#### Repositories - -Following custom repositories are defined for this module: - -* `IIdentityUserRepository` -* `IIdentityRoleRepository` -* `IIdentityClaimTypeRepository` -* ```IIdentitySecurityLogRepository``` -* ```IOrganizationUnitRepository``` - -#### Domain services - -##### User manager - -`IdentityUserManager` is used to manage users, their roles, claims, passwords, emails, etc. It is derived from Microsoft Identity's `UserManager` class where `T` is `IdentityUser`. - -##### Role manager - -`IdentityRoleManager` is used to manage roles and their claims. It is derived from Microsoft Identity's `RoleManager` class where `T` is `IdentityRole`. - -##### Claim type manager - -`IdenityClaimTypeManager` is used to perform some operations for the `IdentityClaimType` aggregate root. - -##### Organization unit manager - -```OrganizationUnitManager``` is used to perform some operations for the ```OrganizationUnit``` aggregate root. - -##### Security log manager - -```IdentitySecurityLogManager``` is used to save security logs. - -### Application Layer - -#### Application Services - -* `IdentityUserAppService` (implements `IIdentityUserAppService`): Implements the use cases of the user management UI. -* `IdentityRoleAppService` (implement `IIdentityRoleAppService`): Implements the use cases of the role management UI. -* `IdentityClaimTypeAppService` (implements `IIdentityClaimTypeAppService`): Implements the use cases of the claim type management UI. -* `IdentitySettingsAppService` (implements `IIdentitySettingsAppService`): Used to get and update settings for the Identity module. -* `IdentityUserLookupAppService` (implements `IIdentityUserLookupAppService`): Used to get information for a user by `id` or `userName`. It is aimed to be used internally by the ABP framework. -* `ProfileAppService` (implements `IProfileAppService`): Used to change a user's profile and the password. -* ```IdentitySecurityLogAppService``` (implements ```IIdentitySecurityLogAppService```): Implements the use cases of the security logs UI. -* ```OrganizationUnitAppService``` (implements ```OrganizationUnitAppService```): Implements the use cases of the organization unit management UI. - -### Database Providers - -This module provides [Entity Framework Core](../Entity-Framework-Core.md) and [MongoDB](../MongoDB.md) options for the database. - -#### Entity Framework Core - -[Volo.Abp.Identity.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.Identity.EntityFrameworkCore) NuGet package implements the EF Core integration. - -##### Database Tables - -* **AbpRoles** - * AbpRoleClaims -* **AbpUsers** - * AbpUserClaims - * AbpUserLogins - * AbpUserRoles - * AbpUserTokens -* **AbpClaimTypes** -* **AbpOrganizationUnits** - * AbpOrganizationUnitRoles - * AbpUserOrganizationUnits -* **AbpSecurityLogs** - -#### MongoDB - -[Volo.Abp.Identity.MongoDB](https://www.nuget.org/packages/Volo.Abp.Identity.MongoDB) NuGet package implements the MongoDB integration. - -##### Database Collections - -* **AbpRoles** -* **AbpUsers** -* **AbpClaimTypes** -* **AbpOrganizationUnits** -* **AbpSecurityLogs** - -#### Common Database Properties - -You can set the following properties of the `AbpIdentityDbProperties` class to change the database options: - -* `DbTablePrefix` (`Abp` by default) is the prefix for table/collection names. -* `DbSchema` (`null` by default) is the database schema. -* `ConnectionStringName` (`AbpIdentity` by default) is the [connection string](../Connection-Strings.md) name for this module. - -These are static properties. If you want to set, do it in the beginning of your application (typically, in `Program.cs`). - diff --git a/docs/en/Modules/IdentityServer.md b/docs/en/Modules/IdentityServer.md deleted file mode 100644 index 90c08f6c6b..0000000000 --- a/docs/en/Modules/IdentityServer.md +++ /dev/null @@ -1,175 +0,0 @@ -# IdentityServer Module - -IdentityServer module provides a full integration with the [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) (IDS) framework, which provides advanced authentication features like single sign-on and API access control. This module persists clients, resources and other IDS-related objects to database. **This module is replaced by** [OpenIddict module](https://docs.abp.io/en/abp/latest/Modules/OpenIddict) after ABP v6.0 in the startup templates. - -> Note: You can not use IdentityServer and OpenIddict modules together. They are separate OpenID provider libraries for the same job. - -## How to Install - -You don't need this module when you are using OpenIddict module. However, if you want to keep using IdentityServer4 for your applications, you can install this module and remove the OpenIddict module. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/identityserver). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## User Interface - -This module implements the domain logic and database integrations, but not provides any UI. Management UI is useful if you need to add clients and resources on the fly. In this case, you may build the management UI yourself or consider to purchase the [ABP Commercial](https://commercial.abp.io/) which provides the management UI for this module. - -## Relations to Other Modules - -This module is based on the [Identity Module](Identity.md) and have an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.IdentityServer) with the [Account Module](Account.md). - -## Options - -### AbpIdentityServerBuilderOptions - -`AbpIdentityServerBuilderOptions` can be configured in `PreConfigureServices` method of your Identity Server [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -```` - -`AbpIdentityServerBuilderOptions` properties: - -* `UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap` (default: true): Updates `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap` to be compatible with Identity Server claims. -* `UpdateAbpClaimTypes` (default: true): Updates `AbpClaimTypes` to be compatible with identity server claims. -* `IntegrateToAspNetIdentity` (default: true): Integrate to ASP.NET Identity. -* `AddDeveloperSigningCredential` (default: true): Set false to suppress AddDeveloperSigningCredential() call on the IIdentityServerBuilder. - -`IIdentityServerBuilder` can be configured in `PreConfigureServices` method of your Identity Server [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - builder.AddSigningCredential(...); - }); -} -```` - -## Internals - -### Domain Layer - -#### Aggregates - -##### ApiResource - -API Resources are needed for allowing clients to request access tokens. - -* `ApiResource` (aggregate root): Represents an API resource in the system. - * `ApiSecret` (collection): secrets of the API resource. - * `ApiScope` (collection): scopes of the API resource. - * `ApiResourceClaim` (collection): claims of the API resource. - -##### Client - -Clients represent applications that can request tokens from your Identity Server. - -* `Client` (aggregate root): Represents an Identity Server client application. - * `ClientScope` (collection): Scopes of the client. - * `ClientSecret` (collection): Secrets of the client. - * `ClientGrantType` (collection): Grant types of the client. - * `ClientCorsOrigin` (collection): CORS origins of the client. - * `ClientRedirectUri` (collection): redirect URIs of the client. - * `ClientPostLogoutRedirectUri` (collection): Logout redirect URIs of the client. - * `ClientIdPRestriction` (collection): Provider restrictions of the client. - * `ClientClaim` (collection): Claims of the client. - * `ClientProperty` (collection): Custom properties of the client. - -##### PersistedGrant - -Persisted Grants stores AuthorizationCodes, RefreshTokens and UserConsent. - -* `PersistedGrant` (aggregate root): Represents PersistedGrant for identity server. - -##### IdentityResource - -Identity resources are data like user ID, name, or email address of a user. - -* `IdentityResource` (aggregate root): Represents and Identity Server identity resource. - * `IdentityClaim` (collection): Claims of identity resource. - -#### Repositories - -Following custom repositories are defined for this module: - -* `IApiResourceRepository` -* `IClientRepository` -* `IPersistentGrantRepository` -* `IIdentityResourceRepository` - -#### Domain Services - -This module doesn't contain any domain service but overrides the services below; - -* `AbpProfileService` (Used when `AbpIdentityServerBuilderOptions.IntegrateToAspNetIdentity` is true) -* `AbpClaimsService` -* `AbpCorsPolicyService` - -### Settings - -This module doesn't define any settings. - -### Application Layer - -#### Application Services - -* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. -* `IdentityServerClaimTypeAppService` (implement `IIdentityServerClaimTypeAppService`): Used to get list of claims. -* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. -* `IdentityResourceAppService` (implements `IIdentityResourceAppService`): Implements the use cases of the Identity resource management UI. - -### Database Providers - -#### Common - -##### Table/Collection Prefix & Schema - -All tables/collections use the `IdentityServer` prefix by default. Set static properties on the `AbpIdentityServerDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection String - -This module uses `AbpIdentityServer` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -* **IdentityServerApiResources** - * IdentityServerApiSecrets - * IdentityServerApiScopes - * IdentityServerApiScopeClaims - * IdentityServerApiClaims -* **IdentityServerClients** - * IdentityServerClientScopes - * IdentityServerClientSecrets - * IdentityServerClientGrantTypes - * IdentityServerClientCorsOrigins - * IdentityServerClientRedirectUris - * IdentityServerClientPostLogoutRedirectUris - * IdentityServerClientIdPRestrictions - * IdentityServerClientClaims - * IdentityServerClientProperties -* **IdentityServerPersistedGrants** -* **IdentityServerIdentityResources** - * IdentityServerIdentityClaims - -#### MongoDB - -##### Collections - -* **IdentityServerApiResources** -* **IdentityServerClients** -* **IdentityServerPersistedGrants** -* **IdentityServerIdentityResources** \ No newline at end of file diff --git a/docs/en/Modules/Index.md b/docs/en/Modules/Index.md deleted file mode 100644 index 5e280268e7..0000000000 --- a/docs/en/Modules/Index.md +++ /dev/null @@ -1,32 +0,0 @@ -# Application Modules - -ABP is a **modular application framework** which consists of dozens of **NuGet & NPM packages**. It also provides a complete infrastructure to build your own application modules which may have entities, services, database integration, APIs, UI components and so on. - -There are **two types of modules.** They don't have any structural difference but are categorized by functionality and purpose: - -* [**Framework modules**](https://github.com/abpframework/abp/tree/dev/framework/src): These are **core modules of the framework** like caching, emailing, theming, security, serialization, validation, EF Core integration, MongoDB integration... etc. They do not have application/business functionalities but makes your daily development easier by providing common infrastructure, integration and abstractions. -* [**Application modules**](https://github.com/abpframework/abp/tree/dev/modules): These modules implement specific application/business functionalities like blogging, document management, identity management, tenant management... etc. They generally have their own entities, services, APIs and UI components. - -## Open Source Application Modules - -There are some **free and open source** application modules developed and maintained as a part of the ABP Framework. - -* [**Account**](Account.md): Provides UI for the account management and allows user to login/register to the application. -* [**Audit Logging**](Audit-Logging.md): Persists audit logs to a database. -* [**Background Jobs**](Background-Jobs.md): Persist background jobs when using the default background job manager. -* [**CMS Kit**](Cms-Kit/Index.md): A set of reusable *Content Management System* features. -* [**Docs**](Docs.md): Used to create technical documentation website. ABP's [own documentation](https://docs.abp.io) already using this module. -* [**Feature Management**](Feature-Management.md): Used to persist and manage the [features](../Features.md). -* **[Identity](Identity.md)**: Manages organization units, roles, users and their permissions, based on the Microsoft Identity library. -* [**IdentityServer**](IdentityServer.md): Integrates to IdentityServer4. -* [**OpenIddict**](OpenIddict.md): Integrates to OpenIddict. -* [**Permission Management**](Permission-Management.md): Used to persist permissions. -* **[Setting Management](Setting-Management.md)**: Used to persist and manage the [settings](../Settings.md). -* [**Tenant Management**](Tenant-Management.md): Manages tenants for a [multi-tenant](../Multi-Tenancy.md) application. -* [**Virtual File Explorer**](Virtual-File-Explorer.md): Provided a simple UI to view files in [virtual file system](../Virtual-File-System.md). - -See [the GitHub repository](https://github.com/abpframework/abp/tree/dev/modules) for source code of all modules. - -## Commercial Application Modules - -[ABP Commercial](https://commercial.abp.io/) license provides **additional pre-built application modules** on top of the ABP framework. See the [module list](https://commercial.abp.io/modules) provided by the ABP Commercial. diff --git a/docs/en/Modules/OpenIddict.md b/docs/en/Modules/OpenIddict.md deleted file mode 100644 index fe0b8270d3..0000000000 --- a/docs/en/Modules/OpenIddict.md +++ /dev/null @@ -1,515 +0,0 @@ -## ABP OpenIddict Module - -OpenIddict module provides an integration with the [OpenIddict](https://github.com/openiddict/openiddict-core) which provides advanced authentication features like single sign-on, single log-out, and API access control. This module persists applications, scopes, and other OpenIddict-related objects to the database. - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as a package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/openiddict). The source code is licensed by [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## User Interface - -This module implements the domain logic and database integrations but does not provide any UI. Management UI is useful if you need to add applications and scopes on the fly. In this case, you may build the management UI yourself or consider purchasing the [ABP Commercial](https://commercial.abp.io/) which provides the management UI for this module. - -## Relations to Other Modules - -This module is based on the [Identity Module](Identity.md) and has an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) with the [Account Module](Account.md). - -## Options - -### OpenIddictBuilder - -`OpenIddictBuilder` can be configured in the `PreConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). - -Example: - -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -``` - -`OpenIddictBuilder` contains various extension methods to configure the OpenIddict services: - -- `AddServer()` registers the OpenIddict token server services in the DI container. Contains `OpenIddictServerBuilder` configurations. -- `AddCore()` registers the OpenIddict core services in the DI container. Contains `OpenIddictCoreBuilder` configurations. -- `AddValidation()` registers the OpenIddict token validation services in the DI container. Contains `OpenIddictValidationBuilder` configurations. - -### OpenIddictCoreBuilder - -`OpenIddictCoreBuilder` contains extension methods to configure the OpenIddict core services. - -Example: - -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -``` - -These services contain: - -- Adding `ApplicationStore`, `AuthorizationStore`, `ScopeStore`, `TokenStore`. -- Replacing `ApplicationManager`, `AuthorizationManager`, `ScopeManager`, `TokenManager`. -- Replacing `ApplicationStoreResolver`, `AuthorizationStoreResolver`, `ScopeStoreResolver`, `TokenStoreResolver`. -- Setting `DefaultApplicationEntity`, `DefaultAuthorizationEntity`, `DefaultScopeEntity`, `DefaultTokenEntity`. - -### OpenIddictServerBuilder - -`OpenIddictServerBuilder` contains extension methods to configure OpenIddict server services. - -Example: - -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -``` - -These services contain: - -- Registering claims, scopes. -- Setting the `Issuer` URI that is used as the base address for the endpoint URIs returned from the discovery endpoint. -- Adding development signing keys, encryption/signing keys, credentials, and certificates. -- Adding/removing event handlers. -- Enabling/disabling grant types. -- Setting authentication server endpoint URIs. - -### OpenIddictValidationBuilder - -`OpenIddictValidationBuilder` contains extension methods to configure OpenIddict validation services. - -Example: - -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -``` - -These services contain: - -- `AddAudiences()` for resource servers. -- `SetIssuer()` URI that is used to determine the actual location of the OAuth 2.0/OpenID Connect configuration document when using provider discovery. -- `SetConfiguration()` to configure `OpenIdConnectConfiguration`. -- `UseIntrospection()` to use introspection instead of local/direct validation. -- Adding encryption key, credentials, and certificates. -- Adding/removing event handlers. -- `SetClientId() ` to set the client identifier `client_id ` when communicating with the remote authorization server (e.g for introspection). -- `SetClientSecret()` to set the identifier `client_secret` when communicating with the remote authorization server (e.g for introspection). -- `EnableAuthorizationEntryValidation()` to enable authorization validation to ensure the `access token` is still valid by making a database call for each API request. *Note:* This may have a negative impact on performance and can only be used with an OpenIddict-based authorization server. -- `EnableTokenEntryValidation()` to enable authorization validation to ensure the `access token` is still valid by making a database call for each API request. *Note:* This may have a negative impact on performance and it is required when the OpenIddict server is configured to use reference tokens. -- `UseLocalServer()` to register the OpenIddict validation/server integration services. -- `UseAspNetCore()` to register the OpenIddict validation services for ASP.NET Core in the DI container. - -## Internals - -### Domain Layer - -#### Aggregates - -##### OpenIddictApplication - -OpenIddictApplications represent the applications that can request tokens from your OpenIddict Server. - -- `OpenIddictApplications` (aggregate root): Represents an OpenIddict application. - - `ClientId` (string): The client identifier associated with the current application. - - `ClientSecret` (string): The client secret associated with the current application. Maybe hashed or encrypted for security reasons. - - `ConsentType` (string): The consent type associated with the current application. - - `DisplayName` (string): The display name associated with the current application. - - `DisplayNames` (string): The localized display names associated with the current application serialized as a JSON object. - - `Permissions` (string): The permissions associated with the current application, serialized as a JSON array. - - `PostLogoutRedirectUris` (string): The logout callback URLs associated with the current application, serialized as a JSON array. - - `Properties` (string): The additional properties associated with the current application serialized as a JSON object or null. - - `RedirectUris` (string): The callback URLs associated with the current application, serialized as a JSON array. - - `Requirements` (string): The requirements associated with the current application - - `Type` (string): The application type associated with the current application. - - `ClientUri` (string): URI to further information about client. - - `LogoUri` (string): URI to client logo. - -##### OpenIddictAuthorization - -OpenIddictAuthorizations are used to keep the allowed scopes, authorization flow types. - -- `OpenIddictAuthorization` (aggregate root): Represents an OpenIddict authorization. - - - `ApplicationId` (Guid?): The application associated with the current authorization. - - - `Properties` (string): The additional properties associated with the current authorization serialized as a JSON object or null. - - - `Scopes` (string): The scopes associated with the current authorization, serialized as a JSON array. - - - `Status` (string): The status of the current authorization. - - - `Subject` (string): The subject associated with the current authorization. - - - `Type` (string): The type of the current authorization. - -##### OpenIddictScope - -OpenIddictScopes are used to keep the scopes of resources. - -- `OpenIddictScope` (aggregate root): Represents an OpenIddict scope. - - - `Description` (string): The public description associated with the current scope. - - - `Descriptions` (string): The localized public descriptions associated with the current scope, serialized as a JSON object. - - - `DisplayName` (string): The display name associated with the current scope. - - - `DisplayNames` (string): The localized display names associated with the current scope serialized as a JSON object. - - - `Name` (string): The unique name associated with the current scope. - - `Properties` (string): The additional properties associated with the current scope serialized as a JSON object or null. - - `Resources` (string): The resources associated with the current scope, serialized as a JSON array. - -##### OpenIddictToken - -OpenIddictTokens are used to persist the application tokens. - -- `OpenIddictToken` (aggregate root): Represents an OpenIddict token. - - - `ApplicationId` (Guid?): The application associated with the current token. - - `AuthorizationId` (Guid?): The application associated with the current token. - - `CreationDate` (DateTime?): The UTC creation date of the current token. - - `ExpirationDate` (DateTime?): The UTC expiration date of the current token. - - `Payload` (string): The payload of the current token, if applicable. Only used for reference tokens and may be encrypted for security reasons. - - - `Properties` (string): The additional properties associated with the current token serialized as a JSON object or null. - - `RedemptionDate` (DateTime?): The UTC redemption date of the current token. - - `Status` (string): The status of the current authorization. - - - `ReferenceId` (string): The reference identifier associated with the current token, if applicable. Only used for reference tokens and may be hashed or encrypted for security reasons. - - - `Status` (string): The status of the current token. - - - `Subject` (string): The subject associated with the current token. - - - `Type` (string): The type of the current token. - -#### Stores - -This module implements OpenIddict stores: - -- `IAbpOpenIdApplicationStore` -- `IOpenIddictAuthorizationStore` -- `IOpenIddictScopeStore` -- `IOpenIddictTokenStore` - -#### AbpOpenIddictStoreOptions - -You can configure the `PruneIsolationLevel/DeleteIsolationLevel` of `AbpOpenIddictStoreOptions` to set the isolation level for the store operations becasue different databases have different isolation levels. - -##### Repositories - -The following custom repositories are defined in this module: - -- `IOpenIddictApplicationRepository` -- `IOpenIddictAuthorizationRepository` -- `IOpenIddictScopeRepository` -- `IOpenIddictTokenRepository` - -##### Domain Services - -This module doesn't contain any domain service but overrides the service below: - -- `AbpApplicationManager` used to populate/get `AbpApplicationDescriptor` information that contains `ClientUri` and `LogoUri`. - -### Database Providers - -#### Common - -##### Table/Collection Prefix & Schema - -All tables/collections use the `OpenIddict` prefix by default. Set static properties on the `AbpOpenIddictDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection String - -This module uses `AbpOpenIddict` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. - -#### Entity Framework Core - -##### Tables - -- **OpenIddictApplications** -- **OpenIddictAuthorizations** -- **OpenIddictScopes** -- **OpenIddictTokens** - -#### MongoDB - -##### Collections - -- **OpenIddictApplications** -- **OpenIddictAuthorizations** -- **OpenIddictScopes** -- **OpenIddictTokens** - -## ASP.NET Core Module - -This module integrates ASP NET Core, with built-in MVC controllers for four protocols. It uses OpenIddict's [Pass-through mode](https://documentation.openiddict.com/guides/index.html#pass-through-mode). - -```cs -AuthorizeController -> connect/authorize -TokenController -> connect/token -LogoutController -> connect/logout -UserInfoController -> connect/userinfo -``` - -> **Device flow** implementation will be done in the commercial module. - -#### AbpOpenIddictAspNetCoreOptions - -`AbpOpenIddictAspNetCoreOptions` can be configured in the `PreConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). - -Example: - -```csharp -PreConfigure(options => -{ - //Set options here... -}); -``` - -`AbpOpenIddictAspNetCoreOptions` properties: - -- `UpdateAbpClaimTypes(default: true)`: Updates `AbpClaimTypes` to be compatible with the Openiddict claims. -- `AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate. This is a certificate used for signing and encrypting the tokens and for **development environment only**. You must set it to **false** for non-development environments. - -> `AddDevelopmentEncryptionAndSigningCertificate` cannot be used in applications deployed on IIS or Azure App Service: trying to use them on IIS or Azure App Service will result in an exception being thrown at runtime (unless the application pool is configured to load a user profile). To avoid that, consider creating self-signed certificates and storing them in the X.509 certificates store of the host machine(s). Please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-development-certificate - -#### Automatically Removing Orphaned Tokens/Authorizations - -The background task that automatically removes orphaned tokens/authorizations. This can be configured by `TokenCleanupOptions` to manage it. - -`TokenCleanupOptions` can be configured in the `ConfigureServices` method of your OpenIddict [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). - -Example: - -```csharp -Configure(options => -{ - //Set options here... -}); -``` - -`TokenCleanupOptions` properties: - -- `IsCleanupEnabled` (default: true): Enable/disable token clean up. -- `CleanupPeriod` (default: 3,600,000 ms): Setting clean up period. -- `DisableAuthorizationPruning`: Setting a boolean indicating whether authorizations pruning should be disabled. -- `DisableTokenPruning`: Setting a boolean indicating whether token pruning should be disabled. -- `MinimumAuthorizationLifespan` (default: 14 days): Setting the minimum lifespan authorizations must have to be pruned. Cannot be less than 10 minutes. -- `MinimumTokenLifespan` (default: 14 days): Setting the minimum lifespan tokens must have to be pruned. Cannot be less than 10 minutes. - -#### Updating Claims In Access_token and Id_token - -[Claims Principal Factory](https://docs.abp.io/en/abp/latest/Authorization#claims-principal-factory) can be used to add/remove claims to the `ClaimsPrincipal`. - -The `AbpDefaultOpenIddictClaimsPrincipalHandler` service will add `Name`, `Email,` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`. - -Create a service that inherits from `IAbpOpenIddictClaimsPrincipalHandler` and add it to DI to fully control the destinations of claims. - -```cs -public class MyClaimDestinationsHandler : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency -{ - public virtual Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context) - { - foreach (var claim in context.Principal.Claims) - { - if (claim.Type == MyClaims.MyClaimsType) - { - claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken); - } - - if (claim.Type == MyClaims.MyClaimsType2) - { - claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken); - } - } - - return Task.CompletedTask; - } -} - -Configure(options => -{ - options.ClaimsPrincipalHandlers.Add(); -}); -``` - -For detailed information, please refer to: [OpenIddict claim destinations](https://documentation.openiddict.com/configuration/claim-destinations.html) - -#### Disable AccessToken Encryption - -ABP disables the `access token encryption` by default for compatibility, it can be enabled manually if needed. - -```cs -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - builder.Configure(options => options.DisableAccessTokenEncryption = false); - }); -} -``` - -https://documentation.openiddict.com/configuration/token-formats.html#disabling-jwt-access-token-encryption - -### Request/Response Process - -The `OpenIddict.Server.AspNetCore` adds an authentication scheme(`Name: OpenIddict.Server.AspNetCore, handler: OpenIddictServerAspNetCoreHandler`) and implements the `IAuthenticationRequestHandler` interface. - -It will be executed first in `AuthenticationMiddleware` and can short-circuit the current request. Otherwise, `DefaultAuthenticateScheme` will be called and continue to execute the pipeline. - -`OpenIddictServerAspNetCoreHandler` will call various built-in handlers (handling requests and responses), And the handler will process according to the context or skip logic that has nothing to do with it. - -Example of a token request: - -``` -POST /connect/token HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - - grant_type=password& - client_id=AbpApp& - client_secret=1q2w3e*& - username=admin& - password=1q2w3E*& - scope=AbpAPI offline_access -``` - -This request will be processed by various handlers. They will confirm the endpoint type of the request, check `HTTP/HTTPS`, verify that the request parameters (`client. scope, etc`) are valid and exist in the database, etc. Various protocol checks. And build a `OpenIddictRequest` object, If there are any errors, the response content may be set and directly short-circuit the current request. - -If everything is ok, the request will go to our processing controller(eg `TokenController`), we can get an `OpenIddictRequest` from the HTTP request at this time. The rest will be based on this object. - -Check the `username` and `password` in the request. If it is correct create a `ClaimsPrincipal` object and return a `SignInResult`, which uses the `OpenIddict.Validation.AspNetCore` authentication scheme name, will calls `OpenIddictServerAspNetCoreHandler` for processing. - -`OpenIddictServerAspNetCoreHandler` do some checks to generate json and replace the http response content. - -The `ForbidResult` `ChallengeResult` are all the above types of processing. - -If you need to customize OpenIddict, you need to replace/delete/add new handlers and make it execute in the correct order. - -Please refer to: -https://documentation.openiddict.com/guides/index.html#events-model - -### PKCE - -https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html - -### Setting Tokens Lifetime - -Update `PreConfigureServices` method of AuthServerModule (or HttpApiHostModule if you don't have tiered/separate-authserver) file: - -```csharp -PreConfigure(builder => -{ - builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(30)); - builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(30)); - builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(30)); - builder.SetRefreshTokenLifetime(TimeSpan.FromDays(14)); -}); -``` - -### Refresh Token - -To use refresh token, it must be supported by OpenIddictServer and the `refresh_token` must be requested by the application. - -> **Note:** Angular application is already configured to use `refresh_token`. - -#### Configuring OpenIddictServer - -Update the **OpenIddictDataSeedContributor**, add `OpenIddictConstants.GrantTypes.RefreshToken` to grant types in `CreateApplicationAsync` method: - -```csharp -await CreateApplicationAsync( - ... - grantTypes: new List //Hybrid flow - { - OpenIddictConstants.GrantTypes.AuthorizationCode, - OpenIddictConstants.GrantTypes.Implicit, - OpenIddictConstants.GrantTypes.RefreshToken, - }, - ... -``` - -> **Note:** You need to re-create this client if you have generated the database already. - -#### Configuring Application: - -You need to request the **offline_access scope** to be able to receive `refresh_token`. - -In **Razor/MVC, Blazor-Server applications**, add `options.Scope.Add("offline_access");` to **OpenIdConnect** options. These application templates are using cookie authentication by default and has default cookie expire options set as: - -```csharp -.AddCookie("Cookies", options => -{ - options.ExpireTimeSpan = TimeSpan.FromDays(365); -}) -``` - -[Cookie ExpireTimeSpan will ignore access_token expiration](https://learn.microsoft.com/en-us/dotnet/api/Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions.ExpireTimeSpan?view=aspnetcore-7.0&viewFallbackFrom=net-7.0) and expired access_token will still be valid if it is set to higher value than the `refresh_token lifetime`. It is recommended to keep **Cookie ExpireTimeSpan** and the **Refresh Token lifetime** same, hence the new token will be persisted in the cookie. - -In **Blazor wasm** applications, add `options.ProviderOptions.DefaultScopes.Add("offline_access");` to **AddOidcAuthentication** options. - -In **Angular** applications, add `offline_access` to **oAuthConfig** scopes in *environment.ts* file. (Angular applications already have this configuration). - -## About localization - -We don't localize any error messages in the OpenIddict module, Because the OAuth 2.0 specification restricts the charset you're allowed to use for the error and error_description parameters: - -> A.7. "error" Syntax -> The "error" element is defined in Sections 4.1.2.1, 4.2.2.1, 5.2, 7.2, and 8.5: - -``` -error = 1*NQSCHAR -``` - -> A.8. "error_description" Syntax ->T he "error_description" element is defined in Sections 4.1.2.1, 4.2.2.1, 5.2, and 7.2: - -``` -error-description = 1*NQSCHAR -NQSCHAR = %x20-21 / %x23-5B / %x5D-7E -``` - -## Demo projects - -In the module's `app` directory there are six projects(including `angular`) - -* `OpenIddict.Demo.Server`: An abp application with integrated modules (has two `clients` and a `scope`). -* `OpenIddict.Demo.API`: ASP NET Core API application using JwtBearer authentication. -* `OpenIddict.Demo.Client.Mvc`: ASP NET Core MVC application using `OpenIdConnect` for authentication. -* `OpenIddict.Demo.Client.Console`: Use `IdentityModel` to test OpenIddict's various endpoints, and call the api of `OpenIddict.Demo.API`. -* `OpenIddict.Demo.Client.BlazorWASM:` ASP NET Core Blazor application using `OidcAuthentication` for authentication. -* `angular`: An angular application that integrates the abp ng modules and uses oauth for authentication. - -#### How to run? - -Confirm the connection string of `appsettings.json` in the `OpenIddict.Demo.Server` project. Running the project will automatically create the database and initialize the data. -After running the `OpenIddict.Demo.API` project, then you can run the rest of the projects to test. - -## Migrating Guide - -[Migrating from IdentityServer to OpenIddict Step by Step Guide ](../Migration-Guides/OpenIddict-Step-by-Step.md) diff --git a/docs/en/Modules/Permission-Management.md b/docs/en/Modules/Permission-Management.md deleted file mode 100644 index e77a284a42..0000000000 --- a/docs/en/Modules/Permission-Management.md +++ /dev/null @@ -1,109 +0,0 @@ -# Permission Management Module - -This module implements the `IPermissionStore` to store and manage permissions values in a database. - -> This document covers only the permission management module which persists permission values to a database. See the [Authorization document](../Authorization.md) to understand the authorization and permission systems. - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages). You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/permission-management). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## User Interface - -### Permission Management Dialog - -Permission management module provides a reusable dialog to manage permissions related to an object. For example, the [Identity Module](Identity.md) uses it to manage permissions of users and roles. The following image shows Identity Module's Role Management page: - -![permissions-module-open-dialog](../images/permissions-module-open-dialog.png) - -When you click *Actions* -> *Permissions* for a role, the permission management dialog is opened. An example screenshot from this dialog: - -![permissions-module-dialog](../images/permissions-module-dialog.png) - -In this dialog, you can grant permissions for the selected role. The tabs in the left side represents main permission groups and the right side contains the permissions defined in the selected group. - -## IPermissionManager - -`IPermissionManager` is the main service provided by this module. It is used to read and change the permission values. `IPermissionManager` is typically used by the *Permission Management Dialog*. However, you can inject it if you need to set a permission value. - -> If you just want to read/check permission values for the current user, use the `IAuthorizationService` or the `[Authorize]` attribute as explained in the [Authorization document](../Authorization.md). - -**Example: Grant permissions to roles and users using the `IPermissionManager` service** - -````csharp -public class MyService : ITransientDependency -{ - private readonly IPermissionManager _permissionManager; - - public MyService(IPermissionManager permissionManager) - { - _permissionManager = permissionManager; - } - - public async Task GrantRolePermissionDemoAsync( - string roleName, string permission) - { - await _permissionManager - .SetForRoleAsync(roleName, permission, true); - } - - public async Task GrantUserPermissionDemoAsync( - Guid userId, string roleName, string permission) - { - await _permissionManager - .SetForUserAsync(userId, permission, true); - } -} -```` - -## Permission Management Providers - -Permission Management Module is extensible, just like the [permission system](../Authorization.md). You can extend it by defining permission management providers. - -[Identity Module](Identity.md) defines the following permission management providers: - -* `UserPermissionManagementProvider`: Manages user-based permissions. -* `RolePermissionManagementProvider`: Manages role-based permissions. - -`IPermissionManager` uses these providers when you get/set permissions. You can define your own provider by implementing the `IPermissionManagementProvider` or inheriting from the `PermissionManagementProvider` base class. - -**Example:** - -````csharp -public class CustomPermissionManagementProvider : PermissionManagementProvider -{ - public override string Name => "Custom"; - - public CustomPermissionManagementProvider( - IPermissionGrantRepository permissionGrantRepository, - IGuidGenerator guidGenerator, - ICurrentTenant currentTenant) - : base( - permissionGrantRepository, - guidGenerator, - currentTenant) - { - } -} -```` - -`PermissionManagementProvider` base class makes the default implementation (using the `IPermissionGrantRepository`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each permission value record). - -Once you create your provider class, you should register it using the `PermissionManagementOptions` [options class](../Options.md): - -````csharp -Configure(options => -{ - options.ManagementProviders.Add(); -}); -```` - -The order of the providers are important. Providers are executed in the reverse order. That means the `CustomPermissionManagementProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. - -## See Also - -* [Authorization](../Authorization.md) \ No newline at end of file diff --git a/docs/en/Modules/Setting-Management.md b/docs/en/Modules/Setting-Management.md deleted file mode 100644 index e26c7c806e..0000000000 --- a/docs/en/Modules/Setting-Management.md +++ /dev/null @@ -1,313 +0,0 @@ -# Setting Management Module - -Setting Management Module implements the `ISettingStore` (see [the setting system](../Settings.md)) to store the setting values in a database and provides the `ISettingManager` to manage (change) the setting values in the database. - -> Setting Management module is already installed and configured for [the startup templates](../Startup-Templates/Index.md). So, most of the times you don't need to manually add this module to your application. - -## ISettingManager - -`ISettingManager` is used to get and set the values for the settings. Examples: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.SettingManagement; - -namespace Demo -{ - public class MyService : ITransientDependency - { - private readonly ISettingManager _settingManager; - - //Inject ISettingManager service - public MyService(ISettingManager settingManager) - { - _settingManager = settingManager; - } - - public async Task FooAsync() - { - Guid user1Id = ...; - Guid tenant1Id = ...; - - //Get/set a setting value for the current user or the specified user - - string layoutType1 = - await _settingManager.GetOrNullForCurrentUserAsync("App.UI.LayoutType"); - string layoutType2 = - await _settingManager.GetOrNullForUserAsync("App.UI.LayoutType", user1Id); - - await _settingManager.SetForCurrentUserAsync("App.UI.LayoutType", "LeftMenu"); - await _settingManager.SetForUserAsync(user1Id, "App.UI.LayoutType", "LeftMenu"); - - //Get/set a setting value for the current tenant or the specified tenant - - string layoutType3 = - await _settingManager.GetOrNullForCurrentTenantAsync("App.UI.LayoutType"); - string layoutType4 = - await _settingManager.GetOrNullForTenantAsync("App.UI.LayoutType", tenant1Id); - - await _settingManager.SetForCurrentTenantAsync("App.UI.LayoutType", "LeftMenu"); - await _settingManager.SetForTenantAsync(tenant1Id, "App.UI.LayoutType", "LeftMenu"); - - //Get/set a global and default setting value - - string layoutType5 = - await _settingManager.GetOrNullGlobalAsync("App.UI.LayoutType"); - string layoutType6 = - await _settingManager.GetOrNullDefaultAsync("App.UI.LayoutType"); - - await _settingManager.SetGlobalAsync("App.UI.LayoutType", "TopMenu"); - } - } -} - -```` - -So, you can get or set a setting value for different setting value providers (Default, Global, User, Tenant... etc). - -> Use the `ISettingProvider` instead of the `ISettingManager` if you only need to read the setting values, because it implements caching and supports all deployment scenarios. You can use the `ISettingManager` if you are creating a setting management UI. - -### Setting Cache - -Setting values are cached using the [distributed cache](../Caching.md) system. Always use the `ISettingManager` to change the setting values which manages the cache for you. - -## Setting Management Providers - -Setting Management module is extensible, just like the [setting system](../Settings.md). You can extend it by defining setting management providers. There are 5 pre-built setting management providers registered it the following order: - -* `DefaultValueSettingManagementProvider`: Gets the value from the default value of the setting definition. It can not set the default value since default values are hard-coded on the setting definition. -* `ConfigurationSettingManagementProvider`: Gets the value from the [IConfiguration service](../Configuration.md). It can not set the configuration value because it is not possible to change the configuration values on runtime. -* `GlobalSettingManagementProvider`: Gets or sets the global (system-wide) value for a setting. -* `TenantSettingManagementProvider`: Gets or sets the setting value for a tenant. -* `UserSettingManagementProvider`: Gets the setting value for a user. - -`ISettingManager` uses the setting management providers on get/set methods. Typically, every setting management provider defines extension methods on the `ISettingManagement` service (like `SetForUserAsync` defined by the user setting management provider). - -If you want to create your own provider, implement the `ISettingManagementProvider` interface or inherit from the `SettingManagementProvider` base class: - -````csharp -public class CustomSettingProvider : SettingManagementProvider, ITransientDependency -{ - public override string Name => "Custom"; - - public CustomSettingProvider(ISettingManagementStore store) - : base(store) - { - } -} -```` - -`SettingManagementProvider` base class makes the default implementation (using the `ISettingManagementStore`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each setting value record). - -Once you create your provider class, you should register it using the `SettingManagementOptions` [options class](../Options.md): - -````csharp -Configure(options => -{ - options.Providers.Add(); -}); -```` - -The order of the providers are important. Providers are executed in the reverse order. That means the `CustomSettingProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. - -## See Also - -* [Settings](../Settings.md) - -## Setting Management UI - -Setting Mangement module provided the email setting UI by default. - -![EmailSettingUi](../images/setting-management-email-ui.png) - -> You can click the Send test email button to send a test email to check your email settings. - -Setting it is extensible; You can add your tabs to this page for your application settings. - -### MVC UI - -#### Create a setting View Component - -Create `MySettingGroup` folder under the `Components` folder. Add a new view component. Name it as `MySettingGroupViewComponent`: - -![MySettingGroupViewComponent](../images/my-setting-group-view-component.png) - -Open the `MySettingGroupViewComponent.cs` and change the whole content as shown below: - -```csharp -public class MySettingGroupViewComponent : AbpViewComponent -{ - public virtual IViewComponentResult Invoke() - { - return View("~/Components/MySettingGroup/Default.cshtml"); - } -} -``` - -> You can also use the `InvokeAsync` method, In this example, we use the `Invoke` method. - -#### Default.cshtml - -Create a `Default.cshtml` file under the `MySettingGroup` folder. - -Open the `Default.cshtml` and change the whole content as shown below: - -```html -
    -

    My setting group page

    -
    -``` - -#### BookStoreSettingPageContributor - -Create a `BookStoreSettingPageContributor.cs` file under the `Settings` folder: - -![BookStoreSettingPageContributor](../images/my-setting-group-page-contributor.png) - -The content of the file is shown below: - -```csharp -public class BookStoreSettingPageContributor : ISettingPageContributor -{ - public Task ConfigureAsync(SettingPageCreationContext context) - { - context.Groups.Add( - new SettingPageGroup( - "Volo.Abp.MySettingGroup", - "MySettingGroup", - typeof(MySettingGroupViewComponent), - order : 1 - ) - ); - - return Task.CompletedTask; - } - - public Task CheckPermissionsAsync(SettingPageCreationContext context) - { - // You can check the permissions here - return Task.FromResult(true); - } -} -``` - -Open the `BookStoreWebModule.cs` file and add the following code: - -```csharp -Configure(options => -{ - options.Contributors.Add(new BookStoreSettingPageContributor()); -}); -``` - -#### Run the Application - -Navigate to `/SettingManagement` route to see the changes: - -![Custom Settings Tab](../images/my-setting-group-ui.png) - -### Blazor UI - -#### Create a Razor Component - -Create `MySettingGroup` folder under the `Pages` folder. Add a new razor component. Name it as `MySettingGroupComponent`: - -![MySettingGroupComponent](../images/my-setting-group-component.png) - -Open the `MySettingGroupComponent.razor` and change the whole content as shown below: - -```csharp - -

    my setting group

    -
    -``` - -#### BookStoreSettingComponentContributor - -Create a `BookStoreSettingComponentContributor.cs` file under the `Settings` folder: - -![BookStoreSettingComponentContributor](../images/my-setting-group-component-contributor.png) - -The content of the file is shown below: - -```csharp -public class BookStoreSettingComponentContributor : ISettingComponentContributor -{ - public Task ConfigureAsync(SettingComponentCreationContext context) - { - context.Groups.Add( - new SettingComponentGroup( - "Volo.Abp.MySettingGroup", - "MySettingGroup", - typeof(MySettingGroupComponent), - order : 1 - ) - ); - - return Task.CompletedTask; - } - - public Task CheckPermissionsAsync(SettingComponentCreationContext context) - { - // You can check the permissions here - return Task.FromResult(true); - } -} -``` - -Open the `BookStoreBlazorModule.cs` file and add the following code: - -```csharp -Configure(options => -{ - options.Contributors.Add(new BookStoreSettingComponentContributor()); -}); -``` - -#### Run the Application - -Navigate to `/setting-management` route to see the changes: - -![Custom Settings Tab](../images/my-setting-group-blazor.png) - -### Angular UI - -#### Create a Component - -Create a component with the following command: - -```bash -yarn ng generate component my-settings -``` - -Open the `app.component.ts` and modify the file as shown below: - -```js -import { Component } from '@angular/core'; -import { SettingTabsService } from '@abp/ng.setting-management/config'; // imported SettingTabsService -import { MySettingsComponent } from './my-settings/my-settings.component'; // imported MySettingsComponent - -@Component(/* component metadata */) -export class AppComponent { - constructor(private settingTabs: SettingTabsService) // injected MySettingsComponent - { - // added below - settingTabs.add([ - { - name: 'MySettings', - order: 1, - requiredPolicy: 'policy key here', - component: MySettingsComponent, - }, - ]); - } -} -``` - -#### Run the Application - -Navigate to `/setting-management` route to see the changes: - -![Custom Settings Tab](../images/custom-settings.png) \ No newline at end of file diff --git a/docs/en/Modules/Tenant-Management.md b/docs/en/Modules/Tenant-Management.md deleted file mode 100644 index 6245db82c8..0000000000 --- a/docs/en/Modules/Tenant-Management.md +++ /dev/null @@ -1,134 +0,0 @@ -# Tenant Management Module - -[Multi-Tenancy](../Multi-Tenancy.md) is one of the core features of ABP Framework. It provides the fundamental infrastructure to build your own SaaS (Software-as-a-Service) solution. ABP's multi-tenancy system abstracts where your tenants are stored, by providing the `ITenantStore` interface. All you need to do is to implement that interface. - -**The Tenant Management module is an implementation of the the `ITenantStore` interface. It stores tenants in a database. It also provides UI to manage your tenants and their [features](../Features.md).** - -> Please **refer to the [Multi-Tenancy](../Multi-Tenancy.md) documentation** to understand the multi-tenancy system of the ABP Framework. This document focuses on the Tenant Management module. - -### About the ABP Commercial SaaS Module - -The [SaaS Module](https://commercial.abp.io/modules/Volo.Saas) is an alternative implementation of this module with more features and possibilities. It is distributed as a part of the [ABP Commercial](https://commercial.abp.io/) subscription. - -## How to Install - -This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. - -### The Source Code - -The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/tenant-management). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. - -## User Interface - -This module adds "*Administration -> Tenant Management -> Tenants*" menu item to the main menu of the application, which opens the page shown below: - -![module-tenant-management-page](../images/module-tenant-management-page.png) - -In this page, you see the all the tenants. You can create a new tenant as shown below: - -![module-tenant-management-new-tenant](../images/module-tenant-management-new-tenant.png) - -In this modal; - -* **Name**: The unique name of the tenant. If you use subdomains for your tenants (like https://some-tenant.your-domain.com), this will be the subdomain name. -* **Admin Email Address**: Email address of the admin user for this tenant. -* **Admin Password**: The password of the admin user for this tenant. - -When you click to *Actions* button near to a tenant, you will see the actions you can take: - -![module-tenant-management-actions](../images/module-tenant-management-actions.png) - -### Managing the Tenant Features - -The Features action opens a modal to enable/disable/set [features](../Features.md) for the related tenant. Here, an example modal: - -![features-modal](../images/features-modal.png) - -### Managing the Host Features - -*Manage Host features* button is used to set features for the host side, if you use the features of your application also in the host side. - -## Distributed Events - -This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module; - -- `TenantEto` is published on changes done on an `Tenant` entity. - -**Example: Get notified when a new tenant has been created** - -```cs -public class MyHandler : - IDistributedEventHandler>, - ITransientDependency -{ - public async Task HandleEventAsync(EntityCreatedEto eventData) - { - TenantEto tenant = eventData.Entity; - // TODO: ... - } -} -``` - - - -`TenantEto` is configured to automatically publish the events. You should configure yourself for the others. See the [Distributed Event Bus document](https://github.com/abpframework/abp/blob/rel-7.3/docs/en/Distributed-Event-Bus.md) to learn details of the pre-defined events. - -> Subscribing to the distributed events is especially useful for distributed scenarios (like microservice architecture). If you are building a monolithic application, or listening events in the same process that runs the Tenant Management Module, then subscribing to the [local events](https://github.com/abpframework/abp/blob/rel-7.3/docs/en/Local-Event-Bus.md) can be more efficient and easier. - -## Internals - -This section can be used as a reference if you want to [customize](../Customizing-Application-Modules-Guide.md) this module without changing [its source code](https://github.com/abpframework/abp/tree/dev/modules/tenant-management). - -### Domain Layer - -#### Aggregates - -* `Tenant` - -#### Repositories - -* `ITenantRepository` - -#### Domain Services - -* `TenantManager` - -### Application Layer - -#### Application Services - -* `TenantAppService` - -#### Permissions - -- `AbpTenantManagement.Tenants`: Tenant management. -- `AbpTenantManagement.Tenants.Create`: Creating a new tenant. -- `AbpTenantManagement.Tenants.Update`: Editing an existing tenant. -- `AbpTenantManagement.Tenants.Delete`: Deleting an existing tenant. -- `AbpTenantManagement.Tenants.ManageFeatures`: Manage features of the tenants. - -### Entity Framework Core Integration - -* `TenantManagementDbContext` (implements `ITenantManagementDbContext`) - -**Database Tables:** - -* `AbpTenants` -* `AbpTenantConnectionStrings` - -### MongoDB Integration - -* `TenantManagementMongoDbContext` (implements `ITenantManagementMongoDbContext`) - -**Database Collections:** - -* `AbpTenants` (also includes the connection string) - -## Notices - -ABP Framework allows to use *database per tenant* approach that allows a tenant can have a dedicated database. This module has the fundamental infrastructure to make that implementation possible (see its source code), however it doesn't implement the application layer and UI functionalities to provide it as an out of the box implementation. You can implement these features yourself, or consider to use the [ABP Commercial Saas Module](https://docs.abp.io/en/commercial/latest/modules/saas) that fully implements it and provides much more business features. - -## See Also - -* [Multi-Tenancy](../Multi-Tenancy.md) -* [ABP Commercial SaaS Module](https://docs.abp.io/en/commercial/latest/modules/saas) diff --git a/docs/en/Modules/Virtual-File-Explorer.md b/docs/en/Modules/Virtual-File-Explorer.md deleted file mode 100644 index a952b2c1db..0000000000 --- a/docs/en/Modules/Virtual-File-Explorer.md +++ /dev/null @@ -1,87 +0,0 @@ -# Virtual File Explorer Module - -## What is Virtual File Explorer Module? - -Virtual File Explorer Module provided a simple UI to view all files in [virtual file system](../Virtual-File-System.md). - -> Virtual File Explorer Module is not installed for [the startup templates](../Startup-Templates/Index.md). So, you need to manually add this module to your application. - -### Installation - -#### 1- Use ABP CLI - -It is recommended to use the [ABP CLI](../CLI.md) to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: - -``` -abp add-module Volo.VirtualFileExplorer -``` - -> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.VirtualFileExplorer.Web). - -#### 2- Manually install - -Or you can also manually install nuget package to `Acme.MyProject.Web` project: - -* Install [Volo.Abp.VirtualFileExplorer.Web](https://www.nuget.org/packages/Volo.Abp.VirtualFileExplorer.Web/) nuget package to `Acme.MyProject.Web` project. - - `Install-Package Volo.Abp.VirtualFileExplorer.Web` - -##### 2.1- Adding Module Dependencies - - * Open `MyProjectWebModule.cs`and add `typeof(AbpVirtualFileExplorerWebModule)` as shown below; - - ```csharp - [DependsOn( - typeof(AbpVirtualFileExplorerWebModule), - typeof(MyProjectApplicationModule), - typeof(MyProjectEntityFrameworkCoreModule), - typeof(AbpAutofacModule), - typeof(AbpIdentityWebModule), - typeof(AbpAccountWebModule), - typeof(AbpAspNetCoreMvcUiBasicThemeModule) - )] - public class MyProjectWebModule : AbpModule - { - //... - } - ``` - -##### 2.2- Adding NPM Package - - * Open `package.json` and add `@abp/virtual-file-explorer": "^2.9.0` as shown below: - - ```json - { - "version": "1.0.0", - "name": "my-app", - "private": true, - "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0", - "@abp/virtual-file-explorer": "^2.9.0" - } - } - ``` - - Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command: - -````bash -abp install-libs -```` - -That's all,Now run the application and Navigate to `/VirtualFileExplorer`. You will see virtual file explorer page: - -![Virtual-File-Explorer](../images/virtual-file-explorer.png) - -### Options - -You can disabled virtual file explorer module via `AbpVirtualFileExplorerOptions` options: - -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.IsEnabled = false; - }); -} -``` \ No newline at end of file diff --git a/docs/en/MongoDB.md b/docs/en/MongoDB.md deleted file mode 100644 index 101712da35..0000000000 --- a/docs/en/MongoDB.md +++ /dev/null @@ -1,513 +0,0 @@ -# MongoDB Integration - -This document explains how to integrate MongoDB as a database provider to ABP based applications and how to configure it. - -## Installation - -`Volo.Abp.MongoDB` is the main NuGet package for the MongoDB integration. Install it to your project (for a layered application, to your data/infrastructure layer), You can use the [ABP CLI](CLI.md) to install it to your project. Execute the following command in the folder of the .csproj file of the layer: - -``` -abp add-package Volo.Abp.MongoDB -``` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.MongoDB). - -Then add `AbpMongoDbModule` module dependency to your [module](Module-Development-Basics.md): - -```c# -using Volo.Abp.MongoDB; -using Volo.Abp.Modularity; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpMongoDbModule))] - public class MyModule : AbpModule - { - //... - } -} -``` - -## Creating a Mongo Db Context - -ABP introduces **Mongo Db Context** concept (which is similar to Entity Framework Core's DbContext) to make it easier to use collections and configure them. An example is shown below: - -```c# -public class MyDbContext : AbpMongoDbContext -{ - public IMongoCollection Questions => Collection(); - - public IMongoCollection Categories => Collection(); - - protected override void CreateModel(IMongoModelBuilder modelBuilder) - { - base.CreateModel(modelBuilder); - - //Customize the configuration for your collections. - } -} -``` - -* It's derived from `AbpMongoDbContext` class. -* Adds a public `IMongoCollection` property for each mongo collection. ABP uses these properties to create default repositories by default. -* Overriding `CreateModel` method allows to configure collection configuration. - -### Configure Mapping for a Collection - -ABP automatically register entities to MongoDB client library for all `IMongoCollection` properties in your DbContext. For the example above, `Question` and `Category` entities are automatically registered. - -For each registered entity, it calls `AutoMap()` and configures known properties of your entity. For instance, if your entity implements `IHasExtraProperties` interface (which is already implemented for every aggregate root by default), it automatically configures `ExtraProperties`. - -So, most of times you don't need to explicitly configure registration for your entities. However, if you need it you can do it by overriding the `CreateModel` method in your DbContext. Example: - -````csharp -protected override void CreateModel(IMongoModelBuilder modelBuilder) -{ - base.CreateModel(modelBuilder); - - modelBuilder.Entity(b => - { - b.CollectionName = "MyQuestions"; //Sets the collection name - b.BsonMap.UnmapProperty(x => x.MyProperty); //Ignores 'MyProperty' - }); -} -```` - -This example changes the mapped collection name to 'MyQuestions' in the database and ignores a property in the `Question` class. - -If you only need to configure the collection name, you can also use `[MongoCollection]` attribute for the collection in your DbContext. Example: - -````csharp -[MongoCollection("MyQuestions")] //Sets the collection name -public IMongoCollection Questions => Collection(); -```` - -### Configure Indexes and CreateCollectionOptions for a Collection - -You can configure indexes and `CreateCollectionOptions` for your collections by overriding the `CreateModel` method. Example: - -````csharp -protected override void CreateModel(IMongoModelBuilder modelBuilder) -{ - base.CreateModel(modelBuilder); - - modelBuilder.Entity(b => - { - b.CreateCollectionOptions.Collation = new Collation(locale:"en_US", strength: CollationStrength.Secondary); - b.ConfigureIndexes(indexes => - { - indexes.CreateOne( - new CreateIndexModel( - Builders.IndexKeys.Ascending("MyProperty"), - new CreateIndexOptions { Unique = true } - ) - ); - } - ); - }); -} -```` - -This example sets a collation for the collection and creates a unique index for the `MyProperty` property. - -### Configure the Connection String Selection - -If you have multiple databases in your application, you can configure the connection string name for your DbContext using the `[ConnectionStringName]` attribute. Example: - -````csharp -[ConnectionStringName("MySecondConnString")] -public class MyDbContext : AbpMongoDbContext -{ - -} -```` - -If you don't configure, the `Default` connection string is used. If you configure a specific connection string name, but not define this connection string name in the application configuration then it fallbacks to the `Default` connection string. - -## Registering DbContext To Dependency Injection - -Use `AddAbpDbContext` method in your module to register your DbContext class for [dependency injection](Dependency-Injection.md) system. - -```c# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.MongoDB; -using Volo.Abp.Modularity; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpMongoDbModule))] - public class MyModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddMongoDbContext(); - - //... - } - } -} -``` - -### Add Default Repositories - -ABP can automatically create default [generic repositories](Repositories.md) for the entities in your DbContext. Just use `AddDefaultRepositories()` option on the registration: - -````C# -services.AddMongoDbContext(options => -{ - options.AddDefaultRepositories(); -}); -```` - -This will create a repository for each [aggregate root entity](Entities.md) (classes derived from `AggregateRoot`) by default. If you want to create repositories for other entities too, then set `includeAllEntities` to `true`: - -```c# -services.AddMongoDbContext(options => -{ - options.AddDefaultRepositories(includeAllEntities: true); -}); -``` - -Then you can inject and use `IRepository` in your services. Assume that you have a `Book` entity with `Guid` primary key: - -```csharp -public class Book : AggregateRoot -{ - public string Name { get; set; } - - public BookType Type { get; set; } -} -``` - -(`BookType` is a simple `enum` here) And you want to create a new `Book` entity in a [domain service](Domain-Services.md): - -```csharp -public class BookManager : DomainService -{ - private readonly IRepository _bookRepository; - - public BookManager(IRepository bookRepository) //inject default repository - { - _bookRepository = bookRepository; - } - - public async Task CreateBook(string name, BookType type) - { - Check.NotNullOrWhiteSpace(name, nameof(name)); - - var book = new Book - { - Id = GuidGenerator.Create(), - Name = name, - Type = type - }; - - await _bookRepository.InsertAsync(book); //Use a standard repository method - - return book; - } -} -``` - -This sample uses `InsertAsync` method to insert a new entity to the database. - -### Add Custom Repositories - -Default generic repositories are powerful enough in most cases (since they implement `IQueryable`). However, you may need to create a custom repository to add your own repository methods. - -Assume that you want to delete all books by type. It's suggested to define an interface for your custom repository: - -```csharp -public interface IBookRepository : IRepository -{ - Task DeleteBooksByType( - BookType type, - CancellationToken cancellationToken = default(CancellationToken) - ); -} -``` - -You generally want to derive from the `IRepository` to inherit standard repository methods. However, you don't have to. Repository interfaces are defined in the domain layer of a layered application. They are implemented in the data/infrastructure layer (`MongoDB` project in a [startup template](https://abp.io/Templates)). - -Example implementation of the `IBookRepository` interface: - -```csharp -public class BookRepository : - MongoDbRepository, - IBookRepository -{ - public BookRepository(IMongoDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task DeleteBooksByType( - BookType type, - CancellationToken cancellationToken = default(CancellationToken)) - { - var collection = await GetCollectionAsync(cancellationToken); - await collection.DeleteManyAsync( - Builders.Filter.Eq(b => b.Type, type), - cancellationToken - ); - } -} -``` - -Now, it's possible to [inject](Dependency-Injection.md) the `IBookRepository` and use the `DeleteBooksByType` method when needed. - -#### Override Default Generic Repository - -Even if you create a custom repository, you can still inject the default generic repository (`IRepository` for this example). Default repository implementation will not use the class you have created. - -If you want to replace default repository implementation with your custom repository, do it inside `AddMongoDbContext` options: - -```csharp -context.Services.AddMongoDbContext(options => -{ - options.AddDefaultRepositories(); - options.AddRepository(); //Replaces IRepository -}); -``` - -This is especially important when you want to **override a base repository method** to customize it. For instance, you may want to override `DeleteAsync` method to delete an entity in a more efficient way: - -```csharp -public async override Task DeleteAsync( - Guid id, - bool autoSave = false, - CancellationToken cancellationToken = default) -{ - //TODO: Custom implementation of the delete method -} -``` - -### Access to the MongoDB API - -In most cases, you want to hide MongoDB APIs behind a repository (this is the main purpose of the repository). However, if you want to access the MongoDB API over the repository, you can use `GetDatabaseAsync()`, `GetCollectionAsync()` or `GetAggregateAsync()` extension methods. Example: - -```csharp -public class BookService -{ - private readonly IRepository _bookRepository; - - public BookService(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task FooAsync() - { - IMongoDatabase database = await _bookRepository.GetDatabaseAsync(); - IMongoCollection books = await _bookRepository.GetCollectionAsync(); - IAggregateFluent bookAggregate = await _bookRepository.GetAggregateAsync(); - } -} -``` - -> Important: You must reference to the `Volo.Abp.MongoDB` package from the project you want to access to the MongoDB API. This breaks encapsulation, but this is what you want in that case. - -### Transactions - -MongoDB supports multi-document transactions starting from the version 4.0 and the ABP Framework supports it. However, the [startup template](Startup-Templates/Index.md) **disables** transactions by default. If your MongoDB **server** supports transactions, you can enable the it in the *YourProjectMongoDbModule* class: - -```csharp -Configure(options => -{ - options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto; -}); -``` - -> Or you can delete this code since this is already the default behavior. - -### Advanced Topics - -### Controlling the Multi-Tenancy - -If your solution is [multi-tenant](Multi-Tenancy.md), tenants may have **separate databases**, you have **multiple** `DbContext` classes in your solution and some of your `DbContext` classes should be usable **only from the host side**, it is suggested to add `[IgnoreMultiTenancy]` attribute on your `DbContext` class. In this case, ABP guarantees that the related `DbContext` always uses the host [connection string](Connection-Strings.md), even if you are in a tenant context. - -**Example:** - -````csharp -[IgnoreMultiTenancy] -public class MyDbContext : AbpMongoDbContext -{ - ... -} -```` - -Do not use the `[IgnoreMultiTenancy]` attribute if any one of your entities in your `DbContext` can be persisted in a tenant database. - -> When you use repositories, ABP already uses the host database for the entities don't implement the `IMultiTenant` interface. So, most of time you don't need to `[IgnoreMultiTenancy]` attribute if you are using the repositories to work with the database. - -#### Set Default Repository Classes - -Default generic repositories are implemented by `MongoDbRepository` class by default. You can create your own implementation and use it for default repository implementation. - -First, define your repository classes like that: - -```csharp -public class MyRepositoryBase - : MongoDbRepository - where TEntity : class, IEntity -{ - public MyRepositoryBase(IMongoDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } -} - -public class MyRepositoryBase - : MongoDbRepository - where TEntity : class, IEntity -{ - public MyRepositoryBase(IMongoDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } -} -``` - -First one is for [entities with composite keys](Entities.md), second one is for entities with single primary key. - -It's suggested to inherit from the `MongoDbRepository` class and override methods if needed. Otherwise, you will have to implement all standard repository methods manually. - -Now, you can use `SetDefaultRepositoryClasses` option: - -```csharp -context.Services.AddMongoDbContext(options => -{ - options.SetDefaultRepositoryClasses( - typeof(MyRepositoryBase<,>), - typeof(MyRepositoryBase<>) - ); - //... -}); -``` - -#### Set Base MongoDbContext Class or Interface for Default Repositories - -If your MongoDbContext inherits from another MongoDbContext or implements an interface, you can use that base class or interface as the MongoDbContext for default repositories. Example: - -```csharp -public interface IBookStoreMongoDbContext : IAbpMongoDbContext -{ - Collection Books { get; } -} -``` - -`IBookStoreMongoDbContext` is implemented by the `BookStoreMongoDbContext` class. Then you can use generic overload of the `AddDefaultRepositories`: - -```csharp -context.Services.AddMongoDbContext(options => -{ - options.AddDefaultRepositories(); - //... -}); -``` - -Now, your custom `BookRepository` can also use the `IBookStoreMongoDbContext` interface: - -```csharp -public class BookRepository - : MongoDbRepository, - IBookRepository -{ - //... -} -``` - -One advantage of using interface for a MongoDbContext is then it becomes replaceable by another implementation. - -#### Replace Other DbContextes - -Once you properly define and use an interface for a MongoDbContext , then any other implementation can use the following ways to replace it: - -#### ReplaceDbContext Attribute - -```csharp -[ReplaceDbContext(typeof(IBookStoreMongoDbContext))] -public class OtherMongoDbContext : AbpMongoDbContext, IBookStoreMongoDbContext -{ - //... -} -``` - -#### ReplaceDbContext Option - -```csharp -context.Services.AddMongoDbContext(options => -{ - //... - options.ReplaceDbContext(); -}); -``` - -In this example, `OtherMongoDbContext` implements `IBookStoreMongoDbContext`. This feature allows you to have multiple MongoDbContext (one per module) on development, but single MongoDbContext (implements all interfaces of all MongoDbContexts) on runtime. - -#### Replacing with Multi-Tenancy - -It is also possible to replace a DbContext based on the [multi-tenancy](Multi-Tenancy.md) side. `ReplaceDbContext` attribute and `ReplaceDbContext` method can get a `MultiTenancySides` option with a default value of `MultiTenancySides.Both`. - -**Example:** Replace DbContext only for tenants, using the `ReplaceDbContext` attribute - -````csharp -[ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)] -```` - -**Example:** Replace DbContext only for the host side, using the `ReplaceDbContext` method - -````csharp -options.ReplaceDbContext(MultiTenancySides.Host); -```` - -### Customize Bulk Operations - -If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IMongoDbBulkOperationProvider`. - -- You may use example template below: - -```csharp -public class MyCustomMongoDbBulkOperationProvider - : IMongoDbBulkOperationProvider, ITransientDependency -{ - public async Task DeleteManyAsync( - IMongoDbRepository repository, - IEnumerable entities, - IClientSessionHandle sessionHandle, - bool autoSave, - CancellationToken cancellationToken) - where TEntity : class, IEntity - { - // Your logic here. - } - - public async Task InsertManyAsync( - IMongoDbRepository repository, - IEnumerable entities, - IClientSessionHandle sessionHandle, - bool autoSave, - CancellationToken cancellationToken) - where TEntity : class, IEntity - { - // Your logic here. - } - - public async Task UpdateManyAsync( - IMongoDbRepository repository, - IEnumerable entities, - IClientSessionHandle sessionHandle, - bool autoSave, - CancellationToken cancellationToken) - where TEntity : class, IEntity - { - // Your logic here. - } -} -``` - -## See Also - -* [Entities](Entities.md) -* [Repositories](Repositories.md) -* [Video tutorial](https://abp.io/video-courses/essentials/abp-mongodb) \ No newline at end of file diff --git a/docs/en/Multi-Lingual-Entities.md b/docs/en/Multi-Lingual-Entities.md deleted file mode 100644 index b0419fe2a1..0000000000 --- a/docs/en/Multi-Lingual-Entities.md +++ /dev/null @@ -1,6 +0,0 @@ -# Multi Lingual Entities - -This feature is still under development. -Follow the below link to get information about the development status - -https://github.com/abpframework/abp/issues/11698 diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md deleted file mode 100644 index 922976767d..0000000000 --- a/docs/en/Multi-Tenancy.md +++ /dev/null @@ -1,443 +0,0 @@ -# Multi-Tenancy - -Multi-Tenancy is a widely used architecture to create **SaaS applications** where the hardware and software **resources are shared by the customers** (tenants). ABP Framework provides all the base functionalities to create **multi tenant applications**. - -Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenancy as like that: - -> Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants. - -## Terminology: Host vs Tenant - -There are two main side of a typical SaaS / Multi-tenant application: - -* A **Tenant** is a customer of the SaaS application that pays money to use the service. -* **Host** is the company that owns the SaaS application and manages the system. - -The Host and the Tenant terms will be used for that purpose in the rest of the document. - -## Configuration - -### AbpMultiTenancyOptions: Enable/Disable Multi-Tenancy - -`AbpMultiTenancyOptions` is the main options class to **enable/disable the multi-tenancy** for your application. - -**Example: Enable multi-tenancy** - -```csharp -Configure(options => -{ - options.IsEnabled = true; -}); -``` - -> Multi-Tenancy is disabled in the ABP Framework by default. However, it is **enabled by default** when you create a new solution using the [startup template](Startup-Templates/Application.md). `MultiTenancyConsts` class in the solution has a constant to control it in a single place. - -### Database Architecture - -ABP Framework supports all the following approaches to store the tenant data in the database; - -* **Single Database**: All tenants are stored in a single database. -* **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant. -* **Hybrid**: Some tenants share a single databases while some tenants may have their own databases. - -[Tenant management module](Modules/Tenant-Management.md) (which comes pre-installed with the startup projects) allows you to set a connection string for any tenant (as optional), so you can achieve any of the approaches. - -## Usage - -Multi-tenancy system is designed to **work seamlessly** and make your application code **multi-tenancy unaware** as much as possible. - -### IMultiTenant - -You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**. - -**Example: A multi-tenant *Product* entity** - -````csharp -using System; -using Volo.Abp.Domain.Entities; -using Volo.Abp.MultiTenancy; - -namespace MultiTenancyDemo.Products -{ - public class Product : AggregateRoot, IMultiTenant - { - public Guid? TenantId { get; set; } //Defined by the IMultiTenant interface - - public string Name { get; set; } - - public float Price { get; set; } - } -} -```` - -`IMultiTenant` interface just defines a `TenantId` property. When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries. A tenant can not access to data of another tenant by default. - -#### Why the TenantId Property is Nullable? - -`IMultiTenant.TenantId` is **nullable**. When it is null that means the entity is owned by the **Host** side and not owned by a tenant. It is useful when you create a functionality in your system that is both used by the tenant and the host sides. - -For example, `IdentityUser` is an entity defined by the [Identity Module](Modules/Identity.md). The host and all the tenants have their own users. So, for the host side, users will have a `null` `TenantId` while tenant users will have their related `TenantId`. - -> **Tip**: If your entity is tenant-specific and has no meaning in the host side, you can force to not set `null` for the `TenantId` in the constructor of your entity. - -#### When to set the TenantId? - -ABP automatically sets the `TenantId` for you when you create a new entity object. It is done in the constructor of the base `Entity` class (all other base entity and aggregate root classes are derived from the `Entity` class). The `TenantId` is set from the current value of the `ICurrentTenant.Id` property (see the next section). - -If you set the `TenantId` value for a specific entity object, it will override the value set by the base class. If you want to set the `TenantId` property yourself, we recommend to do it in the constructor of your entity class and do not change (update) it again (Actually, changing it means that you are moving the entity from a tenant to another tenant. If you want that, you need an extra care about the related entities in the database). - -### ICurrentTenant - -`ICurrentTenant` is the main service to interact with the multi-tenancy infrastructure. - -`ApplicationService`, `DomainService`, `AbpController` and some other base classes already has pre-injected `CurrentTenant` properties. For other type of classes, you can inject the `ICurrentTenant` into your service. - -#### Tenant Properties - -`ICurrentTenant` defines the following properties; - -* `Id` (`Guid`): Id of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request. -* `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request. -* `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`. - -#### Change the Current Tenant - -ABP Framework automatically filters the resources (database, cache...) based on the `ICurrentTenant.Id`. However, in some cases you may want to perform an operation on behalf of a specific tenant, generally when you are in the host context. - -`ICurrentTenant.Change` method changes the current tenant for a limited scope, so you can safely perform operations for the tenant. - -**Example: Get product count of a specific tenant** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Domain.Services; - -namespace MultiTenancyDemo.Products -{ - public class ProductManager : DomainService - { - private readonly IRepository _productRepository; - - public ProductManager(IRepository productRepository) - { - _productRepository = productRepository; - } - - public async Task GetProductCountAsync(Guid? tenantId) - { - using (CurrentTenant.Change(tenantId)) - { - return await _productRepository.GetCountAsync(); - } - } - } -} -```` - -* `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement. -* When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly. -* Use `CurrentTenant.Change(null)` to change scope to the host context. - -> Always use the `Change` method with a `using` statement like done in this example. - -### Data Filtering: Disable the Multi-Tenancy Filter - -As mentioned before, ABP Framework handles data isolation between tenants using the [Data Filtering](Data-Filtering.md) system. In some cases, you may want to disable it and perform a query on all the data, without filtering for the current tenant. - -**Example: Get count of products in the database, including all the products of all the tenants.** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Data; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Domain.Services; -using Volo.Abp.MultiTenancy; - -namespace MultiTenancyDemo.Products -{ - public class ProductManager : DomainService - { - private readonly IRepository _productRepository; - private readonly IDataFilter _dataFilter; - - public ProductManager( - IRepository productRepository, - IDataFilter dataFilter) - { - _productRepository = productRepository; - _dataFilter = dataFilter; - } - - public async Task GetProductCountAsync() - { - using (_dataFilter.Disable()) - { - return await _productRepository.GetCountAsync(); - } - } - } -} - -```` - -See the [Data Filtering document](Data-Filtering.md) for more. - -> Note that this approach won't work if your tenants have **separate databases** since there is no built-in way to query from multiple database in a single database query. You should handle it yourself if you need it. - -## Infrastructure - -### Determining the Current Tenant - -The first thing for a multi-tenant application is to determine the current tenant on the runtime. - -ABP Framework provides an extensible **Tenant Resolving** system for that purpose. Tenant Resolving system then used in the **Multi-Tenancy Middleware** to determine the current tenant for the current HTTP request. - -#### Tenant Resolvers - -##### Default Tenant Resolvers - -The following resolvers are provided and configured by default; - -* `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**. -* `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default. -* `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route. -* `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default. -* `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default. - -###### Problems with the NGINX - -You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please: -http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers -http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers - -###### AbpAspNetCoreMultiTenancyOptions - -`__tenant` parameter name can be changed using `AbpAspNetCoreMultiTenancyOptions`. - -**Example:** - -````csharp -services.Configure(options => -{ - options.TenantKey = "MyTenantKey"; -}); -```` - -If you change the `TenantKey`, make sure to pass it to `CoreModule` in the Angular client as follows: - -```js -@NgModule({ - imports: [ - CoreModule.forRoot({ - // ... - tenantKey: 'MyTenantKey' - }), - ], - // ... -}) -export class AppModule {} -``` - -If you need to access it, you can inject it as follows: - -```js -import { Inject } from '@angular/core'; -import { TENANT_KEY } from '@abp/ng.core'; - -class SomeComponent { - constructor(@Inject(TENANT_KEY) private tenantKey: string) {} -} -``` - -> However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then. - -The `MultiTenancyMiddlewareErrorPageBuilder` is used to handle inactive and non-existent tenants. - -It will respond to an error page by default, you can change it if you want, eg: only output the error log and continue ASP NET Core's request pipeline. - -```csharp -Configure(options => -{ - options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => - { - // Handle the exception. - - // Return true to stop the pipeline, false to continue. - return true; - }; -}); -``` - -##### Domain/Subdomain Tenant Resolver - -In a real application, most of times you will want to determine the current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure the `AbpTenantResolveOptions` to add the domain tenant resolver. - -**Example: Add a subdomain resolver** - -````csharp -Configure(options => -{ - options.AddDomainTenantResolver("{0}.mydomain.com"); -}); -```` - -* `{0}` is the placeholder to determine the current tenant's unique name. -* Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md). -* This should be done in the *Web/API Layer* since the URL is a web related stuff. - -Openiddict is the default Auth Server in ABP (since v6.0). When you use OpenIddict, you must add this code to the `PreConfigure` method as well. - -```csharp -// using Volo.Abp.OpenIddict.WildcardDomains - -PreConfigure(options => -{ - options.EnableWildcardDomainSupport = true; - options.WildcardDomainsFormat.Add("https://{0}.mydomain.com"); -}); -``` - -You must add this code to the `Configure` method as well. - -```csharp -// using Volo.Abp.MultiTenancy; - -Configure(options => -{ - options.AddDomainTenantResolver("{0}.mydomain.com"); -}); - -``` - -> There is an [example](https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver) that uses the subdomain to determine the current tenant. - -If you use a sepereted Auth server, you must install `[Owl.TokenWildcardIssuerValidator](https://www.nuget.org/packages/Owl.TokenWildcardIssuerValidator)` on the `HTTPApi.Host` project - -```bash -dotnet add package Owl.TokenWildcardIssuerValidator -``` - -Then fix the options of the `.AddJwtBearer` block - -```csharp -// using using Owl.TokenWildcardIssuerValidator; - -context.Services - .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); - options.Audience = "ExampleProjectName"; - - // start of added block - options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; - options.TokenValidationParameters.ValidIssuers = new[] - { - "https://{0}.mydomain.com:44349/" //the port may different - }; - // end of added block - }); - -``` - -##### Custom Tenant Resolvers - -You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below: - -````csharp -Configure(options => -{ - options.TenantResolvers.Add(new MyCustomTenantResolveContributor()); -}); -```` - -`MyCustomTenantResolveContributor` must inherit from the `TenantResolveContributorBase` (or implement the `ITenantResolveContributor`) as shown below: - -````csharp -using System.Threading.Tasks; -using Volo.Abp.MultiTenancy; - -namespace MultiTenancyDemo.Web -{ - public class MyCustomTenantResolveContributor : TenantResolveContributorBase - { - public override string Name => "Custom"; - - public override Task ResolveAsync(ITenantResolveContext context) - { - //TODO... - } - } -} -```` - -* A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it. -* `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system. - -#### Multi-Tenancy Middleware - -Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that determines the current tenant from the HTTP request and sets the `ICurrentTenant` properties. - -Multi-Tenancy middleware is typically placed just under the [authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication) middleware (`app.UseAuthentication()`): - -````csharp -app.UseMultiTenancy(); -```` - -> This middleware is already configured in the startup templates, so you normally don't need to manually add it. - -### Tenant Store - -`ITenantStore` is used to get the tenant configuration from a data source. - -> Tenant names are not case-sensitive. `ITenantStore` will use the `NormalizedName` parameter to get tenants, You need to use `ITenantNormalizer` to normalize tenant names. - -#### Tenant Management Module - -The [tenant management module](Modules/Tenant-Management) is **included in the startup templates** and implements the `ITenantStore` interface to get the tenants and their configuration from a database. It also provides the necessary functionality and UI to manage the tenants and their connection strings. - -#### Configuration Data Store - -**If you don't want to use the tenant management module**, the `DefaultTenantStore` is used as the `ITenantStore` implementation. It gets the tenant configurations from the [configuration system](Configuration.md) (`IConfiguration`). You can either configure the `AbpDefaultTenantStoreOptions` [options](Options.md) or set it in your `appsettings.json` file: - -**Example: Define tenants in appsettings.json** - -````json -"Tenants": [ - { - "Id": "446a5211-3d72-4339-9adc-845151f8ada0", - "Name": "tenant1", - "NormalizedName": "TENANT1" - }, - { - "Id": "25388015-ef1c-4355-9c18-f6b6ddbaf89d", - "Name": "tenant2", - "NormalizedName": "TENANT2", - "ConnectionStrings": { - "Default": "...tenant2's db connection string here..." - } - } - ] -```` - -> It is recommended to **use the Tenant Management module**, which is already pre-configured when you create a new application with the ABP startup templates. - -### Other Multi-Tenancy Infrastructure - -ABP Framework was designed to respect to the multi-tenancy in every aspect and most of the times everything will work as expected. - -BLOB Storing, Caching, Data Filtering, Data Seeding, Authorization and all the other services are designed to properly work in a multi-tenant system. - -## The Tenant Management Module - -ABP Framework provides all the the infrastructure to create a multi-tenant application, but doesn't make any assumption about how you manage (create, delete...) your tenants. - -The [Tenant Management module](Modules/Tenant-Management.md) provides a basic UI to manage your tenants and set their connection strings. It is pre-configured for the [application startup template](Startup-Templates/Application.md). - -## See Also - -* [Features](Features.md) diff --git a/docs/en/Nightly-Builds.md b/docs/en/Nightly-Builds.md deleted file mode 100644 index 5b49cb7b38..0000000000 --- a/docs/en/Nightly-Builds.md +++ /dev/null @@ -1,29 +0,0 @@ -# Nightly Builds - -All framework & module packages (both open-source and commercial) are deployed to MyGet every night on weekdays. So, you can install the latest dev-branch builds to try out functionality prior to release. - -## Install & Uninstall Nightly Preview Packages - -The latest version of nightly preview packages can be installed by running the below command in the root folder of the application: - -```bash -abp switch-to-nightly -``` - -> Note that this command doesn't create a project with nightly preview packages. Instead, it switches package versions of a project with the nightly preview packages. - -After this command, a new NuGet feed will be added to the `NuGet.Config` file of your project. Then, you can get the latest code of the ABP Framework without waiting for the next release. - -> ABP nightly NuGet feed is [https://www.myget.org/F/abp-nightly/api/v3/index.json](https://www.myget.org/F/abp-nightly/api/v3/index.json). - -Also, this command creates `.npmrc` files containing two NPM registries in the directory where the `package.json` files are located in your solution. - -> You can check the [abp-nightly gallery](https://www.myget.org/gallery/abp-nightly) (for NPM & NuGet / open-source) and [abp-commercial-npm-nightly gallery](https://www.myget.org/gallery/abp-commercial-npm-nightly) (for NPM / commercial) to see the all nightly preview packages. - -If you're using the ABP Framework nightly preview packages, you can switch back to the stable version by using this command: - -```bash -abp switch-to-stable -``` - -See the [ABP CLI documentation](./CLI.md) for more information. diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md deleted file mode 100644 index bc4fc911c4..0000000000 --- a/docs/en/Object-Extensions.md +++ /dev/null @@ -1,416 +0,0 @@ -# Object Extensions - -ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module. - -> Object extension system normally is not needed for your own objects since you can easily add regular properties to your own classes. - -## IHasExtraProperties Interface - -This is the interface to make a class extensible. It simply defines a `Dictionary` property: - -````csharp -ExtraPropertyDictionary ExtraProperties { get; } -```` - -`ExtraPropertyDictionary` class is inherited from the `Dictionary` class. You can add or get extra properties using this dictionary. - -### Base Classes - -`IHasExtraProperties` interface is implemented by several base classes by default: - -* Implemented by the `AggregateRoot` class (see [entities](Entities.md)). -* Implemented by `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... base [DTO](Data-Transfer-Objects.md) classes. -* Implemented by the `ExtensibleObject`, which is a simple base class can be inherited for any type of object. - -So, if you inherit from these classes, your class will also be extensible. If not, you can always implement it manually. - -### Fundamental Extension Methods - -While you can directly use the `ExtraProperties` property of a class, it is suggested to use the following extension methods while working with the extra properties. - -#### SetProperty - -Used to set the value of an extra property: - -````csharp -user.SetProperty("Title", "My Title"); -user.SetProperty("IsSuperUser", true); -```` - -`SetProperty` returns the same object, so you can chain it: - -````csharp -user.SetProperty("Title", "My Title") - .SetProperty("IsSuperUser", true); -```` - -#### GetProperty - -Used to read the value of an extra property: - -````csharp -var title = user.GetProperty("Title"); - -if (user.GetProperty("IsSuperUser")) -{ - //... -} -```` - -* `GetProperty` is a generic method and takes the object type as the generic parameter. -* Returns the default value if given property was not set before (default value is `0` for `int`, `false` for `bool`... etc). - -##### Non Primitive Property Types - -If your property type is not a primitive (int, bool, enum, string... etc) type, then you need to use non-generic version of the `GetProperty` which returns an `object`. - -#### HasProperty - -Used to check if the object has a property set before. - -#### RemoveProperty - -Used to remove a property from the object. Use this methods instead of setting a `null` value for the property. - -### Some Best Practices - -Using magic strings for the property names is dangerous since you can easily type the property name wrong - it is not type safe. Instead; - -* Define a constant for your extra property names -* Create extension methods to easily set your extra properties. - -Example: - -````csharp -public static class IdentityUserExtensions -{ - private const string TitlePropertyName = "Title"; - - public static void SetTitle(this IdentityUser user, string title) - { - user.SetProperty(TitlePropertyName, title); - } - - public static string GetTitle(this IdentityUser user) - { - return user.GetProperty(TitlePropertyName); - } -} -```` - -Then you can easily set or get the `Title` property: - -````csharp -user.SetTitle("My Title"); -var title = user.GetTitle(); -```` - -## Object Extension Manager - -While you can set arbitrary properties to an extensible object (which implements the `IHasExtraProperties` interface), `ObjectExtensionManager` is used to explicitly define extra properties for extensible classes. - -Explicitly defining an extra property has some use cases: - -* Allows to control how the extra property is handled on object to object mapping (see the section below). -* Allows to define metadata for the property. For example, you can map an extra property to a table field in the database while using the [EF Core](Entity-Framework-Core.md). - -> `ObjectExtensionManager` implements the singleton pattern (`ObjectExtensionManager.Instance`) and you should define object extensions before your application startup. The [application startup template](Startup-Templates/Application.md) has some pre-defined static classes to safely define object extensions inside. - -### AddOrUpdate - -`AddOrUpdate` is the main method to define a extra properties or update extra properties for an object. - -Example: Define extra properties for the `IdentityUser` entity: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdate(options => - { - options.AddOrUpdateProperty("SocialSecurityNumber"); - options.AddOrUpdateProperty("IsSuperUser"); - } - ); -```` - -### AddOrUpdateProperty - -While `AddOrUpdateProperty` can be used on the `options` as shown before, if you want to define a single extra property, you can use the shortcut extension method too: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty("SocialSecurityNumber"); -```` - -Sometimes it would be practical to define a single extra property to multiple types. Instead of defining one by one, you can use the following code: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - new[] - { - typeof(IdentityUserDto), - typeof(IdentityUserCreateDto), - typeof(IdentityUserUpdateDto) - }, - "SocialSecurityNumber" - ); -```` - -### Property Configuration - -`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - //Configure options... - }); -```` - -> `options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document. - -The following sections explain the fundamental property configuration options. - -#### Default Value - -A default value is automatically set for the new property, which is the natural default value for the property type, like `null` for `string`, `false` for `bool` or `0` for `int`. - -There are two ways to override the default value: - -##### DefaultValue Option - -`DefaultValue` option can be set to any value: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "MyIntProperty", - options => - { - options.DefaultValue = 42; - }); -```` - -##### DefaultValueFactory Options - -`DefaultValueFactory` can be set to a function that returns the default value: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "MyDateTimeProperty", - options => - { - options.DefaultValueFactory = () => DateTime.Now; - }); -```` - -`options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` . - -> Tip: Use `DefaultValueFactory` option only if the default value may change over the time (like `DateTime.Now` in this example). If it is a constant value, then use the `DefaultValue` option. - -#### CheckPairDefinitionOnMapping - -Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better. - -## Validation - -You may want to add some **validation rules** for the extra properties you've defined. `AddOrUpdateProperty` method options allows two ways of performing validation: - -1. You can add **data annotation attributes** for a property. -2. You can write an action (code block) to perform a **custom validation**. - -Validation works when you use the object in a method that is **automatically validated** (e.g. controller actions, page handler methods, application service methods...). So, all extra properties are validated whenever the extended object is being validated. - -### Data Annotation Attributes - -All of the standard data annotation attributes are valid for extra properties. Example: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - options.Attributes.Add(new RequiredAttribute()); - options.Attributes.Add( - new StringLengthAttribute(32) { - MinimumLength = 6 - } - ); - }); -```` - -With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided. - -#### Default Validation Attributes - -There are some attributes **automatically added** when you create certain type of properties; - -* `RequiredAttribute` is added for non nullable primitive property types (e.g. `int`, `bool`, `DateTime`...) and `enum` types. -* `EnumDataTypeAttribute` is added for enum types, to prevent to set invalid enum values. - -Use `options.Attributes.Clear();` if you don't want these attributes. - -### Custom Validation - -If you need, you can add a custom action that is executed to validate the extra properties. Example: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - options.Validators.Add(context => - { - var socialSecurityNumber = context.Value as string; - - if (socialSecurityNumber == null || - socialSecurityNumber.StartsWith("X")) - { - context.ValidationErrors.Add( - new ValidationResult( - "Invalid social security number: " + socialSecurityNumber, - new[] { "SocialSecurityNumber" } - ) - ); - } - }); - }); -```` - -`context.ServiceProvider` can be used to resolve a service dependency for advanced scenarios. - -In addition to add custom validation logic for a single property, you can add a custom validation logic that is executed in object level. Example: - -````csharp -ObjectExtensionManager.Instance -.AddOrUpdate(objConfig => -{ - //Define two properties with their own validation rules - - objConfig.AddOrUpdateProperty("Password", propertyConfig => - { - propertyConfig.Attributes.Add(new RequiredAttribute()); - }); - - objConfig.AddOrUpdateProperty("PasswordRepeat", propertyConfig => - { - propertyConfig.Attributes.Add(new RequiredAttribute()); - }); - - //Write a common validation logic works on multiple properties - - objConfig.Validators.Add(context => - { - if (context.ValidatingObject.GetProperty("Password") != - context.ValidatingObject.GetProperty("PasswordRepeat")) - { - context.ValidationErrors.Add( - new ValidationResult( - "Please repeat the same password!", - new[] { "Password", "PasswordRepeat" } - ) - ); - } - }); -}); -```` - -## Object to Object Mapping - -Assume that you've added an extra property to an extensible entity object and used auto [object to object mapping](Object-To-Object-Mapping.md) to map this entity to an extensible DTO class. You need to be careful in such a case, because the extra property may contain a **sensitive data** that should not be available to clients. - -This section offers some **good practices** to control your extra properties on object mapping. - -### MapExtraPropertiesTo - -`MapExtraPropertiesTo` is an extension method provided by the ABP Framework to copy extra properties from an object to another in a controlled manner. Example usage: - -````csharp -identityUser.MapExtraPropertiesTo(identityUserDto); -```` - -`MapExtraPropertiesTo` **requires to define properties** (as described above) in **both sides** (`IdentityUser` and `IdentityUserDto` in this case) in order to copy the value to the target object. Otherwise, it doesn't copy the value even if it does exists in the source object (`identityUser` in this example). There are some ways to overload this restriction. - -#### MappingPropertyDefinitionChecks - -`MapExtraPropertiesTo` gets an additional parameter to control the definition check for a single mapping operation: - -````csharp -identityUser.MapExtraPropertiesTo( - identityUserDto, - MappingPropertyDefinitionChecks.None -); -```` - -> Be careful since `MappingPropertyDefinitionChecks.None` copies all extra properties without any check. `MappingPropertyDefinitionChecks` enum has other members too. - -If you want to completely disable definition check for a property, you can do it while defining the extra property (or update an existing definition) as shown below: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - options.CheckPairDefinitionOnMapping = false; - }); -```` - -#### Ignored Properties - -You may want to ignore some properties on a specific mapping operation: - -````csharp -identityUser.MapExtraPropertiesTo( - identityUserDto, - ignoredProperties: new[] {"MySensitiveProp"} -); -```` - -Ignored properties are not copied to the target object. - -#### AutoMapper Integration - -If you're using the [AutoMapper](https://automapper.org/) library, the ABP Framework also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above. - -You can use the `MapExtraProperties()` method inside your mapping profile. - -````csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap() - .MapExtraProperties(); - } -} -```` - -It has the same parameters with the `MapExtraPropertiesTo` method. - -## Entity Framework Core Database Mapping - -If you're using the EF Core, you can map an extra property to a table field in the database. Example: - -````csharp -ObjectExtensionManager.Instance - .AddOrUpdateProperty( - "SocialSecurityNumber", - options => - { - options.MapEfCore(b => b.HasMaxLength(32)); - } - ); -```` - -See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more. - -## See Also - -* [Module Entity Extensions](Module-Entity-Extensions.md) diff --git a/docs/en/Object-To-Object-Mapping.md b/docs/en/Object-To-Object-Mapping.md deleted file mode 100644 index 40efdd7ee6..0000000000 --- a/docs/en/Object-To-Object-Mapping.md +++ /dev/null @@ -1,341 +0,0 @@ -# Object To Object Mapping - -It's common to map an object to another similar object. It's also tedious and repetitive since generally both classes have the same or similar properties mapped to each other. Imagine a typical [application service](Application-Services.md) method below: - -```csharp -public class UserAppService : ApplicationService -{ - private readonly IRepository _userRepository; - - public UserAppService(IRepository userRepository) - { - _userRepository = userRepository; - } - - public async Task CreateUser(CreateUserInput input) - { - //Manually creating a User object from the CreateUserInput object - var user = new User - { - Name = input.Name, - Surname = input.Surname, - EmailAddress = input.EmailAddress, - Password = input.Password - }; - - await _userRepository.InsertAsync(user); - } -} -``` - -`CreateUserInput` is a simple [DTO](Data-Transfer-Objects.md) class and the `User` is a simple [entity](Entities.md). The code above creates a `User` entity from the given input object. The `User` entity will have more properties in a real-world application and manually creating it will become tedious and error-prone. You also have to change the mapping code when you add new properties to `User` and `CreateUserInput` classes. - -We can use a library to automatically handle these kind of mappings. ABP provides abstractions for object to object mapping and has an integration package to use [AutoMapper](http://automapper.org/) as the object mapper. - -## IObjectMapper - -`IObjectMapper` interface (in the [Volo.Abp.ObjectMapping](https://www.nuget.org/packages/Volo.Abp.ObjectMapping) package) defines a simple `Map` method. The example code introduced before can be re-written as shown below: - -````csharp -public class UserAppService : ApplicationService -{ - private readonly IRepository _userRepository; - - public UserAppService(IRepository userRepository) - { - _userRepository = userRepository; - } - - public async Task CreateUser(CreateUserInput input) - { - //Automatically creating a new User object using the CreateUserInput object - var user = ObjectMapper.Map(input); - - await _userRepository.InsertAsync(user); - } -} -```` - -> `ObjectMapper` is defined in the `ApplicationService` base class in this example. You can directly inject the `IObjectMapper` interface when you need it somewhere else. - -Map method has two generic argument: First one is the source object type while the second one is the destination object type. - -If you need to set properties of an existing object, you can use the second overload of the `Map` method: - -````csharp -public class UserAppService : ApplicationService -{ - private readonly IRepository _userRepository; - - public UserAppService(IRepository userRepository) - { - _userRepository = userRepository; - } - - public async Task UpdateUserAsync(Guid id, UpdateUserInput input) - { - var user = await _userRepository.GetAsync(id); - - //Automatically set properties of the user object using the UpdateUserInput - ObjectMapper.Map(input, user); - - await _userRepository.UpdateAsync(user); - } -} -```` - -You should have defined the mappings before to be able to map objects. See the AutoMapper integration section to learn how to define mappings. - -## AutoMapper Integration - -[AutoMapper](http://automapper.org/) is one of the most popular object to object mapping libraries. [Volo.Abp.AutoMapper](https://www.nuget.org/packages/Volo.Abp.AutoMapper) package defines the AutoMapper integration for the `IObjectMapper`. - -Once you define mappings described as below, you can use the `IObjectMapper` interface just like explained before. - -### Define Mappings - -AutoMapper provides multiple ways of defining mapping between classes. Refer to [its own documentation](https://docs.automapper.org) for all details. - -One way to define object mappings is creating a [Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances) class. Example: - -````csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap(); - } -} -```` - -You should then register profiles using the `AbpAutoMapperOptions`: - -````csharp -[DependsOn(typeof(AbpAutoMapperModule))] -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - //Add all mappings defined in the assembly of the MyModule class - options.AddMaps(); - }); - } -} -```` - -`AddMaps` registers all profile classes defined in the assembly of the given class, typically your module class. It also registers for the [attribute mapping](https://docs.automapper.org/en/stable/Attribute-mapping.html). - -### Configuration Validation - -`AddMaps` optionally takes a `bool` parameter to control the [configuration validation](https://docs.automapper.org/en/stable/Configuration-validation.html) for your [module](Module-Development-Basics.md): - -````csharp -options.AddMaps(validate: true); -```` - -While this option is `false` by default, it is suggested to enable configuration validation as a best practice. - -Configuration validation can be controlled per profile class using `AddProfile` instead of `AddMaps`: - -````csharp -options.AddProfile(validate: true); -```` - -> If you have multiple profiles and need to enable validation only for a few of them, first use `AddMaps` without validation, then use `AddProfile` for each profile you want to validate. - -### Mapping the Object Extensions - -[Object extension system](Object-Extensions.md) allows to define extra properties for existing classes. ABP Framework provides a mapping definition extension to properly map extra properties of two objects. - -````csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap() - .MapExtraProperties(); - } -} -```` - -It is suggested to use the `MapExtraProperties()` method if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](Object-Extensions.md) for more. - -### Other Useful Extension Methods - -There are some more extension methods those can simplify your mapping code. - -#### Ignoring Audit Properties - -It is common to ignore audit properties when you map an object to another. - -Assume that you need to map a `ProductDto` ([DTO](Data-Transfer-Objects.md)) to a `Product` [entity](Entities.md) and the entity is inheriting from the `AuditedEntity` class (which provides properties like `CreationTime`, `CreatorId`, `IHasModificationTime`... etc). - -You probably want to ignore these base properties while mapping from the DTO. You can use `IgnoreAuditedObjectProperties()` method to ignore all audit properties (instead of manually ignoring them one by one): - -````csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap() - .IgnoreAuditedObjectProperties(); - } -} -```` - -There are more extension methods like `IgnoreFullAuditedObjectProperties()` and `IgnoreCreationAuditedObjectProperties()` those can be used based on your entity type. - -> See the "*Base Classes & Interfaces for Audit Properties*" section in the [entities document](Entities.md) to know more about auditing properties. - -#### Ignoring Other Properties - -In AutoMapper, you typically write such a mapping code to ignore a property: - -````csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap() - .ForMember(x => x.CreationTime, map => map.Ignore()); - } -} -```` - -We found it unnecessarily long and created the `Ignore()` extension method: - -````csharp -public class MyProfile : Profile -{ - public MyProfile() - { - CreateMap() - .Ignore(x => x.CreationTime); - } -} -```` - -## Advanced Topics - -### IObjectMapper Interface - -Assume that you have created a **reusable module** which defines AutoMapper profiles and uses `IObjectMapper` when it needs to map objects. Your module then can be used in different applications, by nature of the [modularity](Module-Development-Basics.md). - -`IObjectMapper` is an abstraction and can be replaced by the final application to use another mapping library. The problem here that your reusable module is designed to use the AutoMapper library, because it only defines mappings for it. In such a case, you will want to guarantee that your module always uses AutoMapper even if the final application uses another default object mapping library. - -`IObjectMapper` is used to contextualize the object mapper, so you can use different libraries for different modules/contexts. - -Example usage: - -````csharp -public class UserAppService : ApplicationService -{ - private readonly IRepository _userRepository; - - private readonly IObjectMapper _objectMapper; - - public UserAppService( - IRepository userRepository, - IObjectMapper objectMapper) //Inject module specific mapper - { - _userRepository = userRepository; - _objectMapper = objectMapper; - } - - public async Task CreateUserAsync(CreateUserInput input) - { - //Use the module specific mapper - var user = _objectMapper.Map(input); - - await _userRepository.InsertAsync(user); - } -} -```` - -`UserAppService` injects the `IObjectMapper`, the specific object mapper for this module. It's usage is exactly same of the `IObjectMapper`. - -The example code above don't use the `ObjectMapper` property defined in the `ApplicationService`, but injects the `IObjectMapper`. However, it is still possible to use the base property since the `ApplicationService` defines an `ObjectMapperContext` property that can be set in the class constructor. So, the example about can be re-written as like below: - -````csharp -public class UserAppService : ApplicationService -{ - private readonly IRepository _userRepository; - - public UserAppService(IRepository userRepository) - { - _userRepository = userRepository; - //Set the object mapper context - ObjectMapperContext = typeof(MyModule); - } - - public async Task CreateUserAsync(CreateUserInput input) - { - var user = ObjectMapper.Map(input); - - await _userRepository.InsertAsync(user); - } -} -```` - -While using the contextualized object mapper is same as the normal object mapper, you should register the contextualized mapper in your module's `ConfigureServices` method: - -````csharp -[DependsOn(typeof(AbpAutoMapperModule))] -public class MyModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - //Use AutoMapper for MyModule - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddMaps(validate: true); - }); - } -} -```` - -`IObjectMapper` is an essential feature for a reusable module where it can be used in multiple applications each may use a different library for object to object mapping. All pre-built ABP modules are using it. But, for the final application, you can ignore this interface and always use the default `IObjectMapper` interface. - -### IObjectMapper Interface - -ABP allows you to customize the mapping code for specific classes. Assume that you want to create a custom class to map from `User` to `UserDto`. In this case, you can create a class that implements the `IObjectMapper`: - -````csharp -public class MyCustomUserMapper : IObjectMapper, ITransientDependency -{ - public UserDto Map(User source) - { - //TODO: Create a new UserDto - } - - public UserDto Map(User source, UserDto destination) - { - //TODO: Set properties of an existing UserDto - return destination; - } -} -```` - -ABP automatically discovers and registers the `MyCustomUserMapper` and it is automatically used whenever you use the `IObjectMapper` to map `User` to `UserDto`. A single class may implement more than one `IObjectMapper` each for a different object pairs. - -> This approach is powerful since `MyCustomUserMapper` can inject any other service and use in the `Map` methods. - -Once you implement `IObjectMapper`, ABP can automatically convert a collection of `User` objects to a collection of `UserDto` objects. The following generic collection types are supported: - -* `IEnumerable` -* `ICollection` -* `Collection` -* `IList` -* `List` -* `T[]` (array) - -**Example:** - -````csharp -var users = await _userRepository.GetListAsync(); // returns List -var dtos = ObjectMapper.Map, List>(users); // creates List -```` diff --git a/docs/en/Options.md b/docs/en/Options.md deleted file mode 100644 index 76616aca28..0000000000 --- a/docs/en/Options.md +++ /dev/null @@ -1,118 +0,0 @@ -# Options - -Microsoft has introduced [the options pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) that is used to configure a group of settings used by the framework services. This pattern is implemented by the [Microsoft.Extensions.Options](https://www.nuget.org/packages/Microsoft.Extensions.Options) NuGet package, so it is usable by any type of applications in addition to ASP.NET Core based applications. - -ABP framework follows this option pattern and defines options classes to configure the framework and the modules (they are explained in the documents of the related feature). - -Since [the Microsoft documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) explains the pattern in detail, no reason to repeat all. However, ABP adds a few more features and they will be explained here. - -## Configure Options - -You typically configure options in the `ConfigureServices` of the `Startup` class. However, since ABP framework provides a modular infrastructure, you configure options in the `ConfigureServices` of your [module](Module-Development-Basics.md). Example: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.Configure(options => - { - options.IsEnabled = false; - }); -} -```` - -* `AbpAuditingOptions` is a simple class defines some properties like `IsEnabled` used here. -* `AbpModule` base class defines `Configure` method to make the code simpler. So, instead of `context.Services.Configure<...>`, you can directly use the `Configure<...>` shortcut method. - -If you are developing a reusable module, you may need to define an options class to allow developers to configure your module. In this case, define a plain options class as shown below: - -````csharp -public class MyOptions -{ - public int Value1 { get; set; } - public bool Value2 { get; set; } -} -```` - -Then developers can configure your options just like the `AbpAuditingOptions` example above: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - Configure(options => - { - options.Value1 = 42; - options.Value2 = true; - }); -} -```` - -* In this example, used the shortcut `Configure<...>` method. - -### Get the Option Value - -Whenever you need to get the value of an option, [inject](Dependency-Injection.md) the `IOptions` service into your class and use its `.Value` property. Example: - -````csharp -public class MyService : ITransientDependency -{ - private readonly MyOptions _options; - - public MyService(IOptions options) - { - _options = options.Value; //Notice the options.Value usage! - } - - public void DoIt() - { - var v1 = _options.Value1; - var v2 = _options.Value2; - } -} -```` - -Read [the Microsoft documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) for all details of the options pattern. - -## Pre Configure - -One restriction of the options pattern is that you can only resolve (inject) the `IOptions` and get the option values when the dependency injection configuration completes (that means the `ConfigureServices` methods of all modules complete). - -If you are developing a module, you may need to allow developers to set some options and use these options in the dependency injection registration phase. You may need to configure other services or change the dependency injection registration code based on these option values. - -For such cases, ABP introduces the `PreConfigure` and the `ExecutePreConfiguredActions` extension methods for the `IServiceCollection`. The pattern works as explained below. - -Define a pre option class in your module. Example: - -````csharp -public class MyPreOptions -{ - public bool MyValue { get; set; } -} -```` - -Then any [module class](Module-Development-Basics.md) depends on your module can use the `PreConfigure` method in its `PreConfigureServices` method. Example: - -````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(options => - { - options.MyValue = true; - }); -} -```` - -> Multiple modules can pre-configure the options and override the option values based on their dependency order. - -Finally, your module can execute the `ExecutePreConfiguredActions` method in its `ConfigureServices` method to get the configured option values. Example: - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - var options = context.Services.ExecutePreConfiguredActions(); - if (options.MyValue) - { - //... - } -} -```` - diff --git a/docs/en/PlugIn-Modules.md b/docs/en/PlugIn-Modules.md deleted file mode 100644 index 069bfd40ab..0000000000 --- a/docs/en/PlugIn-Modules.md +++ /dev/null @@ -1,220 +0,0 @@ -# Plug-In Modules - -It is possible to load [modules](Module-Development-Basics.md) as plug-ins. That means you may not reference to a module's assembly in your solution, but you can load that module in the application startup just like any other module. - -## Basic Usage - -The `WebApplicationBuilder.AddApplicationAsync()` extension method can get options to configure the plug-in sources. - -**Example: Load plugins from a folder** - -````csharp -await builder.AddApplicationAsync(options => -{ - options.PlugInSources.AddFolder(@"D:\Temp\MyPlugIns"); -}); -```` - -* This is the `Startup` class of a typical ASP.NET Core application. -* `PlugInSources.AddFolder` gets a folder path and to load assemblies (typically `dll`s) in that folder. - -That's all. ABP will discover the modules in the given folder, configure and initialize them just like regular modules. - -### Plug-In Sources - -`options.PlugInSources` is actually a list of `IPlugInSource` implementations and `AddFolder` is just a shortcut for the following expression: - -````csharp -options.PlugInSources.Add(new FolderPlugInSource(@"D:\Temp\MyPlugIns")); -```` - -> `AddFolder()` only looks for the assembly file in the given folder, but not looks for the sub-folders. You can pass `SearchOption.AllDirectories` as a second parameter to explore plug-ins also from the sub-folders, recursively. - -There are two more built-in Plug-In Source implementations: - -* `PlugInSources.AddFiles()` gets a list of assembly (typically `dll`) files. This is a shortcut of using `FilePlugInSource` class. -* `PlugInSources.AddTypes()` gets a list of module class types. If you use this, you need to load the assemblies of the modules yourself, but it provides flexibility when needed. This is a shortcut of using `TypePlugInSource` class. - -If you need, you can create your own `IPlugInSource` implementation and add to the `options.PlugInSources` just like the others. - -## Example: Creating a Simple Plug-In - -Create a simple **Class Library Project** in a solution: - -![simple-plugin-library](images/simple-plugin-library.png) - -You can add the ABP Framework packages that you need to use in the module. At least, you should add the `Volo.Abp.Core` package to the project, Execute the following command in the folder of the .csproj file that you want to install the package on: - -````bash -abp add-package Volo.Abp.Core -```` - - If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Core). - -Every [module](Module-Development-Basics.md) must declare a class derived from the `AbpModule`. Here, a simple module class that resolves a service and initializes it on the application startup: - -````csharp -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; -using Volo.Abp.Modularity; - -namespace MyPlugIn -{ - public class MyPlungInModule : AbpModule - { - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - var myService = context.ServiceProvider - .GetRequiredService(); - - myService.Initialize(); - } - } -} -```` - -`MyService` can be any class registered to [Dependency Injection](Dependency-Injection.md) system, as show below: - -````csharp -using Microsoft.Extensions.Logging; -using Volo.Abp.DependencyInjection; - -namespace MyPlugIn -{ - public class MyService : ITransientDependency - { - private readonly ILogger _logger; - - public MyService(ILogger logger) - { - _logger = logger; - } - - public void Initialize() - { - _logger.LogInformation("MyService has been initialized"); - } - } -} -```` - -Build the project, open the build folder, find the `MyPlugIn.dll`: - -![simple-plug-in-dll-file](images/simple-plug-in-dll-file.png) - -Copy `MyPlugIn.dll` into the plug-in folder (`D:\Temp\MyPlugIns` for this example). - -> Please delete the `MyPlugIn.deps.json` file if you use `build folder` folder as `PlugInSources`. - -If you have configured the main application like described above (see Basic Usage section), you should see the `MyService has been initialized` log in the application startup. - -## Example: Creating a Plug-In With Razor Pages - -Creating plug-ins with views inside requires a bit more attention. - -> This example assumes you've [created a new web application](https://abp.io/get-started) using the application startup template and MVC / Razor Pages UI. - -Create a new **Class Library** project in a solution: - -![simple-razor-plugin](images/simple-razor-plugin.png) - -Edit the `.csproj` file content: - -````xml - - - - net5.0 - Library - true - - - - - - - -```` - -* Changed `Sdk` to `Microsoft.NET.Sdk.Web`. -* Added `OutputType` and `IsPackable` properties. -* Added `Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared` NuGet package. - -> Depending on [Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared) package is not required. You can reference to a more base package like [Volo.Abp.AspNetCore.Mvc](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc/). However, if you will build a UI page/component, it is suggested to reference to the [Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared) package since it is the most high-level package without depending on a particular [theme](UI/AspNetCore/Theming.md). If there is no problem to depend on a particular theme, you can directly reference to the theme's package to be able to use the theme-specific features in your plug-in. - -Then create your module class in the plug-in: - -````csharp -using System.IO; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; -using Volo.Abp.Modularity; - -namespace MyMvcUIPlugIn -{ - [DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] - public class MyMvcUIPlugInModule : AbpModule - { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(mvcBuilder => - { - //Add plugin assembly - mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(typeof(MyMvcUIPlugInModule).Assembly)); - - //Add CompiledRazorAssemblyPart if the PlugIn module contains razor views. - mvcBuilder.PartManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(typeof(MyMvcUIPlugInModule).Assembly)); - }); - } - } -} -```` - -* Depending on the `AbpAspNetCoreMvcUiThemeSharedModule` since we added the related NuGet package. -* Adding the plug-in's assembly as `AssemblyPart` and `CompiledRazorAssemblyPart` to the `PartManager` of ASP.NET Core MVC. This is required by ASP.NET Core. Otherwise, your controllers or views inside the plug-in doesn't work. - -You can now add a razor page, like `MyPlugInPage.cshtml` inside the `Pages` folder: - -````html -@page -@model MyMvcUIPlugIn.Pages.MyPlugInPage -

    Welcome to my plug-in page

    -

    This page is located inside a plug-in module! :)

    -```` - -Now, you can build the plug-in project. It will produce the following output: - -![simple-razor-plug-in-dll-file](images/simple-razor-plug-in-dll-file.png) - -Copy the `MyMvcUIPlugIn.dll` into the plug-in folder (`D:\Temp\MyPlugIns` for this example). - -If you have configured the main application like described above (see Basic Usage section), you should be able to visit the `/MyPlugInPage` URL when your application: - -![simple-plugin-output](images/simple-plugin-output.png) - -## Discussions - -In real world, your plug-in may have some external dependencies. Also, your application might be designed to support plug-ins. All these are your own system requirements. What ABP does is just loading modules on the application startup. What you do inside that modules is up to you. - -However, we can provide a few suggestions for some common cases. - -### Library Dependencies - -For package/dll dependencies, you can copy the related dlls to the plug-in folder. ABP automatically loads all assemblies in the folder and your plug-in will work as expected. - -> See [Microsoft's documentation](https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#plugin-with-library-dependencies) for some additional explanations for that case. - -### Database Schema - -If your module uses a relational database and [Entity Framework Core](Entity-Framework-Core.md), it will need to have its tables available in the database. There are different ways to ensure the tables have been created when an application uses the plug-in. Some examples; - -1. The Plugin may check if the database tables does exists and create the tables on the application startup or migrate them if the plug-in has been updated and requires some schema changes. You can use EF Core's migration API to do that. -2. You can improve the `DbMigrator` application to find migrations of the plug-ins and execute them. - -There may be other solutions. For example, if your DB admin doesn't allow you to change the database schema in the application code, you may need to manually send a SQL file to the database admin to apply it to the database. - -### Plug In Sample - -We have a sample for this, You can download it from [abp-sample repository](https://github.com/abpframework/abp-samples/tree/master/DocumentationSamples/Plug-In) diff --git a/docs/en/Previews.md b/docs/en/Previews.md deleted file mode 100644 index 70ddc4fe2b..0000000000 --- a/docs/en/Previews.md +++ /dev/null @@ -1,48 +0,0 @@ -# Preview Releases - -The preview versions are released **~4 weeks before** releasing a major or feature version of the ABP Framework. They are released for developers to try and provide feedback to have more stable versions. - -Versioning of a preview release is like that: - -* 3.1.0-rc.1 -* 4.0.0-rc.1 - -More than one preview releases (like 3.1.0-rc.2 and 3.1.0-rc.3) might be published until the stable version (like 3.1.0). - -## Using the Preview Versions - -### Update the CLI - -Before creating or updating an existing solution make sure to update the CLI to the latest preview version, for example: - -````bash -dotnet tool update --global Volo.Abp.Cli --version 6.0.0-rc.2 -```` - -### New Solutions - -To create a project for testing the preview version, you can select the "**preview**" option on the [download page](https://abp.io/get-started) or use the "**--preview**" parameter with the [ABP CLI](CLI.md) new command: - -````bash -abp new Acme.BookStore --preview -```` - -This command will create a new project using the latest preview NuGet packages, NPM packages and the solution template. Whenever the stable version is released, you can switch to the stable version for your solution using the `abp switch-to-stable` command in the root folder of your solution. - -### Existing Solutions - -If you already have a solution and want to use/test the latest preview version, use the following [ABP CLI](CLI.md) command in the root folder of your solution. - -````bash -abp switch-to-preview -```` - -You can return back to the latest stable using the `abp switch-to-stable ` command later. - -````bash -abp switch-to-stable -```` - -## Providing Feedback - -You can open an issue on the [GitHub repository](https://github.com/abpframework/abp/issues/new), if you find a bug or want to provide any kind of feedback. diff --git a/docs/en/RabbitMq.md b/docs/en/RabbitMq.md deleted file mode 100644 index 9fc04aa5d6..0000000000 --- a/docs/en/RabbitMq.md +++ /dev/null @@ -1,3 +0,0 @@ -# RabbitMQ - -TODO! \ No newline at end of file diff --git a/docs/en/Redis-Cache.md b/docs/en/Redis-Cache.md deleted file mode 100644 index aa756e1a2e..0000000000 --- a/docs/en/Redis-Cache.md +++ /dev/null @@ -1,45 +0,0 @@ -# Redis Cache - -ABP Framework [Caching System](Caching.md) extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). So, **any provider** supported by the standard ASP.NET Core distributed cache can be usable in your application and can be configured just like **documented by Microsoft**. - -However, ABP provides an **integration package** for Redis Cache: [Volo.Abp.Caching.StackExchangeRedis](https://www.nuget.org/packages/Volo.Abp.Caching.StackExchangeRedis). There are two reasons for using this package, instead of the standard [Microsoft.Extensions.Caching.StackExchangeRedis](https://www.nuget.org/packages/Microsoft.Extensions.Caching.StackExchangeRedis/) package. - -1. It implements `SetManyAsync` and `GetManyAsync` methods. These are not standard methods of the Microsoft Caching library, but added by the ABP Framework [Caching](Caching.md) system. They **significantly increases the performance** when you need to set/get multiple cache items with a single method call. -2. It **simplifies** the Redis cache **configuration** (will be explained below). - -> Volo.Abp.Caching.StackExchangeRedis is already uses the Microsoft.Extensions.Caching.StackExchangeRedis package, but extends and improves it. - -## Installation - -> This package is already installed in the application startup template if it is using Redis. - -Open a command line in the folder of your `.csproj` file and type the following ABP CLI command: - -````bash -abp add-package Volo.Abp.Caching.StackExchangeRedis -```` - -## Configuration - -Volo.Abp.Caching.StackExchangeRedis package automatically gets the Redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`: - -````js -"Redis": { - "IsEnabled": "true", - "Configuration": "127.0.0.1" -} -```` -The setting `IsEnabled` is optional and will be considered `true` if it is not set. - -Alternatively you can configure the standard [RedisCacheOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.stackexchangeredis.rediscacheoptions) [options](Options.md) class in the `ConfigureServices` method of your [module](Module-Development-Basics.md): - -````csharp -Configure(options => -{ - //... -}); -```` - -## See Also - -* [Caching](Caching.md) \ No newline at end of file diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md deleted file mode 100644 index d99e0cb83e..0000000000 --- a/docs/en/Repositories.md +++ /dev/null @@ -1,511 +0,0 @@ -# Repositories - -"*Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects*" (Martin Fowler). - -Repositories, in practice, are used to perform database operations for domain objects (see [Entities](Entities.md)). Generally, a separate repository is used for each **aggregate root** or entity. - -## Generic Repositories - -ABP can provide a **default generic repository** for each aggregate root or entity. You can [inject](Dependency-Injection.md) `IRepository` into your service and perform standard **CRUD** operations. - -> The database provider layer should be properly configured to be able to use the default generic repositories. It is **already done** if you've created your project using the startup templates. If not, refer to the database provider documents ([EF Core](Entity-Framework-Core.md) / [MongoDB](MongoDB.md)) to configure it. - -**Example usage of a default generic repository:** - -````C# -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Demo -{ - public class PersonAppService : ApplicationService - { - private readonly IRepository _personRepository; - - public PersonAppService(IRepository personRepository) - { - _personRepository = personRepository; - } - - public async Task CreateAsync(CreatePersonDto input) - { - var person = new Person(input.Name); - - await _personRepository.InsertAsync(person); - } - - public async Task GetCountAsync(string filter) - { - return await _personRepository.CountAsync(p => p.Name.Contains(filter)); - } - } -} -```` - -In this example; - -* `PersonAppService` simply injects `IRepository` in it's constructor. -* `CreateAsync` method uses `InsertAsync` to save the new entity. -* `GetCountAsync` method gets a filtered count of all people in the database. - -### Standard Repository Methods - -Generic Repositories provide some standard CRUD features out of the box: - -* `GetAsync`: Returns a single entity by its `Id` or a predicate (lambda expression). - * Throws `EntityNotFoundException` if the requested entity was not found. - * Throws `InvalidOperationException` if there are multiple entities with the given predicate. -* `FindAsync`: Returns a single entity by its `Id` or a predicate (lambda expression). - * Returns `null` if the requested entity was not found. - * Throws `InvalidOperationException` if there are multiple entities with the given predicate. -* `InsertAsync`: Inserts a new entity into the database. -* `UpdateAsync`: Updates an existing entity in the database. -* `DeleteAsync`: Deletes the given entity from the database. - * This method has an overload that takes a predicate (lambda expression) to delete multiple entities to satisfy the given condition. -* `GetListAsync`: Returns the list of all entities in the database. -* `GetPagedListAsync`: Returns a limited list of entities. Gets `skipCount`, `maxResultCount`, and `sorting` parameters. -* `GetCountAsync`: Gets the count of all entities in the database. - -There are overloads of these methods. - -* Provides `UpdateAsync` and `DeleteAsync` methods to update or delete an entity by entity object or its id. -* Provides `DeleteAsync` method to delete multiple entities by a filter. - -### Querying / LINQ over the Repositories - -Repositories provide the `GetQueryableAsync()` method that returns an `IQueryable` object. You can use this object to perform LINQ queries on the entities in the database. - -**Example: Use LINQ with the repositories** - -````csharp -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Demo -{ - public class PersonAppService : ApplicationService - { - private readonly IRepository _personRepository; - - public PersonAppService(IRepository personRepository) - { - _personRepository = personRepository; - } - - public async Task> GetListAsync(string filter) - { - //Obtain the IQueryable - IQueryable queryable = await _personRepository.GetQueryableAsync(); - - //Create a query - var query = from person in queryable - where person.Name == filter - orderby person.Name - select person; - - //Execute the query to get list of people - var people = query.ToList(); - - //Convert to DTO and return to the client - return people.Select(p => new PersonDto {Name = p.Name}).ToList(); - } - } -} -```` - -You could also use the LINQ extension methods: - -````csharp -public async Task> GetListAsync(string filter) -{ - //Obtain the IQueryable - IQueryable queryable = await _personRepository.GetQueryableAsync(); - - //Execute a query - var people = queryable - .Where(p => p.Name.Contains(filter)) - .OrderBy(p => p.Name) - .ToList(); - - //Convert to DTO and return to the client - return people.Select(p => new PersonDto {Name = p.Name}).ToList(); -} -```` - -Any standard LINQ method can be used over the `IQueryable` returned from the repository. - -> This sample uses `ToList()` method, but it is **strongly suggested to use the asynchronous methods** to perform database queries, like `ToListAsync()` for this example. See the **`IQueryable` & Async Operations** section to learn how you can do it. - -> **Exposing `IQueryable` outside of a repository** class may leak your data access logic to the application layer. If you want to strictly follow the **layered architecture** principles, you can consider to implement a custom repository class and wrap your data access logic inside your repository class. You can see the ***Custom Repositories*** section to learn how to create custom repository classes for your application. - -### Bulk Operations - -There are some methods to perform bulk operations in the database; - -* `InsertManyAsync` -* `UpdateManyAsync` -* `DeleteManyAsync` - -These methods work with multiple entities and can take advantage of bulk operations if supported by the underlying database provider. - -> Optimistic concurrency control may not be possible when you use `UpdateManyAsync` and `DeleteManyAsync` methods. - -### Soft / Hard Delete - -`DeleteAsync` method of the repository doesn't delete the entity if the entity is a **soft-delete** entity (that implements `ISoftDelete`). Soft-delete entities are marked as "deleted" in the database. The Data Filter system ensures that the soft deleted entities are not retrieved from the database normally. - -If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method to physically delete the entity from the database in case you need it. - -> See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete. - -### Delete Direct - -`DeleteDirectAsync` method of the repository deletes all entities that fit to the given predicate. It directly deletes entities from the database, without fetching them. - -Some features (like soft-delete, multi-tenancy, and audit logging) won't work, so use this method carefully when you need it. Use the `DeleteAsync` method if you need these features. - -> Currently only [EF Core supports it](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#basic-executedelete-examples), For the ORMs that don't support direct delete, we will fall back to the existing `DeleteAsync` method. - -### Ensure Entities Exist - -The `EnsureExistsAsync` extension method accepts entity id or entities query expression to ensure entities exist, otherwise, it will throw `EntityNotFoundException`. - -### Enabling / Disabling the Change Tracking - -ABP provides repository extension methods and attributes that can be used to control the change-tracking behavior for queried entities in the underlying database provider. - -Disabling change tracking can gain performance if you query many entities from the database for read-only purposes. Querying single or a few entities won't make much performance difference, but you are free to use it whenever you like. - -> If the underlying database provider doesn't support change tracking, then this system won't have any effect. [Entity Framework Core](Entity-Framework-Core.md) supports change tracking, for example, while the [MongoDB](MongoDB.md) provider doesn't support it. - -#### Repository Extension Methods for Change Tracking - -Change tracking is enabled unless you explicitly disable it. - -**Example: Using the `DisableTracking` extension method** - -````csharp -public class MyDemoService : ApplicationService -{ - private readonly IRepository _personRepository; - - public MyDemoService(IRepository personRepository) - { - _personRepository = personRepository; - } - - public async Task DoItAsync() - { - // Change tracking is enabled in that point (by default) - - using (_personRepository.DisableTracking()) - { - // Change tracking is disabled in that point - var list = await _personRepository.GetPagedListAsync(0, 100, "Name ASC"); - } - - // Change tracking is enabled in that point (by default) - } -} -```` - -> `DisableTracking` extension method returns an `IDisposable` object, so you can safely **restore** the change tracking behavior to the **previous state** once the `using` block ends. Basically, `DisableTracking` method ensures that the change tracking is disabled inside the `using` block, but doesn't affect outside of the `using` block. That means, if change tracking was already disabled, `DisableTracking` and the disposable return value do nothing. - -`EnableTracking()` method works exactly opposite to the `DisableTracking()` method. You typically won't use it (because the change tracking is already enabled by default), but it is there in case you need that. - -#### Attributes for Change Tracking - -You typically use the `DisableTracking()` method for the application service methods that only return data, but don't make any change to entities. For such cases, you can use the `DisableEntityChangeTracking` attribute on your method/class as a shortcut to disable the change tracking for the whole method body. - -**Example: Using the `DisableEntityChangeTracking` attribute on a method** - -````csharp -[DisableEntityChangeTracking] -public virtual async Task> GetListAsync() -{ - /* We disabled the change tracking in this method - because we won't change the people objects */ - var people = await _personRepository.GetListAsync(); - return ObjectMapper.Map, List(people); -} -```` - -`EnableEntityChangeTracking` can be used for the opposite purpose, and it ensures that the change tracking is enabled for a given method. Since the change tracking is enabled by default, `EnableEntityChangeTracking` may be needed only if you know that your method is called from a context that disables the change tracking. - -`DisableEntityChangeTracking` and `EnableEntityChangeTracking` attributes can be used on a **method** or on a **class** (which affects all of the class methods). - -ABP uses dynamic proxying to make these attributes work. There are some rules here: - -* If you are **not injecting** the service over an interface (like `IPersonAppService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work. -* Only `async` methods (methods returning a `Task` or `Task`) are intercepted. - -> Change tracking behavior doesn't affect tracking entity objects returned from `InsertAsync` and `UpdateAsync` methods. The objects returned from these methods are always tracked (if the underlying provider has the change tracking feature) and any change you make to these objects are saved into the database. - -## Other Generic Repository Types - -Standard `IRepository` interface exposes the standard `IQueryable` and you can freely query using the standard LINQ methods. This is fine for most of the applications. However, some ORM providers or database systems may not support standard `IQueryable` interface. If you want to use such providers, you can't rely on the `IQueryable`. - -### Basic Repositories - -ABP provides `IBasicRepository` and `IBasicRepository` interfaces to support such scenarios. You can extend these interfaces (and optionally derive from `BasicRepositoryBase`) to create custom repositories for your entities. - -Depending on `IBasicRepository` but not depending on `IRepository` has the advantage of making it possible to work with all data sources even if they don't support `IQueryable`. - -Major vendors, like Entity Framework, NHibernate, or MongoDB already support `IQueryable`. So, working with `IRepository` is the **suggested** way for typical applications. However reusable module developers may consider `IBasicRepository` to support a wider range of data sources. - -### Read Only Repositories - -There are also `IReadOnlyRepository` and `IReadOnlyBasicRepository` interfaces for those who only want to depend on the querying capabilities of the repositories. - -The `IReadOnlyRepository` derives the `IReadOnlyBasicRepository` and provides the following properties and methods as well: - -Properties: - -`AsyncExecuter`: a service that is used to execute an `IQueryable` object asynchronously **without depending on the actual database provider**. - -Methods: - -- `GetListAsync()` -- `GetQueryableAsync()` -- `WithDetails()` 1 overload -- `WithDetailsAsync()` 1 overload - -Whereas the `IReadOnlyBasicRepository` provides the following methods: - -- `GetCountAsync()` -- `GetListAsync()` -- `GetPagedListAsync()` - -They can all be seen below: - -![generic-repositories](images/generic-repositories.png) - -#### Read Only Repositories behavior in Entity Framework Core - -Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/) because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still use the [AsTracking()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.astracking) extension method on the LINQ expression, or `EnableTracking()` extension method on the repository object (See *Enabling / Disabling the Change Tracking* section in this document). - -> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) and then cast it to a read-only repository interface. - -### Generic Repository without a Primary Key - -If your entity does not have an Id primary key (it may have a composite primary key for instance) then you cannot use the `IRepository` (or basic/readonly versions) defined above. In that case, you can inject and use `IRepository` for your entity. - -> `IRepository` has a few missing methods that normally work with the `Id` property of an entity. Because the entity has no `Id` property in that case, these methods are not available. One example is the `Get` method which gets an id and returns the entity with the given id. However, you can still use `IQueryable` features to query entities by standard LINQ methods. - -## Custom Repositories - -Default generic repositories will be sufficient for most cases. However, you may need to create a custom repository class for your entity. - -### Custom Repository Example - -ABP does not force you to implement any interface or inherit from any base class for a repository. It can be just a simple POCO class. However, it's suggested to inherit existing repository interfaces and classes to make your work easier and get the standard methods out of the box. - -#### Custom Repository Interface - -First, define an interface in your domain layer: - -```c# -public interface IPersonRepository : IRepository -{ - Task FindByNameAsync(string name); -} -``` - -This interface extends `IRepository` to take advantage of pre-built repository functionality. - -#### Custom Repository Implementation - -A custom repository is tightly coupled to the data access tool type you are using. In this example, we will use Entity Framework Core: - -````C# -public class PersonRepository : EfCoreRepository, IPersonRepository -{ - public PersonRepository(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - - } - - public async Task FindByNameAsync(string name) - { - var dbContext = await GetDbContextAsync(); - return await dbContext.Set() - .Where(p => p.Name == name) - .FirstOrDefaultAsync(); - } -} -```` - -You can directly access the data access provider (`DbContext` in this case) to perform operations. - -> See [EF Core](Entity-Framework-Core.md) or [MongoDb](MongoDB.md) document for more info about the custom repositories. - -## IQueryable & Async Operations - -`IRepository` provides `GetQueryableAsync()` to obtain an `IQueryable`, which means you can **directly use LINQ extension methods** on it, as shown in the example of the "*Querying / LINQ over the Repositories*" section above. - -**Example: Using the `Where(...)` and the `ToList()` extension methods** - -````csharp -var queryable = await _personRepository.GetQueryableAsync(); -var people = queryable - .Where(p => p.Name.Contains(nameFilter)) - .ToList(); -```` - -`.ToList`, `Count()`... are standard extension methods defined in the `System.Linq` namespace ([see all](https://docs.microsoft.com/en-us/dotnet/api/system.linq.queryable)). - -You normally want to use `.ToListAsync()`, `.CountAsync()`... instead, to be able to write a **truly async code**. - -However, you see that you can't use all the async extension methods in your application or domain layer when you create a new project using the standard [application startup template](Startup-Templates/Application.md), because; - -* These async methods **are not standard LINQ methods** and they are defined in the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) NuGet package. -* The standard template **doesn't have a reference** to the EF Core package from the domain and application layers, to be independent from the database provider. - -Based on your requirements and development model, you have the following options to be able to use the async methods. - -> Using async methods is strongly suggested! Don't use sync LINQ methods while executing database queries to be able to develop a scalable application. - -### Option-1: Reference to the Database Provider Package - -**The easiest solution** is to directly add the EF Core package from the project you want to use these async methods. - -> Add the [Volo.Abp.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore) NuGet package to your project, which indirectly references the EF Core package. This ensures that you use the correct version of the EF Core compatible with the rest of your application. - -When you add the NuGet package to your project, you can take full power of the EF Core extension methods. - -**Example: Directly using the `ToListAsync()` after adding the EF Core package** - -````csharp -var queryable = await _personRepository.GetQueryableAsync(); -var people = queryable - .Where(p => p.Name.Contains(nameFilter)) - .ToListAsync(); -```` - -This method is suggested; - -* If you are developing an application and you **don't plan to change** EF Core in the future, or you can **tolerate** it if you need to change it later. We believe that's reasonable if you are developing a final application. - -#### MongoDB Case - -If you are using [MongoDB](MongoDB.md), you need to add the [Volo.Abp.MongoDB](https://www.nuget.org/packages/Volo.Abp.MongoDB) NuGet package to your project. Even in this case, you can't directly use async LINQ extensions (like `ToListAsync`) because MongoDB doesn't provide async extension methods for `IQueryable`, but provides for `IMongoQueryable`. You need to cast the query to `IMongoQueryable` first to be able to use the async extension methods. - -**Example: Cast `IQueryable` to `IMongoQueryable` and use `ToListAsync()`** - -````csharp -var queryable = await _personRepository.GetQueryableAsync(); -var people = ((IMongoQueryable) queryable - .Where(p => p.Name.Contains(nameFilter))) - .ToListAsync(); -```` - -### Option-2: Use the IRepository Async Extension Methods - -ABP Framework provides async extension methods for the repositories, just similar to async LINQ extension methods. - -**Example: Use `CountAsync` and `FirstOrDefaultAsync` methods on the repositories** - -````csharp -var countAll = await _personRepository - .CountAsync(); - -var count = await _personRepository - .CountAsync(x => x.Name.StartsWith("A")); - -var book1984 = await _bookRepository - .FirstOrDefaultAsync(x => x.Name == "John"); -```` - -The standard LINQ extension methods are supported: *AllAsync, AnyAsync, AverageAsync, ContainsAsync, CountAsync, FirstAsync, FirstOrDefaultAsync, LastAsync, LastOrDefaultAsync, LongCountAsync, MaxAsync, MinAsync, SingleAsync, SingleOrDefaultAsync, SumAsync, ToArrayAsync, ToListAsync*. - -This approach still **has a limitation**. You need to call the extension method directly on the repository object. For example, the below usage is **not supported**: - -```csharp -var queryable = await _bookRepository.GetQueryableAsync(); -var count = await queryable.Where(x => x.Name.Contains("A")).CountAsync(); -``` - -This is because the `CountAsync()` method in this example is called on an `IQueryable` interface, not on the repository object. See the other options for such cases. - -This method is suggested **wherever possible**. - -### Option-3: IAsyncQueryableExecuter - -`IAsyncQueryableExecuter` is a service that is used to execute an `IQueryable` object asynchronously **without depending on the actual database provider**. - -**Example: Inject & use the `IAsyncQueryableExecuter.ToListAsync()` method** - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Linq; - -namespace AbpDemo -{ - public class ProductAppService : ApplicationService, IProductAppService - { - private readonly IRepository _productRepository; - private readonly IAsyncQueryableExecuter _asyncExecuter; - - public ProductAppService( - IRepository productRepository, - IAsyncQueryableExecuter asyncExecuter) - { - _productRepository = productRepository; - _asyncExecuter = asyncExecuter; - } - - public async Task> GetListAsync(string name) - { - //Obtain the IQueryable - var queryable = await _productRepository.GetQueryableAsync(); - - //Create the query - var query = queryable - .Where(p => p.Name.Contains(name)) - .OrderBy(p => p.Name); - - //Run the query asynchronously - List products = await _asyncExecuter.ToListAsync(query); - - //... - } - } -} -```` - -> `ApplicationService` and `DomainService` base classes already have `AsyncExecuter` properties pre-injected and usable without needing an explicit constructor injection. - -ABP Framework executes the query asynchronously using the actual database provider's API. While that is not the usual way to execute a query, it is the best way to use the async API without depending on the database provider. - -This method is suggested; - -* If you want to develop your application code **without depending** on the database provider. -* If you are building a **reusable library** that doesn't have a database provider integration package, but needs to execute an `IQueryable` object in some case. - -For example, ABP Framework uses the `IAsyncQueryableExecuter` in the `CrudAppService` base class (see the [application services](Application-Services.md) document). - -### Option-4: Custom Repository Methods - -You can always create custom repository methods and use the database provider-specific APIs, like async extension methods here. See [EF Core](Entity-Framework-Core.md) or [MongoDb](MongoDB.md) document for more info about the custom repositories. - -This method is suggested; - -* If you want to **completely isolate** your domain & application layers from the database provider. -* If you develop a **reusable [application module](Modules/Index.md)** and don't want to force to a specific database provider, which should be done as a [best practice](Best-Practices/Index.md). - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/generic-repositories) diff --git a/docs/en/Road-Map.md b/docs/en/Road-Map.md deleted file mode 100644 index 667e256fbf..0000000000 --- a/docs/en/Road-Map.md +++ /dev/null @@ -1,43 +0,0 @@ -# ABP Framework Road Map - -This document provides a road map, release schedule, and planned features for the ABP Framework. - -## Next Versions - -### v8.3 - -The next version will be 8.3 and planned to release the stable 8.3 version in August 2024. We will be mostly working on the following topics: - -* Angular Universal ([#15782](https://github.com/abpframework/abp/issues/15782)) -* Angular generate-proxy root namespace options ([#18932](https://github.com/abpframework/abp/issues/18932)) -* Blazor global JS & CSS at runtime ([#19963](https://github.com/abpframework/abp/issues/19963)) -* CMS Kit - Improvement in editing approval system for comments ([#19976](https://github.com/abpframework/abp/issues/19976)) -* Improvements on the existing features and provide more guides. - -See the [8.3 milestone](https://github.com/abpframework/abp/milestone/101) for all the issues we've planned to work on. - -## Backlog Items - -The *Next Versions* section above shows the main focus of the planned versions. However, in each release, we add new features to the core framework and the [application modules](Modules/Index.md). - -Here is a list of major items in the backlog we are considering working on in the next versions. - -* [#86](https://github.com/abpframework/abp/issues/86) / GrapQL Integration -* [#236](https://github.com/abpframework/abp/issues/236) / Resource-based authorization system -* [#2882](https://github.com/abpframework/abp/issues/2882) / Providing a gRPC integration infrastructure (while it is [already possible](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) to create or consume gRPC endpoints for your application, we plan to create endpoints for the [standard application modules](https://docs.abp.io/en/abp/latest/Modules/Index)) -* [#57](https://github.com/abpframework/abp/issues/57) / Built-in CQRS infrastructure -* [#4223](https://github.com/abpframework/abp/issues/4223) / WebHook system -* [#162](https://github.com/abpframework/abp/issues/162) / Azure ElasticDB Integration for multitenancy -* [#2296](https://github.com/abpframework/abp/issues/2296) / Feature toggling infrastructure -* [#16342](https://github.com/abpframework/abp/issues/16342) / CmsKit: Meta information for SEO -* [#16260](https://github.com/abpframework/abp/issues/16260) / GCP Blob Storage Provider -* [#15932](https://github.com/abpframework/abp/issues/15932) / Introduce ABP Diagnostics Module -* [#16756](https://github.com/abpframework/abp/issues/16756) / Blob Storing - Provider configuration UI -* [#16744](https://github.com/abpframework/abp/issues/16744) / State Management API - -You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. The backlog items are subject to change. We are adding new items and changing priorities based on the community feedback and goals of the project. - -## Feature Requests - -Vote for your favorite feature on the related GitHub issues (and write your thoughts). You can create an issue on [the GitHub repository](https://github.com/abpframework/abp) for your feature requests, but first search in the existing issues. - diff --git a/docs/en/SMS-Sending.md b/docs/en/SMS-Sending.md deleted file mode 100644 index 3043ce643a..0000000000 --- a/docs/en/SMS-Sending.md +++ /dev/null @@ -1,113 +0,0 @@ -# SMS Sending - -The ABP Framework provides an abstraction to sending SMS. Having such an abstraction has some benefits; - -- You can then **easily change** your SMS sender without changing your application code. -- If you want to create **reusable application modules**, you don't need to make assumption about how the SMS are sent. - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.Sms -``` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Sms). - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.Sms](https://www.nuget.org/packages/Volo.Abp.Sms) NuGet package to your project: - -``` -Install-Package Volo.Abp.Sms -``` - -2. Add the `AbpSmsModule` to the dependency list of your module: - -```csharp -[DependsOn( - //...other dependencies - typeof(AbpSmsModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -``` - -## Sending SMS - -[Inject](Dependency-Injection.md) the `ISmsSender` into any service and use the `SendAsync` method to send a SMS. - -**Example:** - -```csharp -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Sms; - -namespace MyProject -{ - public class MyService : ITransientDependency - { - private readonly ISmsSender _smsSender; - - public MyService(ISmsSender smsSender) - { - _smsSender = smsSender; - } - - public async Task DoItAsync() - { - await _smsSender.SendAsync( - "+012345678901", // target phone number - "This is test sms..." // message text - ); - } - } -} -``` - -The given `SendAsync` method in the example is an extension method to send an SMS with primitive parameters. In addition, you can pass an `SmsMessage` object which has the following properties: - -- `PhoneNumber` (`string`): Target phone number -- `Text` (`string`): Message text -- `Properties` (`Dictionary`): Key-value pairs to pass custom arguments - -## NullSmsSender - -`NullSmsSender` is a the default implementation of the `ISmsSender`. It writes SMS content to the [standard logger](Logging.md), rather than actually sending the SMS. - -This class can be useful especially in development time where you generally don't want to send real SMS. **However, if you want to actually send SMS, you should implement the `ISmsSender` in your application code.** - -## Implementing the ISmsSender - -You can easily create your SMS sending implementation by creating a class that implements the `ISmsSender` interface, as shown below: - -```csharp -using System.IO; -using System.Threading.Tasks; -using Volo.Abp.Sms; -using Volo.Abp.DependencyInjection; - -namespace AbpDemo -{ - public class MyCustomSmsSender : ISmsSender, ITransientDependency - { - public async Task SendAsync(SmsMessage smsMessage) - { - // Send sms - } - } -} -``` - -## More - -[ABP Commercial](https://commercial.abp.io/) provides Twilio integration package to send SMS over [Twilio service](https://docs.abp.io/en/commercial/latest/modules/twilio-sms). diff --git a/docs/en/Samples/Index.md b/docs/en/Samples/Index.md deleted file mode 100644 index 088fb78f67..0000000000 --- a/docs/en/Samples/Index.md +++ /dev/null @@ -1,99 +0,0 @@ -# Sample Applications - -Here, a list of official samples built with the ABP Framework. Most of these samples are located under the [abpframework/abp-samples](https://github.com/abpframework/abp-samples) GitHub repository. - -## eShopOnAbp - -Reference microservice solution built with the ABP Framework and .NET. - -* [Source code](https://github.com/abpframework/eShopOnAbp) - -## EventHub - -This is a reference application built with the ABP Framework. It implements the Domain Driven Design with multiple application layers. - -* [Live](https://openeventhub.com/) -* [Source code](https://github.com/abpframework/eventhub) - -## CMS Kit Demo - -This reference application built with the ABP Framework and demonstrates the [CMS Kit Module's](../Modules/Cms-Kit/Index.md) capabilities. - -* [Live](https://cms-kit-demo.abpdemo.com/) -* [Source code](https://github.com/abpframework/cms-kit-demo) - -## Book Store - -A simple CRUD application to show basic principles of developing an application with the ABP Framework. The same sample was implemented with different technologies: - -* **Book Store: Razor Pages UI & Entity Framework Core** - * [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF) - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* **Book Store: Blazor UI & Entity Framework Core** - * [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=Blazor&DB=EF) - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* **Book Store: Angular UI & MongoDB** - * [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG&DB=Mongo) - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -* **Book Store: Modular application (Razor Pages UI & EF Core)** - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular) - -While there is no Razor Pages & MongoDB combination, you can check both documents to understand it since DB & UI selection don't effect each other. - -## Other Samples - -* **Event Organizer**: A sample application to create events (meetups) and allow others to register the events. Developed using EF Core and Blazor UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer) - * [Article](https://community.abp.io/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z) -* **Entity Framework Migrations**: A solution to demonstrate how to split your application into multiple databases each database contains different modules. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo) - * [EF Core database migrations document](../Entity-Framework-Core-Migrations.md) -* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) - * [SignalR Integration document](../SignalR-Integration.md) -* **Real Time Messaging In A Distributed Architecture** (using SingalR & RabbitMQ) - * [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo) - * [Article](https://community.abp.io/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17) -* **Dashboard Demo**: A simple application to show how to use the widget system for the ASP.NET Core MVC UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) - * [Widget documentation](../UI/AspNetCore/Widgets.md) -* **RabbitMQ Event Bus Demo**: A solution consists of two applications communicating to each other via distributed events with RabbitMQ integration. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/RabbitMqEventBus) - * [Distributed event bus document](../Distributed-Event-Bus.md) - * [RabbitMQ distributed event bus integration document](../Distributed-Event-Bus-RabbitMQ-Integration.md) -* **Text Templates Demo**: Shows different use cases of the text templating system. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) - * [Text templating documentation](../Text-Templating.md) -* **Stored Procedure Demo**: Demonstrates how to use stored procedures, database views and functions with best practices. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) -* **Passwordless Authentication**: Shows how to add a custom token provider to authenticate a user with a link, instead of entering a password. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication) - * [Article](https://community.abp.io/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj) -* **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization) - * Related articles: - * [Azure Active Directory Authentication](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf) - * [Customize the Login Page](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd) - * [Customize the SignIn Manager](https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753) -* **GRPC Demo**: Shows how to add a gRPC service to an ABP Framework based web application and consume it from a console application. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) -* **Telerik Blazor Integration**: Shows how to install and use Telerik Blazor components with the ABP Framework. - * [Article](https://community.abp.io/articles/how-to-integrate-the-telerik-blazor-components-to-the-abp-blazor-ui-q8g31abb) -* **Angular Material Integration**: Implemented the web application tutorial using the Angular Material library. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreAngularMaterial) - * [Article](https://community.abp.io/articles/using-angular-material-components-with-the-abp-framework-af8ft6t9) -* **DevExtreme Angular Component Integration**: How to install and use DevExtreme components in the ABP Framework Angular UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Angular) - * [Article](https://community.abp.io/articles/using-devextreme-angular-components-with-the-abp-framework-x5nyvj3i) -* **DevExtreme MVC / Razor Pages Component Integration**: How to install and use DevExtreme components in the ABP Framework MVC / Razor Pages UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Mvc) - * [Article](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) -* **Syncfusion Blazor Integration**: Shows how to install and integrate Syncfusion UI with ABP Framework Blazor UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/SyncfusionSample) - * [Article](https://community.abp.io/articles/using-syncfusion-components-with-the-abp-framework-5ccvi8kc) -* **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with the ABP Framework installed. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication) - * [Documentation](../Getting-Started-AspNetCore-Application.md) -* **Using Elsa Workflow with ABP Framework**: Shows how to use the Elsa Core workflow library within an ABP-based application. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo) - * [Article](https://community.abp.io/articles/using-elsa-workflow-with-the-abp-framework-773siqi9) diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md deleted file mode 100644 index 1e2549338f..0000000000 --- a/docs/en/Samples/Microservice-Demo.md +++ /dev/null @@ -1,1421 +0,0 @@ -# Microservice Demo Solution - -> This solution is no longer maintained. See [the eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) for the replacement solution. - -*"Microservices are a software development technique—a variant of the **service-oriented architecture** (SOA) architectural style that structures an application as a collection of **loosely coupled services**. In a microservices architecture, services are **fine-grained** and the protocols are **lightweight**. The benefit of decomposing an application into different smaller services is that it improves **modularity**. This makes the application easier to understand, develop, test, and become more resilient to architecture erosion. It **parallelizes development** by enabling small autonomous teams to **develop, deploy and scale** their respective services independently. It also allows the architecture of an individual service to emerge through **continuous refactoring**. Microservices-based architectures enable **continuous delivery and deployment**."* - -— [Wikipedia](https://en.wikipedia.org/wiki/Microservices) - -## Introduction - -One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](../Microservice-Architecture.md). - -This sample aims to demonstrate a simple yet complete microservice solution; - -* Has multiple, independent, self-deployable **microservices**. -* Multiple **web applications**, each uses a different API gateway. -* Has multiple **gateways** / BFFs (Backend for Frontends) developed using the [Ocelot](https://github.com/ThreeMammals/Ocelot) library. -* Has an **authentication service** developed using the [IdentityServer](https://identityserver.io/) framework. It's also a SSO (Single Sign On) application with necessary UIs. -* Has **multiple databases**. Some microservices has their own database while some services/applications shares a database (to demonstrate different use cases). -* Has different types of databases: **SQL Server** (with **Entity Framework Core** ORM) and **MongoDB**. -* Has a **console application** to show the simplest way of using a service by authenticating. -* Uses [Redis](https://redis.io/) for **distributed caching**. -* Uses [RabbitMQ](https://www.rabbitmq.com/) for service-to-service **messaging**. -* Uses [Docker](https://www.docker.com/) & [Kubernetes](https://kubernetes.io/) to **deploy** & run all services and applications. -* Uses [Elasticsearch](https://www.elastic.co/products/elasticsearch) & [Kibana](https://www.elastic.co/products/kibana) to store and visualize the logs (written using [Serilog](https://serilog.net/)). - -The diagram below shows the system: - -![microservice-sample-diagram-2](../images/microservice-sample-diagram-3.png) - -### Source Code - -You can get the source code from [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo). - -## Running the Solution - -### Pre Requirements - -To be able to run the solution from source code, following tools should be installed and running on your computer: - -* [SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-downloads) 2015+ (can be [express edition](https://www.microsoft.com/en-us/sql-server/sql-server-editions-express)) -* [Redis](https://redis.io/download) 5.0+ -* [RabbitMQ](https://www.rabbitmq.com/install-windows.html) 3.7.11+ -* [MongoDB](https://www.mongodb.com/download-center) 4.0+ -* [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+ -* [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (optional, recommended to show logs) - -### Running Infrastructure - -* Docker-compose is used to run the pre requirements with ease as default. If you don't have it, you can download and start using [Docker for Windows](https://docs.docker.com/docker-for-windows/) from [here](https://docs.docker.com/docker-for-windows/install/) on windows environment. -* Run the command `docker-compose -f docker-compose.infrastructure.yml -f docker-compose.infrastructure.override.yml up -d` at `MicroserviceDemo` directory or run the powershell script `__Run_Infrastructure.ps1` located at `MicroserviceDemo/_run` directory. -* If you don't want to use docker for pre required services and install them on your local development, you need to update `appsettings.json` files of the projects in the MicroserviceDemo solution accordingly. - -### Open & Build the Visual Studio Solution - -* Open the `samples\MicroserviceDemo\MicroserviceDemo.sln` in Visual Studio 2017 (15.9.0+). -* Run `dotnet restore` from the command line inside the `samples\MicroserviceDemo` folder. -* Build the solution in Visual Studio. - -### Create Databases - -MongoDB database is created dynamically, however you need to create database schemas for SQL server databases. The solution is configured to use Entity Core Code First migrations, so you can easily create databases. - -There are two SQL server databases in this solution. - -#### MsDemo_Identity Database - -* Right click to the `AuthServer.Host` project and click to the `Set as startup project`. -* Open the **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console) -* Select `AuthServer.Host` as the **Default project**. -* Run `Update-Database` command. - -![microservice-sample-update-database-authserver](../images/microservice-sample-update-database-authserver.png) - -#### MsDemo_ProductManagement - -- Right click to the `ProductService.Host` project and click to the `Set as startup project`. -- Open the **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console) -- Select `ProductService.Host` as the **Default project**. -- Run `Update-Database` command. - -![microservice-sample-update-database-products](../images/microservice-sample-update-database-products.png) - -### Run Projects - -Run the projects with the following order (right click to each project, set as startup project an press Ctrl+F5 to run without debug): - -* AuthServer.Host -* IdentityService.Host -* TenantManagementService.Host -* BloggingService.Host -* ProductService.Host -* InternalGateway.Host -* BackendAdminAppGateway.Host -* PublicWebSiteGateway.Host -* BackendAdminApp.Host -* PublicWebSite.Host - -When you run projects, they will add some initial demo data to their databases. - -## A Brief Overview of the Solution - -The Visual Studio solution consists of multiple projects each have different roles in the system: - -![microservice-sample-solution](../images/microservice-sample-solution-2.png) - -### Applications - -These are the actual applications those have user interfaces to interact to the users and use the system. - -- **AuthServer.Host**: Host the IdentityServer4 to provide an authentication service to other services and applications. It is a single-sign server and contains the login page. -- **BackendAdminApp.Host**: This is a backend admin application that host UI for Identity and Product management modules. -- **PublicWebSite.Host**: As public web site that contains a simple product list page and blog module UI. -- **ConsoleClientDemo**: A simple console application to demonstrate the usage of services from a C# application. - -### Gateways / BFFs (Backend for Frontend) - -Gateways are used to provide a single entry point to the applications. It can also used for rate limiting, load balancing... etc. Used the [Ocelot](https://github.com/ThreeMammals/Ocelot) library. - -* **BackendAdminAppGateway.Host**: Used by the BackendAdminApp.Host application as backend. -* **PublicWebSiteGateway.Host**: Used by the PublicWebSite.Host application as backend. -* **InternalGateway.Host**: Used for inter-service communication (the communication between microservices). - -### Microservices - -Microservices have no UI, but exposes some REST APIs. - -- **IdentityService.Host**: Hosts the ABP Identity module which is used to manage users & roles. It has no additional service, but only hosts the Identity module's API. -- **TenantManagementService.Host**: Hosts the ABP Tenant Management module which is used to manage roles. It has no additional service, but only hosts the Tenant Management module's API. -- **BloggingService.Host**: Hosts the ABP Blogging module which is used to manage blog & posts (a typical blog application). It has no additional service, but only hosts the Blogging module's API. -- **ProductService.Host**: Hosts the Product module (that is inside the solution) which is used to manage products. It also contains the EF Core migrations to create/update the Product Management database schema. - -### Modules - -* **Product**: A layered module that is developed with the [module development best practices](../Best-Practices/Index.md). It can be embedded into a monolithic application or can be hosted as a microservice by separately deploying API and UI (as done in this demo solution). - -### Databases - -This solution is using multiple databases: - -* **MsDemo_Identity**: An SQL database. Used **SQL Server** by default, but can be any DBMS supported by the EF Core. Shared by AuthServer, IdentityService and the TenantManagementService. Also audit logs, permissions and settings are stored in this database (while they could have their own databases, shared the same database to keep it simple). -* **MsDemo_ProductManagement**: An SQL database. Again, used **SQL Server** by default, but can be any DBMS supported by the EF Core. Used by the ProductService as a dedicated database. -* **MsDemo_Blogging**: A **MongoDB** database. Used by the BloggingService. -* **Elasticsearch**: Used to write logs over Serilog. - -## Applications - -### Authentication Server (AuthServer.Host) - -This project is used by all other services and applications for authentication & single sign on. Mainly, uses **IdentityServer4** to provide these services. It uses some of the [pre-build ABP modules](../Modules/Index) like *Identity*, *Audit Logging* and *Permission Management*. - -#### Database & EF Core Configuration - -This application uses a SQL database (named it as **MsDemo_Identity**) and maintains its schema via **Entity Framework Core migrations.** - -It has a DbContext named **AuthServerDbContext** and defined as shown below: - -````csharp -public class AuthServerDbContext : AbpDbContext -{ - public AuthServerDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.ConfigureIdentity(); - modelBuilder.ConfigureIdentityServer(); - modelBuilder.ConfigureAuditLogging(); - modelBuilder.ConfigurePermissionManagement(); - modelBuilder.ConfigureSettingManagement(); - } -} -```` - -In the **OnModelCreating**, you see **ConfigureX()** method calls. A module with a database schema generally declares such an extension method to configure EF Core mappings for its own entities. This is a flexible approach where you can arrange your databases and modules inside them; You can use a different database for each module, or combine some of them in a shared database. In the AuthServer project, we decided to combine multiple module schemas in a single EF Core DbContext, in a single physical database. These modules are Identity, IdentityServer, AuditLogging, PermissionManagement and SettingManagement modules. - -Notice that this DbContext is only for database migrations. All modules have their own `DbContext` classes those are used in the runtime by the modules. - -#### User Interface - -AuthServer has a simple home page that shows the current user info if the current user has logged in: - -![microservice-sample-authserver-home](../images/microservice-sample-authserver-home.png) - -It also provides Login & Register pages: - -![microservice-sample-authserver-login](../images/microservice-sample-authserver-login.png) - -These pages are not included in the project itself. Instead, AuthServer project uses the prebuilt ABP [account module](https://github.com/abpframework/abp/tree/master/modules/account) with IdentityServer extension. That means it can also act as an OpenId Connect server with necessary UI and logic. - -#### Dependencies - -* **RabbitMQ** for messaging to other services. -* **Redis** for distributed/shared caching. -* **Elasticsearch** for storing logs. - -### Backend Admin Application (BackendAdminApp.Host) - -This is a web application that is used to manage users, roles, permissions and products in the system. - -#### Authentication - -BackendAdminApp redirects to the AuthServer for authentication. Once the user enters a correct username & password, the page is redirected to the backend application again. Authentication configuration is setup in the `BackendAdminAppHostModule` class: - -````charp -context.Services.AddAuthentication(options => -{ - options.DefaultScheme = "Cookies"; - options.DefaultChallengeScheme = "oidc"; -}) -.AddCookie("Cookies", options => -{ - options.Cookie.Expiration = TimeSpan.FromDays(365); - options.ExpireTimeSpan = TimeSpan.FromDays(365); -}) -.AddOpenIdConnect("oidc", options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ClientId = configuration["AuthServer:ClientId"]; - options.ClientSecret = configuration["AuthServer:ClientSecret"]; - options.RequireHttpsMetadata = false; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.Scope.Add("role"); - options.Scope.Add("email"); - options.Scope.Add("phone"); - options.Scope.Add("BackendAdminAppGateway"); - options.Scope.Add("IdentityService"); - options.Scope.Add("ProductService"); - options.ClaimActions.MapAbpClaimTypes(); -}); -```` - -* It adds "Cookies" authentication as the primary authentication type. -* "oidc" authentication is configured to use the AuthServer application as the authentication server. -* It requires the additional identity scopes *role*, *email* and *phone*. -* It requires the API resource scopes *BackendAdminAppGateway*, *IdentityService* and *ProductService* because it will use these services as APIs. - -IdentityServer client settings are stored inside the `appsettings.json` file: - -````json -"AuthServer": { - "Authority": "http://localhost:64999", - "ClientId": "backend-admin-app-client", - "ClientSecret": "1q2w3e*" -} -```` - -#### User Interface - -The BackendAdminApp.Host project itself has not a single UI element/page. It is only used to serve UI pages of the Identity and Product Management modules. `BackendAdminAppHostModule` adds dependencies to `AbpIdentityWebModule` (*[Volo.Abp.Identity.Web](https://www.nuget.org/packages/Volo.Abp.Identity.Web)* package) and `ProductManagementWebModule` (*ProductManagement.Web* project) for that purpose. - -A screenshot from the user management page: - -![microservice-sample-backend-ui](../images/microservice-sample-backend-ui.png) - -A screenshot from the permission management modal for a role: - -![microservice-sample-backend-ui-permissions](../images/microservice-sample-backend-ui-permissions.png) - -#### Using Microservices - -Backend admin application uses the Identity and Product microservices for all operations, over the Backend Admin Gateway (BackendAdminAppGateway.Host). - -##### Remote End Point - -`appsettings.json` file contains the `RemoteServices` section to declare the remote service endpoint(s). Each microservice will normally have different endpoints. However, this solution uses the API Gateway pattern to provide a single endpoint for the applications: - -````json -"RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:65115/" - } -} -```` - -`http://localhost:65115/` is the URL of the *BackendAdminAppGateway.Host* project. It knows where are Identity and Product services are located. - -##### HTTP Clients - -ABP application modules generally provides C# client libraries to consume services (APIs) easily (they generally uses the [Dynamic C# API Clients](../API/Dynamic-CSharp-API-Clients.md) feature of the ABP framework). That means if you need to consume Identity service API, you can reference to its client package and easily use the APIs by provided interfaces. - -For that purpose, `BackendAdminAppHostModule` class declares dependencies for `AbpIdentityHttpApiClientModule` and `ProductManagementHttpApiClientModule`. - -Once you refer these client packages, you can directly inject an application service interface (e.g. `IIdentityUserAppService`) and use its methods like a local method call. It actually invokes remote service calls over HTTP to the related service endpoint. - -##### Passing the Access Token - -Since microservices requires authentication & authorization, each remote service call should contain an Authentication header. This header is obtained from the `access_token` inside the current `HttpContext` for the current user. This is automatically done when you use the `Volo.Abp.Http.Client.IdentityModel` package. `BackendAdminAppHostModule` declares dependencies to this package and to the related `AbpHttpClientIdentityModelModule` class. It is integrated to the HTTP Clients explained above. - -#### Dependencies - -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -### Public Web Site (PublicWebSite.Host) - -This is a public web site project that has a web blog and product list page. - -#### Authentication - -PublicWebSite can show blog posts and product list without login. If you login, you can also manage blogs. It redirects to the AuthServer for authentication. Once the user enters a correct username & password, the page is redirected to the public web site application again. Authentication configuration is setup in the `PublicWebSiteHostModule` class: - -```charp -context.Services.AddAuthentication(options => -{ - options.DefaultScheme = "Cookies"; - options.DefaultChallengeScheme = "oidc"; -}) -.AddCookie("Cookies", options => -{ - options.Cookie.Expiration = TimeSpan.FromDays(365); - options.ExpireTimeSpan = TimeSpan.FromDays(365); -}) -.AddOpenIdConnect("oidc", options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ClientId = configuration["AuthServer:ClientId"]; - options.ClientSecret = configuration["AuthServer:ClientSecret"]; - options.RequireHttpsMetadata = false; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.Scope.Add("role"); - options.Scope.Add("email"); - options.Scope.Add("phone"); - options.Scope.Add("PublicWebSiteGateway"); - options.Scope.Add("ProductService"); - options.Scope.Add("BloggingService"); - options.ClaimActions.MapAbpClaimTypes(); -}); -``` - -- It adds "Cookies" authentication as the primary authentication type. -- "oidc" authentication is configured to use the AuthServer application as the authentication server. -- It requires the additional identity scopes *role*, *email* and *phone*. -- It requires the API resource scopes *PublicWebSiteGateway*, *BloggingService* and *ProductService* because it will use these services as APIs. - -IdentityServer client settings are stored inside the `appsettings.json` file: - -```json -"AuthServer": { - "Authority": "http://localhost:64999", - "ClientId": "public-website-client", - "ClientSecret": "1q2w3e*" -} -``` - -#### User Interface - -The PublicWebSite.Host project has a page to list products (`Pages/Products.cshtml`). It also uses the UI from the blogging module. `PublicWebSiteHostModule` adds dependencies to `BloggingWebModule` (*[Volo.Blogging.Web](https://www.nuget.org/packages/Volo.Blogging.Web)* package) for that purpose. - -A screenshot from the Products page: - -![microservice-sample-public-product-list](../images/microservice-sample-public-product-list.png) - -#### Using Microservices - -Public web site application uses the Blogging and Product microservices for all operations, over the Public Web Site Gateway (PublicWebSiteGateway.Host). - -##### Remote End Point - -`appsettings.json` file contains the `RemoteServices` section to declare the remote service endpoint(s). Each microservice will normally have different endpoints. However, this solution uses the API Gateway pattern to provide a single endpoint for the applications: - -```json -"RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:64897/" - } -} -``` - -`http://localhost:64897/` is the URL of the *PublicWebSiteGateway.Host* project. It knows where are Blogging and Product services are located. - -##### HTTP Clients - -`PublicWebSiteHostModule` class declares dependencies for `BloggingHttpApiClientModule` and `ProductManagementHttpApiClientModule` to be able to use remote HTTP APIs for these services. - -##### Passing the Access Token - -Just like explained in the Backend Admin Application section, Public Web Site project also uses the `AbpHttpClientIdentityModelModule` to pass `access_token` to the calling services for authentication. - -#### Dependencies - -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -### Console Client Demo - -Finally, the solution includes a very simple console application, named ConsoleClientDemo, that uses Identity and Product services by authenticating through the AuthServer. It uses the Internal Gateway (InternalGateway.Host) to perform HTTP API calls. - -#### Remote Service Configuration - -`RemoteService` configuration in the `appsettings.json` file is simple: - -````json -"RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:65129/" - } -} -```` - -`http://localhost:65129/` is the URL of the Internal Gateway. All API calls to the services are performed over this URL. - -#### Authentication (IdentityServer Client) Configuration - -`appsettings.json` also has a configuration for the IdentityServer authentication: - -````json -"IdentityClients": { - "Default": { - "GrantType": "client_credentials", - "ClientId": "console-client-demo", - "ClientSecret": "1q2w3e*", - "Authority": "http://localhost:64999", - "Scope": "InternalGateway IdentityService ProductService" - } -} -```` - -This sample uses the `client_credentials` grant type which requires a `ClientId` and `ClientSecret` for the authentication process. There are also [other grant types](http://docs.identityserver.io/en/latest/topics/grant_types.html). For example, you can use the following configuration to swith to the `password` (Resource Owner Password) grant type: - -````json -"IdentityClients": { - "Default": { - "GrantType": "password", - "ClientId": "console-client-demo", - "ClientSecret": "1q2w3e*", - "UserName": "admin", - "UserPassword": "1q2w3E*", - "Authority": "http://localhost:64999", - "Scope": "InternalGateway IdentityService ProductService" - } -} -```` - -Resource Owner Password requires a `UserName` & `UserPassword` in addition to client credentials. This grant type is useful to call remote services on behalf of a user. - -`Scope` declares the APIs (and the gateway) to grant access. This application uses the Internal Gateway. - -#### HTTP Client Dependencies - -`ConsoleClientDemoModule` has dependencies to `AbpIdentityHttpApiClientModule` and `ProductManagementHttpApiClientModule` in order to use Identity and Product APIs. It also has `AbpHttpClientIdentityModelModule` dependency to authenticate via IdentityServer. - -#### Using the Services - -Using the services is straightforward. See the `ClientDemoService` class which simply injects `IIdentityUserAppService` and `IProductAppService` and uses them. This class also shows a manual HTTP call using an `HttpClient` object. See source code of the `ClientDemoService` for details. - -## API Gateways / BFFs (Backend for Frontend) - -Gateways are used to provide a **single entry point** to the applications. In this way, an application only deal with a single service address (API endpoint) instead of a different addresses for each services. Gateways are also used for rate limiting, security, authentication, load balancing and many more requirements. - -"**Backend for Frontend**" (BFF) is a common architectural pattern which offers to build a **dedicated and specialized** gateway for each different application / client type. This solution uses this pattern and has multiple gateways. - -This solution uses the [Ocelot](https://github.com/ThreeMammals/Ocelot) library to build API Gateways. It's a widely accepted API Gateway library for ASP.NET Core. - -### Backend Admin Application Gateway (BackendAdminAppGateway.Host) - -This is backend (server side API) for the "Backend Admin Application" (don't confuse about the naming; Backend Admin Application is a frontend web application actually, but used by system admins rather than regular users). - -#### Authentication - -This gateway uses IdentityServer `Bearer` authentication and configured like that: - -````csharp -context.Services.AddAuthentication("Bearer") -.AddIdentityServerAuthentication(options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ApiName = configuration["AuthServer:ApiName"]; - options.RequireHttpsMetadata = false; - options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; - options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; - options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; - options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; - options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; - options.InboundJwtClaimTypeMap["phone_number_verified"] = - AbpClaimTypes.PhoneNumberVerified; - options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; -}); -```` - -`AddIdentityServerAuthentication` extension method comes from the [IdentityServer4.AccessTokenValidation](https://www.nuget.org/packages/IdentityServer4.AccessTokenValidation) package, part of the IdentityServer4 project (see [its documentation](http://docs.identityserver.io/en/latest/topics/apis.html)). - -`ApiName` is the API which is being protected, `BackendAdminAppGateway` in this case. So, this solution defines gateways as API resources. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: - -````json -"AuthServer": { - "Authority": "http://localhost:64999", - "ApiName": "BackendAdminAppGateway" -} -```` - -#### Ocelot Configuration - -Ocelot needs to know the real URLs of the microservices to be able to redirect HTTP requests. The configuration for this gateway is like below: - -````json -"ReRoutes": [ - { - "DownstreamPathTemplate": "/api/identity/{everything}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 63568 - } - ], - "UpstreamPathTemplate": "/api/identity/{everything}", - "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] - }, - { - "DownstreamPathTemplate": "/api/productManagement/{everything}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 60244 - } - ], - "UpstreamPathTemplate": "/api/productManagement/{everything}", - "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] - } -], -"GlobalConfiguration": { - "BaseUrl": "http://localhost:65115" -} -```` - -`ReRoutes` is an array of URL mappings. `BaseUrl` in the `GlobalConfiguration` section is the URL of this gateway (Ocelot needs to know its own URL). See [its own documentation](https://ocelot.readthedocs.io/en/latest/features/configuration.html) to better understand the configuration. - -Ocelot is a finalizer ASP.NET Core middleware and should be written as the last item in the pipeline: - -````csharp -app.UseOcelot().Wait(); -```` - -It handles and redirects requests based on the configuration above. - -#### ABP Configuration Endpoints - -ABP provides some built-in APIs to get some configuration and information from the server. Examples: - -* `/api/abp/application-configuration` returns localization texts, permission and setting values (try http://localhost:65115/api/abp/application-configuration for this gateway). -* `/Abp/ServiceProxyScript` returns dynamic javascript proxies to call services from a javascript client (try http://localhost:65115/Abp/ServiceProxyScript for this gateway). - -These endpoints should be served by the gateway service, not by microservices. A microservice can only know permissions related to that microservice. But, once properly configured, gateway can aggregate permission values for multiple services as a single list which is more suitable for clients. - -For this purpose, the ASP.NET Core pipeline was configured to handle some specific routes via MVC, instead of Ocelot. To make this possible, MapWhen extension method is used like that: - -````csharp -app.MapWhen(ctx => ctx.Request.Path.ToString().StartsWith("/api/abp/") || - ctx.Request.Path.ToString().StartsWith("/Abp/"), - app2 => - { - app2.UseConfiguredEndpoints(); - }); - -app.UseOcelot().Wait(); -```` - -This configuration uses standard MVC middleware when request path starts with `/api/abp/` or `/Abp/`. - -#### Swagger - -This gateway is configured to use the [swagger UI](https://swagger.io/tools/swagger-ui/), a popular tool to discover & test HTTP APIs. Normally, Ocelot does not support to show APIs on the swagger, because it can not know details of each microservice API. But it is possible when you follow ABP layered module architecture [best practices](../Best-Practices/Index.md). - -`BackendAdminAppGatewayHostModule` adds dependency to `AbpIdentityHttpApiModule` (*[Volo.Abp.Identity.HttpApi](https://www.nuget.org/packages/Volo.Abp.Identity.HttpApi)* package) and `ProductManagementHttpApiModule` (*ProductManagement.HttpApi* project) to include their HTTP API Controllers. In this way, swagger can discover them. While it references to the API layer, it does not reference to the implementation of application services, because they will be running in the related microservice endpoints and redirected by the Ocelot based on the request URL. - -Anyway, when you open the URL `http://localhost:65115/swagger/index.html`, you will see APIs of all configured microservices. - -#### Permission Management - -Backend Admin Application provides a permission management UI (seen before) and uses this gateway to get/set permissions. Permission management API is hosted inside the gateway, instead of a separate service. This is a design decision, but it could be hosted as another microservice if you would like. - -#### Dependencies - -- **RabbitMQ** for messaging to other services. -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -### Public Web Site Gateway (PublicWebSiteGateway.Host) - -This is backend (server side API gateway) for the "Public Web Site" application. - -#### Authentication - -This gateway uses IdentityServer `Bearer` authentication and configured like that: - -```csharp -context.Services.AddAuthentication("Bearer") -.AddIdentityServerAuthentication(options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ApiName = configuration["AuthServer:ApiName"]; - options.RequireHttpsMetadata = false; - options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; - options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; - options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; - options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; - options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; - options.InboundJwtClaimTypeMap["phone_number_verified"] = - AbpClaimTypes.PhoneNumberVerified; - options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; -}); -``` - -`AddIdentityServerAuthentication` extension method comes from the [IdentityServer4.AccessTokenValidation](https://www.nuget.org/packages/IdentityServer4.AccessTokenValidation) package, part of the IdentityServer4 project (see [its documentation](http://docs.identityserver.io/en/latest/topics/apis.html)). - -`ApiName` is the API which is being protected, `PublicWebSiteGateway` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: - -```json -"AuthServer": { - "Authority": "http://localhost:64999", - "ApiName": "PublicWebSiteGateway" -} -``` - -#### Ocelot Configuration - -Ocelot needs to know the real URLs of the microservices to be able to redirect HTTP requests. The configuration for this gateway is like below: - -```json -"ReRoutes": [ - { - "DownstreamPathTemplate": "/api/productManagement/{everything}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 60244 - } - ], - "UpstreamPathTemplate": "/api/productManagement/{everything}", - "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] - }, - { - "DownstreamPathTemplate": "/api/blogging/{everything}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 62157 - } - ], - "UpstreamPathTemplate": "/api/blogging/{everything}", - "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] - } -], -"GlobalConfiguration": { - "BaseUrl": "http://localhost:64897" -} -``` - -See [its own documentation](https://ocelot.readthedocs.io/en/latest/features/configuration.html) to better understand the Ocelot configuration. - -#### Other - -See the "ABP Configuration Endpoints" and "Swagger" topics inside the "Backend Admin Application Gateway" section which are very similar for this gateway. - -#### Dependencies - -- **RabbitMQ** for messaging to other services. -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -### Internal Gateway (InternalGateway.Host) - -This gateway is not a BFF. It is designed for inter-microservice communication and is not exposed publicly. - -#### Authentication - -This gateway uses IdentityServer `Bearer` authentication and configured like that: - -```csharp -context.Services.AddAuthentication("Bearer") -.AddIdentityServerAuthentication(options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ApiName = configuration["AuthServer:ApiName"]; - options.RequireHttpsMetadata = false; - options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; - options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; - options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; - options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; - options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; - options.InboundJwtClaimTypeMap["phone_number_verified"] = AbpClaimTypes.PhoneNumberVerified; - options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; -}); -``` - -`AddIdentityServerAuthentication` extension method comes from the [IdentityServer4.AccessTokenValidation](https://www.nuget.org/packages/IdentityServer4.AccessTokenValidation) package, part of the IdentityServer4 project (see [its documentation](http://docs.identityserver.io/en/latest/topics/apis.html)). - -`ApiName` is the API which is being protected, `InternalGateway` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: - -```json -"AuthServer": { - "Authority": "http://localhost:64999", - "ApiName": "InternalGateway" -} -``` - -#### Ocelot Configuration - -Ocelot needs to know the real URLs of the microservices to be able to redirect HTTP requests. The configuration for this gateway is like below: - -```json -"ReRoutes": [ - { - "DownstreamPathTemplate": "/api/identity/{everything}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 63568 - } - ], - "UpstreamPathTemplate": "/api/identity/{everything}", - "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] - }, - { - "DownstreamPathTemplate": "/api/productManagement/{everything}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 60244 - } - ], - "UpstreamPathTemplate": "/api/productManagement/{everything}", - "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] - }, - { - "DownstreamPathTemplate": "/api/blogging/{everything}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 62157 - } - ], - "UpstreamPathTemplate": "/api/blogging/{everything}", - "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] - } -], -"GlobalConfiguration": { - "BaseUrl": "http://localhost:65129" -} -``` - -`ReRoutes` configuration covers all microservices in the system. See [its own documentation](https://ocelot.readthedocs.io/en/latest/features/configuration.html) to better understand the Ocelot configuration. - -#### Other - -See the "ABP Configuration Endpoints" and "Swagger" topics inside the "Backend Admin Application Gateway" section which are very similar for this gateway. - -#### Dependencies - -- **RabbitMQ** for messaging to other services. -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -## Microservices - -Microservices are standalone HTTP APIs those implement the business of the system in a distributed manner. - -* They are used by applications and other microservices through the gateways and HTTP APIs. -* They can raise or register to events in the system. -* They can communicate to each other via asynchronous messaging. - -### Identity Service (IdentityService.Host) - -This service provides user and role management APIs. - -#### Database - -Shares the same database (MsDemo_Identity) with the AuthServer application. - -#### Identity Module - -This service actually just hosts the ABP Identity package/module. Does not include any API itself. In order to host it, adds the following dependencies: - -* `AbpIdentityHttpApiModule` (*[Volo.Abp.Identity.HttpApi](https://www.nuget.org/packages/Volo.Abp.Identity.HttpApi)* package) to provide Identity APIs. -* `AbpIdentityApplicationModule` (*[Volo.Abp.Identity.Application](https://www.nuget.org/packages/Volo.Abp.Identity.Application)* package) to host the implementation of the application and domain layers of the module. -* `AbpIdentityEntityFrameworkCoreModule` (*[Volo.Abp.Identity.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.Identity.EntityFrameworkCore)* package) to use EF Core as database API. - -See the [module architecture best practice guide](../Best-Practices/Module-Architecture) to understand the layering better. - -#### Authentication - -This microservice uses IdentityServer `Bearer` authentication and configured like that: - -```csharp -context.Services.AddAuthentication("Bearer") -.AddIdentityServerAuthentication(options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ApiName = configuration["AuthServer:ApiName"]; - options.RequireHttpsMetadata = false; - options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; - options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; - options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; - options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; - options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; - options.InboundJwtClaimTypeMap["phone_number_verified"] = - AbpClaimTypes.PhoneNumberVerified; - options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; -}); -``` - -`ApiName` is the API which is being protected, `IdentityService` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: - -```json -"AuthServer": { - "Authority": "http://localhost:64999", - "ApiName": "IdentityService" -} -``` - -#### Swagger - -Swagger UI is configured and is the default page for this service. If you navigate to the URL `http://localhost:63568/`, you are redirected to the swagger page to see and test the API. - -#### Dependencies - -- **RabbitMQ** for messaging to other services. -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -### Blogging Service (BloggingService.Host) - -This service hosts the blogging API. - -#### Database - -It has a dedicated MongoDB database (MsDemo_Blogging) to store blog and posts. It also uses the MsDemo_Identity SQL database for audit logs, permissions and settings. So, there are two connection strings in the `appsettings.json` file: - -````json -"ConnectionStrings": { - "Default": "Server=localhost;Database=MsDemo_Identity;Trusted_Connection=True", - "Blogging": "mongodb://localhost/MsDemo_Blogging" -} -```` - -#### Blogging Module - -This service actually just hosts the ABP Blogging package/module. Does not include any API itself. In order to host it, adds the following dependencies: - -- `BloggingHttpApiModule` (*[Volo.Blogging.HttpApi](https://www.nuget.org/packages/Volo.Blogging.HttpApi)* package) to provide Blogging APIs. -- `BloggingApplicationModule` (*[Volo.Blogging.Application](https://www.nuget.org/packages/Volo.Blogging.Application)* package) to host the implementation of the application and domain layers of the module. -- `BloggingMongoDbModule` (*[Volo.Blogging.MongoDB](https://www.nuget.org/packages/Volo.Abp.Identity.EntityFrameworkCore)* package) to use MongoDB as the database. - -See the [module architecture best practice guide](../Best-Practices/Module-Architecture) to understand the layering better. - -#### Authentication - -This microservice uses IdentityServer `Bearer` authentication and configured like that: - -```csharp -context.Services.AddAuthentication("Bearer") -.AddIdentityServerAuthentication(options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ApiName = configuration["AuthServer:ApiName"]; - options.RequireHttpsMetadata = false; - options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; - options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; - options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; - options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; - options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; - options.InboundJwtClaimTypeMap["phone_number_verified"] = - AbpClaimTypes.PhoneNumberVerified; - options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; -}); -``` - -`ApiName` is the API which is being protected, `BloggingService` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: - -```json -"AuthServer": { - "Authority": "http://localhost:64999", - "ApiName": "BloggingService" -} -``` - -#### IdentityServer Client - -This microservice also uses the Identity microservice API through the Internal Gateway, because it needs to query user details (username, email, phone, name and surname) in some cases. So, it is also a client for the IdentityServer and defines a section in the `appsettings.json` file for that: - -````json -"IdentityClients": { - "Default": { - "GrantType": "client_credentials", - "ClientId": "blogging-service-client", - "ClientSecret": "1q2w3e*", - "Authority": "http://localhost:64999", - "Scope": "InternalGateway IdentityService" - } -} -```` - -Since it uses the Internal Gateway, it should also configure the remote endpoint of the gateway: - -````json -"RemoteServices": { - "Default": { - "BaseUrl": "http://localhost:65129/", - "UseCurrentAccessToken": "false" - } -} -```` - -When you set `UseCurrentAccessToken` to `false`, ABP ignores the current `access_token` in the current `HttpContext` and authenticates to the AuthServer with the credentials defined above. - -Why not using the token of the current user in the current request? Because, the user may not have required permissions on the Identity module, so it can not just pass the current authentication token directly to the Identity service. In addition, some of the blog service APIs are anonymous (not requires authenticated user), so in some cases there is no "current user" in the HTTP request. For these reasons, Blogging service should be defined as a client for the Identity service with its own credentials and permissions. - -If you check the `AbpPermissionGrants` table in the `MsDemo_Identity` database, you can see the related permission for the `blogging-service-client`. - -![microservice-sample-blogservice-permission-in-database](../images/microservice-sample-blogservice-permission-in-database.png) - -#### Swagger - -Swagger UI is configured and is the default page for this service. If you navigate to the URL `http://localhost:62157/`, you are redirected to the swagger page to see and test the API. - -#### Dependencies - -- **RabbitMQ** for messaging to other services. -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -### Product Service (ProductService.Host) - -This service hosts the Product Management API. - -#### Database & EF Core Migrations - -It has a separated SQL database, named **MsDemo_ProductManagement**, for the product management module. It uses EF Core as the database provider and has a DbContext named `ProductServiceMigrationDbContext`: - -````csharp -public class ProductServiceMigrationDbContext : AbpDbContext -{ - public ProductServiceMigrationDbContext( - DbContextOptions options - ) : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.ConfigureProductManagement(); - } -} -```` - -Actual model configuration is done inside the `modelBuilder.ConfigureProductManagement()` extension method. This project maintains the database schema using EF Core migrations. - -Notice that this DbContext is only for database migrations. Product Management module has its own `DbContext` class that is used in the runtime (See `ProductManagementDbContext` class in the ProductManagement.EntityFrameworkCore project). - -There are two connection strings in the `appsettings.json` file: - -````json -"ConnectionStrings": { - "Default": "Server=localhost;Database=MsDemo_Identity;Trusted_Connection=True", - "ProductManagement": "Server=localhost;Database=MsDemo_ProductManagement;Trusted_Connection=True" -} -```` - -`Default` connection strings points to the MsDemo_Identity database that is used for audit logging, permission and setting stores. `ProductManagement` connection string is used by the product module. - -#### Product Module - -This service actually just hosts the Product Management module. Does not include any API itself. In order to host it, adds the following dependencies: - -- `ProductManagementHttpApiModule` to provide product management APIs. -- `ProductManagementApplicationModule` to host the implementation of the application and domain layers of the module. -- `ProductManagementEntityFrameworkCoreModule` to use EF Core as database API. - -See the [module architecture best practice guide](../Best-Practices/Module-Architecture) to understand the layering better. See the Product Management module section below for more information about this module. - -#### Authentication - -This microservice uses IdentityServer `Bearer` authentication and configured like that: - -```csharp -context.Services.AddAuthentication("Bearer") -.AddIdentityServerAuthentication(options => -{ - options.Authority = configuration["AuthServer:Authority"]; - options.ApiName = configuration["AuthServer:ApiName"]; - options.RequireHttpsMetadata = false; - options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; - options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; - options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; - options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; - options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; - options.InboundJwtClaimTypeMap["phone_number_verified"] = - AbpClaimTypes.PhoneNumberVerified; - options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; -}); -``` - -`ApiName` is the API which is being protected, `ProductService` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: - -```json -"AuthServer": { - "Authority": "http://localhost:64999", - "ApiName": "ProductService" -} -``` - -#### Swagger - -Swagger UI is configured and is the default page for this service. If you navigate to the URL `http://localhost:60244/`, you are redirected to the swagger page to see and test the API. - -#### Dependencies - -- **RabbitMQ** for messaging to other services. -- **Redis** for distributed/shared caching. -- **Elasticsearch** for storing logs. - -## Modules - -ABP provides a strong infrastructure to make modular application development easier by providing services and architecture (see the [module development best practices guide](../Best-Practices/Index.md)). - -This solution demonstrate how to use [prebuilt application modules](../Modules/Index.md) in a distributed architecture. The solution also includes a simple "Product Management" module to show the implementation of a well layered module example. - -### Product Management - -Product Management is a module that consists of several layers and packages/projects: - -![microservice-sample-product-module-in-solution](../images/microservice-sample-product-module-in-solution.png) - -* `ProductManagement.Domain.Shared` contains constants and types shared among all layers. -* `ProductManagement.Domain` contains the domain logic and defines entities, domain services, domain events, business/domain exceptions. -* `ProductManagement.Application.Contracts` contains application service interfaces and DTOs. -* `ProductManagement.Application` contains the implementation of application services. -* `ProductManagement.EntityFrameworkCore` contains DbConext and other EF Core related classes and configuration. -* `ProductManagement.HttpApi` contains API Controllers. -* `ProductManagement.HttpApi.Client` contains C# proxies to directly use the HTTP API remotely. Uses [Dynamic C# API Clients](../API/Dynamic-CSharp-API-Clients.md) feature of the ABP framework. -* `ProductManagement.Web` contains the UI elements (pages, scripts, styles... etc). - -By the help of this layering, it is possible to use the same module as a package reference in a monolithic application or use as a service that runs in another server. It is possible to separate UI (Web) and API layers, so they run in different servers. - -In this solution, Web layer runs in the Backend Admin Application while API layer is hosted by the Product microservice. - -This tutorial will highlight some important aspects of the module. But, it's suggested to see the source code for a better understanding. - -#### Domain Layer - -`Product` is the main [Aggregate Root](../Entities.md) of this module: - -````csharp -public class Product : AuditedAggregateRoot -{ - /// - /// A unique value for this product. - /// ProductManager ensures the uniqueness of it. - /// It can not be changed after creation of the product. - /// - [NotNull] - public string Code { get; private set; } - - [NotNull] - public string Name { get; private set; } - - public float Price { get; private set; } - - public int StockCount { get; private set; } - - //... -} -```` - -All of its properties have private setters which prevents any direct change of the properties from out of the class. Product class ensures its own integrity and validity by its own constructors and methods. - -It has two constructors: - -````csharp -private Product() -{ - //Default constructor is needed for ORMs. -} - -internal Product( - Guid id, - [NotNull] string code, - [NotNull] string name, - float price = 0.0f, - int stockCount = 0) -{ - Check.NotNullOrWhiteSpace(code, nameof(code)); - - if (code.Length >= ProductConsts.MaxCodeLength) - { - throw new ArgumentException( - $"Product code can not be longer than {ProductConsts.MaxCodeLength}" - ); - } - - Id = id; - Code = code; - SetName(Check.NotNullOrWhiteSpace(name, nameof(name))); - SetPrice(price); - SetStockCountInternal(stockCount, triggerEvent: false); -} - -```` - -Default (**parameterless**) constructor is private and is not used in the application code. It is needed because most ORMs requires a parameterless constructor on deserializing entities while getting from the database. - -Second constructor is **internal** that means it can only be used inside the domain layer. This enforces to use the `ProductManager` while creating a new `Product`. Because, `ProductManager` should implement a business rule on a new product creation. This constructor only requires the minimal required arguments to create a new product with some optional arguments. It checks some simple business rules to ensure that the entity is created as a valid product. - -Rest of the class has methods to manipulate properties of the entity. Example: - -````csharp -public Product SetPrice(float price) -{ - if (price < 0.0f) - { - throw new ArgumentException($"{nameof(price)} can not be less than 0.0!"); - } - - Price = price; - return this; -} - -```` - -`SetPrice` method is used to change the price of the product in a safe manner (by checking a validation rule). - -`SetStockCount` is another method that is used to change stock count of a product: - -````csharp -public Product SetStockCount(int stockCount) -{ - return SetStockCountInternal(stockCount); -} - -private Product SetStockCountInternal(int stockCount, bool triggerEvent = true) -{ - if (StockCount < 0) - { - throw new ArgumentException($"{nameof(stockCount)} can not be less than 0!"); - } - - if (StockCount == stockCount) - { - return this; - } - - if (triggerEvent) - { - AddDistributedEvent( - new ProductStockCountChangedEto( - Id, StockCount, stockCount - ) - ); - } - - StockCount = stockCount; - return this; -} - -```` - -This method also triggers a **distributed event** with the `ProductStockCountChangedEto` parameter (Eto is a conventional postfix stands for **E**vent **T**ransfer **O**bject, but not required) to notify listeners that stock count of a product has changed. Any subscriber can receive this event and perform an action based on that knowledge. - -Events are distributed by RabbitMQ for this solution. But ABP is message broker independent by providing necessary abstractions (see the [Event Bus](../Event-Bus.md) document). - -As said before, this module forces to always use the `ProductManager` to create a new `Product`. `ProductManager` is a simple domain service defined as shown: - -````csharp -public class ProductManager : DomainService -{ - private readonly IRepository _productRepository; - - public ProductManager(IRepository productRepository) - { - _productRepository = productRepository; - } - - public async Task CreateAsync( - [NotNull] string code, - [NotNull] string name, - float price = 0.0f, - int stockCount = 0) - { - var existingProduct = - await _productRepository.FirstOrDefaultAsync(p => p.Code == code); - - if (existingProduct != null) - { - throw new ProductCodeAlreadyExistsException(code); - } - - return await _productRepository.InsertAsync( - new Product( - GuidGenerator.Create(), - code, - name, - price, - stockCount - ) - ); - } -} -```` - -* It checks if given code is used before. Throws `ProductCodeAlreadyExistsException` so. -* If uses the `GuidGenerator` (`IGuidGenerator`) service to create a new `Guid`. -* It inserts the entity to the repository. - -So, with this design, uniqueness of the product code is guaranteed. - -`ProductCodeAlreadyExistsException` is a domain/business exception defined as like below: - -````csharp -public class ProductCodeAlreadyExistsException : BusinessException -{ - public ProductCodeAlreadyExistsException(string productCode) - : base("PM:000001", $"A product with code {productCode} has already exists!") - { - - } -} -```` - -`PM:000001` is a code for the exception type that is sent to the clients, so they can understand the error type. Not implemented for this case, but it is also possible to localize business exceptions. See the [exception handling documentation](../Exception-Handling.md). - -#### Application Layer - -Application layer of this module has two services: - -* `ProductAppService` is mainly used by the Backend Admin Application to manage (create, update, delete...) products. It requires permission to perform any operation. -* `PublicProductAppService` is used by the Public Web Site to show list of products to the visitors. It does not require any permission since most of the visitors are not logged in to the application. - -Notice that; instead of putting two application service into the same project, it might be a better principle to have separated application layers per application. But we unified them for simplicity in this solution. - -As an example, `ProductAppService` has the following method to update a product: - -````csharp -[Authorize(ProductManagementPermissions.Products.Update)] -public async Task UpdateAsync(Guid id, UpdateProductDto input) -{ - var product = await _productRepository.GetAsync(id); - - product.SetName(input.Name); - product.SetPrice(input.Price); - product.SetStockCount(input.StockCount); - - return ObjectMapper.Map(product); -} -```` - -* It defines the required permission (*ProductManagementPermissions.Products.Update* is a constant with value `ProductManagement.Update`) to perform this operation. -* Gets the id of the product and a DTO contains the values to update. -* Gets the related product entity from the repository. -* Uses the related methods (like `SetName`) of the `Product` class to change properties, because they are with private setters and the only way to change a value is to use an entity method. -* Returns an updated `ProductDto` to the client (client may need it for some reason) by using the [ObjectMapper](../Object-To-Object-Mapping.md). - -The implementation may vary based on the requirements. This implementation follows the [best practices offered here](../Best-Practices/Application-Services.md). - -#### Other Layers - -See other layers from the source code. - -## Infrastructure - -### Messaging and RabbitMQ - -Asynchronous Messaging is a key concept in distributed systems. It makes possible to communicate as a loosely coupled manner with fault tolerance. It does not require both sides to be online at the moment of messaging. So, it is a widely used communication pattern in microservice architecture. - -#### Distributed Event Bus - -Distributed Events (Event Bus) is a way of messaging where a service raise/trigger events while other services registers/listens to these events to be notified when an important event occurs. ABP makes distributed events easier to use by providing conventions, services and integrations. - -You have seen that the `Product` class publishing an event using the following code line: - -````csharp -AddDistributedEvent(new ProductStockCountChangedEto(Id, StockCount, stockCount)); -```` - -`ProductStockCountChangedEto` was defined as shown below: - -````csharp -[Serializable] -public class ProductStockCountChangedEto : EtoBase -{ - public Guid Id { get; } - - public int OldCount { get; set; } - - public int CurrentCount { get; set; } - - private ProductStockCountChangedEto() - { - //Default constructor is needed for deserialization. - } - - public ProductStockCountChangedEto(Guid id, int oldCount, int currentCount) - { - Id = id; - OldCount = oldCount; - CurrentCount = currentCount; - } -} -```` - -This object stores necessary information about the event. Another service can easily register to this event by implementing the `IDistributedEventHandler` interface with the generic `ProductStockCountChangedEto` parameter: - -````csharp -public class MyHandler : IDistributedEventHandler -{ - public async Task HandleEventAsync(ProductStockCountChangedEto eventData) - { - var productId = eventData.Id; - //... - } -} -```` - -All the integration and communication are done by the ABP framework when you use the [Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) package. If you need to publish events out of an entity, just inject the `IDistributedEventBus` and use the `PublishAsync` method. - -See the [Event Bus](../Event-Bus.md) documentation for more information about the distributed event system. - -#### RabbitMQ Configuration - -In this solution, [RabbitMQ](https://www.rabbitmq.com/) is used for messaging & distributed events. - -[Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) package is required to integrate to the RabbitMQ for distributed event system. Then you need to add dependency to the `AbpEventBusRabbitMqModule` for your module. For example, `ProductServiceHostModule` declares this dependency. - -`AbpEventBusRabbitMqModule` gets configuration from the `appsettings.json` by default. For example, the Product Service has such a configuration: - -````json -"RabbitMQ": { - "Connections": { - "Default": { - "HostName": "localhost" - } - }, - "EventBus": { - "ClientName": "MsDemo_ProductService", - "ExchangeName": "MsDemo" - } -} -```` - -### Caching and Redis - -A distributed system obviously needs to a distributed and shared cache, instead of isolated in-memory caches for each service. - -[Redis](https://redis.io/) is used as a distributed cache in this solution. The solution uses Microsoft's standard [Microsoft.Extensions.Caching.Redis](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Redis) package for integration. All applications and services uses Redis cache when you use and configure this package. See [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more. - -The solution also uses the [Microsoft.AspNetCore.DataProtection.StackExchangeRedis](https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection.StackExchangeRedis) package to share data protection keys between applications and services over Redis cache. - -### Logging, Serilog, Elasticsearch and Kibana - -This solution uses [Serilog](https://serilog.net/) as a logging library. It is a widely used library which has many data source integrations including [Elasticsearch](https://www.elastic.co/products/elasticsearch). - -Logging configurations are done in `Program.cs` files using a code block similar to the given below: - -````csharp -Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .Enrich.WithProperty("Application", "ProductService") - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .WriteTo.Elasticsearch( - new ElasticsearchSinkOptions(new Uri(configuration["ElasticSearch:Url"])) - { - AutoRegisterTemplate = true, - AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6, - IndexFormat = "msdemo-log-{0:yyyy.MM}" - }) - .CreateLogger(); -```` - -This configures multiple log target: File and Elasticsearch. `Application` property is set to `ProductService` for this example. This is a way of distinguishing the logs of multiple services in a single database. You can then query logs by the `Application` name. - -Elasticsearch URL is read from the `appsettings.json` configuration file: - -````json -"ElasticSearch": { - "Url": "http://localhost:9200" -} -```` - -If you use Kibana, which is a Visualization tool that is well integrated to Elasticsearch, you can see some fancy UI about your logs: - -![microservice-sample-kibana-2](../images/microservice-sample-kibana-2.png) - -*Figure - A dashboard that shows log and error counts by service/application.* - -![microservice-sample-kibana-1](../images/microservice-sample-kibana-1.png) - -*Figure - A list of log entries* - -Kibana URL is `http://localhost:5601/` by default. - -### Audit Logging - -ABP provides automatic audit logging which saves every request in detail (who is the current user, what is the browser/client, what actions performed, which entities changed, even which properties of entities has been updated). See the [audit logging document](../Audit-Logging.md) for details. - -All of the services and applications are configured to write audit logs. Audit logs are saved to the MsDemo_Identity SQL database. So, you can query all audit logs of all applications from a single point. - -An Audit Log record has a `CorrelationId` property that can be used to track a request. When a service calls another service in a single web request, they both save audit logs with the same `CorrelationId`. See the `AbpAuditLogs` table in the database. - -### Multi-Tenancy - -The solution has been configured to provide a [multi-tenant](../Multi-Tenancy.md) system, where each tenant can have their isolated users, roles, permissions and other data. diff --git a/docs/en/Samples/eShopOnAbp/Index.md b/docs/en/Samples/eShopOnAbp/Index.md deleted file mode 100644 index 068d39f92b..0000000000 --- a/docs/en/Samples/eShopOnAbp/Index.md +++ /dev/null @@ -1,3 +0,0 @@ -# eShopOnAbp - -This document is in progress. Please see the source code for now: https://github.com/abpframework/eShopOnAbp diff --git a/docs/en/Settings.md b/docs/en/Settings.md deleted file mode 100644 index 61c55f99b4..0000000000 --- a/docs/en/Settings.md +++ /dev/null @@ -1,222 +0,0 @@ -# Settings - -[Configuration system](Configuration.md) is a good way to configure the application on startup. In addition to the configurations, ABP provides another way to set and get some application settings. - -A setting is a name-value pair stored in a dynamic data source, generally in a database. Setting system is extensible and there are pre-built providers for a user, a tenant, global and default. - -## Defining Settings - -A setting must be defined before its use. ABP was designed to be [modular](Module-Development-Basics.md), so different modules can have different settings. A module must create a class derived from the `SettingDefinitionProvider` in order to define its settings. An example setting definition provider is shown below: - -````csharp -public class EmailSettingProvider : SettingDefinitionProvider -{ - public override void Define(ISettingDefinitionContext context) - { - context.Add( - new SettingDefinition("Smtp.Host", "127.0.0.1"), - new SettingDefinition("Smtp.Port", "25"), - new SettingDefinition("Smtp.UserName"), - new SettingDefinition("Smtp.Password", isEncrypted: true), - new SettingDefinition("Smtp.EnableSsl", "false") - ); - } -} -```` - -ABP automatically discovers this class and registers the setting definitions. - -### SettingDefinition - -`SettingDefinition` class has the following properties: - -* **Name**: Unique name of the setting in the application. This is **the only mandatory property**. Used to get/set the value of this setting in the application code (It's a good idea to define a const string for a setting name instead of using a magic string). -* **DefaultValue**: A setting may have a default value. -* **DisplayName**: A localizable string that can be used to show the setting name on the UI. -* **Description**: A localizable string that can be used to show the setting description on the UI. -* **IsVisibleToClients**: A boolean value indicates that whether this setting value is available in the client side or not. Default value is false to prevent accidently publishing an internal critical setting value. -* **IsInherited**: A boolean value indicates that whether this setting value is inherited from other providers or not. Default value is true and fallbacks to the next provider if the setting value was not set for the requested provider (see the setting value providers section for more). -* **IsEncrypted**: A boolean value indicates that whether this setting value should be encrypted on save and decrypted on read. It makes possible to secure the setting value in the database. -* **Providers**: Can be used to restrict providers available for a particular setting (see the setting value providers section for more). -* **Properties**: A name/value collection to set custom properties about this setting those can be used later in the application code. - -### Change Setting Definitions of a Depended Module - -In some cases, you may want to change some properties of a settings defined in some other module that your application/module depends on. A setting definition provider can query and update setting definitions. - -The following example gets a setting defined by the [Volo.Abp.Emailing](Emailing.md) package and changes its properties: - -````csharp -public class MySettingDefinitionProvider : SettingDefinitionProvider -{ - public override void Define(ISettingDefinitionContext context) - { - var smtpHost = context.GetOrNull("Abp.Mailing.Smtp.Host"); - if (smtpHost != null) - { - smtpHost.DefaultValue = "mail.mydomain.com"; - smtpHost.DisplayName = - new LocalizableString( - typeof(MyLocalizationResource), - "SmtpServer_DisplayName" - ); - } - } -} -```` - -> Using constants for the setting names is a good practice and ABP packages do it. `Abp.Mailing.Smtp.Host` setting name is a constant defined by the `EmailSettingNames` class (in the `Volo.Abp.Emailing` namespace). - -## Reading the Setting Values - -### ISettingProvider - -`ISettingProvider` is used to get the value of a setting or get the values of all the settings. Example usages: - -````csharp -public class MyService -{ - private readonly ISettingProvider _settingProvider; - - //Inject ISettingProvider in the constructor - public MyService(ISettingProvider settingProvider) - { - _settingProvider = settingProvider; - } - - public async Task FooAsync() - { - //Get a value as string. - string userName = await _settingProvider.GetOrNullAsync("Smtp.UserName"); - - //Get a bool value and fallback to the default value (false) if not set. - bool enableSsl = await _settingProvider.GetAsync("Smtp.EnableSsl"); - - //Get a bool value and fallback to the provided default value (true) if not set. - bool enableSsl = await _settingProvider.GetAsync( - "Smtp.EnableSsl", defaultValue: true); - - //Get a bool value with the IsTrueAsync shortcut extension method - bool enableSsl = await _settingProvider.IsTrueAsync("Smtp.EnableSsl"); - - //Get an int value or the default value (0) if not set - int port = (await _settingProvider.GetAsync("Smtp.Port")); - - //Get an int value or null if not provided - int? port = (await _settingProvider.GetOrNullAsync("Smtp.Port"))?.To(); - } -} -```` - -> `ISettingProvider` is a very common service and some base classes (like `IApplicationService`) already property-inject it. You can directly use the `SettingProvider` property in such cases. - -### Reading Setting Values on the Client Side - -If a setting is allowed to be visible on the client side, current value of the setting can also be read from the client code. See the following documents to understand how to get the setting values in different UI types; - -* [MVC / Razor Pages](UI/AspNetCore/JavaScript-API/Settings.md) -* [Angular](UI/Angular/Settings.md) -* [Blazor](UI/Blazor/Settings.md) - -## Setting Value Providers - -Setting system is extensible, you can extend it by defining setting value providers to get setting values from any source and based on any condition. - -`ISettingProvider` uses the setting value providers to obtain a setting value. It fallbacks to the next value provider if a value provider can not get the setting value. - -There are 5 pre-built setting value providers registered by the order below: - -* `DefaultValueSettingValueProvider`: Gets the value from the default value of the setting definition, if set (see the SettingDefinition section above). -* `ConfigurationSettingValueProvider`: Gets the value from the [IConfiguration service](Configuration.md). -* `GlobalSettingValueProvider`: Gets the global (system-wide) value for a setting, if set. -* `TenantSettingValueProvider`: Gets the setting value for the current tenant, if set (see the [multi-tenancy](Multi-Tenancy.md) document). -* `UserSettingValueProvider`: Gets the setting value for the current user, if set (see the [current user](CurrentUser.md) document). - -> Setting fallback system works from bottom (user) to top (default). - -Global, Tenant and User setting value providers use the `ISettingStore` to read the value from the data source (see the section below). - -### Setting Values in the Application Configuration - -As mentioned in the previous section, `ConfigurationSettingValueProvider` reads the settings from the `IConfiguration` service, which can read values from the `appsettings.json` by default. So, the easiest way to configure setting values to define them in the `appsettings.json` file. - -For example, you can configure [IEmailSender](Emailing.md) settings as shown below: - -````json -{ - "Settings": { - "Abp.Mailing.DefaultFromAddress": "noreply@mydomain.com", - "Abp.Mailing.DefaultFromDisplayName": "My Application", - "Abp.Mailing.Smtp.Host": "mail.mydomain.com", - "Abp.Mailing.Smtp.Port": "547", - "Abp.Mailing.Smtp.UserName": "myusername", - "Abp.Mailing.Smtp.Password": "mySecretPassW00rd", - "Abp.Mailing.Smtp.EnableSsl": "True" - } -} -```` - -Setting values should be configured under the `Settings` section as like in this example. - -> `IConfiguration` is an .NET Core service and it can read values not only from the `appsettings.json`, but also from the environment, user secrets... etc. See [Microsoft's documentation]( https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/ ) for more. - -### Custom Setting Value Providers - -If you need to extend the setting system, you can define a class derived from the `SettingValueProvider` class. Example: - -````csharp -public class CustomSettingValueProvider : SettingValueProvider -{ - public override string Name => "Custom"; - - public CustomSettingValueProvider(ISettingStore settingStore) - : base(settingStore) - { - } - - public override Task GetOrNullAsync(SettingDefinition setting) - { - /* Return the setting value or null - Use the SettingStore or another data source */ - } -} -```` - -> Alternatively, you can implement the `ISettingValueProvider` interface. Remember to register it to the [dependency injection](Dependency-Injection.md) in this case. - -Every provider should have a unique Name (which is "Custom" here). Built-in providers use the given names: - -* `DefaultValueSettingValueProvider`: "**D**". -* `ConfigurationSettingValueProvider`: "**C**". -* `GlobalSettingValueProvider`: "**G**". -* `TenantSettingValueProvider`: "**T**". -* `UserSettingValueProvider`: "**U**". - -One-letter names were preferred to reduce the data size in the database (provider name is repeated in each row). - -Once you define a custom setting value provider, you need to explicitly register it to the `AbpSettingOptions`: - -````csharp -Configure(options => -{ - options.ValueProviders.Add(); -}); -```` - -This example adds it as the last item, so it will be the first value provider used by the `ISettingProvider`. You could add it to another index in the `options.ValueProviders` list. - -### ISettingStore - -While a setting value provider is free to use any source to get the setting value, the `ISettingStore` service is the default source of the setting values. Global, Tenant and User setting value providers use it. - -## ISettingEncryptionService - -`ISettingEncryptionService` is used to encrypt/decrypt setting values when `IsEncrypted` property of a setting definition was set to `true`. - -You can replace this service in the dependency injection system to customize the encryption/decryption process. Default implementation uses the `StringEncryptionService` which is implemented with the AES algorithm by default (see string [encryption document](String-Encryption.md) for more). - -## Setting Management Module - -The core setting system is pretty independent and doesn't make any assumption about how you manage (change) the setting values. Even the default `ISettingStore` implementation is the `NullSettingStore` which returns null for all setting values. - -The setting management module completes it (and implements `ISettingStore`) by managing setting values in a database. See the [Setting Management Module document](Modules/Setting-Management.md) for more. diff --git a/docs/en/SignalR-Integration.md b/docs/en/SignalR-Integration.md deleted file mode 100644 index f441c64ae1..0000000000 --- a/docs/en/SignalR-Integration.md +++ /dev/null @@ -1,240 +0,0 @@ -# SignalR Integration - -> It is already possible to follow [the standard Microsoft tutorial](https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr) to add [SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) to your application. However, ABP provides SignalR integration packages those simplify the integration and usage. - -## Installation - -### Server Side - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -#### Using the ABP CLI - -Open a command line window in the folder of your project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.AspNetCore.SignalR -``` - -> You typically want to add this package to the web or API layer of your application, depending on your architecture. - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.SignalR). - -#### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.AspNetCore.SignalR](https://www.nuget.org/packages/Volo.Abp.AspNetCore.SignalR) NuGet package to your project: - - ``` - Install-Package Volo.Abp.AspNetCore.SignalR - ``` - - Or use the Visual Studio NuGet package management UI to install it. - -2. Add the `AbpAspNetCoreSignalRModule` to the dependency list of your module: - -```csharp -[DependsOn( - //...other dependencies - typeof(AbpAspNetCoreSignalRModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -``` - -> You don't need to use the `services.AddSignalR()` and the `app.UseEndpoints(...)`, it's done by the `AbpAspNetCoreSignalRModule`. - -### Client Side - -Client side installation depends on your UI framework / client type. - -#### ASP.NET Core MVC / Razor Pages UI - -Run the following command in the root folder of your web project: - -```bash -yarn add @abp/signalr -``` - -> This requires to [install yarn](https://yarnpkg.com/) if you haven't install before. - -This will add the `@abp/signalr` to the dependencies in the `package.json` of your project: - -```json -{ - ... - "dependencies": { - ... - "@abp/signalr": "~2.7.0" - } -} -``` - -Run the following [ABP CLI](CLI.md) command in the root folder of your web project: - -```bash -abp install-libs -``` - -This will copy the SignalR JavaScript files into your project: - -![signal-js-file](images/signal-js-file.png) - -Finally, add the following code to your page/view to include the `signalr.js` file - -```xml -@section scripts { - -} -``` - -It requires to add `@using Volo.Abp.AspNetCore.Mvc.UI.Packages.SignalR` to your page/view. - -> You could add the `signalr.js` file in a standard way. But using the `SignalRBrowserScriptContributor` has additional benefits. See the [Client Side Package Management](UI/AspNetCore/Client-Side-Package-Management.md) and [Bundling & Minification](UI/AspNetCore/Bundling-Minification.md) documents for details. - -That's all. you can use the [SignalR JavaScript API](https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client) in your page. - -#### Other UI Frameworks / Clients - -Please refer to [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) for other type of clients. - -## The ABP Framework Integration - -This section covers the additional benefits when you use the ABP Framework integration packages. - -### Hub Route & Mapping - -ABP automatically registers all the hubs to the [dependency injection](Dependency-Injection.md) (as transient) and maps the hub endpoint. So, you don't have to use the ` app.UseEndpoints(...)` to map your hubs. Hub route (URL) is determined conventionally based on your hub name. - -Example: - -```csharp -public class MessagingHub : Hub -{ - //... -} -``` - -The hub route will be `/signalr-hubs/messaging` for the `MessagingHub`: - -- Adding a standard `/signalr-hubs/` prefix -- Continue with the **kebab-case** hub name, without the `Hub` suffix. - -If you want to specify the route, you can use the `HubRoute` attribute: - -```csharp -[HubRoute("/my-messaging-hub")] -public class MessagingHub : Hub -{ - //... -} -``` - -### AbpHub Base Classes - -Instead of the standard `Hub` and `Hub` classes, you can inherit from the `AbpHub` or `AbpHub` which have useful base properties like `CurrentUser`. - -Example: - -```csharp -public class MessagingHub : AbpHub -{ - public async Task SendMessage(string targetUserName, string message) - { - var currentUserName = CurrentUser.UserName; //Access to the current user info - var txt = L["MyText"]; //Localization - } -} -``` - -> While you could inject the same properties into your hub constructor, this way simplifies your hub class. - -### Manual Registration / Mapping - -ABP automatically registers all the hubs to the [dependency injection](Dependency-Injection.md) as a **transient service**. If you want to **disable auto dependency injection** registration for your hub class, just add a `DisableConventionalRegistration` attribute. You can still register your hub class to dependency injection in the `ConfigureServices` method of your module if you like: - -```csharp -context.Services.AddTransient(); -``` - -When **you or ABP** register the class to the dependency injection, it is automatically mapped to the endpoint route configuration just as described in the previous sections. You can use `DisableAutoHubMap` attribute if you want to manually map your hub class. - -For manual mapping, you have two options: - -1. Use the `AbpSignalROptions` to add your map configuration (in the `ConfigureServices` method of your [module](Module-Development-Basics.md)), so ABP still performs the endpoint mapping for your hub: - -```csharp -Configure(options => -{ - options.Hubs.Add( - new HubConfig( - typeof(MessagingHub), //Hub type - "/my-messaging/route", //Hub route (URL) - hubOptions => - { - //Additional options - hubOptions.LongPolling.PollTimeout = TimeSpan.FromSeconds(30); - } - ) - ); -}); -``` - -This is a good way to provide additional SignalR options. - -If you don't want to disable auto hub map, but still want to perform additional SignalR configuration, use the `options.Hubs.AddOrUpdate(...)` method: - -```csharp -Configure(options => -{ - options.Hubs.AddOrUpdate( - typeof(MessagingHub), //Hub type - config => //Additional configuration - { - config.RoutePattern = "/my-messaging-hub"; //override the default route - config.ConfigureActions.Add(hubOptions => - { - //Additional options - hubOptions.LongPolling.PollTimeout = TimeSpan.FromSeconds(30); - }); - } - ); -}); -``` - -This is the way you can modify the options of a hub class defined in a depended module (where you don't have the source code access). - -2. Change `app.UseConfiguredEndpoints` in the `OnApplicationInitialization` method of your [module](Module-Development-Basics.md) as shown below (added a lambda method as the parameter). - -```csharp -app.UseConfiguredEndpoints(endpoints => -{ - endpoints.MapHub("/my-messaging-hub", options => - { - options.LongPolling.PollTimeout = TimeSpan.FromSeconds(30); - }); -}); -``` - -### UserIdProvider - -ABP implements SignalR's `IUserIdProvider` interface to provide the current user id from the `ICurrentUser` service of the ABP framework (see [the current user service](CurrentUser.md)), so it will be integrated to the authentication system of your application. The implementing class is the `AbpSignalRUserIdProvider`, if you want to change/override it. - -## Example Application - -See the [SignalR Integration Demo](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) as a sample application. It has a simple Chat page to send messages between (authenticated) users. - -![signalr-demo-chat](images/signalr-demo-chat.png) - -## Remarks - -ABP Framework doesn't change the SignalR. It works in your ABP Framework based application just like any other ASP.NET Core application. - -Refer to the Microsoft's documentation to [host and scale](https://docs.microsoft.com/en-us/aspnet/core/signalr/scale) your application, integrate to [Azure](https://docs.microsoft.com/en-us/aspnet/core/signalr/publish-to-azure-web-app) or [Redis backplane](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane)... etc. - -## See Also - -- [Microsoft SignalR documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) -- [Real-Time Messaging In A Distributed Architecture Using ABP, SignalR & RabbitMQ](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ) diff --git a/docs/en/SimpleStateChecker.md b/docs/en/SimpleStateChecker.md deleted file mode 100644 index 584fef1592..0000000000 --- a/docs/en/SimpleStateChecker.md +++ /dev/null @@ -1,125 +0,0 @@ -# Simple State Checker - -The simple state checking system can be used to enable/disable an object based on some dynamic conditions. For example, you can disable a menu item on the user interface, if the current user has not granted for a given permission. The simple state checking system provides a generic way to define and check such conditions. - -## Definition state checker. - -Any class can inherit `IHasSimpleStateCheckers` to support state checks. - -````csharp -public class MyObject : IHasSimpleStateCheckers -{ - public int Id { get; set; } - - public List> SimpleStateCheckers { get; } - - public MyObject() - { - SimpleStateCheckers = new List>(); - } -} -```` - -The `MyObject` class contains a collection of state checkers, you can add your custom checkers to it. - -````csharp -public class MyObjectStateChecker : ISimpleStateChecker -{ - public Task IsEnabledAsync(SimpleStateCheckerContext context) - { - var currentUser = context.ServiceProvider.GetRequiredService(); - return Task.FromResult(currentUser.IsInRole("Admin")); - } -} -```` - -````csharp -var myobj = new MyObject() -{ - Id = 100 -}; - -myobj.SimpleStateCheckers.Add(new MyObjectStateChecker()); -```` - -## Definition Global State Checkers - -`AbpSimpleStateCheckerOptions` is the options class that used to set the global state checkers for specific object. - -Example: Add global state for `MyObject`: - -````csharp -services.Configure>(options => -{ - options.GlobalSimpleStateCheckers.Add(); - //options.GlobalSimpleStateCheckers.Add<>(); //Add more global state checkers -}); -```` - -> Write this inside the `ConfigureServices` method of your module. - -## Check the state - -You can inject `ISimpleStateCheckerManager` service to check state. - -````csharp -bool enabled = await stateCheckerManager.IsEnabledAsync(myobj); -```` - -### Batch check the states - -If there are many instance items that require state checking, there may be performance problems. - -In this case, you can implement `ISimpleBatchStateChecker`. It can check multiple items at once. -You need to make sure that the same `ISimpleBatchStateChecker` instance is added to the `SimpleStateCheckers` of multiple instances. - -> `SimpleBatchStateCheckerBase` inherits the `ISimpleBatchStateChecker` interface and implements the `IsEnabledAsync` method of a single object by default. - -````csharp -public class MyObjectBatchStateChecker : SimpleBatchStateCheckerBase -{ - public override Task> IsEnabledAsync(SimpleBatchStateCheckerContext context) - { - var result = new SimpleStateCheckerResult(context.States); - - foreach (var myObject in context.States) - { - if (myObject.Id > 100) - { - result[myObject] = true; - } - } - - return Task.FromResult(result); - } -} -```` - -````csharp -var myobj1 = new MyObject() -{ - Id = 100 -}; -var myobj2 = new MyObject() -{ - Id = 99 -}; - -var myObjectBatchStateChecker = new MyObjectBatchStateChecker(); - -myobj1.SimpleStateCheckers.Add(myObjectBatchStateChecker); -myobj2.SimpleStateCheckers.Add(myObjectBatchStateChecker); - -SimpleStateCheckerResult stateCheckerResult = await stateCheckerManager.IsEnabledAsync(new []{ myobj1, myobj2 }); -```` - -## Built-in State Checkers - -The `PermissionDefinition`, `ApplicationMenuItem` and `ToolbarItem` objects have implemented state checks and have built-in general state checkers, you can directly use their extension methods. - -````csharp -RequireAuthenticated(); -RequirePermissions(bool requiresAll, params string[] permissions); -RequireFeatures(bool requiresAll, params string[] features); -RequireGlobalFeatures(bool requiresAll, params Type[] globalFeatures); -```` diff --git a/docs/en/Specifications.md b/docs/en/Specifications.md deleted file mode 100644 index 2ef9c4496e..0000000000 --- a/docs/en/Specifications.md +++ /dev/null @@ -1,257 +0,0 @@ -# Specifications - -Specification Pattern is used to define **named, reusable, combinable and testable filters** for entities and other business objects. - -> A Specification is a part of the Domain Layer. - -## Installation - -> This package is **already installed** when you use the startup templates. So, most of the times you don't need to manually install it. - -Install the [Volo.Abp.Specifications](https://abp.io/package-detail/Volo.Abp.Specifications) package to your project. You can use the [ABP CLI](CLI.md) *add-package* command in a command line terminal when the current folder is the root folder of your project (`.csproj`): - -````bash -abp add-package Volo.Abp.Specifications -```` - -## Defining the Specifications - -Assume that you've a Customer entity as defined below: - -````csharp -using System; -using Volo.Abp.Domain.Entities; - -namespace MyProject -{ - public class Customer : AggregateRoot - { - public string Name { get; set; } - - public byte Age { get; set; } - - public long Balance { get; set; } - - public string Location { get; set; } - } -} -```` - -You can create a new Specification class derived from the `Specification`. - -**Example: A specification to select the customers with 18+ age:** - -````csharp -using System; -using System.Linq.Expressions; -using Volo.Abp.Specifications; - -namespace MyProject -{ - public class Age18PlusCustomerSpecification : Specification - { - public override Expression> ToExpression() - { - return c => c.Age >= 18; - } - } -} -```` - -You simply define a lambda [Expression](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions) to define a specification. - -> Instead, you can directly implement the `ISpecification` interface, but the `Specification` base class much simplifies it. - -## Using the Specifications - -There are two common use cases of the specifications. - -### IsSatisfiedBy - -`IsSatisfiedBy` method can be used to check if a single object satisfies the specification. - -**Example: Throw exception if the customer doesn't satisfy the age specification** - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace MyProject -{ - public class CustomerService : ITransientDependency - { - public async Task BuyAlcohol(Customer customer) - { - if (!new Age18PlusCustomerSpecification().IsSatisfiedBy(customer)) - { - throw new Exception( - "This customer doesn't satisfy the Age specification!" - ); - } - - //TODO... - } - } -} -```` - -### ToExpression & Repositories - -`ToExpression()` method can be used to use the specification as Expression. In this way, you can use a specification to **filter entities while querying from the database**. - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Domain.Services; - -namespace MyProject -{ - public class CustomerManager : DomainService, ITransientDependency - { - private readonly IRepository _customerRepository; - - public CustomerManager(IRepository customerRepository) - { - _customerRepository = customerRepository; - } - - public async Task> GetCustomersCanBuyAlcohol() - { - var queryable = await _customerRepository.GetQueryableAsync(); - var query = queryable.Where( - new Age18PlusCustomerSpecification().ToExpression() - ); - - return await AsyncExecuter.ToListAsync(query); - } - } -} -```` - -> Specifications are correctly translated to SQL/Database queries and executed efficiently in the DBMS side. While it is not related to the Specifications, see the [Repositories](Repositories.md) document if you want to know more about the `AsyncExecuter`. - -Actually, using the `ToExpression()` method is not necessary since the specifications are automatically casted to Expressions. This would also work: - -````csharp -var queryable = await _customerRepository.GetQueryableAsync(); -var query = queryable.Where( - new Age18PlusCustomerSpecification() -); -```` - -## Composing the Specifications - -One powerful feature of the specifications is that they are composable with `And`, `Or`, `Not` and `AndNot` extension methods. - -Assume that you have another specification as defined below: - -```csharp -using System; -using System.Linq.Expressions; -using Volo.Abp.Specifications; - -namespace MyProject -{ - public class PremiumCustomerSpecification : Specification - { - public override Expression> ToExpression() - { - return (customer) => (customer.Balance >= 100000); - } - } -} -``` - -You can combine the `PremiumCustomerSpecification` with the `Age18PlusCustomerSpecification` to query the count of premium adult customers as shown below: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Domain.Services; -using Volo.Abp.Specifications; - -namespace MyProject -{ - public class CustomerManager : DomainService, ITransientDependency - { - private readonly IRepository _customerRepository; - - public CustomerManager(IRepository customerRepository) - { - _customerRepository = customerRepository; - } - - public async Task GetAdultPremiumCustomerCountAsync() - { - return await _customerRepository.CountAsync( - new Age18PlusCustomerSpecification() - .And(new PremiumCustomerSpecification()).ToExpression() - ); - } - } -} -```` - -If you want to make this combination another reusable specification, you can create such a combination specification class deriving from the `AndSpecification`: - -````csharp -using Volo.Abp.Specifications; - -namespace MyProject -{ - public class AdultPremiumCustomerSpecification : AndSpecification - { - public AdultPremiumCustomerSpecification() - : base(new Age18PlusCustomerSpecification(), - new PremiumCustomerSpecification()) - { - } - } -} -```` - -Now, you can re-write the `GetAdultPremiumCustomerCountAsync` method as shown below: - -````csharp -public async Task GetAdultPremiumCustomerCountAsync() -{ - return await _customerRepository.CountAsync( - new AdultPremiumCustomerSpecification() - ); -} -```` - -> You see the power of the specifications with these samples. If you change the `PremiumCustomerSpecification` later, say change the balance from `100.000` to `200.000`, all the queries and combined specifications will be effected by the change. This is a good way to reduce code duplication! - -## Discussions - -While the specification pattern is older than C# lambda expressions, it's generally compared to expressions. Some developers may think it's not needed anymore and we can directly pass expressions to a repository or to a domain service as shown below: - -````csharp -var count = await _customerRepository.CountAsync(c => c.Balance > 100000 && c.Age => 18); -```` - -Since ABP's [Repository](Repositories.md) supports Expressions, this is a completely valid use. You don't have to define or use any specification in your application and you can go with expressions. - -So, what's the point of a specification? Why and when should we consider to use them? - -### When To Use? - -Some benefits of using specifications: - -- **Reusable**: Imagine that you need the Premium Customer filter in many places in your code base. If you go with expressions and do not create a specification, what happens if you later change the "Premium Customer" definition? Say you want to change the minimum balance from $100,000 to $250,000 and add another condition to be a customer older than 3 years. If you'd used a specification, you just change a single class. If you repeated (copy/pasted) the same expression everywhere, you need to change all of them. -- **Composable**: You can combine multiple specifications to create new specifications. This is another type of reusability. -- **Named**: `PremiumCustomerSpecification` better explains the intent rather than a complex expression. So, if you have an expression that is meaningful in your business, consider using specifications. -- **Testable**: A specification is a separately (and easily) testable object. - -### When To Not Use? - -- **Non business expressions**: Do not use specifications for non business-related expressions and operations. -- **Reporting**: If you are just creating a report, do not create specifications, but directly use `IQueryable` & LINQ expressions. You can even use plain SQL, views or another tool for reporting. DDD does not necessarily care about reporting, so the way you query the underlying data store can be important from a performance perspective. \ No newline at end of file diff --git a/docs/en/Startup-Templates/Application-Single-Layer.md b/docs/en/Startup-Templates/Application-Single-Layer.md deleted file mode 100644 index 1b2e28be64..0000000000 --- a/docs/en/Startup-Templates/Application-Single-Layer.md +++ /dev/null @@ -1,110 +0,0 @@ -# Application (Single Layer) Startup Template - -## Introduction - -This template provides a simple solution structure with a single project. This document explains that solution structure in details. - -### The Difference Between the Application Startup Templates - -ABP's [Application Startup Template](Application.md) provides a well-organized and layered solution to create maintainable business applications based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. However, some developers find this template a little bit complex for simple and short-term applications. The single-layer application template has been created to provide a simpler development model for such applications. This template has the same functionality, features and modules on runtime with the [Application Startup Template](Application.md) but the development model is minimal and everything is in a single project (`.csproj`). - -## How to Start with It? - -You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can generate a CLI command for this startup template from the [Get Started](https://abp.io/get-started) page. In this section, we will use the ABP CLI. - -Firstly, install the ABP CLI if you haven't installed it before: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -Then, use the `abp new` command in an empty folder to create a new solution: - -```bash -abp new Acme.BookStore -t app-nolayers -``` - -* `Acme.BookStore` is the solution name, like *YourCompany.YourProduct*. You can use single-level, two-level or three-level naming. -* In this example, the `-t` (or `--template`) option specifies the template name. - -### Specify the UI Framework - -This template provides multiple UI frameworks: - -* `mvc`: ASP.NET Core MVC UI with Razor Pages (default) -* `blazor`: Blazor UI -* `blazor-server`: Blazor Server UI -* `angular`: Angular UI -* `none`: Without UI (for HTTP API development) - -Use the `-u` (or `--ui`) option to specify the UI framework while creating the solution: - -```bash -abp new Acme.BookStore -t app-nolayers -u angular -``` - -This example specifies the UI type (the `-u` option) as `angular`. You can also specify `mvc`, `blazor`, `blazor-server` or `none` for the UI type. - -### Specify the Database Provider - -This template supports the following database providers: - -- `ef`: Entity Framework Core (default) -- `mongodb`: MongoDB - -Use the `-d` (or `--database-provider`) option to specify the database provider while creating the solution: - -```bash -abp new Acme.BookStore -t app-nolayers -d mongodb -``` - -## Solution Structure - -If you don't specify any additional options while creating an `app-nolayers` template, you will have a solution as shown below: - -![](../images/bookstore-single-layer-solution-structure.png) - -In the next sections, we will explain the structure based on this example. Your startup solution can be slightly different based on your preferences. - -### Folder Structure - -Since this template provides a single-project solution, we've separated concerns into folders instead of projects. You can see the pre-defined folders as shown below: - -![](../images/single-layer-folder-structure.png) - -* Define your database mappings (for [EF Core](../Entity-Framework-Core.md) or [MongoDB](../MongoDB.md)) and [repositories](../Repositories.md) in the `Data` folder. -* Define your [entities](../Entities.md) in the `Entities` folder. -* Define your UI localization keys/values in the `Localization` folder. -* Define your UI menu items in the `Menus` folder. -* Define your [object-to-object mapping](../Object-To-Object-Mapping.md) classes in the `ObjectMapping` folder. -* Define your UI pages (Razor Pages) in the `Pages` folder (create `Controllers` and `Views` folder yourself if you prefer the MVC pattern). -* Define your [application services](../Application-Services.md) in the `Services` folder. - -### How to Run? - -Before running the application, you need to create the database and seed the initial data. To do that, you can run the following command in the directory of your project (in the same folder of the `.csproj` file): - -```bash -dotnet run --migrate-database -``` - -This command will create the database and seed the initial data for you. Then you can run the application with any IDE that supports .NET or by running the `dotnet run` command in the directory of your project. The default username is `admin` and the password is `1q2w3E*`. - -> While creating a database & applying migrations seem only necessary for relational databases, you should run this command even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. - -### The Angular UI - -If you choose `Angular` as the UI framework, the solution will be separated into two folders: - -* An `angular` folder that contains the Angular UI application, the client-side code. -* An `aspnet-core` folder that contains the ASP.NET Core solution (a single project), the server-side code. - -The server-side is similar to the solution described in the *Solution Structure* section above. This project serves the API, so the Angular application can consume it. - -The client-side application consumes the HTTP APIs as mentioned. You can see the folder structure of the Angular project shown below: - -![](../images/single-layer-angular-folder-structure.png) - - ## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/app-template) \ No newline at end of file diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md deleted file mode 100644 index fb5a1f1c07..0000000000 --- a/docs/en/Startup-Templates/Application.md +++ /dev/null @@ -1,464 +0,0 @@ -# Application Startup Template - -## Introduction - -This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. - -This document explains **the solution structure** and projects in details. If you want to start quickly, follow the guides below: - -* [The getting started document](../Getting-Started.md) explains how to create a new application in a few minutes. -* [The application development tutorial](../Tutorials/Part-1) explains step by step application development. - -## How to Start With? - -You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can generate a CLI command from the [Get Started](https://abp.io/get-started) page. CLI approach is used here. - -First, install the ABP CLI if you haven't installed it before: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Then use the `abp new` command in an empty folder to create a new solution: - -````bash -abp new Acme.BookStore -t app -```` - -* `Acme.BookStore` is the solution name, like *YourCompany.YourProduct*. You can use single-level, two-level or three-level naming. -* This example specified the template name (`-t` or `--template` option). However, `app` is already the default template if you didn't specify it. - -### Specify the UI Framework - -This template provides multiple UI frameworks: - -* `mvc`: ASP.NET Core MVC UI with Razor Pages (default) -* `blazor`: Blazor UI -* `blazor-server`: Blazor Server UI -* `angular`: Angular UI - -Use the `-u` or `--ui` option to specify the UI framework: - -````bash -abp new Acme.BookStore -u angular -```` - -### Specify the Database Provider - -This template supports the following database providers: - -- `ef`: Entity Framework Core (default) -- `mongodb`: MongoDB - -Use `-d` (or `--database-provider`) option to specify the database provider: - -````bash -abp new Acme.BookStore -d mongodb -```` - -### Specify the Mobile Application Framework - -This template supports the following mobile application frameworks: - -- `react-native`: React Native - -Use the `-m` (or `--mobile`) option to specify the mobile application framework: - -````bash -abp new Acme.BookStore -m react-native -```` - -If not specified, no mobile application will be created. - -## Solution Structure - -Based on the options you've specified, you will get a slightly different solution structure. - -### Default Structure - -If you don't specify any additional options, you will have a solution as shown below: - -![bookstore-rider-solution-v6](../images/solution-structure-solution-explorer-rider.png) - -Projects are organized in `src` and `test` folders. `src` folder contains the actual application which is layered based on [DDD](../Domain-Driven-Design.md) principles as mentioned before. - -The diagram below shows the layers & project dependencies of the application: - -![layered-project-dependencies](../images/layered-project-dependencies.png) - -Each section below will explain the related project & its dependencies. - -#### .Domain.Shared Project - -This project contains constants, enums and other objects these are actually a part of the domain layer, but needed to be used by all layers/projects in the solution. - -A `BookType` enum and a `BookConsts` class (which may have some constant fields for the `Book` entity, like `MaxNameLength`) are good candidates for this project. - -* This project has no dependency on other projects in the solution. All other projects depend on this one directly or indirectly. - -#### .Domain Project - -This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../Entities.md), [domain services](../Domain-Services.md), [value objects](../Value-Objects.md), [repository interfaces](../Repositories.md) and other domain objects. - -A `Book` entity, a `BookManager` domain service and an `IBookRepository` interface are good candidates for this project. - -* Depends on the `.Domain.Shared` because it uses constants, enums and other objects defined in that project. - -#### .Application.Contracts Project - -This project mainly contains [application service](../Application-Services.md) **interfaces** and [Data Transfer Objects](../Data-Transfer-Objects.md) (DTO) of the application layer. It exists to separate the interface & implementation of the application layer. In this way, the interface project can be shared to the clients as a contract package. - -An `IBookAppService` interface and a `BookCreationDto` class are good candidates for this project. - -* Depends on the `.Domain.Shared` because it may use constants, enums and other shared objects of this project in the application service interfaces and DTOs. - -#### .Application Project - -This project contains the [application service](../Application-Services.md) **implementations** of the interfaces defined in the `.Application.Contracts` project. - -A `BookAppService` class is a good candidate for this project. - -* Depends on the `.Application.Contracts` project to be able to implement the interfaces and use the DTOs. -* Depends on the `.Domain` project to be able to use domain objects (entities, repository interfaces... etc.) to perform the application logic. - -#### .EntityFrameworkCore Project - -This is the integration project for the EF Core. It defines the `DbContext` and implements repository interfaces defined in the `.Domain` project. - -* Depends on the `.Domain` project to be able to reference to entities and repository interfaces. - -> This project is available only if you are using EF Core as the database provider. If you select another database provider, its name will be different. - -#### .DbMigrator Project - -This is a console application that simplifies the execution of database migrations on development and production environments. When you run this application, it: - -* Creates the database if necessary. -* Applies the pending database migrations. -* Seeds initial data if needed. - -> This project has its own `appsettings.json` file. So, if you want to change the database connection string, remember to change this file too. - -Especially, seeding initial data is important at this point. ABP has a modular data seed infrastructure. See [its documentation](../Data-Seeding.md) for more about the data seeding. - -While creating database & applying migrations seem only necessary for relational databases, this project comes even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. - -* Depends on the `.EntityFrameworkCore` project (for EF Core) since it needs to access to the migrations. -* Depends on the `.Application.Contracts` project to be able to access permission definitions, because the initial data seeder grants all permissions to the admin role by default. - -#### .HttpApi Project - -This project is used to define your API Controllers. - -Most of the time you don't need to manually define API Controllers since ABP's [Auto API Controllers](../API/Auto-API-Controllers.md) feature creates them automagically based on your application layer. However, in case of you need to write API controllers, this is the best place to do it. - -* Depends on the `.Application.Contracts` project to be able to inject the application service interfaces. - -#### .HttpApi.Client Project - -This is a project that defines C# client proxies to use the HTTP APIs of the solution. You can share this library to 3rd-party clients, so they can easily consume your HTTP APIs in their Dotnet applications (For other types of applications, they can still use your APIs, either manually or using a tool in their own platform) - -Most of the time you don't need to manually create C# client proxies, thanks to ABP's [Dynamic C# API Clients](../API/Dynamic-CSharp-API-Clients.md) feature. - -`.HttpApi.Client.ConsoleTestApp` project is a console application created to demonstrate the usage of the client proxies. - -* Depends on the `.Application.Contracts` project to be able to share the same application service interfaces and DTOs with the remote service. - -> You can delete this project & dependencies if you don't need to create C# client proxies for your APIs. - -#### .Web Project - -This project contains the User Interface (UI) of the application if you are using ASP.NET Core MVC UI. It contains Razor pages, JavaScript files, CSS files, images and so on... - -This project contains the main `appsettings.json` file that contains the connection string and other configurations of the application. - -* Depends on the `.HttpApi` project since the UI layer needs to use APIs and the application service interfaces of the solution. - -> If you check the source code of the `.Web.csproj` file, you will see the references to the `.Application` and the `.EntityFrameworkCore` projects. -> -> These references are actually not needed while coding your UI layer, because the UI layer normally doesn't depend on the EF Core or the Application layer's implementation. These startup templates are ready for tiered deployment, where the API layer is hosted on a separate server than the UI layer. -> -> However, if you don't choose the `--tiered` option, these references will be in the .Web project to be able to host the Web, API and application layers in a single application endpoint. -> -> This gives you the ability to use domain entities & repositories in your presentation layer. However, this is considered as a bad practice according to DDD. - -#### Test Projects - -The solution has multiple test projects, one for each layer: - -* `.Domain.Tests` is used to test the domain layer. -* `.Application.Tests` is used to test the application layer. -* `.EntityFrameworkCore.Tests` is used to test EF Core configuration and custom repositories. -* `.Web.Tests` is used to test the UI (if you are using ASP.NET Core MVC UI). -* `.TestBase` is a base (shared) project for all tests. - -In addition, `.HttpApi.Client.ConsoleTestApp` is a console application (not an automated test project) which demonstrate the usage of HTTP APIs from a .NET application. - -Test projects are prepared for integration testing; - -* It is fully integrated into the ABP framework and all services in your application. -* It uses SQLite in-memory database for EF Core. For MongoDB, it uses the [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library. -* Authorization is disabled, so any application service can be easily used in tests. - -You can still create unit tests for your classes which will be harder to write (because you will need to prepare mock/fake objects), but faster to run (because it only tests a single class and skips all the initialization processes). - -#### How to Run? - -Set `.Web` as the startup project and run the application. The default username is `admin` and the password is `1q2w3E*`. - -See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) for more information. - -### Tiered Structure - -If you have selected the ASP.NET Core UI and specified the `--tiered` option, the solution created will be a tiered solution. The purpose of the tiered structure is to be able to **deploy Web applications and HTTP API to different servers**: - -![bookstore-visual-studio-solution-v3](../images/tiered-solution-servers.png) - -* Browser runs your UI by executing HTML, CSS & JavaScript. -* Web servers host static UI files (CSS, JavaScript, image... etc.) & dynamic components (e.g. Razor pages). It performs HTTP requests to the API server to execute the business logic of the application. -* The API Server hosts the HTTP APIs which then use the application & domain layers of the application to perform the business logic. -* Finally, database server hosts your database. - -So, the resulting solution allows a 4-tiered deployment, by comparing to 3-tiered deployment of the default structure explained before. - -> Unless you actually need such a 4-tiered deployment, it's suggested to go with the default structure which is simpler to develop, deploy and maintain. - -The solution structure is shown below: - -![bookstore-rider-solution-v6](../images/bookstore-rider-solution-tiered.png) - -As different from the default structure, two new projects come into play: `.AuthServer` & `.HttpApi.Host`. - -#### .AuthServer Project - -This project is used as an authentication server for other projects. `.Web` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the AuthServer. Then uses the access token to call the HTTP API server. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. - -![tiered-solution-applications](../images/tiered-solution-applications-authserver.png) - -ABP uses the [OpenIddict Module](../Modules/OpenIddict.md) that uses the open-source [OpenIddict-core](https://github.com/openiddict/openiddict-core) library for the authentication between applications. See [OpenIddict documentation](https://documentation.openiddict.com/) for details about the OpenIddict and OpenID Connect protocol. - -It has its own `appsettings.json` that contains database connection and other configurations. - -#### .HttpApi.Host Project - -This project is an application that hosts the API of the solution. It has its own `appsettings.json` that contains database connection and other configurations. - -#### .Web Project - -Just like the default structure, this project contains the User Interface (UI) of the application. It contains razor pages, JavaScript files, style files, images and so on... - -This project contains an `appsettings.json` file, but this time it does not have a connection string because it never connects to the database. Instead, it mainly contains the endpoint of the remote API server and the authentication server. - -#### Pre-requirements - -* [Redis](https://redis.io/): The applications use Redis as a distributed cache. So, you need to have Redis installed & running. - -#### How to Run? - -You should run the application with the given order: - -* First, run the `.AuthServer` since other applications depend on it. -* Then run the `.HttpApi.Host` since it is used by the `.Web` application. -* Finally, you can run the `.Web` project and login to the application (using `admin` as the username and `1q2w3E*` as the password). - -### Blazor UI -If you choose `Blazor` as the UI Framework (using the `-u blazor` or `-u blazor-server` option), the solution will have a project named `.Blazor`. This project contains the Blazor UI application. According to your choice, it will be a Blazor WebAssembly or Blazor Server application. If Blazor WebAssembly is selected, the solution will also have a `.HttpApi.Host`. This project is an ASP.NET Core application that hosts the backend application for the Blazor single page application. - -#### .Blazor Project (Server) -The Blazor Server project is similar to the ASP.NET Core MVC project. It replaces `.Web` project with `.Blazor` in the solution structure above. It has the same folder structure and the same application flow. Since it's an ASP.NET Core application, it can contain **.cshtml** files and **.razor** components at the same time. If routing matches a razor component, the Blazor UI will be used. Otherwise, the request will be handled by the MVC framework. - -![abp solution structure blazor server](../images/layered-project-dependencies-blazor-server.png) - -#### .Blazor Project (WebAssembly) -The Blazor WebAssembly project is a single page application that runs on the browser. You'll see it as `.Blazor` project in the solution. It uses the `.HttpApi.Host` project to communicate with the backend. It can't be used without the backend application. It contains only **.razor** components. It's a pure client-side application. It doesn't have any server-side code. Everything in this layer will be for the client side. - -![abp solution structure blazor wasm](../images/layered-project-dependencies-blazor-wasm.png) - -### Angular UI - -If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into two folders: - -* `angular` folder contains the Angular UI application, the client-side code. -* `aspnet-core` folder contains the ASP.NET Core solution, the server-side code. - -The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the `Angular` application consumes it. - -Angular application folder structure looks like below: - -![angular-folder-structure](../images/angular-folder-structure.png) - - -Each of ABP Commercial modules is an NPM package. Some ABP modules are added as a dependency in `package.json`. These modules install with their dependencies. To see all ABP packages, you can run the following command in the `angular` folder: - -```bash -yarn list --pattern abp -``` - -Angular application module structure: - -![Angular template structure diagram](../images/angular-template-structure-diagram.png) - -#### AppModule - -`AppModule` is the root module of the application. Some of the ABP modules and some essential modules are imported to `AppModule`. - -ABP Config modules have also been imported to `AppModule` for initial requirements of the lazy-loadable ABP modules. - -#### AppRoutingModule - -There are lazy-loadable ABP modules in the `AppRoutingModule` as routes. - -> Paths of ABP Modules should not be changed. - -You should add `routes` property in the `data` object to add a link on the menu to redirect to your custom pages. - -```js -{ - path: 'dashboard', - loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule), - canActivate: [authGuard, permissionGuard], - data: { - routes: { - name: 'ProjectName::Menu:Dashboard', - order: 2, - iconClass: 'fa fa-dashboard', - requiredPolicy: 'ProjectName.Dashboard.Host' - } as ABP.Route - } -} -``` -In the above example; -* If the user is not logged in, authGuard blocks access and redirects to the login page. -* permissionGuard checks the user's permission with the `requiredPolicy` property of the `routes` object. If the user is not authorized to access the page, the 403 page appears. -* The `name` property of `routes` is the menu link label. A localization key can be defined. -* The `iconClass` property of the `routes` object is the menu link icon class. -* The `requiredPolicy` property of the `routes` object is the required policy key to access the page. - -After the above `routes` definition, if the user is authorized, the dashboard link will appear on the menu. - -#### Shared Module - -The modules that may be required for all modules have been imported to the `SharedModule`. You should import `SharedModule` to all modules. - -See the [Sharing Modules](https://angular.io/guide/sharing-ngmodules) document. - -#### Environments - -The files under the `src/environments` folder have the essential configuration of the application. - -#### Home Module - -Home module is an example lazy-loadable module that loads on the root address of the application. - -#### Styles - -The required style files are added to the `styles` array in `angular.json`. `AppComponent` loads some style files lazily via `LazyLoadService` after the main bundle is loaded to shorten the first rendering time. - -#### Testing - -You should create your tests in the same folder as the file you want to test. - -See the [testing document](https://angular.io/guide/testing). - -#### Depended Packages - -* [NG Bootstrap](https://ng-bootstrap.github.io/) is used as UI component library. -* [NGXS](https://www.ngxs.io/) is used as state management library. -* [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) is used to support for OAuth 2 and OpenId Connect (OIDC). -* [Chart.js](https://www.chartjs.org/) is used to create widgets. -* [ngx-validate](https://github.com/ng-turkey/ngx-validate) is used for dynamic validation of reactive forms. - -### React Native - -If the `-m react-native` option is specified in the new project command, the solution includes the [React Native](https://reactnative.dev/) application in the `react-native` folder. - -The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the React Native application consumes it. - -The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features. - -React Native application folder structure as like below: - -![react-native-folder-structure](../images/react-native-folder-structure.png) - -* `App.js` is the bootstrap component of the application. -* `Environment.js` file has the essential configuration of the application. `prod` and `dev` configurations are defined in this file. -* [Contexts](https://reactjs.org/docs/context.html) are created in the `src/contexts` folder. -* [Higher order components](https://reactjs.org/docs/higher-order-components.html) are created in the `src/hocs` folder. -* [Custom hooks](https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook) are created in `src/hooks`. -* [Axios interceptors](https://github.com/axios/axios#interceptors) are created in the `src/interceptors` folder. -* Utility functions are exported from `src/utils` folder. - -#### Components - -Components that can be used on all screens are created in the `src/components` folder. All components have been created as a function that is able to use [hooks](https://reactjs.org/docs/hooks-intro.html). - -#### Screens - -![react-native-navigation-structure](../images/react-native-navigation-structure.png) - -Screens are created by creating folders that separate their names in the `src/screens` folder. Certain parts of some screens can be split into components. - -Each screen is used in a navigator in the `src/navigators` folder. - -#### Navigation - -[React Navigation](https://reactnavigation.org/) is used as a navigation library. Navigators are created in the `src/navigators`. A [drawer](https://reactnavigation.org/docs/drawer-based-navigation/) navigator and several [stack](https://reactnavigation.org/docs/hello-react-navigation/#installing-the-stack-navigator-library) navigators have been created in this folder. See the [above diagram](#screens) for the navigation structure. - -#### State Management - -[Redux](https://redux.js.org/) is used as a state management library. [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development. - -Actions, reducers, sagas and selectors are created in the `src/store` folder. Store folder is as below: - -![react-native-store-folder](../images/react-native-store-folder.png) - -* [**Store**](https://redux.js.org/basics/store) is defined in the `src/store/index.js` file. -* [**Actions**](https://redux.js.org/basics/actions/) are payloads of information that send data from your application to your store. -* [**Reducers**](https://redux.js.org/basics/reducers) specify how the application's state changes in response to actions sent to the store. -* [**Redux-Saga**](https://redux-saga.js.org/) is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage. Sagas are created in the `src/store/sagas` folder. -* [**Reselect**](https://github.com/reduxjs/reselect) library is used to create memoized selectors. Selectors are created in the `src/store/selectors` folder. - -#### APIs - -[Axios](https://github.com/axios/axios) is used as an HTTP client library. An Axios instance has exported from `src/api/API.js` file to make HTTP calls with the same config. `src/api` folder also has the API files that have been created for API calls. - -#### Theming - -[Native Base](https://nativebase.io/) is used as UI components library. Native Base components can customize easily. See the [Native Base customize](https://docs.nativebase.io/customizing-components) documentation. We followed the same way. - -* Native Base theme variables are in the `src/theme/variables` folder. -* Native Base component styles are in the `src/theme/components` folder. These files have been generated with Native Base's `ejectTheme` script. -* Styles of components override with the files under the `src/theme/overrides` folder. - -#### Testing - -Unit tests will be created. - -See the [Testing Overview](https://reactjs.org/docs/testing.html) document. - -#### Depended Libraries - -* [Native Base](https://nativebase.io/) is used as UI components library. -* [React Navigation](https://reactnavigation.org/) is used as navigation library. -* [Axios](https://github.com/axios/axios) is used as an HTTP client library. -* [Redux](https://redux.js.org/) is used as state management library. -* [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development. -* [Redux-Saga](https://redux-saga.js.org/) is used to manage asynchronous processes. -* [Redux Persist](https://github.com/rt2zz/redux-persist) is used as state persistence. -* [Reselect](https://github.com/reduxjs/reselect) is used to create memoized selectors. -* [i18n-js](https://github.com/fnando/i18n-js) is used as i18n library. -* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) library allows loading fonts easily. -* [Formik](https://github.com/jaredpalmer/formik) is used to build forms. -* [Yup](https://github.com/jquense/yup) is used for form validations. - -## Social / External Logins - -If you want to configure social/external logins for your application, please follow the [Social/External Logins](../Authentication/Social-External-Logins.md) document. - -## What's Next? - -- [The getting started document](../Getting-Started.md) explains how to create a new application in a few minutes. -- [The application development tutorial](../Tutorials/Part-1.md) explains step by step application development. - -## See Also -* [Video tutorial](https://abp.io/video-courses/essentials/app-template) diff --git a/docs/en/Startup-Templates/Console.md b/docs/en/Startup-Templates/Console.md deleted file mode 100644 index b002eb3e5e..0000000000 --- a/docs/en/Startup-Templates/Console.md +++ /dev/null @@ -1,27 +0,0 @@ -# Console Application Startup Template - -This template is used to create a minimalist console application project. - -## How to Start With? - -First, install the [ABP CLI](../CLI.md) if you haven't installed before: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Then use the `abp new` command in an empty folder to create a new solution: - -````bash -abp new Acme.MyConsoleApp -t console -```` - -`Acme.MyConsoleApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. - -## Solution Structure - -After you use the above command to create a solution, you will have a solution like shown below: - -![basic-console-application-solution](../images/basic-console-application-solution.png) - -* `HelloWorldService` is a sample service that implements the `ITransientDependency` interface to register this service to the [dependency injection](../Dependency-Injection.md) system. \ No newline at end of file diff --git a/docs/en/Startup-Templates/Index.md b/docs/en/Startup-Templates/Index.md deleted file mode 100644 index e30f2729e2..0000000000 --- a/docs/en/Startup-Templates/Index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Startup Templates - -While you can start with an empty project and add needed packages manually, startup templates make easy and comfortable to start a new solution with the ABP framework. Click the name from the list below to see the documentation of the related startup template: - -* [**`app`**](Application.md): Application template. -* [**`app-nolayers`**](Application-Single-Layer.md): Application (single layer) template. -* [**`module`**](Module.md): Module/service template. -* [**`console`**](Console.md): Console template. -* [**`WPF`**](WPF.md): WPF template. -* [**`MAUI`**](MAUI.md): MAUI template. diff --git a/docs/en/Startup-Templates/MAUI.md b/docs/en/Startup-Templates/MAUI.md deleted file mode 100644 index ab171ddef1..0000000000 --- a/docs/en/Startup-Templates/MAUI.md +++ /dev/null @@ -1,27 +0,0 @@ -# MAUI Application Startup Template - -This template is used to create a minimalist MAUI application project. - -## How to Start With? - -First, install the [ABP CLI](../CLI.md) if you haven't installed before: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Then use the `abp new` command in an empty folder to create a new solution: - -````bash -abp new Acme.MyMauiApp -t maui -```` - -`Acme.MyMauiApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. - -## Solution Structure - -After you use the above command to create a solution, you will have a solution like shown below: - -![basic-maui-application-solution](../images/basic-maui-application-solution.png) - -* `HelloWorldService` is a sample service that implements the `ITransientDependency` interface to register this service to the [dependency injection](../Dependency-Injection.md) system. \ No newline at end of file diff --git a/docs/en/Startup-Templates/Module.md b/docs/en/Startup-Templates/Module.md deleted file mode 100644 index aa33ac64df..0000000000 --- a/docs/en/Startup-Templates/Module.md +++ /dev/null @@ -1,250 +0,0 @@ -# Module Startup Template - -This template can be used to create a **reusable [application module](../Modules/Index.md)** based on the [module development best practices & conventions](../Best-Practices/Index.md). It is also suitable for creating **microservices** (with or without UI). - -## How to Start With? - -You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can generate a CLI command from the [Get Started](https://abp.io/get-started) page. CLI approach is used here. - -First, install the ABP CLI if you haven't installed before: - -```bash -dotnet tool install -g Volo.Abp.Cli -``` - -Then use the `abp new` command in an empty folder to create a new solution: - -```bash -abp new Acme.IssueManagement -t module -``` - -- `Acme.IssueManagement` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. - -### Without User Interface - -The template comes with MVC, Blazor & Angular user interfaces by default. You can use `--no-ui` option to not include any of these UI layers. - -````bash -abp new Acme.IssueManagement -t module --no-ui -```` - -## Solution Structure - -Based on the options you've specified, you will get a slightly different solution structure. If you don't specify any option, you will have a solution like shown below: - -![issuemanagement-module-solution](../images/issuemanagement-module-solution.png) - -Projects are organized as `src`, `test` and `host` folders: - -* `src` folder contains the actual module which is layered based on [DDD](../Domain-Driven-Design.md) principles. -* `test` folder contains unit & integration tests. -* `host` folder contains applications with different configurations to demonstrate how to host the module in an application. These are not a part of the module, but useful on development. - -The diagram below shows the layers & project dependencies of the module: - -![layered-project-dependencies-module](../images/layered-project-dependencies-module.png) - -Each section below will explain the related project & its dependencies. - -### .Domain.Shared Project - -This project contains constants, enums and other objects these are actually a part of the domain layer, but needed to be used by all layers/projects in the solution. - -An `IssueType` enum and an `IssueConsts` class (which may have some constant fields for the `Issue` entity, like `MaxTitleLength`) are good candidates for this project. - -- This project has no dependency to other projects in the solution. All other projects depend on this directly or indirectly. - -### .Domain Project - -This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../Entities.md), [domain services](../Domain-Services.md), value types, [repository interfaces](../Repositories.md) and other domain objects. - -An `Issue` entity, an `IssueManager` domain service and an `IIssueRepository` interface are good candidates for this project. - -- Depends on the `.Domain.Shared` because it uses constants, enums and other objects defined in that project. - -### .Application.Contracts Project - -This project mainly contains [application service](../Application-Services.md) **interfaces** and [Data Transfer Objects](../Data-Transfer-Objects.md) (DTO) of the application layer. It does exists to separate interface & implementation of the application layer. In this way, the interface project can be shared to the clients as a contract package. - -An `IIssueAppService` interface and an `IssueCreationDto` class are good candidates for this project. - -- Depends on the `.Domain.Shared` because it may use constants, enums and other shared objects of this project in the application service interfaces and DTOs. - -### .Application Project - -This project contains the [application service](../Application-Services.md) **implementations** of the interfaces defined in the `.Application.Contracts` project. - -An `IssueAppService` class is a good candidate for this project. - -- Depends on the `.Application.Contracts` project to be able to implement the interfaces and use the DTOs. -- Depends on the `.Domain` project to be able to use domain objects (entities, repository interfaces... etc.) to perform the application logic. - -### .EntityFrameworkCore Project - -This is the integration project for EF Core. It defines the `DbContext` and implements repository interfaces defined in the `.Domain` project. - -- Depends on the `.Domain` project to be able to reference to entities and repository interfaces. - -> You can delete this project if you don't want to support EF Core for your module. - -### .MongoDB Project - -This is the integration project for MongoDB. - -- Depends on the `.Domain` project to be able to reference to entities and repository interfaces. - -> You can delete this project if you don't want to support MongoDB for your module. - -### Test Projects - -The solution has multiple test projects, one for each layer: - -- `.Domain.Tests` is used to test the domain layer. -- `.Application.Tests` is used to test the application layer. -- `.EntityFrameworkCore.Tests` is used to test EF Core configuration and custom repositories. -- `.MongoDB.Tests` is used to test MongoDB configuration and custom repositories. -- `.TestBase` is a base (shared) project for all tests. - -In addition, `.HttpApi.Client.ConsoleTestApp` is a console application (not an automated test project) which demonstrate the usage of HTTP APIs from a Dotnet application. - -Test projects are prepared for integration testing; - -- It is fully integrated to ABP framework and all services in your application. -- It uses SQLite in-memory database for EF Core. For MongoDB, it uses the [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library. -- Authorization is disabled, so any application service can be easily used in tests. - -You can still create unit tests for your classes which will be harder to write (because you will need to prepare mock/fake objects), but faster to run (because it only tests a single class and skips all initialization process). - -> Domain & Application tests are using EF Core. If you remove EF Core integration or you want to use MongoDB for testing these layers, you should manually change project references & module dependencies. - -### Host Projects - -The solution has a few host applications to run your module. Host applications are used to run your module in a fully configured application. It is useful on development. Host applications includes some other modules in addition to the module being developed: - -Host applications support two types of scenarios. - -#### Single (Unified) Application Scenario - -If your module has a UI, then `.Web.Unified` application is used to host the UI and API on a single point. It has its own `appsettings.json` file (that includes the database connection string) and EF Core database migrations. - -For the `.Web.Unified` application, there is a single database, named `YourProjectName_Unified` (like *IssueManagement_Unified* for this sample). - -> If you've selected the `--no-ui` option, this project will not be in your solution. - -##### How to Run? - -Set `host/YourProjectName.Web.Unified` as the startup project, run `Update-Database` command for the EF Core from Package Manager Console and run your application. Default username is `admin` and password is `1q2w3E*`. - -#### Separated Deployment & Databases Scenario - -In this scenario, there are three applications; - -* `.AuthServer` application is an authentication server used by other applications. It has its own `appsettings.json` that contains database connection and other configurations. -* `.HttpApi.Host` hosts the HTTP API of the module. It has its own `appsettings.json` that contains database connections and other configurations. -* `.Web.Host` host the UI of the module. This project contains an `appsettings.json` file, but it does not have a connection string because it never connects to the database. Instead, it mainly contains endpoint of the remote API server and the authentication server. - -The diagram below shows the relation of the applications: - -![tiered-solution-applications](../images/tiered-solution-applications.png) - -`.Web.Host` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the `.AuthServer`. Then uses the access token to call the `.HttpApi.Host`. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. - -##### Pre-requirements - -* [Redis](https://redis.io/): The applications use Redis as as distributed cache. So, you need to have Redis installed & running. - -##### How to Run? - -You should run the application with the given order: - -- First, run the `.AuthServer` since other applications depends on it. -- Then run the `.HttpApi.Host` since it is used by the `.Web.Host` application. -- Finally, you can run the `.Web.Host` project and login to the application using `admin` as the username and `1q2w3E*` as the password. - -## UI - -### Angular UI - -The solution will have a folder called `angular` in it. This is where the Angular client-side code is located. When you open that folder in an IDE, the folder structure will look like below: - -![Folder structure of ABP Angular module project](../images/angular-module-folder-structure.png) - -* _angular/projects/issue-management_ folder contains the Angular module project. -* _angular/projects/dev-app_ folder contains a development application that runs your module. - -The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API and the `Angular` demo application consumes it. You will not need to run the `.Web.Host` project though. - -#### How to Run the Angular Development App - -For module development, you will need the `dev-app` project up and running. So, here is how we can start the development server. - -First, we need to install dependencies: - -1. Open your terminal at the root folder, i.e. `angular`. -2. Run `yarn` or `npm install`. - -The dependencies will be installed and some of them are ABP modules published as NPM packages. To see all ABP packages, you can run the following command in the `angular` folder: - -```bash -yarn list --pattern abp -``` - -> There is no equivalent of this command in npm. - -The module you will develop depends on two of these ABP packages: _@abp/ng.core_ and _@abp/ng.theme.shared_. Rest of the ABP modules are included in _package.json_ because of the `dev-app` project. - -Once all dependencies are installed, follow the steps below to serve your development app: - -1. Make sure `.AuthServer` and `*.HttpApi.Host` projects are up and running. -2. Open your terminal at the root folder, i.e. `angular`. -3. Run `yarn start` or `npm start`. - -![ABP Angular module dev-app project](../images/angular-module-dev-app-project.png) - -The issue management page is empty in the beginning. You may change the content in `IssueManagementComponent` at the _angular/projects/issue-management/src/lib/issue-management.component.ts_ path and observe that the view changes accordingly. - -Now, let's have a closer look at some key elements of your project. - -#### Main Module - -`IssueManagementModule` at the _angular/projects/issue-management/src/lib/issue-management.module.ts_ path is the main module of your module project. There are a few things worth mentioning in it: - -- Essential ABP modules, i.e. `CoreModule` and `ThemeSharedModule`, are imported. -- `IssueManagementRoutingModule` is imported. -- `IssueManagementComponent` is declared. -- It is prepared for configurability. The `forLazy` static method enables [a configuration to be passed to the module when it is loaded by the router](https://volosoft.com/blog/how-to-configure-angular-modules-loaded-by-the-router). - - -#### Main Routing Module - -`IssueManagementRoutingModule` at the _angular/projects/issue-management/src/lib/issue-management-routing.module.ts_ path is the main routing module of your module project. It currently does two things: - -- Loads `DynamicLayoutComponent` at base path it is given. -- Loads `IssueManagementComponent` as child to the layout, again at the given base path. - -You can rearrange this module to load more than one component at different routes, but you need to update the route provider at _angular/projects/issue-management/config/src/providers/route.provider.ts_ to match the new routing structure with the routes in the menu. Please check [Modifying the Menu](../UI/Angular/Modifying-the-Menu.md) to see how route providers work. - -#### Config Module - -There is a config module at the _angular/projects/issue-management/config/src/issue-management-config.module.ts_ path. The static `forRoot` method of this module is supposed to be called at the route level. So, you may assume the following will take place: - -```js -@NgModule({ - imports: [ - /* other imports */ - - IssueManagementConfigModule.forRoot(), - ], - - /* rest of the module meta data */ -}) -export class AppModule {} -``` - -You can use this static method to configure an application that uses your module project. An example of such configuration is already implemented and the `ISSUE_MANAGEMENT_ROUTE_PROVIDERS` token is provided here. The method can take options which enables further configuration possibilities. - -The difference between the `forRoot` method of the config module and the `forLazy` method of the main module is that, for smallest bundle size, the former should only be used when you have to configure an app before your module is even loaded. - -#### Testing Angular UI - -Please see the [testing document](../UI/Angular/Testing.md). diff --git a/docs/en/Startup-Templates/WPF.md b/docs/en/Startup-Templates/WPF.md deleted file mode 100644 index 7eb6a5c73e..0000000000 --- a/docs/en/Startup-Templates/WPF.md +++ /dev/null @@ -1,27 +0,0 @@ -# WPF Application Startup Template - -This template is used to create a minimalist WPF application project. - -## How to Start With? - -First, install the [ABP CLI](../CLI.md) if you haven't installed before: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Then use the `abp new` command in an empty folder to create a new solution: - -````bash -abp new Acme.MyWpfApp -t wpf -```` - -`Acme.MyWpfApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. - -## Solution Structure - -After you use the above command to create a solution, you will have a solution like shown below: - -![basic-wpf-application-solution](../images/basic-wpf-application-solution.png) - -* `HelloWorldService` is a sample service that implements the `ITransientDependency` interface to register this service to the [dependency injection](../Dependency-Injection.md) system. \ No newline at end of file diff --git a/docs/en/String-Encryption.md b/docs/en/String-Encryption.md deleted file mode 100644 index 867190365d..0000000000 --- a/docs/en/String-Encryption.md +++ /dev/null @@ -1,125 +0,0 @@ -# String Encryption - -ABP Framework provides string encryption feature that allows to **Encrypt** and **Decrypt** strings. - -## Installation - -> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. - -If installation is needed, it is suggested to use the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -```bash -abp add-package Volo.Abp.Security -``` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.Security](https://www.nuget.org/packages/Volo.Abp.Security) NuGet package to your project: - - `Install-Package Volo.Abp.Security` - -2. Add the `AbpSecurityModule` to the dependency list of your module: - - ```csharp - [DependsOn( - //...other dependencies - typeof(AbpSecurityModule) // <-- Add module dependency like that - )] - public class YourModule : AbpModule - { - } - ``` - -## Using String Encryption - -All encryption operations are included in `IStringEncryptionService`. You can inject it and start to use. - -```csharp - public class MyService : DomainService - { - protected IStringEncryptionService StringEncryptionService { get; } - - public MyService(IStringEncryptionService stringEncryptionService) - { - StringEncryptionService = stringEncryptionService; - } - - public string Encrypt(string value) - { - // To enrcypt a value - return StringEncryptionService.Encrypt(value); - } - - public string Decrpyt(string value) - { - // To decrypt a value - return StringEncryptionService.Decrypt(value); - } - } -``` - -### Using Custom PassPhrase - -`IStringEncryptionService` methods has **passPharase** parameter with default value and it uses default PassPhrase when you don't pass passPhrase parameter. - -```csharp -// Default Pass Phrase -var encryptedValue = StringEncryptionService.Encrypt(value); - -// Custom Pass Phrase -var encryptedValue = StringEncryptionService.Encrypt(value, "MyCustomPassPhrase"); - -// Encrypt & Decrypt have same parameters. -var decryptedValue = StringEncryptionService.Decrypt(value, "MyCustomPassPhrase"); -``` - -### Using Custom Salt - -`IStringEncryptionService` methods has **salt** parameter with default value and it uses default Salt when you don't pass the parameter. - -```csharp -// Default Salt -var encryptedValue = StringEncryptionService.Encrypt(value); - -// Custom Salt -var encryptedValue = StringEncryptionService.Encrypt(value, salt: Encoding.UTF8.GetBytes("MyCustomSalt")); - -// Encrypt & Decrypt have same parameters. -var decryptedValue = StringEncryptionService.Decrypt(value, salt: Encoding.UTF8.GetBytes("MyCustomSalt")); -``` - -*** - -## String Encryption Options - -Default values can be configured with `AbpStringEncryptionOptions` type. - -```csharp -Configure(opts => -{ - opts.DefaultPassPhrase = "MyStrongPassPhrase"; - opts.DefaultSalt = Encoding.UTF8.GetBytes("MyStrongSalt"); - opts.InitVectorBytes = Encoding.UTF8.GetBytes("YetAnotherStrongSalt"); - opts.Keysize = 512; -}); -``` - -- **DefaultPassPhrase**: Default password to encrypt/decrypt texts. It's recommended to set to another value for security. Default value: `gsKnGZ041HLL4IM8` - -- **DefaultSalt**: A value which is used as salt while encrypting/decrypting. - - Default value: `Encoding.ASCII.GetBytes("hgt!16kl")` - -- **InitVectorBytes:** This constant string is used as a "salt" value for the PasswordDeriveBytes function calls. This size of the IV (in bytes) must = (keysize / 8). Default keysize is 256, so the IV must be 32 bytes long. Using a 16 character string here gives us 32 bytes when converted to a byte array. - - Default value: `Encoding.ASCII.GetBytes("jkE49230Tf093b42")` - -- **Keysize:** This constant is used to determine the keysize of the encryption algorithm. - - Default value: `256` \ No newline at end of file diff --git a/docs/en/Testing.md b/docs/en/Testing.md deleted file mode 100644 index 6ad6bc5443..0000000000 --- a/docs/en/Testing.md +++ /dev/null @@ -1,833 +0,0 @@ -# Automated Testing - -## Introduction - -ABP Framework has been designed with testability in mind. There are some different levels of automated testing; - -* **Unit Tests**: You typically test a single class (or a very few classes together). These tests will be fast. However, you generally need to deal with mocking for the dependencies of your service(s). -* **Integration Tests**: You typically test a service, but this time you don't mock the fundamental infrastructure and services to see if they properly working together. -* **UI Tests**: You test the UI of the application, just like the users interact with your application. - -### Unit Tests vs Integration Tests - -Integration tests have some significant **advantages** compared to unit tests; - -* **Easier to write** since you don't work to establish mocking and dealing with the dependencies. -* Your test code runs with all the real services and infrastructure (including database mapping and queries), so it is much closer to the **real application test**. - -While they have some drawbacks; - -* They are **slower** compared to unit tests since all the infrastructure is prepared for each test case. -* A bug in a service may make multiple test cases broken, so it may be **harder to find the real problem** in some cases. - -We suggest to go mixed: Write unit or integration test where it is necessary and you find effective to write and maintain it. - -## The Application Startup Template - -The [Application Startup Template](Startup-Templates/Application.md) comes with the test infrastructure properly installed and configured for you. - -### The Test Projects - -See the following solution structure in the Visual Studio: - -![solution-test-projects](images/solution-test-projects.png) - -There are more than one test project, organized by the layers; - -* `Domain.Tests` is used to test your Domain Layer objects (like [Domain Services](Domain-Services.md) and [Entities](Entities.md)). -* `Application.Tests` is used to test your Application Layer (like [Application Services](Application-Services.md)). -* `EntityFrameworkCore.Tests` is used to implement abstract test classes and test your custom repository implementations or EF Core mappings (this project will be different if you use another [Database Provider](Data-Access.md)). -* `Web.Tests` is used to test the UI Layer (like Pages, Controllers and View Components). This project does exists only for MVC / Razor Page applications. -* `TestBase` contains some classes those are shared/used by the other projects. - -> `HttpApi.Client.ConsoleTestApp` is not an automated test application. It is an example Console Application that shows how to consume your HTTP APIs from a .NET Console Application. - -The following sections will introduce the base classes and other infrastructure included in these projects. - -### The Test Infrastructure - -The startup solution has the following libraries already installed; - -* [xUnit](https://xunit.net/) as the test framework. -* [NSubstitute](https://nsubstitute.github.io/) as the mocking library. -* [Shouldly](https://github.com/shouldly/shouldly) as the assertion library. - -While you are free to replace them with your favorite tools, this document and examples will be base on these tooling. - -## The Test Explorer - -You can use the Test Explorer to view and run the tests in Visual Studio. For other IDEs, see their own documentation. - -### Open the Test Explorer - -Open the *Test Explorer*, under the *Tests* menu, if it is not already open: - -![vs-test-explorer](images/vs-test-explorer.png) - -### Run the Tests - -Then you can click to the Run All or Run buttons to run the tests. The initial startup template has some sample tests for you: - -![vs-startup-template-tests](images/vs-startup-template-tests.png) - -### Run Tests In Parallel - -The test infrastructure is compatible to run the tests in parallel. It is **strongly suggested** to run all the tests in parallel, which is pretty faster then running them one by one. - -To enable it, click to the caret icon near to the settings (gear) button and select the *Run Tests In Parallel*. - -![vs-run-tests-in-parallel](images/vs-run-tests-in-parallel.png) - -## Unit Tests - -For Unit Tests, you don't need to much infrastructure. You typically instantiate your class and provide some pre-configured mocked objects to prepare your object to test. - -### Classes Without Dependencies - -In this simplest case, the class you want to test has no dependencies. In this case, you can directly instantiate your class, call its methods and make your assertions. - -#### Example: Testing an Entity - -Assume that you've an `Issue` [entity](Entities.md) as shown below: - -````csharp -using System; -using Volo.Abp.Domain.Entities; - -namespace MyProject.Issues -{ - public class Issue : AggregateRoot - { - public string Title { get; set; } - public string Description { get; set; } - public bool IsLocked { get; set; } - public bool IsClosed { get; private set; } - public DateTime? CloseDate { get; private set; } - - public void Close() - { - IsClosed = true; - CloseDate = DateTime.UtcNow; - } - - public void Open() - { - if (!IsClosed) - { - return; - } - - if (IsLocked) - { - throw new IssueStateException("You can not open a locked issue!"); - } - - IsClosed = false; - CloseDate = null; - } - } -} - -```` - -Notice that the `IsClosed` and `CloseDate` properties have private setters to force some business rules by using the `Open()` and `Close()` methods; - -* Whenever you close an issue, the `CloseDate` should be set to the [current time](Timing.md). -* An issue can not be re-opened if it is locked. And if it is re-opened, the `CloseDate` should be set to `null`. - -Since the `Issue` entity is a part of the Domain Layer, we should test it in the `Domain.Tests` project. Create an `Issue_Tests` class inside the `Domain.Tests` project: - -````csharp -using Shouldly; -using Xunit; - -namespace MyProject.Issues -{ - public class Issue_Tests - { - [Fact] - public void Should_Set_The_CloseDate_Whenever_Close_An_Issue() - { - // Arrange - - var issue = new Issue(); - issue.CloseDate.ShouldBeNull(); // null at the beginning - - // Act - - issue.Close(); - - // Assert - - issue.IsClosed.ShouldBeTrue(); - issue.CloseDate.ShouldNotBeNull(); - } - } -} -```` - -This test follows the AAA (Arrange-Act-Assert) pattern; - -* **Arrange** part creates an `Issue` entity and ensures the `CloseDate` is `null` at the beginning. -* **Act** part executes the method we want to test for this case. -* **Assert** part checks if the `Issue` properties are same as we expect to be. - -`[Fact]` attribute is defined by the [xUnit](https://xunit.net/) library and marks a method as a test method. `Should...` extension methods are provided by the [Shouldly](https://github.com/shouldly/shouldly) library. You can directly use the `Assert` class of the xUnit, but Shouldly makes it much comfortable and straightforward. - -When you execute the tests, you will see that is passes successfully: - -![issue-first-test](images/issue-first-test.png) - -Let's add two more test methods: - -````csharp -[Fact] -public void Should_Allow_To_ReOpen_An_Issue() -{ - // Arrange - - var issue = new Issue(); - issue.Close(); - - // Act - - issue.Open(); - - // Assert - - issue.IsClosed.ShouldBeFalse(); - issue.CloseDate.ShouldBeNull(); -} - -[Fact] -public void Should_Not_Allow_To_ReOpen_A_Locked_Issue() -{ - // Arrange - - var issue = new Issue(); - issue.Close(); - issue.IsLocked = true; - - // Act & Assert - - Assert.Throws(() => - { - issue.Open(); - }); -} -```` - -`Assert.Throws` checks if the executed code throws a matching exception. - -> See the xUnit & Shoudly documentations to learn more about these libraries. - -### Classes With Dependencies - -If your service has dependencies and you want to unit test this service, you need to mock the dependencies. - -#### Example: Testing a Domain Service - -Assume that you've an `IssueManager` [Domain Service](Domain-Services.md) that is defined as below: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Domain.Services; - -namespace MyProject.Issues -{ - public class IssueManager : DomainService - { - public const int MaxAllowedOpenIssueCountForAUser = 3; - - private readonly IIssueRepository _issueRepository; - - public IssueManager(IIssueRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task AssignToUserAsync(Issue issue, Guid userId) - { - var issueCount = await _issueRepository.GetIssueCountOfUserAsync(userId); - - if (issueCount >= MaxAllowedOpenIssueCountForAUser) - { - throw new BusinessException( - code: "IM:00392", - message: $"You can not assign more" + - $"than {MaxAllowedOpenIssueCountForAUser} issues to a user!" - ); - } - - issue.AssignedUserId = userId; - } - } -} -```` - -`IssueManager` depends on the `IssueRepository` service, that will be mocked in this example. - -**Business Rule**: The example `AssignToUserAsync` doesn't allow to assign more than 3 (`MaxAllowedOpenIssueCountForAUser` constant) issues to a user. If you want to assign an issue in this case, you first need to unassign an existing issue. - -The test case below tries to make a valid assignment: - -````csharp -using System; -using System.Threading.Tasks; -using NSubstitute; -using Shouldly; -using Volo.Abp; -using Xunit; - -namespace MyProject.Issues -{ - public class IssueManager_Tests - { - [Fact] - public async Task Should_Assign_An_Issue_To_A_User() - { - // Arrange - - var userId = Guid.NewGuid(); - - var fakeRepo = Substitute.For(); - fakeRepo.GetIssueCountOfUserAsync(userId).Returns(1); - - var issueManager = new IssueManager(fakeRepo); - - var issue = new Issue(); - - // Act - - await issueManager.AssignToUserAsync(issue, userId); - - //Assert - - issue.AssignedUserId.ShouldBe(userId); - await fakeRepo.Received(1).GetIssueCountOfUserAsync(userId); - } - } -} -```` - -* `Substitute.For` creates a mock (fake) object that is passed into the `IssueManager` constructor. -* `fakeRepo.GetIssueCountOfUserAsync(userId).Returns(1)` ensures that the `GetIssueCountOfUserAsync` method of the repository returns `1`. -* `issueManager.AssignToUserAsync` doesn't throw any exception since the repository returns `1` for the currently assigned issue count. -* `issue.AssignedUserId.ShouldBe(userId);` line checks if the `AssignedUserId` has the correct value. -* `await fakeRepo.Received(1).GetIssueCountOfUserAsync(userId);` checks if the `IssueManager` called the `GetIssueCountOfUserAsync` method exactly one time. - -Let's add a second test to see if it prevents to assign issues to a user more than the allowed count: - -````csharp -[Fact] -public async Task Should_Not_Allow_To_Assign_Issues_Over_The_Limit() -{ - // Arrange - - var userId = Guid.NewGuid(); - - var fakeRepo = Substitute.For(); - fakeRepo - .GetIssueCountOfUserAsync(userId) - .Returns(IssueManager.MaxAllowedOpenIssueCountForAUser); - - var issueManager = new IssueManager(fakeRepo); - - // Act & Assert - - var issue = new Issue(); - - await Assert.ThrowsAsync(async () => - { - await issueManager.AssignToUserAsync(issue, userId); - }); - - issue.AssignedUserId.ShouldBeNull(); - await fakeRepo.Received(1).GetIssueCountOfUserAsync(userId); -} -```` - -> For more information on the mocking, see the [NSubstitute](https://nsubstitute.github.io/) documentation. - -It is relatively easy to mock a single dependency. But, when your dependencies grow, it gets harder to setup the test objects and mock all the dependencies. See the *Integration Tests* section that doesn't require mocking the dependencies. - -### Tip: Share the Test Class Constructor - -[xUnit](https://xunit.net/) creates a **new test class instance** (`IssueManager_Tests` for this example) for each test method. So, you can move some *Arrange* code into the constructor to reduce the code duplication. The constructor will be executed for each test case and doesn't affect each other, even if they work in parallel. - -**Example: Refactor the `IssueManager_Tests` to reduce the code duplication** - -````csharp -using System; -using System.Threading.Tasks; -using NSubstitute; -using Shouldly; -using Volo.Abp; -using Xunit; - -namespace MyProject.Issues -{ - public class IssueManager_Tests - { - private readonly Guid _userId; - private readonly IIssueRepository _fakeRepo; - private readonly IssueManager _issueManager; - private readonly Issue _issue; - - public IssueManager_Tests() - { - _userId = Guid.NewGuid(); - _fakeRepo = Substitute.For(); - _issueManager = new IssueManager(_fakeRepo); - _issue = new Issue(); - } - - [Fact] - public async Task Should_Assign_An_Issue_To_A_User() - { - // Arrange - _fakeRepo.GetIssueCountOfUserAsync(_userId).Returns(1); - - // Act - await _issueManager.AssignToUserAsync(_issue, _userId); - - //Assert - _issue.AssignedUserId.ShouldBe(_userId); - await _fakeRepo.Received(1).GetIssueCountOfUserAsync(_userId); - } - - [Fact] - public async Task Should_Not_Allow_To_Assign_Issues_Over_The_Limit() - { - // Arrange - _fakeRepo - .GetIssueCountOfUserAsync(_userId) - .Returns(IssueManager.MaxAllowedOpenIssueCountForAUser); - - // Act & Assert - await Assert.ThrowsAsync(async () => - { - await _issueManager.AssignToUserAsync(_issue, _userId); - }); - - _issue.AssignedUserId.ShouldBeNull(); - await _fakeRepo.Received(1).GetIssueCountOfUserAsync(_userId); - } - } -} -```` - -> Keep your test code clean to create a maintainable test suite. - -## Integration Tests - -> You can also follow the [web application development tutorial](Tutorials/Part-1.md) to learn developing a full stack application, including the integration tests. - -### The Integration Test Infrastructure - -ABP Provides a complete infrastructure to write integration tests. All the ABP infrastructure and services will perform in your tests. The application startup template comes with the necessary infrastructure pre-configured for you; - -#### The Database - -The startup template is configured to use **in-memory SQLite** database for the EF Core (for MongoDB, it uses [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library). So, all the configuration and queries are performed against a real database and you can even test database transactions. - -Using in-memory SQLite database has two main advantages; - -* It is faster compared to an external DBMS. -* It create a **new fresh database** for each test case, so tests doesn't affect each other. - -> **Tip**: Do not use EF Core's In-Memory database for advanced integration tests. It is not a real DBMS and has many differences in details. For example, it doesn't support transaction and rollback scenarios, so you can't truly test the failing scenarios. On the other hand, In-Memory SQLite is a real DBMS and supports the fundamental SQL database features. - -### The Seed Data - -Writing tests against an empty database is not practical. In most cases, you need to some initial data in the database. For example, if you write a test class that query, update and delete the products, it would be helpful to have a few products in the database before executing the test case. - -ABP's [Data Seeding](Data-Seeding.md) system is a powerful way to seed the initial data. The application startup template has a *YourProject*TestDataSeedContributor class in the `.TestBase` project. You can fill it to have an initial data that you can use for each test method. - -**Example: Create some Issues as the seed data** - -````csharp -using System.Threading.Tasks; -using MyProject.Issues; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; - -namespace MyProject -{ - public class MyProjectTestDataSeedContributor - : IDataSeedContributor, ITransientDependency - { - private readonly IIssueRepository _issueRepository; - - public MyProjectTestDataSeedContributor(IIssueRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task SeedAsync(DataSeedContext context) - { - await _issueRepository.InsertAsync( - new Issue - { - Title = "Test issue one", - Description = "Test issue one description", - AssignedUserId = TestData.User1Id - }); - - await _issueRepository.InsertAsync( - new Issue - { - Title = "Test issue two", - Description = "Test issue two description", - AssignedUserId = TestData.User1Id - }); - - await _issueRepository.InsertAsync( - new Issue - { - Title = "Test issue three", - Description = "Test issue three description", - AssignedUserId = TestData.User1Id - }); - - await _issueRepository.InsertAsync( - new Issue - { - Title = "Test issue four", - Description = "Test issue four description", - AssignedUserId = TestData.User2Id - }); - } - } -} -```` - -Also created a static class to store the User `Ids`: - -````csharp -using System; - -namespace MyProject -{ - public static class TestData - { - public static Guid User1Id = Guid.Parse("41951813-5CF9-4204-8B18-CD765DBCBC9B"); - public static Guid User2Id = Guid.Parse("2DAB4460-C21B-4925-BF41-A52750A9B999"); - } -} -```` - -In this way, we can use these known Issues and the User `Id`s to perform the tests. - -### Example: Testing a Domain Service - -`AbpIntegratedTest` class (defined in the [Volo.Abp.TestBase](https://www.nuget.org/packages/Volo.Abp.TestBase) package) is used to write tests integrated to the ABP Framework. `T` is the Type of the root module to setup and initialize the application. - -The application startup template has base classes in each test project, so you can derive from these base classes to make it easier. - -See the `IssueManager` tests are re-written as integration tests - -````csharp -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp; -using Xunit; - -namespace MyProject.Issues -{ - public abstract class IssueManager_Integration_Tests : MyProjectDomainTestBase - where TStartupModule : IAbpModule - { - private readonly IssueManager _issueManager; - private readonly Issue _issue; - - protected IssueManager_Integration_Tests() - { - _issueManager = GetRequiredService(); - _issue = new Issue - { - Title = "Test title", - Description = "Test description" - }; - } - - [Fact] - public async Task Should_Not_Allow_To_Assign_Issues_Over_The_Limit() - { - // Act & Assert - await Assert.ThrowsAsync(async () => - { - await _issueManager.AssignToUserAsync(_issue, TestData.User1Id); - }); - - _issue.AssignedUserId.ShouldBeNull(); - } - - [Fact] - public async Task Should_Assign_An_Issue_To_A_User() - { - // Act - await _issueManager.AssignToUserAsync(_issue, TestData.User2Id); - - //Assert - _issue.AssignedUserId.ShouldBe(TestData.User2Id); - } - } -} -```` - -> The `IssueManager_Integration_Tests` class is an abstract class, and tests in this class are not seen on the tests explorer, see the **Implementing unit tests in EF Core and MongoDB** section below to learn how to list tests in the test explorer and run them. - -* First test method assigns the issue to the User 1, which has already assigned to 3 issues in the Data Seed code. So, it throws a `BusinessException`. -* Second test method assigns the issue to User 2, which has only 1 issue assigned. So, the method succeeds. - -This class typically locates in the `.Domain.Tests` project since it tests a class located in the `.Domain` project. It is derived from the `MyProjectDomainTestBase` which is already configured to properly run the tests. - -Writing such an integration test class is very straightforward. Another benefit is that you won't need to change the test class later when you add another dependency to the `IssueManager` class. - -### Example: Testing an Application Service - -Testing an [Application Service](Application-Services.md) is not so different. Assume that you've created an `IssueAppService` as defined below: - -````csharp -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace MyProject.Issues -{ - public class IssueAppService : ApplicationService, IIssueAppService - { - private readonly IIssueRepository _issueRepository; - - public IssueAppService(IIssueRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task> GetListAsync() - { - var issues = await _issueRepository.GetListAsync(); - - return ObjectMapper.Map, List>(issues); - } - } -} -```` - -*(assuming you've also defined the `IIssueAppService` and `IssueDto` and created the [object mapping](Object-To-Object-Mapping.md) between `Issue` and the `IssueDto`)* - -Now, you can write a test class inside the `.Application.Tests` project: - -````csharp -using System.Threading.Tasks; -using Shouldly; -using Xunit; - -namespace MyProject.Issues -{ - public abstract class IssueAppService_Tests : MyProjectApplicationTestBase - where TStartupModule : IAbpModule - { - private readonly IIssueAppService _issueAppService; - - protected IssueAppService_Tests() - { - _issueAppService = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_All_Issues() - { - //Act - var issueDtos = await _issueAppService.GetListAsync(); - - //Assert - issueDtos.Count.ShouldBeGreaterThan(0); - } - } -} -```` - -> The `IssueAppService_Tests` class is an abstract, and tests in this class are not seen on the tests explorer, see the **Implementing unit tests in EF Core and MongoDB** section below to learn how to list tests in the test explorer and run them. - -It's that simple. This test method tests everything, including the application service, EF Core mapping, object to object mapping and the repository implementation. In this way, you can fully test the Application Layer and the Domain Layer of your solution. - -### Dealing with Unit of Work in Integration Tests - -ABP's [unit of work](Unit-Of-Work.md) system controls the database connection and transaction management in your application. It seamlessly works while you writing your application code, so you may not aware of it. - -In the ABP Framework, all the database operations must be performed inside a unit of work scope. When you test an [application service](Application-Services.md) method, the unit of work scope will be the scope of your application service method. If you are testing a [repository](Repositories.md) method, the unit of work scope will be the scope of your repository method. - -In some cases, you may need to manually control the unit of work scope. Consider the following test method: - -````csharp -public abstract class IssueRepository_Tests : MyProjectDomainTestBase - where TStartupModule : IAbpModule -{ - private readonly IRepository _issueRepository; - - protected IssueRepository_Tests() - { - _issueRepository = GetRequiredService>(); - } - - public async Task Should_Query_By_Title() - { - IQueryable queryable = await _issueRepository.GetQueryableAsync(); - var issue = queryable.FirstOrDefaultAsync(i => i.Title == "My issue title"); - issue.ShouldNotBeNull(); - } -} -```` - -> The `IssueRepository_Tests` class is an abstract, and tests in this class are not seen on the tests explorer, see the **Implementing unit tests in EF Core and MongoDB** section below to learn how to list tests in the test explorer and run them. - -We are using `_issueRepository.GetQueryableAsync` to obtain an `IQueryable` object. Then, we are using the `FirstOrDefaultAsync` method to query an issue by its title. The database query is executed at this point, and you get an exception indicating that there is no active unit of work. - -To make that test properly working, you should manually start a unit of work scope as shown in the following example: - -````csharp -public abstract class IssueRepository_Tests : MyProjectDomainTestBase - where TStartupModule : IAbpModule -{ - private readonly IRepository _issueRepository; - private readonly IUnitOfWorkManager _unitOfWorkManager; - - protected IssueRepository_Tests() - { - _issueRepository = GetRequiredService>(); - _unitOfWorkManager = GetRequiredService(); - } - - public async Task Should_Query_By_Title() - { - using (var uow = _unitOfWorkManager.Begin()) - { - IQueryable queryable = await _issueRepository.GetQueryableAsync(); - var issue = queryable.FirstOrDefaultAsync(i => i.Title == "My issue title"); - issue.ShouldNotBeNull(); - await uow.CompleteAsync(); - } - } -} -```` - -We've used the `IUnitOfWorkManager` service to create a unit of work scope, then called the `FirstOrDefaultAsync` method inside that scope, so we don't have the problem anymore. - -> Note that we've tested the `FirstOrDefaultAsync` to demonstrate the unit of work problem. Normally, as a good principle, you should write tests only your own code. - -You could also use `WithUnitOfWorkAsync` to achieve the same functionality instead of writing the same using block in your tests. - -Here is the same test scenario using `WithUnitOfWorkAsync`: - -````csharp -public abstract class IssueRepository_Tests : MyProjectDomainTestBase - where TStartupModule : IAbpModule -{ - private readonly IRepository _issueRepository; - private readonly IUnitOfWorkManager _unitOfWorkManager; - - protected IssueRepository_Tests() - { - _issueRepository = GetRequiredService>(); - _unitOfWorkManager = GetRequiredService(); - } - - public async Task Should_Query_By_Title() - { - await WithUnitOfWorkAsync(() => - { - IQueryable queryable = await _issueRepository.GetQueryableAsync(); - var issue = queryable.FirstOrDefaultAsync(i => i.Title == "My issue title"); - issue.ShouldNotBeNull(); - }); - } -} -```` - -There are multiple overloads of the `WithUnitOfWorkAsync` method that you can use based on your specific needs. - -### Working with DbContext - -In some cases, you may want to directory work with the Entity Framework's `DbContext` object to perform database operations in your test methods. In this case, you can use `IDbContextProvider`service to obtain a `DbContext` instance inside a unit of work. - -The following example shows how you can create a `DbContext` object in a test method: - -````csharp -public abstract class MyDbContext_Tests : MyProjectDomainTestBase - where TStartupModule : IAbpModule -{ - private readonly IDbContextProvider _dbContextProvider; - private readonly IUnitOfWorkManager _unitOfWorkManager; - - protected IssueRepository_Tests() - { - _dbContextProvider = GetRequiredService>(); - _unitOfWorkManager = GetRequiredService(); - } - - public async Task Should_Query_By_Title() - { - using (var uow = _unitOfWorkManager.Begin()) - { - var dbContext = await _dbContextProvider.GetDbContextAsync(); - var issue = await dbContext.Issues.FirstOrDefaultAsync(i => i.Title == "My issue title"); - issue.ShouldNotBeNull(); - await uow.CompleteAsync(); - } - } -} -```` - -> The `MyDbContext_Tests` class is an abstract, and tests in this class are not seen on the tests explorer, see the **Implementing unit tests in EF Core and MongoDB** section below to learn how to list tests in the test explorer and run them. - -Just like we've done in the *Dealing with Unit of Work in Integration Tests* section, we should perform `DbContext` operations inside an active unit of work. - -For [MongoDB](MongoDB.md), you can use the `IMongoDbContextProvider` service to obtain a `DbContext` object and directly use MongoDB APIs in your test methods. - -## Implementing unit tests in EF Core and MongoDB - -The unit test classes in the `.Domain.Test` and `.Application.Tests` projects are all abstract classes. Therefore, we need to implement the test classes in EF Core or MongoDB test projects to run the tests, otherwise they will be ignored. - -An example implementation for the `IssueManager_Integration_Tests` class in the `.EntityFrameworkCore.Tests` project is shown below: - -````csharp -namespace MyProject.EntityFrameworkCore.Applications; - -public class EfCoreIssueAppService_Tests : IssueAppService_Tests -{ - -} -```` - -> By deriving from the related abstract classes, now we can see the all tests in the test explorers and run them. - -![unitest-efcore-mongodb](images/unitest-efcore-mongodb.png) - -As you can see from the folder structure, all tests are clearly placed into the related subfolders, and they will be seen in the test explorer with this separation. Thus, you can clearly see which tests are related to which layers and projects. - -## UI Tests - -In general, there are two types of UI Tests; - -### Non Visual Tests - -Such tests completely depends on your UI Framework choice; - -* For an MVC / Razor Pages UI, you typically make request to the server, get some HTML and test if some expected DOM elements exist in the returned result. -* Angular has its own infrastructure and practices to test the components, views and services. - -See the following documents to learn Non Visual UI Testing; - -* [Testing in ASP.NET Core MVC / Razor Pages](UI/AspNetCore/Testing.md) -* [Testing in Angular](UI/Angular/Testing.md) -* [Testing in Blazor](UI/Blazor/Testing.md) - -### Visual Tests - -Visual Tests are used to interact with the application UI just like a real user does. It fully tests the application, including the visual appearance of the pages and components. - -Visual UI Testing is out of the scope for the ABP Framework. There are a lot of tooling in the industry (like [Selenium](https://www.selenium.dev/)) that you can use to test your application's UI. diff --git a/docs/en/Text-Templating-Razor.md b/docs/en/Text-Templating-Razor.md deleted file mode 100644 index bf911abd4a..0000000000 --- a/docs/en/Text-Templating-Razor.md +++ /dev/null @@ -1,580 +0,0 @@ -# Razor Integration - - -The Razor template is a standard C# class, so you can freely use the functions of C#, such as `dependency injection`, using `LINQ`, custom methods, and even using `Repository`. - - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.TextTemplating.Razor -```` - -> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.TextTemplating.Razor). - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.TextTemplating.Razor](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Razor) NuGet package to your project: - -```` -Install-Package Volo.Abp.TextTemplating.Razor -```` - -2. Add the `AbpTextTemplatingRazorModule` to the dependency list of your module: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpTextTemplatingRazorModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -```` - -## Add MetadataReference to CSharpCompilerOptions - -You need to add the `MetadataReference` of the type used in the template to `CSharpCompilerOptions's References`. - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - Configure(options => - { - options.References.Add(MetadataReference.CreateFromFile(typeof(YourModule).Assembly.Location)); - }); -} -```` - -## Add MetadataReference for a template. - -You can add some `MetadataReference` to the template - -````csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - services.Configure(options => - { - //Hello is template name. - options.TemplateReferences.Add("Hello", new List() - { - Assembly.Load("Microsoft.Extensions.Logging.Abstractions"), - Assembly.Load("Microsoft.Extensions.Logging") - } - .Select(x => MetadataReference.CreateFromFile(x.Location)) - .ToList()); - }); -} -```` - -## Defining Templates - -Before rendering a template, you should define it. Create a class inheriting from the `TemplateDefinitionProvider` base class: - -````csharp -public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider -{ - public override void Define(ITemplateDefinitionContext context) - { - context.Add( - new TemplateDefinition("Hello") //template name: "Hello" - .WithRazorEngine() - .WithVirtualFilePath( - "/Demos/Hello/Hello.cshtml", //template content path - isInlineLocalized: true - ) - ); - } -} -```` - -* `context` object is used to add new templates or get the templates defined by depended modules. Used `context.Add(...)` to define a new template. -* `TemplateDefinition` is the class represents a template. Each template must have a unique name (that will be used while you are rendering the template). -* `/Demos/Hello/Hello.cshtml` is the path of the template file. -* `isInlineLocalized` is used to declare if you are using a single template for all languages (`true`) or different templates for each language (`false`). See the Localization section below for more. -* `WithRenderEngine` method is used to set the render engine of the template. - -### The Template Base - -Every `cshtml` template page needs to inherit `RazorTemplatePageBase` or `RazorTemplatePageBase`. -There are some useful properties in the base class that can be used in templates. eg: `Localizer`, `ServiceProvider`. - -### The Template Content - -`WithVirtualFilePath` indicates that we are using the [Virtual File System](Virtual-File-System.md) to store the template content. Create a `Hello.cshtml` file inside your project and mark it as "**embedded resource**" on the properties window: - -![hello-template-razor](images/hello-template-razor.png) - -Example `Hello.cshtml` content is shown below: - -```` -@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase -Hello @Model.Name -```` - -The `HelloModel` class is: -````csharp -namespace HelloModelNamespace -{ - public class HelloModel - { - public string Name { get; set; } - } -} -```` - -The [Virtual File System](Virtual-File-System.md) requires to add your files in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class: - -````csharp -Configure(options => -{ - options.FileSets.AddEmbedded("TextTemplateDemo"); -}); -```` - -* `TextTemplateDemoModule` is the module class that you define your template in. -* `TextTemplateDemo` is the root namespace of your project. - -## Rendering the Template - -`ITemplateRenderer` service is used to render a template content. - -### Example: Rendering a Simple Template - -````csharp -public class HelloDemo : ITransientDependency -{ - private readonly ITemplateRenderer _templateRenderer; - - public HelloDemo(ITemplateRenderer templateRenderer) - { - _templateRenderer = templateRenderer; - } - - public async Task RunAsync() - { - var result = await _templateRenderer.RenderAsync( - "Hello", //the template name - new HelloModel - { - Name = "John" - } - ); - - Console.WriteLine(result); - } -} -```` - -* `HelloDemo` is a simple class that injects the `ITemplateRenderer` in its constructor and uses it inside the `RunAsync` method. -* `RenderAsync` gets two fundamental parameters: - * `templateName`: The name of the template to be rendered (`Hello` in this example). - * `model`: An object that is used as the `model` inside the template (a `HelloModel` object in this example). - -The result shown below for this example: - -````csharp -Hello John :) -```` -## Localization - -It is possible to localize a template content based on the current culture. There are two types of localization options described in the following sections. - -### Inline localization - -Inline localization uses the [localization system](Localization.md) to localize texts inside templates. - -#### Example: Reset Password Link - -Assuming you need to send an email to a user to reset her/his password. Here, the model/template content: - -````csharp -namespace ResetMyPasswordModelNamespace -{ - public class ResetMyPasswordModel - { - public string Link { get; set; } - - public string Name { get; set; } - } -} -```` - -````html -@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase -@Localizer["ResetMyPassword", Model.Name] -```` - -`Localizer` service is used to localize the given key based on the current user culture. You need to define the `ResetMyPassword` key inside your localization file: - -````json -"ResetMyPasswordTitle": "Reset my password", -"ResetMyPassword": "Hi {0}, Click here to reset your password" -```` - -You also need to declare the localization resource to be used with this template, inside your template definition provider class: - -````csharp -context.Add( - new TemplateDefinition( - "PasswordReset", //Template name - typeof(DemoResource) //LOCALIZATION RESOURCE - ) - .WithRazorEngine() - .WithVirtualFilePath( - "/Demos/PasswordReset/PasswordReset.cshtml", //template content path - isInlineLocalized: true - ) -); -```` - -That's all. When you render this template like that: - -````csharp -var result = await _templateRenderer.RenderAsync( - "PasswordReset", //the template name - new PasswordResetModel - { - Name = "john", - Link = "https://abp.io/example-link?userId=123&token=ABC" - } -); -```` - -You will see the localized result: - -````html -Hi john, Click here to reset your password -```` - -> If you define the [default localization resource](Localization.md) for your application, then no need to declare the resource type for the template definition. - -### Multiple Contents Localization - -Instead of a single template that uses the localization system to localize the template, you may want to create different template files for each language. It can be needed if the template should be completely different for a specific culture rather than simple text localizations. - -#### Example: Welcome Email Template - -Assuming that you want to send a welcome email to your users, but want to define a completely different template based on the user culture. - -First, create a folder and put your templates inside it, like `en.cshtml`, `tr.cshtml`... one for each culture you support: - -![multiple-file-template-razor](images/multiple-file-template-razor.png) - -Then add your template definition in the template definition provider class: - -````csharp -context.Add( - new TemplateDefinition( - name: "WelcomeEmail", - defaultCultureName: "en" - ) - .WithRazorEngine() - .WithVirtualFilePath( - "/Demos/WelcomeEmail/Templates", //template content folder - isInlineLocalized: false - ) -); -```` - -* Set **default culture name**, so it fallbacks to the default culture if there is no template for the desired culture. -* Specify **the template folder** rather than a single template file. -* Set `isInlineLocalized` to `false` for this case. - -That's all, you can render the template for the current culture: - -````csharp -var result = await _templateRenderer.RenderAsync("WelcomeEmail"); -```` - -> Skipped the modal for this example to keep it simple, but you can use models as just explained before. - -### Specify the Culture - -`ITemplateRenderer` service uses the current culture (`CultureInfo.CurrentUICulture`) if not specified. If you need, you can specify the culture as the `cultureName` parameter: - -````csharp -var result = await _templateRenderer.RenderAsync( - "WelcomeEmail", - cultureName: "en" -); -```` - -## Layout Templates - -Layout templates are used to create shared layouts among other templates. It is similar to the layout system in the ASP.NET Core MVC / Razor Pages. - -### Example: Email HTML Layout Template - -For example, you may want to create a single layout for all of your email templates. - -First, create a template file just like before: - -````html -@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase - - - - - - - @Body - - -```` - -* A layout template must have a `Body` part as a place holder for the rendered child content. - -The register your template in the template definition provider: - -````csharp -context.Add( - new TemplateDefinition( - "EmailLayout", - isLayout: true //SET isLayout! - ) - .WithRazorEngine() - .WithVirtualFilePath( - "/Demos/EmailLayout/EmailLayout.cshtml", - isInlineLocalized: true - ) -); -```` - -Now, you can use this template as the layout of any other template: - -````csharp -context.Add( - new TemplateDefinition( - name: "WelcomeEmail", - defaultCultureName: "en", - layout: "EmailLayout" //Set the LAYOUT - ) - .WithRazorEngine() - .WithVirtualFilePath( - "/Demos/WelcomeEmail/Templates", - isInlineLocalized: false - ) -); -```` - -## Global Context - -ABP passes the `model` that can be used to access to the model inside the template. You can pass more global variables if you need. - -An example template content: - -````html -@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase -A global object value: @GlobalContext["myGlobalObject"] -```` - -This template assumes that that is a `myGlobalObject` object in the template rendering context. You can provide it like shown below: - -````csharp -var result = await _templateRenderer.RenderAsync( - "GlobalContextUsage", - globalContext: new Dictionary - { - {"myGlobalObject", "TEST VALUE"} - } -); -```` - -The rendering result will be: - -```` -A global object value: TEST VALUE -```` - -## Replacing the Existing Templates - -It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code. - -### Option-1: Using the Virtual File System - -The [Virtual File System](Virtual-File-System.md) allows you to override any file by placing the same file into the same path in your project. - -#### Example: Replace the Standard Email Layout Template - -ABP Framework provides an [email sending system](Emailing.md) that internally uses the text templating to render the email content. It defines a standard email layout template in the `/Volo/Abp/Emailing/Templates/Layout.cshtml` path. The unique name of the template is `Abp.StandardEmailTemplates.Layout` and this string is defined as a constant on the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` static class. - -Do the following steps to replace the template file with your own; - -**1)** Add a new file into the same location (`/Volo/Abp/Emailing/Templates/Layout.cshtml`) in your project: - -![replace-email-layout-razor](images/replace-email-layout-razor.png) - -**2)** Prepare your email layout template: - -````html -@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase - - - - - - -

    This my header

    - - @Body - -
    - This is my footer... -
    - - -```` - -This example simply adds a header and footer to the template and renders the content between them (see the *Layout Templates* section above to understand it). - -**3)** Configure the embedded resources in the `.csproj` file - -* Add [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet package to the project. -* Add `true` into the `...` section of your `.csproj` file. -* Add the following code into your `.csproj` file: - -````xml - - - - -```` - -This makes the template files "embedded resource". - -**4)** Configure the virtual file system - -Configure the `AbpVirtualFileSystemOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) to add the embedded files into the virtual file system: - -```csharp -Configure(options => -{ - options.FileSets.AddEmbedded(); -}); -``` - -`BookStoreDomainModule` should be your module name, in this example code. - -> Be sure that your module (directly or indirectly) [depends on](Module-Development-Basics.md) the `AbpEmailingModule`. Because the VFS can override files based on the dependency order. - -Now, your template will be used when you want to render the email layout template. - -### Option-2: Using the Template Definition Provider - -You can create a template definition provider class that gets the email layout template and changes the virtual file path for the template. - -**Example: Use the `/MyTemplates/EmailLayout.cshtml` file instead of the standard template** - -```csharp -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.TextTemplating; - -namespace MyProject -{ - public class MyTemplateDefinitionProvider - : TemplateDefinitionProvider, ITransientDependency - { - public override void Define(ITemplateDefinitionContext context) - { - var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Layout); - - emailLayoutTemplate - .WithVirtualFilePath( - "/MyTemplates/EmailLayout.cshtml", - isInlineLocalized: true - ); - } - } -} -``` - -You should still add the file `/MyTemplates/EmailLayout.cshtml` to the virtual file system as explained before. This approach allows you to locate templates in any folder instead of the folder defined by the depended module. - -Beside the template content, you can manipulate the template definition properties, like `DisplayName`, `Layout` or `LocalizationSource`. - -## Advanced Features - -This section covers some internals and more advanced usages of the text templating system. - -### Template Content Provider - -`ITemplateRenderer` is used to render the template, which is what you want for most of the cases. However, you can use the `ITemplateContentProvider` to get the raw (not rendered) template contents. - -> `ITemplateContentProvider` is internally used by the `ITemplateRenderer` to get the raw template contents. - -Example: - -````csharp -public class TemplateContentDemo : ITransientDependency -{ - private readonly ITemplateContentProvider _templateContentProvider; - - public TemplateContentDemo(ITemplateContentProvider templateContentProvider) - { - _templateContentProvider = templateContentProvider; - } - - public async Task RunAsync() - { - var result = await _templateContentProvider - .GetContentOrNullAsync("Hello"); - - Console.WriteLine(result); - } -} -```` - -The result will be the raw template content: - -```` -@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase -Hello @Model.Name -```` - -* `GetContentOrNullAsync` returns `null` if no content defined for the requested template. -* It can get a `cultureName` parameter that is used if template has different files for different cultures (see Multiple Contents Localization section above). - -### Template Content Contributor - -`ITemplateContentProvider` service uses `ITemplateContentContributor` implementations to find template contents. There is a single pre-implemented content contributor, `VirtualFileTemplateContentContributor`, which gets template contents from the virtual file system as described above. - -You can implement the `ITemplateContentContributor` to read raw template contents from another source. - -Example: - -````csharp -public class MyTemplateContentProvider - : ITemplateContentContributor, ITransientDependency -{ - public async Task GetOrNullAsync(TemplateContentContributorContext context) - { - var templateName = context.TemplateDefinition.Name; - - //TODO: Try to find content from another source - return null; - } -} - -```` - -Return `null` if your source can not find the content, so `ITemplateContentProvider` fallbacks to the next contributor. - -### Template Definition Manager - -`ITemplateDefinitionManager` service can be used to get the template definitions (created by the template definition providers). - -## See Also - -* [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. -* [Localization system](Localization.md). -* [Virtual File System](Virtual-File-System.md). diff --git a/docs/en/Text-Templating-Scriban.md b/docs/en/Text-Templating-Scriban.md deleted file mode 100644 index 324fed7b14..0000000000 --- a/docs/en/Text-Templating-Scriban.md +++ /dev/null @@ -1,526 +0,0 @@ -# Scriban Integration - -## Installation - -It is suggested to use the [ABP CLI](CLI.md) to install this package. - -### Using the ABP CLI - -Open a command line window in the folder of the project (.csproj file) and type the following command: - -````bash -abp add-package Volo.Abp.TextTemplating.Scriban -```` - -### Manual Installation - -If you want to manually install; - -1. Add the [Volo.Abp.TextTemplating.Scriban](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Scriban) NuGet package to your project: - -```` -Install-Package Volo.Abp.TextTemplating.Scriban -```` - -2. Add the `AbpTextTemplatingScribanModule` to the dependency list of your module: - -````csharp -[DependsOn( - //...other dependencies - typeof(AbpTextTemplatingScribanModule) //Add the new module dependency - )] -public class YourModule : AbpModule -{ -} -```` - -## Defining Templates - -Before rendering a template, you should define it. Create a class inheriting from the `TemplateDefinitionProvider` base class: - -````csharp -public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider -{ - public override void Define(ITemplateDefinitionContext context) - { - context.Add( - new TemplateDefinition("Hello") //template name: "Hello" - .WithVirtualFilePath( - "/Demos/Hello/Hello.tpl", //template content path - isInlineLocalized: true - ) - .WithScribanEngine() - ); - } -} -```` - -* `context` object is used to add new templates or get the templates defined by depended modules. Used `context.Add(...)` to define a new template. -* `TemplateDefinition` is the class represents a template. Each template must have a unique name (that will be used while you are rendering the template). -* `/Demos/Hello/Hello.tpl` is the path of the template file. -* `isInlineLocalized` is used to declare if you are using a single template for all languages (`true`) or different templates for each language (`false`). See the Localization section below for more. -* `WithRenderEngine` method is used to set the render engine of the template. - -### The Template Content - -`WithVirtualFilePath` indicates that we are using the [Virtual File System](Virtual-File-System.md) to store the template content. Create a `Hello.tpl` file inside your project and mark it as "**embedded resource**" on the properties window: - -![hello-template](images/hello-template.png) - -Example `Hello.tpl` content is shown below: - -```` -Hello {%{{{model.name}}}%} :) -```` - -The [Virtual File System](Virtual-File-System.md) requires to add your files in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class: - -````csharp -Configure(options => -{ - options.FileSets.AddEmbedded("TextTemplateDemo"); -}); -```` - -* `TextTemplateDemoModule` is the module class that you define your template in. -* `TextTemplateDemo` is the root namespace of your project. - -## Rendering the Template - -`ITemplateRenderer` service is used to render a template content. - -### Example: Rendering a Simple Template - -````csharp -public class HelloDemo : ITransientDependency -{ - private readonly ITemplateRenderer _templateRenderer; - - public HelloDemo(ITemplateRenderer templateRenderer) - { - _templateRenderer = templateRenderer; - } - - public async Task RunAsync() - { - var result = await _templateRenderer.RenderAsync( - "Hello", //the template name - new HelloModel - { - Name = "John" - } - ); - - Console.WriteLine(result); - } -} -```` - -* `HelloDemo` is a simple class that injects the `ITemplateRenderer` in its constructor and uses it inside the `RunAsync` method. -* `RenderAsync` gets two fundamental parameters: - * `templateName`: The name of the template to be rendered (`Hello` in this example). - * `model`: An object that is used as the `model` inside the template (a `HelloModel` object in this example). - -The result shown below for this example: - -````csharp -Hello John :) -```` - -### Anonymous Model - -While it is suggested to create model classes for the templates, it would be practical (and possible) to use anonymous objects for simple cases: - -````csharp -var result = await _templateRenderer.RenderAsync( - "Hello", - new - { - Name = "John" - } -); -```` - -In this case, we haven't created a model class, but created an anonymous object as the model. - -### PascalCase vs snake_case - -PascalCase property names (like `UserName`) is used as snake_case (like `user_name`) in the templates. - -## Localization - -It is possible to localize a template content based on the current culture. There are two types of localization options described in the following sections. - -### Inline localization - -Inline localization uses the [localization system](Localization.md) to localize texts inside templates. - -#### Example: Reset Password Link - -Assuming you need to send an email to a user to reset her/his password. Here, the template content: - -```` -{%{{{L "ResetMyPassword" model.name}}}%} -```` - -`L` function is used to localize the given key based on the current user culture. You need to define the `ResetMyPassword` key inside your localization file: - -````json -"ResetMyPasswordTitle": "Reset my password", -"ResetMyPassword": "Hi {0}, Click here to reset your password" -```` - -You also need to declare the localization resource to be used with this template, inside your template definition provider class: - -````csharp -context.Add( - new TemplateDefinition( - "PasswordReset", //Template name - typeof(DemoResource) //LOCALIZATION RESOURCE - ) - .WithScribanEngine() - .WithVirtualFilePath( - "/Demos/PasswordReset/PasswordReset.tpl", //template content path - isInlineLocalized: true - ) -); -```` - -That's all. When you render this template like that: - -````csharp -var result = await _templateRenderer.RenderAsync( - "PasswordReset", //the template name - new PasswordResetModel - { - Name = "john", - Link = "https://abp.io/example-link?userId=123&token=ABC" - } -); -```` - -You will see the localized result: - -````csharp -Hi john, Click here to reset your password -```` - -> If you define the [default localization resource](Localization.md) for your application, then no need to declare the resource type for the template definition. - -### Multiple Contents Localization - -Instead of a single template that uses the localization system to localize the template, you may want to create different template files for each language. It can be needed if the template should be completely different for a specific culture rather than simple text localizations. - -#### Example: Welcome Email Template - -Assuming that you want to send a welcome email to your users, but want to define a completely different template based on the user culture. - -First, create a folder and put your templates inside it, like `en.tpl`, `tr.tpl`... one for each culture you support: - -![multiple-file-template](images/multiple-file-template.png) - -Then add your template definition in the template definition provider class: - -````csharp -context.Add( - new TemplateDefinition( - name: "WelcomeEmail", - defaultCultureName: "en" - ) - .WithScribanEngine() - .WithVirtualFilePath( - "/Demos/WelcomeEmail/Templates", //template content folder - isInlineLocalized: false - ) -); -```` - -* Set **default culture name**, so it fallbacks to the default culture if there is no template for the desired culture. -* Specify **the template folder** rather than a single template file. -* Set `isInlineLocalized` to `false` for this case. - -That's all, you can render the template for the current culture: - -````csharp -var result = await _templateRenderer.RenderAsync("WelcomeEmail"); -```` - -> Skipped the modal for this example to keep it simple, but you can use models as just explained before. - -### Specify the Culture - -`ITemplateRenderer` service uses the current culture (`CultureInfo.CurrentUICulture`) if not specified. If you need, you can specify the culture as the `cultureName` parameter: - -````csharp -var result = await _templateRenderer.RenderAsync( - "WelcomeEmail", - cultureName: "en" -); -```` - -## Layout Templates - -Layout templates are used to create shared layouts among other templates. It is similar to the layout system in the ASP.NET Core MVC / Razor Pages. - -### Example: Email HTML Layout Template - -For example, you may want to create a single layout for all of your email templates. - -First, create a template file just like before: - -````xml - - - - - - - {%{{{content}}}%} - - -```` - -* A layout template must have a **{%{{{content}}}%}** part as a place holder for the rendered child content. - -The register your template in the template definition provider: - -````csharp -context.Add( - new TemplateDefinition( - "EmailLayout", - isLayout: true //SET isLayout! - ) - .WithScribanEngine() - .WithVirtualFilePath( - "/Demos/EmailLayout/EmailLayout.tpl", - isInlineLocalized: true - ) -); -```` - -Now, you can use this template as the layout of any other template: - -````csharp -context.Add( - new TemplateDefinition( - name: "WelcomeEmail", - defaultCultureName: "en", - layout: "EmailLayout" //Set the LAYOUT - ) - .WithScribanEngine() - .WithVirtualFilePath( - "/Demos/WelcomeEmail/Templates", - isInlineLocalized: false - ) -); -```` - -## Global Context - -ABP passes the `model` that can be used to access to the model inside the template. You can pass more global variables if you need. - -An example template content: - -```` -A global object value: {%{{{myGlobalObject}}}%} -```` - -This template assumes that that is a `myGlobalObject` object in the template rendering context. You can provide it like shown below: - -````csharp -var result = await _templateRenderer.RenderAsync( - "GlobalContextUsage", - globalContext: new Dictionary - { - {"myGlobalObject", "TEST VALUE"} - } -); -```` - -The rendering result will be: - -```` -A global object value: TEST VALUE -```` - -## Replacing the Existing Templates - -It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code. - -### Option-1: Using the Virtual File System - -The [Virtual File System](Virtual-File-System.md) allows you to override any file by placing the same file into the same path in your project. - -#### Example: Replace the Standard Email Layout Template - -ABP Framework provides an [email sending system](Emailing.md) that internally uses the text templating to render the email content. It defines a standard email layout template in the `/Volo/Abp/Emailing/Templates/Layout.tpl` path. The unique name of the template is `Abp.StandardEmailTemplates.Layout` and this string is defined as a constant on the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` static class. - -Do the following steps to replace the template file with your own; - -**1)** Add a new file into the same location (`/Volo/Abp/Emailing/Templates/Layout.tpl`) in your project: - -![replace-email-layout](images/replace-email-layout.png) - -**2)** Prepare your email layout template: - -````html - - - - - - -

    This my header

    - - {%{{{content}}}%} - -
    - This is my footer... -
    - - -```` - -This example simply adds a header and footer to the template and renders the content between them (see the *Layout Templates* section above to understand it). - -**3)** Configure the embedded resources in the `.csproj` file - -* Add [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet package to the project. -* Add `true` into the `...` section of your `.csproj` file. -* Add the following code into your `.csproj` file: - -````xml - - - - -```` - -This makes the template files "embedded resource". - -**4)** Configure the virtual file system - -Configure the `AbpVirtualFileSystemOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) to add the embedded files into the virtual file system: - -```csharp -Configure(options => -{ - options.FileSets.AddEmbedded(); -}); -``` - -`BookStoreDomainModule` should be your module name, in this example code. - -> Be sure that your module (directly or indirectly) [depends on](Module-Development-Basics.md) the `AbpEmailingModule`. Because the VFS can override files based on the dependency order. - -Now, your template will be used when you want to render the email layout template. - -### Option-2: Using the Template Definition Provider - -You can create a template definition provider class that gets the email layout template and changes the virtual file path for the template. - -**Example: Use the `/MyTemplates/EmailLayout.tpl` file instead of the standard template** - -```csharp -using Volo.Abp.DependencyInjection; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.TextTemplating; - -namespace MyProject -{ - public class MyTemplateDefinitionProvider - : TemplateDefinitionProvider, ITransientDependency - { - public override void Define(ITemplateDefinitionContext context) - { - var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Layout); - - emailLayoutTemplate - .WithVirtualFilePath( - "/MyTemplates/EmailLayout.tpl", - isInlineLocalized: true - ); - } - } -} -``` - -You should still add the file `/MyTemplates/EmailLayout.tpl` to the virtual file system as explained before. This approach allows you to locate templates in any folder instead of the folder defined by the depended module. - -Beside the template content, you can manipulate the template definition properties, like `DisplayName`, `Layout` or `LocalizationSource`. - -## Advanced Features - -This section covers some internals and more advanced usages of the text templating system. - -### Template Content Provider - -`ITemplateRenderer` is used to render the template, which is what you want for most of the cases. However, you can use the `ITemplateContentProvider` to get the raw (not rendered) template contents. - -> `ITemplateContentProvider` is internally used by the `ITemplateRenderer` to get the raw template contents. - -Example: - -````csharp -public class TemplateContentDemo : ITransientDependency -{ - private readonly ITemplateContentProvider _templateContentProvider; - - public TemplateContentDemo(ITemplateContentProvider templateContentProvider) - { - _templateContentProvider = templateContentProvider; - } - - public async Task RunAsync() - { - var result = await _templateContentProvider - .GetContentOrNullAsync("Hello"); - - Console.WriteLine(result); - } -} -```` - -The result will be the raw template content: - -```` -Hello {%{{{model.name}}}%} :) -```` - -* `GetContentOrNullAsync` returns `null` if no content defined for the requested template. -* It can get a `cultureName` parameter that is used if template has different files for different cultures (see Multiple Contents Localization section above). - -### Template Content Contributor - -`ITemplateContentProvider` service uses `ITemplateContentContributor` implementations to find template contents. There is a single pre-implemented content contributor, `VirtualFileTemplateContentContributor`, which gets template contents from the virtual file system as described above. - -You can implement the `ITemplateContentContributor` to read raw template contents from another source. - -Example: - -````csharp -public class MyTemplateContentProvider - : ITemplateContentContributor, ITransientDependency -{ - public async Task GetOrNullAsync(TemplateContentContributorContext context) - { - var templateName = context.TemplateDefinition.Name; - - //TODO: Try to find content from another source - return null; - } -} - -```` - -Return `null` if your source can not find the content, so `ITemplateContentProvider` fallbacks to the next contributor. - -### Template Definition Manager - -`ITemplateDefinitionManager` service can be used to get the template definitions (created by the template definition providers). - -## See Also - -* [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. -* [Localization system](Localization.md). -* [Virtual File System](Virtual-File-System.md). diff --git a/docs/en/Text-Templating.md b/docs/en/Text-Templating.md deleted file mode 100644 index 47e6c4cc80..0000000000 --- a/docs/en/Text-Templating.md +++ /dev/null @@ -1,37 +0,0 @@ -# Text Templating - -## Introduction - -ABP Framework provides a simple, yet efficient text template system. Text templating is used to dynamically render contents based on a template and a model (a data object): - -Template + Model =renderer=> Rendered Content - -It is very similar to an ASP.NET Core Razor View (or Page): - -*RAZOR VIEW (or PAGE) + MODEL ==render==> HTML CONTENT* - -You can use the rendered output for any purpose, like sending emails or preparing some reports. - -Template rendering engine is very powerful; - -* It supports **conditional logics**, **loops** and much more. -* Template content **can be localized**. -* You can define **layout templates** to be used as the layout while rendering other templates. -* You can pass arbitrary objects to the template context (beside the model) for advanced scenarios. - -ABP Framework provides two templating engines; - -* **[Razor](Text-Templating-Razor.md)** -* **[Scriban](Text-Templating-Scriban.md)** - -You can use different template engines in the same application, or even create a new custom template engine. - -## Source Code - -Get [the source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. - -## See Also - -* [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. -* [Localization system](Localization.md). -* [Virtual File System](Virtual-File-System.md). \ No newline at end of file diff --git a/docs/en/Themes/Basic.md b/docs/en/Themes/Basic.md deleted file mode 100644 index 819138de5f..0000000000 --- a/docs/en/Themes/Basic.md +++ /dev/null @@ -1,2 +0,0 @@ -This document has been moved to [here](../UI/AspNetCore/Basic-Theme.md). - diff --git a/docs/en/Themes/Index.md b/docs/en/Themes/Index.md deleted file mode 100644 index a588e37d3a..0000000000 --- a/docs/en/Themes/Index.md +++ /dev/null @@ -1,30 +0,0 @@ -# The Official Themes -ABP Framework provides a complete UI theming system. While you can build your own themes, you can use the following pre-built themes freely in your applications. - -## The Basic Theme -The Basic Theme is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/) styles. You can take the Basic Theme as the base theme and build your own theme or styling on top of it. Here, a screenshot from the theme: - -![basic-theme-application-layout](../images/basic-theme-application-layout.png) - -### Documentation - -- [Basic Theme - MVC UI](../UI/AspNetCore/Basic-Theme.md) -- [Basic Theme - Blazor UI](../UI/Blazor/Basic-Theme.md) -- [Basic Theme - Angular UI](../UI/Angular/Basic-Theme.md) - -## The LeptonX Lite Theme -**LeptonX Lite** is the free version of the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the ABP Commercial. Here, a screenshot from the theme: - -![LeptonX Lite application layout](../images/leptonxlite-theme-application-layout.jpeg) - -### Documentation - -- [LeptonX Lite - MVC UI](LeptonXLite/AspNetCore.md) -- [LeptonX Lite - Blazor UI](LeptonXLite/Blazor.md) -- [LeptonX Lite - Angular UI](LeptonXLite/Angular.md) - -## See Also - -* [Theming - MVC UI](../UI/AspNetCore/Theming.md) -* [Theming - Blazor UI](../UI/Blazor/Theming.md) -* [Theming - Angular UI](../UI/Angular/Theming.md) \ No newline at end of file diff --git a/docs/en/Themes/LeptonXLite/Angular.md b/docs/en/Themes/LeptonXLite/Angular.md deleted file mode 100644 index e7c8142bda..0000000000 --- a/docs/en/Themes/LeptonXLite/Angular.md +++ /dev/null @@ -1,304 +0,0 @@ -# LeptonX Lite Angular UI - -LeptonX Lite has implementation for the ABP Framework Angular Client. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). - -> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). - -> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. - -## Installation - -This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below: - -To add `LeptonX-lite` into your project, - -- Install `@abp/ng.theme.lepton-x` - -```bash -yarn add @abp/ng.theme.lepton-x -``` - -- Install `bootstrap-icons` - -```bash -yarn add bootstrap-icons -``` - -- Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one in the following link : - -* [Styles - Angular UI](../../UI/Angular/Theme-Configurations.md) - -Note: You should remove the old theme styles from "angular.json" if you are switching from "ThemeBasic" or "Lepton." -Look at the [Theme Configurations](../../UI/Angular/Theme-Configurations) list of styles. Depending on your theme, you can alter your styles in angular.json. - -- Finally, remove `ThemeBasicModule` from `app.module.ts`, and import the related modules in `app.module.ts` - -```js -import { ThemeLeptonXModule } from "@abp/ng.theme.lepton-x"; -import { SideMenuLayoutModule } from "@abp/ng.theme.lepton-x/layouts"; - -@NgModule({ - imports: [ - // ... - - // do not forget to remove ThemeBasicModule or other old theme module - // ThemeBasicModule.forRoot(), - ThemeLeptonXModule.forRoot(), - SideMenuLayoutModule.forRoot(), - ], - // ... -}) -export class AppModule {} -``` - -Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well: - -```js -import { AccountLayoutModule } from "@abp/ng.theme.lepton-x/account"; - -@NgModule({ - // ... - imports: [ - // ... - AccountLayoutModule.forRoot(), - // ... - ], - // ... -}) -export class AppModule {} -``` - -To change the logos and brand color of `LeptonX`, simply add the following CSS to the `styles.scss` - -```css -:root { - --lpx-logo: url("/assets/images/logo.png"); - --lpx-logo-icon: url("/assets/images/logo-icon.png"); - --lpx-brand: #edae53; -} -``` - -- `--lpx-logo` is used to place the logo in the menu. -- `--lpx-logo-icon` is a square icon used when the menu is collapsed. -- `--lpx-brand` is a color used throughout the application, especially on active elements. - -### Server Side - -In order to migrate to LeptonX on your server side projects (Host and/or AuthServer projects), please follow the [Server Side Migration](AspNetCore.md) document. - -## Customization - -### Layouts - -The Angular version of LeptonX Lite provides **layout components** for your **user interface** on [ABP Framework Theming](https://docs.abp.io/en/abp/latest/UI/Angular/Theming). You can use the layouts to **organize your user interface**. You can replace the **layout components** and some parts of the **layout components** with the [ABP replaceable component system](https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement). - -The main responsibility of a theme is to **provide** the layouts. There are **three pre-defined layouts that must be implemented by all the themes:** - -- **ApplicationLayoutComponent:** The **default** layout which is used by the **main** application pages. -- **AccountLayoutComponent:** Mostly used by the **account module** for **login**, **register**, **forgot password**... pages. -- **EmptyLayoutComponent:** The **Minimal** layout that **has no layout components** at all. - -The **Layout components** and all the replacable components are predefined in `eThemeLeptonXComponents` as enum. - -### How to replace a component - -```js -import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService -import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum -import { eThemeLeptonXComponents } from '@abp/ng.theme.lepton-x'; // imported eThemeLeptonXComponents enum - -//... - -@Component(/* component metadata */) -export class AppComponent { - constructor( - private replaceableComponents: ReplaceableComponentsService, // injected the service - ) { - this.replaceableComponents.add({ - component: YourNewApplicationLayoutComponent, - key: eThemeLeptonXComponents.ApplicationLayout, - }); - } -} -``` - -See the [Component Replacement](https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) documentation for more information on how to replace components. - -### Brand Component - -The **brand component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**. You can change the logo via css but if you want to change logo component, the key is `eThemeLeptonXComponents.Logo` - -```js -///... -this.replaceableComponents.add({ - component: YourNewLogoComponent, - key: eThemeLeptonXComponents.Logo, -}); -///... -``` - -![Brand component](../../images/leptonxlite-brand-component.png) - -## Breadcrumb Component - -On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**. - -```js -///... -this.replaceableComponents.add({ - component: YourNewSidebarComponent, - key: eThemeLeptonXComponents.Breadcrumb, -}); -///... -``` - -![Breadcrumb component](../../images/leptonxlite-breadcrumb-component.png) - -## Navbar Component - -Sidebar menus have been used as a **directory for Related Pages** to a **Service** offering, **Navigation** items to a **specific service** or topic and even just as **Links** the user may be interested in. - -```js -///... -this.replaceableComponents.add({ - component: YourNewSidebarComponent, - key: eThemeLeptonXComponents.Navbar, -}); -///... -``` - -![Sidebar menu component](../../images/leptonxlite-sidebar-menu-component.png) - -## Page Alerts Component - -Provides contextual **feedback messages** for typical user actions with a handful of **available** and **flexible** **alert messages**. Alerts are available for any length of text, as well as an **optional dismiss button**. - -![Page alerts component](../../images/leptonxlite-page-alerts-component.png) - -```js -///... -this.replaceableComponents.add({ - component: YourNewPageAlertContainerComponent, - key: eThemeLeptonXComponents.PageAlertContainer, -}); -///... -``` - -## Toolbar Component - -![Breadcrumb component](../../images/leptonxlite-toolbar-component.png) - -Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**. - -```js -///... -this.replaceableComponents.add({ - component: YourNewNavItemsComponent, - key: eThemeLeptonXComponents.NavItems, -}); -///... -``` - -## Toolbar Items - -There are two parts to the toolbar. The first is Language-Switch. The second is the User-Profile element. You can swap out each of these parts individually. - -## Language Switch Component - -Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** **without trying to locate it across the website.** - -![Language switch component](../../images/leptonxlite-language-switch-component.png) - -```js -///... -this.replaceableComponents.add({ - component: YourNewLanguagesComponent, - key: eThemeLeptonXComponents.Languages, -}); -///... -``` - -## User Menu Component - -The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc. - -![User menu component](../../images/leptonxlite-user-menu-component.png) - -```js -///... -this.replaceableComponents.add({ - component: YourNewCurrentUserComponent, - key: eThemeLeptonXComponents.CurrentUser, -}); -///... -``` - -Note: The language selection component in the Volo app is not replaceable. It is part of the settings menu. - -## Mobile Navbar Component - -The **mobile navbar component** is used to display the **navbar menu on mobile devices**. The mobile navbar component is a **dropdown menu** that contains language selection and user menu. - -![Mobile user menu component](../../images/leptonxlite-mobile-user-menu-component.png) - -```js -///... -this.replaceableComponents.add({ - component: YourNewMobileNavbarComponent, - key: eThemeLeptonXComponents.MobileNavbar, -}); -///... -``` - -## Mobile Navbar Items. - -There are two parts of the mobile navbar. The mobile navbar has Language-Switch and User-Profile. You can swap out each of these parts individually. - -The Mobile language-Selection component key is `eThemeLeptonXComponents.MobileLanguageSelection`. - -The Mobile User-Profile component key is `eThemeLeptonXComponents.MobileUserProfile`. - -## Footer Component - -![Angular Footer Component](../../images/angular-footer.png) - -The Footer is the section of content at the very bottom of the site. This section of the content can be modified. -Inject **FooterLinksService** and use the **setFooterInfo** method of **FooterLinksService** -to assign path or link and description. -**descUrl** and **footerLinks** are nullable. Constant **footerLinks** are on the right side of footer. - -```js -///... - -const footerLinks = [ - { - link: "/components/bootstrap/badge", - text: "Manage Your Profile", - }, - { - link: "/components/bootstrap/border", - text: "My Security Logs", - }, -]; - -const footerInfo: FooterNav = { - desc: "Home", - descUrl: "/components/home", - footerLinks: footerLinks, -}; - -this.footerLinksService.setFooterInfo(footerInfo); - -///... -``` - -If you want to change the footer component, the key is `eThemeLeptonXComponents.Footer` - -```js -///... -this.replaceableComponents.add({ - component: YourNewFooterComponent, - key: eThemeLeptonXComponents.Footer, -}); -///... -``` diff --git a/docs/en/Themes/LeptonXLite/AspNetCore.md b/docs/en/Themes/LeptonXLite/AspNetCore.md deleted file mode 100644 index 8bdf85704f..0000000000 --- a/docs/en/Themes/LeptonXLite/AspNetCore.md +++ /dev/null @@ -1,221 +0,0 @@ -# LeptonX Lite MVC UI -LeptonX Lite has implementation for the ABP Framework Razor Pages. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). - -> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). - -> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. - -## Installation - -This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below: - -- Add the **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application. - -```bash -dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite --prerelease -``` - -- Remove the **Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic** reference from the project since it's not necessary after switching to LeptonX Lite. - -- Make sure the old theme is removed and LeptonX is added in your Module class. - -```diff -[DependsOn( - // Remove the BasicTheme module from DependsOn attribute -- typeof(AbpAspNetCoreMvcUiBasicThemeModule), - - // Add the LeptonX Lite module to DependsOn attribute -+ typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), -)] -``` - -- Replace `BasicThemeBundles` with `LeptonXLiteThemeBundles` in `AbpBundlingOptions`: - -```diff -Configure(options => -{ - options.StyleBundles.Configure( - // Remove the following line -- BasicThemeBundles.Styles.Global, - // Add the following line instead -+ LeptonXLiteThemeBundles.Styles.Global, - bundle => - { - bundle.AddFiles("/global-styles.css"); - } - ); -}); -``` - -## Customization - -### Layouts - -LeptonX Lite MVC provides **layouts** for your **user interface** based [ABP Framework Theming](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming). You can use **layouts** to **organize your user interface**. - -The main responsibility of a theme is to **provide** the layouts. There are **three pre-defined layouts that must be implemented by all the themes:** - -* **Application:** The **default** layout which is used by the **main** application pages. - -* **Account:** Mostly used by the **account module** for **login**, **register**, **forgot password**... pages. - -* **Empty:** The **Minimal** layout that **has no layout components** at all. - -**Layout names** are **constants** defined in the `LeptonXLiteTheme` class in the **MVC** project **root**. - -> The layout pages define under the `Themes/LeptonXLite/Layouts` folder and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -### Toolbars - -LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. - -- `LeptonXLiteToolbars.Main` -- `LeptonXLiteToolbars.MainMobile` - -```csharp -public class MyProjectNameMainToolbarContributor : IToolbarContributor -{ - public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) - { - if (context.Toolbar.Name == LeptonXLiteToolbars.Main) - { - context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); - } - - if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) - { - context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); - } - } -} -``` - -# LeptonX Lite MVC Components - -ABP **helps** you make **highly customizable UI**. You can easily **customize** your themes to fit your needs. **The Virtual File System** makes it possible to **manage files** that **do not physically** exist on the **file system** (disk). It's mainly used to embed **(js, css, image..)** files into assemblies and **use them like** physical files at runtime. An application (or another module) can **override** a **virtual file of a module** just like placing a file with the **same name** and **extension** into the **same folder** of the **virtual file**. - -LeptonX Lite is built on the [Abp Framework](https://abp.io/), so you can **easily** customize your Asp.Net Core MVC user interface by following [Abp Mvc UI Customization](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface). - -## Branding Component - -The **branding component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**. - -![Brand component](../../images/leptonxlite-brand-component.png) - -### How to override the Branding Component in LeptonX Lite MVC - -* The **branding component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Brand/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **branding component (C# file)** is defined in the `Themes/LeptonXLite/Components/Brand/MainNavbarBrandViewComponent.cs` file and you can **override it** by creating a file with the **same name** and under the **same folder**. - -### How to override the favicon in LeptonX Lite MVC - -You can add a new favicon to the `~/wwwroot/favicon.svg` and `~/wwwroot/favicon.ico` paths to override the current favicon. - -## Breadcrumb Component - -On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**. - -![Breadcrumb component](../../images/leptonxlite-breadcrumb-component.png) - -### How to override the Breadcrumb Component in LeptonX Lite MVC - -* The **breadcrumb component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **breadcrumb component (C# file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/BreadcrumbsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -## Sidebar Menu Component - -Sidebar menus have been used as **a directory for Related Pages** to a **Service** offering, **Navigation** items to a **specific service** or topic and even just as **Links** the user may be interested in. - -![Sidebar menu component](../../images/leptonxlite-sidebar-menu-component.png) - -### How to override the Sidebar Menu Component in LeptonX Lite MVC - -* **Sidebar menu page (.cshtml)** is defined in the `Themes/LeptonXLite/Components/Menu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* If you want to **override the menu component (C#)** you can override the `Themes/LeptonXLite/Components/Menu/MainMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -> The **sidebar menu** renders menu items **dynamically**. The **menu item** is a **partial view** and is defined in the `Themes/LeptonXLite/Components/Menu/_MenuItem.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -## Page Alerts Component - -Provides contextual **feedback messages** for typical user actions with the handful of **available** and **flexible** **alert messages**. Alerts are available for any length of text, as well as an **optional dismiss button**. - -![Page alerts component](../../images/leptonxlite-page-alerts-component.png) - -### How to override the Page Alerts Component in LeptonX Lite MVC - -* The **page alerts component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **page alerts component (C#)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/PageAlertsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -## Toolbar Component - -Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**. - -### How to override the Toolbar Component in LeptonX Lite MVC - -* The **toolbar component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Toolbar/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **toolbar component (C#)** is defined in the `Themes/LeptonXLite/Components/Toolbar/ToolbarViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -## Toolbar Item Component - -The toolbar item is a **single item** that **contains** a **link**, an **icon**, a **label** etc.. - -### How to override the Toolbar Item Component in LeptonX Lite MVC - -* The **toolbar item component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **toolbar item component (C#)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/ToolbarItemsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -You can find the toolbar components below: - -## Language Switch Component - -Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** **without trying to locate it across the website.** - -![Language switch component](../../images/leptonxlite-language-switch-component.png) - -### How to override the Language Switch Component in LeptonX Lite MVC - -* The **language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/LanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -## Mobile Language Switch Component - -The **mobile** **language switch component** is used to switch the language of the website **on mobile devices**. The mobile language switch component is a **dropdown menu** that **contains all the languages** of the website. - -![Mobil language switch component](../../images/leptonxlite-mobile-language-switch-component.png) - -### How to override the Mobile Language Switch Component in LeptonX Lite MVC - -* The **mobile language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **mobile language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/MobileLanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -## User Menu Component - -The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc. - -![User menu component](../../images/leptonxlite-user-menu-component.png) - -### How to override the User Menu Component in LeptonX Lite MVC - -* The **user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/UserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **user menu component (C#)** is defined in the `Themes/LeptonXLite/Components/UserMenu/UserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -## Mobile User Menu Component - -The **mobile user menu component** is used to display the **user menu on mobile devices**. The mobile user menu component is a **dropdown menu** that contains all the **options** of the **user menu**. - -![Mobile user menu component](../../images/leptonxlite-mobile-user-menu-component.png) - -### How to override the Mobile User Menu Component in LeptonX Lite MVC - -* The **mobile user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileUserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. - -* The **mobile user menu component (C#)** is defined in the `Themes/LeptonLite/Components/MobileUserMenu/MobileUserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. diff --git a/docs/en/Themes/LeptonXLite/Blazor.md b/docs/en/Themes/LeptonXLite/Blazor.md deleted file mode 100644 index d2565bea40..0000000000 --- a/docs/en/Themes/LeptonXLite/Blazor.md +++ /dev/null @@ -1,528 +0,0 @@ -# LeptonX Lite Blazor UI - -````json -//[doc-params] -{ - "UI": ["Blazor", "BlazorServer"] -} -```` - -LeptonX Lite has implementation for the ABP Framework Blazor WebAssembly & Blazor Server. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). - -> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). - -> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. - -## Installation - -This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below: - -{{if UI == "Blazor"}} -- Complete the [MVC Razor Pages Installation](AspNetCore.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and if Auth Server is separated, install to the **OpenIddict**_. - - -- Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme** package to your **Blazor WebAssembly** application with the following command: - - ```bash - dotnet add package Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme --prerelease - ``` - -- Remove **Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme** reference from the project since it's not necessary after switching to LeptonX Lite. - -- Remove the old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to the **DependsOn** attribute. - -```diff -[DependsOn( - // Remove BasicTheme module from DependsOn attribute -- typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule), - - // Add LeptonX Lite module to DependsOn attribute -+ typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule), -)] -``` - -- Change startup App component with the LeptonX one. - -```csharp -// Make sure the 'App' comes from 'Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite' namespace. -builder.RootComponents.Add("#ApplicationContainer"); -``` - -- Run the `abp bundle` command in your **Blazor** application folder. - -{{end}} - - -{{if UI == "BlazorServer"}} - -- Complete the [MVC Razor Pages Installation](AspNetCore.md#installation) first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and **AuthServer**_. - -- Add **Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme** package to your **Blazor server** application with the following command: - - ```bash - dotnet add package Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme --prerelease - ``` - -- Remove **Volo.Abp.AspNetCore.Components.Server.BasicTheme** reference from the project since it's not necessary after switching to LeptonX Lite. - - -- Remove old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsServerLeptonXLiteThemeModule** type to the **DependsOn** attribute. - - ```diff - [DependsOn( - // Remove BasicTheme module from DependsOn attribute - - typeof(AbpAspNetCoreComponentsServerBasicThemeModule), - - // Add LeptonX Lite module to DependsOn attribute - + typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule) - )] - ``` - -- Replace `BlazorBasicThemeBundles` with `BlazorLeptonXLiteThemeBundles` in `AbpBundlingOptions`: - ```diff - options.StyleBundles.Configure( - // Remove following line - - BlazorBasicThemeBundles.Styles.Global, - // Add following line instead - + BlazorLeptonXLiteThemeBundles.Styles.Global, - bundle => - { - bundle.AddFiles("/blazor-global-styles.css"); - //You can remove the following line if you don't use Blazor CSS isolation for components - bundle.AddFiles("/MyProjectName.Blazor.styles.css"); - }); - ``` - -- Update `_Host.cshtml` file. _(located under **Pages** folder by default.)_ - - - Add following usings to Locate **App** and **BlazorLeptonXLiteThemeBundles** classes. - ```csharp - @using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite - @using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling - ``` - - Then replace script & style bundles as following: - ```diff - // Remove following line - - - // Add following line instead - + - ``` - - ```diff - // Remove following line - - - // Add following line instead - + - ``` - -{{end}} - - ---- - -## Customization - -### Layout - -* Create a razor page, like `MyMainLayout.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; -@using Volo.Abp.DependencyInjection - -@inherits MainLayout -@attribute [ExposeServices(typeof(MainLayout))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Main Layout"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMainLayout.razor.cs`, in your blazor application as shown below: - -```csharp -[ExposeServices(typeof(MainLayout))] -[Dependency(ReplaceServices = true)] -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - public partial class MyMainLayout - { - public string Name = "My Main Layout"; - } -} -``` - -> Don't forget to remove the repeated attributes from the razor page! -> Don't forget to remove the `@code` section from the razor page! - -### Toolbars - -LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. - -- `LeptonXLiteToolbars.Main` -- `LeptonXLiteToolbars.MainMobile` - -```csharp -public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) -{ - if (context.Toolbar.Name == LeptonXLiteToolbars.Main) - { - context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); - } - - if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) - { - context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); - } - - return Task.CompletedTask; -} -``` - -{{if UI == "BlazorServer"}} - -> _You can visit the [Toolbars Documentation](https://docs.abp.io/en/abp/latest/UI/Blazor/Toolbars) for better understanding._ - -{{end}} - -## Components - -LeptonX Blazor is built on the basis of components. You can use the components in your application as you wish, or you can customize the components by overriding them. If you want to override a component please follow the steps. - -### Branding Component - -The **branding component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**. - -![](../../images/leptonxlite-brand-component.png) - -#### How to Override Branding Component - -* Create a razor page, like `MyBrandingComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; -@using Volo.Abp.DependencyInjection - -@inherits Branding -@attribute [ExposeServices(typeof(Branding))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Branding Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyBrandingComponent.razor.cs`, in your blazor application as shown below: - -```csharp -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - public partial class MyBrandingComponent - { - public string Name = "My Branding Component"; - } -} -``` - -### How to override the favicon - -Startup templates contain `favicon.ico` files under the `wwwroot` folder of the Blazor application. You can change this file to override the current favicon. - -### Breadcrumb Component - -On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**. - -![](../../images/leptonxlite-breadcrumb-component.png) - -#### How to Override the BreadCrumb Component - -* Create a razor page, like `MyBreadcrumbsComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; -@using Volo.Abp.DependencyInjection - -@inherits Breadcrumbs -@attribute [ExposeServices(typeof(Breadcrumbs))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Breadcrumbs Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyBreadcrumbsComponent.razor.cs`, in your blazor application as shown below: -* -```csharp -using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; -using Volo.Abp.DependencyInjection; - -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - [ExposeServices(typeof(Breadcrumbs))] - [Dependency(ReplaceServices = true)] - public partial class MyBreadcrumbsComponent - { - public string Name = "My Breadcrumbs Component"; - } -} -``` - -### Main Menu Component - -Sidebar menus have been used as **a directory for Related Pages** for a **Service** offering, **Navigation** items for a **specific service** or topic and even just as **Links** the user may be interested in. - -![](../../images/leptonxlite-sidebar-menu-component.png) - -#### How to Override the Main Menu Component - -* Create a razor page, like `MyMainMenuComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Navigation; -@using Volo.Abp.DependencyInjection - -@inherits MainMenu -@attribute [ExposeServices(typeof(MainMenu))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Main Menu Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMainMenu.razor.cs`, in your blazor application as shown below: - -```csharp -using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Navigation; -using Volo.Abp.DependencyInjection; - -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - [ExposeServices(typeof(MainMenu))] - [Dependency(ReplaceServices = true)] - public partial class MainMenu - { - public string Name = "My Main Menu Component"; - } -} -``` - -> The **main menu** renders the menu items **dynamically**. The **menu item** is a **razor component** named `MainMenuItem.razor.cs` in the same namespace with **main menu** and you can **override it** like the main menu. - -### Toolbar Items Component - -Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**. - -#### How to Override the Toolbar Items Component - -* Create a razor page, like `MyToolbarItemsComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; -@using Volo.Abp.DependencyInjection - -@inherits ToolbarItemsComponent -@attribute [ExposeServices(typeof(ToolbarItemsComponent))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Toolbar Items Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyToolbarItemsComponent.razor.cs`, in your blazor application as shown below: - -```csharp -using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; -using Volo.Abp.DependencyInjection; - -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - [ExposeServices(typeof(ToolbarItemsComponent))] - [Dependency(ReplaceServices = true)] - public partial class MyToolbarItemsComponent - { - public string Name = "My Toolbar Items Component"; - } -} -``` - -### Language Switch Component - -Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** **without trying to locate it across the website.** - -![](../../images/leptonxlite-language-switch-component.png) - -#### How to Override the Language Switch Component - -* Create a razor page, like `MyLanguageSwitchComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -@using Volo.Abp.DependencyInjection - -@inherits LanguageSwitchComponent -@attribute [ExposeServices(typeof(LanguageSwitchComponent))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Language Switch Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyLanguageSwitchComponent.razor.cs`, in your blazor application as shown below: - -```csharp -using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -using Volo.Abp.DependencyInjection; - -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - [ExposeServices(typeof(LanguageSwitchComponent))] - [Dependency(ReplaceServices = true)] - public partial class MyLanguageSwitchComponent - { - public string Name = "My Language Switch Component"; - } -} -``` - -### Mobile Language Switch Component - -The **mobile** **language switch component** is used to switch the language of the website **on mobile devices**. The mobile language switch component is a **dropdown menu** that **contains all the languages** of the website. - -![](../../images/leptonxlite-mobile-language-switch-component.png) - -#### How to Override the Mobile Language Switch Component - -* Create a razor page, like `MyMobilLanguageSwitchComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -@using Volo.Abp.DependencyInjection - -@inherits MobilLanguageSwitchComponent -@attribute [ExposeServices(typeof(MobilLanguageSwitchComponent))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Mobile Language Switch Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMobilLanguageSwitchComponent.razor.cs`, in your blazor application as shown below: - -```csharp -using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -using Volo.Abp.DependencyInjection; - -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - [ExposeServices(typeof(MobilLanguageSwitchComponent))] - [Dependency(ReplaceServices = true)] - public partial class MyMobilLanguageSwitchComponent - { - public string Name = "My Mobile Language Switch Component"; - } -} -``` - -### User Menu Component - -The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc. - -![](../../images/leptonxlite-user-menu-component.png) - -#### How to Override the User Menu Component - -* Create a razor page, like `MyUserMenuComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -@using Volo.Abp.DependencyInjection - -@inherits MobilLanguageSwitchComponent -@attribute [ExposeServices(typeof(MobilLanguageSwitchComponent))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My User Menu Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyUserMenuComponent.razor.cs`, in your blazor application as shown below: - -```csharp -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -using Volo.Abp.DependencyInjection; - -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - [ExposeServices(typeof(UserMenuComponent))] - [Dependency(ReplaceServices = true)] - public partial class MyUserMenuComponent - { - public string Name = "My User Menu Component"; - } -} -``` - -### Mobile User Menu Component - -The **mobile user menu component** is used to display the **user menu on mobile devices**. The mobile user menu component is a **dropdown menu** that contains all the **options** of the **user menu**. - -![](../../images/leptonxlite-mobile-user-menu-component.png) - -#### How to override the Mobile User Menu Component - -* Create a razor page, like `MyMobileUserMenuComponent.razor`, in your blazor application as shown below: - -```html -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -@using Volo.Abp.DependencyInjection - -@inherits MobilUserMenuComponent -@attribute [ExposeServices(typeof(MobilUserMenuComponent))] -@attribute [Dependency(ReplaceServices = true)] - -@Name - -@code { - string Name = "My Mobile User Menu Component"; -} -``` - -* If you prefer to use a code-behind file for the C# code of your component, create a razor component, like `MyMobileUserMenuComponent.razor.cs`, in your blazor application as shown below: - -```csharp -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Themes.LeptonXLite.Toolbar; -using Volo.Abp.DependencyInjection; - -namespace LeptonXLite.DemoApp.Blazor.MyComponents -{ - [ExposeServices(typeof(MobileUserMenuComponent))] - [Dependency(ReplaceServices = true)] - public partial class MyMobileUserMenuComponent - { - public string Name = "My Mobile User Menu Component"; - } -} -``` diff --git a/docs/en/Timing.md b/docs/en/Timing.md deleted file mode 100644 index 9b3eb0cca9..0000000000 --- a/docs/en/Timing.md +++ /dev/null @@ -1,113 +0,0 @@ -# Timing - -Working with times & [time zones](https://en.wikipedia.org/wiki/Time_zone) is always tricky, especially if you need to build a **global system** that is used by users in **different time zones**. - -ABP provides a basic infrastructure to make it easy and handle automatically wherever possible. This document covers the ABP Framework services and systems related to time and time zones. - -> If you are creating a local application that runs in a single time zone region, you may not need all these systems. But even in this case, it is suggested to use the `IClock` service introduced in this document. - -## IClock - -`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. The things especially gets complicated when you save this value to a database and read later, or send it to a client in a **different time zone**. - -One solution to this problem is always use `DateTime.UtcNow` and assume all `DateTime` objects as UTC time. In this way, you can convert it to the time zone of the target client when needed. - -`IClock` provides an abstraction while getting the current time, so you can control the kind of the date time (UTC or local) in a single point in your application. - -**Example: Getting the current time** - -````csharp -using Volo.Abp.DependencyInjection; -using Volo.Abp.Timing; - -namespace AbpDemo -{ - public class MyService : ITransientDependency - { - private readonly IClock _clock; - - public MyService(IClock clock) - { - _clock = clock; - } - - public void Foo() - { - //Get the current time! - var now = _clock.Now; - } - } -} -```` - -* Inject the `IClock` service when you need to get the current time. Common base classes (like ApplicationService) already injects it and provides as a base property - so, you can directly use as `Clock`. -* Use the `Now` property to get the current time. - -> Most of the times, `IClock` is the only service you need to know and use in your application. - -### Clock Options - -`AbpClockOptions` is the [options](Options.md) class that used to set the clock kind. - -**Example: Use UTC Clock** - -````csharp -Configure(options => -{ - options.Kind = DateTimeKind.Utc; -}); -```` - -Write this inside the `ConfigureServices` method of your [module](Module-Development-Basics.md). - -> Default `Kind` is `Unspecified`, that actually make the Clock as it doesn't exists at all. Either make it `Utc` or `Local` if you want to get benefit of the Clock system. - -### DateTime Normalization - -Other important function of the `IClock` is to normalize `DateTime` objects. - -**Example usage:** - -````csharp -DateTime dateTime = ...; //Get from somewhere -var normalizedDateTime = Clock.Normalize(dateTime) -```` - -`Normalize` method works as described below: - -* Converts the given `DateTime` to the UTC (by using the `DateTime.ToUniversalTime()` method) if current Clock is UTC and given `DateTime` is local. -* Converts the given `DateTime` to the local (by using the `DateTime.ToLocalTime()` method) if current Clock is local and given `DateTime` is UTC. -* Sets `Kind` of the given `DateTime` (using the `DateTime.SpecifyKind(...)` method) to the `Kind` of the current Clock if given `DateTime`'s `Kind` is `Unspecified`. - -`Normalize` method is used by the ABP Framework when the it gets a `DateTime` that is not created by `IClock.Now` and may not be compatible with the current Clock type. Examples; - -* `DateTime` type binding in the ASP.NET Core MVC model binding. -* Saving data to and reading data from database via [Entity Framework Core](Entity-Framework-Core.md). -* Working with `DateTime` objects on [JSON deserialization](Json-Serialization.md). - -#### DisableDateTimeNormalization Attribute - -`DisableDateTimeNormalization` attribute can be used to disable the normalization operation for desired classes or properties. - -### Other IClock Properties - -In addition to the `Now`, `IClock` service has the following properties: - -* `Kind`: Returns a `DateTimeKind` for the currently used clock type (`DateTimeKind.Utc`, `DateTimeKind.Local` or `DateTimeKind.Unspecified`). -* `SupportsMultipleTimezone`: Returns `true` if currently used clock is UTC. - -## Time Zones - -This section covers the ABP Framework infrastructure related to managing time zones. - -### TimeZone Setting - -ABP Framework defines **a setting**, named `Abp.Timing.TimeZone`, that can be used to set and get the time zone for a user, [tenant](Multi-Tenancy.md) or globally for the application. The default value is `UTC`. - -See the [setting documentation](Settings.md) to learn more about the setting system. - -### ITimezoneProvider - -`ITimezoneProvider` is a service to simple convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get list of these time zones and get a `TimeZoneInfo` with a given name. - -It has been implemented using the [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) library. diff --git a/docs/en/Tutorials/Angular/Part-I.md b/docs/en/Tutorials/Angular/Part-I.md deleted file mode 100644 index 2867a3159f..0000000000 --- a/docs/en/Tutorials/Angular/Part-I.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tutorials - -## Application Development - -* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) -* [With Angular UI](../Part-1?UI=NG) - - \ No newline at end of file diff --git a/docs/en/Tutorials/Angular/Part-II.md b/docs/en/Tutorials/Angular/Part-II.md deleted file mode 100644 index 2867a3159f..0000000000 --- a/docs/en/Tutorials/Angular/Part-II.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tutorials - -## Application Development - -* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) -* [With Angular UI](../Part-1?UI=NG) - - \ No newline at end of file diff --git a/docs/en/Tutorials/Angular/Part-III.md b/docs/en/Tutorials/Angular/Part-III.md deleted file mode 100644 index 2867a3159f..0000000000 --- a/docs/en/Tutorials/Angular/Part-III.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tutorials - -## Application Development - -* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) -* [With Angular UI](../Part-1?UI=NG) - - \ No newline at end of file diff --git a/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md deleted file mode 100644 index 2867a3159f..0000000000 --- a/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tutorials - -## Application Development - -* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) -* [With Angular UI](../Part-1?UI=NG) - - \ No newline at end of file diff --git a/docs/en/Tutorials/AspNetCore-Mvc/Part-II.md b/docs/en/Tutorials/AspNetCore-Mvc/Part-II.md deleted file mode 100644 index 2867a3159f..0000000000 --- a/docs/en/Tutorials/AspNetCore-Mvc/Part-II.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tutorials - -## Application Development - -* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) -* [With Angular UI](../Part-1?UI=NG) - - \ No newline at end of file diff --git a/docs/en/Tutorials/AspNetCore-Mvc/Part-III.md b/docs/en/Tutorials/AspNetCore-Mvc/Part-III.md deleted file mode 100644 index 2867a3159f..0000000000 --- a/docs/en/Tutorials/AspNetCore-Mvc/Part-III.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tutorials - -## Application Development - -* [With ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) -* [With Angular UI](../Part-1?UI=NG) - - \ No newline at end of file diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md deleted file mode 100644 index b98395d6ac..0000000000 --- a/docs/en/Tutorials/Part-1.md +++ /dev/null @@ -1,523 +0,0 @@ -# Web Application Development Tutorial - Part 1: Creating the Server Side -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` -````json -//[doc-nav] -{ - "Next": { - "Name": "The Book List Page", - "Path": "Tutorials/Part-2" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the database provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts: - -- **Part 1: Creating the server side (this part)** -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -> After downloading the source code, you might need to run some commands before running the application. See the _After Creating the Solution_ section below for more information. - -{{if UI == "MVC" && DB == "EF"}} - -### Video Tutorial - -This part is also recorded as a video tutorial and **published on YouTube**. - -{{end}} - -## Creating the Solution - -Before starting the development, create a new solution named `Acme.BookStore` and run it by following the [getting started tutorial](../Getting-Started.md). - -## After Creating the Solution - -### Installing the Client-Side Packages - -[ABP CLI](../CLI.md) runs the `abp install-libs` command behind the scenes to install the required NPM packages for your solution while creating the application. - -However, sometimes this command might need to be manually run. For example, you need to run this command, if you have cloned the application, or the resources from *node_modules* folder didn't copy to *wwwroot/libs* folder, or if you have added a new client-side package dependency to your solution. - -For such cases, run the `abp install-libs` command on the root directory of your solution to install all required NPM packages: - -```bash -abp install-libs -``` - -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. - -{{if UI=="Blazor" || UI=="BlazorServer"}} - -### Bundling and Minification - -`abp bundle` command offers bundling and minification support for client-side resources (JavaScript and CSS files) for Blazor projects. This command automatically run when you create a new solution with the [ABP CLI](../CLI.md). - -However, sometimes you might need to run this command manually. To update script & style references without worrying about dependencies, ordering, etc. in a project, you can run this command in the directory of your `Blazor.Client` project: - -```bash -abp bundle -``` - -> For more details about managing style and script references in Blazor or MAUI Blazor apps, see [Managing Global Scripts & Styles](../UI/Blazor/Global-Scripts-Styles.md). - -{{end}} - -## Create the Book Entity - -**Domain layer** in the startup template is separated into two projects: - -- `Acme.BookStore.Domain` contains your [entities](../Entities.md), [domain services](../Domain-Services.md) and other core domain objects. -- `Acme.BookStore.Domain.Shared` contains `constants`, `enums` or other domain related objects that can be shared with clients. - -So, define your entities in the domain layer (`Acme.BookStore.Domain` project) of the solution. - -The main entity of the application is the `Book`. Create a `Books` folder (namespace) in the `Acme.BookStore.Domain` project and add a `Book` class inside it: - -````csharp -using System; -using Volo.Abp.Domain.Entities.Auditing; - -namespace Acme.BookStore.Books; - -public class Book : AuditedAggregateRoot -{ - public string Name { get; set; } - - public BookType Type { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } -} -```` - -* ABP Framework has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is a [Domain Driven Design](../Domain-Driven-Design.md) concept which can be thought as a root entity that is directly queried and worked on (see the [entities document](../Entities.md) for more). -* The `Book` entity inherits from the `AuditedAggregateRoot` which adds some base [auditing](../Audit-Logging.md) properties (like `CreationTime`, `CreatorId`, `LastModificationTime`...) on top of the `AggregateRoot` class. ABP automatically manages these properties for you. -* `Guid` is the **primary key type** of the `Book` entity. - -> This tutorial leaves the entity properties with **public get/set** for the sake of simplicity. See the [entities document](../Entities.md) if you want to learn more about DDD best practices. - -### BookType Enum - -The `Book` entity uses the `BookType` enum. Create a `Books` folder (namespace) in the `Acme.BookStore.Domain.Shared` project and add a `BookType` inside it: - -````csharp -namespace Acme.BookStore.Books; - -public enum BookType -{ - Undefined, - Adventure, - Biography, - Dystopia, - Fantastic, - Horror, - Science, - ScienceFiction, - Poetry -} -```` - -The final folder/file structure should be as shown below: - -![bookstore-book-and-booktype](images/bookstore-book-and-booktype.png) - -### Add the Book Entity to the DbContext - -{{if DB == "EF"}} - -EF Core requires that you relate the entities with your `DbContext`. The easiest way to do so is adding a `DbSet` property to the `BookStoreDbContext` class in the `Acme.BookStore.EntityFrameworkCore` project, as shown below: - -````csharp -public class BookStoreDbContext : AbpDbContext -{ - public DbSet Books { get; set; } - //... -} -```` - -{{end}} - -{{if DB == "Mongo"}} - -Add a `IMongoCollection Books` property to the `BookStoreMongoDbContext` inside the `Acme.BookStore.MongoDB` project: - -```csharp -public class BookStoreMongoDbContext : AbpMongoDbContext -{ - public IMongoCollection Books => Collection(); - //... -} -``` - -{{end}} - -{{if DB == "EF"}} - -### Map the Book Entity to a Database Table - -Navigate to the `OnModelCreating` method in the `BookStoreDbContext` class and add the mapping code for the `Book` entity: - -````csharp -using Acme.BookStore.Books; -... - -namespace Acme.BookStore.EntityFrameworkCore; - -public class BookStoreDbContext : - AbpDbContext, - IIdentityDbContext, - ITenantManagementDbContext -{ - ... - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.ConfigurePermissionManagement(); - ... - - /* Configure your own tables/entities inside here */ - - builder.Entity(b => - { - b.ToTable(BookStoreConsts.DbTablePrefix + "Books", - BookStoreConsts.DbSchema); - b.ConfigureByConvention(); //auto configure for the base class props - b.Property(x => x.Name).IsRequired().HasMaxLength(128); - }); - } -} -```` - -* `BookStoreConsts` has constant values for the schema and table prefixes for your tables. You don't have to use it, but it's suggested to control the table prefixes in a single point. -* The `ConfigureByConvention()` method gracefully configures/maps the inherited properties. Always use it for all your entities. - -### Add Database Migration - -The startup solution is configured to use [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. - -Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore` project and type the following command: - -```bash -dotnet ef migrations add Created_Book_Entity -``` - -This will add a new migration class to the project: - -![bookstore-efcore-migration](./images/bookstore-efcore-migration.png) - -> If you are using Visual Studio, you may want to use the `Add-Migration Created_Book_Entity` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that `Acme.BookStore.EntityFrameworkCore` is the startup project in Visual Studio and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. - -{{end}} - -### Add Sample Seed Data - -> It's good to have some initial data in the database before running the application. This section introduces the [Data Seeding](../Data-Seeding.md) system of the ABP framework. You can skip this section if you don't want to create the data seeding, but it is suggested to follow along and learn this useful ABP Framework feature. - -Create a class that implements the `IDataSeedContributor` interface in the `*.Domain` project by copying the following code: - -```csharp -using System; -using System.Threading.Tasks; -using Acme.BookStore.Books; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore; - -public class BookStoreDataSeederContributor - : IDataSeedContributor, ITransientDependency -{ - private readonly IRepository _bookRepository; - - public BookStoreDataSeederContributor(IRepository bookRepository) - { - _bookRepository = bookRepository; - } - - public async Task SeedAsync(DataSeedContext context) - { - if (await _bookRepository.GetCountAsync() <= 0) - { - await _bookRepository.InsertAsync( - new Book - { - Name = "1984", - Type = BookType.Dystopia, - PublishDate = new DateTime(1949, 6, 8), - Price = 19.84f - }, - autoSave: true - ); - - await _bookRepository.InsertAsync( - new Book - { - Name = "The Hitchhiker's Guide to the Galaxy", - Type = BookType.ScienceFiction, - PublishDate = new DateTime(1995, 9, 27), - Price = 42.0f - }, - autoSave: true - ); - } - } -} -``` - -* This code simply uses the `IRepository` (the default [repository](../Repositories.md)) to insert two books to the database in case there weren't any books in it. - -### Update the Database - -Run the `Acme.BookStore.DbMigrator` application to update the database: - -![bookstore-dbmigrator-on-solution](images/bookstore-dbmigrator-on-solution.png) - -`.DbMigrator` is a console application that can be run to **migrate the database schema** and **seed the data** on **development** and **production** environments. - -## Create the Application Service - -The application layer is separated into two projects: - -* `Acme.BookStore.Application.Contracts` contains your [DTO](../Data-Transfer-Objects.md)s and [application service](../Application-Services.md) interfaces. -* `Acme.BookStore.Application` contains the implementations of your application services. - -In this section, you will create an application service to get, create, update and delete books using the `CrudAppService` base class of the ABP Framework. - -### BookDto - -`CrudAppService` base class requires to define the fundamental DTOs for the entity. Create a `Books` folder (namespace) in the `Acme.BookStore.Application.Contracts` project and add a `BookDto` class inside it: - -````csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore.Books; - -public class BookDto : AuditedEntityDto -{ - public string Name { get; set; } - - public BookType Type { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } -} -```` - -* **DTO** classes are used to **transfer data** between the *presentation layer* and the *application layer*. See the [Data Transfer Objects document](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects) for more details. -* The `BookDto` is used to transfer the book data to the presentation layer in order to show the book information on the UI. -* The `BookDto` is derived from the `AuditedEntityDto` which has audit properties just like the `Book` entity defined above. - -It will be needed to map the `Book` entities to the `BookDto` objects while returning books to the presentation layer. [AutoMapper](https://automapper.org) library can automate this conversion when you define the proper mapping. The startup template comes with AutoMapper pre-configured. So, you can just define the mapping in the `BookStoreApplicationAutoMapperProfile` class in the `Acme.BookStore.Application` project: - -````csharp -using Acme.BookStore.Books; -using AutoMapper; - -namespace Acme.BookStore; - -public class BookStoreApplicationAutoMapperProfile : Profile -{ - public BookStoreApplicationAutoMapperProfile() - { - CreateMap(); - } -} -```` - -> See the [object to object mapping](../Object-To-Object-Mapping.md) document for details. - -### CreateUpdateBookDto - -Create a `CreateUpdateBookDto` class in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project: - -````csharp -using System; -using System.ComponentModel.DataAnnotations; - -namespace Acme.BookStore.Books; - -public class CreateUpdateBookDto -{ - [Required] - [StringLength(128)] - public string Name { get; set; } = string.Empty; - - [Required] - public BookType Type { get; set; } = BookType.Undefined; - - [Required] - [DataType(DataType.Date)] - public DateTime PublishDate { get; set; } = DateTime.Now; - - [Required] - public float Price { get; set; } -} -```` - -* This `DTO` class is used to get a book information from the user interface while creating or updating the book. -* It defines data annotation attributes (like `[Required]`) to define validations for the properties. `DTO`s are [automatically validated](https://docs.abp.io/en/abp/latest/Validation) by the ABP framework. - -As done to the `BookDto` above, we should define the mapping from the `CreateUpdateBookDto` object to the `Book` entity. The final class will be as shown below: - -````csharp -using Acme.BookStore.Books; -using AutoMapper; - -namespace Acme.BookStore; - -public class BookStoreApplicationAutoMapperProfile : Profile -{ - public BookStoreApplicationAutoMapperProfile() - { - CreateMap(); - CreateMap(); - } -} -```` - -### IBookAppService - -Next step is to define an interface for the application service. Create an `IBookAppService` interface in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project: - -````csharp -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Acme.BookStore.Books; - -public interface IBookAppService : - ICrudAppService< //Defines CRUD methods - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting - CreateUpdateBookDto> //Used to create/update a book -{ - -} -```` - -* Defining interfaces for the application services **are not required** by the framework. However, it's suggested as a best practice. -* `ICrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead, you could inherit from the empty `IApplicationService` interface and define your own methods manually (which will be done for the authors in the next parts). -* There are some variations of the `ICrudAppService` where you can use separated DTOs for each method (like using different DTOs for create and update). - -### BookAppService - -It is time to implement the `IBookAppService` interface. Create a new class, named `BookAppService` in the `Books` namespace (folder) of the `Acme.BookStore.Application` project: - -````csharp -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore.Books; - -public class BookAppService : - CrudAppService< - Book, //The Book entity - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting - CreateUpdateBookDto>, //Used to create/update a book - IBookAppService //implement the IBookAppService -{ - public BookAppService(IRepository repository) - : base(repository) - { - - } -} -```` - -* `BookAppService` is derived from `CrudAppService<...>` which implements all the CRUD (create, read, update, delete) methods defined by the `ICrudAppService`. -* `BookAppService` injects `IRepository` which is the default repository for the `Book` entity. ABP automatically creates default repositories for each aggregate root (or entity). See the [repository document](https://docs.abp.io/en/abp/latest/Repositories). -* `BookAppService` uses `IObjectMapper` service ([see](../Object-To-Object-Mapping.md)) to map the `Book` objects to the `BookDto` objects and `CreateUpdateBookDto` objects to the `Book` objects. The Startup template uses the [AutoMapper](http://automapper.org/) library as the object mapping provider. We have defined the mappings before, so it will work as expected. - -## Auto API Controllers - -In a typical ASP.NET Core application, you create **API Controllers** to expose the application services as **HTTP API** endpoints. This allows browsers or 3rd-party clients to call them over HTTP. - -ABP can [**automagically**](../API/Auto-API-Controllers.md) configure your application services as MVC API Controllers by convention. - -### Swagger UI - -The startup template is configured to run the [Swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application ({{if UI=="MVC"}}`Acme.BookStore.Web`{{else}}`Acme.BookStore.HttpApi.Host`{{end}}) by pressing `CTRL+F5` and navigate to `https://localhost:/swagger/` on your browser. Replace `` with your own port number. - -You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints: - -![bookstore-swagger](./images/bookstore-swagger.png) - -Swagger has a nice interface to test the APIs. - -If you try to execute the `[GET] /api/app/book` API to get a list of books, the server returns such a JSON result: - -````json -{ - "totalCount": 2, - "items": [ - { - "name": "The Hitchhiker's Guide to the Galaxy", - "type": 7, - "publishDate": "1995-09-27T00:00:00", - "price": 42, - "lastModificationTime": null, - "lastModifierId": null, - "creationTime": "2020-07-03T21:04:18.4607218", - "creatorId": null, - "id": "86100bb6-cbc1-25be-6643-39f62806969c" - }, - { - "name": "1984", - "type": 3, - "publishDate": "1949-06-08T00:00:00", - "price": 19.84, - "lastModificationTime": null, - "lastModifierId": null, - "creationTime": "2020-07-03T21:04:18.3174016", - "creatorId": null, - "id": "41055277-cce8-37d7-bb37-39f62806960b" - } - ] -} -```` - -That's pretty cool since we haven't written a single line of code to create the API controller, but now we have a fully working REST API! \ No newline at end of file diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md deleted file mode 100644 index 91476b8cd8..0000000000 --- a/docs/en/Tutorials/Part-10.md +++ /dev/null @@ -1,1236 +0,0 @@ -# Web Application Development Tutorial - Part 10: Book to Author Relation -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Previous": { - "Name": "Authors: User Interface", - "Path": "Tutorials/Part-9" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts; - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- **Part 10: Book to Author Relation (this part)** - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -## Introduction - -We have created `Book` and `Author` functionalities for the book store application. However, currently there is no relation between these entities. - -In this tutorial, we will establish a **1 to N** relation between the `Author` and the `Book` entities. - -## Add Relation to The Book Entity - -Open the `Books/Book.cs` in the `Acme.BookStore.Domain` project and add the following property to the `Book` entity: - -````csharp -public Guid AuthorId { get; set; } -```` - -{{if DB=="EF"}} - -> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (like we will be doing below) which makes your application code simpler. - -{{end}} - -## Database & Data Migration - -Added a new, required `AuthorId` property to the `Book` entity. But, **what about the existing books** on the database? They currently don't have `AuthorId`s and this will be a problem when we try to run the application. - -This is a **typical migration problem** and the decision depends on your case; - -* If you haven't published your application to the production yet, you can just delete existing books in the database, or you can even delete the entire database in your development environment. -* You can update the existing data programmatically on data migration or seed phase. -* You can manually handle it on the database. - -We prefer to **delete the database** {{if DB=="EF"}}(you can run the `Drop-Database` in the *Package Manager Console*){{end}} since this is just an example project and data loss is not important. Since this topic is not related to the ABP Framework, we don't go deeper for all the scenarios. - -{{if DB=="EF"}} - -### Update the EF Core Mapping - -Locate to `OnModelCreating` method in the `BookStoreDbContext` class that under the `EntityFrameworkCore` folder of the `Acme.BookStore.EntityFrameworkCore` project and change the `builder.Entity` part as shown below: - -````csharp -builder.Entity(b => -{ - b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); //auto configure for the base class props - b.Property(x => x.Name).IsRequired().HasMaxLength(128); - - // ADD THE MAPPING FOR THE RELATION - b.HasOne().WithMany().HasForeignKey(x => x.AuthorId).IsRequired(); -}); -```` - -### Add New EF Core Migration - -The startup solution is configured to use [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. - -Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore` project and type the following command: - -````bash -dotnet ef migrations add Added_AuthorId_To_Book -```` - -This should create a new migration class with the following code in its `Up` method: - -````csharp -migrationBuilder.AddColumn( - name: "AuthorId", - table: "AppBooks", - type: "uniqueidentifier", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); - -migrationBuilder.CreateIndex( - name: "IX_AppBooks_AuthorId", - table: "AppBooks", - column: "AuthorId"); - -migrationBuilder.AddForeignKey( - name: "FK_AppBooks_AppAuthors_AuthorId", - table: "AppBooks", - column: "AuthorId", - principalTable: "AppAuthors", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); -```` - -* Adds an `AuthorId` field to the `AppBooks` table. -* Creates an index on the `AuthorId` field. -* Declares the foreign key to the `AppAuthors` table. - -> If you are using Visual Studio, you may want to use `Add-Migration Added_AuthorId_To_Book -c BookStoreDbContext` and `Update-Database -Context BookStoreDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. - -{{end}} - -## Change the Data Seeder - -Since the `AuthorId` is a required property of the `Book` entity, current data seeder code can not work. Open the `BookStoreDataSeederContributor` in the `Acme.BookStore.Domain` project and change as the following: - -````csharp -using System; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Acme.BookStore.Books; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore; - -public class BookStoreDataSeederContributor - : IDataSeedContributor, ITransientDependency -{ - private readonly IRepository _bookRepository; - private readonly IAuthorRepository _authorRepository; - private readonly AuthorManager _authorManager; - - public BookStoreDataSeederContributor( - IRepository bookRepository, - IAuthorRepository authorRepository, - AuthorManager authorManager) - { - _bookRepository = bookRepository; - _authorRepository = authorRepository; - _authorManager = authorManager; - } - - public async Task SeedAsync(DataSeedContext context) - { - if (await _bookRepository.GetCountAsync() > 0) - { - return; - } - - var orwell = await _authorRepository.InsertAsync( - await _authorManager.CreateAsync( - "George Orwell", - new DateTime(1903, 06, 25), - "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)." - ) - ); - - var douglas = await _authorRepository.InsertAsync( - await _authorManager.CreateAsync( - "Douglas Adams", - new DateTime(1952, 03, 11), - "Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'." - ) - ); - - await _bookRepository.InsertAsync( - new Book - { - AuthorId = orwell.Id, // SET THE AUTHOR - Name = "1984", - Type = BookType.Dystopia, - PublishDate = new DateTime(1949, 6, 8), - Price = 19.84f - }, - autoSave: true - ); - - await _bookRepository.InsertAsync( - new Book - { - AuthorId = douglas.Id, // SET THE AUTHOR - Name = "The Hitchhiker's Guide to the Galaxy", - Type = BookType.ScienceFiction, - PublishDate = new DateTime(1995, 9, 27), - Price = 42.0f - }, - autoSave: true - ); - } -} -```` - -The only change is that we set the `AuthorId` properties of the `Book` entities. - -> Delete existing books or delete the database before executing the `DbMigrator`. See the *Database & Data Migration* section above for more info. - -{{if DB=="EF"}} - -You can now run the `.DbMigrator` console application to **migrate** the **database schema** and **seed** the initial data. - -{{else if DB=="Mongo"}} - -You can now run the `.DbMigrator` console application to **seed** the initial data. - -{{end}} - -## Application Layer - -We will change the `BookAppService` to support the Author relation. - -### Data Transfer Objects - -Let's begin from the DTOs. - -#### BookDto - -Open the `BookDto` class in the `Books` folder of the `Acme.BookStore.Application.Contracts` project and add the following properties: - -```csharp -public Guid AuthorId { get; set; } -public string AuthorName { get; set; } -``` - -The final `BookDto` class should be following: - -```csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore.Books; - -public class BookDto : AuditedEntityDto -{ - public Guid AuthorId { get; set; } - - public string AuthorName { get; set; } - - public string Name { get; set; } - - public BookType Type { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } -} -``` - -#### CreateUpdateBookDto - -Open the `CreateUpdateBookDto` class in the `Books` folder of the `Acme.BookStore.Application.Contracts` project and add an `AuthorId` property as shown: - -````csharp -public Guid AuthorId { get; set; } -```` - -#### AuthorLookupDto - -Create a new class, `AuthorLookupDto`, inside the `Books` folder of the `Acme.BookStore.Application.Contracts` project: - -````csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore.Books; - -public class AuthorLookupDto : EntityDto -{ - public string Name { get; set; } -} -```` - -This will be used in a new method that will be added to the `IBookAppService`. - -### IBookAppService - -Open the `IBookAppService` interface in the `Books` folder of the `Acme.BookStore.Application.Contracts` project and add a new method, named `GetAuthorLookupAsync`, as shown below: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Acme.BookStore.Books; - -public interface IBookAppService : - ICrudAppService< //Defines CRUD methods - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting - CreateUpdateBookDto> //Used to create/update a book -{ - // ADD the NEW METHOD - Task> GetAuthorLookupAsync(); -} -```` - -This new method will be used from the UI to get a list of authors and fill a dropdown list to select the author of a book. - -### BookAppService - -Open the `BookAppService` class in the `Books` folder of the `Acme.BookStore.Application` project and replace the file content with the following code: - -{{if DB=="EF"}} - -```csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Dynamic.Core; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Acme.BookStore.Permissions; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Entities; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore.Books; - -[Authorize(BookStorePermissions.Books.Default)] -public class BookAppService : - CrudAppService< - Book, //The Book entity - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting - CreateUpdateBookDto>, //Used to create/update a book - IBookAppService //implement the IBookAppService -{ - private readonly IAuthorRepository _authorRepository; - - public BookAppService( - IRepository repository, - IAuthorRepository authorRepository) - : base(repository) - { - _authorRepository = authorRepository; - GetPolicyName = BookStorePermissions.Books.Default; - GetListPolicyName = BookStorePermissions.Books.Default; - CreatePolicyName = BookStorePermissions.Books.Create; - UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Delete; - } - - public override async Task GetAsync(Guid id) - { - //Get the IQueryable from the repository - var queryable = await Repository.GetQueryableAsync(); - - //Prepare a query to join books and authors - var query = from book in queryable - join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id - where book.Id == id - select new { book, author }; - - //Execute the query and get the book with author - var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query); - if (queryResult == null) - { - throw new EntityNotFoundException(typeof(Book), id); - } - - var bookDto = ObjectMapper.Map(queryResult.book); - bookDto.AuthorName = queryResult.author.Name; - return bookDto; - } - - public override async Task> GetListAsync(PagedAndSortedResultRequestDto input) - { - //Get the IQueryable from the repository - var queryable = await Repository.GetQueryableAsync(); - - //Prepare a query to join books and authors - var query = from book in queryable - join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id - select new {book, author}; - - //Paging - query = query - .OrderBy(NormalizeSorting(input.Sorting)) - .Skip(input.SkipCount) - .Take(input.MaxResultCount); - - //Execute the query and get a list - var queryResult = await AsyncExecuter.ToListAsync(query); - - //Convert the query result to a list of BookDto objects - var bookDtos = queryResult.Select(x => - { - var bookDto = ObjectMapper.Map(x.book); - bookDto.AuthorName = x.author.Name; - return bookDto; - }).ToList(); - - //Get the total count with another query - var totalCount = await Repository.GetCountAsync(); - - return new PagedResultDto( - totalCount, - bookDtos - ); - } - - public async Task> GetAuthorLookupAsync() - { - var authors = await _authorRepository.GetListAsync(); - - return new ListResultDto( - ObjectMapper.Map, List>(authors) - ); - } - - private static string NormalizeSorting(string sorting) - { - if (sorting.IsNullOrEmpty()) - { - return $"book.{nameof(Book.Name)}"; - } - - if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase)) - { - return sorting.Replace( - "authorName", - "author.Name", - StringComparison.OrdinalIgnoreCase - ); - } - - return $"book.{sorting}"; - } -} -``` - -Let's see the changes we've done: - -* Added `[Authorize(BookStorePermissions.Books.Default)]` to authorize the methods we've newly added/overrode (remember, authorize attribute is valid for all the methods of the class when it is declared for a class). -* Injected `IAuthorRepository` to query from the authors. -* Overrode the `GetAsync` method of the base `CrudAppService`, which returns a single `BookDto` object with the given `id`. - * Used a simple LINQ expression to join books and authors and query them together for the given book id. - * Used `AsyncExecuter.FirstOrDefaultAsync(...)` to execute the query and get a result. It is a way to use asynchronous LINQ extensions without depending on the database provider API. Check the [repository documentation](../Repositories.md) to understand why we've used it. - * Throws an `EntityNotFoundException` which results an `HTTP 404` (not found) result if requested book was not present in the database. - * Finally, created a `BookDto` object using the `ObjectMapper`, then assigning the `AuthorName` manually. -* Overrode the `GetListAsync` method of the base `CrudAppService`, which returns a list of books. The logic is similar to the previous method, so you can easily understand the code. -* Created a new method: `GetAuthorLookupAsync`. This simple gets all the authors. The UI uses this method to fill a dropdown list and select and author while creating/editing books. - -{{else if DB=="Mongo"}} - -```csharp -using System; -using System.Collections.Generic; -using System.Linq.Dynamic.Core; -using System.Linq; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Acme.BookStore.Permissions; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore.Books; - -[Authorize(BookStorePermissions.Books.Default)] -public class BookAppService : - CrudAppService< - Book, //The Book entity - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting - CreateUpdateBookDto>, //Used to create/update a book - IBookAppService //implement the IBookAppService -{ - private readonly IAuthorRepository _authorRepository; - - public BookAppService( - IRepository repository, - IAuthorRepository authorRepository) - : base(repository) - { - _authorRepository = authorRepository; - GetPolicyName = BookStorePermissions.Books.Default; - GetListPolicyName = BookStorePermissions.Books.Default; - CreatePolicyName = BookStorePermissions.Books.Create; - UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Create; - } - - public async override Task GetAsync(Guid id) - { - var book = await Repository.GetAsync(id); - var bookDto = ObjectMapper.Map(book); - - var author = await _authorRepository.GetAsync(book.AuthorId); - bookDto.AuthorName = author.Name; - - return bookDto; - } - - public async override Task> - GetListAsync(PagedAndSortedResultRequestDto input) - { - //Set a default sorting, if not provided - if (input.Sorting.IsNullOrWhiteSpace()) - { - input.Sorting = nameof(Book.Name); - } - - //Get the IQueryable from the repository - var queryable = await Repository.GetQueryableAsync(); - - //Get the books - var books = await AsyncExecuter.ToListAsync( - queryable - .OrderBy(input.Sorting) - .Skip(input.SkipCount) - .Take(input.MaxResultCount) - ); - - //Convert to DTOs - var bookDtos = ObjectMapper.Map, List>(books); - - //Get a lookup dictionary for the related authors - var authorDictionary = await GetAuthorDictionaryAsync(books); - - //Set AuthorName for the DTOs - bookDtos.ForEach(bookDto => bookDto.AuthorName = - authorDictionary[bookDto.AuthorId].Name); - - //Get the total count with another query (required for the paging) - var totalCount = await Repository.GetCountAsync(); - - return new PagedResultDto( - totalCount, - bookDtos - ); - } - - public async Task> GetAuthorLookupAsync() - { - var authors = await _authorRepository.GetListAsync(); - - return new ListResultDto( - ObjectMapper.Map, List>(authors) - ); - } - - private async Task> - GetAuthorDictionaryAsync(List books) - { - var authorIds = books - .Select(b => b.AuthorId) - .Distinct() - .ToArray(); - - var queryable = await _authorRepository.GetQueryableAsync(); - - var authors = await AsyncExecuter.ToListAsync( - queryable.Where(a => authorIds.Contains(a.Id)) - ); - - return authors.ToDictionary(x => x.Id, x => x); - } -} -``` - -Let's see the changes we've done: - -* Added `[Authorize(BookStorePermissions.Books.Default)]` to authorize the methods we've newly added/overrode (remember, authorize attribute is valid for all the methods of the class when it is declared for a class). -* Injected `IAuthorRepository` to query from the authors. -* Overrode the `GetAsync` method of the base `CrudAppService`, which returns a single `BookDto` object with the given `id`. -* Overrode the `GetListAsync` method of the base `CrudAppService`, which returns a list of books. This code separately queries the authors from database and sets the name of the authors in the application code. Instead, you could create a custom repository method and perform a join query or take the power of the MongoDB API to get the books and their authors in a single query, which would be more performant. -* Created a new method: `GetAuthorLookupAsync`. This simple gets all the authors. The UI uses this method to fill a dropdown list and select and author while creating/editing books. - -{{end}} - -### Object to Object Mapping Configuration - -Introduced the `AuthorLookupDto` class and used object mapping inside the `GetAuthorLookupAsync` method. So, we need to add a new mapping definition inside the `BookStoreApplicationAutoMapperProfile.cs` file of the `Acme.BookStore.Application` project: - -````csharp -CreateMap(); -```` - -## Unit Tests - -Some of the unit tests will fail since we made some changed on the `AuthorAppService`. Open the `BookAppService_Tests` in the `Books` folder of the `Acme.BookStore.Application.Tests` project and change the content as the following: - -```csharp -using System; -using System.Linq; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Shouldly; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Modularity; -using Volo.Abp.Validation; -using Xunit; - -namespace Acme.BookStore.Books; - -public abstract class BookAppService_Tests : BookStoreApplicationTestBase - where TStartupModule : IAbpModule -{ - private readonly IBookAppService _bookAppService; - private readonly IAuthorAppService _authorAppService; - - protected BookAppService_Tests() - { - _bookAppService = GetRequiredService(); - _authorAppService = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_List_Of_Books() - { - //Act - var result = await _bookAppService.GetListAsync( - new PagedAndSortedResultRequestDto() - ); - - //Assert - result.TotalCount.ShouldBeGreaterThan(0); - result.Items.ShouldContain(b => b.Name == "1984" && - b.AuthorName == "George Orwell"); - } - - [Fact] - public async Task Should_Create_A_Valid_Book() - { - var authors = await _authorAppService.GetListAsync(new GetAuthorListDto()); - var firstAuthor = authors.Items.First(); - - //Act - var result = await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - AuthorId = firstAuthor.Id, - Name = "New test book 42", - Price = 10, - PublishDate = System.DateTime.Now, - Type = BookType.ScienceFiction - } - ); - - //Assert - result.Id.ShouldNotBe(Guid.Empty); - result.Name.ShouldBe("New test book 42"); - } - - [Fact] - public async Task Should_Not_Create_A_Book_Without_Name() - { - var exception = await Assert.ThrowsAsync(async () => - { - await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - }); - - exception.ValidationErrors - .ShouldContain(err => err.MemberNames.Any(m => m == "Name")); - } -} -``` - -* Changed the assertion condition in the `Should_Get_List_Of_Books` from `b => b.Name == "1984"` to `b => b.Name == "1984" && b.AuthorName == "George Orwell"` to check if the author name was filled. -* Changed the `Should_Create_A_Valid_Book` method to set the `AuthorId` while creating a new book, since it is required anymore. - -## The User Interface - -{{if UI=="MVC"}} - -### The Book List - -Book list page change is trivial. Open the `Pages/Books/Index.js` in the `Acme.BookStore.Web` project and add an `authorName` column between the `name` and `type` columns: - -````js -... -{ - title: l('Name'), - data: "name" -}, - -// ADDED the NEW AUTHOR NAME COLUMN -{ - title: l('Author'), - data: "authorName" -}, - -{ - title: l('Type'), - data: "type", - render: function (data) { - return l('Enum:BookType.' + data); - } -}, -... -```` - -When you run the application, you can see the *Author* column on the table: - -![bookstore-added-author-to-book-list](images/bookstore-added-author-to-book-list-2.png) - -### Create Modal - -Open the `Pages/Books/CreateModal.cshtml.cs` in the `Acme.BookStore.Web` project and change the file content as shown below: - -```csharp -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Threading.Tasks; -using Acme.BookStore.Books; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; - -namespace Acme.BookStore.Web.Pages.Books; - -public class CreateModalModel : BookStorePageModel -{ - [BindProperty] - public CreateBookViewModel Book { get; set; } - - public List Authors { get; set; } - - private readonly IBookAppService _bookAppService; - - public CreateModalModel( - IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnGetAsync() - { - Book = new CreateBookViewModel(); - - var authorLookup = await _bookAppService.GetAuthorLookupAsync(); - Authors = authorLookup.Items - .Select(x => new SelectListItem(x.Name, x.Id.ToString())) - .ToList(); - } - - public async Task OnPostAsync() - { - await _bookAppService.CreateAsync( - ObjectMapper.Map(Book) - ); - return NoContent(); - } - - public class CreateBookViewModel - { - [SelectItems(nameof(Authors))] - [DisplayName("Author")] - public Guid AuthorId { get; set; } - - [Required] - [StringLength(128)] - public string Name { get; set; } = string.Empty; - - [Required] - public BookType Type { get; set; } = BookType.Undefined; - - [Required] - [DataType(DataType.Date)] - public DateTime PublishDate { get; set; } = DateTime.Now; - - [Required] - public float Price { get; set; } - } -} -``` - -* Changed type of the `Book` property from `CreateUpdateBookDto` to the new `CreateBookViewModel` class defined in this file. The main motivation of this change to customize the model class based on the User Interface (UI) requirements. We didn't want to use UI-related `[SelectItems(nameof(Authors))]` and `[DisplayName("Author")]` attributes inside the `CreateUpdateBookDto` class. -* Added `Authors` property that is filled inside the `OnGetAsync` method using the `IBookAppService.GetAuthorLookupAsync` method defined before. -* Changed the `OnPostAsync` method to map `CreateBookViewModel` object to a `CreateUpdateBookDto` object since `IBookAppService.CreateAsync` expects a parameter of this type. - -### Edit Modal - -Open the `Pages/Books/EditModal.cshtml.cs` in the `Acme.BookStore.Web` project and change the file content as shown below: - -```csharp -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Threading.Tasks; -using Acme.BookStore.Books; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; - -namespace Acme.BookStore.Web.Pages.Books; - -public class EditModalModel : BookStorePageModel -{ - [BindProperty] - public EditBookViewModel Book { get; set; } - - public List Authors { get; set; } - - private readonly IBookAppService _bookAppService; - - public EditModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnGetAsync(Guid id) - { - var bookDto = await _bookAppService.GetAsync(id); - Book = ObjectMapper.Map(bookDto); - - var authorLookup = await _bookAppService.GetAuthorLookupAsync(); - Authors = authorLookup.Items - .Select(x => new SelectListItem(x.Name, x.Id.ToString())) - .ToList(); - } - - public async Task OnPostAsync() - { - await _bookAppService.UpdateAsync( - Book.Id, - ObjectMapper.Map(Book) - ); - - return NoContent(); - } - - public class EditBookViewModel - { - [HiddenInput] - public Guid Id { get; set; } - - [SelectItems(nameof(Authors))] - [DisplayName("Author")] - public Guid AuthorId { get; set; } - - [Required] - [StringLength(128)] - public string Name { get; set; } = string.Empty; - - [Required] - public BookType Type { get; set; } = BookType.Undefined; - - [Required] - [DataType(DataType.Date)] - public DateTime PublishDate { get; set; } = DateTime.Now; - - [Required] - public float Price { get; set; } - } -} -``` - -* Changed type of the `Book` property from `CreateUpdateBookDto` to the new `EditBookViewModel` class defined in this file, just like done before for the create modal above. -* Moved the `Id` property inside the new `EditBookViewModel` class. -* Added `Authors` property that is filled inside the `OnGetAsync` method using the `IBookAppService.GetAuthorLookupAsync` method. -* Changed the `OnPostAsync` method to map `EditBookViewModel` object to a `CreateUpdateBookDto` object since `IBookAppService.UpdateAsync` expects a parameter of this type. - -These changes require a small change in the `EditModal.cshtml`. Remove the `` tag since we no longer need to it (since moved it to the `EditBookViewModel`). The final content of the `EditModal.cshtml` should be following: - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Web.Pages.Books -@using Microsoft.Extensions.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model EditModalModel -@inject IStringLocalizer L -@{ - Layout = null; -} - - - - - - - - - -```` - -### Object to Object Mapping Configuration - -The changes above requires to define some object to object mappings. Open the `BookStoreWebAutoMapperProfile.cs` in the `Acme.BookStore.Web` project and add the following mapping definitions inside the constructor: - -```csharp -CreateMap(); -CreateMap(); -CreateMap(); -``` - -You can run the application and try to create a new book or update an existing book. You will see a drop down list on the create/update form to select the author of the book: - -![bookstore-added-authors-to-modals](images/bookstore-added-authors-to-modals-2.png) - -{{else if UI=="NG"}} - -### Service Proxy Generation - -Since the HTTP APIs have been changed, you need to update Angular client side [service proxies](../UI/Angular/Service-Proxies.md). Before running `generate-proxy` command, your host must be up and running. - -Run the following command in the `angular` folder (you may need to stop the angular application): - -```bash -abp generate-proxy -t ng -``` -This command will update the service proxy files under the `/src/app/proxy/` folder. - -### The Book List - -Book list page change is trivial. Open the `/src/app/book/book.component.html` and add the following column definition between the `Name` and `Type` columns: - -````html - -```` - -When you run the application, you can see the *Author* column on the table: - -![bookstore-books-with-authorname-angular](images/bookstore-books-with-authorname-angular-2.png) - -### Create/Edit Forms - -The next step is to add an Author selection (dropdown) to the create/edit forms. The final UI will look like the one shown below: - -![bookstore-angular-author-selection](images/bookstore-angular-author-selection-2.png) - -Added the Author dropdown as the first element in the form. - -Open the `/src/app/book/book.component.ts` and and change the content as shown below: - -````js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookService, BookDto, bookTypeOptions, AuthorLookupDto } from '@proxy/books'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -@Component({ - selector: 'app-book', - templateUrl: './book.component.html', - styleUrls: ['./book.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], -}) -export class BookComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - form: FormGroup; - - selectedBook = {} as BookDto; - - authors$: Observable; - - bookTypes = bookTypeOptions; - - isModalOpen = false; - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder, - private confirmation: ConfirmationService - ) { - this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items)); - } - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getList(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - createBook() { - this.selectedBook = {} as BookDto; - this.buildForm(); - this.isModalOpen = true; - } - - editBook(id: string) { - this.bookService.get(id).subscribe((book) => { - this.selectedBook = book; - this.buildForm(); - this.isModalOpen = true; - }); - } - - buildForm() { - this.form = this.fb.group({ - authorId: [this.selectedBook.authorId || null, Validators.required], - name: [this.selectedBook.name || null, Validators.required], - type: [this.selectedBook.type || null, Validators.required], - publishDate: [ - this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null, - Validators.required, - ], - price: [this.selectedBook.price || null, Validators.required], - }); - } - - save() { - if (this.form.invalid) { - return; - } - - const request = this.selectedBook.id - ? this.bookService.update(this.selectedBook.id, this.form.value) - : this.bookService.create(this.form.value); - - request.subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } - - delete(id: string) { - this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => { - if (status === Confirmation.Status.confirm) { - this.bookService.delete(id).subscribe(() => this.list.get()); - } - }); - } -} -```` - -* Added imports for the `AuthorLookupDto`, `Observable` and `map`. -* Added `authors$: Observable;` field after the `selectedBook`. -* Added `this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items));` into the constructor. -* Added ` authorId: [this.selectedBook.authorId || null, Validators.required],` into the `buildForm()` function. - -Open the `/src/app/book/book.component.html` and add the following form group just before the book name form group: - -````html -
    - * - -
    -```` - -That's all. Just run the application and try to create or edit an author. - -{{end}} - -{{if UI == "Blazor" || UI == "BlazorServer"}} - -### The Book List - -It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the `Acme.BookStore.Blazor.Client` project and add the following `DataGridColumn` definition just after the `Name` (book name) column: - -````xml - -```` - -When you run the application, you can see the *Author* column on the table: - -![blazor-bookstore-book-list-with-authors](images/blazor-bookstore-book-list-with-authors-2.png) - -### Create Book Modal - -Add the following field to the `@code` section of the `Books.razor` file: - -````csharp -IReadOnlyList authorList = Array.Empty(); -```` - -Override the `OnInitializedAsync` method and adding the following code: - -````csharp -protected override async Task OnInitializedAsync() -{ - await base.OnInitializedAsync(); - authorList = (await AppService.GetAuthorLookupAsync()).Items; -} -```` - -* It is essential to call the `base.OnInitializedAsync()` since `AbpCrudPageBase` has some initialization code to be executed. - -Override the `OpenCreateModalAsync` method and adding the following code: - -````csharp -protected override async Task OpenCreateModalAsync() -{ - if (!authorList.Any()) - { - throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); - } - - await base.OpenCreateModalAsync(); - NewEntity.AuthorId = authorList.First().Id; -} -```` - -The final `@code` block should be the following: - -````csharp -@code -{ - //ADDED A NEW FIELD - IReadOnlyList authorList = Array.Empty(); - - public Books() // Constructor - { - CreatePolicyName = BookStorePermissions.Books.Create; - UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Delete; - } - - //GET AUTHORS ON INITIALIZATION - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - authorList = (await AppService.GetAuthorLookupAsync()).Items; - } - - protected override async Task OpenCreateModalAsync() - { - if (!authorList.Any()) - { - throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); - } - - await base.OpenCreateModalAsync(); - NewEntity.AuthorId = authorList.First().Id; - } -} -```` - -Finally, add the following `Field` definition into the `ModalBody` of the *Create* modal, as the first item, before the `Name` field: - -````xml - - @L["Author"] - - -```` - -This requires to add a new localization key to the `en.json` file: - -````js -"AnAuthorIsRequiredForCreatingBook": "An author is required to create a book" -```` - -You can run the application to see the *Author Selection* while creating a new book: - -![book-create-modal-with-author](images/book-create-modal-with-author-2.png) - -### Edit Book Modal - -Add the following `Field` definition into the `ModalBody` of the *Edit* modal, as the first item, before the `Name` field: - -````xml - - @L["Author"] - - -```` - -That's all. We are reusing the `authorList` defined for the *Create* modal. - -{{end}} diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md deleted file mode 100644 index a34bbfc2a6..0000000000 --- a/docs/en/Tutorials/Part-2.md +++ /dev/null @@ -1,684 +0,0 @@ -# Web Application Development Tutorial - Part 2: The Book List Page -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` -````json -//[doc-nav] -{ - "Next": { - "Name": "Creating, Updating and Deleting Books", - "Path": "Tutorials/Part-3" - }, - "Previous": { - "Name": "Creating the Server Side", - "Path": "Tutorials/Part-1" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts: - -- [Part 1: Creating the server side](Part-1.md) -- **Part 2: The book list page (this part)** -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -{{if UI == "MVC" && DB == "EF"}} - -### Video Tutorial - -This part is also recorded as a video tutorial and **published on YouTube**. - -{{end}} - -{{if UI == "MVC"}} - -## Dynamic JavaScript Proxies - -It's common to call the HTTP API endpoints via AJAX from the **JavaScript** side. You can use `$.ajax` or another tool to call the endpoints. However, ABP offers a better way. - -ABP **dynamically** creates **[JavaScript Proxies](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md)** for all the API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**. - -### Testing in the Developer Console - -You can easily test the JavaScript proxies using your favorite browser's **Developer Console**. Run the application, open your browser's **developer tools** (*shortcut is generally F12*), switch to the **Console** tab, type the following code and press enter: - -````js -acme.bookStore.books.book.getList({}).done(function (result) { console.log(result); }); -```` - -* `acme.bookStore.books` is the namespace of the `BookAppService` converted to [camelCase](https://en.wikipedia.org/wiki/Camel_case). -* `book` is the conventional name for the `BookAppService` (removed `AppService` postfix and converted to camelCase). -* `getList` is the conventional name for the `GetListAsync` method defined in the `CrudAppService` base class (removed `Async` postfix and converted to camelCase). -* The `{}` argument is used to send an empty object to the `GetListAsync` method which normally expects an object of type `PagedAndSortedResultRequestDto` that is used to send paging and sorting options to the server (all properties are optional with default values, so you can send an empty object). -* The `getList` function returns a `promise`. You can pass a callback to the `then` (or `done`) function to get the result returned from the server. - -Running this code produces the following output: - -![bookstore-javascript-proxy-console](images/bookstore-javascript-proxy-console.png) - -You can see the **book list** returned from the server. You can also check the **network** tab of the developer tools to see the client to server communication: - -![bookstore-getlist-result-network](images/bookstore-getlist-result-network.png) - -Let's **create a new book** using the `create` function: - -````js -acme.bookStore.books.book.create({ - name: 'Foundation', - type: 7, - publishDate: '1951-05-24', - price: 21.5 - }).then(function (result) { - console.log('successfully created the book with id: ' + result.id); - }); -```` - -> If you downloaded the source code of the tutorial and are following the steps from the sample, you should also pass the `authorId` parameter to the create method for **creating a new book**. - -You should see a message in the console that looks something like this: - -````text -successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 -```` - -Check the `Books` table in the database to see the new book row. You can try `get`, `update` and `delete` functions yourself. - -We will use these dynamic proxy functions in the next sections to communicate with the server. - -{{end}} - -## Localization - -Before starting the UI development, we first want to prepare the localization texts (you normally do this when needed while developing your application). - -Localization texts are located under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project: - -![bookstore-localization-files](images/bookstore-localization-files-v2.png) - -Open the `en.json` (*the English translations*) file and change the content as shown below: - -````json -{ - "Culture": "en", - "Texts": { - "Menu:Home": "Home", - "Welcome": "Welcome", - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", - "Menu:BookStore": "Book Store", - "Menu:Books": "Books", - "Actions": "Actions", - "Close": "Close", - "Delete": "Delete", - "Edit": "Edit", - "PublishDate": "Publish date", - "NewBook": "New book", - "Name": "Name", - "Type": "Type", - "Price": "Price", - "CreationTime": "Creation time", - "AreYouSure": "Are you sure?", - "AreYouSureToDelete": "Are you sure you want to delete this item?", - "Enum:BookType.0": "Undefined", - "Enum:BookType.1": "Adventure", - "Enum:BookType.2": "Biography", - "Enum:BookType.3": "Dystopia", - "Enum:BookType.4": "Fantastic", - "Enum:BookType.5": "Horror", - "Enum:BookType.6": "Science", - "Enum:BookType.7": "Science fiction", - "Enum:BookType.8": "Poetry" - } -} -```` - -* Localization key names are arbitrary. You can set any name. We prefer some conventions for specific text types; - * Add `Menu:` prefix for menu items. - * Use `Enum:.` or `.` naming convention to localize the enum members. When you do it like that, ABP can automatically localize the enums in some proper cases. - -If a text is not defined in the localization file, it **falls back** to the localization key (as ASP.NET Core's standard behavior). - -> ABP's localization system is built on the [ASP.NET Core's standard localization](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) system and extends it in many ways. Check the [localization document](../Localization.md) for details. - -{{if UI == "MVC"}} - -## Create a Books Page - -It's time to create something visible and usable! Instead of the classic MVC, we will use the [Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start) approach which is recommended by Microsoft. - -Create a `Books` folder under the `Pages` folder of the `Acme.BookStore.Web` project. Add a new Razor Page by right clicking the Books folder then selecting **Add > Razor Page** menu item. Name it as `Index`: - -![bookstore-add-index-page](images/bookstore-add-index-page-v2.png) - -Open the `Index.cshtml` and change the whole content as shown below: - -````html -@page -@using Acme.BookStore.Web.Pages.Books -@model IndexModel - -

    Books

    -```` - -`Index.cshtml.cs` content should be like that: - -```csharp -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Acme.BookStore.Web.Pages.Books; - -public class IndexModel : PageModel -{ - public void OnGet() - { - - } -} -``` - -### Add Books Page to the Main Menu - -Open the `BookStoreMenuContributor` class in the `Menus` folder and add the following code to the end of the `ConfigureMainMenuAsync` method: - -````csharp -context.Menu.AddItem( - new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" - ).AddItem( - new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/Books" - ) - ) -); -```` - -Run the project, login to the application with the username `admin` and the password `1q2w3E*` and you can see that the new menu item has been added to the main menu: - -![bookstore-menu-items](images/bookstore-new-menu-item-2.png) - -When you click on the Books menu item under the Book Store parent, you will be redirected to the new empty Books Page. - -### Book List - -We will use the [Datatables.net](https://datatables.net/) jQuery library to show the book list. Datatables library completely works via AJAX, it is fast, popular and provides a good user experience. - -> Datatables library is configured in the startup template, so you can directly use it in any page without including any style or script file for your page. - -#### Index.cshtml - -Change the `Pages/Books/Index.cshtml` as the following: - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Web.Pages.Books -@using Microsoft.Extensions.Localization -@model IndexModel -@inject IStringLocalizer L -@section scripts -{ - -} - - -

    @L["Books"]

    -
    - - - -
    -```` - -* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) is used to add external **scripts** to the page. It has many additional features compared to the standard `script` tag. It handles **minification** and **versioning**. Check the [bundling & minification document](../UI/AspNetCore/Bundling-Minification.md) for details. -* `abp-card` is a tag helper for Twitter Bootstrap's [card component](https://getbootstrap.com/docs/4.5/components/card/). There are other useful tag helpers provided by the ABP Framework to easily use most of [bootstrap](https://getbootstrap.com/)'s components. You could use the regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by the help of the IntelliSense and compiles time type checking. For further information, check the [tag helpers](../UI/AspNetCore/Tag-Helpers/Index.md) document. - -#### Index.js - -Create an `Index.js` file under the `Pages/Books` folder: - -![bookstore-index-js-file](images/bookstore-index-js-file-v3.png) - -The content of the file is shown below: - -````js -$(function () { - var l = abp.localization.getResource('BookStore'); - - var dataTable = $('#BooksTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), - columnDefs: [ - { - title: l('Name'), - data: "name" - }, - { - title: l('Type'), - data: "type", - render: function (data) { - return l('Enum:BookType.' + data); - } - }, - { - title: l('PublishDate'), - data: "publishDate", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(); - } - }, - { - title: l('Price'), - data: "price" - }, - { - title: l('CreationTime'), data: "creationTime", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(luxon.DateTime.DATETIME_SHORT); - } - } - ] - }) - ); -}); -```` - -* `abp.localization.getResource` gets a function that is used to localize text using the same JSON file defined on the server side. In this way, you can share the localization values with the client side. -* `abp.libs.datatables.normalizeConfiguration` is a helper function defined by the ABP Framework. There's no requirement to use it, but it simplifies the [Datatables](https://datatables.net/) configuration by providing conventional default values for missing options. -* `abp.libs.datatables.createAjax` is another helper function to adapt the ABP's dynamic JavaScript API proxies to the [Datatable](https://datatables.net/)'s expected parameter format -* `acme.bookStore.books.book.getList` is the dynamic JavaScript proxy function introduced before. -* [luxon](https://moment.github.io/luxon/) library is also a standard library that is pre-configured in the solution, so you can use to perform date/time operations easily. - -> See [Datatables documentation](https://datatables.net/manual/) for all configuration options. - -## Run the Final Application - -You can run the application! The final UI of this part is shown below: - -![Book list](images/bookstore-book-list-4.png) - -This is a fully working, server side paged, sorted and localized table of books. - -{{else if UI == "NG"}} - -## Install NPM packages - -> Notice: This tutorial is based on the ABP Framework v3.1.0+ If your project version is older, then please upgrade your solution. Check the [migration guide](../UI/Angular/Migration-Guide-v3.md) if you are upgrading an existing project with v2.x. - -If you haven't done it before, open a new command line interface (terminal window) and go to your `angular` folder and then run the `yarn` command to install the NPM packages: - -```bash -yarn -``` - -## Create a Books Page - -It's time to create something visible and usable! There are some tools that we will use when developing the Angular frontend application: - -- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) will be used as the UI component library. -- [Ngx-Datatable](https://swimlane.gitbook.io/ngx-datatable/) will be used as the datatable library. - -Run the following command line to create a new module, named `BookModule` in the root folder of the angular application: - -```bash -yarn ng generate module book --module app --routing --route books -``` - -This command should produce the following output: - -````bash -> yarn ng generate module book --module app --routing --route books - -yarn run v1.19.1 -$ ng generate module book --module app --routing --route books -CREATE src/app/book/book-routing.module.ts (336 bytes) -CREATE src/app/book/book.module.ts (335 bytes) -CREATE src/app/book/book.component.html (19 bytes) -CREATE src/app/book/book.component.spec.ts (614 bytes) -CREATE src/app/book/book.component.ts (268 bytes) -CREATE src/app/book/book.component.scss (0 bytes) -UPDATE src/app/app-routing.module.ts (1289 bytes) -Done in 3.88s. -```` - -### BookModule - -Open the `/src/app/book/book.module.ts` and replace the content as shown below: - -````js -import { NgModule } from '@angular/core'; -import { SharedModule } from '../shared/shared.module'; -import { BookRoutingModule } from './book-routing.module'; -import { BookComponent } from './book.component'; - -@NgModule({ - declarations: [BookComponent], - imports: [ - BookRoutingModule, - SharedModule - ] -}) -export class BookModule { } - -```` - -* Added the `SharedModule`. `SharedModule` exports some common modules needed to create user interfaces. -* `SharedModule` already exports the `CommonModule`, so we've removed the `CommonModule`. - -### Routing - -The generated code places the new route definition to the `src/app/app-routing.module.ts` file as shown below: - -````js -const routes: Routes = [ - // other route definitions... - { path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) }, -]; -```` - -Now, open the `src/app/route.provider.ts` file and replace the `configureRoutes` function declaration as shown below: - -```js -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - { - path: '/', - name: '::Menu:Home', - iconClass: 'fas fa-home', - order: 1, - layout: eLayoutType.application, - }, - { - path: '/book-store', - name: '::Menu:BookStore', - iconClass: 'fas fa-book', - order: 2, - layout: eLayoutType.application, - }, - { - path: '/books', - name: '::Menu:Books', - parentName: '::Menu:BookStore', - layout: eLayoutType.application, - }, - ]); - }; -} -``` - -`RoutesService` is a service provided by the ABP Framework to configure the main menu and the routes. - -* `path` is the URL of the route. -* `name` is the localized menu item name (check the [localization document](../UI/Angular/Localization.md) for details). -* `iconClass` is the icon of the menu item (you can use [Font Awesome](https://fontawesome.com/) icons by default). -* `order` is the order of the menu item. -* `layout` is the layout of the BooksModule's routes (there are three types of pre-defined layouts: `eLayoutType.application`, `eLayoutType.account` or `eLayoutType.empty`). - -For more information, check the [RoutesService document](../UI/Angular/Modifying-the-Menu.md#via-routesservice). - -### Service Proxy Generation - -[ABP CLI](../CLI.md) provides a `generate-proxy` command that generates client proxies for your HTTP APIs to make your HTTP APIs easy to consume by the client side. Before running the `generate-proxy` command, your host must be up and running. - -> **Warning**: There is a problem with IIS Express; it doesn't allow connecting to the application from another process. If you are using Visual Studio, select the `Acme.BookStore.HttpApi.Host` instead of IIS Express in the run button drop-down list, as shown in the figure below: - -![vs-run-without-iisexpress](images/vs-run-without-iisexpress.png) - -Once the host application is running, execute the following command in the `angular` folder: - -```bash -abp generate-proxy -t ng -``` - -This command will create the following files under the `/src/app/proxy/books` folder: - -![Generated files](images/generated-proxies-3.png) - -### BookComponent - -Open the `/src/app/book/book.component.ts` file and replace the content as below: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookService, BookDto } from '@proxy/books'; - -@Component({ - selector: 'app-book', - templateUrl: './book.component.html', - styleUrls: ['./book.component.scss'], - providers: [ListService], -}) -export class BookComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - constructor(public readonly list: ListService, private bookService: BookService) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getList(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } -} -``` - -* We imported and injected the generated `BookService`. -* We are using the [ListService](../UI/Angular/List-Service.md), a utility service from the ABP Framework which provides easy pagination, sorting and searching. - -Open the `/src/app/book/book.component.html` and replace the content as shown below: - -```html -
    -
    -
    -
    -
    - {%{{{ '::Menu:Books' | abpLocalization }}}%} -
    -
    -
    -
    -
    -
    - - - - - {%{{{ '::Enum:BookType.' + row.type | abpLocalization }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - - -
    -
    -``` - -Now you can see the final result on your browser: - -![Book list final result](images/bookstore-book-list-angular.png) - -{{else if UI == "Blazor" || UI == "BlazorServer"}} - -## Create a Books Page - -It's time to create something visible and usable! Right click on the `Pages` folder under the `Acme.BookStore.Blazor.Client` project and add a new **razor component**, named `Books.razor`: - -![blazor-add-books-component](images/blazor-add-books-component.png) - -Replace the contents of this component as shown below: - -````html -@page "/books" - -

    Books

    - -@code { - -} -```` - -### Add the Books Page to the Main Menu - -Open the `BookStoreMenuContributor` class in the `Blazor.Client` project add the following code to the end of the `ConfigureMainMenuAsync` method: - -````csharp -context.Menu.AddItem( - new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" - ).AddItem( - new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/books" - ) - ) -); -```` - -Run the project, login to the application with the username `admin` and the password `1q2w3E*` and see that the new menu item has been added to the main menu: - -![blazor-menu-bookstore](images/bookstore-new-menu-item-2.png) - -When you click on the Books menu item under the Book Store parent, you will be redirected to the new empty Books Page. - -### Book List - -We will use the [Blazorise library](https://blazorise.com/) as the UI component kit. It is a very powerful library that supports major HTML/CSS frameworks, including Bootstrap. - -ABP Framework provides a generic base class - `AbpCrudPageBase<...>`, to create CRUD style pages. This base class is compatible with the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `AbpCrudPageBase` to automate the code behind for the standard CRUD stuff. - -Open the `Books.razor` and replace the content as the following: - -````xml -@page "/books" -@using Volo.Abp.Application.Dtos -@using Acme.BookStore.Books -@using Acme.BookStore.Localization -@using Microsoft.Extensions.Localization -@inject IStringLocalizer L -@inherits AbpCrudPageBase - - - -

    @L["Books"]

    -
    - - - - - - - @L[$"Enum:BookType.{context.Type}"] - - - - - @context.PublishDate.ToShortDateString() - - - - - - - @context.CreationTime.ToLongDateString() - - - - - -
    -```` - -> If you see some syntax errors, you can ignore them if your application is properly built and running. Visual Studio still has some bugs with Blazor. - -* Inherited from `AbpCrudPageBase` which implements all the CRUD details for us. -* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class. -* Injected `IStringLocalizer` (as `L` object) and used for localization. - -While the code above is pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better. - -#### About the AbpCrudPageBase - -We will continue benefitting from `AbpCrudPageBase` for the books page. You could just inject the `IBookAppService` and perform all the server side calls yourself (thanks to the [Dynamic C# HTTP API Client Proxy](../API/Dynamic-CSharp-API-Clients.md) system of the ABP Framework). We will do it manually for the authors page to demonstrate how to call the server side HTTP APIs in your Blazor applications. - -## Run the Final Application - -You can run the application! The final UI of this part is shown below: - -![blazor-bookstore-book-list](images/blazor-bookstore-book-list-2.png) - -This is a fully working, server side paged, sorted and localized table of books. - -{{end # UI }} diff --git a/docs/en/Tutorials/Part-3.md b/docs/en/Tutorials/Part-3.md deleted file mode 100644 index e1972bc9f0..0000000000 --- a/docs/en/Tutorials/Part-3.md +++ /dev/null @@ -1,1571 +0,0 @@ -# Web Application Development Tutorial - Part 3: Creating, Updating and Deleting Books -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Integration Tests", - "Path": "Tutorials/Part-4" - }, - "Previous": { - "Name": "The Book List Page", - "Path": "Tutorials/Part-2" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts: - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- **Part 3: Creating, updating and deleting books (this part)** -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -{{if UI == "MVC" && DB == "EF"}} - -### Video Tutorial - -This part is also recorded as a video tutorial and **published on YouTube**. - -{{end}} - -{{if UI == "MVC"}} - -## Creating a New Book - -In this section, you will learn how to create a new modal dialog form to create a new book. The modal dialog will look like the image below: - -![bookstore-create-dialog](./images/bookstore-create-dialog-3.png) - -### Create the Modal Form - -Create a new razor page named `CreateModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project. - -![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png) - -#### CreateModal.cshtml.cs - -Open the `CreateModal.cshtml.cs` file (`CreateModalModel` class) and replace it with the following code: - -````C# -using System.Threading.Tasks; -using Acme.BookStore.Books; -using Microsoft.AspNetCore.Mvc; - -namespace Acme.BookStore.Web.Pages.Books -{ - public class CreateModalModel : BookStorePageModel - { - [BindProperty] - public CreateUpdateBookDto Book { get; set; } - - private readonly IBookAppService _bookAppService; - - public CreateModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public void OnGet() - { - Book = new CreateUpdateBookDto(); - } - - public async Task OnPostAsync() - { - await _bookAppService.CreateAsync(Book); - return NoContent(); - } - } -} -```` - -* This class is derived from the `BookStorePageModel` instead of the standard `PageModel`. `BookStorePageModel` indirectly inherits the `PageModel` and adds some common properties & methods that can be shared in your page model classes. -* `[BindProperty]` attribute on the `Book` property binds post request data to this property. -* This class simply injects the `IBookAppService` in the constructor and calls the `CreateAsync` method in the `OnPostAsync` handler. -* It creates a new `CreateUpdateBookDto` object in the `OnGet` method. ASP.NET Core can work without creating a new instance like that. However, it doesn't create an instance for you and if your class has some default value assignments or code execution in the class constructor, they won't work. For this case, we set default values for some of the `CreateUpdateBookDto` properties. - -#### CreateModal.cshtml - -Open the `CreateModal.cshtml` file and paste the code below: - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Web.Pages.Books -@using Microsoft.Extensions.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model CreateModalModel -@inject IStringLocalizer L -@{ - Layout = null; -} - - - - - - - - - -```` - -* This modal uses `abp-dynamic-form` [tag helper](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) to automatically create the form from the `CreateUpdateBookDto` model class. -* `abp-model` attribute indicates the model object where it's the `Book` property in this case. -* `abp-form-content` tag helper is a placeholder to render the form controls (it is optional and needed only if you have added some other content in the `abp-dynamic-form` tag, just like in this page). - -> Tip: `Layout` should be `null` just as done in this example since we don't want to include all the layout for the modals when they are loaded via AJAX. - -### Add the "New book" Button - -Open the `Pages/Books/Index.cshtml` and set the content of `abp-card-header` tag as below: - -````html - - - - @L["Books"] - - - - - - -```` - -The final content of `Index.cshtml` is shown below: - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Web.Pages.Books -@using Microsoft.Extensions.Localization -@model IndexModel -@inject IStringLocalizer L -@section scripts -{ - -} - - - - - - @L["Books"] - - - - - - - - - - -```` - -This adds a new button called **New book** to the **top-right** of the table: - -![bookstore-new-book-button](./images/bookstore-new-book-button-3.png) - -Open the `Pages/Books/Index.js` file and add the following code right after the `Datatable` configuration: - -````js -var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - -createModal.onResult(function () { - dataTable.ajax.reload(); -}); - -$('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); -}); -```` - -* `abp.ModalManager` is a helper class to manage modals on the client side. It internally uses Twitter Bootstrap's standard modal, but abstracts many details by providing a simple API. -* `createModal.onResult(...)` used to refresh the data table after creating a new book. -* `createModal.open();` is used to open the model to create a new book. - -The final content of the `Index.js` file should be like this: - -````js -$(function () { - var l = abp.localization.getResource('BookStore'); - - var dataTable = $('#BooksTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), - columnDefs: [ - { - title: l('Name'), - data: "name" - }, - { - title: l('Type'), - data: "type", - render: function (data) { - return l('Enum:BookType.' + data); - } - }, - { - title: l('PublishDate'), - data: "publishDate", - dataFormat: "datetime" - }, - { - title: l('Price'), - data: "price" - }, - { - title: l('CreationTime'), data: "creationTime", - dataFormat: "datetime" - } - ] - }) - ); - - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); -```` - -Now, you can **run the application** and add some new books using the new modal form. - -## Updating a Book - -Create a new razor page, named `EditModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project: - -![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png) - -### EditModal.cshtml.cs - -Open the `EditModal.cshtml.cs` file (`EditModalModel` class) and replace it with the following code: - -````csharp -using System; -using System.Threading.Tasks; -using Acme.BookStore.Books; -using Microsoft.AspNetCore.Mvc; - -namespace Acme.BookStore.Web.Pages.Books; - -public class EditModalModel : BookStorePageModel -{ - [HiddenInput] - [BindProperty(SupportsGet = true)] - public Guid Id { get; set; } - - [BindProperty] - public CreateUpdateBookDto Book { get; set; } - - private readonly IBookAppService _bookAppService; - - public EditModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnGetAsync() - { - var bookDto = await _bookAppService.GetAsync(Id); - Book = ObjectMapper.Map(bookDto); - } - - public async Task OnPostAsync() - { - await _bookAppService.UpdateAsync(Id, Book); - return NoContent(); - } -} -```` - -* `[HiddenInput]` and `[BindProperty]` are standard ASP.NET Core MVC attributes. `SupportsGet` is used to be able to get the `Id` value from the query string parameter of the request. -* In the `OnGetAsync` method, we get the `BookDto` from the `BookAppService` and this is being mapped to the DTO object `CreateUpdateBookDto`. -* The `OnPostAsync` uses `BookAppService.UpdateAsync(...)` to update the entity. - -### Mapping from BookDto to CreateUpdateBookDto - -To be able to map the `BookDto` to `CreateUpdateBookDto`, configure a new mapping. To do this, open the `BookStoreWebAutoMapperProfile.cs` file in the `Acme.BookStore.Web` project and change it as shown below: - -````csharp -using AutoMapper; - -namespace Acme.BookStore.Web; - -public class BookStoreWebAutoMapperProfile : Profile -{ - public BookStoreWebAutoMapperProfile() - { - CreateMap(); - } -} -```` - -* We have just added `CreateMap();` to define this mapping. - -> Notice that we do the mapping definition in the web layer as a best practice since it is only needed in this layer. - -### EditModal.cshtml - -Replace `EditModal.cshtml` content with the following content: - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Web.Pages.Books -@using Microsoft.Extensions.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model EditModalModel -@inject IStringLocalizer L -@{ - Layout = null; -} - - - - - - - - - - -```` - -This page is very similar to `CreateModal.cshtml`, except: - -* It includes an `abp-input` for the `Id` property to store the `Id` of the editing book (which is a hidden input). -* It uses `Books/EditModal` as the post URL. - -### Add "Actions" Dropdown to the Table - -We will add a dropdown button to the table named *Actions*. - -Open the `Pages/Books/Index.js` file and replace the content as below: - -````js -$(function () { - var l = abp.localization.getResource('BookStore'); - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), - columnDefs: [ - { - title: l('Actions'), - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - } - ] - } - }, - { - title: l('Name'), - data: "name" - }, - { - title: l('Type'), - data: "type", - render: function (data) { - return l('Enum:BookType.' + data); - } - }, - { - title: l('PublishDate'), - data: "publishDate", - dataFormat: "datetime" - }, - { - title: l('Price'), - data: "price" - }, - { - title: l('CreationTime'), data: "creationTime", - dataFormat: "datetime" - } - ] - }) - ); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); -```` - -* Added a new `ModalManager` named `editModal` to open the edit modal dialog. -* Added a new column at the beginning of the `columnDefs` section. This column is used for the "*Actions*" dropdown button. -* The "*Edit*" action simply calls `editModal.open()` to open the edit dialog. -* The `editModal.onResult(...)` callback refreshes the data table when you close the edit modal. - -You can run the application and edit any book by selecting the edit action on a book. - -The final UI looks as below: - -![bookstore-books-table-actions](./images/bookstore-edit-button-3.png) - -> Notice that you don't see the "Actions" button in the figure below. Instead, you see an "Edit" button. ABP is smart enough to show a single simple button instead of a actions dropdown button when the dropdown has only a single item. After the next section, it will turn to a drop down button. - -## Deleting a Book - -Open the `Pages/Books/Index.js` file and add a new item to the `rowAction` `items`: - -````js -{ - text: l('Delete'), - confirmMessage: function (data) { - return l('BookDeletionConfirmationMessage', data.record.name); - }, - action: function (data) { - acme.bookStore.books.book - .delete(data.record.id) - .then(function() { - abp.notify.info(l('SuccessfullyDeleted')); - dataTable.ajax.reload(); - }); - } -} -```` - -* The `confirmMessage` option is used to ask a confirmation question before executing the `action`. -* The `acme.bookStore.books.book.delete(...)` method makes an AJAX request to the server to delete a book. -* `abp.notify.info()` shows a notification after the delete operation. - -Since we've used two new localization texts (`BookDeletionConfirmationMessage` and `SuccessfullyDeleted`) you need to add these to the localization file (`en.json` under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project): - -````json -"BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?", -"SuccessfullyDeleted": "Successfully deleted!" -```` - -The final `Index.js` content is shown below: - -````js -$(function () { - var l = abp.localization.getResource('BookStore'); - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), - columnDefs: [ - { - title: l('Actions'), - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - }, - { - text: l('Delete'), - confirmMessage: function (data) { - return l( - 'BookDeletionConfirmationMessage', - data.record.name - ); - }, - action: function (data) { - acme.bookStore.books.book - .delete(data.record.id) - .then(function() { - abp.notify.info( - l('SuccessfullyDeleted') - ); - dataTable.ajax.reload(); - }); - } - } - ] - } - }, - { - title: l('Name'), - data: "name" - }, - { - title: l('Type'), - data: "type", - render: function (data) { - return l('Enum:BookType.' + data); - } - }, - { - title: l('PublishDate'), - data: "publishDate", - dataFormat: "datetime" - }, - { - title: l('Price'), - data: "price" - }, - { - title: l('CreationTime'), data: "creationTime", - dataFormat: "datetime" - } - ] - }) - ); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); -```` - -You can run the application and try to delete a book. - -{{end}} - -{{if UI == "NG"}} - -## Creating a New Book - -In this section, you will learn how to create a new modal dialog form to create a new book. - -### BookComponent - -Open `/src/app/book/book.component.ts` and replace the content as below: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookService, BookDto } from '@proxy/books'; - -@Component({ - selector: 'app-book', - templateUrl: './book.component.html', - styleUrls: ['./book.component.scss'], - providers: [ListService], -}) -export class BookComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - isModalOpen = false; // add this line - - constructor(public readonly list: ListService, private bookService: BookService) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getList(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - // add new method - createBook() { - this.isModalOpen = true; - } -} -``` - -* We defined a property called `isModalOpen` and a method called `createBook`. - - -Open `/src/app/book/book.component.html` and make the following changes: - -```html -
    -
    -
    -
    -
    {%{{{ '::Menu:Books' | abpLocalization }}}%}
    -
    -
    - - -
    - -
    - -
    -
    -
    -
    - -
    -
    - - - - -

    {%{{{ '::NewBook' | abpLocalization }}}%}

    -
    - - - - - - -
    -``` - -* Added a `New book` button to the card header.. -* Added the `abp-modal` which renders a modal to allow user to create a new book. `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits. - -You can open your browser and click the **New book** button to see the new modal. - -![Empty modal for new book](./images/bookstore-empty-new-book-modal-2.png) - -### Create a Reactive Form - -[Reactive forms](https://angular.io/guide/reactive-forms) provide a model-driven approach to handling form inputs whose values change over time. - -Open `/src/app/book/book.component.ts` and replace the content as below: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; // add bookTypeOptions -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this - -@Component({ - selector: 'app-book', - templateUrl: './book.component.html', - styleUrls: ['./book.component.scss'], - providers: [ListService], -}) -export class BookComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - form: FormGroup; // add this line - - // add bookTypes as a list of BookType enum members - bookTypes = bookTypeOptions; - - isModalOpen = false; - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder // inject FormBuilder - ) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getList(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - createBook() { - this.buildForm(); // add this line - this.isModalOpen = true; - } - - // add buildForm method - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } - - // add save method - save() { - if (this.form.invalid) { - return; - } - - this.bookService.create(this.form.value).subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } -} -``` - -* Imported `FormGroup`, `FormBuilder` and `Validators` from `@angular/forms`. -* Added a `form: FormGroup` property. -* Added a `bookTypes` property as a list of `BookType` enum members. That will be used in form options. -* Injected `FormBuilder` into the constructor. [FormBuilder](https://angular.io/api/forms/FormBuilder) provides convenient methods for generating form controls. It reduces the amount of boilerplate needed to build complex forms. -* Added a `buildForm` method to the end of the file and executed the `buildForm()` in the `createBook` method. -* Added a `save` method. - -Open `/src/app/book/book.component.html` and replace ` ` with the following code part: - -```html - -
    -
    - * - -
    - -
    - * - -
    - -
    - * - -
    - -
    - * - -
    -
    -
    -``` - -Also replace ` ` with the following code part: - -````html - - - - - - -```` - -### Datepicker - -We've used [NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview) in this component. So, we need to arrange the dependencies related to this component. - -Open `/src/app/book/book.module.ts` and replace the content as below: - -```js -import { NgModule } from '@angular/core'; -import { SharedModule } from '../shared/shared.module'; -import { BookRoutingModule } from './book-routing.module'; -import { BookComponent } from './book.component'; -import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; // add this line - -@NgModule({ - declarations: [BookComponent], - imports: [ - BookRoutingModule, - SharedModule, - NgbDatepickerModule, // add this line - ] -}) -export class BookModule { } -``` - -* We imported `NgbDatepickerModule` to be able to use the date picker. - -Open `/src/app/book/book.component.ts` and replace the content as below: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; - -// added this line -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; - -@Component({ - selector: 'app-book', - templateUrl: './book.component.html', - styleUrls: ['./book.component.scss'], - providers: [ - ListService, - { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter } // add this line - ], -}) -export class BookComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - form: FormGroup; - - bookTypes = bookTypeOptions; - - isModalOpen = false; - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getList(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - createBook() { - this.buildForm(); - this.isModalOpen = true; - } - - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } - - save() { - if (this.form.invalid) { - return; - } - - this.bookService.create(this.form.value).subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } -} -``` - -* Imported ` NgbDateNativeAdapter` and `NgbDateAdapter`. -* We added a new provider `NgbDateAdapter` that converts the Datepicker value to `Date` type. Check out the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details. - -Now, you can open your browser to see the changes: - -![Save button to the modal](./images/bookstore-new-book-form-v3.png) - -## Updating a Book - -Open `/src/app/book/book.component.ts` and replace the content as shown below: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; - -@Component({ - selector: 'app-book', - templateUrl: './book.component.html', - styleUrls: ['./book.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], -}) -export class BookComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - selectedBook = {} as BookDto; // declare selectedBook - - form: FormGroup; - - bookTypes = bookTypeOptions; - - isModalOpen = false; - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getList(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - createBook() { - this.selectedBook = {} as BookDto; // reset the selected book - this.buildForm(); - this.isModalOpen = true; - } - - // Add editBook method - editBook(id: string) { - this.bookService.get(id).subscribe((book) => { - this.selectedBook = book; - this.buildForm(); - this.isModalOpen = true; - }); - } - - buildForm() { - this.form = this.fb.group({ - name: [this.selectedBook.name || '', Validators.required], - type: [this.selectedBook.type || null, Validators.required], - publishDate: [ - this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null, - Validators.required, - ], - price: [this.selectedBook.price || null, Validators.required], - }); - } - - // change the save method - save() { - if (this.form.invalid) { - return; - } - - const request = this.selectedBook.id - ? this.bookService.update(this.selectedBook.id, this.form.value) - : this.bookService.create(this.form.value); - - request.subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } -} -``` - -* We declared a variable named `selectedBook` as `BookDto`. -* We added an `editBook` method. This method fetches the book with the given `id` and sets it to `selectedBook` object. -* We replaced the `buildForm` method so that it creates the form with the `selectedBook` data. -* We replaced the `createBook` method so it sets `selectedBook` to an empty object. -* We changed the `save` method to handle both of create and update operations. - -### Add "Actions" Dropdown to the Table - -Open `/src/app/book/book.component.html`  and add the following `ngx-datatable-column` definition as the first column in the `ngx-datatable`: - -```html - - -
    - -
    - -
    -
    -
    -
    -``` - -Added an "Actions" dropdown as the first column of the table that is shown below: - -![Action buttons](./images/bookstore-actions-buttons-2.png) - -Also, change the `ng-template #abpHeader` section as shown below: - -```html - -

    {%{{{ (selectedBook.id ? '::Edit' : '::NewBook' ) | abpLocalization }}}%}

    -
    -``` - -This template will show the **Edit** text for edit record operation, **New Book** for new record operation in the title. - -## Deleting a Book - -Open the `/src/app/book/book.component.ts` file and inject the `ConfirmationService`. - -Replace the constructor as below: - -```js -// ... - -// add new imports -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; - -//change the constructor -constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder, - private confirmation: ConfirmationService // inject the ConfirmationService -) {} - -// Add a delete method -delete(id: string) { - this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => { - if (status === Confirmation.Status.confirm) { - this.bookService.delete(id).subscribe(() => this.list.get()); - } - }); -} -``` - -* We imported `ConfirmationService`. -* We injected `ConfirmationService` to the constructor. -* Added a `delete` method. - -> Check out the [Confirmation Popup documentation](../UI/Angular/Confirmation-Service) for more about this service. - -### Add a Delete Button - - -Open `/src/app/book/book.component.html` and modify the `ngbDropdownMenu` to add the delete button as shown below: - -```html -
    - - -
    -``` - -The final actions dropdown UI looks like below: - -![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown-2.png) - -Clicking the "Delete" action calls the `delete` method which then shows a confirmation popup as shown below: - -![bookstore-confirmation-popup](./images/bookstore-confirmation-popup-2.png) - -{{end}} - -{{if UI == "Blazor" || UI == "BlazorServer"}} - -## Creating a New Book - -In this section, you will learn how to create a new modal dialog form to create a new book. Since we've inherited from the `AbpCrudPageBase`, we only need to develop the view part. - -### Add a "New Button" Button - -Open the `Books.razor` and replace the `` section with the following code: - -````xml - - - -

    @L["Books"]

    -
    - - - -
    -
    -```` - -This will change the card header by adding a "New book" button to the right side: - -![blazor-add-book-button](./images/blazor-add-book-button-2.png) - -Now, we can add a modal that will be opened when we click the button. - -### Book Creation Modal - -Open the `Books.razor` and add the following code to the end of the page: - -````xml - - - -
    - - @L["NewBook"] - - - - - - - @L["Name"] - - - - - - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - - -
    -
    -
    -```` - -This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper` at the top of the file, just before the `@inherits...` line: - -````csharp -@inject AbpBlazorMessageLocalizerHelper LH -```` - -* The form implements validation and the `AbpBlazorMessageLocalizerHelper` is used to simply localize the validation messages. -* The `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` methods are defined by the base class. Check out the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components. - -That's all. Run the application and try to add a new book: - -![blazor-new-book-modal](./images/blazor-new-book-modal-2.png) - -## Updating a Book - -Editing a book is similar to creating a new book. - -### Actions Dropdown - -Open the `Books.razor` and add the following `DataGridEntityActionsColumn` section inside the `DataGridColumns` as the first item: - -````xml - - - - - - - -```` - -* `OpenEditModalAsync` is defined in the base class which takes the entity (book) to edit. - -The `DataGridEntityActionsColumn` component is used to show an "Actions" dropdown for each row in the `DataGrid`. The `DataGridEntityActionsColumn` shows a **single button** instead of a dropdown if there is only one available action inside it: - -![blazor-edit-book-action](./images/blazor-edit-book-action-3.png) - -### Edit Modal - -We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page: - -````xml - - - -
    - - @EditingEntity.Name - - - - - - - @L["Name"] - - - - - - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - - -
    -
    -
    -```` - -### AutoMapper Configuration - -The base `AbpCrudPageBase` uses the [object to object mapping](../Object-To-Object-Mapping.md) system to convert an incoming `BookDto` object to a `CreateUpdateBookDto` object. So, we need to define the mapping. - -Open the `BookStoreBlazorAutoMapperProfile` inside the `Acme.BookStore.Blazor.Client` project and change the content as the following: - -````csharp -using Acme.BookStore.Books; -using AutoMapper; - -namespace Acme.BookStore.Blazor.Client; - -public class BookStoreBlazorAutoMapperProfile : Profile -{ - public BookStoreBlazorAutoMapperProfile() - { - CreateMap(); - } -} -```` - -* We've just added the `CreateMap();` line to define the mapping. - -### Test the Editing Modal - -You can now run the application and try to edit a book. - -![blazor-edit-book-modal](./images/blazor-edit-book-modal-2.png) - -> Tip: Try to leave the *Name* field empty and submit the form to show the validation error message. - -## Deleting a Book - -Open the `Books.razor` page and add the following `EntityAction` code under the "Edit" action inside `EntityActions`: - -````xml - -```` - -* `DeleteEntityAsync` is defined in the base class that deletes the entity by performing a call to the server. -* `ConfirmationMessage` is a callback to show a confirmation message before executing the action. -* `GetDeleteConfirmationMessage` is defined in the base class. You can override this method (or pass another value to the `ConfirmationMessage` parameter) to customize the localization message. - -The "Actions" button becomes a dropdown since it has two actions now: - -![blazor-delete-book-action](./images/blazor-delete-book-action-2.png) - -Run the application and try to delete a book. - -## Full CRUD UI Code - -Here's the complete code to create the book management CRUD page, that has been developed in the last two parts: - -````xml -@page "/books" -@using Volo.Abp.Application.Dtos -@using Acme.BookStore.Books -@using Acme.BookStore.Localization -@using Microsoft.Extensions.Localization -@using Volo.Abp.AspNetCore.Components.Web -@inject IStringLocalizer L -@inject AbpBlazorMessageLocalizerHelper LH -@inherits AbpCrudPageBase - - - - - -

    @L["Books"]

    -
    - - - -
    -
    - - - - - - - - - - - - - - - @L[$"Enum:BookType.{context.Type}"] - - - - - @context.PublishDate.ToShortDateString() - - - - - - - @context.CreationTime.ToLongDateString() - - - - - -
    - - - - -
    - - @L["NewBook"] - - - - - - - @L["Name"] - - - - - - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - - -
    -
    -
    - - - - -
    - - @EditingEntity.Name - - - - - - - @L["Name"] - - - - - - - - - @L["Type"] - - - - @L["PublishDate"] - - - - @L["Price"] - - - - - - - - -
    -
    -
    -```` - -{{end}} - diff --git a/docs/en/Tutorials/Part-4.md b/docs/en/Tutorials/Part-4.md deleted file mode 100644 index 4172072c74..0000000000 --- a/docs/en/Tutorials/Part-4.md +++ /dev/null @@ -1,300 +0,0 @@ -# Web Application Development Tutorial - Part 4: Integration Tests -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Authorization", - "Path": "Tutorials/Part-5" - }, - "Previous": { - "Name": "Creating, Updating and Deleting Books", - "Path": "Tutorials/Part-3" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts; - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- **Part 4: Integration tests (this part)** -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -{{if UI == "MVC" && DB == "EF"}} - -### Video Tutorial - -This part is also recorded as a video tutorial and **published on YouTube**. - -{{end}} - -## Test Projects in the Solution - -This part covers the **server side** tests. There are several test projects in the solution: - -![bookstore-test-projects-v2](./images/bookstore-test-projects-mvc.png) - -> Test projects slightly differs based on your UI and Database selection. For example, if you select MongoDB, then the `Acme.BookStore.EntityFrameworkCore.Tests` will be `Acme.BookStore.MongoDB.Tests`. - -Each project is used to test the related project. Test projects use the following libraries for testing: - -* [Xunit](https://github.com/xunit/xunit) as the main test framework. -* [Shoudly](https://github.com/shouldly/shouldly) as the assertion library. -* [NSubstitute](http://nsubstitute.github.io/) as the mocking library. - -{{if DB=="EF"}} - -> The test projects are configured to use **SQLite in-memory** as the database. A separate database instance is created and seeded (with the [data seed system](../Data-Seeding.md)) to prepare a fresh database for every test. - -{{else if DB=="Mongo"}} - -> **[EphemeralMongo](https://github.com/asimmon/ephemeral-mongo)** library is used to mock the MongoDB database. A separate database instance is created and seeded (with the [data seed system](../Data-Seeding.md)) to prepare a fresh database for every test. - -{{end}} - -## Adding Test Data - -If you had created a data seed contributor as described in the [first part](Part-1.md), the same data will be available in your tests. So, you can skip this section. If you haven't created the seed contributor, you can use the `BookStoreTestDataSeedContributor` to seed the same data to be used in the tests below. - -## Testing the BookAppService - -Add a new test class, named `BookAppService_Tests` in the `Books` namespace (folder) of the `Acme.BookStore.Application.Tests` project: - -````csharp -using System; -using System.Linq; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Modularity; -using Volo.Abp.Validation; -using Xunit; - -namespace Acme.BookStore.Books; - -public abstract class BookAppService_Tests : BookStoreApplicationTestBase - where TStartupModule : IAbpModule -{ - private readonly IBookAppService _bookAppService; - - protected BookAppService_Tests() - { - _bookAppService = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_List_Of_Books() - { - //Act - var result = await _bookAppService.GetListAsync( - new PagedAndSortedResultRequestDto() - ); - - //Assert - result.TotalCount.ShouldBeGreaterThan(0); - result.Items.ShouldContain(b => b.Name == "1984"); - } -} -```` - -{{if DB == "EF"}} -Add a new implementation class of `BookAppService_Tests` class, named `EfCoreBookAppService_Tests` in the `EntityFrameworkCore\Applications\Books` namespace (folder) of the `Acme.BookStore.EntityFrameworkCore.Tests` project: - -````csharp -using Acme.BookStore.Books; -using Xunit; - -namespace Acme.BookStore.EntityFrameworkCore.Applications.Books; - -[Collection(BookStoreTestConsts.CollectionDefinitionName)] -public class EfCoreBookAppService_Tests : BookAppService_Tests -{ - -} -```` -{{end}} - -{{if DB == "Mongo"}} -Add a new implementation class of `BookAppService_Tests` class, named `MongoDBBookAppService_Tests` in the `MongoDb\Applications\Books` namespace (folder) of the `Acme.BookStore.MongoDB.Tests` project: - -````csharp -using Acme.BookStore.MongoDB; -using Acme.BookStore.Books; -using Xunit; - -namespace Acme.BookStore.MongoDb.Applications.Books; - -[Collection(BookStoreTestConsts.CollectionDefinitionName)] -public class MongoDBBookAppService_Tests : BookAppService_Tests -{ - -} -```` -{{end}} - -* `Should_Get_List_Of_Books` test simply uses `BookAppService.GetListAsync` method to get and check the list of books. -* We can safely check the book "1984" by its name, because we know that this books is available in the database since we've added it in the seed data. - -Add a new test method to the `BookAppService_Tests` class that creates a new **valid** book: - -````csharp -[Fact] -public async Task Should_Create_A_Valid_Book() -{ - //Act - var result = await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "New test book 42", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - - //Assert - result.Id.ShouldNotBe(Guid.Empty); - result.Name.ShouldBe("New test book 42"); -} -```` - -Add a new test that tries to create an invalid book and fails: - -````csharp -[Fact] -public async Task Should_Not_Create_A_Book_Without_Name() -{ - var exception = await Assert.ThrowsAsync(async () => - { - await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - }); - - exception.ValidationErrors - .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); -} -```` - -* Since the `Name` is empty, ABP will throw an `AbpValidationException`. - -The final test class should be as shown below: - -````csharp -using System; -using System.Linq; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Modularity; -using Volo.Abp.Validation; -using Xunit; - -namespace Acme.BookStore.Books; - -public abstract class BookAppService_Tests : BookStoreApplicationTestBase - where TStartupModule : IAbpModule -{ - private readonly IBookAppService _bookAppService; - - protected BookAppService_Tests() - { - _bookAppService = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_List_Of_Books() - { - //Act - var result = await _bookAppService.GetListAsync( - new PagedAndSortedResultRequestDto() - ); - - //Assert - result.TotalCount.ShouldBeGreaterThan(0); - result.Items.ShouldContain(b => b.Name == "1984"); - } - - [Fact] - public async Task Should_Create_A_Valid_Book() - { - //Act - var result = await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "New test book 42", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - - //Assert - result.Id.ShouldNotBe(Guid.Empty); - result.Name.ShouldBe("New test book 42"); - } - - [Fact] - public async Task Should_Not_Create_A_Book_Without_Name() - { - var exception = await Assert.ThrowsAsync(async () => - { - await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - }); - - exception.ValidationErrors - .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); - } -} -```` - -Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests: - -![bookstore-appservice-tests](./images/bookstore-appservice-tests.png) - -Congratulations, the **green icons** indicates that the tests have been successfully passed! \ No newline at end of file diff --git a/docs/en/Tutorials/Part-5.md b/docs/en/Tutorials/Part-5.md deleted file mode 100644 index c881df7294..0000000000 --- a/docs/en/Tutorials/Part-5.md +++ /dev/null @@ -1,580 +0,0 @@ -# Web Application Development Tutorial - Part 5: Authorization -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Authors: Domain Layer", - "Path": "Tutorials/Part-6" - }, - "Previous": { - "Name": "Integration Tests", - "Path": "Tutorials/Part-4" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts; - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- **Part 5: Authorization (this part)** -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -{{if UI == "MVC" && DB == "EF"}} - -### Video Tutorial - -This part is also recorded as a video tutorial and **published on YouTube**. - -{{end}} - -## Permissions - -ABP Framework provides an [authorization system](../Authorization.md) based on the ASP.NET Core's [authorization infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). One major feature added on top of the standard authorization infrastructure is the **permission system** which allows to define permissions and enable/disable per role, user or client. - -### Permission Names - -A permission must have a unique name (a `string`). The best way is to define it as a `const`, so we can reuse the permission name. - -Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: - -````csharp -namespace Acme.BookStore.Permissions; - -public static class BookStorePermissions -{ - public const string GroupName = "BookStore"; - - public static class Books - { - public const string Default = GroupName + ".Books"; - public const string Create = Default + ".Create"; - public const string Edit = Default + ".Edit"; - public const string Delete = Default + ".Delete"; - } -} -```` - -This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as `BookStore.Books.Create`. ABP doesn't force you to a structure, but we find this way useful. - -### Permission Definitions - -You should define permissions before using them. - -Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: - -````csharp -using Acme.BookStore.Localization; -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Localization; - -namespace Acme.BookStore.Permissions; - -public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider -{ - public override void Define(IPermissionDefinitionContext context) - { - var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore")); - - var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books")); - booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create")); - booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit")); - booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete")); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } -} -```` - -This class defines a **permission group** (to group permissions on the UI, will be seen below) and **4 permissions** inside this group. Also, **Create**, **Edit** and **Delete** are children of the `BookStorePermissions.Books.Default` permission. A child permission can be selected **only if the parent was selected**. - -Finally, edit the localization file (`en.json` under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project) to define the localization keys used above: - -````json -"Permission:BookStore": "Book Store", -"Permission:Books": "Book Management", -"Permission:Books.Create": "Creating new books", -"Permission:Books.Edit": "Editing the books", -"Permission:Books.Delete": "Deleting the books" -```` - -> Localization key names are arbitrary and there is no forcing rule. But we prefer the convention used above. - -### Permission Management UI - -Once you define the permissions, you can see them on the **permission management modal**. - -Go to the *Administration -> Identity -> Roles* page, select *Permissions* action for the admin role to open the permission management modal: - -![bookstore-permissions-ui](images/bookstore-permissions-ui-2.png) - -Grant the permissions you want and save the modal. - -> **Tip**: New permissions are automatically granted to the admin role if you run the `Acme.BookStore.DbMigrator` application. - -## Authorization - -Now, you can use the permissions to authorize the book management. - -### Application Layer & HTTP API - -Open the `BookAppService` class and set the policy names as the permission names defined above: - -````csharp -using System; -using Acme.BookStore.Permissions; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore.Books; - -public class BookAppService : - CrudAppService< - Book, //The Book entity - BookDto, //Used to show books - Guid, //Primary key of the book entity - PagedAndSortedResultRequestDto, //Used for paging/sorting - CreateUpdateBookDto>, //Used to create/update a book - IBookAppService //implement the IBookAppService -{ - public BookAppService(IRepository repository) - : base(repository) - { - GetPolicyName = BookStorePermissions.Books.Default; - GetListPolicyName = BookStorePermissions.Books.Default; - CreatePolicyName = BookStorePermissions.Books.Create; - UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Delete; - } -} -```` - -Added code to the constructor. Base `CrudAppService` automatically uses these permissions on the CRUD operations. This makes the **application service** secure, but also makes the **HTTP API** secure since this service is automatically used as an HTTP API as explained before (see [auto API controllers](../API/Auto-API-Controllers.md)). - -> You will see the declarative authorization, using the `[Authorize(...)]` attribute, later while developing the author management functionality. - -{{if UI == "MVC"}} - -### Razor Page - -While securing the HTTP API & the application service prevents unauthorized users to use the services, they can still navigate to the book management page. While they will get authorization exception when the page makes the first AJAX call to the server, we should also authorize the page for a better user experience and security. - -Open the `BookStoreWebModule` and add the following code block inside the `ConfigureServices` method: - -````csharp -Configure(options => -{ - options.Conventions.AuthorizePage("/Books/Index", BookStorePermissions.Books.Default); - options.Conventions.AuthorizePage("/Books/CreateModal", BookStorePermissions.Books.Create); - options.Conventions.AuthorizePage("/Books/EditModal", BookStorePermissions.Books.Edit); -}); -```` - -Now, unauthorized users are redirected to the **login page**. - -#### Hide the New Book Button - -The book management page has a *New Book* button that should be invisible if the current user has no *Book Creation* permission. - -![bookstore-new-book-button-small](images/bookstore-new-book-button-small-2.png) - -Open the `Pages/Books/Index.cshtml` file and change the content as shown below: - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Permissions -@using Acme.BookStore.Web.Pages.Books -@using Microsoft.AspNetCore.Authorization -@using Microsoft.Extensions.Localization -@model IndexModel -@inject IStringLocalizer L -@inject IAuthorizationService AuthorizationService -@section scripts -{ - -} - - - - - - @L["Books"] - - - @if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create)) - { - - } - - - - - - - -```` - -* Added `@inject IAuthorizationService AuthorizationService` to access to the authorization service. -* Used `@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))` to check the book creation permission to conditionally render the *New Book* button. - -### JavaScript Side - -Books table in the book management page has an actions button for each row. The actions button includes *Edit* and *Delete* actions: - -![bookstore-edit-delete-actions](images/bookstore-edit-delete-actions-2.png) - -We should hide an action if the current user has not granted for the related permission. Datatables row actions has a `visible` option that can be set to `false` to hide the action item. - -Open the `Pages/Books/Index.js` inside the `Acme.BookStore.Web` project and add a `visible` option to the `Edit` action as shown below: - -````js -{ - text: l('Edit'), - visible: abp.auth.isGranted('BookStore.Books.Edit'), //CHECK for the PERMISSION - action: function (data) { - editModal.open({ id: data.record.id }); - } -} -```` - -Do same for the `Delete` action: - -````js -visible: abp.auth.isGranted('BookStore.Books.Delete') -```` - -* `abp.auth.isGranted(...)` is used to check a permission that is defined before. -* `visible` could also be get a function that returns a `bool` if the value will be calculated later, based on some conditions. - -### Menu Item - -Even we have secured all the layers of the book management page, it is still visible on the main menu of the application. We should hide the menu item if the current user has no permission. - -Open the `BookStoreMenuContributor` class, find the code block below: - -````csharp -context.Menu.AddItem( - new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" - ).AddItem( - new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/Books" - ) - ) -); -```` - -And replace this code block with the following: - -````csharp -context.Menu.AddItem( - new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" - ).AddItem( - new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/Books" - ).RequirePermissions(BookStorePermissions.Books.Default) // Check the permission! - ) -); -```` - -We've only added the `.RequirePermissions(BookStorePermissions.Books.Default)` extension method call for the inner menu item. - -{{else if UI == "NG"}} - -### Angular Guard Configuration - -First step of the UI is to prevent unauthorized users to see the "Books" menu item and enter to the book management page. - -Open the `/src/app/book/book-routing.module.ts` and replace with the following content: - -````js -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { authGuard, permissionGuard } from '@abp/ng.core'; -import { BookComponent } from './book.component'; - -const routes: Routes = [ - { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class BookRoutingModule {} -```` - -* Imported `authGuard` and `permissionGuard` from the `@abp/ng.core`. -* Added `canActivate: [authGuard, permissionGuard]` to the route definition. - -Open the `/src/app/route.provider.ts` and add `requiredPolicy: 'BookStore.Books'` to the `/books` route. The `/books` route block should be following: - -````js -{ - path: '/books', - name: '::Menu:Books', - parentName: '::Menu:BookStore', - layout: eLayoutType.application, - requiredPolicy: 'BookStore.Books', -} -```` - -### Hide the New Book Button - -The book management page has a *New Book* button that should be invisible if the current user has no *Book Creation* permission. - -![bookstore-new-book-button-small](images/bookstore-new-book-button-small.png) - -Open the `/src/app/book/book.component.html` file and replace the create button HTML content as shown below: - -````html - - -```` - -* Just added `*abpPermission="'BookStore.Books.Create'"` that hides the button if the current user has no permission. - -### Hide the Edit and Delete Actions - -Books table in the book management page has an actions button for each row. The actions button includes *Edit* and *Delete* actions: - -![bookstore-edit-delete-actions](images/bookstore-edit-delete-actions-2.png) - -We should hide an action if the current user has not granted for the related permission. - -Open the `/src/app/book/book.component.html` file and replace the edit and delete buttons contents as shown below: - -````html - - - - - -```` - -* Added `*abpPermission="'BookStore.Books.Edit'"` that hides the edit action if the current user has no editing permission. -* Added `*abpPermission="'BookStore.Books.Delete'"` that hides the delete action if the current user has no delete permission. - -{{else if UI == "Blazor"}} - -### Authorize the Razor Component - -Open the `/Pages/Books.razor` file in the `Acme.BookStore.Blazor.Client` project and add an `Authorize` attribute just after the `@page` directive and the following namespace imports (`@using` lines), as shown below: - -````html -@page "/books" -@attribute [Authorize(BookStorePermissions.Books.Default)] -@using Acme.BookStore.Permissions -@using Microsoft.AspNetCore.Authorization -... -```` - -Adding this attribute prevents to enter this page if the current hasn't logged in or hasn't granted for the given permission. In case of attempt, the user is redirected to the login page. - -### Show/Hide the Actions - -The book management page has a *New Book* button and *Edit* and *Delete* actions for each book. We should hide these buttons/actions if the current user has not granted for the related permissions. - -The base `AbpCrudPageBase` class already has the necessary functionality for these kind of operations. - -#### Set the Policy (Permission) Names - -Add the following code block to the end of the `Books.razor` file: - -````csharp -@code -{ - public Books() // Constructor - { - CreatePolicyName = BookStorePermissions.Books.Create; - UpdatePolicyName = BookStorePermissions.Books.Edit; - DeletePolicyName = BookStorePermissions.Books.Delete; - } -} -```` - -The base `AbpCrudPageBase` class automatically checks these permissions on the related operations. It also defines the given properties for us if we need to check them manually: - -* `HasCreatePermission`: True, if the current user has permission to create the entity. -* `HasUpdatePermission`: True, if the current user has permission to edit/update the entity. -* `HasDeletePermission`: True, if the current user has permission to delete the entity. - -> **Blazor Tip**: While adding the C# code into a `@code` block is fine for small code parts, it is suggested to use the code behind approach to develop a more maintainable code base when the code block becomes longer. We will use this approach for the authors part. - -#### Hide the New Book Button - -Wrap the *New Book* button by an `if` block as shown below: - -````xml -@if (HasCreatePermission) -{ - -} -```` - -#### Hide the Edit/Delete Actions - -`EntityAction` component defines `Visible` attribute (parameter) to conditionally show the action. - -Update the `EntityActions` section as shown below: - -````xml - - - - -```` - -#### About the Permission Caching - -You can run and test the permissions. Remove a book related permission from the admin role to see the related button/action disappears from the UI. - -**ABP Framework caches the permissions** of the current user in the client side. So, when you change a permission for yourself, you need to manually **refresh the page** to take the effect. If you don't refresh and try to use the prohibited action you get an HTTP 403 (forbidden) response from the server. - -> Changing a permission for a role or user immediately available on the server side. So, this cache system doesn't cause any security problem. - -### Menu Item - -Even we have secured all the layers of the book management page, it is still visible on the main menu of the application. We should hide the menu item if the current user has no permission. - -Open the `BookStoreMenuContributor` class in the `Acme.BookStore.Blazor.Client` project, find the code block below: - -````csharp -context.Menu.AddItem( - new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" - ).AddItem( - new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/books" - ) - ) -); -```` - -And replace this code block with the following: - -````csharp -var bookStoreMenu = new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" -); - -context.Menu.AddItem(bookStoreMenu); - -//CHECK the PERMISSION -if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) -{ - bookStoreMenu.AddItem(new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/books" - )); -} -```` - -You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return value. The final `ConfigureMainMenuAsync` method should be the following: - -````csharp -private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) -{ - var l = context.GetLocalizer(); - - context.Menu.Items.Insert( - 0, - new ApplicationMenuItem( - "BookStore.Home", - l["Menu:Home"], - "/", - icon: "fas fa-home" - ) - ); - - var bookStoreMenu = new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" - ); - - context.Menu.AddItem(bookStoreMenu); - - //CHECK the PERMISSION - if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) - { - bookStoreMenu.AddItem(new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/books" - )); - } -} -```` - -{{end}} \ No newline at end of file diff --git a/docs/en/Tutorials/Part-6.md b/docs/en/Tutorials/Part-6.md deleted file mode 100644 index 55b9d2689e..0000000000 --- a/docs/en/Tutorials/Part-6.md +++ /dev/null @@ -1,281 +0,0 @@ -# Web Application Development Tutorial - Part 6: Authors: Domain Layer -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Authors: Database Integration", - "Path": "Tutorials/Part-7" - }, - "Previous": { - "Name": "Authorization", - "Path": "Tutorials/Part-5" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts; - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- **Part 6: Authors: Domain layer (this part)** -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -## Introduction - -In the previous parts, we've used the ABP infrastructure to easily build some services; - -* Used the [CrudAppService](../Application-Services.md) base class instead of manually developing an application service for standard create, read, update and delete operations. -* Used [generic repositories](../Repositories.md) to completely automate the database layer. - -For the "Authors" part; - -* We will **do some of the things manually** to show how you can do it in case of need. -* We will implement some **Domain Driven Design (DDD) best practices**. - -> **The development will be done layer by layer to concentrate on an individual layer in one time. In a real project, you will develop your application feature by feature (vertical) as done in the previous parts. In this way, you will experience both approaches.** - -## The Author Entity - -Create an `Authors` folder (namespace) in the `Acme.BookStore.Domain` project and add an `Author` class inside it: - -````csharp -using System; -using JetBrains.Annotations; -using Volo.Abp; -using Volo.Abp.Domain.Entities.Auditing; - -namespace Acme.BookStore.Authors; - -public class Author : FullAuditedAggregateRoot -{ - public string Name { get; private set; } - public DateTime BirthDate { get; set; } - public string ShortBio { get; set; } - - private Author() - { - /* This constructor is for deserialization / ORM purpose */ - } - - internal Author( - Guid id, - string name, - DateTime birthDate, - string? shortBio = null) - : base(id) - { - SetName(name); - BirthDate = birthDate; - ShortBio = shortBio; - } - - internal Author ChangeName(string name) - { - SetName(name); - return this; - } - - private void SetName(string name) - { - Name = Check.NotNullOrWhiteSpace( - name, - nameof(name), - maxLength: AuthorConsts.MaxNameLength - ); - } -} -```` - -* Inherited from `FullAuditedAggregateRoot` which makes the entity [soft delete](../Data-Filtering.md) (that means when you delete it, it is not deleted in the database, but just marked as deleted) with all the [auditing](../Entities.md) properties. -* `private set` for the `Name` property restricts to set this property from out of this class. There are two ways of setting the name (in both cases, we validate the name): - * In the constructor, while creating a new author. - * Using the `ChangeName` method to update the name later. -* The `constructor` and the `ChangeName` method is `internal` to force to use these methods only in the domain layer, using the `AuthorManager` that will be explained later. -* `Check` class is an ABP Framework utility class to help you while checking method arguments (it throws `ArgumentException` on an invalid case). - -`AuthorConsts` is a simple class that is located under the `Authors` namespace (folder) of the `Acme.BookStore.Domain.Shared` project: - -````csharp -namespace Acme.BookStore.Authors; - -public static class AuthorConsts -{ - public const int MaxNameLength = 64; -} - -```` - -Created this class inside the `Acme.BookStore.Domain.Shared` project since we will re-use it on the [Data Transfer Objects](../Data-Transfer-Objects.md) (DTOs) later. - -## AuthorManager: The Domain Service - -`Author` constructor and `ChangeName` methods are `internal`, so they can be used only in the domain layer. Create an `AuthorManager` class in the `Authors` folder (namespace) of the `Acme.BookStore.Domain` project: - -````csharp -using System; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Volo.Abp; -using Volo.Abp.Domain.Services; - -namespace Acme.BookStore.Authors; - -public class AuthorManager : DomainService -{ - private readonly IAuthorRepository _authorRepository; - - public AuthorManager(IAuthorRepository authorRepository) - { - _authorRepository = authorRepository; - } - - public async Task CreateAsync( - string name, - DateTime birthDate, - string? shortBio = null) - { - Check.NotNullOrWhiteSpace(name, nameof(name)); - - var existingAuthor = await _authorRepository.FindByNameAsync(name); - if (existingAuthor != null) - { - throw new AuthorAlreadyExistsException(name); - } - - return new Author( - GuidGenerator.Create(), - name, - birthDate, - shortBio - ); - } - - public async Task ChangeNameAsync( - Author author, - string newName) - { - Check.NotNull(author, nameof(author)); - Check.NotNullOrWhiteSpace(newName, nameof(newName)); - - var existingAuthor = await _authorRepository.FindByNameAsync(newName); - if (existingAuthor != null && existingAuthor.Id != author.Id) - { - throw new AuthorAlreadyExistsException(newName); - } - - author.ChangeName(newName); - } -} -```` - -* `AuthorManager` forces to create an author and change name of an author in a controlled way. The application layer (will be introduced later) will use these methods. - -> **DDD tip**: Do not introduce domain service methods unless they are really needed and perform some core business rules. For this case, we needed this service to be able to force the unique name constraint. - -Both methods checks if there is already an author with the given name and throws a special business exception, `AuthorAlreadyExistsException`, defined in the `Acme.BookStore.Domain` project (in the `Authors` folder) as shown below: - -````csharp -using Volo.Abp; - -namespace Acme.BookStore.Authors; - -public class AuthorAlreadyExistsException : BusinessException -{ - public AuthorAlreadyExistsException(string name) - : base(BookStoreDomainErrorCodes.AuthorAlreadyExists) - { - WithData("name", name); - } -} -```` - -`BusinessException` is a special exception type. It is a good practice to throw domain related exceptions when needed. It is automatically handled by the ABP Framework and can be easily localized. `WithData(...)` method is used to provide additional data to the exception object that will later be used on the localization message or for some other purpose. - -Open the `BookStoreDomainErrorCodes` in the `Acme.BookStore.Domain.Shared` project and change as shown below: - -````csharp -namespace Acme.BookStore; - -public static class BookStoreDomainErrorCodes -{ - public const string AuthorAlreadyExists = "BookStore:00001"; -} -```` - -This is a unique string represents the error code thrown by your application and can be handled by client applications. For users, you probably want to localize it. Open the `Localization/BookStore/en.json` inside the `Acme.BookStore.Domain.Shared` project and add the following entry: - -````json -"BookStore:00001": "There is already an author with the same name: {name}" -```` - -Whenever you throw an `AuthorAlreadyExistsException`, the end user will see a nice error message on the UI. - -## IAuthorRepository - -`AuthorManager` injects the `IAuthorRepository`, so we need to define it. Create this new interface in the `Authors` folder (namespace) of the `Acme.BookStore.Domain` project: - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore.Authors; - -public interface IAuthorRepository : IRepository -{ - Task FindByNameAsync(string name); - - Task> GetListAsync( - int skipCount, - int maxResultCount, - string sorting, - string filter = null - ); -} -```` - -* `IAuthorRepository` extends the standard `IRepository` interface, so all the standard [repository](../Repositories.md) methods will also be available for the `IAuthorRepository`. -* `FindByNameAsync` was used in the `AuthorManager` to query an author by name. -* `GetListAsync` will be used in the application layer to get a listed, sorted and filtered list of authors to show on the UI. - -We will implement this repository in the next part. - -> Both of these methods might **seem unnecessary** since the standard repositories already provide generic querying methods and you can easily use them instead of defining such custom methods. You're right and do it like in a real application. However, for this **"learning" tutorial**, it is useful to explain how to create custom repository methods when you really need it. - -## Conclusion - -This part covered the domain layer of the authors functionality of the book store application. The main files created/updated in this part was highlighted in the picture below: - -![bookstore-author-domain-layer](images/bookstore-author-domain-layer.png) \ No newline at end of file diff --git a/docs/en/Tutorials/Part-7.md b/docs/en/Tutorials/Part-7.md deleted file mode 100644 index 9f0c51b5e9..0000000000 --- a/docs/en/Tutorials/Part-7.md +++ /dev/null @@ -1,244 +0,0 @@ -# Web Application Development Tutorial - Part 7: Authors: Database Integration -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Authors: Application Layer", - "Path": "Tutorials/Part-8" - }, - "Previous": { - "Name": "Authors: Domain Layer", - "Path": "Tutorials/Part-6" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts; - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- **Part 7: Authors: Database Integration (this part)** -- [Part 8: Authors: Application Layer](Part-8.md) -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -## Introduction - -This part explains how to configure the database integration for the `Author` entity introduced in the previous part. - -{{if DB=="EF"}} - -## DB Context - -Open the `BookStoreDbContext` in the `Acme.BookStore.EntityFrameworkCore` project and add the following `DbSet` property: - -````csharp -public DbSet Authors { get; set; } -```` - -Then locate to the `OnModelCreating` method in `BookStoreDbContext` class in the same project and add the following lines to the end of the method: - -````csharp -builder.Entity(b => -{ - b.ToTable(BookStoreConsts.DbTablePrefix + "Authors", - BookStoreConsts.DbSchema); - - b.ConfigureByConvention(); - - b.Property(x => x.Name) - .IsRequired() - .HasMaxLength(AuthorConsts.MaxNameLength); - - b.HasIndex(x => x.Name); -}); -```` - -This is just like done for the `Book` entity before, so no need to explain again. - -## Create a new Database Migration - -The startup solution is configured to use [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. - -Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore` project and type the following command: - -````bash -dotnet ef migrations add Added_Authors -```` - -This will add a new migration class to the project: - -![bookstore-efcore-migration-authors](./images/bookstore-efcore-migration-authors.png) - -You can apply changes to the database using the following command, in the same command-line terminal: - -````bash -dotnet ef database update -```` - -> If you are using Visual Studio, you may want to use the `Add-Migration Added_Authors` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that `Acme.BookStore.EntityFrameworkCore` is the startup project in Visual Studio and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. - -{{else if DB=="Mongo"}} - -## DB Context - -Open the `BookStoreMongoDbContext` in the `MongoDb` folder of the `Acme.BookStore.MongoDB` project and add the following property to the class: - -````csharp -public IMongoCollection Authors => Collection(); -```` - -{{end}} - -## Implementing the IAuthorRepository - -{{if DB=="EF"}} - -Create a new class, named `EfCoreAuthorRepository` inside the `Acme.BookStore.EntityFrameworkCore` project (in the `Authors` folder) and paste the following code: - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Dynamic.Core; -using System.Threading.Tasks; -using Acme.BookStore.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Volo.Abp.Domain.Repositories.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore; - -namespace Acme.BookStore.Authors; - -public class EfCoreAuthorRepository - : EfCoreRepository, - IAuthorRepository -{ - public EfCoreAuthorRepository( - IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task FindByNameAsync(string name) - { - var dbSet = await GetDbSetAsync(); - return await dbSet.FirstOrDefaultAsync(author => author.Name == name); - } - - public async Task> GetListAsync( - int skipCount, - int maxResultCount, - string sorting, - string filter = null) - { - var dbSet = await GetDbSetAsync(); - return await dbSet - .WhereIf( - !filter.IsNullOrWhiteSpace(), - author => author.Name.Contains(filter) - ) - .OrderBy(sorting) - .Skip(skipCount) - .Take(maxResultCount) - .ToListAsync(); - } -} -```` - -* Inherited from the `EfCoreRepository`, so it inherits the standard repository method implementations. -* `WhereIf` is a shortcut extension method of the ABP Framework. It adds the `Where` condition only if the first condition meets (it filters by name, only if the filter was provided). You could do the same yourself, but these type of shortcut methods makes our life easier. -* `sorting` can be a string like `Name`, `Name ASC` or `Name DESC`. It is possible by using the [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet package. - -> See the [EF Core Integration document](../Entity-Framework-Core.md) for more information on the EF Core based repositories. - -{{else if DB=="Mongo"}} - -Create a new class, named `MongoDbAuthorRepository` inside the `Acme.BookStore.MongoDB` project (in the `Authors` folder) and paste the following code: - -```csharp -using System; -using System.Linq; -using System.Linq.Dynamic.Core; -using System.Collections.Generic; -using System.Threading.Tasks; -using Acme.BookStore.MongoDB; -using MongoDB.Driver; -using MongoDB.Driver.Linq; -using Volo.Abp.Domain.Repositories.MongoDB; -using Volo.Abp.MongoDB; - -namespace Acme.BookStore.Authors; - -public class MongoDbAuthorRepository - : MongoDbRepository, - IAuthorRepository -{ - public MongoDbAuthorRepository( - IMongoDbContextProvider dbContextProvider - ) : base(dbContextProvider) - { - } - - public async Task FindByNameAsync(string name) - { - var queryable = await GetMongoQueryableAsync(); - return await queryable.FirstOrDefaultAsync(author => author.Name == name); - } - - public async Task> GetListAsync( - int skipCount, - int maxResultCount, - string sorting, - string filter = null) - { - var queryable = await GetMongoQueryableAsync(); - return await queryable - .WhereIf>( - !filter.IsNullOrWhiteSpace(), - author => author.Name.Contains(filter) - ) - .OrderBy(sorting) - .As>() - .Skip(skipCount) - .Take(maxResultCount) - .ToListAsync(); - } -} -``` - -* Inherited from the `MongoDbRepository`, so it inherits the standard repository method implementations. -* `WhereIf` is a shortcut extension method of the ABP Framework. It adds the `Where` condition only if the first condition meets (it filters by name, only if the filter was provided). You could do the same yourself, but these type of shortcut methods makes our life easier. -* `sorting` can be a string like `Name`, `Name ASC` or `Name DESC`. It is possible by using the [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet package. - -> See the [MongoDB Integration document](../MongoDB.md) for more information on the MongoDB based repositories. - -{{end}} \ No newline at end of file diff --git a/docs/en/Tutorials/Part-8.md b/docs/en/Tutorials/Part-8.md deleted file mode 100644 index 5263cbc44e..0000000000 --- a/docs/en/Tutorials/Part-8.md +++ /dev/null @@ -1,606 +0,0 @@ -# Web Application Development Tutorial - Part 8: Authors: Application Layer -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Authors: User Interface", - "Path": "Tutorials/Part-9" - }, - "Previous": { - "Name": "Authors: Database Integration", - "Path": "Tutorials/Part-7" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts; - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- **Part 8: Author: Application Layer (this part)** -- [Part 9: Authors: User Interface](Part-9.md) -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -## Introduction - -This part explains to create an application layer for the `Author` entity created before. - -## IAuthorAppService - -We will first create the [application service](../Application-Services.md) interface and the related [DTO](../Data-Transfer-Objects.md)s. Create a new interface, named `IAuthorAppService`, in the `Authors` namespace (folder) of the `Acme.BookStore.Application.Contracts` project: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Acme.BookStore.Authors; - -public interface IAuthorAppService : IApplicationService -{ - Task GetAsync(Guid id); - - Task> GetListAsync(GetAuthorListDto input); - - Task CreateAsync(CreateAuthorDto input); - - Task UpdateAsync(Guid id, UpdateAuthorDto input); - - Task DeleteAsync(Guid id); -} -```` - -* `IApplicationService` is a conventional interface that is inherited by all the application services, so the ABP Framework can identify the service. -* Defined standard methods to perform CRUD operations on the `Author` entity. -* `PagedResultDto` is a pre-defined DTO class in the ABP Framework. It has an `Items` collection and a `TotalCount` property to return a paged result. -* Preferred to return an `AuthorDto` (for the newly created author) from the `CreateAsync` method, while it is not used by this application - just to show a different usage. - -This interface is using the DTOs defined below (create them for your project). - -### AuthorDto - -````csharp -using System; -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore.Authors; - -public class AuthorDto : EntityDto -{ - public string Name { get; set; } - - public DateTime BirthDate { get; set; } - - public string ShortBio { get; set; } -} -```` - -* `EntityDto` simply has an `Id` property with the given generic argument. You could create an `Id` property yourself instead of inheriting the `EntityDto`. - -### GetAuthorListDto - -````csharp -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore.Authors; - -public class GetAuthorListDto : PagedAndSortedResultRequestDto -{ - public string? Filter { get; set; } -} -```` - -* `Filter` is used to search authors. It can be `null` (or empty string) to get all the authors. -* `PagedAndSortedResultRequestDto` has the standard paging and sorting properties: `int MaxResultCount`, `int SkipCount` and `string Sorting`. - -> ABP Framework has such base DTO classes to simplify and standardize your DTOs. See the [DTO documentation](../Data-Transfer-Objects.md) for all. - -### CreateAuthorDto - -````csharp -using System; -using System.ComponentModel.DataAnnotations; - -namespace Acme.BookStore.Authors; - -public class CreateAuthorDto -{ - [Required] - [StringLength(AuthorConsts.MaxNameLength)] - public string Name { get; set; } = string.Empty; - - [Required] - public DateTime BirthDate { get; set; } - - public string? ShortBio { get; set; } -} -```` - -Data annotation attributes can be used to validate the DTO. See the [validation document](../Validation.md) for details. - -### UpdateAuthorDto - -````csharp -using System; -using System.ComponentModel.DataAnnotations; - -namespace Acme.BookStore.Authors; - -public class UpdateAuthorDto -{ - [Required] - [StringLength(AuthorConsts.MaxNameLength)] - public string Name { get; set; } = string.Empty; - - [Required] - public DateTime BirthDate { get; set; } - - public string? ShortBio { get; set; } -} -```` - -> We could share (re-use) the same DTO among the create and the update operations. While you can do it, we prefer to create different DTOs for these operations since we see they generally be different by the time. So, code duplication is reasonable here compared to a tightly coupled design. - -## AuthorAppService - -It is time to implement the `IAuthorAppService` interface. Create a new class, named `AuthorAppService` in the `Authors` namespace (folder) of the `Acme.BookStore.Application` project: - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Acme.BookStore.Permissions; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore.Authors; - -[Authorize(BookStorePermissions.Authors.Default)] -public class AuthorAppService : BookStoreAppService, IAuthorAppService -{ - private readonly IAuthorRepository _authorRepository; - private readonly AuthorManager _authorManager; - - public AuthorAppService( - IAuthorRepository authorRepository, - AuthorManager authorManager) - { - _authorRepository = authorRepository; - _authorManager = authorManager; - } - - //...SERVICE METHODS WILL COME HERE... -} -```` - -* `[Authorize(BookStorePermissions.Authors.Default)]` is a declarative way to check a permission (policy) to authorize the current user. See the [authorization document](../Authorization.md) for more. `BookStorePermissions` class will be updated below, don't worry for the compile error for now. -* Derived from the `BookStoreAppService`, which is a simple base class comes with the startup template. It is derived from the standard `ApplicationService` class. -* Implemented the `IAuthorAppService` which was defined above. -* Injected the `IAuthorRepository` and `AuthorManager` to use in the service methods. - -Now, we will introduce the service methods one by one. Copy the explained method into the `AuthorAppService` class. - -### GetAsync - -````csharp -public async Task GetAsync(Guid id) -{ - var author = await _authorRepository.GetAsync(id); - return ObjectMapper.Map(author); -} -```` - -This method simply gets the `Author` entity by its `Id`, converts to the `AuthorDto` using the [object to object mapper](../Object-To-Object-Mapping.md). This requires to configure the AutoMapper, which will be explained later. - -### GetListAsync - -````csharp -public async Task> GetListAsync(GetAuthorListDto input) -{ - if (input.Sorting.IsNullOrWhiteSpace()) - { - input.Sorting = nameof(Author.Name); - } - - var authors = await _authorRepository.GetListAsync( - input.SkipCount, - input.MaxResultCount, - input.Sorting, - input.Filter - ); - - var totalCount = input.Filter == null - ? await _authorRepository.CountAsync() - : await _authorRepository.CountAsync( - author => author.Name.Contains(input.Filter)); - - return new PagedResultDto( - totalCount, - ObjectMapper.Map, List>(authors) - ); -} -```` - -* Default sorting is "by author name" which is done in the beginning of the method in case of it wasn't sent by the client. -* Used the `IAuthorRepository.GetListAsync` to get a paged, sorted and filtered list of authors from the database. We had implemented it in the previous part of this tutorial. Again, it actually was not needed to create such a method since we could directly query over the repository, but wanted to demonstrate how to create custom repository methods. -* Directly queried from the `AuthorRepository` while getting the count of the authors. If a filter is sent, then we are using it to filter entities while getting the count. -* Finally, returning a paged result by mapping the list of `Author`s to a list of `AuthorDto`s. - -### CreateAsync - -````csharp -[Authorize(BookStorePermissions.Authors.Create)] -public async Task CreateAsync(CreateAuthorDto input) -{ - var author = await _authorManager.CreateAsync( - input.Name, - input.BirthDate, - input.ShortBio - ); - - await _authorRepository.InsertAsync(author); - - return ObjectMapper.Map(author); -} -```` - -* `CreateAsync` requires the `BookStorePermissions.Authors.Create` permission (in addition to the `BookStorePermissions.Authors.Default` declared for the `AuthorAppService` class). -* Used the `AuthorManager` (domain service) to create a new author. -* Used the `IAuthorRepository.InsertAsync` to insert the new author to the database. -* Used the `ObjectMapper` to return an `AuthorDto` representing the newly created author. - -> **DDD tip**: Some developers may find useful to insert the new entity inside the `_authorManager.CreateAsync`. We think it is a better design to leave it to the application layer since it better knows when to insert it to the database (maybe it requires additional works on the entity before insert, which would require to an additional update if we perform the insert in the domain service). However, it is completely up to you. - -### UpdateAsync - -````csharp -[Authorize(BookStorePermissions.Authors.Edit)] -public async Task UpdateAsync(Guid id, UpdateAuthorDto input) -{ - var author = await _authorRepository.GetAsync(id); - - if (author.Name != input.Name) - { - await _authorManager.ChangeNameAsync(author, input.Name); - } - - author.BirthDate = input.BirthDate; - author.ShortBio = input.ShortBio; - - await _authorRepository.UpdateAsync(author); -} -```` - -* `UpdateAsync` requires the additional `BookStorePermissions.Authors.Edit` permission. -* Used the `IAuthorRepository.GetAsync` to get the author entity from the database. `GetAsync` throws `EntityNotFoundException` if there is no author with the given id, which results a `404` HTTP status code in a web application. It is a good practice to always bring the entity on an update operation. -* Used the `AuthorManager.ChangeNameAsync` (domain service method) to change the author name if it was requested to change by the client. -* Directly updated the `BirthDate` and `ShortBio` since there is not any business rule to change these properties, they accept any value. -* Finally, called the `IAuthorRepository.UpdateAsync` method to update the entity on the database. - -{{if DB == "EF"}} - -> **EF Core Tip**: Entity Framework Core has a **change tracking** system and **automatically saves** any change to an entity at the end of the unit of work (You can simply think that the ABP Framework automatically calls `SaveChanges` at the end of the method). So, it will work as expected even if you don't call the `_authorRepository.UpdateAsync(...)` in the end of the method. If you don't consider to change the EF Core later, you can just remove this line. - -{{end}} - -### DeleteAsync - -````csharp -[Authorize(BookStorePermissions.Authors.Delete)] -public async Task DeleteAsync(Guid id) -{ - await _authorRepository.DeleteAsync(id); -} -```` - -* `DeleteAsync` requires the additional `BookStorePermissions.Authors.Delete` permission. -* It simply uses the `DeleteAsync` method of the repository. - -## Permission Definitions - -You can't compile the code since it is expecting some constants declared in the `BookStorePermissions` class. - -Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: - -````csharp -namespace Acme.BookStore.Permissions; - -public static class BookStorePermissions -{ - public const string GroupName = "BookStore"; - - public static class Books - { - public const string Default = GroupName + ".Books"; - public const string Create = Default + ".Create"; - public const string Edit = Default + ".Edit"; - public const string Delete = Default + ".Delete"; - } - - // *** ADDED a NEW NESTED CLASS *** - public static class Authors - { - public const string Default = GroupName + ".Authors"; - public const string Create = Default + ".Create"; - public const string Edit = Default + ".Edit"; - public const string Delete = Default + ".Delete"; - } -} -```` - -Then open the `BookStorePermissionDefinitionProvider` in the same project and add the following lines at the end of the `Define` method: - -````csharp -var authorsPermission = bookStoreGroup.AddPermission( - BookStorePermissions.Authors.Default, L("Permission:Authors")); -authorsPermission.AddChild( - BookStorePermissions.Authors.Create, L("Permission:Authors.Create")); -authorsPermission.AddChild( - BookStorePermissions.Authors.Edit, L("Permission:Authors.Edit")); -authorsPermission.AddChild( - BookStorePermissions.Authors.Delete, L("Permission:Authors.Delete")); -```` - -Finally, add the following entries to the `Localization/BookStore/en.json` inside the `Acme.BookStore.Domain.Shared` project, to localize the permission names: - -````csharp -"Permission:Authors": "Author Management", -"Permission:Authors.Create": "Creating new authors", -"Permission:Authors.Edit": "Editing the authors", -"Permission:Authors.Delete": "Deleting the authors" -```` - -## Object to Object Mapping - -`AuthorAppService` is using the `ObjectMapper` to convert the `Author` objects to `AuthorDto` objects. So, we need to define this mapping in the AutoMapper configuration. - -Open the `BookStoreApplicationAutoMapperProfile` class inside the `Acme.BookStore.Application` project and add the following line to the constructor: - -````csharp -CreateMap(); -```` - -## Data Seeder - -As just done for the books before, it would be good to have some initial author entities in the database. This will be good while running the application first time, but also it is very useful for the automated tests. - -Open the `BookStoreDataSeederContributor` in the `Acme.BookStore.Domain` project and change the file content with the code below: - -````csharp -using System; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Acme.BookStore.Books; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore; - -public class BookStoreDataSeederContributor - : IDataSeedContributor, ITransientDependency -{ - private readonly IRepository _bookRepository; - private readonly IAuthorRepository _authorRepository; - private readonly AuthorManager _authorManager; - - public BookStoreDataSeederContributor( - IRepository bookRepository, - IAuthorRepository authorRepository, - AuthorManager authorManager) - { - _bookRepository = bookRepository; - _authorRepository = authorRepository; - _authorManager = authorManager; - } - - public async Task SeedAsync(DataSeedContext context) - { - if (await _bookRepository.GetCountAsync() <= 0) - { - await _bookRepository.InsertAsync( - new Book - { - Name = "1984", - Type = BookType.Dystopia, - PublishDate = new DateTime(1949, 6, 8), - Price = 19.84f - }, - autoSave: true - ); - - await _bookRepository.InsertAsync( - new Book - { - Name = "The Hitchhiker's Guide to the Galaxy", - Type = BookType.ScienceFiction, - PublishDate = new DateTime(1995, 9, 27), - Price = 42.0f - }, - autoSave: true - ); - } - - // ADDED SEED DATA FOR AUTHORS - - if (await _authorRepository.GetCountAsync() <= 0) - { - await _authorRepository.InsertAsync( - await _authorManager.CreateAsync( - "George Orwell", - new DateTime(1903, 06, 25), - "Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)." - ) - ); - - await _authorRepository.InsertAsync( - await _authorManager.CreateAsync( - "Douglas Adams", - new DateTime(1952, 03, 11), - "Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'." - ) - ); - } - } -} -```` - -{{if DB=="EF"}} - -You can now run the `.DbMigrator` console application to **migrate** the **database schema** and **seed** the initial data. - -{{else if DB=="Mongo"}} - -You can now run the `.DbMigrator` console application to **seed** the initial data. - -{{end}} - -## Testing the Author Application Service - -Finally, we can write some tests for the `IAuthorAppService`. Add a new class, named `AuthorAppService_Tests` in the `Authors` namespace (folder) of the `Acme.BookStore.Application.Tests` project: - -````csharp -using System; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Modularity; -using Xunit; - -namespace Acme.BookStore.Authors; - -public abstract class AuthorAppService_Tests : BookStoreApplicationTestBase - where TStartupModule : IAbpModule -{ - private readonly IAuthorAppService _authorAppService; - - protected AuthorAppService_Tests() - { - _authorAppService = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_All_Authors_Without_Any_Filter() - { - var result = await _authorAppService.GetListAsync(new GetAuthorListDto()); - - result.TotalCount.ShouldBeGreaterThanOrEqualTo(2); - result.Items.ShouldContain(author => author.Name == "George Orwell"); - result.Items.ShouldContain(author => author.Name == "Douglas Adams"); - } - - [Fact] - public async Task Should_Get_Filtered_Authors() - { - var result = await _authorAppService.GetListAsync( - new GetAuthorListDto {Filter = "George"}); - - result.TotalCount.ShouldBeGreaterThanOrEqualTo(1); - result.Items.ShouldContain(author => author.Name == "George Orwell"); - result.Items.ShouldNotContain(author => author.Name == "Douglas Adams"); - } - - [Fact] - public async Task Should_Create_A_New_Author() - { - var authorDto = await _authorAppService.CreateAsync( - new CreateAuthorDto - { - Name = "Edward Bellamy", - BirthDate = new DateTime(1850, 05, 22), - ShortBio = "Edward Bellamy was an American author..." - } - ); - - authorDto.Id.ShouldNotBe(Guid.Empty); - authorDto.Name.ShouldBe("Edward Bellamy"); - } - - [Fact] - public async Task Should_Not_Allow_To_Create_Duplicate_Author() - { - await Assert.ThrowsAsync(async () => - { - await _authorAppService.CreateAsync( - new CreateAuthorDto - { - Name = "Douglas Adams", - BirthDate = DateTime.Now, - ShortBio = "..." - } - ); - }); - } - - //TODO: Test other methods... -} -```` - -{{if DB == "EF"}} -Add a new implementation class of `AuthorAppService_Tests` class, named `EfCoreAuthorAppService_Tests` in the `EntityFrameworkCore\Applications\Authors` namespace (folder) of the `Acme.BookStore.EntityFrameworkCore.Tests` project: - -````csharp -using Acme.BookStore.Authors; -using Xunit; - -namespace Acme.BookStore.EntityFrameworkCore.Applications.Authors; - -[Collection(BookStoreTestConsts.CollectionDefinitionName)] -public class EfCoreAuthorAppService_Tests : AuthorAppService_Tests -{ - -} -```` -{{end}} - -{{if DB == "Mongo"}} -Add a new implementation class of `AuthorAppService_Tests` class, named `MongoDBAuthorAppService_Tests` in the `MongoDb\Applications\Authors` namespace (folder) of the `Acme.BookStore.MongoDB.Tests` project: - -````csharp -using Acme.BookStore.MongoDB; -using Acme.BookStore.Authors; -using Xunit; - -namespace Acme.BookStore.MongoDb.Applications.Authors; - -[Collection(BookStoreTestConsts.CollectionDefinitionName)] -public class MongoDBAuthorAppService_Tests : AuthorAppService_Tests -{ - -} -```` -{{end}} - -Created some tests for the application service methods, which should be clear to understand. diff --git a/docs/en/Tutorials/Part-9.md b/docs/en/Tutorials/Part-9.md deleted file mode 100644 index 433d48360f..0000000000 --- a/docs/en/Tutorials/Part-9.md +++ /dev/null @@ -1,1260 +0,0 @@ -# Web Application Development Tutorial - Part 9: Authors: User Interface -````json -//[doc-params] -{ - "UI": ["MVC","Blazor","BlazorServer","NG"], - "DB": ["EF","Mongo"] -} -```` - -````json -//[doc-nav] -{ - "Next": { - "Name": "Book to Author Relation", - "Path": "Tutorials/Part-10" - }, - "Previous": { - "Name": "Authors: Application Layer", - "Path": "Tutorials/Part-8" - } -} -```` - -## About This Tutorial - -In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: - -* **{{DB_Value}}** as the ORM provider. -* **{{UI_Value}}** as the UI Framework. - -This tutorial is organized as the following parts; - -- [Part 1: Creating the server side](Part-1.md) -- [Part 2: The book list page](Part-2.md) -- [Part 3: Creating, updating and deleting books](Part-3.md) -- [Part 4: Integration tests](Part-4.md) -- [Part 5: Authorization](Part-5.md) -- [Part 6: Authors: Domain layer](Part-6.md) -- [Part 7: Authors: Database Integration](Part-7.md) -- [Part 8: Authors: Application Layer](Part-8.md) -- **Part 9: Authors: User Interface (this part)** -- [Part 10: Book to Author Relation](Part-10.md) - -### Download the Source Code - -This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few combinations of the source code to be downloaded: - -* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) -* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) -* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - -> If you encounter the "filename too long" or "unzip" error on Windows, please see [this guide](../KB/Windows-Path-Too-Long-Fix.md). - -## Introduction - -This part explains how to create a CRUD page for the `Author` entity introduced in the previous parts. - -{{if UI == "MVC"}} - -## The Authors List Page - -Create a new razor page, `Index.cshtml` under the `Pages/Authors` folder of the `Acme.BookStore.Web` project and change the content as given below. - -### Index.cshtml - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Permissions -@using Acme.BookStore.Web.Pages.Authors -@using Microsoft.AspNetCore.Authorization -@using Microsoft.Extensions.Localization -@inject IStringLocalizer L -@inject IAuthorizationService AuthorizationService -@model IndexModel - -@section scripts -{ - -} - - - - - - @L["Authors"] - - - @if (await AuthorizationService - .IsGrantedAsync(BookStorePermissions.Authors.Create)) - { - - } - - - - - - - -```` - -This is a simple page similar to the Books page we had created before. It imports a JavaScript file which will be introduced below. - -### Index.cshtml.cs - -````csharp -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Acme.BookStore.Web.Pages.Authors; - -public class IndexModel : PageModel -{ - public void OnGet() - { - - } -} -```` - -### Index.js - -````js -$(function () { - var l = abp.localization.getResource('BookStore'); - var createModal = new abp.ModalManager(abp.appPath + 'Authors/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Authors/EditModal'); - - var dataTable = $('#AuthorsTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(acme.bookStore.authors.author.getList), - columnDefs: [ - { - title: l('Actions'), - rowAction: { - items: - [ - { - text: l('Edit'), - visible: - abp.auth.isGranted('BookStore.Authors.Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - }, - { - text: l('Delete'), - visible: - abp.auth.isGranted('BookStore.Authors.Delete'), - confirmMessage: function (data) { - return l( - 'AuthorDeletionConfirmationMessage', - data.record.name - ); - }, - action: function (data) { - acme.bookStore.authors.author - .delete(data.record.id) - .then(function() { - abp.notify.info( - l('SuccessfullyDeleted') - ); - dataTable.ajax.reload(); - }); - } - } - ] - } - }, - { - title: l('Name'), - data: "name" - }, - { - title: l('BirthDate'), - data: "birthDate", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(); - } - } - ] - }) - ); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewAuthorButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); -```` - -Briefly, this JavaScript page; - -* Creates a Data table with `Actions`, `Name` and `BirthDate` columns. - * `Actions` column is used to add *Edit* and *Delete* actions. - * `BirthDate` provides a `render` function to format the `DateTime` value using the [luxon](https://moment.github.io/luxon/) library. -* Uses the `abp.ModalManager` to open *Create* and *Edit* modal forms. - -This code is very similar to the Books page created before, so we will not explain it more. - -### Localizations - -This page uses some localization keys we need to declare. Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project and add the following entries: - -````json -"Menu:Authors": "Authors", -"Authors": "Authors", -"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?", -"BirthDate": "Birth date", -"NewAuthor": "New author" -```` - -Notice that we've added more keys. They will be used in the next sections. - -### Add to the Main Menu - -Open the `BookStoreMenuContributor.cs` in the `Menus` folder of the `Acme.BookStore.Web` project and add a new *Authors* menu item under the *Book Store* menu item. The following code (in the `ConfigureMainMenuAsync` method) shows the final code part: - -````csharp -context.Menu.AddItem( - new ApplicationMenuItem( - "BooksStore", - l["Menu:BookStore"], - icon: "fa fa-book" - ).AddItem( - new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/Books" - ).RequirePermissions(BookStorePermissions.Books.Default) - ).AddItem( // ADDED THE NEW "AUTHORS" MENU ITEM UNDER THE "BOOK STORE" MENU - new ApplicationMenuItem( - "BooksStore.Authors", - l["Menu:Authors"], - url: "/Authors" - ).RequirePermissions(BookStorePermissions.Authors.Default) - ) -); -```` - -### Run the Application - -Run and login to the application. **You can not see the menu item since you don't have permission yet.** Go to the `Identity/Roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**: - -![bookstore-author-permissions](images/bookstore-author-permissions-3.png) - -As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**: - -![bookstore-authors-page](images/bookstore-authors-page-3.png) - -The page is fully working except *New author* and *Actions/Edit* since we haven't implemented them yet. - -> **Tip**: If you run the `.DbMigrator` console application after defining a new permission, it automatically grants these new permissions to the admin role and you don't need to manually grant the permissions yourself. - -## Create Modal - -Create a new razor page, `CreateModal.cshtml` under the `Pages/Authors` folder of the `Acme.BookStore.Web` project and change the content as given below. - -### CreateModal.cshtml - -```html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Web.Pages.Authors -@using Microsoft.Extensions.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model CreateModalModel -@inject IStringLocalizer L -@{ - Layout = null; -} -
    - - - - - - - - - -
    -``` - -We had used [dynamic forms](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) of the ABP Framework for the books page before. We could use the same approach here, but we wanted to show how to do it manually. Actually, not so manually, because we've used `abp-input` tag helper in this case to simplify creating the form elements. - -You can definitely use the standard Bootstrap HTML structure, but it requires to write a lot of code. `abp-input` automatically adds validation, localization and other standard elements based on the data type. - -### CreateModal.cshtml.cs - -```csharp -using System; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; - -namespace Acme.BookStore.Web.Pages.Authors; - -public class CreateModalModel : BookStorePageModel -{ - [BindProperty] - public CreateAuthorViewModel Author { get; set; } - - private readonly IAuthorAppService _authorAppService; - - public CreateModalModel(IAuthorAppService authorAppService) - { - _authorAppService = authorAppService; - } - - public void OnGet() - { - Author = new CreateAuthorViewModel(); - } - - public async Task OnPostAsync() - { - var dto = ObjectMapper.Map(Author); - await _authorAppService.CreateAsync(dto); - return NoContent(); - } - - public class CreateAuthorViewModel - { - [Required] - [StringLength(AuthorConsts.MaxNameLength)] - public string Name { get; set; } = string.Empty; - - [Required] - [DataType(DataType.Date)] - public DateTime BirthDate { get; set; } - - [TextArea] - public string? ShortBio { get; set; } - } -} -``` - -This page model class simply injects and uses the `IAuthorAppService` to create a new author. The main difference between the book creation model class is that this one is declaring a new class, `CreateAuthorViewModel`, for the view model instead of re-using the `CreateAuthorDto`. - -The main reason of this decision was to show you how to use a different model class inside the page. But there is one more benefit: We added two attributes to the class members, which were not present in the `CreateAuthorDto`: - -* Added `[DataType(DataType.Date)]` attribute to the `BirthDate` which shows a date picker on the UI for this property. -* Added `[TextArea]` attribute to the `ShortBio` which shows a multi-line text area instead of a standard textbox. - -In this way, you can specialize the view model class based on your UI requirements without touching to the DTO. As a result of this decision, we have used `ObjectMapper` to map `CreateAuthorViewModel` to `CreateAuthorDto`. To be able to do that, you need to add a new mapping code to the `BookStoreWebAutoMapperProfile` constructor: - -````csharp -using Acme.BookStore.Authors; // ADDED NAMESPACE IMPORT -using Acme.BookStore.Books; -using AutoMapper; - -namespace Acme.BookStore.Web; - -public class BookStoreWebAutoMapperProfile : Profile -{ - public BookStoreWebAutoMapperProfile() - { - CreateMap(); - - // ADD a NEW MAPPING - CreateMap(); - } -} -```` - -"New author" button will work as expected and open a new model when you run the application again: - -![bookstore-new-author-modal](images/bookstore-new-author-modal-2.png) - -## Edit Modal - -Create a new razor page, `EditModal.cshtml` under the `Pages/Authors` folder of the `Acme.BookStore.Web` project and change the content as given below. - -### EditModal.cshtml - -````html -@page -@using Acme.BookStore.Localization -@using Acme.BookStore.Web.Pages.Authors -@using Microsoft.Extensions.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model EditModalModel -@inject IStringLocalizer L -@{ - Layout = null; -} -
    - - - - - - - - - - -
    -```` - -### EditModal.cshtml.cs - -```csharp -using System; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; - -namespace Acme.BookStore.Web.Pages.Authors; - -public class EditModalModel : BookStorePageModel -{ - [BindProperty] - public EditAuthorViewModel Author { get; set; } - - private readonly IAuthorAppService _authorAppService; - - public EditModalModel(IAuthorAppService authorAppService) - { - _authorAppService = authorAppService; - } - - public async Task OnGetAsync(Guid id) - { - var authorDto = await _authorAppService.GetAsync(id); - Author = ObjectMapper.Map(authorDto); - } - - public async Task OnPostAsync() - { - await _authorAppService.UpdateAsync( - Author.Id, - ObjectMapper.Map(Author) - ); - - return NoContent(); - } - - public class EditAuthorViewModel - { - [HiddenInput] - public Guid Id { get; set; } - - [Required] - [StringLength(AuthorConsts.MaxNameLength)] - public string Name { get; set; } = string.Empty; - - [Required] - [DataType(DataType.Date)] - public DateTime BirthDate { get; set; } - - [TextArea] - public string? ShortBio { get; set; } - } -} -``` - -This class is similar to the `CreateModal.cshtml.cs` while there are some main differences; - -* Uses the `IAuthorAppService.GetAsync(...)` method to get the editing author from the application layer. -* `EditAuthorViewModel` has an additional `Id` property which is marked with the `[HiddenInput]` attribute that creates a hidden input for this property. - -This class requires to add two object mapping declarations to the `BookStoreWebAutoMapperProfile` class: - -```csharp -using Acme.BookStore.Authors; -using Acme.BookStore.Books; -using AutoMapper; - -namespace Acme.BookStore.Web; - -public class BookStoreWebAutoMapperProfile : Profile -{ - public BookStoreWebAutoMapperProfile() - { - CreateMap(); - - CreateMap(); - - // ADD THESE NEW MAPPINGS - CreateMap(); - CreateMap(); - } -} -``` - -That's all! You can run the application and try to edit an author. - -{{else if UI == "NG"}} - -## The Author Management Page - -Run the following command line to create a new module, named `AuthorModule` in the root folder of the angular application: - -```bash -yarn ng generate module author --module app --routing --route authors -``` - -This command should produce the following output: - -```bash -> yarn ng generate module author --module app --routing --route authors - -yarn run v1.19.1 -$ ng generate module author --module app --routing --route authors -CREATE src/app/author/author-routing.module.ts (344 bytes) -CREATE src/app/author/author.module.ts (349 bytes) -CREATE src/app/author/author.component.html (21 bytes) -CREATE src/app/author/author.component.spec.ts (628 bytes) -CREATE src/app/author/author.component.ts (276 bytes) -CREATE src/app/author/author.component.scss (0 bytes) -UPDATE src/app/app-routing.module.ts (1396 bytes) -Done in 2.22s. -``` - -### AuthorModule - -Open the `/src/app/author/author.module.ts` and replace the content as shown below: - -```js -import { NgModule } from '@angular/core'; -import { SharedModule } from '../shared/shared.module'; -import { AuthorRoutingModule } from './author-routing.module'; -import { AuthorComponent } from './author.component'; -import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; - -@NgModule({ - declarations: [AuthorComponent], - imports: [SharedModule, AuthorRoutingModule, NgbDatepickerModule], -}) -export class AuthorModule {} -``` - -- Added the `SharedModule`. `SharedModule` exports some common modules needed to create user interfaces. -- `SharedModule` already exports the `CommonModule`, so we've removed the `CommonModule`. -- Added `NgbDatepickerModule` that will be used later on the author create and edit forms. - -### Menu Definition - -Open the `src/app/route.provider.ts` file and add the following menu definition: - -````js -{ - path: '/authors', - name: '::Menu:Authors', - parentName: '::Menu:BookStore', - layout: eLayoutType.application, - requiredPolicy: 'BookStore.Authors', -} -```` - -The final `configureRoutes` function declaration should be following: - -```js -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - { - path: '/', - name: '::Menu:Home', - iconClass: 'fas fa-home', - order: 1, - layout: eLayoutType.application, - }, - { - path: '/book-store', - name: '::Menu:BookStore', - iconClass: 'fas fa-book', - order: 2, - layout: eLayoutType.application, - }, - { - path: '/books', - name: '::Menu:Books', - parentName: '::Menu:BookStore', - layout: eLayoutType.application, - requiredPolicy: 'BookStore.Books', - }, - { - path: '/authors', - name: '::Menu:Authors', - parentName: '::Menu:BookStore', - layout: eLayoutType.application, - requiredPolicy: 'BookStore.Authors', - }, - ]); - }; -} -``` - -### Service Proxy Generation - -[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides `generate-proxy` command that generates client proxies for your HTTP APIs to make easy to consume your HTTP APIs from the client side. Before running `generate-proxy` command, your host must be up and running. - -Run the following command in the `angular` folder: - -```bash -abp generate-proxy -t ng -``` - -This command generates the service proxy for the author service and the related model (DTO) classes: - -![bookstore-angular-service-proxy-author](images/bookstore-angular-service-proxy-author-2.png) - -### AuthorComponent - -Open the `/src/app/author/author.component.ts` file and replace the content as below: - -```js -import { Component, OnInit } from '@angular/core'; -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { AuthorService, AuthorDto } from '@proxy/authors'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; - -@Component({ - selector: 'app-author', - templateUrl: './author.component.html', - styleUrls: ['./author.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], -}) -export class AuthorComponent implements OnInit { - author = { items: [], totalCount: 0 } as PagedResultDto; - - isModalOpen = false; - - form: FormGroup; - - selectedAuthor = {} as AuthorDto; - - constructor( - public readonly list: ListService, - private authorService: AuthorService, - private fb: FormBuilder, - private confirmation: ConfirmationService - ) {} - - ngOnInit(): void { - const authorStreamCreator = (query) => this.authorService.getList(query); - - this.list.hookToQuery(authorStreamCreator).subscribe((response) => { - this.author = response; - }); - } - - createAuthor() { - this.selectedAuthor = {} as AuthorDto; - this.buildForm(); - this.isModalOpen = true; - } - - editAuthor(id: string) { - this.authorService.get(id).subscribe((author) => { - this.selectedAuthor = author; - this.buildForm(); - this.isModalOpen = true; - }); - } - - buildForm() { - this.form = this.fb.group({ - name: [this.selectedAuthor.name || '', Validators.required], - birthDate: [ - this.selectedAuthor.birthDate ? new Date(this.selectedAuthor.birthDate) : null, - Validators.required, - ], - }); - } - - save() { - if (this.form.invalid) { - return; - } - - if (this.selectedAuthor.id) { - this.authorService - .update(this.selectedAuthor.id, this.form.value) - .subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } else { - this.authorService.create(this.form.value).subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } - } - - delete(id: string) { - this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure') - .subscribe((status) => { - if (status === Confirmation.Status.confirm) { - this.authorService.delete(id).subscribe(() => this.list.get()); - } - }); - } -} -``` - -Open the `/src/app/author/author.component.html` and replace the content as below: - -````html -
    -
    -
    -
    -
    - {%{{{ '::Menu:Authors' | abpLocalization }}}%} -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - - -
    - -
    - - -
    -
    -
    -
    - - - - {%{{{ row.birthDate | date }}}%} - - -
    -
    -
    - - - -

    {%{{{ (selectedAuthor.id ? '::Edit' : '::NewAuthor') | abpLocalization }}}%}

    -
    - - -
    -
    - * - -
    - -
    - * - -
    -
    -
    - - - - - - -
    -```` - -### Localizations - -This page uses some localization keys we need to declare. Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project and add the following entries: - -````json -"Menu:Authors": "Authors", -"Authors": "Authors", -"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?", -"BirthDate": "Birth date", -"NewAuthor": "New author" -```` - -### Run the Application - -Run and login to the application. **You can not see the menu item since you don't have permission yet.** Go to the `identity/roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**: - -![bookstore-author-permissions](images/bookstore-author-permissions-2.png) - -As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**: - -![bookstore-authors-page](images/bookstore-angular-authors-page-2.png) - -That's all! This is a fully working CRUD page, you can create, edit and delete authors. - -> **Tip**: If you run the `.DbMigrator` console application after defining a new permission, it automatically grants these new permissions to the admin role and you don't need to manually grant the permissions yourself. - -{{end}} - -{{if UI == "Blazor" || UI == "BlazorServer"}} - -## The Author Management Page - -### Authors Razor Component - -Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStore.Blazor.Client` project with the following content: - -````xml -@page "/authors" -@using Acme.BookStore.Authors -@using Acme.BookStore.Localization -@using Volo.Abp.AspNetCore.Components.Web -@inherits BookStoreComponentBase -@inject IAuthorAppService AuthorAppService -@inject AbpBlazorMessageLocalizerHelper LH - - - - -

    @L["Authors"]

    -
    - - @if (CanCreateAuthor) - { - - } - -
    -
    - - - - - - - - @L["Actions"] - - - @if (CanEditAuthor) - { - - @L["Edit"] - - } - @if (CanDeleteAuthor) - { - - @L["Delete"] - - } - - - - - - - - @context.BirthDate.ToShortDateString() - - - - - -
    - - - - -
    - - @L["NewAuthor"] - - - - - - - @L["Name"] - - - - - - - - - @L["BirthDate"] - - - - - @L["ShortBio"] - - - - - - - - - - - - - -
    -
    -
    - - - - -
    - - @EditingAuthor.Name - - - - - - - @L["Name"] - - - - - - - - - @L["BirthDate"] - - - - - @L["ShortBio"] - - - - - - - - - - - - - -
    -
    -
    -```` - -* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`, but uses its own implementation. -* Injects the `IAuthorAppService` to consume the server side HTTP APIs from the UI. We can directly inject application service interfaces and use just like regular method calls by the help of [Dynamic C# HTTP API Client Proxy System](../API/Dynamic-CSharp-API-Clients.md), which performs REST API calls for us. See the `Authors` class below to see the usage. -* Injects the `IAuthorizationService` to check [permissions](../Authorization.md). -* Injects the `IObjectMapper` for [object to object mapping](../Object-To-Object-Mapping.md). - -Create a new code behind file, `Authors.razor.cs`, under the `Pages` folder, with the following content: - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Acme.BookStore.Authors; -using Acme.BookStore.Permissions; -using Blazorise; -using Blazorise.DataGrid; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore.Blazor.Client.Pages; - -public partial class Authors -{ - private IReadOnlyList AuthorList { get; set; } - - private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount; - private int CurrentPage { get; set; } - private string CurrentSorting { get; set; } - private int TotalCount { get; set; } - - private bool CanCreateAuthor { get; set; } - private bool CanEditAuthor { get; set; } - private bool CanDeleteAuthor { get; set; } - - private CreateAuthorDto NewAuthor { get; set; } - - private Guid EditingAuthorId { get; set; } - private UpdateAuthorDto EditingAuthor { get; set; } - - private Modal CreateAuthorModal { get; set; } - private Modal EditAuthorModal { get; set; } - - private Validations CreateValidationsRef; - - private Validations EditValidationsRef; - - public Authors() - { - NewAuthor = new CreateAuthorDto(); - EditingAuthor = new UpdateAuthorDto(); - } - - protected override async Task OnInitializedAsync() - { - await SetPermissionsAsync(); - await GetAuthorsAsync(); - } - - private async Task SetPermissionsAsync() - { - CanCreateAuthor = await AuthorizationService - .IsGrantedAsync(BookStorePermissions.Authors.Create); - - CanEditAuthor = await AuthorizationService - .IsGrantedAsync(BookStorePermissions.Authors.Edit); - - CanDeleteAuthor = await AuthorizationService - .IsGrantedAsync(BookStorePermissions.Authors.Delete); - } - - private async Task GetAuthorsAsync() - { - var result = await AuthorAppService.GetListAsync( - new GetAuthorListDto - { - MaxResultCount = PageSize, - SkipCount = CurrentPage * PageSize, - Sorting = CurrentSorting - } - ); - - AuthorList = result.Items; - TotalCount = (int)result.TotalCount; - } - - private async Task OnDataGridReadAsync(DataGridReadDataEventArgs e) - { - CurrentSorting = e.Columns - .Where(c => c.SortDirection != SortDirection.Default) - .Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : "")) - .JoinAsString(","); - CurrentPage = e.Page - 1; - - await GetAuthorsAsync(); - - await InvokeAsync(StateHasChanged); - } - - private void OpenCreateAuthorModal() - { - CreateValidationsRef.ClearAll(); - - NewAuthor = new CreateAuthorDto(); - CreateAuthorModal.Show(); - } - - private void CloseCreateAuthorModal() - { - CreateAuthorModal.Hide(); - } - - private void OpenEditAuthorModal(AuthorDto author) - { - EditValidationsRef.ClearAll(); - - EditingAuthorId = author.Id; - EditingAuthor = ObjectMapper.Map(author); - EditAuthorModal.Show(); - } - - private async Task DeleteAuthorAsync(AuthorDto author) - { - var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name]; - if (!await Message.Confirm(confirmMessage)) - { - return; - } - - await AuthorAppService.DeleteAsync(author.Id); - await GetAuthorsAsync(); - } - - private void CloseEditAuthorModal() - { - EditAuthorModal.Hide(); - } - - private async Task CreateAuthorAsync() - { - if (await CreateValidationsRef.ValidateAll()) - { - await AuthorAppService.CreateAsync(NewAuthor); - await GetAuthorsAsync(); - CreateAuthorModal.Hide(); - } - } - - private async Task UpdateAuthorAsync() - { - if (await EditValidationsRef.ValidateAll()) - { - await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor); - await GetAuthorsAsync(); - EditAuthorModal.Hide(); - } - } -} -```` - -This class typically defines the properties and methods used by the `Authors.razor` page. - -### Object Mapping - -`Authors` class uses the `IObjectMapper` in the `OpenEditAuthorModal` method. So, we need to define this mapping. - -Open the `BookStoreBlazorAutoMapperProfile.cs` in the `Acme.BookStore.Blazor.Client` project and add the following mapping code in the constructor: - -````csharp -CreateMap(); -```` - -You will need to declare a `using Acme.BookStore.Authors;` statement to the beginning of the file. - -### Add to the Main Menu - -Open the `BookStoreMenuContributor.cs` in the `Acme.BookStore.Blazor.Client` project and add the following code to the end of the `ConfigureMainMenuAsync` method: - -````csharp -if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default)) -{ - bookStoreMenu.AddItem(new ApplicationMenuItem( - "BooksStore.Authors", - l["Menu:Authors"], - url: "/authors" - )); -} -```` - -### Localizations - -We should complete the localizations we've used above. Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project and add the following entries: - -````json -"Menu:Authors": "Authors", -"Authors": "Authors", -"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?", -"BirthDate": "Birth date", -"NewAuthor": "New author" -```` - -### Run the Application - -Run and login to the application. **If you don't see the Authors menu item under the Book Store menu, that means you don't have the permission yet.** Go to the `identity/roles` page, click to the *Actions* button and select the *Permissions* action for the **admin role**: - -![bookstore-author-permissions](images/bookstore-author-permissions-2.png) - -As you see, the admin role has no *Author Management* permissions yet. Click to the checkboxes and save the modal to grant the necessary permissions. You will see the *Authors* menu item under the *Book Store* in the main menu, after **refreshing the page**: - -![bookstore-authors-page](images/bookstore-authors-page-3.png) - -That's all! This is a fully working CRUD page, you can create, edit and delete the authors. - -> **Tip**: If you run the `.DbMigrator` console application after defining a new permission, it automatically grants these new permissions to the admin role and you don't need to manually grant the permissions yourself. - -{{end}} \ No newline at end of file diff --git a/docs/en/Tutorials/Todo/Index.md b/docs/en/Tutorials/Todo/Index.md deleted file mode 100644 index 28725fd2c8..0000000000 --- a/docs/en/Tutorials/Todo/Index.md +++ /dev/null @@ -1,889 +0,0 @@ -# Quick Start - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] -} -```` - -This is a single-part quick-start tutorial to build a simple todo application with the ABP Framework. Here's a screenshot from the final application: - -![todo-list](todo-list.png) - -You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp). - -This documentation has a video tutorial on **YouTube**!! You can watch it here: - -{{if UI=="MVC" && DB =="EF"}} - - - -{{else if UI=="Blazor" && DB=="EF"}} - - - -{{else if UI=="BlazorServer" && DB=="EF"}} - - - -{{else if UI=="NG" && DB=="EF"}} - - - -{{else if UI=="MVC" && DB=="Mongo"}} - - - -{{else if UI=="BlazorServer" && DB=="Mongo"}} - - - -{{else if UI=="Blazor" && DB=="Mongo"}} - - - -{{else if UI=="NG" && DB=="Mongo"}} - - - -{{end}} - -## Pre-Requirements - -* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 8.0+](https://dotnet.microsoft.com/download/dotnet) development. -* [Node v16.x](https://nodejs.org/) - -{{if DB=="Mongo"}} - -* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/) - -{{end}} - -## Install ABP CLI Tool - -We will use the [ABP CLI](../../CLI.md) to create new ABP solutions. You can run the following command on a terminal window to install this dotnet tool: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -## Create Your ABP Solution - -Create an empty folder, open a command-line terminal and execute the following command in the terminal: - -````bash -abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} -```` - -{{if UI=="NG"}} - -This will create a new solution, named *TodoApp* with `angular` and `aspnet-core` folders. Once the solution is ready, open the ASP.NET Core solution in your favorite IDE. - -{{else}} - -This will create a new solution, named *TodoApp*. Once the solution is ready, open it in your favorite IDE. - -{{end}} - -### Create the Database - -If you are using Visual Studio, right click on the `TodoApp.DbMigrator` project, select *Set as StartUp Project*, then hit *Ctrl+F5* to run it without debugging. It will create the initial database and seed the initial data. - -{{if DB=="EF"}} - -> Some IDEs (e.g. Rider) may have problems for the first run since *DbMigrator* adds the initial migration and re-compiles the project. In this case, open a command-line terminal in the folder of the `.DbMigrator` project and execute the `dotnet run` command. - -{{end}} - -### Before Running the Application - -#### Installing the Client-Side Packages - -[ABP CLI](../../CLI.md) runs the `abp install-libs` command behind the scenes to install the required NPM packages for your solution while creating the application. - -However, sometimes this command might need to be manually run. For example, you need to run this command, if you have cloned the application, or the resources from *node_modules* folder didn't copy to *wwwroot/libs* folder, or if you have added a new client-side package dependency to your solution. - -For such cases, run the `abp install-libs` command on the root directory of your solution to install all required NPM packages: - -```bash -abp install-libs -``` - -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. - -{{if UI=="Blazor" || UI=="BlazorServer"}} - -#### Bundling and Minification - -`abp bundle` command offers bundling and minification support for client-side resources (JavaScript and CSS files) for Blazor projects. This command automatically run when you create a new solution with the [ABP CLI](../../CLI.md). - -However, sometimes you might need to run this command manually. To update script & style references without worrying about dependencies, ordering, etc. in a project, you can run this command in the directory of your blazor application: - -```bash -abp bundle -``` - -> For more details about managing style and script references in Blazor or MAUI Blazor apps, see [Managing Global Scripts & Styles](../../UI/Blazor/Global-Scripts-Styles.md). - -{{end}} - -### Run the Application - -{{if UI=="MVC" || UI=="BlazorServer"}} - -It is good to run the application before starting the development. Ensure the {{if UI=="BlazorServer"}}`TodoApp.Blazor`{{else}}`TodoApp.Web`{{end}} project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the initial UI: - -{{else if UI=="Blazor"}} - -It is good to run the application before starting the development. The solution has two main applications; - -* `TodoApp.HttpApi.Host` hosts the server-side HTTP API. -* `TodoApp.Blazor` is the client-side Blazor WebAssembly application. - -Ensure the `TodoApp.HttpApi.Host` project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the server-side HTTP API on the [Swagger UI](https://swagger.io/tools/swagger-ui/): - -![todo-swagger-ui-initial](todo-swagger-ui-initial.png) - -You can explore and test your HTTP API with this UI. Now, we can set the `TodoApp.Blazor` as the startup project and run it to open the actual Blazor application UI: - -{{else if UI=="NG"}} - -It is good to run the application before starting the development. The solution has two main applications: - -* `TodoApp.HttpApi.Host` (in the .NET solution) host the server-side HTTP API. -* `angular` folder contains the Angular application. - -Ensure that the `TodoApp.HttpApi.Host` project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the server-side HTTP API on the [Swagger UI](https://swagger.io/tools/swagger-ui/): - -![todo-swagger-ui-initial](todo-swagger-ui-initial.png) - -You can explore and test your HTTP API with this UI. If it works, we can run the Angular client application. - -You can run the application using the following command: - -````bash -npm start -```` - -This command takes time, but eventually runs and opens the application in your default browser: - -{{end}} - -![todo-ui-initial](todo-ui-initial.png) - -You can click on the *Login* button, use `admin` as the username and `1q2w3E*` as the password to login to the application. - -All ready. We can start coding! - -## Domain Layer - -This application has a single [entity](../../Entities.md) and we'll start by creating it. Create a new `TodoItem` class inside the *TodoApp.Domain* project: - -````csharp -using System; -using Volo.Abp.Domain.Entities; - -namespace TodoApp -{ - public class TodoItem : BasicAggregateRoot - { - public string Text { get; set; } - } -} -```` - -`BasicAggregateRoot` is the simplest base class to create root entities, and `Guid` is the primary key (`Id`) of the entity here. - -## Database Integration - -{{if DB=="EF"}} - -Next step is to setup the [Entity Framework Core](../../Entity-Framework-Core.md) configuration. - -### Mapping Configuration - -Open the `TodoAppDbContext` class in the `EntityFrameworkCore` folder of the *TodoApp.EntityFrameworkCore* project and add a new `DbSet` property to this class: - -````csharp -public DbSet TodoItems { get; set; } -```` - -Then navigate to the `OnModelCreating` method in the `TodoAppDbContext` class and add the mapping code for the `TodoItem ` entity: - -````csharp -protected override void OnModelCreating(ModelBuilder builder) -{ - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.ConfigurePermissionManagement(); - ... - - /* Configure your own tables/entities inside here */ - builder.Entity(b => - { - b.ToTable("TodoItems"); - }); -} -```` - -We've mapped the `TodoItem` entity to the `TodoItems` table in the database. - -### Code First Migrations - -The startup solution is configured to use Entity Framework Core [Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. - -Open a command-line terminal in the directory of the *TodoApp.EntityFrameworkCore* project and type the following command: - -````bash -dotnet ef migrations add Added_TodoItem -```` - -This will add a new migration class to the project: - -![todo-efcore-migration](todo-efcore-migration.png) - -You can apply changes to the database using the following command, in the same command-line terminal: - -````bash -dotnet ef database update -```` - -> If you are using Visual Studio, you may want to use the `Add-Migration Added_TodoItem` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} is the startup project and `TodoApp.EntityFrameworkCore` is the *Default Project* in PMC. - -{{else if DB=="Mongo"}} - -Next step is to setup the [MongoDB](../../MongoDB.md) configuration. Open the `TodoAppMongoDbContext` class in the `MongoDb` folder of the *TodoApp.MongoDB* project and make the following changes: - -1. Add a new property to the class: - -````csharp -public IMongoCollection TodoItems => Collection(); -```` - -2. Add the following code inside the `CreateModel` method: - -````csharp -modelBuilder.Entity(b => -{ - b.CollectionName = "TodoItems"; -}); -```` - -{{end}} - -Now, we can use the ABP repositories to save and retrieve the todo items, as we'll do in the next section. - -## Application Layer - -An [Application Service](../../Application-Services.md) is used to perform the use cases of the application. We need to perform the following use cases: - -* Get the list of the todo items -* Create a new todo item -* Delete an existing todo item - -### Application Service Interface - -We can start by defining an interface for the application service. Create a new `ITodoAppService` interface in the *TodoApp.Application.Contracts* project, as shown below: - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace TodoApp -{ - public interface ITodoAppService : IApplicationService - { - Task> GetListAsync(); - Task CreateAsync(string text); - Task DeleteAsync(Guid id); - } -} -```` - -### Data Transfer Object - -`GetListAsync` and `CreateAsync` methods return `TodoItemDto`. `ApplicationService` typically gets and returns DTOs ([Data Transfer Objects](../../Data-Transfer-Objects.md)) instead of entities. So, we should define the DTO class here. Create a new `TodoItemDto` class inside the *TodoApp.Application.Contracts* project: - -````csharp -using System; - -namespace TodoApp -{ - public class TodoItemDto - { - public Guid Id { get; set; } - public string Text { get; set; } - } -} -```` - -This is a very simple DTO class that matches our `TodoItem` entity. We are ready to implement the `ITodoAppService`. - -### Application Service Implementation - -Create a `TodoAppService` class inside the *TodoApp.Application* project, as shown below: - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace TodoApp -{ - public class TodoAppService : ApplicationService, ITodoAppService - { - private readonly IRepository _todoItemRepository; - - public TodoAppService(IRepository todoItemRepository) - { - _todoItemRepository = todoItemRepository; - } - - // TODO: Implement the methods here... - } -} -```` - -This class inherits from the `ApplicationService` class of the ABP Framework and implements the `ITodoAppService` that was defined before. ABP provides default generic [repositories](../../Repositories.md) for the entities. We can use them to perform the fundamental database operations. This class [injects](../../Dependency-Injection.md) `IRepository`, which is the default repository for the `TodoItem` entity. We will use it to implement the use cases described before. - -#### Getting Todo Items - -Let's start by implementing the `GetListAsync` method: - -````csharp -public async Task> GetListAsync() -{ - var items = await _todoItemRepository.GetListAsync(); - return items - .Select(item => new TodoItemDto - { - Id = item.Id, - Text = item.Text - }).ToList(); -} -```` - -We are simply getting the complete `TodoItem` list from the database, mapping them to `TodoItemDto` objects and returning as the result. - -#### Creating a New Todo Item - -Next method is `CreateAsync` and we can implement it as shown below: - -````csharp -public async Task CreateAsync(string text) -{ - var todoItem = await _todoItemRepository.InsertAsync( - new TodoItem {Text = text} - ); - - return new TodoItemDto - { - Id = todoItem.Id, - Text = todoItem.Text - }; -} -```` - -The repository's `InsertAsync` method inserts the given `TodoItem` to the database and returns the same `TodoItem` object. It also sets the `Id`, so we can use it on the returning object. We are simply returning a `TodoItemDto` by creating from the new `TodoItem` entity. - -#### Deleting a Todo Item - -Finally, we can implement the `DeleteAsync` as the following code block: - -````csharp -public async Task DeleteAsync(Guid id) -{ - await _todoItemRepository.DeleteAsync(id); -} -```` - -The application service is ready to be used from the UI layer. - -## User Interface Layer - -It is time to show the todo items on the UI! Before starting to write the code, it would be good to remember what we are trying to build. Here's a sample screenshot from the final UI: - -![todo-list](todo-list.png) - -> **We will keep the UI side minimal for this tutorial to make the tutorial simple and focused. See the [web application development tutorial](../Part-1.md) to build real-life pages with all aspects.** - -{{if UI=="MVC"}} - -### Index.cshtml.cs - -Open the `Index.cshtml.cs` file in the `Pages` folder of the *TodoApp.Web* project and replace the content with the following code block: - -````csharp -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace TodoApp.Web.Pages -{ - public class IndexModel : TodoAppPageModel - { - public List TodoItems { get; set; } - - private readonly ITodoAppService _todoAppService; - - public IndexModel(ITodoAppService todoAppService) - { - _todoAppService = todoAppService; - } - - public async Task OnGetAsync() - { - TodoItems = await _todoAppService.GetListAsync(); - } - } -} -```` - -This class uses the `ITodoAppService` to get the list of todo items and assign the `TodoItems` property. We will use it to render the todo items on the razor page. - -### Index.cshtml - -Open the `Index.cshtml` file in the `Pages` folder of the *TodoApp.Web* project and replace it with the following content: - -````xml -@page -@model TodoApp.Web.Pages.IndexModel -@section styles { - -} -@section scripts { - -} -
    - - - - TODO LIST - - - - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
      - @foreach (var todoItem in Model.TodoItems) - { -
    • - @todoItem.Text -
    • - } -
    -
    -
    -
    -```` - -We are using ABP's [card tag helper](../../UI/AspNetCore/Tag-Helpers/Cards.md) to create a simple card view. You could directly use the standard bootstrap HTML structure, however the ABP [tag helpers](../../UI/AspNetCore/Tag-Helpers/Index.md) make it much easier and type safe. - -This page imports a CSS and a JavaScript file, so we should also create them. - -### Index.js - -Open the `Index.js` file in the `Pages` folder of the *TodoApp.Web* project and replace it with the following content: - -````js -$(function () { - - // DELETING ITEMS ///////////////////////////////////////// - $('#TodoList').on('click', 'li i', function(){ - var $li = $(this).parent(); - var id = $li.attr('data-id'); - - todoApp.todo.delete(id).then(function(){ - $li.remove(); - abp.notify.info('Deleted the todo item.'); - }); - }); - - // CREATING NEW ITEMS ///////////////////////////////////// - $('#NewItemForm').submit(function(e){ - e.preventDefault(); - - var todoText = $('#NewItemText').val(); - todoApp.todo.create(todoText).then(function(result){ - $('
  • ') - .html(' ' + result.text) - .appendTo($('#TodoList')); - $('#NewItemText').val(''); - }); - }); -}); -```` - -In the first part, we are subscribing to the click events of the trash icons near the todo items, deleting the related item on the server and showing a notification on the UI. Also, we are removing the deleted item from the DOM, so we don't need to refresh the page. - -In the second part, we are creating a new todo item on the server. If it succeeds, we are then manipulating the DOM to insert a new `
  • ` element to the todo list. This way we don't need to refresh the whole page after creating a new todo item. - -The interesting part here is how we communicate with the server. See the *Dynamic JavaScript Proxies & Auto API Controllers* section to understand how it works. But now, let's continue and complete the application. - -### Index.css - -As the final touch, open the `Index.css` file in the `Pages` folder of the *TodoApp.Web* project and replace it with the following content: - -````css -#TodoList{ - list-style: none; - margin: 0; - padding: 0; -} - -#TodoList li { - padding: 5px; - margin: 5px 0px; - border: 1px solid #cccccc; - background-color: #f5f5f5; -} - -#TodoList li i -{ - opacity: 0.5; -} - -#TodoList li i:hover -{ - opacity: 1; - color: #ff0000; - cursor: pointer; -} -```` - -This is a simple styling for the todo page. We believe that you can do much better :) - -Now, you can run the application again and see the result. - -### Dynamic JavaScript Proxies & Auto API Controllers - -In the `Index.js` file, we've used the `todoApp.todo.delete(...)` and `todoApp.todo.create(...)` functions to communicate with the server. These functions are dynamically created by the ABP Framework, thanks to the [Dynamic JavaScript Client Proxy](../../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) system. They perform HTTP API calls to the server and return a promise, so you can register a callback to the `then` function as we've done above. - -However, you may notice that we haven't created any API Controllers, so how does the server handle these requests? This question brings us to the [Auto API Controller](../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to API Controllers by convention. - -If you open the [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering the `/swagger` URL in your application, you can see the Todo API: - -![todo-api](todo-api.png) - -{{else if UI=="Blazor" || UI=="BlazorServer"}} - -### Index.razor.cs - -Open the `Index.razor.cs` file in the `Pages` folder of the *TodoApp.Blazor* project and replace the content with the following code block: - -````csharp -using Microsoft.AspNetCore.Components; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace TodoApp.Blazor.Pages -{ - public partial class Index - { - [Inject] - private ITodoAppService TodoAppService { get; set; } - - private List TodoItems { get; set; } = new List(); - private string NewTodoText { get; set; } - - protected override async Task OnInitializedAsync() - { - TodoItems = await TodoAppService.GetListAsync(); - } - - private async Task Create() - { - var result = await TodoAppService.CreateAsync(NewTodoText); - TodoItems.Add(result); - NewTodoText = null; - } - - private async Task Delete(TodoItemDto todoItem) - { - await TodoAppService.DeleteAsync(todoItem.Id); - await Notify.Info("Deleted the todo item."); - TodoItems.Remove(todoItem); - } - } -} -```` - -This class uses `ITodoAppService` to perform operations for the todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server. - -{{if UI=="Blazor"}} - -See the *Dynamic C# Proxies & Auto API Controllers* section below to learn how we could inject and use the application service interface from the Blazor application which is running on the browser! But now, let's continue and complete the application. - -{{end # Blazor}} - -### Index.razor - -Open the `Index.razor` file in the `Pages` folder of the *TodoApp.Blazor* project and replace the content with the following code block: - -````xml -@page "/" -@inherits TodoAppComponentBase -
    - - - - TODO LIST - - - - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
      - @foreach (var todoItem in TodoItems) - { -
    • - @todoItem.Text -
    • - } -
    -
    -
    -
    -```` - -### Index.razor.css - -As the final touch, open the `Index.razor.css` file in the `Pages` folder of the *TodoApp.Blazor* project and replace it with the following content: - -````css -#TodoList{ - list-style: none; - margin: 0; - padding: 0; -} - -#TodoList li { - padding: 5px; - margin: 5px 0px; - border: 1px solid #cccccc; - background-color: #f5f5f5; -} - -#TodoList li i -{ - opacity: 0.5; -} - -#TodoList li i:hover -{ - opacity: 1; - color: #ff0000; - cursor: pointer; -} -```` - -This is a simple styling for the todo page. We believe that you can do much better :) - -Now, you can run the application again to see the result. - -{{if UI=="Blazor"}} - -### Dynamic C# Proxies & Auto API Controllers - -In the `Index.razor.cs` file, we've injected (with the `[Inject]` attribute) and used the `ITodoAppService` just like using a local service. Remember that the Blazor application is running on the browser while the implementation of this application service is running on the server. - -The magic is done by the ABP Framework's [Dynamic C# Client Proxy](../../API/Dynamic-CSharp-API-Clients.md) system. It uses the standard `HttpClient` and performs HTTP API requests to the remote server. It also handles all the standard tasks for us, including authorization, JSON serialization and exception handling. - -However, you may ask that we haven't created any API Controller, so how does the server handle these requests? This question brings us to the [Auto API Controller](../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to API Controllers by convention. - -If you run the `TodoApp.HttpApi.Host` application, you can see the Todo API: - -![todo-api](todo-api.png) - -{{end # Blazor}} - -{{else if UI=="NG"}} - -### Service Proxy Generation - -ABP provides a handy feature to automatically create client-side services to easily consume HTTP APIs provided by the server. - -You first need to run the `TodoApp.HttpApi.Host` project since the proxy generator reads API definitions from the server application. - -> **Warning**: There is an issue with IIS Express: it doesn't allow connecting to the application from another process. If you are using Visual Studio, select the `TodoApp.HttpApi.Host` instead of IIS Express in the run button drop-down list, as shown in the figure below: - -![run-without-iisexpress](run-without-iisexpress.png) - -Once you run the `TodoApp.HttpApi.Host` project, open a command-line terminal in the `angular` folder and type the following command: - -````bash -abp generate-proxy -t ng -```` - -If everything goes well, it should generate an output as shown below: - -````bash -CREATE src/app/proxy/generate-proxy.json (170978 bytes) -CREATE src/app/proxy/README.md (1000 bytes) -CREATE src/app/proxy/todo.service.ts (794 bytes) -CREATE src/app/proxy/models.ts (66 bytes) -CREATE src/app/proxy/index.ts (58 bytes) -```` - -We can then use `todoService` to use the server-side HTTP APIs, as we'll do in the next section. - -### home.component.ts - -Open the `/angular/src/app/home/home.component.ts` file and replace its content with the following code block: - -````js -import { ToasterService } from '@abp/ng.theme.shared'; -import { Component, OnInit } from '@angular/core'; -import { TodoItemDto, TodoService } from '@proxy'; - -@Component({ - selector: 'app-home', - templateUrl: './home.component.html', - styleUrls: ['./home.component.scss'] -}) -export class HomeComponent implements OnInit { - - todoItems: TodoItemDto[]; - newTodoText: string; - - constructor( - private todoService: TodoService, - private toasterService: ToasterService) - { } - - ngOnInit(): void { - this.todoService.getList().subscribe(response => { - this.todoItems = response; - }); - } - - create(): void{ - this.todoService.create(this.newTodoText).subscribe((result) => { - this.todoItems = this.todoItems.concat(result); - this.newTodoText = null; - }); - } - - delete(id: string): void { - this.todoService.delete(id).subscribe(() => { - this.todoItems = this.todoItems.filter(item => item.id !== id); - this.toasterService.info('Deleted the todo item.'); - }); - } -} - -```` - -We've used `todoService` to get the list of todo items and assigned the returning value to the `todoItems` array. We've also added `create` and `delete` methods. These methods will be used on the view side. - -### home.component.html - -Open the `/angular/src/app/home/home.component.html` file and replace its content with the following code block: - -````html -
    -
    -
    -
    TODO LIST
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
      -
    • - {%{{{ todoItem.text }}}%} -
    • -
    -
    -
    -
    -```` - -### home.component.scss - -As the final touch, open the `/angular/src/app/home/home.component.scss` file and replace its content with the following code block: - -````css -#TodoList{ - list-style: none; - margin: 0; - padding: 0; -} - -#TodoList li { - padding: 5px; - margin: 5px 0px; - border: 1px solid #cccccc; - background-color: #f5f5f5; -} - -#TodoList li i -{ - opacity: 0.5; -} - -#TodoList li i:hover -{ - opacity: 1; - color: #ff0000; - cursor: pointer; -} -```` - -This is a simple styling for the todo page. We believe that you can do much better :) - -Now, you can run the application again to see the result. - -{{end}} - -## Conclusion - -In this tutorial, we've built a very simple application to warm up for the ABP Framework. If you are looking to build a serious application, please check the [web application development tutorial](../Part-1.md) which covers all the aspects of real-life web application development. - -## Source Code - -You can find source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp). - -## See Also - -* [Web Application Development Tutorial](../Part-1.md) diff --git a/docs/en/Tutorials/Todo/Overall.md b/docs/en/Tutorials/Todo/Overall.md deleted file mode 100644 index 2fc92ae2ac..0000000000 --- a/docs/en/Tutorials/Todo/Overall.md +++ /dev/null @@ -1,14 +0,0 @@ -# Quick Start: Overall - -**Welcome to the ABP Framework**. This is a single-part, quick-start tutorial to build a simple application. Start with this tutorial if you want to quickly understand how ABP Framework works. - -## Select the Solution Architecture - -This tutorial has multiple versions. Please select the one best fits for you: - -* **[Single-Layer Solution](Single-Layer/Index.md)**: Creates a single-project solution. Recommended for building an application with a **simpler and easy to understand** architecture. -* **[Layered Solution Architecture](Index.md)**: A fully layered (multiple projects) solution based on [Domain Driven Design](../../Domain-Driven-Design.md) practices. Recommended for long-term projects that need a **maintainable and extensible** codebase. - -## See Also - -* Check the [Web Application Development Tutorial](../Part-1.md) to see a real-life web application development in a layered architecture. diff --git a/docs/en/Tutorials/Todo/Single-Layer/Index.md b/docs/en/Tutorials/Todo/Single-Layer/Index.md deleted file mode 100644 index b04080e635..0000000000 --- a/docs/en/Tutorials/Todo/Single-Layer/Index.md +++ /dev/null @@ -1,875 +0,0 @@ -# Quick Start - -````json -//[doc-params] -{ - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] -} -```` - -This is a single-part quick-start tutorial to build a simple todo application with the ABP Framework. Here's a screenshot from the final application: - -![todo-list](../todo-list.png) - -You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer). - -{{if UI=="Blazor"}} -We are currently preparing a video tutorial for Blazor UI. You can watch other tutorials for the three UI types from [here](https://www.youtube.com/playlist?list=PLsNclT2aHJcPqZxk7D4tU8LtTeCFcN_ci). -{{else}} -This documentation has a video tutorial on **YouTube**!! You can watch it here: -{{end}} - -{{if UI=="MVC" && DB =="EF"}} - - - -{{else if UI=="BlazorServer" && DB=="EF"}} - - - -{{else if UI=="NG" && DB=="EF"}} - - - -{{else if UI=="MVC" && DB=="Mongo"}} - - - -{{else if UI=="BlazorServer" && DB=="Mongo"}} - - - -{{else if UI=="NG" && DB=="Mongo"}} - - - -{{end}} - -## Pre-Requirements - -* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 8.0+](https://dotnet.microsoft.com/download/dotnet) development. -* [Node v16.x](https://nodejs.org/) - -{{if DB=="Mongo"}} - -* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/) - -{{end}} - -## Creating a New Solution - -In this tutorial, we will use the [ABP CLI](../../../CLI.md) to create the sample application with the ABP Framework. You can run the following command in a command-line terminal to install the **ABP CLI**, if you haven't installed it yet: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -Then create an empty folder, open a command-line terminal and execute the following command in the terminal: - -````bash -abp new TodoApp -t app-nolayers{{if UI=="BlazorServer"}} -u blazor-server{{else if UI=="Blazor"}} -u blazor{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} -```` - -{{if UI=="NG"}} - -This will create a new solution, named *TodoApp*, with `angular` and `aspnet-core` folders. Once the solution is ready, open the solution (in the `aspnet-core` folder) with your favorite IDE. - -{{else if UI=="Blazor"}} - -This will create a new solution with three projects: - -* A `blazor` application that contains the Blazor code, the client-side. -* A `host` application, hosts and serves the `blazor` application. -* A `contracts` project, shared library between these two projects. - -Once the solution is ready, open it in your favorite IDE. - -{{else}} - -This will create a new solution with a single project, named *TodoApp*. Once the solution is ready, open it in your favorite IDE. - -{{end}} - -### Create the Database - -You can run the following command in the {{if UI=="Blazor"}} directory of your `TodoApp.Host` project {{else}}root directory of your project (in the same folder of the `.csproj` file){{end}} to create the database and seed the initial data: - -```bash -dotnet run --migrate-database -``` - -This command will create the database and seed the initial data for you. Then you can run the application. - -### Before Running the Application - -#### Installing the Client-Side Packages - -[ABP CLI](../../../CLI.md) runs the `abp install-libs` command behind the scenes to install the required NPM packages for your solution while creating the application. - -However, sometimes this command might need to be manually run. For example, you need to run this command, if you have cloned the application, or the resources from *node_modules* folder didn't copy to *wwwroot/libs* folder, or if you have added a new client-side package dependency to your solution. - -For such cases, run the `abp install-libs` command on the root directory of your solution to install all required NPM packages: - -```bash -abp install-libs -``` - -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. - -{{if UI=="Blazor" || UI=="BlazorServer"}} - -#### Bundling and Minification - -`abp bundle` command offers bundling and minification support for client-side resources (JavaScript and CSS files) for Blazor projects. This command automatically run when you create a new solution with the [ABP CLI](../../../CLI.md). - -However, sometimes you might need to run this command manually. To update script & style references without worrying about dependencies, ordering, etc. in a project, you can run this command in the directory of your blazor application: - -```bash -abp bundle -``` - -> For more details about managing style and script references in Blazor or MAUI Blazor apps, see [Managing Global Scripts & Styles](../../../UI/Blazor/Global-Scripts-Styles.md). - -{{end}} - -### Run the Application - -{{if UI=="MVC" || UI=="BlazorServer"}} - -It is good to run the application before starting the development. Running the application is pretty straight-forward, you can run the application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project: - -{{else if UI=="Blazor"}} - -It is good to run the application before starting the development. Running the application is pretty straight-forward, you just need to run the `TodoApp.Host` application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project. - -> **Note:** The `host` application hosts and serves the `blazor` application. Therefore, you should run the `host` application only. - -After the application runs, open the application in your default browser: - -{{else if UI=="NG"}} - -It is good to run the application before starting the development. The solution has two main applications: - -* `TodoApp` (in the .NET solution) hosts the server-side HTTP API, so the Angular application can consume it. (server-side application) -* `angular` folder contains the Angular application. (client-side application) - -Firstly, run the `TodoApp` project in your favorite IDE (or run the `dotnet run` CLI command on your project directory) to see the server-side HTTP API on [Swagger UI](https://swagger.io/tools/swagger-ui/): - -![todo-swagger-ui-initial](./todo-single-layer-ui-initial.png) - -You can explore and test your HTTP API with this UI. If it works, then we can run the Angular client application. - -You can run the application using the following (or `yarn start`) command: - -````bash -npm start -```` - -This command takes time, but eventually runs and opens the application in your default browser: - -{{end}} - -![todo-ui-initial](../todo-ui-initial.png) - -You can click on the *Login* button and use `admin` as the username and `1q2w3E*` as the password to login to the application. - -All right. We can start coding! - -## Defining the Entity - -This application will have a single [entity](../../../Entities.md) and we can start by creating it. So, create a new `TodoItem` class under the `Entities` folder of {{if UI=="Blazor"}}the `TodoApp.Host` project{{else}}the project{{end}}: - -````csharp -using Volo.Abp.Domain.Entities; - -namespace TodoApp{{if UI=="Blazor"}}.{{end}}Entities; - -public class TodoItem : BasicAggregateRoot -{ - public string Text { get; set; } -} -```` - -`BasicAggregateRoot` is the simplest base class to create root entities, and `Guid` is the primary key (`Id`) of the entity here. - -## Database Integration - -{{if DB=="EF"}} - -Next step is to setup the [Entity Framework Core](../../../Entity-Framework-Core.md) configuration. - -### Mapping Configuration - -Open the `TodoAppDbContext` class (in the `Data` folder) and add a new `DbSet` property to this class: - -````csharp -public DbSet TodoItems { get; set; } -```` - -Then navigate to the `OnModelCreating` method in the same class and add the following mapping code for the `TodoItem ` entity: - -````csharp -protected override void OnModelCreating(ModelBuilder builder) -{ - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - builder.ConfigurePermissionManagement(); - ... - - /* Configure your own tables/entities inside here */ - builder.Entity(b => - { - b.ToTable("TodoItems"); - }); -} -```` - -We've mapped the `TodoItem` entity to the `TodoItems` table in the database. The next step is to create a migration and apply the changes to the database. - -### Code First Migrations - -The startup solution is configured to use Entity Framework Core [Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. - -Open a command-line terminal in the {{if UI=="Blazor"}} directory of your `TodoApp.Host` project {{else}}root directory of your project (in the same folder of the `.csproj` file){{end}} and type the following command: - -````bash -dotnet ef migrations add Added_TodoItem -```` - -This will add a new migration class to the project. You should see the new migration in the `Migrations` folder: - -![todo-efcore-migration](todo-efcore-migration-single-layer.png) - -Then, you can apply changes to the database using the following command, in the same command-line terminal: - -````bash -dotnet ef database update -```` - -{{else if DB=="Mongo"}} - -The next step is to setup the [MongoDB](../../../MongoDB.md) configuration. Open the `TodoAppDbContext` class (under the **Data** folder) in your project and make the following changes: - -1. Add a new property to the class: - -````csharp -public IMongoCollection TodoItems => Collection(); -```` - -2. Add the following code inside the `CreateModel` method: - -````csharp -modelBuilder.Entity(b => -{ - b.CollectionName = "TodoItems"; -}); -```` - -{{end}} - -After the database integrations, now we can start to create application service methods and implement our use-cases. - -## Creating the Application Service - -An [application service](../../../Application-Services.md) is used to perform the use cases of the application. We need to perform the following use cases in this application: - -* Get the list of the todo items -* Create a new todo item -* Delete an existing todo item - -Before starting to implement these use cases, first we need to create a DTO class that will be used in the application service. - -### Creating the Data Transfer Object (DTO) - -[Application services](../../../Application-Services.md) typically get and return DTOs ([Data Transfer Objects](../../../Data-Transfer-Objects.md)) instead of entities. So, create a new `TodoItemDto` class under the `Services/Dtos` folder{{if UI=="Blazor"}} of your `TodoApp.Contracts` project{{end}}: - -```csharp -namespace TodoApp.Services.Dtos; - -public class TodoItemDto -{ - public Guid Id { get; set; } - public string Text { get; set; } -} -``` - -This is a very simple DTO class that has the same properties as the `TodoItem` entity. Now, we are ready to implement our use-cases. - -{{if UI=="Blazor"}} - -### The Application Service Interface - -Create a `ITodoAppService` interface under the `Services` folder of the `TodoApp.Contracts` project, as shown below: - -```csharp -using TodoApp.Services.Dtos; -using Volo.Abp.Application.Services; - -namespace TodoApp.Services; - -public interface ITodoAppService : IApplicationService -{ - Task> GetListAsync(); - - Task CreateAsync(string text); - - Task DeleteAsync(Guid id); -} -``` - -{{end}} - -### The Application Service Implementation - -Create a `TodoAppService` class under the `Services` folder of {{if UI=="Blazor"}}your `TodoApp.Host` project{{else}}your project{{end}}, as shown below: - -```csharp -using TodoApp.Services; -{{if UI=="Blazor"}} -using TodoApp.Services.Dtos; -using TodoApp.Entities; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; -{{else}} -using TodoAppEntities; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; -{{end}} - -namespace TodoApp.Services; - -public class TodoAppService : TodoAppAppService{{if UI=="Blazor"}}, ITodoAppService{{end}} -{ - private readonly IRepository _todoItemRepository; - - public TodoAppService(IRepository todoItemRepository) - { - _todoItemRepository = todoItemRepository; - } - - // TODO: Implement the methods here... -} -``` - -This class inherits from the `TodoAppAppService`, which inherits from the `ApplicationService` class of the ABP Framework and implements our use-cases. ABP provides default generic [repositories](../../../Repositories.md) for the entities. We can use them to perform the fundamental database operations. This class [injects](../../../Dependency-Injection.md) `IRepository`, which is the default repository for the `TodoItem` entity. We will use it to implement our use cases. - -#### Getting the Todo Items - -Let's start by implementing the `GetListAsync` method, which is used to get a list of todo items: - -````csharp -public async Task> GetListAsync() -{ - var items = await _todoItemRepository.GetListAsync(); - return items - .Select(item => new TodoItemDto - { - Id = item.Id, - Text = item.Text - }).ToList(); -} -```` - -We are simply getting the `TodoItem` list from the repository, mapping them to the `TodoItemDto` objects and returning as the result. - -#### Creating a New Todo Item - -The next method is `CreateAsync` and we can implement it as shown below: - -````csharp -public async Task CreateAsync(string text) -{ - var todoItem = await _todoItemRepository.InsertAsync( - new TodoItem {Text = text} - ); - - return new TodoItemDto - { - Id = todoItem.Id, - Text = todoItem.Text - }; -} -```` - -The repository's `InsertAsync` method inserts the given `TodoItem` to the database and returns the same `TodoItem` object. It also sets the `Id`, so we can use it on the returning object. We are simply returning a `TodoItemDto` by creating from the new `TodoItem` entity. - -#### Deleting a Todo Item - -Finally, we can implement the `DeleteAsync` as the following code block: - -````csharp -public async Task DeleteAsync(Guid id) -{ - await _todoItemRepository.DeleteAsync(id); -} -```` - -The application service is ready to be used from the UI layer. So, let's implement it. - -## User Interface - -It is time to show the todo items on the UI! Before starting to write the code, it would be good to remember what we are trying to build. Here's a sample screenshot from the final UI: - -![todo-list](../todo-list.png) - -{{if UI=="MVC"}} - -### Index.cshtml.cs - -Open the `Index.cshtml.cs` file in the `Pages` folder and replace the content with the following code block: - -```csharp -using TodoApp.Services; -using TodoApp.Services.Dtos; -using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; - -namespace TodoApp.Pages; - -public class IndexModel : AbpPageModel -{ - public List TodoItems { get; set; } - - private readonly TodoAppService _todoAppService; - - public IndexModel(TodoAppService todoAppService) - { - _todoAppService = todoAppService; - } - - public async Task OnGetAsync() - { - TodoItems = await _todoAppService.GetListAsync(); - } -} -``` - -This class uses `TodoAppService` to get the list of todo items and assign the `TodoItems` property. We will use it to render the todo items on the razor page. - -### Index.cshtml - -Open the `Index.cshtml` file in the `Pages` folder and replace it with the following content: - -```xml -@page -@model TodoApp.Pages.IndexModel - -@section styles { - -} -@section scripts { - -} - -
    - - - - TODO LIST - - - - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
      - @foreach (var todoItem in Model.TodoItems) - { -
    • - @todoItem.Text -
    • - } -
    -
    -
    -
    -``` - -We are using ABP's [card tag helper](../../../UI/AspNetCore/Tag-Helpers/Cards.md) to create a simple card view. You could directly use the standard bootstrap HTML structure, however the ABP [tag helpers](../../../UI/AspNetCore/Tag-Helpers/Index.md) make it much easier and type safe. - -This page imports a CSS and a JavaScript file, so we should also create them. - -### Index.cshtml.js - -Open the `Index.cshtml.js` file in the `Pages` folder and replace with the following content: - -````js -$(function () { - - // DELETING ITEMS ///////////////////////////////////////// - $('#TodoList').on('click', 'li i', function(){ - var $li = $(this).parent(); - var id = $li.attr('data-id'); - - todoApp.services.todo.delete(id).then(function(){ - $li.remove(); - abp.notify.info('Deleted the todo item.'); - }); - }); - - // CREATING NEW ITEMS ///////////////////////////////////// - $('#NewItemForm').submit(function(e){ - e.preventDefault(); - - var todoText = $('#NewItemText').val(); - todoApp.services.todo.create(todoText).then(function(result){ - $('
  • ') - .html(' ' + result.text) - .appendTo($('#TodoList')); - $('#NewItemText').val(''); - }); - }); -}); -```` - -In the first part, we subscribed to the click events of the trash icons near the todo items, deleted the related item on the server and showed a notification on the UI. Also, we removed the deleted item from the DOM, so we wouldn't need to refresh the page. - -In the second part, we created a new todo item on the server. If it succeeded, we would then manipulate the DOM to insert a new `
  • ` element to the todo list. This way, we wouldn't need to refresh the whole page after creating a new todo item. - -The interesting part here is how we communicate with the server. See the *Dynamic JavaScript Proxies & Auto API Controllers* section to understand how it works. But now, let's continue and complete the application. - -### Index.cshtml.css - -As for the final touch, open the `Index.cshtml.css` file in the `Pages` folder and replace with the following content: - -````css -#TodoList{ - list-style: none; - margin: 0; - padding: 0; -} - -#TodoList li { - padding: 5px; - margin: 5px 0px; - border: 1px solid #cccccc; - background-color: #f5f5f5; -} - -#TodoList li i -{ - opacity: 0.5; -} - -#TodoList li i:hover -{ - opacity: 1; - color: #ff0000; - cursor: pointer; -} -```` - -This is a simple styling for the todo page. We believe that you can do much better :) - -Now, you can run the application again and see the result. - -### Dynamic JavaScript Proxies & Auto API Controllers - -In the `Index.cshtml.js` file, we've used the `todoApp.services.todo.delete(...)` and `todoApp.services.todo.create(...)` functions to communicate with the server. These functions are dynamically created by the ABP Framework, thanks to the [Dynamic JavaScript Client Proxy](../../../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) system. They perform HTTP API calls to the server and return a promise, so you can register a callback to the `then` function as we've done above. - -> `services` keyword comes from the namespace (`namespace TodoApp.Services;`). It's a naming convention. - -However, you may notice that we haven't created any API Controllers, so how does the server handle these requests? This question brings us to the [Auto API Controller](../../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to **API Controllers** by convention. - -If you open [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering the `/swagger` URL in your application, you can see the Todo API: - -![todo-api](../todo-api.png) - -{{else if UI=="Blazor" || UI=="BlazorServer"}} - -### Index.razor.cs - -Open the `Index.razor.cs` file in the `Pages` folder{{if UI=="Blazor"}} in your `Todo.Blazor` project{{end}} and replace the content with the following code block: - -```csharp -{{if UI=="Blazor"}} -using Microsoft.AspNetCore.Components; -using TodoApp.Services; -using TodoApp.Services.Dtos; -{{else}} -using Microsoft.AspNetCore.Components; -using TodoApp.Services; -using TodoApp.Services.Dtos; -{{end}} - -namespace TodoApp.Pages; - -public partial class Index -{ - [Inject] - private {{if UI=="Blazor"}}ITodoAppService{{else}}TodoAppService{{end}} TodoAppService { get; set; } - - private List TodoItems { get; set; } = new List(); - private string NewTodoText { get; set; } - - protected override async Task OnInitializedAsync() - { - TodoItems = await TodoAppService.GetListAsync(); - } - - private async Task Create() - { - var result = await TodoAppService.CreateAsync(NewTodoText); - TodoItems.Add(result); - NewTodoText = null; - } - - private async Task Delete(TodoItemDto todoItem) - { - await TodoAppService.DeleteAsync(todoItem.Id); - await Notify.Info("Deleted the todo item."); - TodoItems.Remove(todoItem); - } -} -``` - -This class uses the {{if UI=="Blazor"}}`ITodoAppService`{{else}}`TodoAppService`{{end}} to get the list of todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server. - -### Index.razor - -Open the `Index.razor` file in the `Pages` folder and replace the content with the following code block: - -```xml -@page "/" -@inherits TodoAppComponentBase - -
    - - - - TODO LIST - - - - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
      - @foreach (var todoItem in TodoItems) - { -
    • - - @todoItem.Text -
    • - } -
    -
    -
    -
    -``` - -### Index.razor.css - -As the final touch, open the `Index.razor.css` file in the `Pages` folder and replace it with the following content: - -````css -#TodoList{ - list-style: none; - margin: 0; - padding: 0; -} - -#TodoList li { - padding: 5px; - margin: 5px 0px; - border: 1px solid #cccccc; - background-color: #f5f5f5; -} - -#TodoList li i -{ - opacity: 0.5; -} - -#TodoList li i:hover -{ - opacity: 1; - color: #ff0000; - cursor: pointer; -} -```` - -This is a simple styling for the todo page. We believe that you can do much better :) - -Now, you can run the {{if UI=="Blazor"}}`TodoApp.Host` project{{else}}application{{end}} again to see the result. - -{{else if UI=="NG"}} - -### Service Proxy Generation - -ABP provides a handy feature to automatically create client-side services to easily consume HTTP APIs provided by the server. - -You first need to run the `TodoApp` project since the proxy generator reads API definitions from the server application. - -Once you run the `TodoApp` project (**Swagger API Definition** will be shown), open a command-line terminal in the directory of `angular` folder and run the following command: - -```bash -abp generate-proxy -t ng -``` - -If everything goes well, it should generate an output as shown below: - -```bash -CREATE src/app/proxy/generate-proxy.json (182755 bytes) -CREATE src/app/proxy/README.md (1000 bytes) -CREATE src/app/proxy/services/todo.service.ts (833 bytes) -CREATE src/app/proxy/services/dtos/models.ts (71 bytes) -CREATE src/app/proxy/services/dtos/index.ts (26 bytes) -CREATE src/app/proxy/services/index.ts (81 bytes) -CREATE src/app/proxy/index.ts (61 bytes) -``` - -Then, we can use the `TodoService` to use the server-side HTTP APIs, as we'll do in the next section. - -### home.component.ts - -Open the `/angular/src/app/home/home.component.ts` file and replace its content with the following code block: - -```ts -import { ToasterService } from "@abp/ng.theme.shared"; -import { Component, OnInit } from '@angular/core'; -import { TodoItemDto } from "@proxy/services/dtos"; -import { TodoService } from "@proxy/services"; - -@Component({ - selector: 'app-home', - templateUrl: './home.component.html', - styleUrls: ['./home.component.scss'], -}) - -export class HomeComponent implements OnInit { - - todoItems: TodoItemDto[]; - newTodoText: string; - - constructor( - private todoService: TodoService, - private toasterService: ToasterService) - { } - - ngOnInit(): void { - this.todoService.getList().subscribe(response => { - this.todoItems = response; - }); - } - - create(): void{ - this.todoService.create(this.newTodoText).subscribe((result) => { - this.todoItems = this.todoItems.concat(result); - this.newTodoText = null; - }); - } - - delete(id: string): void { - this.todoService.delete(id).subscribe(() => { - this.todoItems = this.todoItems.filter(item => item.id !== id); - this.toasterService.info('Deleted the todo item.'); - }); - } -} -``` - -We've used `TodoService` to get the list of todo items and assigned the returning value to the `todoItems` array. We've also added `create` and `delete` methods. These methods will be used on the view side. - -### home.component.html - -Open the `/angular/src/app/home/home.component.html` file and replace its content with the following code block: - -````html -
    -
    -
    -
    TODO LIST
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
      -
    • - {%{{{ todoItem.text }}}%} -
    • -
    -
    -
    -
    -```` - -### home.component.scss - -As the final touch, open the `/angular/src/app/home/home.component.scss` file and replace its content with the following code block: - -````css -#TodoList{ - list-style: none; - margin: 0; - padding: 0; -} - -#TodoList li { - padding: 5px; - margin: 5px 0px; - border: 1px solid #cccccc; - background-color: #f5f5f5; -} - -#TodoList li i -{ - opacity: 0.5; -} - -#TodoList li i:hover -{ - opacity: 1; - color: #ff0000; - cursor: pointer; -} -```` - -This is a simple styling for the todo page. We believe that you can do much better :) - -Now, you can run the application again to see the result. - -{{end}} - -## Conclusion - -In this tutorial, we've built a very simple application to warm up with the ABP Framework. - -## Source Code - -You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer). - -## See Also - -* Check the [Web Application Development Tutorial](../../Part-1.md) to see a real-life web application development in a layered architecture using the [Application Startup Template](../../../Startup-Templates/Application.md). diff --git a/docs/en/Tutorials/Todo/Single-Layer/todo-efcore-migration-single-layer.png b/docs/en/Tutorials/Todo/Single-Layer/todo-efcore-migration-single-layer.png deleted file mode 100644 index fe067e9817..0000000000 Binary files a/docs/en/Tutorials/Todo/Single-Layer/todo-efcore-migration-single-layer.png and /dev/null differ diff --git a/docs/en/Tutorials/Todo/Single-Layer/todo-single-layer-ui-initial.png b/docs/en/Tutorials/Todo/Single-Layer/todo-single-layer-ui-initial.png deleted file mode 100644 index 1a46bbefaa..0000000000 Binary files a/docs/en/Tutorials/Todo/Single-Layer/todo-single-layer-ui-initial.png and /dev/null differ diff --git a/docs/en/Tutorials/Todo/run-without-iisexpress.png b/docs/en/Tutorials/Todo/run-without-iisexpress.png deleted file mode 100644 index 7713daf004..0000000000 Binary files a/docs/en/Tutorials/Todo/run-without-iisexpress.png and /dev/null differ diff --git a/docs/en/Tutorials/Todo/todo-api.png b/docs/en/Tutorials/Todo/todo-api.png deleted file mode 100644 index e698356e40..0000000000 Binary files a/docs/en/Tutorials/Todo/todo-api.png and /dev/null differ diff --git a/docs/en/Tutorials/Todo/todo-efcore-migration.png b/docs/en/Tutorials/Todo/todo-efcore-migration.png deleted file mode 100644 index 8c98351e96..0000000000 Binary files a/docs/en/Tutorials/Todo/todo-efcore-migration.png and /dev/null differ diff --git a/docs/en/Tutorials/Todo/todo-list.png b/docs/en/Tutorials/Todo/todo-list.png deleted file mode 100644 index f50c950331..0000000000 Binary files a/docs/en/Tutorials/Todo/todo-list.png and /dev/null differ diff --git a/docs/en/Tutorials/Todo/todo-swagger-ui-initial.png b/docs/en/Tutorials/Todo/todo-swagger-ui-initial.png deleted file mode 100644 index c0b4ae3596..0000000000 Binary files a/docs/en/Tutorials/Todo/todo-swagger-ui-initial.png and /dev/null differ diff --git a/docs/en/Tutorials/Todo/todo-ui-initial.png b/docs/en/Tutorials/Todo/todo-ui-initial.png deleted file mode 100644 index 780fafa15f..0000000000 Binary files a/docs/en/Tutorials/Todo/todo-ui-initial.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-add-book-button-2.png b/docs/en/Tutorials/images/blazor-add-book-button-2.png deleted file mode 100644 index beec83e6ea..0000000000 Binary files a/docs/en/Tutorials/images/blazor-add-book-button-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-add-book-button.png b/docs/en/Tutorials/images/blazor-add-book-button.png deleted file mode 100644 index a28856cd91..0000000000 Binary files a/docs/en/Tutorials/images/blazor-add-book-button.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-add-books-component.png b/docs/en/Tutorials/images/blazor-add-books-component.png deleted file mode 100644 index ccb414ec4f..0000000000 Binary files a/docs/en/Tutorials/images/blazor-add-books-component.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-bookstore-book-list-2.png b/docs/en/Tutorials/images/blazor-bookstore-book-list-2.png deleted file mode 100644 index dd94f29fef..0000000000 Binary files a/docs/en/Tutorials/images/blazor-bookstore-book-list-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors-2.png b/docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors-2.png deleted file mode 100644 index 1ecc08d6c3..0000000000 Binary files a/docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors.png b/docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors.png deleted file mode 100644 index e9eb55dc6f..0000000000 Binary files a/docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-bookstore-book-list.png b/docs/en/Tutorials/images/blazor-bookstore-book-list.png deleted file mode 100644 index 61cf0ebf66..0000000000 Binary files a/docs/en/Tutorials/images/blazor-bookstore-book-list.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-delete-book-action-2.png b/docs/en/Tutorials/images/blazor-delete-book-action-2.png deleted file mode 100644 index 8fd45a5789..0000000000 Binary files a/docs/en/Tutorials/images/blazor-delete-book-action-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-delete-book-action.png b/docs/en/Tutorials/images/blazor-delete-book-action.png deleted file mode 100644 index 258bd29d1d..0000000000 Binary files a/docs/en/Tutorials/images/blazor-delete-book-action.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-edit-book-action-2.png b/docs/en/Tutorials/images/blazor-edit-book-action-2.png deleted file mode 100644 index 3c3fed77ff..0000000000 Binary files a/docs/en/Tutorials/images/blazor-edit-book-action-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-edit-book-action-3.png b/docs/en/Tutorials/images/blazor-edit-book-action-3.png deleted file mode 100644 index 60e19b3cda..0000000000 Binary files a/docs/en/Tutorials/images/blazor-edit-book-action-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-edit-book-action.png b/docs/en/Tutorials/images/blazor-edit-book-action.png deleted file mode 100644 index 7328154c7e..0000000000 Binary files a/docs/en/Tutorials/images/blazor-edit-book-action.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-edit-book-modal-2.png b/docs/en/Tutorials/images/blazor-edit-book-modal-2.png deleted file mode 100644 index 0d6311c298..0000000000 Binary files a/docs/en/Tutorials/images/blazor-edit-book-modal-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-edit-book-modal.png b/docs/en/Tutorials/images/blazor-edit-book-modal.png deleted file mode 100644 index 679ce2bdc7..0000000000 Binary files a/docs/en/Tutorials/images/blazor-edit-book-modal.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-menu-bookstore.png b/docs/en/Tutorials/images/blazor-menu-bookstore.png deleted file mode 100644 index 097175dc64..0000000000 Binary files a/docs/en/Tutorials/images/blazor-menu-bookstore.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-new-book-modal-2.png b/docs/en/Tutorials/images/blazor-new-book-modal-2.png deleted file mode 100644 index 6ac1879bdf..0000000000 Binary files a/docs/en/Tutorials/images/blazor-new-book-modal-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/blazor-new-book-modal.png b/docs/en/Tutorials/images/blazor-new-book-modal.png deleted file mode 100644 index 08942800dc..0000000000 Binary files a/docs/en/Tutorials/images/blazor-new-book-modal.png and /dev/null differ diff --git a/docs/en/Tutorials/images/book-create-modal-with-author-2.png b/docs/en/Tutorials/images/book-create-modal-with-author-2.png deleted file mode 100644 index 6588b9d0e6..0000000000 Binary files a/docs/en/Tutorials/images/book-create-modal-with-author-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/book-create-modal-with-author.png b/docs/en/Tutorials/images/book-create-modal-with-author.png deleted file mode 100644 index 0ecaac08f4..0000000000 Binary files a/docs/en/Tutorials/images/book-create-modal-with-author.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-actions-buttons-2.png b/docs/en/Tutorials/images/bookstore-actions-buttons-2.png deleted file mode 100644 index 022ab1fd26..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-actions-buttons-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-actions-buttons.png b/docs/en/Tutorials/images/bookstore-actions-buttons.png deleted file mode 100644 index 8f495e0cc3..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-actions-buttons.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-add-create-dialog-v2.png b/docs/en/Tutorials/images/bookstore-add-create-dialog-v2.png deleted file mode 100644 index fd06f3e4e5..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-add-create-dialog-v2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-add-edit-dialog.png b/docs/en/Tutorials/images/bookstore-add-edit-dialog.png deleted file mode 100644 index adfc036d0b..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-add-edit-dialog.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-add-index-page-v2.png b/docs/en/Tutorials/images/bookstore-add-index-page-v2.png deleted file mode 100644 index a4760261c6..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-add-index-page-v2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-add-migration-authors.png b/docs/en/Tutorials/images/bookstore-add-migration-authors.png deleted file mode 100644 index 2c16034ec4..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-add-migration-authors.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-added-author-to-book-list-2.png b/docs/en/Tutorials/images/bookstore-added-author-to-book-list-2.png deleted file mode 100644 index 615bdc551e..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-added-author-to-book-list-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-added-author-to-book-list-angular.png b/docs/en/Tutorials/images/bookstore-added-author-to-book-list-angular.png deleted file mode 100644 index e033826c2a..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-added-author-to-book-list-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-added-author-to-book-list.png b/docs/en/Tutorials/images/bookstore-added-author-to-book-list.png deleted file mode 100644 index 38dfd300a7..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-added-author-to-book-list.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-added-authors-to-modals-2.png b/docs/en/Tutorials/images/bookstore-added-authors-to-modals-2.png deleted file mode 100644 index ed59389b80..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-added-authors-to-modals-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-added-authors-to-modals.png b/docs/en/Tutorials/images/bookstore-added-authors-to-modals.png deleted file mode 100644 index ea2aba892c..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-added-authors-to-modals.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-angular-author-selection-2.png b/docs/en/Tutorials/images/bookstore-angular-author-selection-2.png deleted file mode 100644 index fca18f5cb0..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-angular-author-selection-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-angular-author-selection.png b/docs/en/Tutorials/images/bookstore-angular-author-selection.png deleted file mode 100644 index 8518ba521a..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-angular-author-selection.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-angular-authors-page-2.png b/docs/en/Tutorials/images/bookstore-angular-authors-page-2.png deleted file mode 100644 index 71ade7df74..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-angular-authors-page-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-angular-authors-page.png b/docs/en/Tutorials/images/bookstore-angular-authors-page.png deleted file mode 100644 index 3d965f2179..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-angular-authors-page.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-angular-file-tree.png b/docs/en/Tutorials/images/bookstore-angular-file-tree.png deleted file mode 100644 index b311cf50c6..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-angular-file-tree.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png b/docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png deleted file mode 100644 index 56a6395c31..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-angular-service-proxy-author.png b/docs/en/Tutorials/images/bookstore-angular-service-proxy-author.png deleted file mode 100644 index 5d89973669..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-angular-service-proxy-author.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-appservice-tests.png b/docs/en/Tutorials/images/bookstore-appservice-tests.png deleted file mode 100644 index 276c616cac..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-appservice-tests.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-author-domain-layer.png b/docs/en/Tutorials/images/bookstore-author-domain-layer.png deleted file mode 100644 index 2256971559..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-author-domain-layer.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-author-permissions-2.png b/docs/en/Tutorials/images/bookstore-author-permissions-2.png deleted file mode 100644 index 3d9a4be983..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-author-permissions-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-author-permissions-3.png b/docs/en/Tutorials/images/bookstore-author-permissions-3.png deleted file mode 100644 index 22b7b2112e..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-author-permissions-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-author-permissions.png b/docs/en/Tutorials/images/bookstore-author-permissions.png deleted file mode 100644 index bfb4c208ef..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-author-permissions.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-authors-blazor-ui.png b/docs/en/Tutorials/images/bookstore-authors-blazor-ui.png deleted file mode 100644 index 13a6ad74c7..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-authors-blazor-ui.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-authors-page-2.png b/docs/en/Tutorials/images/bookstore-authors-page-2.png deleted file mode 100644 index a61debba0e..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-authors-page-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-authors-page-3.png b/docs/en/Tutorials/images/bookstore-authors-page-3.png deleted file mode 100644 index 468171d079..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-authors-page-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-authors-page.png b/docs/en/Tutorials/images/bookstore-authors-page.png deleted file mode 100644 index 477437b525..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-authors-page.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-book-and-booktype.png b/docs/en/Tutorials/images/bookstore-book-and-booktype.png deleted file mode 100644 index e4b53d6298..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-book-and-booktype.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-book-list-2.png b/docs/en/Tutorials/images/bookstore-book-list-2.png deleted file mode 100644 index 8305a6caf4..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-book-list-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-book-list-3.png b/docs/en/Tutorials/images/bookstore-book-list-3.png deleted file mode 100644 index 2022efbb35..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-book-list-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-book-list-4.png b/docs/en/Tutorials/images/bookstore-book-list-4.png deleted file mode 100644 index fd32a9fcc2..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-book-list-4.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-book-list-angular.png b/docs/en/Tutorials/images/bookstore-book-list-angular.png deleted file mode 100644 index 91d9374499..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-book-list-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-book-list.png b/docs/en/Tutorials/images/bookstore-book-list.png deleted file mode 100644 index f30c4eec72..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-book-list.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-books-table-actions.png b/docs/en/Tutorials/images/bookstore-books-table-actions.png deleted file mode 100644 index 431fb2defc..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-books-table-actions.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-books-table.png b/docs/en/Tutorials/images/bookstore-books-table.png deleted file mode 100644 index 7254a97566..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-books-table.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-books-with-authorname-angular-2.png b/docs/en/Tutorials/images/bookstore-books-with-authorname-angular-2.png deleted file mode 100644 index f040ac3ba4..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-books-with-authorname-angular-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png b/docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png deleted file mode 100644 index d388a49cd0..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-confirmation-popup-2.png b/docs/en/Tutorials/images/bookstore-confirmation-popup-2.png deleted file mode 100644 index 5698c3f1b6..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-confirmation-popup-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-confirmation-popup.png b/docs/en/Tutorials/images/bookstore-confirmation-popup.png deleted file mode 100644 index 86b908e14a..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-confirmation-popup.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-create-dialog-2.png b/docs/en/Tutorials/images/bookstore-create-dialog-2.png deleted file mode 100644 index eb84d88065..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-create-dialog-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-create-dialog-3.png b/docs/en/Tutorials/images/bookstore-create-dialog-3.png deleted file mode 100644 index 0365de3b26..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-create-dialog-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-create-dialog.png b/docs/en/Tutorials/images/bookstore-create-dialog.png deleted file mode 100644 index f09f2f394f..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-create-dialog.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-create-project-angular.png b/docs/en/Tutorials/images/bookstore-create-project-angular.png deleted file mode 100644 index c778baa656..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-create-project-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-create-project-mvc.png b/docs/en/Tutorials/images/bookstore-create-project-mvc.png deleted file mode 100644 index d7b2a9abec..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-create-project-mvc.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png b/docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png deleted file mode 100644 index 9d168b3c55..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png b/docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png deleted file mode 100644 index 65fd9da213..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-database-tables-ef.png b/docs/en/Tutorials/images/bookstore-database-tables-ef.png deleted file mode 100644 index 4b99c50713..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-database-tables-ef.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-database-tables-mongodb.png b/docs/en/Tutorials/images/bookstore-database-tables-mongodb.png deleted file mode 100644 index 88e1acc758..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-database-tables-mongodb.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-dbmigrator-on-solution.png b/docs/en/Tutorials/images/bookstore-dbmigrator-on-solution.png deleted file mode 100644 index cfc74b1f0a..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-dbmigrator-on-solution.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-edit-button-2.png b/docs/en/Tutorials/images/bookstore-edit-button-2.png deleted file mode 100644 index df5b8128f8..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-edit-button-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-edit-button-3.png b/docs/en/Tutorials/images/bookstore-edit-button-3.png deleted file mode 100644 index 672e4610f1..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-edit-button-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-edit-button.png b/docs/en/Tutorials/images/bookstore-edit-button.png deleted file mode 100644 index b6083364f7..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-edit-button.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-edit-delete-actions-2.png b/docs/en/Tutorials/images/bookstore-edit-delete-actions-2.png deleted file mode 100644 index a21f621c47..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-edit-delete-actions-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-edit-delete-actions.png b/docs/en/Tutorials/images/bookstore-edit-delete-actions.png deleted file mode 100644 index 923acba533..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-edit-delete-actions.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-efcore-migration-authors.png b/docs/en/Tutorials/images/bookstore-efcore-migration-authors.png deleted file mode 100644 index 60b458ae5b..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-efcore-migration-authors.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-efcore-migration.png b/docs/en/Tutorials/images/bookstore-efcore-migration.png deleted file mode 100644 index d5f057d66e..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-efcore-migration.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-empty-new-book-modal-2.png b/docs/en/Tutorials/images/bookstore-empty-new-book-modal-2.png deleted file mode 100644 index 9dd24e4cee..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-empty-new-book-modal-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-empty-new-book-modal.png b/docs/en/Tutorials/images/bookstore-empty-new-book-modal.png deleted file mode 100644 index 6168a91fdf..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-empty-new-book-modal.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-final-actions-dropdown-2.png b/docs/en/Tutorials/images/bookstore-final-actions-dropdown-2.png deleted file mode 100644 index 15403f2921..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-final-actions-dropdown-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-final-actions-dropdown.png b/docs/en/Tutorials/images/bookstore-final-actions-dropdown.png deleted file mode 100644 index 94d4e20aef..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-final-actions-dropdown.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-generate-state-books.png b/docs/en/Tutorials/images/bookstore-generate-state-books.png deleted file mode 100644 index 1a89bfbaeb..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-generate-state-books.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-getlist-result-network.png b/docs/en/Tutorials/images/bookstore-getlist-result-network.png deleted file mode 100644 index ef9977e00c..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-getlist-result-network.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-homepage.png b/docs/en/Tutorials/images/bookstore-homepage.png deleted file mode 100644 index dc015aa67d..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-homepage.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-index-js-file-v2.png b/docs/en/Tutorials/images/bookstore-index-js-file-v2.png deleted file mode 100644 index 2db5ab1a5e..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-index-js-file-v2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-index-js-file-v3.png b/docs/en/Tutorials/images/bookstore-index-js-file-v3.png deleted file mode 100644 index a3b34c5161..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-index-js-file-v3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-initial-book-list-page.png b/docs/en/Tutorials/images/bookstore-initial-book-list-page.png deleted file mode 100644 index b7082da979..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-initial-book-list-page.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-initial-books-page-with-layout.png b/docs/en/Tutorials/images/bookstore-initial-books-page-with-layout.png deleted file mode 100644 index a07f488012..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-initial-books-page-with-layout.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-javascript-proxy-console.png b/docs/en/Tutorials/images/bookstore-javascript-proxy-console.png deleted file mode 100644 index 141da741dd..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-javascript-proxy-console.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-localization-files-v2.png b/docs/en/Tutorials/images/bookstore-localization-files-v2.png deleted file mode 100644 index 34f5819fe7..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-localization-files-v2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-menu-items.png b/docs/en/Tutorials/images/bookstore-menu-items.png deleted file mode 100644 index ef3c404855..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-menu-items.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-migrations-applied-angular.png b/docs/en/Tutorials/images/bookstore-migrations-applied-angular.png deleted file mode 100644 index 8c14e6e6f5..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-migrations-applied-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-migrations-applied-mvc.png b/docs/en/Tutorials/images/bookstore-migrations-applied-mvc.png deleted file mode 100644 index 8369cfef8e..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-migrations-applied-mvc.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-author-modal-2.png b/docs/en/Tutorials/images/bookstore-new-author-modal-2.png deleted file mode 100644 index 2126872081..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-author-modal-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-author-modal.png b/docs/en/Tutorials/images/bookstore-new-author-modal.png deleted file mode 100644 index 98e939a264..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-author-modal.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-button-2.png b/docs/en/Tutorials/images/bookstore-new-book-button-2.png deleted file mode 100644 index 1e49299f1d..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-button-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-button-3.png b/docs/en/Tutorials/images/bookstore-new-book-button-3.png deleted file mode 100644 index 8de58736e6..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-button-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-button-small-2.png b/docs/en/Tutorials/images/bookstore-new-book-button-small-2.png deleted file mode 100644 index a356d6e352..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-button-small-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-button-small.png b/docs/en/Tutorials/images/bookstore-new-book-button-small.png deleted file mode 100644 index 4a3eac313b..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-button-small.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-button.png b/docs/en/Tutorials/images/bookstore-new-book-button.png deleted file mode 100644 index b4d36d6075..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-button.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-form-v2.png b/docs/en/Tutorials/images/bookstore-new-book-form-v2.png deleted file mode 100644 index 9c06825eea..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-form-v2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-form-v3.png b/docs/en/Tutorials/images/bookstore-new-book-form-v3.png deleted file mode 100644 index 1dbf73c98b..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-form-v3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-book-form.png b/docs/en/Tutorials/images/bookstore-new-book-form.png deleted file mode 100644 index aecc1d4a1a..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-book-form.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-menu-item-2.png b/docs/en/Tutorials/images/bookstore-new-menu-item-2.png deleted file mode 100644 index 19c5806a93..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-menu-item-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-new-menu-item.png b/docs/en/Tutorials/images/bookstore-new-menu-item.png deleted file mode 100644 index d77d1938db..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-new-menu-item.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-open-package-manager-console.png b/docs/en/Tutorials/images/bookstore-open-package-manager-console.png deleted file mode 100644 index 7b1cccb748..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-open-package-manager-console.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-permissions-ui-2.png b/docs/en/Tutorials/images/bookstore-permissions-ui-2.png deleted file mode 100644 index 61503b1c6c..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-permissions-ui-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-permissions-ui.png b/docs/en/Tutorials/images/bookstore-permissions-ui.png deleted file mode 100644 index d2259fdedd..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-permissions-ui.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-pmc-add-book-migration-v2.png b/docs/en/Tutorials/images/bookstore-pmc-add-book-migration-v2.png deleted file mode 100644 index b1bdadbc9c..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-pmc-add-book-migration-v2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-pmc-add-book-migration.png b/docs/en/Tutorials/images/bookstore-pmc-add-book-migration.png deleted file mode 100644 index cb3b6440c7..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-pmc-add-book-migration.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-service-terminal-output.png b/docs/en/Tutorials/images/bookstore-service-terminal-output.png deleted file mode 100644 index 7567f0d95f..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-service-terminal-output.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-solution-structure-angular.png b/docs/en/Tutorials/images/bookstore-solution-structure-angular.png deleted file mode 100644 index 88dcacffed..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-solution-structure-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-solution-structure-mvc.png b/docs/en/Tutorials/images/bookstore-solution-structure-mvc.png deleted file mode 100644 index ce821eba72..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-solution-structure-mvc.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-start-project-angular.png b/docs/en/Tutorials/images/bookstore-start-project-angular.png deleted file mode 100644 index c3c9a25beb..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-start-project-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-start-project-mvc.png b/docs/en/Tutorials/images/bookstore-start-project-mvc.png deleted file mode 100644 index d8e64184b3..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-start-project-mvc.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-swagger-book-dto-properties.png b/docs/en/Tutorials/images/bookstore-swagger-book-dto-properties.png deleted file mode 100644 index b0e8fac507..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-swagger-book-dto-properties.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-swagger.png b/docs/en/Tutorials/images/bookstore-swagger.png deleted file mode 100644 index 423142e15d..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-swagger.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-test-js-proxy-getlist-network.png b/docs/en/Tutorials/images/bookstore-test-js-proxy-getlist-network.png deleted file mode 100644 index ffa63dc581..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-test-js-proxy-getlist-network.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-test-js-proxy-getlist.png b/docs/en/Tutorials/images/bookstore-test-js-proxy-getlist.png deleted file mode 100644 index 7fe3cead35..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-test-js-proxy-getlist.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-test-projects-angular.png b/docs/en/Tutorials/images/bookstore-test-projects-angular.png deleted file mode 100644 index 51eb6455de..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-test-projects-angular.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-test-projects-mvc.png b/docs/en/Tutorials/images/bookstore-test-projects-mvc.png deleted file mode 100644 index 45d08ecea3..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-test-projects-mvc.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-test-projects-v2.png b/docs/en/Tutorials/images/bookstore-test-projects-v2.png deleted file mode 100644 index 45d08ecea3..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-test-projects-v2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-update-database-after-book-entity.png b/docs/en/Tutorials/images/bookstore-update-database-after-book-entity.png deleted file mode 100644 index 81cb3f1440..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-update-database-after-book-entity.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-user-management.png b/docs/en/Tutorials/images/bookstore-user-management.png deleted file mode 100644 index d7d3429826..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-user-management.png and /dev/null differ diff --git a/docs/en/Tutorials/images/bookstore-visual-studio-solution-v3.png b/docs/en/Tutorials/images/bookstore-visual-studio-solution-v3.png deleted file mode 100644 index ce821eba72..0000000000 Binary files a/docs/en/Tutorials/images/bookstore-visual-studio-solution-v3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/generate-proxy-command.png b/docs/en/Tutorials/images/generate-proxy-command.png deleted file mode 100644 index d7f5706d78..0000000000 Binary files a/docs/en/Tutorials/images/generate-proxy-command.png and /dev/null differ diff --git a/docs/en/Tutorials/images/generated-proxies-2.png b/docs/en/Tutorials/images/generated-proxies-2.png deleted file mode 100644 index f13aff1ac6..0000000000 Binary files a/docs/en/Tutorials/images/generated-proxies-2.png and /dev/null differ diff --git a/docs/en/Tutorials/images/generated-proxies-3.png b/docs/en/Tutorials/images/generated-proxies-3.png deleted file mode 100644 index 8af6942e58..0000000000 Binary files a/docs/en/Tutorials/images/generated-proxies-3.png and /dev/null differ diff --git a/docs/en/Tutorials/images/generated-proxies.png b/docs/en/Tutorials/images/generated-proxies.png deleted file mode 100644 index 669e095abb..0000000000 Binary files a/docs/en/Tutorials/images/generated-proxies.png and /dev/null differ diff --git a/docs/en/Tutorials/images/mozilla-self-signed-cert-error.png b/docs/en/Tutorials/images/mozilla-self-signed-cert-error.png deleted file mode 100644 index 4f411dd748..0000000000 Binary files a/docs/en/Tutorials/images/mozilla-self-signed-cert-error.png and /dev/null differ diff --git a/docs/en/Tutorials/images/vs-run-without-iisexpress.png b/docs/en/Tutorials/images/vs-run-without-iisexpress.png deleted file mode 100644 index e837ed6ffc..0000000000 Binary files a/docs/en/Tutorials/images/vs-run-without-iisexpress.png and /dev/null differ diff --git a/docs/en/Tutorials/images/youtube.png b/docs/en/Tutorials/images/youtube.png deleted file mode 100644 index 9916840e0f..0000000000 Binary files a/docs/en/Tutorials/images/youtube.png and /dev/null differ diff --git a/docs/en/UI/Angular/Abp-Window-Service.md b/docs/en/UI/Angular/Abp-Window-Service.md deleted file mode 100644 index bc060f79b5..0000000000 --- a/docs/en/UI/Angular/Abp-Window-Service.md +++ /dev/null @@ -1,41 +0,0 @@ -# Abp Window Service - - -## Download Blob as File -AbpWindowService is an Angular service designed to provide utility methods related to window operations. The service has a `downloadBlob` function, which is used for downloading blobs as files within the context of a web application. - -### Usage - -To make use of the `AbpWindowService` in your Angular application, follow the steps below: - -### Injection -Firstly, ensure that the service is injected into the component or any other Angular entity where you wish to use it. - -```js -import { AbpWindowService } from '@abp/ng.core'; - -constructor(private abpWindowService: AbpWindowService) { } -// or -// private abpWindowService = inject(AbpWindowService) -``` - -### Downloading a Blob - -Once you have the service injected, you can use the downloadBlob method to initiate the download of blob data as a file. For instance: - -```js -someMethod() { - const myBlob = new Blob(["Hello, World!"], { type: "text/plain" }); - this.abpWindowService.downloadBlob(myBlob, "hello.txt"); -} -``` - -### Permissions & Considerations - -Ensure that you have appropriate permissions and user interactions before triggering a download. Since downloadBlob initiates a download programmatically, it's best to tie this action to direct user interactions, such as button clicks, to prevent unexpected behaviors or browser restrictions. - - -### DOCUMENT Token in Service - -Angular, being a platform-agnostic framework, is designed to support not only browser-based applications but also other environments like server-side rendering (SSR) through Angular Universal. This design philosophy introduces challenges when accessing global browser-specific objects like window or document directly. To address this, Angular provides a DOCUMENT token that can be used to inject the document object into Angular entities like components and services. - diff --git a/docs/en/UI/Angular/Account-Module.md b/docs/en/UI/Angular/Account-Module.md deleted file mode 100644 index 28852f6b4d..0000000000 --- a/docs/en/UI/Angular/Account-Module.md +++ /dev/null @@ -1,148 +0,0 @@ -# Angular UI Account Module - -Angular UI account module is available as of v4.3. It contains some pages (login, register, My account, etc.). - -If you add the account module to your project; - -- "My account" link in the current user dropdown on the top bar will redirect the user to a page in the account module. -- You can switch the authentication flow to the resource owner password flow. - - -### Account Module Implementation - -Install the `@abp/ng.account` NPM package by running the below command: - -```bash -npm install @abp/ng.account -``` - -> Make sure v4.3 or higher version is installed. - -Open the `app.module.ts` and add `AccountConfigModule.forRoot()` to the imports array as shown below: - -```js -// app.module.ts - -import { AccountConfigModule } from '@abp/ng.account/config'; -//... - -@NgModule({ - imports: [ - //... - AccountConfigModule.forRoot() - ], - //... -}) -export class AppModule {} -``` - -Open the `app-routing.module.ts` and add the `account` route to `routes` array as follows: - -```js -// app-routing.module.ts -const routes: Routes = [ - //... - { - path: 'account', - loadChildren: () => import('@abp/ng.account').then(m => m.AccountModule.forLazy()), - }, - //... -export class AppRoutingModule {} -``` - -### Account Public Module Implementation for Commercial Templates - -The pro startup template comes with `@volo/abp.ng.account` package. You should update the package version to v4.3 or higher version. The package can be updated by running the following command: - -```bash -npm install @volo/abp.ng.account -``` -> Make sure v4.3 or higher version is installed. - -Open the `app.module.ts` and add `AccountPublicConfigModule.forRoot()` to the imports array as shown below: - -> Ensure that the `Account Layout Module` has been added if you are using the Lepton X theme. If you miss the step, you will get an error message that says `Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "AccountLayoutModule.forRoot()" to your app.module configuration.` when you try to access the account pages. Otherwise, you can skip adding the `AccountLayoutModule` step. - - -```js -// app.module.ts - -import { AccountPublicConfigModule } from '@volo/abp.ng.account/public/config'; -// if you are using or want to use Lepton X, you should add AccountLayoutModule -// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account' - -//... - -@NgModule({ - imports: [ - //... - AccountPublicConfigModule.forRoot(), - // AccountLayoutModule.forRoot() // Only for Lepton X - ], - //... -}) -export class AppModule {} -``` - -Open the `app-routing.module.ts` and add the `account` route to `routes` array as follows: - -```js -// app-routing.module.ts -const routes: Routes = [ - //... - { - path: 'account', - loadChildren: () => import('@volo/abp.ng.account/public').then(m => m.AccountPublicModule.forLazy()), - }, - //... -export class AppRoutingModule {} -``` - -### My Account Page - -Before v4.3, the "My account" link in the current user dropdown on the top bar redirected the user to MVC's profile management page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account module instead. - -### Personal Info Page Confirm Message - -When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert. - -If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false - -```js -// app-routing.module.ts -const routes: Routes = [ - //... - { - path: 'account', - loadChildren: () => import('@volo/abp.ng.account/public').then(m => m.AccountPublicModule.forLazy({ isPersonalSettingsChangedConfirmationActive:false })), - }, - //... -export class AppRoutingModule {} -``` - -### Security Logs Page [COMMERCIAL] - -Before v4.3, the "Security Logs" link in the current user dropdown on the top bar redirected the user to MVC's security logs page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account public module instead. - -### Resource Owner Password Flow - -OAuth is preconfigured as authorization code flow in Angular application templates by default. If you added the account module to your project, you can switch the flow to resource owner password flow by changing the OAuth configuration in the _environment.ts_ files as shown below: - -```js -import { Config } from '@abp/ng.core'; - -export const environment = { - // other options removed for sake of brevity - - oAuthConfig: { - issuer: 'https://localhost:44305', // AuthServer url - clientId: 'MyProjectName_App', - dummyClientSecret: '1q2w3e*', - scope: 'offline_access MyProjectName', - }, - - // other options removed for sake of brevity -} as Config.Environment; -``` - -See the [Authorization in Angular UI](./Authorization.md) document for more details. diff --git a/docs/en/UI/Angular/Authorization.md b/docs/en/UI/Angular/Authorization.md deleted file mode 100644 index d2e8a61d51..0000000000 --- a/docs/en/UI/Angular/Authorization.md +++ /dev/null @@ -1,181 +0,0 @@ -## Authorization in Angular UI - -OAuth is preconfigured in Angular application templates. So, when you start a project using the CLI (or Suite, for that matter), authorization already works. ABP Angular UI packages are using [angular-oauth2-oidc library](https://github.com/manfredsteyer/angular-oauth2-oidc#logging-in) for managing OAuth in the Angular client. -You can find **OAuth configuration** in the _environment.ts_ files. - -### Authorization Code Flow - -```js -import { Config } from '@abp/ng.core'; - -const baseUrl = 'http://localhost:4200'; - -export const environment = { - // other options removed for sake of brevity - - oAuthConfig: { - issuer: 'https://localhost:44305', - redirectUri: baseUrl, - clientId: 'MyProjectName_App', - responseType: 'code', - scope: 'offline_access MyProjectName', - }, - - // other options removed for sake of brevity -} as Config.Environment; - -``` - -This configuration results in an [OAuth authorization code flow with PKCE](https://tools.ietf.org/html/rfc7636). -According to this flow, the user is redirected to an external login page which is built with MVC. So, if you need **to customize the login page**, please follow [this community article](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd). - -### Resource Owner Password Flow - -If you have used the [Angular UI account module](./Account-Module) in your project, you can switch to the resource owner password flow by changing the OAuth configuration in the _environment.ts_ files as shown below: - -```js -import { Config } from '@abp/ng.core'; - -export const environment = { - // other options removed for sake of brevity - - oAuthConfig: { - issuer: 'https://localhost:44305', - clientId: 'MyProjectName_App', - dummyClientSecret: '1q2w3e*', - scope: 'offline_access MyProjectName', - }, - - // other options removed for sake of brevity -} as Config.Environment; -``` - -According to this flow, the user is redirected to the login page in the account module. - -### Error Filtering - -In [AuthFlowStrategy](https://github.com/abpframework/abp/blob/21e70fd66154d4064d03b1a438f20a2e4318715e/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts#L24) class, there is a method called `listenToOauthErrors` that listens to `OAuthErrorEvent` errors. This method clears the localStorage for OAuth keys. However, in certain cases, we might want to skip this process. To achieve this, we can use the `AuthErrorFilterService`. -The `AuthErrorFilterService` is an abstract service that needs to be replaced with a custom implementation - -> By default, this service is replaced in the `@abp/ng.oauth` package - -### Usage - -#### 1.Create an auth-filter.provider - -```js -import { APP_INITIALIZER, inject } from '@angular/core'; -import { AuthErrorFilter, AuthErrorEvent, AuthErrorFilterService } from '@abp/ng.core'; -import { eCustomersAuthFilterNames } from '../enums'; - -export const CUSTOMERS_AUTH_FILTER_PROVIDER = [ - { provide: APP_INITIALIZER, useFactory: configureAuthFilter, multi: true }, -]; - -type Reason = object & { error: { grant_type: string | undefined } }; - -function configureAuthFilter() { - const errorFilterService = inject( - AuthErrorFilterService, AuthErrorEvent>, - ); - const filter: AuthErrorFilter = { - id: eCustomersAuthFilterNames.LinkedUser, - executable: true, - execute: (event: AuthErrorEvent) => { - const { reason } = event; - const { - error: { grant_type }, - } = (reason || {}); - - return !!grant_type && grant_type === eCustomersAuthFilterNames.LinkedUser; - }, - }; - - return () => errorFilterService.add(filter); -} -``` - -- `AuthErrorFilter:` is a model for filter object and it have 3 properties - - `id:` a unique key in the list for the filter object - - `executable:` a status for the filter object. If it's false then it won't work, yet it'll stay in the list - - `execute:` a function that stores the skip logic - -#### 2.Add to the FeatureConfigModule - -```js -import { ModuleWithProviders, NgModule } from "@angular/core"; -import { CUSTOMERS_AUTH_FILTER_PROVIDER } from "./providers/auth-filter.provider"; - -@NgModule() -export class CustomersConfigModule { - static forRoot(): ModuleWithProviders { - return { - ngModule: CustomersConfigModule, - providers: [CUSTOMERS_AUTH_FILTER_PROVIDER], - }; - } -} -``` - -Now it'll skip the clearing of OAuth storage keys for `LinkedUser` grant_type if any `OAuthErrorEvent` occurs - -#### Replace with custom implementation - -Use the `AbstractAuthErrorFilter` class for signs of process. - -#### Example - -`my-auth-error-filter.service.ts` - -```js -import { Injectable, signal } from '@angular/core'; -import { MyAuthErrorEvent } from 'angular-my-auth-oidc'; -import { AbstractAuthErrorFilter, AuthErrorFilter } from '@abp/ng.core'; - -@Injectable({ providedIn: 'root' }) -export class OAuthErrorFilterService extends AbstractAuthErrorFilter< - AuthErrorFilter, - MyAuthErrorEvent -> { - protected readonly _filters = signal>>([]); - readonly filters = this._filters.asReadonly(); - - get(id: string): AuthErrorFilter { - return this._filters().find(({ id: _id }) => _id === id); - } - - add(filter: AuthErrorFilter): void { - this._filters.update(items => [...items, filter]); - } - - patch(item: Partial>): void { - const _item = this.filters().find(({ id }) => id === item.id); - if (!_item) { - return; - } - - Object.assign(_item, item); - } - - remove(id: string): void { - const item = this.filters().find(({ id: _id }) => _id === id); - if (!item) { - return; - } - - this._filters.update(items => items.filter(({ id: _id }) => _id !== id)); - } - - run(event: MyAuthErrorEvent): boolean { - return this.filters() - .filter(({ executable }) => !!executable) - .map(({ execute }) => execute(event)) - .some(item => item); - } -} - -``` - -## See Also - -* [Video tutorials](https://abp.io/video-courses/essentials/authorization) \ No newline at end of file diff --git a/docs/en/UI/Angular/Basic-Theme.md b/docs/en/UI/Angular/Basic-Theme.md deleted file mode 100644 index 651be9b637..0000000000 --- a/docs/en/UI/Angular/Basic-Theme.md +++ /dev/null @@ -1,107 +0,0 @@ -# Angular UI: Basic Theme - -The Basic Theme is a theme implementation for the Angular UI. It is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/). You can take the Basic Theme as the **base theme** and build your own theme or styling on top of it. See the *Customization* section. - -> If you are looking for a professional, enterprise ready theme, you can check the [Lepton Theme](https://commercial.abp.io/themes), which is a part of the [ABP Commercial](https://commercial.abp.io/). - -> See the [Theming document](Theming.md) to learn about themes. - -## Installation - -If you need to manually this theme, follow the steps below: - -* Install the [@abp/ng.theme.basic](https://www.npmjs.com/package/@abp/ng.theme.basic) NPM package to your Angular project. -* Open the `src/app/app.module.ts` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule.forRoot()` to the `imports` array. -* Open the `src/app/shared/shared.module` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` and `exports` array. - -The `ThemeBasicModule` is registered own layouts (`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`) to a service which is exposed by `@abp/ng.core` package on application initialization. - -## Application Layout - -![basic-theme-application-layout](../../images/basic-theme-application-layout.png) - -Application Layout implements the following parts, in addition to the common parts mentioned above; - -* Logo area -* Routes area -* Language selection & user menu -* [Page Alerts](Page-Alerts.md) - -See Application Layout components: - -![application layout components](./images/layout-components.png) - -### How to Use a Layout - -Routes should be added to the menu by calling `add` method `RoutesService`. A layout can be set in the object of your route. See the [modifying the menu](Modifying-the-Menu#how-to-add-a-navigation-element) for more information. - -## Customization - -You have two options two customize this theme: - -### Overriding Styles / Components - -In this approach, you continue to use the theme as an NPM package and customize the parts you need to. There are several ways to customize it; - -#### Override the Styles - -You can simply override the styles in the global styles (`src/styles.scss`) file of your application. - -#### Override the Components - -See the [Component Replacement](Component-Replacement.md) to learn how you can replace components, customize and extend the user interface. - -### Copy & Customize - -You can run the following [ABP CLI](../../CLI.md) command in **Angular** project directory to copy the source code to your solution: - -`abp add-package @abp/ng.theme.basic --with-source-code` - ----- - -Or, you can download the [source code](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-basic) of the Basic Theme, manually copy the project content into your project (`projects/theme-basic` folder), open `angular.json` file and add configuration below to the `projects` object: - -```json -{ - "projects": { - ... - "theme-basic": { - "projectType": "library", - "root": "projects/theme-basic", - "sourceRoot": "projects/theme-basic/src", - "prefix": "abp", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:ng-packagr", - "options": { - "tsConfig": "projects/theme-basic/tsconfig.lib.json", - "project": "projects/theme-basic/ng-package.json" - }, - "configurations": { - "production": { - "tsConfig": "projects/theme-basic/tsconfig.lib.prod.json" - } - } - } - } - } - } -} -``` - -Then, open the `tsconfig.json` file and add new paths as follows: - -```json -"paths": { - ... - "@abp/ng.theme.basic": ["projects/theme-basic/src/public-api.ts"], - "@abp/ng.theme.basic/testing": ["projects/theme-basic/testing/src/public-api.ts"] -} -``` - - -You can now freely customize the theme based on your application requirements. - -## See Also - -* [Theming](Theming.md) diff --git a/docs/en/UI/Angular/Breadcrumb.md b/docs/en/UI/Angular/Breadcrumb.md deleted file mode 100644 index 58ae826278..0000000000 --- a/docs/en/UI/Angular/Breadcrumb.md +++ /dev/null @@ -1,50 +0,0 @@ -## Breadcrumb Component - -ABP provides a component that listens to the angular router's `NavigationEnd` -event and creates inputs for `BreadcrumbItemsComponent`. This component is used in -ABP components with [`PageComponent`](./Page-Component.md). - -## Breadcrumb Items Component - -`BreadcrumbItemsComponent` is used to display breadcrumb items. It can be useful -when you want to display breadcrumb items in a different way than the default. - -### Usage - -Example of overriding the default template of `PageComponent`: - -```html - - - - - -``` - -```js -import { Component } from "@angular/core"; -import { ABP } from "@abp/ng.core"; - -@Component({ - /* component metadata */ -}) -export class YourComponent { - breadCrumbItems: ABP.Route[] = [ - { - name: "Item 1", - }, - { - name: "Item 2", - path: "/path", - }, - ]; -} -``` - -### Inputs - -- items: Partial[] : Array of ABP.Route objects. The source code of ABP.Route can be found in [github](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L69). - -## See Also - -- [Page Component](./Page-Component.md) diff --git a/docs/en/UI/Angular/Caps-Lock-Directive.md b/docs/en/UI/Angular/Caps-Lock-Directive.md deleted file mode 100644 index 64a3fc42ca..0000000000 --- a/docs/en/UI/Angular/Caps-Lock-Directive.md +++ /dev/null @@ -1,80 +0,0 @@ -# Caps Lock Directive - -In password inputs, You may want to show if Caps Lock is on. To make this even easier, you can use the `TrackCapsLockDirective` which has been exposed by the `@abp/ng.core` package. - - -## Getting Started - -`TrackCapsLockDirective` is standalone. In order to use the `TrackCapsLockDirective` in an HTML template, import it to related module or your standalone component: - -**Importing to NgModule** -```ts -import { TrackCapsLockDirective } from '@abp/ng.core'; - -@NgModule({ - //... - declarations: [ - ..., - TestComponent - ], - imports: [ - ..., - TrackCapsLockDirective - ], -}) -export class MyFeatureModule {} -``` - -## Usage - -The `TrackCapsLockDirective` is very easy to use. The directive's selector is **`abpCapsLock`**. By adding the `abpCapsLock` event to an element, you can track the status of Caps Lock. You can use this to warn user. - -See an example usage: - -**NgModule Component usage** -```ts -@Component({ - selector: 'test-component', - template: ` -
    - - - icon -
    - ` -}) -export class TestComponent{ - capsLock = false; -} -``` - -**Standalone Component usage** -```ts -import { TrackCapsLockDirective } from '@abp/ng.core' - -@Component({ - selector: 'standalone-component', - standalone: true, - template: ` -
    - - - icon -
    - `, - imports: [TrackCapsLockDirective] -}) -export class StandaloneComponent{ - capsLock = false; -} -``` - -The `abpCapsLock` event has been added to the `` element. Press Caps Lock to activate the `TrackCapsLockDirective`. - -See the result: - -![Show Password directive](./images/CapsLockDirective1.png) - -To see Caps Lock icon press Caps Lock. - -![Show Password directive](./images/CapsLockDirective2.png) diff --git a/docs/en/UI/Angular/Card-Component.md b/docs/en/UI/Angular/Card-Component.md deleted file mode 100644 index ca056853ff..0000000000 --- a/docs/en/UI/Angular/Card-Component.md +++ /dev/null @@ -1,206 +0,0 @@ -# Card Component - -The ABP Card Component is a wrapper component for the Bootstrap card class. -It supports all the features that Bootstrap card component provides. - -ABP Card Component has three main components, `CardHeader`, `CardBody` and `CardFooter`. These components have their own class and style inputs - -|Component |Selector |Input Properties | -|--------- |-----------------|------------------------------------| -|CardHeader|`abp-card-header`| `cardHeaderClass`,`cardHeaderStyle`| -|CardBody |`abp-card-body` | `cardBodyClass`,`cardBodyStyle` | -|CardFooter|`abp-card-footer`| `cardFooterClass`,`cardFooterStyle`| - -In addition to these components, the Card component provides directives like `CardHeader`,`CardTitle`,`CardSubtitle`,`CardImgTop`. - -|Directive |Selector | -|-------------|-------------------------------------------------------------| -|CardHeader |`abp-card-header`,`[abp-card-header]`,`[abpCardHeader]` | -|CardTitle |`abp-card-title`,`[abp-card-title]`,`[abpCardTitle]` | -|CardSubtitle |`abp-card-subtitle`,`[abp-card-subtitle]`,`[abpCardSubtitle]`| -|CardImgTop |`abp-card-img-top`,`[abp-card-img-top]`,`[abpCardImgTop]` | - - -# Usage - -ABP Card Component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, you don't need to import it again. If not, first import it as shown below: - -```ts -// my-feature.module.ts - -import { ThemeSharedModule } from '@abp/ng.theme.shared'; -import { CardDemoComponent } from './card-demo.component'; - -@NgModule({ - imports: [ - ThemeSharedModule , - // ... - ], - declarations: [CardDemoComponent], - // ... -}) -export class MyFeatureModule {} - -``` - -Then, the `abp-card` component can be used. See the examples below: - -## CardBody - -```ts -// card-demo.component.ts - -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-card-demo', - template: ` - - This is some text within a card body - - `, -}) -export class CardDemoComponent { } -``` -See the card body result below: - -![abp-card-body](./images/abp-card-body.png) - -## Titles, Text and Links - -```ts - -//card-demo.component.ts -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-card-demo', - template: ` - - -
    Card Title
    -
    Card subtitle
    -

    Some quick example text to build on the card title and make up the bulk of the card's content.

    - Card link - Another link -
    -
    - `, -}) -export class CardDemoComponent { } -``` -See the card title, text and link result below: - -![abp-card-title-text-link](./images/abp-card-title-text-link.png) - -## Images - -```ts - -//card-demo.component.ts -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-card-demo', - template: ` - - ... - -

    Some quick example text to build on the card title and make up the bulk of the card's content.

    -
    -
    - `, -}) -export class CardDemoComponent { } -``` -See the card image result below: - -![abp-card-image-top](./images/abp-card-image.png) - -## List Groups - -```ts - -//card-demo.component.ts -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-card-demo', - template: ` - -
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    -
    - `, -}) -export class CardDemoComponent { } -``` -See the group list result below: - -![abp-card-list-group](./images/abp-card-list-group.png) - -## Kitchen Sink - -```ts - -//card-demo.component.ts -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-card-demo', - template: ` - - ... - -
    Card title
    -

    Some quick example text to build on the card title and make up the bulk of the card's content.

    -
    -
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    - - Card link - Another link - -
    - `, -}) -export class CardDemoComponent { } -``` -See kitchen sink result below: - -![abp-card-kitchen-sink](./images/abp-card-kitchen-sink.png) - -## Header and Footer - -```ts - -//card-demo.component.ts -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-card-demo', - template: ` - - Featured - -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - - 2 days ago - -
    - `, -}) -export class CardDemoComponent { } -``` -See the header and footer result below: - -![abp-card-header-footer](./images/abp-card-header-footer.png) diff --git a/docs/en/UI/Angular/Chart-Component.md b/docs/en/UI/Angular/Chart-Component.md deleted file mode 100644 index e2bf2c2e1e..0000000000 --- a/docs/en/UI/Angular/Chart-Component.md +++ /dev/null @@ -1,258 +0,0 @@ -# Chart Component - -ABP Chart component exposed by `@abp/ng.components/chart.js` is based on [`charts.js`](https://www.chartjs.org/) v3+. You don't need to install the `chart.js` package. Since the `@abp/ng.components` is dependent on the `chart.js`, the package is already installed in your project. - -> Chart component loads `chart.js` script lazy. So it does not increase the bundle size. - -## How to Use - -First of all, need to import the `ChartModule` to your feature module as follows: - -```ts -// your-feature.module.ts - -import { ChartModule } from "@abp/ng.components/chart.js"; -import { ChartDemoComponent } from "./chart-demo.component"; - -@NgModule({ - imports: [ - ChartModule, - // ... - ], - declarations: [ChartDemoComponent], - // ... -}) -export class YourFeatureModule {} -``` - -Then, `abp-chart` component can be used. See an example: - -```ts -// chart-demo.component.ts - -import { Component } from "@angular/core"; - -@Component({ - selector: "app-chart-demo", - template: ` `, -}) -export class ChartDemoComponent { - data = { - labels: ["Data 1", "Data 2", "Data 3"], - datasets: [ - { - label: "Dataset 1", - data: [40, 15, 45], - backgroundColor: ["#ff7675", "#fdcb6e", "#0984e3"], - }, - ], - }; -} -``` - -> **Important Note**: Changing the chart data without creating a new data instance does not trigger change detection. In order to chart to redraw itself, a new data object needs to be created. - -See the result: - -![pie-chart](./images/pie-chart.png) - -## Examples - -### Doughnut - -```ts -import { Component } from "@angular/core"; - -@Component({ - selector: "app-chart-demo", - template: ` - - `, -}) -export class ChartDemoComponent { - data = { - labels: ["Data 1", "Data 2", "Data 3"], - datasets: [ - { - label: "Dataset 1", - data: [40, 15, 45], - backgroundColor: ["#a0e6c3", "#f0ea4c", "#5b9dc3"], - }, - ], - }; - - options = { - plugins: { - title: { - display: true, - text: "Doughnut Chart", - fontSize: 16, - }, - legend: { - position: "bottom", - }, - }, - }; - - myPlugin = [ - { - afterRender: (chart, args, options) => { - console.log("chart has been rendered"); - }, - }, - ]; -} -``` - -Result: - -![Doughnut Chart](./images/doughnut-chart.png) - -### Bar - -```ts -import { Component } from "@angular/core"; - -@Component({ - selector: "app-chart-demo", - template: ` - - `, -}) -export class ChartDemoComponent { - data = { - labels: ["January", "February", "March", "April", "May", "June", "July"], - datasets: [ - { - label: "First dataset", - backgroundColor: "#42A5F5", - data: [65, 59, 80, 81, 56, 55, 40], - }, - { - label: "Second dataset", - backgroundColor: "#FFA726", - data: [28, 48, 40, 19, 86, 27, 90], - }, - ], - }; -} -``` - -Result: - -![Bar Chart](./images/bar-chart.png) - -### Radar - -```ts -import { Component } from "@angular/core"; - -@Component({ - selector: "app-chart-demo", - template: ` - - - - `, -}) -export class ChartDemoComponent { - data = { - labels: [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - datasets: [ - { - label: "Dataset 1", - backgroundColor: "rgba(179,181,198,0.2)", - borderColor: "rgba(179,181,198,1)", - data: [65, 59, 90, 81, 56, 55, 40, 35, 82, 51, 62, 95], - }, - { - label: "Dataset 2", - backgroundColor: "rgba(255,99,132,0.2)", - borderColor: "rgba(255,99,132,1)", - data: [28, 48, 40, 58, 96, 27, 100, 44, 85, 77, 71, 39], - }, - ], - }; - - addDataset() { - this.data = { - ...this.data, - datasets: [ - ...this.data.datasets, - { - label: "Dataset 3", - backgroundColor: "rgba(54,162,235,0.2)", - borderColor: "rgba(54, 162, 235, 1)", - data: [90, 95, 98, 91, 99, 96, 89, 95, 98, 93, 92, 90], - }, - ], - }; - } -} -``` - -Result: - -![Bar Chart](./images/radar-chart.gif) - -See the [`chart.js` samples](https://www.chartjs.org/docs/latest/samples) for more examples. - -## API - -### `abp-chart` - -#### Properties - -| Name | Description | Type | Default | -| --------------- | ---------------------------------------------------------------- | ----------------------- | ------- | -| `[type]` | Type of the chart. | `string` | null | -| `[data]` | Chart data to display | `any` | null | -| `[options]` | Chart options to customize | `any` | null | -| `[plugins]` | Chart plugins to customize behavior | `any` | null | -| `[width]` | Width of the chart | `string` | null | -| `[height]` | Height of the chart | `string` | null | -| `[responsive]` | Whether the chart is responsive | `boolean` | true | -| `(dataSelect)` | A callback that executes when an element on the chart is clicked | `EventEmitter` | - | -| `(initialized)` | A callback that executes when the chart is initialized | `EventEmitter` | - | - -#### Methods - -| Name | Description | Parameters | -| ---------------- | ------------------------------------------------------------------- | ---------- | -| `refresh` | Redraws the chart | - | -| `reinit` | Destroys the chart then creates it again | - | -| `getBase64Image` | Returns a base 64 encoded string of the chart in it's current state | - | -| `generateLegend` | Returns an HTML string of a legend for the chart | - | -| `getCanvas` | Returns the canvas HTML element | - | diff --git a/docs/en/UI/Angular/Checkbox-Component.md b/docs/en/UI/Angular/Checkbox-Component.md deleted file mode 100644 index 898f86732c..0000000000 --- a/docs/en/UI/Angular/Checkbox-Component.md +++ /dev/null @@ -1,51 +0,0 @@ -# Checkbox Component - -The ABP Checkbox Component is a reusable form input component for the checkbox type. - -# Inputs - -- `label` -- `labelClass (default form-check-label)` -- `checkboxId` -- `checkboxReadonly` -- `checkboxReadonly (default form-check-input)` -- `checkboxStyle` - -# Outputs - -- `checkboxBlur` -- `checkboxFocus` - -# Usage - -The ABP Checkbox component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below: - -```ts -// my-feature.module.ts - -import { ThemeSharedModule } from "@abp/ng.theme.shared"; -import { CheckboxDemoComponent } from "./CheckboxDemoComponent.component"; - -@NgModule({ - imports: [ - ThemeSharedModule, - // ... - ], - declarations: [CheckboxDemoComponent], - // ... -}) -export class MyFeatureModule {} -``` - -Then, the `abp-checkbox` component can be used. See the example below: - -```html -
    - - -
    -``` - -See the checkbox input result below: - -![abp-checkbox](./images/form-checkbox.png) diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md deleted file mode 100644 index d21489d232..0000000000 --- a/docs/en/UI/Angular/Component-Replacement.md +++ /dev/null @@ -1,669 +0,0 @@ -# Component Replacement - -You can replace some ABP components with your custom components. - -The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_. - -## How to Replace a Component - -Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`. - -Then, open the `app.component.ts` and execute the `add` method of `ReplaceableComponentsService` to replace your component with an ABP component as shown below: - -```js -import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService -import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum -//... - -@Component(/* component metadata */) -export class AppComponent { - constructor( - private replaceableComponents: ReplaceableComponentsService, // injected the service - ) { - this.replaceableComponents.add({ - component: YourNewRoleComponent, - key: eIdentityComponents.Roles, - }); - } -} -``` - -![Example Usage](./images/component-replacement.gif) - - -## How to Replace a Layout - -Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way. - -> A layout component template should contain `` element. - -The example below describes how to replace the `ApplicationLayoutComponent`: - -Run the following command to generate a layout in `angular` folder: - -```bash -yarn ng generate component my-application-layout -``` - -Add the following code in your layout template (`my-application-layout.component.html`) where you want the page to be loaded. - -```html - -``` - -Open `app.component.ts` in `src/app` folder and modify it as shown below: - -```js -import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService -import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys -import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent - -@Component(/* component metadata */) -export class AppComponent { - constructor( - private replaceableComponents: ReplaceableComponentsService, // injected the service - ) { - this.replaceableComponents.add({ - component: MyApplicationLayoutComponent, - key: eThemeBasicComponents.ApplicationLayout, - }); - } -} -``` - -> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the `add` method of `ReplaceableComponentsService` as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of the `add` method is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated. - -### Layout Components - -![Layout Components](./images/layout-components.png) - -### How to Add a New Layout Component - -To add a new layout component, you need to follow these steps: - -#### Step 1: Create a New Angular Component - -This component should have a 'router-outlet' for dynamic content loading. You can create a new component using the Angular CLI. Run the following command in your terminal: - -```bash -ng generate component new-layout -``` -This command will create a new component named `new-layout`. Now, open the new-layout.component.html file and add a `router-outlet` to it: - -```html - -``` -This 'router-outlet' will act as a placeholder that Angular dynamically fills based on the current router state. - -note: (don't forget: you should add the app in the app.module.ts file) - -#### Step 2: Define a Variable for the Layout Component - -Although this step is optional, it can be useful if you're going to use the layout component's value multiple times. You can define a variable for the layout component like this: - -```javascript -export const eCustomLayout = { - key: 'CustomLayout', - component: 'CustomLayoutComponent', -}; -``` -In this variable, `key` is a unique identifier for the layout component, and `component` is the name of the layout component. -You can use this variable when you need to refer to the layout component. - -#### Step 3: Add the Layout Component to the ABP Replaceable-System - -Next, you need to add the new layout component to the `ReplaceableComponentsService`. This service allows you to replace a component with another one dynamically. - -You can do this by defining a provider for `APP_INITIALIZER` that uses a factory function. In this function, you inject the `ReplaceableComponentsService` and use its `add` method to add the new layout component. - -Here's how you can do it: - -```javascript -export const CUSTOM_LAYOUT_PROVIDERS = [ - { provide: APP_INITIALIZER, useFactory: configureLayoutFn, deps: [ReplaceableComponentsService], multi: true }, - -]; -function configureLayoutFn() { - const service= inject( ReplaceableComponentsService) - return () =>{ - service.add({ - key: eCustomLayout.component, - component: CustomLayoutComponent, - }) - } -} -``` -In this code, `configureLayoutFn` is a factory function that adds the new layout component to the `ReplaceableComponentsService`. The `APP_INITIALIZER` provider runs this function when the application starts. - -note: (don't forget: you should add the CUSTOM_LAYOUT_PROVIDERS in the app.module.ts file) - -#### Step 4: Define the Application's Dynamic Layouts - -Finally, you need to define the application's dynamic layouts. This is a map where the keys are the layout keys and the values are the layout components. - -You can add the new layout to the existing layouts like this: - -```javascript -export const myDynamicLayouts = new Map([...DEFAULT_DYNAMIC_LAYOUTS, [eCustomLayout.key, eCustomLayout.component]]); -``` - -#### Step 5: Pass the Dynamic Layouts to the CoreModule - -The final step is to pass the dynamic layouts to the `CoreModule` using the `forRoot` method. This method allows you to configure the module with a static method. - -Here's how you can do it: - -```javascript -@NgModule({ - declarations: [AppComponent], - imports: [ - // other imports... - CoreModule.forRoot({ - dynamicLayouts: myDynamicLayouts, - environment, - registerLocaleFn: registerLocale(), - }), - // other imports... - NewLayoutComponent - ], - providers: [APP_ROUTE_PROVIDER, CUSTOM_LAYOUT_PROVIDERS], - bootstrap: [AppComponent], -}) -export class AppModule {} -``` -In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `CoreModule` using the `forRoot` method. - - -Now that you have defined the new layout, you can use it in the router definition. You do this by adding a new route that uses the new layout. - -Here's how you can do it: - -```javascript -// route.provider.ts -import { eCustomLayout } from './custom-layout/custom-layout.provider'; -import { RoutesService, eLayoutType } from '@abp/ng.core'; -import { APP_INITIALIZER } from '@angular/core'; - -export const APP_ROUTE_PROVIDER = [ - { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, -]; - -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - { - path: '/', - name: '::Menu:Home', - iconClass: 'fas fa-home', - order: 1, - layout: eLayoutType.application, - }, - { - path: '/dashboard', - name: '::Menu:Dashboard', - iconClass: 'fas fa-chart-line', - order: 2, - layout: eCustomLayout.key as eLayoutType, - requiredPolicy: 'MyProjectName.Dashboard.Host || MyProjectName.Dashboard.Tenant', - }, - ]); - }; -} -``` - -#### How to Replace LogoComponent - -![LogoComponent](./images/logo-component.png) - -Run the following command in `angular` folder to create a new component called `LogoComponent`. - -```bash -yarn ng generate component logo --inlineTemplate --inlineStyle -``` - - -Open the generated `logo.component.ts` in `src/app/logo` folder and replace its content with the following: - -```js -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-logo', - template: ` - - - logo - - `, -}) -export class LogoComponent {} -``` - -Open `app.component.ts` in `src/app` folder and modify it as shown below: - -```js -import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService -import { LogoComponent } from './logo/logo.component'; // imported LogoComponent -import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents -//... - -@Component(/* component metadata */) -export class AppComponent implements OnInit { - constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService - - ngOnInit() { - //... - - this.replaceableComponents.add({ - component: LogoComponent, - key: eThemeBasicComponents.Logo, - }); - } -} -``` - -The final UI looks like below: - -![New logo](./images/replaced-logo-component.png) - -#### How to Replace RoutesComponent - -![RoutesComponent](./images/routes-component.png) - -Run the following command in `angular` folder to create a new component called `RoutesComponent`. - -```bash -yarn ng generate component routes -``` - -Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following: - -```js -import { Component, HostBinding } from '@angular/core'; - -@Component({ - selector: 'app-routes', - templateUrl: 'routes.component.html', -}) -export class RoutesComponent { - @HostBinding('class.mx-auto') - marginAuto = true; - - get smallScreen() { - return window.innerWidth < 992; - } -} -``` - -Import the `SharedModule` to the `imports` array of `AppModule`: - -```js -// app.module.ts - -import { SharedModule } from './shared/shared.module'; - -@NgModule({ - imports: [ - //... - SharedModule - ] -)} -``` - -Open the generated `routes.component.html` in `src/app/routes` folder and replace its content with the following: - -```html - -``` - -Open `app.component.ts` in `src/app` folder and modify it as shown below: - -```js -import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService -import { RoutesComponent } from './routes/routes.component'; // imported RoutesComponent -import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents -//... - -@Component(/* component metadata */) -export class AppComponent implements OnInit { - constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService - - ngOnInit() { - //... - - this.replaceableComponents.add({ - component: RoutesComponent, - key: eThemeBasicComponents.Routes, - }); - } -} -``` - -The final UI looks like below: - -![New routes](./images/replaced-routes-component.png) - -#### How to Replace NavItemsComponent - -![NavItemsComponent](./images/nav-items-component.png) - -Run the following command in `angular` folder to create a new component called `NavItemsComponent`. - -```bash -yarn ng generate component nav-items -``` - -Open the generated `nav-items.component.ts` in `src/app/nav-items` folder and replace the content with the following: - -```js -import { - AuthService, - ConfigStateService, - CurrentUserDto, - LanguageInfo, - NAVIGATE_TO_MANAGE_PROFILE, - SessionStateService, -} from '@abp/ng.core'; -import { Component, Inject } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import snq from 'snq'; - -@Component({ - selector: 'app-nav-items', - templateUrl: 'nav-items.component.html', -}) -export class NavItemsComponent { - currentUser$: Observable = this.configState.getOne$('currentUser'); - selectedTenant$ = this.sessionState.getTenant$(); - - languages$: Observable = this.configState.getDeep$('localization.languages'); - - get smallScreen(): boolean { - return window.innerWidth < 992; - } - - get defaultLanguage$(): Observable { - return this.languages$.pipe( - map( - languages => - snq( - () => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName - ), - '' - ) - ); - } - - get dropdownLanguages$(): Observable { - return this.languages$.pipe( - map( - languages => - snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)), - [] - ) - ); - } - - get selectedLangCulture(): string { - return this.sessionState.getLanguage(); - } - - constructor( - @Inject(NAVIGATE_TO_MANAGE_PROFILE) public navigateToManageProfile, - private configState: ConfigStateService, - private authService: AuthService, - private sessionState: SessionStateService - ) {} - - onChangeLang(cultureName: string) { - this.sessionState.setLanguage(cultureName); - } - - navigateToLogin() { - this.authService.navigateToLogin(); - } - - logout() { - this.authService.logout().subscribe(); - } -} -``` - -Import the `SharedModule` to the `imports` array of `AppModule`: - -```js -// app.module.ts - -import { SharedModule } from './shared/shared.module'; - -@NgModule({ - imports: [ - //... - SharedModule - ] -)} -``` - -Open the generated `nav-items.component.html` in `src/app/nav-items` folder and replace the content with the following: - -```html - -``` - -Open `app.component.ts` in `src/app` folder and modify it as shown below: - -```js -import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService -import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent -import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents -//... - -@Component(/* component metadata */) -export class AppComponent implements OnInit { - constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService - - ngOnInit() { - //... - - this.replaceableComponents.add({ - component: NavItemsComponent, - key: eThemeBasicComponents.NavItems, - }); - } -} -``` - -The final UI looks like below: - -![New nav-items](./images/replaced-nav-items-component.png) - -## See Also - -- [How Replaceable Components Work with Extensions](./How-Replaceable-Components-Work-with-Extensions.md) -- [How to Replace PermissionManagementComponent](./Permission-Management-Component-Replacement.md) diff --git a/docs/en/UI/Angular/Config-State-Service.md b/docs/en/UI/Angular/Config-State-Service.md deleted file mode 100644 index 668b6f8568..0000000000 --- a/docs/en/UI/Angular/Config-State-Service.md +++ /dev/null @@ -1,135 +0,0 @@ -# Config State Service - -`ConfigStateService` is a singleton service, i.e. provided in root level of your application, and keeps the application configuration response in the internal store. - -## Before Use - -In order to use the `ConfigStateService` you must inject it in your class as a dependency. - -```js -import { ConfigStateService } from '@abp/ng.core'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent { - constructor(private config: ConfigStateService) {} -} -``` - -You do not have to provide the `ConfigStateService` at module or component/directive level, because it is already **provided in root**. - -## Get Methods - -`ConfigStateService` has numerous get methods which allow you to get a specific configuration or all configurations. - -Get methods with "$" at the end of the method name (e.g. `getAll$`) return an RxJs stream. The streams are triggered when set or patched the state. - -### How to Get All Configurations - -You can use the `getAll` or `getAll$` method of `ConfigStateService` to get all of the applcation configuration response object. It is used as follows: - -```js -// this.config is instance of ConfigStateService - -const config = this.config.getAll(); - -// or -this.config.getAll$().subscribe(config => { - // use config here -}) -``` - -### How to Get a Specific Configuration - -You can use the `getOne` or `getOne$` method of `ConfigStateService` to get a specific configuration property. For that, the property name should be passed to the method as parameter. - -```js -// this.config is instance of ConfigStateService - -const currentUser = this.config.getOne("currentUser"); - -// or -this.config.getOne$("currentUser").subscribe(currentUser => { - // use currentUser here -}) -``` - -On occasion, you will probably want to be more specific than getting just the current user. For example, here is how you can get the `tenantId`: - -```js -const tenantId = this.config.getDeep("currentUser.tenantId"); - -// or -this.config.getDeep$("currentUser.tenantId").subscribe(tenantId => { - // use tenantId here -}) -``` - -or by giving an array of keys as parameter: - -```js -const tenantId = this.config.getDeep(["currentUser", "tenantId"]); -``` - -FYI, `getDeep` is able to do everything `getOne` does. Just keep in mind that `getOne` is slightly faster. - -### How to Get a Feature - -You can use the `getFeature` or `getFeature$` method of `ConfigStateService` to get a feature value. For that, the feature name should be passed to the method as parameter. - -```js -// this.config is instance of ConfigStateService - -const enableLdapLogin = this.config.getFeature("Account.EnableLdapLogin"); - -// or -this.config.getFeature$("Account.EnableLdapLogin").subscribe(enableLdapLogin => { - // use enableLdapLogin here -}) -``` - -> For more information, see the [features document](./Features). - -### How to Get a Setting - -You can use the `getSetting` or `getSetting$` method of `ConfigStateService` to get a setting. For that, the setting name should be passed to the method as parameter. - -```js -// this.config is instance of ConfigStateService - -const twoFactorBehaviour = this.config.getSetting("Abp.Identity.TwoFactor.Behaviour"); - -// or -this.config.getSetting$("Abp.Identity.TwoFactor.Behaviour").subscribe(twoFactorBehaviour => { - // use twoFactorBehaviour here -}) -``` - -> For more information, see the [settings document](./Settings). - -#### State Properties - -Please refer to `ApplicationConfigurationDto` type for all the properties you can get with `getOne` and `getDeep`. It can be found in the [models.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/models.ts#L11). - - -## Set State - -`ConfigStateService` has a method named `setState` which allow you to set the state value. - -You can get the application configuration response and set the `ConfigStateService` state value as shown below: - -```js -import {AbpApplicationConfigurationService, ConfigStateService} from '@abp/ng.core'; - -constructor(private abpApplicationConfigurationService: AbpApplicationConfigurationService, private config: ConfigStateService) { - this.abpApplicationConfigurationService.get({ includeLocalizationResources: false }).subscribe(config => { - this.config.setState(config); - }) -} -``` - -## See Also - -- [Settings](./Settings.md) -- [Features](./Features.md) diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md deleted file mode 100644 index d6774bb0cb..0000000000 --- a/docs/en/UI/Angular/Config-State.md +++ /dev/null @@ -1 +0,0 @@ -**ConfigState has been deprecated.** Use the [ConfigStateService](./Config-State-Service) instead. \ No newline at end of file diff --git a/docs/en/UI/Angular/Confirmation-Service.md b/docs/en/UI/Angular/Confirmation-Service.md deleted file mode 100644 index 53b5dae397..0000000000 --- a/docs/en/UI/Angular/Confirmation-Service.md +++ /dev/null @@ -1,201 +0,0 @@ -# Confirmation Popup - -You can use the `ConfirmationService` in @abp/ng.theme.shared package to display a confirmation popup by placing at the root level in your project. - -## Getting Started - -You do not have to provide the `ConfirmationService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. - -```js -import { ConfirmationService } from '@abp/ng.theme.shared'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent { - constructor(private confirmation: ConfirmationService) {} -} -``` - -## Usage - -You can use the `success`, `warn`, `error`, and `info` methods of `ConfirmationService` to display a confirmation popup. - -### How to Display a Confirmation Popup - -```js -const confirmationStatus$ = this.confirmation.success("Message", "Title"); -``` - -- The `ConfirmationService` methods accept three parameters that are `message`, `title`, and `options`. -- `success`, `warn`, `error`, and `info` methods return an [RxJS Subject](https://rxjs-dev.firebaseapp.com/guide/subject) to listen to confirmation popup closing event. The type of event value is [`Confirmation.Status`](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts#L24) that is an enum. - -### How to Listen Closing Event - -You can subscribe to the confirmation closing event like below: - -```js -import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared'; - -constructor(private confirmation: ConfirmationService) {} - -this.confirmation - .warn('::WillBeDeleted', { key: '::AreYouSure', defaultValue: 'Are you sure?' }) - .subscribe((status: Confirmation.Status) => { - // your code here - }); -``` - -- The `message` and `title` parameters accept a string, localization key or localization object. See the [localization document](./Localization.md) -- `Confirmation.Status` is an enum and has three properties; - - `Confirmation.Status.confirm` is a closing event value that will be emitted when the popup is closed by the confirm button. - - `Confirmation.Status.reject` is a closing event value that will be emitted when the popup is closed by the cancel button. - - `Confirmation.Status.dismiss` is a closing event value that will be emitted when the popup is closed by pressing the escape or clicking the backdrop. - -If you are not interested in the confirmation status, you do not have to subscribe to the returned observable: - -```js -this.confirmation.error("You are not authorized.", "Error"); -``` - -### How to Display a Confirmation Popup With Given Options - -Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods: - -```js -const options: Partial = { - hideCancelBtn: false, - hideYesBtn: false, - dismissible: false, - cancelText: "Close", - yesText: "Confirm", - messageLocalizationParams: ["Demo"], - titleLocalizationParams: [], - // You can customize icon - // icon: 'fa fa-exclamation-triangle', // or - // iconTemplate : '' -} - -this.confirmation.warn( - "AbpIdentity::RoleDeletionConfirmationMessage", - "Are you sure?", - options -); -``` - -- `hideCancelBtn` option hides the cancellation button when `true`. Default value is `false`. -- `hideYesBtn` option hides the confirmation button when `true`. Default value is `false`. -- `dismissible` option allows dismissing the confirmation popup by pressing escape or clicking the backdrop. Default value is `true`. -- `cancelText` is the text of the cancellation button. A localization key or localization object can be passed. Default value is `AbpUi::Cancel`. -- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`. -- `messageLocalizationParams` is the interpolation parameters for the localization of the message. -- `titleLocalizationParams` is the interpolation parameters for the localization of the title. -- `icon` is the custom class of the icon. Default value is `undefined`. -- `iconTemplate` is the template for icon. Default value is `undefined`. - -With the options above, the confirmation popup looks like this: - -![confirmation](./images/confirmation.png) - -You are able to pass in an HTML string as title, message, or button texts. Here is an example: - -```js -const options: Partial = { - yesText: 'Yes, delete it', -}; - -this.confirmation.warn( - ` - Role Demo will be deleted -
    - Do you confirm that? - `, - 'Are you sure?', - options -); -``` - -Since the values are HTML now, localization should be handled manually. Check out the [LocalizationService](./Localization#using-the-localization-service) to see how you can accomplish that. - -> Please note that all strings will be sanitized by Angular and not every HTML string will work. Only values that are considered as "safe" by Angular will be displayed. - -### How to Remove a Confirmation Popup - -The open confirmation popup can be removed manually via the `clear` method: - -```js -this.confirmation.clear(); -``` - -### How to Change Icons of The Confirmation Popup - -You can change icons with the token of "confirmationIcons" in ThemeSharedModule in the app.module.ts. The changes will affect all confirmation popup in the project. - -```js -... -ThemeSharedModule.forRoot({ - confirmationIcons: { - info: 'fa fa-info-circle', - success: 'fa fa-check-circle', - warning: 'fa fa-exclamation-triangle', - error: 'fa fa-times-circle', - default: 'fa fa-question-circle', - }, -}), -... -``` - - -## API - -### success - -```js -success( - message: Config.LocalizationParam, - title: Config.LocalizationParam, - options?: Partial, -): Observable -``` - -> See the [`LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/localization.ts#L6) and [`Confirmation` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts) - -### warn - -```js -warn( - message: Config.LocalizationParam, - title: Config.LocalizationParam, - options?: Partial, -): Observable -``` - -### error - -```js -error( - message: Config.LocalizationParam, - title: Config.LocalizationParam, - options?: Partial, -): Observable -``` - -### info - -```js -info( - message: Config.LocalizationParam, - title: Config.LocalizationParam, - options?: Partial, -): Observable -``` - -### clear - -```js -clear( - status: Confirmation.Status = Confirmation.Status.dismiss -): void -``` - -- `status` parameter is the value of the confirmation closing event. diff --git a/docs/en/UI/Angular/Container-Strategy.md b/docs/en/UI/Angular/Container-Strategy.md deleted file mode 100644 index 3610c5ddd8..0000000000 --- a/docs/en/UI/Angular/Container-Strategy.md +++ /dev/null @@ -1,101 +0,0 @@ -# ContainerStrategy - -`ContainerStrategy` is an abstract class exposed by @abp/ng.core package. There are two container strategies extending it: `ClearContainerStrategy` and `InsertIntoContainerStrategy`. Implementing the same methods and properties, both of these strategies help you define how your containers will be prepared and where your content will be projected. - - - -## API - -`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**. - - -### constructor - -```js -constructor( - public containerRef: ViewContainerRef, - private index?: number, // works only in InsertIntoContainerStrategy -) -``` - -- `containerRef` is the `ViewContainerRef` that will be used when projecting the content. - - -### getIndex - -```js -getIndex(): number -``` - -This method return the given index clamped by `0` and `length` of the `containerRef`. For strategies without an index, it returns `0`. - - -### prepare - -```js -prepare(): void -``` - -This method is called before content projection. Based on used container strategy, it either clears the container or does nothing (noop). - - - -## ClearContainerStrategy - -`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**. - - - -## InsertIntoContainerStrategy - -`InsertIntoContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **project your content at a specific node index in the container**. - - - -## Predefined Container Strategies - -Predefined container strategies are accessible via `CONTAINER_STRATEGY` constant. - - -### Clear - -```js -CONTAINER_STRATEGY.Clear(containerRef: ViewContainerRef) -``` - -Clears given container before content projection. - - -### Append - -```js -CONTAINER_STRATEGY.Append(containerRef: ViewContainerRef) -``` - -Projected content will be appended to the container. - - -### Prepend - -```js -CONTAINER_STRATEGY.Prepend(containerRef: ViewContainerRef) -``` - -Projected content will be prepended to the container. - - -### Insert - -```js -CONTAINER_STRATEGY.Insert( - containerRef: ViewContainerRef, - index: number, -) -``` - -Projected content will be inserted into to the container at given index (clamped by `0` and `length` of the `containerRef`). - - -## See Also - -- [ProjectionStrategy](./Projection-Strategy.md) diff --git a/docs/en/UI/Angular/Content-Projection-Service.md b/docs/en/UI/Angular/Content-Projection-Service.md deleted file mode 100644 index ee64f57e20..0000000000 --- a/docs/en/UI/Angular/Content-Projection-Service.md +++ /dev/null @@ -1,73 +0,0 @@ -# Projecting Angular Content - -You can use the `ContentProjectionService` in @abp/ng.core package in order to project content in an easy and explicit way. - -## Getting Started - -You do not have to provide the `ContentProjectionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. - -```js -import { ContentProjectionService } from '@abp/ng.core'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent { - constructor(private contentProjectionService: ContentProjectionService) {} -} -``` - -## Usage - -You can use the `projectContent` method of `ContentProjectionService` to render components and templates dynamically in your project. - -### How to Project Components to Root Level - -If you pass a `RootComponentProjectionStrategy` as the first parameter of `projectContent` method, the `ContentProjectionService` will resolve the projected component and place it at the root level. If provided, it will also pass the component a context. - -```js -const strategy = PROJECTION_STRATEGY.AppendComponentToBody( - SomeOverlayComponent, - { someOverlayProp: "SOME_VALUE" } -); - -const componentRef = this.contentProjectionService.projectContent(strategy); -``` - -In the example above, `SomeOverlayComponent` component will placed at the **end** of `` and a `ComponentRef` will be returned. Additionally, the given context will be applied, so `someOverlayProp` of the component will be set to `SOME_VALUE`. - -> You should keep the returned `ComponentRef` instance, as it is a reference to the projected component and you will need that reference to destroy the projected view and the component instance. - -### How to Project Components and Templates into a Container - -If you pass a `ComponentProjectionStrategy` or `TemplateProjectionStrategy` as the first parameter of `projectContent` method, and a `ViewContainerRef` as the second parameter of that strategy, the `ContentProjectionService` will project the component or template to the given container. If provided, it will also pass the component or the template a context. - -```js -const strategy = PROJECTION_STRATEGY.ProjectComponentToContainer( - SomeComponent, - viewContainerRefOfTarget, - { someProp: "SOME_VALUE" } -); - -const componentRef = this.contentProjectionService.projectContent(strategy); -``` - -In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeComponent` component will be placed inside it. In addition, the given context will be applied and `someProp` of the component will be set to `SOME_VALUE`. - -> You should keep the returned `ComponentRef` or `EmbeddedViewRef`, as they are a reference to the projected content and you will need them to destroy it when necessary. - -Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy. - -## API - -### projectContent - -```js -projectContent | TemplateRef>( - projectionStrategy: ProjectionStrategy, - injector = this.injector, -): ComponentRef | EmbeddedViewRef -``` - -- `projectionStrategy` parameter is the primary focus here and is explained above. -- `injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`. diff --git a/docs/en/UI/Angular/Content-Security-Strategy.md b/docs/en/UI/Angular/Content-Security-Strategy.md deleted file mode 100644 index f32317c9ca..0000000000 --- a/docs/en/UI/Angular/Content-Security-Strategy.md +++ /dev/null @@ -1,74 +0,0 @@ -# ContentSecurityStrategy - -`ContentSecurityStrategy` is an abstract class exposed by @abp/ng.core package. It helps you mark inline scripts or styles as safe in terms of [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). - - - - -## API - - -### constructor - -```js -constructor(public nonce?: string) -``` - -- `nonce` enables whitelisting inline script or styles in order to avoid using `unsafe-inline` in [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) and [style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles) directives. - - -### applyCSP - -```js -applyCSP(element: HTMLScriptElement | HTMLStyleElement): void -``` - -This method maps the aforementioned properties to the given `element`. - - - - -## LooseContentSecurityPolicy - -`LooseContentSecurityPolicy` is a class that extends `ContentSecurityStrategy`. It requires `nonce` and marks given `` element will place at the **end** of `` and `scriptElement` will be an `HTMLScriptElement`. - -Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. - -> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method. - -### How to Insert Styles - -If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `` element will place at the **end** of `` and `styleElement` will be an `HTMLStyleElement`. - -Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. - -> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method. - -### How to Remove Inserted Scripts & Styles - -If you pass the inserted `HTMLScriptElement` or `HTMLStyleElement` element as the first parameter of `removeContent` method, the `DomInsertionService` will remove the given element. - -```js -import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent { - private styleElement: HTMLStyleElement; - - constructor(private domInsertionService: DomInsertionService) {} - - ngOnInit() { - this.styleElement = this.domInsertionService.insertContent( - CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}') - ); - } - - ngOnDestroy() { - this.domInsertionService.removeContent(this.styleElement); - } -} -``` - -In the example above, `` element **will be removed** from `` when the component is destroyed. - -## API - -### insertContent - -```js -insertContent( - contentStrategy: ContentStrategy, -): T -``` - -- `contentStrategy` parameter is the primary focus here and is explained above. -- returns `HTMLScriptElement` or `HTMLStyleElement` based on given strategy. - -### removeContent - -```js -removeContent(element: HTMLScriptElement | HTMLStyleElement): void -``` - -- `element` parameter is the inserted `HTMLScriptElement` or `HTMLStyleElement` element, which was returned by `insertContent` method. - -### has - -```js -has(content: string): boolean -``` - -The `has` method returns a boolean value that indicates the given content has already been added to the DOM or not. - -- `content` parameter is the content of the inserted `HTMLScriptElement` or `HTMLStyleElement` element. diff --git a/docs/en/UI/Angular/Dom-Strategy.md b/docs/en/UI/Angular/Dom-Strategy.md deleted file mode 100644 index e7b6c68b0f..0000000000 --- a/docs/en/UI/Angular/Dom-Strategy.md +++ /dev/null @@ -1,90 +0,0 @@ -# DomStrategy - -`DomStrategy` is a class exposed by @abp/ng.core package. Its instances define how an element will be attached to the DOM and are consumed by other classes such as `LoadingStrategy`. - - -## API - - -### constructor - -```js -constructor( - public target?: HTMLElement, - public position?: InsertPosition -) -``` - -- `target` is an HTMLElement (_default: document.head_). -- `position` defines where the created element will be placed. All possible values of `position` can be found [here](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement) (_default: 'beforeend'_). - - -### insertElement - -```js -insertElement(element: HTMLElement): void -``` - -This method inserts given `element` to `target` based on the `position`. - - - -## Predefined Dom Strategies - -Predefined dom strategies are accessible via `DOM_STRATEGY` constant. - - -### AppendToBody - -```js -DOM_STRATEGY.AppendToBody() -``` - -`insertElement` will place the given `element` at the end of ``. - - -### AppendToHead - -```js -DOM_STRATEGY.AppendToHead() -``` - -`insertElement` will place the given `element` at the end of ``. - - -### PrependToHead - -```js -DOM_STRATEGY.PrependToHead() -``` - -`insertElement` will place the given `element` at the beginning of ``. - - -### AfterElement - -```js -DOM_STRATEGY.AfterElement(target: HTMLElement) -``` - -`insertElement` will place the given `element` after (as a sibling to) the `target`. - - -### BeforeElement - -```js -DOM_STRATEGY.BeforeElement(target: HTMLElement) -``` - -`insertElement` will place the given `element` before (as a sibling to) the `target`. - - - - -## See Also - -- [DomInsertionService](./Dom-Insertion-Service.md) -- [LazyLoadService](./Lazy-Load-Service.md) -- [LoadingStrategy](./Loading-Strategy.md) -- [ContentStrategy](./Content-Strategy.md) -- [ProjectionStrategy](./Projection-Strategy.md) diff --git a/docs/en/UI/Angular/Dynamic-Form-Extensions.md b/docs/en/UI/Angular/Dynamic-Form-Extensions.md deleted file mode 100644 index 2fb73a6fac..0000000000 --- a/docs/en/UI/Angular/Dynamic-Form-Extensions.md +++ /dev/null @@ -1,346 +0,0 @@ -# Dynamic Form (or Form Prop) Extensions for Angular UI - - -## Introduction - -Form prop extension system allows you to add a new field to the create and/or edit forms for a form or change/remove an already existing one. A "Date of Birth" field was added to the user management page below: - -Form Prop Extension Example: 'Date of Birth' Field - -You can validate the field, perform visibility checks, and do more. You will also have access to the current entity when creating a contributor for an edit form. - -## How to Set Up - -In this example, we will add a "Date of Birth" field in the user management page of the [Identity Module](../../Modules/Identity.md) and validate it. - -### Step 1. Create Form Prop Contributors - -The following code prepares two constants named `identityCreateFormPropContributors` and `identityEditFormPropContributors`, ready to be imported and used in your root module: - -```js -// src/app/form-prop-contributors.ts - -import { - eIdentityComponents, - IdentityCreateFormPropContributors, -} from '@abp/ng.identity'; -import { IdentityUserDto } from '@abp/ng.identity/proxy'; -import { ePropType, FormProp, FormPropList } from '@abp/ng.components/extensible'; -import { Validators } from '@angular/forms'; - -const birthdayProp = new FormProp({ - type: ePropType.Date, - name: 'birthday', - displayName: 'AbpIdentity::Birthday', - validators: () => [Validators.required], -}); - -export function birthdayPropContributor(propList: FormPropList) { - propList.addByIndex(birthdayProp, 4); -} - -export const identityCreateFormPropContributors: IdentityCreateFormPropContributors = { - // enum indicates the page to add contributors to - [eIdentityComponents.Users]: [ - birthdayPropContributor, - // You can add more contributors here - ], -}; - -export const identityEditFormPropContributors = identityCreateFormPropContributors; -// you may define different contributors for edit form if you like - -``` - - -The list of props, conveniently named as `propList`, is a **doubly linked list**. That is why we have used the `addByIndex` method, which adds the given value to the specified index of the list. You may find [all available methods here](../Common/Utils/Linked-List.md). - -### Step 2. Import and Use Form Prop Contributors - -Import `identityCreateFormPropContributors` and `identityEditFormPropContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below: - -```js -// src/app/app-routing.module.ts - -// other imports -import { - identityCreateFormPropContributors, - identityEditFormPropContributors, -} from './form-prop-contributors'; - -const routes: Routes = [ - // other routes - - { - path: 'identity', - loadChildren: () => - import('@abp/ng.identity').then(m => - m.IdentityModule.forLazy({ - createFormPropContributors: identityCreateFormPropContributors, - editFormPropContributors: identityEditFormPropContributors, - }) - ), - }, - - // other routes -]; -``` - -That is it, `birthdayProp` form prop will be added, and you will see the datepicker for the "Date of Birth" field right before the "Email address" in the forms of the users page in the `IdentityModule`. - -## Object Extensions - -Extra properties defined on an existing entity will be included in the create and edit forms and validated based on their configuration. The form values will also be mapped to and from `extraProperties` automatically. They are available when defining custom contributors, so you can drop, modify, or reorder them. The `isExtra` identifier will be set to `true` for these properties and will define this automatic behavior. - -## API - -### PropData\ - -`PropData` is the shape of the parameter passed to all callbacks or predicates in a `FormProp`. - -It has the following properties: - -- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `ExtensibleFormPropComponent`, including, but not limited to, its parent components. - - ```js - { - type: ePropType.Enum, - name: 'myField', - options: data => { - const restService = data.getInjected(RestService); - const usersComponent = data.getInjected(UsersComponent); - - // Use restService and usersComponent public props and methods here - } - }, - ``` - -- **record** is the row data, i.e. current value of the selected item to edit. This property is _available only on edit forms_. - - ```js - { - type: ePropType.String, - name: 'myProp', - readonly: data => data.record.someOtherProp, - } - ``` - -### PropCallback\ - -`PropCallback` is the type of the callback function that can be passed to a `FormProp` as `prop` parameter. A prop callback gets a single parameter, the `PropData`. The return type may be anything, including `void`. Here is a simplified representation: - -```js -type PropCallback = (data?: PropData) => R; -``` - -### PropPredicate\ - -`PropPredicate` is the type of the predicate function that can be passed to a `FormProp` as `visible` parameter. A prop predicate gets a single parameter, the `PropData`. The return type must be `boolean`. Here is a simplified representation: - -```js -type PropPredicate = (data?: PropData) => boolean; -``` - -### FormPropOptions\ - -`FormPropOptions` is the type that defines required and optional properties you have to pass in order to create a form prop. - -Its type definition is as follows: - -```js -type FormPropOptions = { - type: ePropType; - name: string; - displayName?: string; - id?: string; - permission?: string; - visible?: PropPredicate; - readonly?: PropPredicate; - disabled?: PropPredicate; - validators?: PropCallback; - asyncValidators?: PropCallback; - defaultValue?: boolean | number | string | Date; - options?: PropCallback[]>>; - autocomplete?: string; - isExtra? boolean; - formText?: string; - tooltip?: FormPropTooltip; -}; -``` - -As you see, passing `type` and `name` is enough to create a form prop. Here is what each property is good for: - -- **type** is the type of the prop value. It defines which input is rendered for the prop in the form. (_required_) -- **name** is the property name (or key) which will be used to read the value of the prop. (_required_) -- **displayName** is the name of the property which will be localized and shown as column header. (_default:_ `options.name`) -- **id** will be set as the `for` attribute of the label and the `id` attribute of the input for the field. (_default:_ `options.name`) -- **permission** is the permission context which will be used to decide if a column for this form prop should be displayed to the user or not. (_default:_ `undefined`) -- **visible** is a predicate that will be used to decide if this prop should be displayed on the form or not. (_default:_ `() => true`) -- **readonly** is a predicate that will be used to decide if this prop should be readonly or not. (_default:_ `() => false`) -- **disabled** is a predicate that will be used to decide if this prop should be disabled or not. (_default:_ `() => false`) -- **validators** is a callback that returns validators for the prop. (_default:_ `() => []`) -- **asyncValidators** is a callback that returns async validators for the prop. (_default:_ `() => []`) -- **defaultValue** is the initial value the field will have. (_default:_ `null`) -- **options** is a callback that is called when a dropdown is needed. It must return an observable. (_default:_ `undefined`) -- **autocomplete** will be set as the `autocomplete` attribute of the input for the field. Please check [possible values](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#Values). (_default:_ `'off'`) -- **isExtra** indicates this prop is an object extension. When `true`, the value of the field will be mapped from and to `extraProperties` of the entity. (_default:_ `undefined`) -- **formText** is the definition of the field. Placed under the field. (_default:_ `undefined`) -- **tooltip** is the tooltip for the field placed near of the label (_default:_ `undefined`) - -> Important Note: Do not use `record` property of `PropData` in create form predicates and callbacks, because it will be `undefined`. You can use it on edit form contributors though. - -You may find a full example below. - -### FormProp\ - -`FormProp` is the class that defines your form props. It takes a `FormPropOptions` and sets the default values to the properties, creating a form prop that can be passed to a form contributor. - -```js -const options: FormPropOptions = { - type: ePropType.Enum, - name: 'myProp', - displayName: 'Default::MyPropName', - id: 'my-prop', - permission: 'AbpIdentity.Users.ReadSensitiveData', // hypothetical - visible: data => { - const store = data.getInjected(Store); - const selectSensitiveDataVisibility = ConfigState.getSetting( - 'Abp.Identity.IsSensitiveDataVisible' // hypothetical - ); - - return store.selectSnapshot(selectSensitiveDataVisibility).toLowerCase() === 'true'; - }, - readonly: data => data.record.someProp, - disabled: data => data.record.someOtherProp, - validators: () => [Validators.required], - asyncValidators: data => { - const http = data.getInjected(HttpClient); - - function validate(control: AbstractControl): Observable { - if (control.pristine) return of(null); - - return http - .get('https://api.my-brand.io/hypothetical/endpoint/' + control.value) - .pipe(map(response => (response.valid ? null : { invalid: true }))); - } - - return [validate]; - }, - defaultValue: 0, - options: data => { - const service = data.getInjected(MyIdentityService); - - return service.getMyPropOptions() - .pipe( - map(({items}) => items.map( - item => ({key: item.name, value: item.id }) - )), - ); - }, - autocomplete: 'off', - isExtra: true, - template: undefined | Type, // Custom angular component - tooltip: { text: 'Default::MyPropName_Tooltip', placement: 'top' }, - formText: 'Default::MyPropName_Description', -}; - -const prop = new FormProp(options); -``` -FormProp has the template option since version 6.0. it can accept custom angular component. -The component can access PropData and Prop. -Example of the custom prop component. -```js -import { - EXTENSIBLE_FORM_VIEW_PROVIDER, - EXTENSIONS_FORM_PROP, - EXTENSIONS_FORM_PROP_DATA, -} from '@abp/ng.components/extensible'; - - -@Component({ - selector: 'my-custom-custom-prop', - templateUrl: './my-custom-custom-prop.component.html', - viewProviders: [EXTENSIBLE_FORM_VIEW_PROVIDER], //you should add this, otherwise form-group doesn't work. -}) -export class MyCustomPropComponent { - constructor( - @Inject(EXTENSIONS_FORM_PROP) private formProp: FormProp, - @Inject(EXTENSIONS_FORM_PROP_DATA) private propData: ProfileDto, - ...) - ... -} -``` - -It also has two static methods to create its instances: - -- **FormProp.create\\(options: FormPropOptions\\)** is used to create an instance of `FormProp`. - ```js - const prop = FormProp.create(options); - ``` -- **FormProp.createMany\\(options: FormPropOptions\\[\]\)** is used to create multiple instances of `FormProp` with given array of `FormPropOptions`. - ```js - const props = FormProp.createMany(optionsArray); - ``` - -### FormPropList\ - -`FormPropList` is the list of props passed to every prop contributor callback as the first parameter named `propList`. It is a **doubly linked list**. You may find [all available methods here](../Common/Utils/Linked-List.md). - -The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this: - -```js -export function reorderUserContributors( - propList: FormPropList, -) { - // drop email node - const emailPropNode = propList.dropByValue( - 'AbpIdentity::EmailAddress', - (prop, displayName) => prop.displayName === displayName, - ); - - // add it back after phoneNumber - propList.addAfter( - emailPropNode.value, - 'phoneNumber', - (value, name) => value.name === name, - ); -} -``` - -### CreateFormPropContributorCallback\ - -`CreateFormPropContributorCallback` is the type that you can pass as **create form** prop contributor callbacks to static `forLazy` methods of the modules. - -```js -export function myPropCreateContributor( - propList: FormPropList, -) { - // add myProp as 2nd field from the start - propList.add(myProp).byIndex(1); -} - -export const identityCreateFormPropContributors = { - [eIdentityComponents.Users]: [myPropCreateContributor], -}; -``` - -### EditFormPropContributorCallback\ - -`EditFormPropContributorCallback` is the type that you can pass as **edit form** prop contributor callbacks to static `forLazy` methods of the modules. - -```js -export function myPropEditContributor( - propList: FormPropList, -) { - // add myProp as 2nd field from the end - propList.add(myProp).byIndex(-1); -} - -export const identityEditFormPropContributors = { - [eIdentityComponents.Users]: [myPropEditContributor], -}; -``` - -## See Also - -- [Customizing Application Modules Guide](../../Customizing-Application-Modules-Guide.md) diff --git a/docs/en/UI/Angular/Ellipsis-Directive.md b/docs/en/UI/Angular/Ellipsis-Directive.md deleted file mode 100644 index c462331574..0000000000 --- a/docs/en/UI/Angular/Ellipsis-Directive.md +++ /dev/null @@ -1,83 +0,0 @@ -# Ellipsis - -Text inside an HTML element can be truncated easily with an ellipsis by using CSS. To make this even easier, you can use the `EllipsisDirective` which has been exposed by the `@abp/ng.theme.shared` package. - - -## Getting Started - -In order to use the `EllipsisDirective` in an HTML template, the **`ThemeSharedModule`** should be imported into your module like this: - -```js -// ... -import { ThemeSharedModule } from '@abp/ng.theme.shared'; - -@NgModule({ - //... - imports: [..., ThemeSharedModule], -}) -export class MyFeatureModule {} -``` - -or **if you would not like to import** the `ThemeSharedModule`, you can import the **`EllipsisModule`** as shown below: - - -```js -// ... -import { EllipsisModule } from '@abp/ng.theme.shared'; - -@NgModule({ - //... - imports: [..., EllipsisModule], -}) -export class MyFeatureModule {} -``` - -## Usage - -The `EllipsisDirective` is very easy to use. The directive's selector is **`abpEllipsis`**. By adding the `abpEllipsis` attribute to an HTML element, you can activate the `EllipsisDirective` for the HTML element. - -See an example usage: - -```html -

    - Lorem ipsum dolor sit, amet consectetur adipisicing elit. Laboriosam commodi quae aspernatur, - corporis velit et suscipit id consequuntur amet minima expedita cum reiciendis dolorum - cupiditate? Voluptas eaque voluptatum odio deleniti quo vel illum nemo accusamus nulla ratione - impedit dolorum expedita necessitatibus fugiat ullam beatae, optio eum cupiditate ducimus - architecto. -

    -``` - -The `abpEllipsis` attribute has been added to the `

    ` element that containing very long text inside to activate the `EllipsisDirective`. - -See the result: - -![Ellipsis directive result](./images/ellipsis-directive-result1.jpg) - -The long text has been truncated by using the directive. - -The UI before using the directive looks like this: - -![Before using the EllipsisDirective](./images/ellipsis-directive-before.jpg) - -### Specifying Max Width of an HTML Element - -An HTML element max width can be specified as shown below: - -```html -

    - Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque, optio! -
    - -
    - Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque, optio! -
    - -
    - Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque, optio! -
    -``` - -See the result: - -![Ellipsis directive result 2](./images/ellipsis-directive-result2.jpg) \ No newline at end of file diff --git a/docs/en/UI/Angular/Entity-Action-Extensions.md b/docs/en/UI/Angular/Entity-Action-Extensions.md deleted file mode 100644 index ead1a39b72..0000000000 --- a/docs/en/UI/Angular/Entity-Action-Extensions.md +++ /dev/null @@ -1,409 +0,0 @@ -# Entity Action Extensions for Angular UI - -## Introduction - -Entity action extension system allows you to add a new action to the action menu for an entity. A "Click Me" action was added to the user management page below: - -Entity Action Extension Example: 'Click Me!' Action - -You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access the current entity in your code. - -## How to Set Up - -In this example, we will add a "Click Me!" action and alert the current row's `userName` in the user management page of the [Identity Module](../../Modules/Identity.md). - -### Step 1. Create Entity Action Contributors - -The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root module: - -```ts -// src/app/entity-action-contributors.ts - -import { eIdentityComponents, IdentityEntityActionContributors } from '@abp/ng.identity'; -import { IdentityUserDto } from '@abp/ng.identity/proxy'; -import { EntityAction, EntityActionList } from '@abp/ng.components/extensible'; - -const alertUserName = new EntityAction({ - text: 'Click Me!', - action: (data) => { - // Replace alert with your custom code - alert(data.record.userName); - }, - // See EntityActionOptions in API section for all options -}); - -export function alertUserNameContributor(actionList: EntityActionList) { - actionList.addTail(alertUserName); -} - -export const identityEntityActionContributors: IdentityEntityActionContributors = { - // enum indicates the page to add contributors to - [eIdentityComponents.Users]: [ - alertUserNameContributor, - // You can add more contributors here - ], -}; -``` - -The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addTail` method, which adds the given value to the end of the list. You may find [all available methods here](../Common/Utils/Linked-List.md). - -### Step 2. Import and Use Entity Action Contributors - -Import `identityEntityActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below: - -```js -// src/app/app-routing.module.ts - -// other imports -import { identityEntityActionContributors } from './entity-action-contributors'; - -const routes: Routes = [ - // other routes - - { - path: 'identity', - loadChildren: () => - import('@abp/ng.identity').then(m => - m.IdentityModule.forLazy({ - entityActionContributors: identityEntityActionContributors, - }) - ), - }, - - // other routes -]; -``` - -That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the "Users" page (`UsersComponent`) of the `IdentityModule`. - -## How to Place a Custom Modal and Trigger It by Entity Actions - -Let's employ dependency injection to extend the functionality of `IdentityModule` and add a quick view action for the User entity. We will take a lazy-loaded approach. - -Entity Action Extension Example: Custom Modal - -1. Create a folder at this path: `src/app/identity-extended` - -2. Add an entity action similar to this: - ```js - // src/app/identity-extended/entity-action-contributors.ts - - import { - eIdentityComponents, - IdentityEntityActionContributors, - IdentityUserDto, - } from '@abp/ng.identity'; - import { EntityAction, EntityActionList } from '@abp/ng.components/extensible'; - import { IdentityExtendedComponent } from './identity-extended.component'; - - const quickViewAction = new EntityAction({ - text: 'Quick View', - action: data => { - const component = data.getInjected(IdentityExtendedComponent); - component.openUserQuickView(data.record); - }, - }); - - export function customModalContributor(actionList: EntityActionList) { - actionList.addTail(quickViewAction); - } - - export const identityEntityActionContributors: IdentityEntityActionContributors = { - // enum indicates the page to add contributors to - [eIdentityComponents.Users]: [ - customModalContributor, - // You can add more contributors here - ], - }; - ``` - -3. Create a parent component to the identity module. - ```js - // src/app/identity-extended/identity-extended.component.ts - - import { IdentityUserDto } from '@abp/ng.identity'; - import { Component } from '@angular/core'; - - @Component({ - selector: 'app-identity-extended', - templateUrl: './identity-extended.component.html', - }) - export class IdentityExtendedComponent { - isUserQuickViewVisible: boolean; - - user: IdentityUserDto; - - openUserQuickView(record: IdentityUserDto) { - this.user = new Proxy(record, { - get: (target, prop) => target[prop] || '—', - }); - this.isUserQuickViewVisible = true; - } - } - ``` - -4. Add a router outlet and a modal to the parent component. - ```html - - - - - - -

    {%{{{ user.userName }}}%}

    -
    - - - - - - - - - - - - - - - - - - - - - -
    {%{{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}}%}{%{{{ user.name }}}%}
    {%{{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}}%}{%{{{ user.surname }}}%}
    {%{{{ 'AbpIdentity::EmailAddress' | abpLocalization }}}%}{%{{{ user.email }}}%}
    {%{{{ 'AbpIdentity::PhoneNumber' | abpLocalization }}}%}{%{{{ user.phoneNumber }}}%}
    -
    - - - - -
    - ``` - -5. Add a module for the component and load `IdentityModule` as seen below: - ```js - // src/app/identity-extended/identity-extended.module.ts - - import { CoreModule } from '@abp/ng.core'; - import { IdentityModule } from '@abp/ng.identity'; - import { ThemeSharedModule } from '@abp/ng.theme.shared'; - import { NgModule } from '@angular/core'; - import { RouterModule } from '@angular/router'; - import { identityEntityActionContributors } from './entity-action-contributors'; - import { IdentityExtendedComponent } from './identity-extended.component'; - - @NgModule({ - imports: [ - CoreModule, - ThemeSharedModule, - RouterModule.forChild([ - { - path: '', - component: IdentityExtendedComponent, - children: [ - { - path: '', - loadChildren: () => - IdentityModule.forLazy({ - entityActionContributors: identityEntityActionContributors, - }), - }, - ], - }, - ]), - ], - declarations: [IdentityExtendedComponent], - }) - export class IdentityExtendedModule {} - ``` - -6. Load `IdentityExtendedModule` instead of `IdentityModule` in your root routing module. - ```js - // src/app/app-routing.module.ts - - const routes: Routes = [ - // other routes - - { - path: 'identity', - loadChildren: () => - import('./identity-extended/identity-extended.module') - .then(m => m.IdentityExtendedModule), - }, - - // other routes - ]; - ``` - -That's it. As you see, we reached the `IdentityExtendedComponent` through dependency injection and called one of its methods in our action. The specific user was also available via `data.record`, so we were able to display a summary view. - -## API - -### ActionData\ - -`ActionData` is the shape of the parameter passed to all callbacks or predicates in an `EntityAction`. - -It has the following properties: - -- **record** is the row data, i.e. current value rendered in the table. - - ```js - { - text: 'Click Me!', - action: data => { - alert(data.record.userName); - }, - } - ``` - -- **index** is the table index where the record is at. - -- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `GridActionsComponent`, including, but not limited to, its parent component. - - ```js - { - text: 'Click Me!', - action: data => { - const restService = data.getInjected(RestService); - - // Use restService public props and methods here - }, - visible: data => { - const usersComponent = data.getInjected(UsersComponent); - - // Use usersComponent public props and methods here - }, - } - ``` - -### ActionCallback\ - -`ActionCallback` is the type of the callback function that can be passed to an `EntityAction` as `action` parameter. An action callback gets a single parameter, the `ActionData`. The return type may be anything, including `void`. Here is a simplified representation: - -```js -type ActionCallback = (data?: ActionData) => R; -``` - -### ActionPredicate\ - -`ActionPredicate` is the type of the predicate function that can be passed to an `EntityAction` as `visible` parameter. An action predicate gets a single parameter, the `ActionData`. The return type must be `boolean`. Here is a simplified representation: - -```js -type ActionPredicate = (data?: ActionData) => boolean; -``` - -### EntityActionOptions\ - -`EntityActionOptions` is the type that defines required and optional properties you have to pass in order to create an entity action. - -Its type definition is as follows: - -```js -type EntityActionOptions = { - action: ActionCallback, - text: string, - icon?: string, - permission?: string, - visible?: ActionPredicate, - btnClass?: string, - btnStyle?: string, - showOnlyIcon?: boolean, - tooltip?: FormPropTooltip; -}; -``` - -As you see, passing `action` and `text` is enough to create an entity action. Here is what each property is good for: - -- **action** is a callback that is called when the grid action is clicked. (_required_) -- **text** is the button text which will be localized. (_required_) -- **icon** is the classes that define an icon to be placed before the text. (_default:_ `''`) -- **permission** is the permission context which will be used to decide if this type of grid action should be displayed to the user or not. (_default:_ `undefined`) -- **visible** is a predicate that will be used to decide if the current record should have this grid action or not. (_default:_ `() => true`) -- **btnClass** is the classes that will be applied to the button. (_default:_ `'btn btn-primary text-center'`) -- **btnStyle** is the styles that will be applied to the button. (_default:_ `''`) -- **showOnlyIcon** is shows only the icon itself. (_default:_ `false`) -- **tooltip** is only available in single entity action button. Adds an tooltip for button. (_default:_ undefined) - -You may find a full example below. - -### EntityAction\ - -`EntityAction` is the class that defines your entity actions. It takes an `EntityActionOptions` and sets the default values to the properties, creating an entity action that can be passed to an entity contributor. - -```js -const options: EntityActionOptions = { - action: data => { - const component = data.getInjected(IdentityExtendedComponent); - component.unlock(data.record.id); - }, - text: 'AbpIdentity::Unlock', - icon: 'fa fa-unlock', - permission: 'AbpIdentity.Users.Update', - visible: data => data.record.isLockedOut, - btnClass:'btn btn-warning text-center', - btnStyle: '', //Adds inline style - showOnlyIcon: true, - tooltip: { text: 'AbpIdentity::Edit', placement: 'top' } -}; - -const action = new EntityAction(options); -``` - -It also has two static methods to create its instances: - -- **EntityAction.create\\(options: EntityActionOptions\\)** is used to create an instance of `EntityAction`. - ```js - const action = EntityAction.create(options); - ``` -- **EntityAction.createMany\\(options: EntityActionOptions\\[\]\)** is used to create multiple instances of `EntityAction` with given array of `EntityActionOptions`. - ```js - const actions = EntityAction.createMany(optionsArray); - ``` - -### EntityActionList\ - -`EntityActionList` is the list of actions passed to every action contributor callback as the first parameter named `actionList`. It is a **doubly linked list**. You may find [all available methods here](../Common/Utils/Linked-List.md). - -The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this: - -```js -export function reorderUserContributors( - actionList: EntityActionList, -) { - // drop "Unlock" button - const unlockActionNode = actionList.dropByValue( - 'AbpIdentity::Unlock', - (action, text) => action.text === text, - ); - - // add it back to the head of the list - actionList.addHead(unlockActionNode.value); -} -``` - -### EntityActionContributorCallback\ - -`EntityActionContributorCallback` is the type that you can pass as entity action contributor callbacks to static `forLazy` methods of the modules. - -```js -// lockUserContributor should have EntityActionContributorCallback type - -export function lockUserContributor( - actionList: EntityActionList, -) { - // add lockUser as 3rd action - actionList.add(lockUser).byIndex(2); -} - -export const identityEntityActionContributors = { - [eIdentityComponents.Users]: [lockUserContributor], -}; -``` - -## See Also - -- [Customizing Application Modules Guide](../../Customizing-Application-Modules-Guide.md) diff --git a/docs/en/UI/Angular/Environment.md b/docs/en/UI/Angular/Environment.md deleted file mode 100644 index 026c5c5fc7..0000000000 --- a/docs/en/UI/Angular/Environment.md +++ /dev/null @@ -1,177 +0,0 @@ -# Environment - -Every application needs some **environment** variables. In Angular world, this is usually managed by `environment.ts`, `environment.prod.ts` and so on. It is the same for ABP as well. - -Current `Environment` configuration holds sub config classes as follows: - -```js -export interface Environment { - apis: Apis; - application: Application; - oAuthConfig: AuthConfig; - production: boolean; - remoteEnv?: RemoteEnv; -} -``` - -## Apis - -```js -export interface Apis { - [key: string]: ApiConfig; - default: ApiConfig; -} - -export interface ApiConfig { - [key: string]: string; - rootNamespace?: string; - url: string; -} -``` - -Api config has to have a default config and it may have some additional ones for different modules. -I.e. you may want to connect to different Apis for different modules. - -Take a look at following example - -```json -{ - // ... - "apis": { - "default": { - "url": "https://localhost:8080" - }, - "AbpIdentity": { - "url": "https://localhost:9090" - } - } - // ... -} -``` - -When an api from `AbpIdentity` is called, the request will be sent to `"https://localhost:9090"`. -Everything else will be sent to `"https://localhost:8080"` - -- `rootNamespace` **(new)** : Root namespace of the related API. e.g. Acme.BookStore - -## Application - -```js -export interface Application { - name: string; - baseUrl?: string; - logoUrl?: string; -} -``` - -- `name`: Name of the backend Application. It is also used by `logo.component` if `logoUrl` is not provided. -- `logoUrl`: Url of the application logo. It is used by `logo.component` -- `baseUrl`: [For detailed information](./Multi-Tenancy.md#domain-tenant-resolver) - -## AuthConfig - -For authentication, we use angular-oauth2-oidc. Please check their [docs](https://github.com/manfredsteyer/angular-oauth2-oidc) out - -## RemoteEnvironment - -Some applications need to integrate an existing config into the `environment` used throughout the application. -Abp Framework supports this out of box. - -To integrate an existing config json into the `environment`, you need to set `remoteEnv` - -```js -export type customMergeFn = ( - localEnv: Partial, - remoteEnv: any -) => Config.Environment; - -export interface RemoteEnv { - url: string; - mergeStrategy: "deepmerge" | "overwrite" | customMergeFn; - method?: string; - headers?: ABP.Dictionary; -} -``` - -- `url` \*: Required. The url to be used to retrieve environment config -- `mergeStrategy` \*: Required. Defines how the local and the remote `environment` json will be merged - - `deepmerge`: Both local and remote `environment` json will be merged recursively. If both configs have same nested path, the remote `environment` will be prioritized. - - `overwrite`: Remote `environment` will be used and local environment will be ignored. - - `customMergeFn`: You can also provide your own merge function as shown in the example. It will take two parameters, `localEnv: Partial` and `remoteEnv` and it needs to return a `Config.Environment` object. -- `method`: HTTP method to be used when retrieving environment config. Default: `GET` -- `headers`: If extra headers are needed for the request, it can be set through this field. - -## EnvironmentService - -` EnvironmentService` is a singleton service, i.e. provided in root level of your application, and keeps the environment in the internal store. - -### Before Use - -In order to use the `EnvironmentService` you must inject it in your class as a dependency. - -```js -import { EnvironmentService } from '@abp/ng.core'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent { - constructor(private environment: EnvironmentService) {} -} -``` - -You do not have to provide the `EnvironmentService` at module or component/directive level, because it is already **provided in root**. - -### Get Methods - -`EnvironmentService` has numerous get methods which allow you to get a specific value or all environment object. - -Get methods with "$" at the end of the method name (e.g. `getEnvironment$`) return an RxJs stream. The streams are triggered when set or patched the state. - -#### How to Get Environment Object - -You can use the `getEnvironment` or `getEnvironment$` method of `EnvironmentService` to get all of the environment object. It is used as follows: - -```js -// this.environment is instance of EnvironmentService - -const environment = this.environment.getEnvironment(); - -// or -this.environment.getEnvironment$().subscribe((environment) => { - // use environment here -}); -``` - -#### How to Get API URL - -The `getApiUrl` or `getApiUrl$` method is used to get a specific API URL from the environment object. This is how you can use it: - -```js -// this.environment is instance of EnvironmentService - -const apiUrl = this.environment.getApiUrl(); -// environment.apis.default.url - -this.environment.getApiUrl$("search").subscribe((searchUrl) => { - // environment.apis.search.url -}); -``` - -This method returns the `url` of a specific API based on the key given as its only parameter. If there is no key, `'default'` is used. - -#### How to Set the Environment - -`EnvironmentService` has a method named `setState` which allows you to set the state value. - -```js -// this.environment is instance of EnvironmentService - -this.environment.setState(newEnvironmentObject); -``` - -Note that **you do not have to call this method at application initiation**, because the environment variables are already being stored at start. - -#### Environment Properties - -Please refer to `Environment` type for all the properties. It can be found in the [environment.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/environment.ts#L4). diff --git a/docs/en/UI/Angular/Extensions-Overall.md b/docs/en/UI/Angular/Extensions-Overall.md deleted file mode 100644 index 2bb9862b1d..0000000000 --- a/docs/en/UI/Angular/Extensions-Overall.md +++ /dev/null @@ -1,34 +0,0 @@ -## Angular UI Extensions - -Angular UI extensions system allows you to add a new action to the actions menu, a new column to the data table, a new action to the toolbar of a page, and add a new field to the create and/or edit forms. - -See the documents below for the details: - -* [Entity Action Extensions](Entity-Action-Extensions.md) -* [Data Table Column (or Entity Prop) Extensions](Data-Table-Column-Extensions.md) -* [Page Toolbar Extension](Page-Toolbar-Extensions.md) -* [Dynamic Form (or Form Prop) Extensions](Dynamic-Form-Extensions.md) - -## Extensible Table Component - -Using [ngx-datatable](https://github.com/swimlane/ngx-datatable) in extensible table. - -````ts - - -```` - - * ` actionsText : ` ** Column name of action column. **Type** : string - * ` data : ` Items shows in your table. **Type** : Array - * ` list : ` Instance of ListService. **Type** : ListService - * `actionsColumnWidth : ` Width of your action column. **Type** : number - * ` actionsTemplate : ` Template of the action when "click this button" or whatever. Generally ng-template. **Type** : TemplateRef - * ` recordsTotal : ` Count of the record total. **Type** : number - * ` tableActivate : ` The Output(). A cell or row was focused via the keyboard or a mouse click. **Type** : EventEmitter() diff --git a/docs/en/UI/Angular/Feature-Libraries.md b/docs/en/UI/Angular/Feature-Libraries.md deleted file mode 100644 index bdfff93371..0000000000 --- a/docs/en/UI/Angular/Feature-Libraries.md +++ /dev/null @@ -1,92 +0,0 @@ -# About Feature Libraries - -ABP has an ever-growing number of feature modules and [introducing a new one](../../Module-Development-Basics.md) is always possible. When the UI is Angular, these features have modular Angular libraries accompanying them. - -## Feature Library Content - -Each library has at least two modules: - -1. The main module contains all components, services, types, enums, etc. to deliver the required UI when the feature is loaded. From here on, we will refer to these modules as **"feature module"**. -2. There is also a **"config module"** per library which helps us configure applications to run these modules or make them accessible. - -## How to Add a Feature Library to Your Project - - - -The manual setup of a feature library has three steps: - -### 1. Install the Library - -Feature libraries are usually published as an npm package. If a library you want to use does not exist in your project, you may install it via the following command: - -```shell -yarn add @my-company-name/my-project-name -``` - -...or... - -```shell -npm install @my-company-name/my-project-name -``` - -The `my-company-name` and `my-project-name` parts are going to change according to the package you want to use. For example, if we want to install the ABP Identity module, the package installation will be as seen below: - -```shell -yarn add @abp/ng.identity -``` - -> Identity is used just as an example. If you have initiated your project with ABP CLI or ABP Suite, the identity library will already be installed and configured in your project. - -### 2. Import the Config Module - -As of ABP v3.0, every lazy-loaded module has a config module available via a secondary entry point on the same package. Importing them in your root module looks like this: - -```js -import { IdentityConfigModule } from "@abp/ng.identity/config"; - -@NgModule({ - imports: [ - // other imports - IdentityConfigModule.forRoot(), - ], - // providers, declarations, and bootstrap -}) -export class AppModule {} -``` - -We need the config modules for actions required before feature modules are loaded (lazily). For example, the above import configures the menu to display links to identity pages. - -Furthermore, depending on the library, the `.forRoot` static method may receive some options that configure how the feature works. - -### 3. Import the Feature Module - -Finally, the feature module should be [loaded lazily via Angular router](https://angular.io/guide/lazy-loading-ngmodules). If you open the `/src/app/app-routing.module.ts` file, you should see `IdentityModule` is loaded exactly as follows: - -```js -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -const routes: Routes = [ - // other routes - { - path: "identity", - loadChildren: () => - import("@abp/ng.identity").then((m) => m.IdentityModule.forLazy()), - }, - // other routes -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - -When you load the identity feature like this, the "Users" page, for example, will have a route path of `/identity/users`. [1](#f-modify-route) - -Depending on the library, the `.forLazy` static method may also receive some options that configure how the feature works. - ---- - -1 _Libraries expect to work at a predefined path. Please check [how to patch a navigation element](./Modifying-the-Menu.md#how-to-patch-or-remove-a-navigation-element), if you want to use a different path from the default one (e.g. '/identity')._ [↩](#a-modify-route) diff --git a/docs/en/UI/Angular/Features.md b/docs/en/UI/Angular/Features.md deleted file mode 100644 index 3278bfd988..0000000000 --- a/docs/en/UI/Angular/Features.md +++ /dev/null @@ -1,33 +0,0 @@ -# Features - -You can get the value of a feature on the client-side using the [config state service](./Config-State.md) if it is allowed by the feature definition on the server-side. - -> This document explains how to get feature values in an Angular application. See the [Features document](../../Features.md) to learn the feature system. - -## Before Use - -To use the `ConfigStateService`, you must inject it in your class as a dependency. You do not have to provide the service explicitly, because it is already **provided in root**. - -```js -import { ConfigStateService } from '@abp/ng.core'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent { - constructor(private config: ConfigStateService) {} -} -``` - -## How to Get a Specific Feature - -You can use the `getFeature` method of `ConfigStateService` to get a specific feature from the configuration state. Here is an example: - -```js -// this.config is instance of ConfigStateService - -const defaultLang = this.config.getFeature("Identity.TwoFactor"); -// 'Optional' -``` - -You can then check the value of the feature to perform your logic. Please note that **feature keys are case-sensitive**. diff --git a/docs/en/UI/Angular/Form-Validation.md b/docs/en/UI/Angular/Form-Validation.md deleted file mode 100644 index a178fa213c..0000000000 --- a/docs/en/UI/Angular/Form-Validation.md +++ /dev/null @@ -1,198 +0,0 @@ -# Form Validation - -Reactive forms in ABP Angular UI are validated by [ngx-validate](https://www.npmjs.com/package/@ngx-validate/core) and helper texts are shown automatically based on validation rules and error blueprints. You do not have to add any elements or components to your templates. The library handles that for you. Here is how the experience is: - -The ngx-validate library validates an Angular reactive form and an error text appears under each wrong input based on the validation rule and the error blueprint. - -## How to Add New Error Messages - -You can add a new error message by passing validation options to the `ThemeSharedModule` in your root module. - -```js -import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core"; -import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared"; - -@NgModule({ - imports: [ - ThemeSharedModule.forRoot({ - validation: { - blueprints: { - uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]", - }, - }, - - // rest of theme shared config - }), - - // other imports - ], - - // rest of the module metadata -}) -export class AppModule {} -``` - -Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root module. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work. - -```js -import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core"; -import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared"; - -@NgModule({ - providers: [ - { - provide: VALIDATION_BLUEPRINTS, - useValue: { - ...DEFAULT_VALIDATION_BLUEPRINTS, - uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]", - }, - }, - - // other providers - ], - - // rest of the module metadata -}) -export class AppModule {} -``` - -When a [validator](https://angular.io/guide/form-validation#defining-custom-validators) or an [async validator](https://angular.io/guide/form-validation#creating-asynchronous-validators) returns an error with the key given to the error blueprints (`uniqueUsername` here), the validation library will be able to display an error message after localizing according to the given key and interpolation params. The result will look like this: - -An already taken username is entered while creating new user and a custom error message appears under the input after validation. - -In this example; - -- Localization key is `::AlreadyExists`. -- The interpolation param is `username`. -- Localization resource is defined as `"AlreadyExists": "Sorry, “{0}” already exists."`. -- And the validator should return `{ uniqueUsername: { username: "admin" } }` as the error object. - -## How to Change Existing Error Messages - -You can overwrite an existing error message by passing validation options to the `ThemeSharedModule` in your root module. Let's imagine you have a custom localization resource for required inputs. - -```json -"RequiredInput": "Oops! We need this input." -``` - -To use this instead of the built-in required input message, all you need to do is the following. - -```js -import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core"; -import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared"; - -@NgModule({ - imports: [ - ThemeSharedModule.forRoot({ - validation: { - blueprints: { - required: "::RequiredInput", - }, - }, - - // rest of theme shared config - }), - - // other imports - ], - - // rest of the module metadata -}) -export class AppModule {} -``` - -Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root module. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work. - -```js -import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core"; -import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared"; - -@NgModule({ - providers: [ - { - provide: VALIDATION_BLUEPRINTS, - useValue: { - ...DEFAULT_VALIDATION_BLUEPRINTS, - required: "::RequiredInput", - }, - }, - - // other providers - ], - - // rest of the module metadata -}) -export class AppModule {} -``` - -The error message will look like this: - -A required field is cleared and the custom error message appears under the input. - -## How to Disable Validation on a Form - -If you want to validate a form manually, you can always disable automatic validation on it. All you need to do is place `skipValidation` on the form element. - -```html -
    - -
    -``` - -## How to Disable Validation on a Specific Field - -Validation works on any element or component with a `formControl` or `formControlName` directive. You can disable automatic validation on a specific field by placing `skipValidation` on the input element or component. - -```html - -``` - -## How to Use a Custom Error Component - -First, build a custom error component. Extending the existing `ValidationErrorComponent` would make it easier. - -```js -import { ValidationErrorComponent } from "@abp/ng.theme.basic"; -import { ChangeDetectionStrategy, Component } from "@angular/core"; - -@Component({ - selector: "app-validation-error", - template: ` -
    - {%{{{ error.message | abpLocalization: error.interpoliteParams }}}%} -
    - `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ErrorComponent extends ValidationErrorComponent {} -``` - -Then, declare and provide it in your root module. - -```js -import { VALIDATION_ERROR_TEMPLATE } from "@ngx-validate/core"; - -@NgModule({ - // rest of the module metadata - - declarations: [ - // other declarables - ErrorComponent, - ], - providers: [ - // other providers - { - provide: VALIDATION_ERROR_TEMPLATE, - useValue: ErrorComponent, - }, - ], -}) -export class AppModule {} -``` - -The error message will be bold and italic now: - -A required field is cleared and a bold and italic error message appears. diff --git a/docs/en/UI/Angular/FormInput-Component.md b/docs/en/UI/Angular/FormInput-Component.md deleted file mode 100644 index f1dedd824a..0000000000 --- a/docs/en/UI/Angular/FormInput-Component.md +++ /dev/null @@ -1,49 +0,0 @@ -# Form Input Component - -The ABP FormInput Component is a reusable form input component for the text type. - -# Inputs -* `label` -* `labelClass (default form-label)` -* `inputPlaceholder` -* `inputReadonly` -* `inputClass (default form-control)` - -# Outputs -* `formBlur` -* `formFocus` - -# Usage - -The ABP FormInput component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, there's no need to import it again. If not, then first import it as shown below: - -```ts -import { ThemeSharedModule } from "@abp/ng.theme.shared"; -import { FormInputDemoComponent } from "./FomrInputDemoComponent.component"; - -@NgModule({ - imports: [ - ThemeSharedModule, - // ... - ], - declarations: [FormInputDemoComponent], -}) -export class MyFeatureModule {} -``` - -Then, the `abp-form-input` component can be used. See the example below: - -```html -
    -
    - -
    -
    -``` - -See the form input result below: - -![abp-form-input](./images/form-input.png) diff --git a/docs/en/UI/Angular/GlobalFeatures.md b/docs/en/UI/Angular/GlobalFeatures.md deleted file mode 100644 index c8f9ba78df..0000000000 --- a/docs/en/UI/Angular/GlobalFeatures.md +++ /dev/null @@ -1,47 +0,0 @@ -# Angular: Global Features API - -The `ConfigStateService.getGlobalFeatures` API allows you to get the enabled features of the [Global Features](../../Global-Features.md) on the client side. - -> This document only explains the JavaScript API. See the [Global Features](../../Global-Features.md) document to understand the ABP Global Features system. - -## Usage - -````js - -import { ConfigStateService } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent implements OnInit { - constructor(private config: ConfigStateService) {} - - ngOnInit(): void { - // Gets all enabled global features. - const getGlobalFeatures = this.config.getGlobalFeatures(); - - //Example result is: `{ enabledFeatures: [ 'Shopping.Payment', 'Ecommerce.Subscription' ] }` - - // or - this.config.getGlobalFeatures$().subscribe(getGlobalFeatures => { - // use getGlobalFeatures here - }) - - // Check the global feature is enabled - this.config.getGlobalFeatureIsEnabled('Ecommerce.Subscription') - - //Example result is `true` - - this.config.getGlobalFeatureIsEnabled('My.Subscription') - - //Example result is `false` - - // or - this.config.getGlobalFeatureIsEnabled$('Ecommerce.Subscription').subscribe((isEnabled:boolean) => { - // use isEnabled here - }) - } -} - - diff --git a/docs/en/UI/Angular/HTTP-Error-Handling.md b/docs/en/UI/Angular/HTTP-Error-Handling.md deleted file mode 100644 index 8b4077fb53..0000000000 --- a/docs/en/UI/Angular/HTTP-Error-Handling.md +++ /dev/null @@ -1,229 +0,0 @@ -# HTTP Error Handling - -### Error Configurations - -ABP offers a configurations for errors handling like below - -```ts -import { ThemeSharedModule } from '@abp/ng.theme.shared'; -import { MyCustomRouteErrorComponent } from './my-custom-route.component'; - -@NgModule({ - imports: [ - ThemeSharedModule.forRoot({ - httpErrorConfig: { - skipHandledErrorCodes: [403], - errorScreen: { - forWhichErrors: [404], - component: CustomErrorComponent, - hideCloseIcon: false - } - } - }), - ... - ], -}) -export class AppModule {} -``` - -- `ErrorScreenErrorCodes` the error codes that you can pass to `skipHandledErrorCodes` and `forWhichErrors`. -- `skipHandledErrorCodes` the error codes those you don't want to handle it. -- `errorScreen` the screen that you want to show when a route error occurs. - - `component` component that you want to show. - - `forWhichErrors` same as `ErrorScreenErrorCodes` - - `hideCloseIcon` hides close icon in default ABP component. - -## Custom HTTP Error Handler -When the `RestService` is used, all HTTP errors are reported to the [`HttpErrorReporterService`](./HTTP-Error-Reporter-Service), and then `ErrorHandler`, a service exposed by the `@abp/ng.theme.shared` package automatically handles the errors. - -### Function Method `Deprecated` - -A custom HTTP error handler can be registered to an injection token named `HTTP_ERROR_HANDLER`. If a custom handler function is registered, the `ErrorHandler` executes that function. - -See an example: - -```ts -// http-error-handler.ts -import { ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core'; -import { ToasterService } from '@abp/ng.theme.shared'; -import { HttpErrorResponse } from '@angular/common/http'; -import { Injector } from '@angular/core'; -import { of, EMPTY } from 'rxjs'; -import { Error404Component } from './error404/error404.component'; - -export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) { - if (httpError.status === 400) { - const toaster = injector.get(ToasterService); - toaster.error(httpError.error?.error?.message || 'Bad request!', '400'); - return EMPTY; - } - - if (httpError.status === 404) { - const contentProjection = injector.get(ContentProjectionService); - contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component)); - return EMPTY; - } - - return of(httpError); -} - -// app.module.ts -import { Error404Component } from './error404/error404.component'; -import { handleHttpErrors } from './http-error-handling'; -import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared'; - -@NgModule({ - // ... - providers: [ - // ... - { provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors } - ], - declarations: [ - //... - Error404Component], -}) -export class AppModule {} -``` - -In the example above: - -- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs. -- 400 bad request errors is handled. When a 400 error occurs. - -- Since `of(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors. - -**Note 1:** If you put `return EMPTY` to next line of handling an error, default error handling will not work for that error. [EMPTY](https://rxjs.dev/api/index/const/EMPTY) can be imported from `rxjs`. - -```ts -export function handleHttpErrors( - injector: Injector, - httpError: HttpErrorResponse -) { - if (httpError.status === 403) { - // handle 403 errors here - return EMPTY; // put return EMPTY to skip default error handling - } -} -``` - -**Note 2:** If you put `return of(httpError)`, default error handling will work. - -- `of` is a function. It can be imported from `rxjs`. -- `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`. - -```ts -import { of } from "rxjs"; - -export function handleHttpErrors( - injector: Injector, - httpError: HttpErrorResponse -) { - if (httpError.status === 500) { - // handle 500 errors here - } - - // you can return the of(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above. - return of(httpError); -} -``` - -### Service Method - -You can provide **more than one handler** with services, a custom HTTP error handler service can be registered with injection token named **`CUSTOM_ERROR_HANDLERS`**. ABP has some default [error handlers](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts). - -### How To Add New Handler Service - -ABP error handler services are implements the interface of **CustomHttpErrorHandlerService**. - -**Interface of `CUSTOM_ERROR_HANDLERS`** - -```ts -interface CustomHttpErrorHandlerService { - readonly priority: number; - canHandle(error: unknown): boolean; - execute(): void; -} -``` - -- **`priority`** ABP sorts the services according to the number of the priority variable. Higher priority will be checked first. -- **`canHandle`** Check if the service can handle the error. Returns boolean. -- **`execute`** If the service can handle the error, then run the execute method. - -**In Summary** - -- Services are sorted by their priority number. -- Start from highest priority service and run canHandle() method. Pick the service if can handle the error, if not check next service. -- If the service found, run the execute method of a service. Done. - -See an example: - -```ts -// custom-error-handler.service.ts -import { inject, Injectable } from "@angular/core"; -import { HttpErrorResponse } from "@angular/common/http"; -import { CustomHttpErrorHandlerService } from "@abp/ng.theme.shared"; -import { CUSTOM_HTTP_ERROR_HANDLER_PRIORITY } from "@abp/ng.theme.shared"; -import { ToasterService } from "@abp/ng.theme.shared"; - -@Injectable({ providedIn: "root" }) -export class MyCustomErrorHandlerService - implements CustomHttpErrorHandlerService -{ - // You can write any number here, ex: 9999 - readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh; - protected readonly toaster = inject(ToasterService); - private error: HttpErrorResponse | undefined = undefined; - - // What kind of error should be handled by this service? You can decide it in this method. If error is suitable to your case then return true; otherwise return false. - canHandle(error: unknown): boolean { - if (error instanceof HttpErrorResponse && error.status === 400) { - this.error = error; - return true; - } - return false; - } - - // If this service is picked from ErrorHandler, this execute method will be called. - execute() { - this.toaster.error( - this.error.error?.error?.message || "Bad request!", - "400" - ); - } -} -``` - -```ts - -// app.module.ts -import { CUSTOM_ERROR_HANDLERS, ... } from '@abp/ng.theme.shared'; -import { MyCustomErrorHandlerService } from './custom-error-handler.service'; - -@NgModule({ - // ... - providers: [ - // ... - { - provide: CUSTOM_ERROR_HANDLERS, - useExisting: MyCustomErrorHandlerService, - multi: true, - } - ] -}) -export class AppModule {} -``` - -In the example above: - -- Created a service named `MyCustomErrorHandlerService`, and provided via `useExisting` key because we dont want another instance of it. And set `multi` key to true because ABP default error handlers are also provided with **CUSTOM_ERROR_HANDLERS** injection token. - -- 400 errors are handled from custom `MyCustomErrorHandlerService`. When a 400 error occurs, backend error message will be displayed as shown below: - -![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg) - -### Notes - -- If your service cannot handle the error. Then ABP will check the next Error Service. -- If none of the service handle the error. Then basic confirmation message about the error will be shown to the user. -- You can provide more than one service, with CUSTOM_ERROR_HANDLER injection token. -- If you want your custom service to be evaluated (checked) earlier, set the priority variable high. diff --git a/docs/en/UI/Angular/HTTP-Error-Reporter-Service.md b/docs/en/UI/Angular/HTTP-Error-Reporter-Service.md deleted file mode 100644 index 5f02589751..0000000000 --- a/docs/en/UI/Angular/HTTP-Error-Reporter-Service.md +++ /dev/null @@ -1,71 +0,0 @@ -# HTTP Error Reporter Service - -`HttpErrorReporterService` is a service which is exposed by `@abp/ng.core` package. HTTP errors can be reported by using this service. The service emits an event when an error is reported and keeps the errors as an array. The [`RestService`](./HTTP-Requests#restservice) uses the `HttpErrorReporterService` for reporting errors. - -See the example below to learn how to report an error: - -```ts -import { HttpErrorReporterService } from '@abp/ng.core'; -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { of } from 'rxjs'; -import { catchError } from 'rxjs/operators'; - -@Injectable() -export class SomeService { - constructor(private http: HttpClient, private httpErrorReporter: HttpErrorReporterService) {} - - getData() { - return this.http.get('http://example.com/get-data').pipe( - catchError(err => { - this.httpErrorReporter.reportError(err); - return of(null); - }), - ); - } -} -``` - -See the following example to learn listening the reported errors: - -```ts -import { HttpErrorReporterService } from '@abp/ng.core'; -import { HttpErrorResponse } from '@angular/common/http'; -import { Injectable } from '@angular/core'; - -@Injectable() -export class MyErrorHandler { - constructor(private httpErrorReporter: HttpErrorReporterService) { - this.handleErrors(); - } - - handleErrors() { - this.httpErrorReporter.reporter$.subscribe((err: HttpErrorResponse) => { - // handle the errors here - }); - } -} -``` - - -## API - - -### `reporter$: Observable` - -`reporter$` is a getter, returns an observable. It emits an event when a new error is reported. The event value type is `HttpErrorResponse`. - - -### `errors$: Observable` - -`errors$` is a getter, returns an observable. It emits an event when a new error is reported. The event value is all errors reported at runtime. - -### `errors: HttpErrorResponse` - -`errors` is a getter that returns all errors reported. - - -### `reportError(error: HttpErrorResponse): void` - -`reportError` is a method. The errors can be reported via this. -When an error is reported, the method triggers the `reports$` and `errors$` observables to emit an event. \ No newline at end of file diff --git a/docs/en/UI/Angular/HTTP-Requests.md b/docs/en/UI/Angular/HTTP-Requests.md deleted file mode 100644 index 9bef5cb8f2..0000000000 --- a/docs/en/UI/Angular/HTTP-Requests.md +++ /dev/null @@ -1,190 +0,0 @@ -# How to Make HTTP Requests - -## About HttpClient - -Angular has the amazing [HttpClient](https://angular.io/guide/http) for communication with backend services. It is a layer on top and a simplified representation of [XMLHttpRequest Web API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). It also is the recommended agent by Angular for any HTTP request. There is nothing wrong with using the `HttpClient` in your ABP project. - -However, `HttpClient` leaves error handling to the caller (method). In other words, HTTP errors are handled manually and by hooking into the observer of the `Observable` returned. - -```js -getConfig() { - this.http.get(this.configUrl).subscribe( - config => this.updateConfig(config), - error => { - // Handle error here - }, - ); -} -``` - -Although clear and flexible, handling errors this way is repetitive work, even when error processing is delegated to the store or any other injectable. - -An `HttpInterceptor` is able to catch `HttpErrorResponse` and can be used for a centralized error handling. Nevertheless, cases where default error handler, therefore the interceptor, must be disabled require additional work and comprehension of Angular internals. Check [this issue](https://github.com/angular/angular/issues/20203) for details. - -## RestService - -ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by default. - -### Getting Started with RestService - -In order to use the `RestService`, you must inject it in your class as a dependency. - -```js -import { RestService } from '@abp/ng.core'; - -@Injectable({ - /* class metadata here */ -}) -class DemoService { - constructor(private rest: RestService) {} -} -``` - -You do not have to provide the `RestService` at module or component/directive level, because it is already **provided in root**. - -### How to Make a Request with RestService - -You can use the `request` method of the `RestService` is for HTTP requests. Here is an example: - -```js -getFoo(id: number) { - const request: Rest.Request = { - method: 'GET', - url: '/api/some/path/to/foo/' + id, - }; - - return this.rest.request(request); -} -``` - -The `request` method always returns an `Observable`. Therefore you can do the following wherever you use `getFoo` method: - -```js -doSomethingWithFoo(id: number) { - this.demoService.getFoo(id).subscribe( - foo => { - // Do something with foo. - } - ) -} -``` - -**You do not have to worry about unsubscription.** The `RestService` uses `HttpClient` behind the scenes, so every observable it returns is a finite observable, i.e. it closes subscriptions automatically upon success or error. - -As you see, `request` method gets a request options object with `Rest.Request` type. This generic type expects the interface of the request body. You may pass `null` when there is no body, like in a `GET` or a `DELETE` request. Here is an example where there is one: - -```js -postFoo(body: Foo) { - const request: Rest.Request = { - method: 'POST', - url: '/api/some/path/to/foo', - body - }; - - return this.rest.request(request); -} -``` - -You may [check here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23) for complete `Rest.Request` type, which has only a few changes compared to [HttpRequest](https://angular.io/api/common/http/HttpRequest) class in Angular. - -### How to Disable Default Error Handler of RestService - -The `request` method, used with defaults, always handles errors. Let's see how you can change that behavior and handle errors yourself: - -```js -deleteFoo(id: number) { - const request: Rest.Request = { - method: 'DELETE', - url: '/api/some/path/to/foo/' + id, - }; - - return this.rest.request(request, { skipHandleError: true }); -} -``` - -`skipHandleError` config option, when set to `true`, disables the error handler and the returned observable starts throwing an error that you can catch in your subscription. - -```js -removeFooFromList(id: number) { - this.demoService.deleteFoo(id).subscribe( - foo => { - // Do something with foo. - }, - error => { - // Do something with error. - } - ) -} -``` - -### How to Get a Specific API Endpoint From Application Config - -Another nice config option that `request` method receives is `apiName` (available as of v2.4), which can be used to get a specific module endpoint from application configuration. - -```js -putFoo(body: Foo, id: string) { - const request: Rest.Request = { - method: 'PUT', - url: '/' + id, - body - }; - - return this.rest.request(request, {apiName: 'foo'}); -} -``` - -`putFoo` above will request `https://localhost:44305/api/some/path/to/foo/{id}` as long as the environment variables are as follows: - -```js -// environment.ts - -export const environment = { - apis: { - default: { - url: "https://localhost:44305", - }, - foo: { - url: "https://localhost:44305/api/some/path/to/foo", - }, - }, - - /* rest of the environment variables here */ -}; -``` - -### How to Observe Response Object or HTTP Events Instead of Body - -`RestService` assumes you are generally interested in the body of a response and, by default, sets `observe` property as `'body'`. However, there may be times you are rather interested in something else, such as a custom proprietary header. For that, the `request` method receives `observe` property in its config object. - -```js -getSomeCustomHeaderValue() { - const request: Rest.Request = { - method: 'GET', - url: '/api/some/path/that/sends/some-custom-header', - }; - - return this.rest.request>( - request, - {observe: Rest.Observe.Response}, - ).pipe( - map(response => response.headers.get('Some-Custom-Header')) - ); -} -``` - -You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10). - -### How to Skip HTTP interceptors and ABP headers - -The ABP Framework adds several HTTP headers to the HttpClient, such as the "Auth token" or "tenant Id". -The ABP Server must possess the information but the ABP user may not want to send this informations to an external server. -ExternalHttpClient and IS EXTERNAL REQUEST HttpContext Token were added in V6.0.4. -The ABP Http interceptors check the value of the `IS_EXTERNAL_REQUEST` token. If the token is True then ABP-specific headers won't be added to Http Request. -The `ExternalHttpClient` extends from `HTTPClient` and sets the `IS_EXTERNAL_REQUEST` context token to true. -When you are using `ExternalHttpClient` as HttpClient in your components, it does not add ABP-specific headers. - -Note: With `IS_EXTERNAL_REQUEST` or without it, ABP loading service works. - -## See Also - -- [HTTP Error Handling / Customization](./HTTP-Error-Handling) diff --git a/docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md b/docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md deleted file mode 100644 index c617b89a14..0000000000 --- a/docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md +++ /dev/null @@ -1,271 +0,0 @@ -# How Replaceable Components Work with Extensions - -Additional UI extensibility points ([Entity action extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Entity-Action-Extensions), [data table column extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Data-Table-Column-Extensions), [page toolbar extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Page-Toolbar-Extensions) and others) are used in ABP pages to allow to control entity actions, table columns and page toolbar of a page. If you replace a page, you need to apply some configurations to be able to work extension components in your component. Let's see how to do this by replacing the roles page. - -Create a new module called `MyRolesModule`: - -```bash -yarn ng generate module my-roles --module app -``` - -Create a new component called `MyRolesComponent`: - -```bash -yarn ng generate component my-roles/my-roles --flat --export -``` - -Open the generated `src/app/my-roles/my-roles.component.ts` file and replace its content with the following: - -```js -import { ListService, PagedAndSortedResultRequestDto, PagedResultDto } from '@abp/ng.core'; -import { eIdentityComponents, IdentityRoleDto, IdentityRoleService, RolesComponent } from '@abp/ng.identity'; -import { ePermissionManagementComponents } from '@abp/ng.permission-management'; -import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared'; -import { - EXTENSIONS_IDENTIFIER, - FormPropData, - generateFormFromProps -} from '@abp/ng.components/extensible'; -import { Component, Injector, OnInit } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { finalize } from 'rxjs/operators'; - -@Component({ - selector: 'app-my-roles', - templateUrl: './my-roles.component.html', - providers: [ - ListService, - { - provide: EXTENSIONS_IDENTIFIER, - useValue: eIdentityComponents.Roles, - }, - { - provide: RolesComponent, - useExisting: MyRolesComponent - } - ], -}) -export class MyRolesComponent implements OnInit { - data: PagedResultDto = { items: [], totalCount: 0 }; - - form: FormGroup; - - selected: IdentityRoleDto; - - isModalVisible: boolean; - - visiblePermissions = false; - - providerKey: string; - - modalBusy = false; - - permissionManagementKey = ePermissionManagementComponents.PermissionManagement; - - onVisiblePermissionChange = event => { - this.visiblePermissions = event; - }; - - constructor( - public readonly list: ListService, - protected confirmationService: ConfirmationService, - protected injector: Injector, - protected service: IdentityRoleService, - ) {} - - ngOnInit() { - this.hookToQuery(); - } - - buildForm() { - const data = new FormPropData(this.injector, this.selected); - this.form = generateFormFromProps(data); - } - - openModal() { - this.buildForm(); - this.isModalVisible = true; - } - - add() { - this.selected = {} as IdentityRoleDto; - this.openModal(); - } - - edit(id: string) { - this.service.get(id).subscribe(res => { - this.selected = res; - this.openModal(); - }); - } - - save() { - if (!this.form.valid) return; - this.modalBusy = true; - - const { id } = this.selected; - (id - ? this.service.update(id, { ...this.selected, ...this.form.value }) - : this.service.create(this.form.value) - ) - .pipe(finalize(() => (this.modalBusy = false))) - .subscribe(() => { - this.isModalVisible = false; - this.list.get(); - }); - } - - delete(id: string, name: string) { - this.confirmationService - .warn('AbpIdentity::RoleDeletionConfirmationMessage', 'AbpIdentity::AreYouSure', { - messageLocalizationParams: [name], - }) - .subscribe((status: Confirmation.Status) => { - if (status === Confirmation.Status.confirm) { - this.service.delete(id).subscribe(() => this.list.get()); - } - }); - } - - private hookToQuery() { - this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res)); - } - - openPermissionsModal(providerKey: string) { - this.providerKey = providerKey; - setTimeout(() => { - this.visiblePermissions = true; - }, 0); - } - - sort(data) { - const { prop, dir } = data.sorts[0]; - this.list.sortKey = prop; - this.list.sortOrder = dir; - } -} -``` - -```js - { - provide: EXTENSIONS_IDENTIFIER, - useValue: eIdentityComponents.Roles, - }, - { - provide: RolesComponent, - useExisting: MyRolesComponent - } -``` - -The two providers we have defined in `MyRolesComponent` are required for the extension components to work correctly. - -* With the first provider, we defined the extension identifier for using `RolesComponent`'s extension actions in the `MyRolesComponent`. -* With the second provider, we have replaced the `RolesComponent` injection with the `MyRolesComponent`. Default extension actions of the `RolesComponent` try to get `RolesComponent` instance. However, the actions can get the `MyRolesComponent` instance after defining the second provider. - -Open the generated `src/app/my-role/my-role.component.html` file and replace its content with the following: - -```html -
    -
    -
    -
    -
    My Roles
    -
    -
    - -
    -
    -
    - -
    - -
    -
    - - - -

    {%{{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewRole') | abpLocalization }}}%}

    -
    - - -
    - -
    -
    - - - - {%{{{ - 'AbpIdentity::Save' | abpLocalization - }}}%} - -
    - - - -``` - -We have added the `abp-page-toolbar`, `abp-extensible-table`, and `abp-extensible-form` extension components to template of the `MyRolesComponent`. - -You should import the required modules for the `MyRolesComponent` to `MyRolesModule`. Open the `src/my-roles/my-roles.module.ts` file and replace the content with the following: - -```js -import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions'; -import { NgModule } from '@angular/core'; -import { SharedModule } from '../shared/shared.module'; -import { MyRolesComponent } from './my-roles.component'; -import { PermissionManagementModule } from '@abp/ng.permission-management'; - -@NgModule({ - declarations: [MyRolesComponent], - imports: [SharedModule, UiExtensionsModule, PermissionManagementModule], - exports: [MyRolesComponent], -}) -export class MyRolesModule {} -``` - -- `UiExtensionsModule` imported to be able to use the extension components in your component. -- `PermissionManagementModule` imported to be able to use the `abp-permission-*management` in your component. - -As the last step, it is needs to be replaced the `RolesComponent` with the `MyRolesComponent`. Open the `app.component.ts` and modify its content as shown below: - -```js -import { ReplaceableComponentsService } from '@abp/ng.core'; -import { eIdentityComponents } from '@abp/ng.identity'; -import { MyRolesComponent } from './my-roles/my-roles.component'; - -@Component(/* component metadata */) -export class AppComponent { - constructor(private replaceableComponents: ReplaceableComponentsService) { - this.replaceableComponents.add({ component: MyRolesComponent, key: eIdentityComponents.Roles }); - } -} -``` - -After the steps above, the `RolesComponent` has been successfully replaced with the `MyRolesComponent`. When you navigate to the `/identity/roles` URL, you will see the `MyRolesComponent`'s template and see the extension components working correctly. - -![my-roles-component-with-extensions](./images/my-roles-component-with-extensions.jpg) - -![my-roles-component-form-extensions](./images/my-roles-component-form-extensions.jpg) diff --git a/docs/en/UI/Angular/Internet-Connection-Service.md b/docs/en/UI/Angular/Internet-Connection-Service.md deleted file mode 100644 index d9002b660e..0000000000 --- a/docs/en/UI/Angular/Internet-Connection-Service.md +++ /dev/null @@ -1,30 +0,0 @@ -# Internet Connection Service -`InternetConnectionService` is a service which is exposed by the `@abp/ng.core` package. **You can use this service in order to check your internet connection** - -## Getting Started -When you inject the InternetConnectionService you can get the current internet status, and it gets immediately updated if the status changes. - -`InternetConnectionService` provides two choices to catch the network status: -1. Signal (readonly) -2. Observable - - -# How To Use -İt's easy, just inject the service and get the network status. - -**You can get via signal** -```ts -class SomeComponent{ - internetConnectionService = inject(InternetConnectionService); - isOnline = this.internetConnectionService.networkStatus -} -``` -**or you can get the observable** -```ts -class SomeComponent{ - internetConnectionService = inject(InternetConnectionService); - isOnline = this.internetConnectionService.networkStatus$ -} -``` - -To see how we implement to the template, check the `InternetConnectionStatusComponent` diff --git a/docs/en/UI/Angular/Lazy-Load-Service.md b/docs/en/UI/Angular/Lazy-Load-Service.md deleted file mode 100644 index eccea527ab..0000000000 --- a/docs/en/UI/Angular/Lazy-Load-Service.md +++ /dev/null @@ -1,206 +0,0 @@ -# Lazy Loading Scripts & Styles - -You can use the `LazyLoadService` in @abp/ng.core package in order to lazy load scripts and styles in an easy and explicit way. - - - - -## Getting Started - -You do not have to provide the `LazyLoadService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. - -```js -import { LazyLoadService } from '@abp/ng.core'; - -@Component({ - /* class metadata here */ -}) -class DemoComponent { - constructor(private lazyLoadService: LazyLoadService) {} -} -``` - - - - -## Usage - -You can use the `load` method of `LazyLoadService` to create a ` -```` - -### External/CDN file Support - -The bundling system automatically recognizes the external/CDN files and adds them to the page without any change. - -#### Using External/CDN files in `AbpBundlingOptions` - -````csharp -Configure(options => -{ - options.StyleBundles - .Add("MyStyleBundle", configuration => - { - configuration - .AddFiles("/styles/my-style1.css") - .AddFiles("/styles/my-style2.css") - .AddFiles("https://cdn.abp.io/bootstrap.css") - .AddFiles("/styles/my-style3.css") - .AddFiles("/styles/my-style4.css"); - }); - - options.ScriptBundles - .Add("MyScriptBundle", configuration => - { - configuration - .AddFiles("/scripts/my-script1.js") - .AddFiles("/scripts/my-script2.js") - .AddFiles("https://cdn.abp.io/bootstrap.js") - .AddFiles("/scripts/my-script3.js") - .AddFiles("/scripts/my-script4.js"); - }); -}); -```` - -**Output HTML:** - -````html - - - - - - - -```` - -#### Using External/CDN files in Tag Helpers. - -````html - - - - - - - - - - - - - - - -```` - -**Output HTML:** - -````html - - - - - - - -```` - -## Themes - -Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more. - -## Best Practices & Suggestions - -It's suggested to define multiple bundles for an application, each one is used for different purposes. - -* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them. -* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice. -* **Module bundles**: For shared resources among the pages of an individual module. -* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice. - -Establish a balance between performance, network bandwidth usage and count of many bundles. - -## See Also - -* [Client Side Package Management](Client-Side-Package-Management.md) -* [Theming](Theming.md) diff --git a/docs/en/UI/AspNetCore/Client-Side-Package-Management.md b/docs/en/UI/AspNetCore/Client-Side-Package-Management.md deleted file mode 100644 index dc242e163d..0000000000 --- a/docs/en/UI/AspNetCore/Client-Side-Package-Management.md +++ /dev/null @@ -1,116 +0,0 @@ - -## ASP.NET Core MVC Client Side Package Management - -ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually. - -However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn. - -Finally, we suggest the [**Yarn**](https://classic.yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM. - -### @ABP NPM Packages - -ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state. - -One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions. - -To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [GitHub repository](https://github.com/volosoft/abp/tree/master/npm/packs). - -The benefit of a **standard package** is: - -* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version. -* It contains the mappings copy library resources (js, css, img... files) from the `node_modules` folder to `wwwroot/libs` folder. See the *Mapping The Library Resources* section for more. - -Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example: - -```json -{ - ... - "dependencies": { - "@abp/bootstrap": "^1.0.0" - } -} -``` - -It's suggested to depend on a standard package instead of directly depending on a third-party package. - -#### Package Installation - -After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies: - -```bash -yarn -``` - -Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before. - -#### Package Contribution - -If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted: - -* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package). -* It should be the **latest stable** version of the package. -* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages. -* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package. -* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created. - -See current standard packages for examples. - -### Mapping The Library Resources - -Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project. - -Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers. - -ABP CLI's `abp install-libs` command **copies resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies. - -The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options. - -#### Resource Mapping Definition File - -A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below: - -```json -module.exports = { - aliases: { - "@node_modules": "./node_modules", - "@libs": "./wwwroot/libs" - }, - clean: [ - "@libs", - "!@libs/**/foo.txt" - ], - mappings: { - - } -} -``` - -* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication. -* **clean** section is a list of folders to clean before copying the files. Glob matching and negation is enabled, so you can fine-tune what to delete and keep. The example above will clean everything inside `./wwwroot/libs`, but keep any `foo.txt` files. -* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package. - -An example mapping configuration is shown below: - -```json -mappings: { - "@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/", - "@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/", - "@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/", - "@node_modules/bootstrap-v4-rtl/dist/**/*": "@libs/bootstrap-v4-rtl/dist/" -} -``` - -#### install-libs Command - -Once you properly configure the `abp.resourcemapping.js` file, you can run the following ABP CLI command from the command line: - -````bash -abp install-libs -```` - -When you run this command, all packages will copy their own resources into the `wwwroot/libs` folder. Running `abp install-libs` is only necessary if you make a change in your dependencies in the **package.json** file. - -#### See Also - -* [Bundling & Minification](Bundling-Minification.md) -* [Theming](Theming.md) diff --git a/docs/en/UI/AspNetCore/Customization-User-Interface.md b/docs/en/UI/AspNetCore/Customization-User-Interface.md deleted file mode 100644 index 991f1247b8..0000000000 --- a/docs/en/UI/AspNetCore/Customization-User-Interface.md +++ /dev/null @@ -1,477 +0,0 @@ -# ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide - -This document explains how to override the user interface of a depended [application module](../../Modules/Index.md) or [theme](Theming.md) for ASP.NET Core MVC / Razor Page applications. - -## Overriding a Page - -This section covers the [Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/) development, which is the recommended approach to create server rendered user interface for ASP.NET Core. Pre-built modules typically uses the Razor Pages approach instead of the classic MVC pattern (next sections will cover the MVC pattern too). - -You typically have three kind of override requirement for a page: - -* Overriding **only the Page Model** (C#) side to perform additional logic without changing the page UI. -* Overriding **only the Razor Page** (.chtml file) to change the UI without changing the c# behind the page. -* **Completely overriding** the page. - -### Overriding a Page Model (C#) - -````csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Identity; -using Volo.Abp.Identity.Web.Pages.Identity.Users; - -namespace Acme.BookStore.Web.Pages.Identity.Users -{ - [Dependency(ReplaceServices = true)] - [ExposeServices(typeof(EditModalModel))] - public class MyEditModalModel : EditModalModel - { - public MyEditModalModel( - IIdentityUserAppService identityUserAppService, - IIdentityRoleAppService identityRoleAppService - ) : base( - identityUserAppService, - identityRoleAppService) - { - } - - public async override Task OnPostAsync() - { - //TODO: Additional logic - await base.OnPostAsync(); - //TODO: Additional logic - } - } -} -```` - -* This class inherits from and replaces the `EditModalModel` for the users and overrides the `OnPostAsync` method to perform additional logic before and after the underlying code. -* It uses `ExposeServices` and `Dependency` attributes to replace the class. - -### Overriding a Razor Page (.CSHTML) - -Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through creating the same `.cshtml` file under the same path. - -#### Example - -This example overrides the **login page** UI defined by the [Account Module](../../Modules/Account.md). - -The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path: - -![overriding-login-cshtml](../../images/overriding-login-cshtml.png) - -You typically want to copy the original `.cshtml` file of the module, then make the necessary changes. You can find the original file [here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml). Do not copy the `Login.cshtml.cs` file which is the code behind file for the razor page and we don't want to override it yet (see the next section). - -> Don't forget to add [_ViewImports.cshtml](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/layout?view=aspnetcore-7.0#importing-shared-directives) if the page you want to override contains [ABP Tag Helpers](../AspNetCore/Tag-Helpers/Index.md). - -````csharp -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI -@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap -@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling -```` - -That's all, you can change the file content however you like. - -### Completely Overriding a Razor Page - -You may want to completely override a page; the razor and the c# file related to the page. - -In such a case; - -1. Override the C# page model class just like described above, but don't replace the existing page model class. -2. Override the Razor Page just described above, but also change the @model directive to point your new page model. - -#### Example - -This example overrides the **login page** defined by the [Account Module](../../Modules/Account.md). - -Create a page model class deriving from the ` LoginModel ` (defined in the ` Volo.Abp.Account.Web.Pages.Account ` namespace): - -````csharp -public class MyLoginModel : LoginModel -{ - public MyLoginModel( - IAuthenticationSchemeProvider schemeProvider, - IOptions accountOptions - ) : base( - schemeProvider, - accountOptions) - { - - } - - public override Task OnPostAsync(string action) - { - //TODO: Add logic - return base.OnPostAsync(action); - } - - //TODO: Add new methods and properties... -} -```` - -You can override any method or add new properties/methods if needed. - -> Notice that we didn't use `[Dependency(ReplaceServices = true)]` or `[ExposeServices(typeof(LoginModel))]` since we don't want to replace the existing class in the dependency injection, we define a new one. - -Copy `Login.cshtml` file into your solution as just described above. Change the **@model** directive to point to the `MyLoginModel`: - -````xml -@page -... -@model Acme.BookStore.Web.Pages.Account.MyLoginModel -... -```` - -That's all! Make any change in the view and run your application. - -#### Replacing Page Model Without Inheritance - -You don't have to inherit from the original page model class (like done in the previous example). Instead, you can completely **re-implement** the page yourself. In this case, just derive from `PageModel`, `AbpPageModel` or any suitable base class you need. - -## Overriding a View Component - -The ABP Framework, pre-built themes and modules define some **re-usable view components**. These view components can be replaced just like a page described above. - -### Example - -The screenshot below was taken from the [Basic Theme](Basic-Theme.md) comes with the application startup template. - -![bookstore-brand-area-highlighted](../../images/bookstore-brand-area-highlighted.png) - -The [Basic Theme](Basic-Theme.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it. - -First, create your logo and place under a folder in your web application. We used `wwwroot/logos/bookstore-logo.png` path. Then copy the Brand component's view ([from here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml)) from the basic theme files under the `Themes/Basic/Components/Brand` folder. The result should be similar the picture below: - -![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png) - -Then change the `Default.cshtml` as you like. Example content can be like that: - -````xml - - - -```` - -Now, you can run the application to see the result: - -![bookstore-added-logo](../../images/bookstore-added-logo.png) - -If you need, you can also replace [the code behind c# class](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) of the component just using the dependency injection system. - -### Overriding the Theme - -Just as explained above, you can replace any component, layout or c# class of the used theme. See the [theming document](Theming.md) for more information on the theming system. - -## Overriding Static Resources - -Overriding a static embedded resource (like JavaScript, Css or image files) of a module is pretty easy. Just place a file in the same path in your solution and let the [Virtual File System](../../Virtual-File-System.md) to handle it. - -## Manipulating the Bundles - -The [Bundling & Minification](Bundling-Minification.md) system provides an **extensible and dynamic** system to create **script** and **style** bundles. It allows you to extend and manipulate the existing bundles. - -### Example: Add a Global CSS File - -For example, ABP Framework defines a **global style bundle** which is added to every page (actually, added to the layout by the themes). Let's add a **custom style file** to the end of the bundle files, so we can override any global style. - -First, create a CSS file and locate it in a folder inside the `wwwroot`: - -![bookstore-global-css-file](../../images/bookstore-global-css-file.png) - -Define some custom CSS rules inside the file. Example: - -````css -.card-title { - color: orange; - font-size: 2em; - text-decoration: underline; -} - -.btn-primary { - background-color: red; -} -```` - -Then add this file to the standard global style bundle in the `ConfigureServices` method of your [module](../../Module-Development-Basics.md): - -````csharp -Configure(options => -{ - options.StyleBundles.Configure( - StandardBundles.Styles.Global, //The bundle name! - bundleConfiguration => - { - bundleConfiguration.AddFiles("/styles/my-global-styles.css"); - } - ); -}); -```` - -#### The Global Script Bundle - -Just like the `StandardBundles.Styles.Global`, there is a `StandardBundles.Scripts.Global` that you can add files or manipulate the existing ones. - -### Example: Manipulate the Bundle Files - -The example above adds a new file to the bundle. You can do more if you create a **bundle contributor** class. Example: - -````csharp -public class MyGlobalStyleBundleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.Clear(); - context.Files.Add("/styles/my-global-styles.css"); - } -} -```` - -Then you can add the contributor to an existing bundle: - -````csharp -Configure(options => -{ - options.StyleBundles.Configure( - StandardBundles.Styles.Global, - bundleConfiguration => - { - bundleConfiguration.AddContributors(typeof(MyGlobalStyleBundleContributor)); - } - ); -}); -```` - -It is not a good idea to clear all CSS files. In a real world scenario, you can find and replace a specific file with your own file. - -### Example: Add a JavaScript File for a Specific Page - -The examples above works with the global bundle added to the layout. What if you want to add a CSS/JavaScript file (or replace a file) for a specific page defines inside a depended module? - -Assume that you want to run a **JavaScript code** once the user enters to the **Role Management** page of the Identity Module. - -First, create a standard JavaScript file under the `wwwroot`, `Pages` or `Views` folder (ABP support to add static resources inside these folders by default). We prefer the `Pages/Identity/Roles` folder to follow the conventions: - -![bookstore-added-role-js-file](../../images/bookstore-added-role-js-file.png) - -Content of the file is simple: - -````js -$(function() { - abp.log.info('My custom role script file has been loaded!'); -}); -```` - -Then add this file to the bundle of the role management page: - -````csharp -Configure(options => -{ - options.ScriptBundles - .Configure( - typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName, - bundleConfig => - { - bundleConfig.AddFiles("/Pages/Identity/Roles/my-role-script.js"); - }); -}); -```` - -`typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName` is the safe way to get the bundle name for the role management page. - -> Notice that not every page defines such page bundles. They define only if needed. - -In addition to adding new CSS/JavaScript file to a page, you also can replace the existing one (by defining a bundle contributor). - -## Layout Customization - -Layouts are defined by the theme ([see the theming](Theming.md)) by design. They are not included in a downloaded application solution. In this way you can easily **upgrade** the theme and get new features. You can not **directly change** the layout code in your application unless you replace it by your own layout (will be explained in the next sections). - -There are some common ways to **customize the layout** described in the next sections. - -### Menu Contributors - -There are two **standard menus** defined by the ABP Framework: - -![bookstore-menus-highlighted](../../images/bookstore-menus-highlighted.png) - -* `StandardMenus.Main`: The main menu of the application. -* `StandardMenus.User`: The user menu (generally at the top right of the screen). - -Rendering the menus is a responsibility of the theme, but **menu items** are determined by the modules and your application code. Just implement the `IMenuContributor` interface and **manipulate the menu items** in the `ConfigureMenuAsync` method. - -Menu contributors are executed whenever need to render the menu. There is already a menu contributor defined in the **application startup template**, so you can take it as an example and improve if necessary. See the [navigation menu](Navigation-Menu.md) document for more. - -### Toolbar Contributors - -[Toolbar system](Toolbars.md) is used to define **toolbars** on the user interface. Modules (or your application) can add **items** to a toolbar, then the theme renders the toolbar on the **layout**. - -There is only one **standard toolbar** (named "Main" - defined as a constant: `StandardToolbars.Main`). For the basic theme, it is rendered as shown below:![bookstore-toolbar-highlighted](../../images/bookstore-toolbar-highlighted.png) - -In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here. - -#### Example: Add a Notification Icon - -In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **view component**. So, first, create a new view component in your project: - -![bookstore-notification-view-component](../../images/bookstore-notification-view-component.png) - -**NotificationViewComponent.cs** - -````csharp -public class NotificationViewComponent : AbpViewComponent -{ - public async Task InvokeAsync() - { - return View("/Pages/Shared/Components/Notification/Default.cshtml"); - } -} -```` - -**Default.cshtml** - -````xml -
    - -
    -```` - -Now, we can create a class implementing the `IToolbarContributor` interface: - -````csharp -public class MyToolbarContributor : IToolbarContributor -{ - public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) - { - if (context.Toolbar.Name == StandardToolbars.Main) - { - context.Toolbar.Items - .Insert(0, new ToolbarItem(typeof(NotificationViewComponent))); - } - - return Task.CompletedTask; - } -} -```` - -This class adds the `NotificationViewComponent` as the first item in the `Main` toolbar. - -Finally, you need to add this contributor to the `AbpToolbarOptions`, in the `ConfigureServices` of your module: - -````csharp -Configure(options => -{ - options.Contributors.Add(new MyToolbarContributor()); -}); -```` - -That's all, you will see the notification icon on the toolbar when you run the application: - -![bookstore-notification-icon-on-toolbar](../../images/bookstore-notification-icon-on-toolbar.png) - -`NotificationViewComponent` in this sample simply returns a view without any data. In real life, you probably want to **query database** (or call an HTTP API) to get notifications and pass to the view. If you need, you can add a `JavaScript` or `CSS` file to the global bundle (as described before) for your toolbar item. - -See the [toolbars document](Toolbars.md) for more about the toolbar system. - -### Layout Hooks - -[Layout Hooks](Layout-Hooks.md) system allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Then you can then add a **view component** into a hook point. - -#### Example: Add Google Analytics Script - -Assume that you need to add the Google Analytics script to the layout (that will be available for all the pages). First, **create a view component** in your project: - -![bookstore-google-analytics-view-component](../../images/bookstore-google-analytics-view-component.png) - -**GoogleAnalyticsViewComponent.cs** - -````csharp -public class GoogleAnalyticsViewComponent : AbpViewComponent -{ - public IViewComponentResult Invoke() - { - return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml"); - } -} -```` - -**Default.cshtml** - -````html - -```` - -Change `UA-xxxxxx-1` with your own code. - -You can then add this component to any of the hook points in the `ConfigureServices` of your module: - -````csharp -Configure(options => -{ - options.Add( - LayoutHooks.Head.Last, //The hook name - typeof(GoogleAnalyticsViewComponent) //The component to add - ); -}); -```` - -Now, the GA code will be inserted in the `head` of the page as the last item. You (or the modules you are using) can add multiple items to the same hook. All of them will be added to the layout. - -The configuration above adds the `GoogleAnalyticsViewComponent` to all layouts. You may want to only add to a specific layout: - -````csharp -Configure(options => -{ - options.Add( - LayoutHooks.Head.Last, - typeof(GoogleAnalyticsViewComponent), - layout: StandardLayouts.Application //Set the layout to add - ); -}); -```` - -See the layouts section below to learn more about the layout system. - -### Layouts - -Layout system allows themes to define standard, named layouts and allows any page to select a proper layout for its purpose. There are three pre-defined layouts: - -* "**Application**": The main (and the default) layout for an application. It typically contains header, menu (sidebar), footer, toolbar... etc. -* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default. -* "**Empty**": Empty and minimal layout. - -These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are the standard layout names and implemented by all the themes out of the box. - -#### Layout Location - -You can find the layout files [here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary. - -#### ITheme - -ABP Framework uses the `ITheme` service to get the layout location by the layout name. You can replace this service to dynamically select the layout location. - -#### IThemeManager - -`IThemeManager` is used to obtain the current theme and get the layout path. Any page can determine the layout of its own. Example: - -````html -@using Volo.Abp.AspNetCore.Mvc.UI.Theming -@inject IThemeManager ThemeManager -@{ - Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty); -} -```` - -This page will use the empty layout. You use `ThemeManager.CurrentTheme.GetEmptyLayout();` extension method as a shortcut. - -If you want to set the layout for all the pages under a specific folder, then write the code above in a `_ViewStart.cshtml` file under that folder. diff --git a/docs/en/UI/AspNetCore/Data-Table-Column-Extensions.md b/docs/en/UI/AspNetCore/Data-Table-Column-Extensions.md deleted file mode 100644 index fec0ac71fb..0000000000 --- a/docs/en/UI/AspNetCore/Data-Table-Column-Extensions.md +++ /dev/null @@ -1,161 +0,0 @@ -# Data Table Column Extensions for ASP.NET Core UI - -## Introduction - -Data table column extension system allows you to add a **new table column** on the user interface. The example below adds a new column with the "Social security no" title: - -![user-action-extension-click-me](../../images/table-column-extension-example.png) - -You can use the standard column options to fine control the table column. - -> Note that this is a low level API to find control the table column. If you want to show an extension property on the table, see the [module entity extension](../../Module-Entity-Extensions.md) document. - -## How to Set Up - -### Create a JavaScript File - -First, add a new JavaScript file to your solution. We added inside the `/Pages/Identity/Users` folder of the `.Web` project: - -![user-action-extension-on-solution](../../images/user-action-extension-on-solution.png) - -Here, the content of this JavaScript file: - -```js -abp.ui.extensions.tableColumns - .get('identity.user') - .addContributor(function (columnList) { - columnList.addTail({ //add as the last column - title: 'Social security no', - data: 'extraProperties.SocialSecurityNumber', - orderable: false, - render: function (data, type, row) { - if (row.extraProperties.SocialSecurityNumber) { - return '' + - row.extraProperties.SocialSecurityNumber + - ''; - } else { - return 'undefined'; - } - } - }); - }); -``` - -This example defines a custom `render` function to return a custom HTML to render in the column. - -### Add the File to the User Management Page - -Then you need to add this JavaScript file to the user management page. You can take the power of the [Bundling & Minification system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification). - -Write the following code inside the `ConfigureServices` of your module class: - -```csharp -Configure(options => -{ - options.ScriptBundles.Configure( - typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName, - bundleConfiguration => - { - bundleConfiguration.AddFiles( - "/Pages/Identity/Users/my-user-extensions.js" - ); - }); -}); -``` - -This configuration adds `my-user-extensions.js` to the user management page of the Identity Module. `typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName` is the name of the bundle in the user management page. This is a common convention used for all the ABP Commercial modules. - -### Rendering the Column - -This example assumes that you've defined a `SocialSecurityNumber` extra property using the [module entity extension](../../Module-Entity-Extensions.md) system. However; - -* You can add a new column that is related to an existing property of the user (that was not added to the table by default). Example: - -````js -abp.ui.extensions.tableColumns - .get('identity.user') - .addContributor(function (columnList) { - columnList.addTail({ - title: 'Phone confirmed?', - data: 'phoneNumberConfirmed', - render: function (data, type, row) { - if (row.phoneNumberConfirmed) { - return 'YES'; - } else { - return 'NO'; - } - } - }); - }); -```` - -* You can add a new custom column that is not related to any entity property, but a completely custom information. Example: - -````js -abp.ui.extensions.tableColumns - .get('identity.user') - .addContributor(function (columnList) { - columnList.addTail({ - title: 'Custom column', - data: {}, - orderable: false, - render: function (data) { - if (data.phoneNumber) { - return "call: " + data.phoneNumber; - } else { - return ''; - } - } - }); - }); -```` - -## API - -This section explains details of the `abp.ui.extensions.tableColumns` JavaScript API. - -### abp.ui.extensions.tableColumns.get(entityName) - -This method is used to access the table columns for an entity of a specific module. It takes one parameter: - -* **entityName**: The name of the entity defined by the related module. - -### abp.ui.extensions.tableColumns.get(entityName).columns - -The `columns` property is used to retrieve a [doubly linked list](../Common/Utils/Linked-List.md) of previously defined columns for a table. All contributors are executed in order to prepare the final column list. This is normally called by the modules to show the columns in the table. However, you can use it if you are building your own extensible UIs. - -### abp.ui.extensions.tableColumns.get(entityName).addContributor(contributeCallback [, order]) - -The `addContributor` method covers all scenarios, e.g. you want to add your column in a different position in the list, change or remove an existing column. `addContributor` has the following parameters: - -* **contributeCallback**: A callback function that is called whenever the column list should be created. You can freely modify the column list inside this callback method. -* **order** (optional): The order of the callback in the callback list. Your callback is added to the end of the list (so, you have opportunity to modify columns added by the previous contributors). You can set it `0` to add your contributor as the first item. - -#### Example - -```js -var myColumnDefinition = { - title: 'Custom column', - data: {}, - orderable: false, - render: function(data) { - if (data.phoneNumber) { - return "call: " + data.phoneNumber; - } else { - return ''; - } - } -}; - -abp.ui.extensions.tableColumns - .get('identity.user') - .addContributor(function (columnList) { - // Remove an item from actionList - columnList.dropHead(); - - // Add a new item to the actionList - columnList.addHead(myColumnDefinition); - }); -``` - -> `columnList` is [linked list](../Common/Utils/Linked-List.md). You can use its methods to build a list of columns however you need. diff --git a/docs/en/UI/AspNetCore/Data-Tables.md b/docs/en/UI/AspNetCore/Data-Tables.md deleted file mode 100644 index d5181b2da9..0000000000 --- a/docs/en/UI/AspNetCore/Data-Tables.md +++ /dev/null @@ -1,298 +0,0 @@ -# ASP.NET Core MVC / Razor Pages: Data Tables - -A Data Table (aka Data Grid) is a UI component to show tabular data to the users. There are a lot of Data table components/libraries and **you can use any one you like** with the ABP Framework. However, the startup templates come with the [DataTables.Net](https://datatables.net/) library as **pre-installed and configured**. ABP Framework provides adapters for this library and make it easy to use with the API endpoints. - -An example screenshot from the user management page that shows the user list in a data table: - -![datatables-example](../../images/datatables-example.png) - -## DataTables.Net Integration - -First of all, you can follow the official documentation to understand how the [DataTables.Net](https://datatables.net/) works. This section will focus on the ABP addons & integration points rather than fully covering the usage of this library. - -### A Quick Example - -You can follow the [web application development tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC) for a complete example application that uses the DataTables.Net as the Data Table. This section shows a minimalist example. - -You do nothing to add DataTables.Net library to the page since it is already added to the global [bundle](Bundling-Minification.md) by default. - -First, add an `abp-table` as shown below, with an `id`: - -````html - -```` - -> `abp-table` is a [Tag Helper](Tag-Helpers/Index.md) defined by the ABP Framework, but a simple `` tag would also work. - -Then call the `DataTable` plugin on the table selector: - -````js -var dataTable = $('#BooksTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - order: [[1, "asc"]], - searching: false, - ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), - columnDefs: [ - { - title: l('Actions'), - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - ///... - } - } - ] - } - }, - { - title: l('Name'), - data: "name" - }, - { - title: l('PublishDate'), - data: "publishDate", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(); - } - }, - { - title: l('Price'), - data: "price" - } - ] - }) -); -```` - -The example code above uses some ABP integration features those will be explained in the next sections. - -### Configuration Normalization - -`abp.libs.datatables.normalizeConfiguration` function takes a DataTables configuration and normalizes to simplify it; - -* Sets `scrollX` option to `true`, if not set. -* Sets `target` index for the column definitions. -* Sets the `language` option to [localize](../../Localization.md) the table in the current language. - -#### Default Configuration - -`normalizeConfiguration` uses the default configuration. You can change the default configuration using the `abp.libs.datatables.defaultConfigurations` object. Example: - -````js -abp.libs.datatables.defaultConfigurations.scrollX = false; -```` - -Here, the all configuration options; - -* `scrollX`: `false` by default. -* `dom`: Default value is `<"dataTable_filters"f>rt<"row dataTable_footer"<"col-auto"l><"col-auto"i><"col"p>>`. -* `language`: A function that returns the localization text using the current language. - -### AJAX Adapter - -DataTables.Net has its own expected data format while getting results of an AJAX call to the server to get the table data. They are especially related how paging and sorting parameters are sent and received. ABP Framework also offers its own conventions for the client-server [AJAX](JavaScript-API/Ajax.md) communication. - -The `abp.libs.datatables.createAjax` method (used in the example above) adapts request and response data format and perfectly works with the [Dynamic JavaScript Client Proxy](Dynamic-JavaScript-Proxies.md) system. - -This works automatically, so most of the times you don't need to know how it works. See the [DTO document](../../Data-Transfer-Objects.md) if you want to learn more about `IPagedAndSortedResultRequest`, `IPagedResult` and other standard interfaces and base DTO classes those are used in client to server communication. - -The `createAjax` also supports you to customize request parameters and handle the responses. - -**Example:** - -````csharp -var inputAction = function (requestData, dataTableSettings) { - return { - id: $('#Id').val(), - name: $('#Name').val(), - }; -}; - -var responseCallback = function(result) { - - // your custom code. - - return { - recordsTotal: result.totalCount, - recordsFiltered: result.totalCount, - data: result.items - }; -}; - -ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList, inputAction, responseCallback) -```` - -If you don't need access or modify the `requestData` or the `dataTableSettings`, you can specify a simple object as the second parameter. - -````js -ajax: abp.libs.datatables.createAjax( - acme.bookStore.books.book.getList, - { id: $('#Id').val(), name: $('#Name').val() } -) -```` - -### Row Actions - -`rowAction` is an option defined by the ABP Framework to the column definitions to show a drop down button to take actions for a row in the table. - -The example screenshot below shows the actions for each user in the user management table: - -![datatables-example](../../images/datatables-row-actions.png) - -`rowAction` is defined as a part of a column definition: - -````csharp -{ - title: l('Actions'), - rowAction: { - //TODO: CONFIGURATION - } -}, -```` - -**Example: Show *Edit* and *Delete* actions for a book row** - -````js -{ - title: l('Actions'), - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - //TODO: Open a modal to edit the book - } - }, - { - text: l('Delete'), - confirmMessage: function (data) { - return "Are you sure to delete the book " + data.record.name; - }, - action: function (data) { - acme.bookStore.books.book - .delete(data.record.id) - .then(function() { - abp.notify.info("Successfully deleted!"); - data.table.ajax.reload(); - }); - } - } - ] - } -}, -```` - -#### Action Items - -`items` is an array of action definitions. An action definition can have the following options; - -* `text`: The text (a `string`) for this action to be shown in the actions drop down. -* `action`: A `function` that is executed when the user clicks to the action. The function takes a `data` argument that has the following fields; - * `data.record`: This is the data object related to the row. You can access the data fields like `data.record.id`, `data.record.name`... etc. - * `data.table`: The DataTables instance. -* `confirmMessage`: A `function` (see the example above) that returns a message (`string`) to show a dialog to get a confirmation from the user before executing the `action`. Example confirmation dialog: - -![datatables-row-actions-confirmation](../../images/datatables-row-actions-confirmation.png) - -You can use the [localization](JavaScript-API/Localization.md) system to show a localized message. - -* `visible`: A `bool` or a `function` that returns a `bool`. If the result is `false`, then the action is not shown in the actions dropdown. This is generally combined by the [authorization](JavaScript-API/Auth.md) system to hide the action if the user has no permission to take this action. Example: - -````js -visible: abp.auth.isGranted('BookStore.Books.Delete'); -```` - -If you define a `function`, then the `function` has two arguments: `record` (the data object of the related row) and the `table` (the DataTable instance). So, you can decide to show/hide the action dynamically, based on the row data and other conditions. - -* `iconClass`: Can be used to show a font-icon, like a [Font-Awesome](https://fontawesome.com/) icon (ex: `fas fa-trash-alt`), near to the action text. Example screenshot: - -![datatables-row-actions-confirmation](../../images/datatables-row-actions-icon.png) - -* `enabled`: A `function` that returns a `bool` to disable the action. The `function` takes a `data` object with two fields: `data.record` is the data object related to the row and `data.table` is the DataTables instance. -* `displayNameHtml`: Set this to `true` is the `text` value contains HTML tags. - -There are some rules with the action items; - -* If none of the action items is visible then the actions column is not rendered. - -### Data Format - -#### The Problem - -See the *Creation Time* column in the example below: - -````js -{ - title: l('CreationTime'), - data: "creationTime", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(luxon.DateTime.DATETIME_SHORT); - } -} -```` - -The `render` is a standard DataTables option to render the column content by a custom function. This example uses the [luxon](https://moment.github.io/luxon/) library (which is installed by default) to write a human readable value of the `creationTime` in the current user's language. Example output of the column: - -![datatables-custom-render-date](../../images/datatables-custom-render-date.png) - -If you don't define the render option, then the result will be ugly and not user friendly: - -![datatables-custom-render-date](../../images/datatables-default-render-date.png) - -However, rendering a `DateTime` is almost same and repeating the same rendering logic everywhere is against to the DRY (Don't Repeat Yourself!) principle. - -#### dataFormat Option - -`dataFormat` column option specifies the data format that is used to render the column data. The same output could be accomplished using the following column definition: - -````js -{ - title: l('CreationTime'), - data: "creationTime", - dataFormat: 'datetime' -} -```` - -`dataFormat: 'datetime'` specifies the data format for this column. There are a few pre-defined `dataFormat`s: - -* `boolean`: Shows a `check` icon for `true` and `times` icon for `false` value and useful to render `bool` values. -* `date`: Shows date part of a `DateTime` value, formatted based on the current culture. -* `datetime`: Shows date & time (excluding seconds) of a `DateTime` value, formatted based on the current culture. - -### Default Renderers - -`abp.libs.datatables.defaultRenderers` option allows you to define new data formats and set renderers for them. - -**Example: Render male / female icons based on the gender** - -````js -abp.libs.datatables.defaultRenderers['gender'] = function(value) { - if (value === 'f') { - return ''; - } else { - return ''; - } -}; -```` - -Assuming that the possible values for a column data is `f` and `m`, the `gender` data format shows female/male icons instead of `f` and `m` texts. You can now set `dataFormat: 'gender'` for a column definition that has the proper data values. - -> You can write the default renderers in a single JavaScript file and add it to the [Global Script Bundle](Bundling-Minification.md), so you can reuse them in all the pages. - -## Other Data Grids - -You can use any library you like. For example, [see this article](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) to learn how to use DevExtreme Data Grid in your applications. diff --git a/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md b/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md deleted file mode 100644 index 31c2dbe7ea..0000000000 --- a/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md +++ /dev/null @@ -1,97 +0,0 @@ -# Dynamic JavaScript API Client Proxies - -It is typical to consume your HTTP APIs from your JavaScript code. To do that, you normally deal with low level AJAX calls, like $.ajax, or better [abp.ajax](JavaScript-API/Ajax.md). ABP Framework provides **a better way** to call your HTTP APIs from your JavaScript code: JavaScript API Client Proxies! - -## Static vs Dynamic JavaScript Client Proxies - -ABP provides **two types** of client proxy generation system. This document explains the **dynamic client proxies**, which generates client-side proxies on runtime. You can also see the [Static JavaScript API Client Proxies](Static-JavaScript-Proxies.md) documentation to learn how to generate proxies on development time. - -Development-time (static) client proxy generation has a **slight performance advantage** since it doesn't need to obtain the HTTP API definition on runtime. However, you should **re-generate** the client proxy code whenever you change your API endpoint definition. On the other hand, dynamic client proxies are generated on runtime and provides an **easier development experience**. - -## A Quick Example - -Assume that you have an application service defined as shown below: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Acme.BookStore.Authors -{ - public interface IAuthorAppService : IApplicationService - { - Task GetAsync(Guid id); - - Task> GetListAsync(GetAuthorListDto input); - - Task CreateAsync(CreateAuthorDto input); - - Task UpdateAsync(Guid id, UpdateAuthorDto input); - - Task DeleteAsync(Guid id); - } -} -```` - -> You can follow the [web application development tutorial](../../Tutorials/Part-1.md) to learn how to create [application services](../../Application-Services.md), expose them as [HTTP APIs](../../API/Auto-API-Controllers.md) and consume from the JavaScript code as a complete example. - -You can call any of the methods just like calling a JavaScript function. The JavaScript function has the identical function **name**, **parameters** and the **return value** with the C# method. - -**Example: Get the authors list** - -````js -acme.bookStore.authors.author.getList({ - maxResultCount: 10 -}).then(function(result){ - console.log(result.items); -}); -```` - -**Example: Delete an author** - -```js -acme.bookStore.authors.author - .delete('7245a066-5457-4941-8aa7-3004778775f0') //Get id from somewhere! - .then(function() { - abp.notify.info('Successfully deleted!'); - }); -``` - -## AJAX Details - -JavaScript client proxy functions use the [abp.ajax](JavaScript-API/Ajax.md) under the hood. So, you have the same benefits like **automatic error handling**. Also, you can fully control the AJAX call by providing the options. - -### The Return Value - -Every function returns a [Deferred object](https://api.jquery.com/category/deferred-object/). That means you can chain with `then` to get the result, `catch` to handle the error, `always` to perform an action once the operation completes (success or failed). - -### AJAX Options - -Every function gets an additional **last parameter** after your own parameters. The last parameter is called as `ajaxParams`. It is an object that overrides the AJAX options. - -**Example: Set `type` and `dataType` AJAX options** - -````js -acme.bookStore.authors.author - .delete('7245a066-5457-4941-8aa7-3004778775f0', { - type: 'POST', - dataType: 'xml' - }) - .then(function() { - abp.notify.info('Successfully deleted!'); - }); -```` - -See the [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) documentation for all the available options. - -## Service Proxy Script Endpoint - -The magic is done by the `/Abp/ServiceProxyScript` endpoint defined by the ABP Framework and automatically added to the layout. You can visit this endpoint in your application to see the client proxy function definitions. This script file is automatically generated by the ABP Framework based on the server side method definitions and the related HTTP endpoint details. - -## See Also - -* [Static JavaScript API Client Proxies](Static-JavaScript-Proxies.md) -* [Auto API Controllers](../../API/Auto-API-Controllers.md) -* [Web Application Development Tutorial](../../Tutorials/Part-1.md) \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/Entity-Action-Extensions.md b/docs/en/UI/AspNetCore/Entity-Action-Extensions.md deleted file mode 100644 index f6e865375d..0000000000 --- a/docs/en/UI/AspNetCore/Entity-Action-Extensions.md +++ /dev/null @@ -1,108 +0,0 @@ -# Entity Action Extensions for ASP.NET Core UI - -## Introduction - -Entity action extension system allows you to add a **new action** to the action menu for an entity. A **Click Me** action was added to the *User Management* page below: - -![user-action-extension-click-me](../../images/user-action-extension-click-me.png) - -You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can access to the current entity in your code. - -## How to Set Up - -In this example, we will add a "Click Me!" action and execute a JavaScript code for the user management page of the [Identity Module](../../Modules/Identity.md). - -### Create a JavaScript File - -First, add a new JavaScript file to your solution. We added inside the `/Pages/Identity/Users` folder of the `.Web` project: - -![user-action-extension-on-solution](../../images/user-action-extension-on-solution.png) - -Here, the content of this JavaScript file: - -```js -var clickMeAction = { - text: 'Click Me!', - action: function(data) { - //TODO: Write your custom code - alert(data.record.userName); - } -}; - -abp.ui.extensions.entityActions - .get('identity.user') - .addContributor(function(actionList) { - actionList.addTail(clickMeAction); - }); -``` - -In the `action` function, you can do anything you need. See the API section for a detailed usage. - -### Add the File to the User Management Page - -Then you need to add this JavaScript file to the user management page. You can take the power of the [Bundling & Minification System](Bundling-Minification.md). - -Write the following code inside the `ConfigureServices` of your module class: - -```csharp -Configure(options => -{ - options.ScriptBundles.Configure( - typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName, - bundleConfiguration => - { - bundleConfiguration.AddFiles( - "/Pages/Identity/Users/my-user-extensions.js" - ); - }); -}); -``` - -This configuration adds `my-user-extensions.js` to the user management page of the Identity Module. `typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName` is the name of the bundle in the user management page. This is a common convention used for all the ABP Commercial modules. - -That's all. Run your application to see the result. - -## API - -This section explains details of the `abp.ui.extensions.entityActions` JavaScript API. - -### abp.ui.extensions.entityActions.get(entityName) - -This method is used to access the entity actions of a specific module. It takes one parameter: - -* **entityName**: The name of the entity defined by the related module. - -### abp.ui.extensions.entityActions.get(entityName).actions - -The `actions` property is used to retrieve a [doubly linked list](../Common/Utils/Linked-List.md) of previously defined actions for an entity. All contributors are executed in order to prepare the final actions list. This is normally called by the modules to show the actions in the grid. However, you can use it if you are building your own extensible UIs. - -### abp.ui.extensions.entityActions.get(entityName).addContributor(contributeCallback) - -The `addContributor` method covers all scenarios, e.g. you want to add your action in a different position in the list, change or remove an existing action item. `addContributor` with the following parameter: - -* **contributeCallback**: A callback function that is called whenever the action list should be created. You can freely modify the action list inside this callback method. - -#### Example - -```js -var clickMe2Action = { - text: 'Click Me 2!', - icon: 'fas fa-hand-point-right', - action: function(data) { - //TODO: Write your custom code - alert(data.record.userName); - } -}; - -abp.ui.extensions.entityActions - .get('identity.user') - .addContributor(function(actionList) { - // Remove an item from actionList - actionList.dropHead(); - - // Add the new item to the actionList - actionList.addHead(clickMe2Action); - }); -``` - -> `actionList` is [linked list](../Common/Utils/Linked-List.md). You can use its methods to build a list of columns however you need. diff --git a/docs/en/UI/AspNetCore/Forms-Validation.md b/docs/en/UI/AspNetCore/Forms-Validation.md deleted file mode 100644 index 257752a517..0000000000 --- a/docs/en/UI/AspNetCore/Forms-Validation.md +++ /dev/null @@ -1,227 +0,0 @@ -# ASP.NET Core MVC / Razor Pages: Forms & Validation - -ABP Framework provides infrastructure and conventions to make easier to create forms, localize display names for the form elements and handle server & client side validation; - -* [abp-dynamic-form](Tag-Helpers/Dynamic-Forms.md) tag helper automates **creating a complete form** from a C# model class: Creates the input elements, handles localization and client side validation. -* [ABP Form tag helpers](Tag-Helpers/Form-elements.md) (`abp-input`, `abp-select`, `abp-radio`...) render **a single form element** with handling localization and client side validation. -* ABP Framework automatically **localizes the display name** of a form element without needing to add a `[DisplayName]` attribute. -* **Validation errors** are automatically localized based on the user culture. - -> This document is for the **client side validation** and it doesn't cover the server side validation. Check the [validation document](../../Validation.md) for server side validation infrastructure. - -## The Classic Way - -In a typical Bootstrap based ASP.NET Core MVC / Razor Pages UI, you [need to write](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation#client-side-validation) such a boilerplate code to create a simple form element: - -````html -
    - - - -
    -```` - -You can continue to use this approach if you need or prefer it. However, ABP Form tag helpers can produce the same output with a minimal code. - -## ABP Dynamic Forms - -[abp-dynamic-form](Tag-Helpers/Dynamic-Forms.md) tag helper completely automates the form creation. Take this model class as an example: - -```csharp -using System; -using System.ComponentModel.DataAnnotations; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; - -namespace MyProject.Web.Pages -{ - public class MovieViewModel - { - [Required] - [StringLength(256)] - public string Name { get; set; } - - [Required] - [DataType(DataType.Date)] - public DateTime ReleaseDate { get; set; } - - [Required] - [TextArea] - [StringLength(1000)] - public string Description { get; set; } - - public Genre Genre { get; set; } - - public float? Price { get; set; } - - public bool PreOrder { get; set; } - } -} -``` - -It uses the data annotation attributes to define validation rules and UI styles for the properties. `Genre`, is an `enum` in this example: - -````csharp -namespace MyProject.Web.Pages -{ - public enum Genre - { - Classic, - Action, - Fiction, - Fantasy, - Animation - } -} -```` - -In order to create the form in a razor page, create a property in your `PageModel` class: - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace MyProject.Web.Pages -{ - public class CreateMovieModel : PageModel - { - [BindProperty] - public MovieViewModel Movie { get; set; } - - public void OnGet() - { - Movie = new MovieViewModel(); - } - - public async Task OnPostAsync() - { - if (ModelState.IsValid) - { - //TODO: Save the Movie - } - } - } -} -``` - -Then you can render the form in the `.cshtml` file: - -```html -@page -@model MyProject.Web.Pages.CreateMovieModel - -

    Create a new Movie

    - - -``` - -The result is shown below: - -![abp-dynamic-form-result](../../images/abp-dynamic-form-result.png) - -See the *Localization & Validation* section below to localize the field display names and see how the validation works. - -> See [its own document](Tag-Helpers/Dynamic-Forms.md) for all options of the `abp-dynamic-form` tag helper. - -## ABP Form Tag Helpers - -`abp-dynamic-form` covers most of the scenarios and allows you to control and customize the form using the attributes. - -However, if you want to **render the form body yourself** (for example, you may want to fully control the **form layout**), you can directly use the [ABP Form Tag Helpers](Tag-Helpers/Form-elements.md). The same auto-generated form above can be created using the ABP Form Tag Helpers as shown below: - -```html -@page -@model MyProject.Web.Pages.CreateMovieModel - -

    Create a new Movie

    - -
    - - - - - - - Save - -``` - -> See the [ABP Form Tag Helpers](Tag-Helpers/Form-elements.md) document for details of these tag helpers and their options. - -## Validation & Localization - -Both of the Dynamic Form and the Form Tag Helpers **automatically validate** the input based on the data annotation attributes and shows validation error messages on the user interface. Error messages are **automatically localized** based on the current culture. - -**Example: User leaves empty a required string property** - -![abp-form-input-validation-error](../../images/abp-form-input-validation-error.png) - -The error message below is shown if the language is French: - -![abp-form-input-validation-error](../../images/abp-form-input-validation-error-french.png) - -Validation errors are already [translated](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization) a lot of languages. You can [contribute](../../Contribution/Index.md) to the translation for your own language or override the texts for your own application by following the [localization](../../Localization.md) documentation. - -## Display Name Localization - -ABP Framework uses the property name as the field name on the user interface. You typically want to [localize](../../Localization.md) this name based on the current culture. - -ABP Framework can conventionally localize the fields on the UI when you add the localization keys to the localization JSON files. - -Example: French localization for the *Name* property (add into the `fr.json` in the application): - -````js -"Name": "Nom" -```` - -Then the UI will use the given name for French language: - -![abp-form-input-validation-error](../../images/abp-form-input-validation-error-french-name.png) - -### Using the `DisplayName:` Prefix - -Directly using the property name as the localization key may be a problem if you need to use the property name for other purpose, which a different translation value. In this case, use the `DisplayName:` prefix for the localization key: - -````js -"DisplayName:Name": "Nom" -```` - -ABP prefers to use the `DisplayName:Name` key over the `Name` key if it does exists. - -### Using a Custom Localization Key - -If you need, you can use the `[DisplayName]` attribute to specify the localization key for a specific property: - -````csharp -[DisplayName("MyNameKey")] -public string Name { get; set; } -```` - -In this case, you can add an entry to the localization file using the key `MyNameKey`. - -> If you use the `[DisplayName]` but not add a corresponding entity to the localization file, then ABP Framework shows the given key as the field name, `MyNameKey` for this case. So, it provides a way to specify a hard coded display name even if you don't need to use the localization system. - -### Enum Localization - -Enum members are also automatically localized wherever possible. For example, when we added `` to the form (like we did in the *ABP Form Tag Helpers* section), ABP can automatically fill the localized names of Enum members. To enabled it, you should define the localized values in your localization JSON file. Example entries for the `Genre` Enum defined in the *ABP Form Tag Helpers* section: - -````json -"Enum:Genre.0": "Classic movie", -"Enum:Genre.1": "Action movie", -"Enum:Genre.2": "Fiction", -"Enum:Genre.3": "Fantasy", -"Enum:Genre.4": "Animation/Cartoon" -```` - -You can use one of the following syntaxes for the localization keys: - -* `Enum:.` -* `.` - -> Remember that if you don't specify values for your Enum, the values will be ordered, starting from `0`. - -> MVC tag helpers also support using Enum member names instead of values (so, you can define `"Enum:Genre.Action"` instead of `"Enum:Genre.1"`, for example), but it is not suggested. Because, when you serialize Enum properties to JSON and send to clients, default serializer uses Enum values instead of Enum names. So, the Enum name won't be available to clients, and it will be a problem if you want to use the same localization values on the client side. - -## See Also - -* [Server Side Validation](../../Validation.md) diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Ajax.md b/docs/en/UI/AspNetCore/JavaScript-API/Ajax.md deleted file mode 100644 index c100199d27..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Ajax.md +++ /dev/null @@ -1,150 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI JavaScript AJAX API - -`abp.ajax` API provides a convenient way of performing AJAX calls to the server. It internally uses JQuery's `$.ajax`, but automates some common tasks for you; - -* Automatically **handles & localize the errors** and informs the user (using the [abp.message](Message.md)). So you typically don't care about errors. -* Automatically adds **anti forgery** token to the HTTP header to satisfy CSRF protection validation on the server side. -* Automatically sets **default options** and allows to configure the defaults in a single place. -* Can **block** a UI part (or the full page) during the AJAX operation. -* Allows to fully customize any AJAX call, by using the standard `$.ajax` **options**. - -> While `abp.ajax` makes the AJAX call pretty easier, you typically will use the [Dynamic JavaScript Client Proxy](../Dynamic-JavaScript-Proxies.md) system to perform calls to your server side HTTP APIs. `abp.ajax` can be used when you need to perform low level AJAX operations. - -## Basic Usage - -`abp.ajax` accepts an options object that is accepted by the standard [$.ajax](https://api.jquery.com/jquery.ajax/#jQuery-ajax-settings). All the standard options are valid. It returns a [promise](https://api.jquery.com/category/deferred-object/) as the return value. - -**Example: Get the list of users** - -````js -abp.ajax({ - type: 'GET', - url: '/api/identity/users' -}).then(function(result){ - console.log(result); -}); -```` - -This command logs the list of users to the console, if you've **logged in** to the application and have [permission](../../../Authorization.md) for the user management page of the [Identity Module](../../../Modules/Identity.md). - -## Error Handling - -The example AJAX call above shows an **error message** if you haven't login to the application or you don't have the necessary permissions to perform this request: - -![ajax-error](../../../images/ajax-error.png) - -All kinds of errors are automatically handled by `abp.ajax`, unless you want to disable it. - -### Standard Error Response - -`abp.ajax` is compatible with the [exception handling system](../../../Exception-Handling.md) of the ABP Framework and it properly handles the standard error format returned from the server. A typical error message is a JSON as like below: - -````json -{ - "error": { - "code": "App:010042", - "message": "This topic is locked and can not add a new message", - "details": "A more detailed info about the error..." - } -} -```` - -The error message is directly shown to the user, using the `message` and `details` properties. - -### Non-Standard Error Response & HTTP Status Codes - -It also handles errors even if the standard error format was not sent by the server. This can be case if you bypass the ABP exception handling system and manually build the HTTP response on the server. In that case, **HTTP status codes** are considered. - -The following HTTP Status Codes are pre-defined; - -* **401**: Shows an error message like "*You should be authenticated (sign in) in order to perform this operation*". When the users click the OK button, they are redirected to the home page of the application to make them login again. -* **403**: Shows an error message like "*You are not allowed to perform this operation*". -* **404**: Shows an error message like "*The resource requested could not found on the server*". -* **Others**: Shows a generic error message like "*An error has occurred. Error detail not sent by server*". - -All these messages are localized based on the current user's language. - -### Manually Handling the Errors - -Since `abp.ajax` returns a promise, you can always chain a `.cactch(...)` call to register a callback that is executed if the AJAX request fails. - -**Example: Show an alert if the AJAX request fails** - -````js -abp.ajax({ - type: 'GET', - url: '/api/identity/users' -}).then(function(result){ - console.log(result); -}).catch(function(){ - alert("request failed :("); -}); -```` - -While your callback is fired, ABP still handles the error itself. If you want to disable automatic error handling, pass `abpHandleError: false` the the `abp.ajax` options. - -**Example: Disable the auto error handling** - -````js -abp.ajax({ - type: 'GET', - url: '/api/identity/users', - abpHandleError: false //DISABLE AUTO ERROR HANDLING -}).then(function(result){ - console.log(result); -}).catch(function(){ - alert("request failed :("); -}); -```` - -If you set `abpHandleError: false` and don't catch the error yourself, then the error will be hidden and the request silently fails. `abp.ajax` still logs the error to the browser console (see the *Configuration* section to override it). - -## Configuration - -`abp.ajax` has a **global configuration** that you can customize based on your requirements. - -### Default AJAX Options - -`abp.ajax.defaultOpts` object is used to configure default options used while performing an AJAX call, unless you override them. Default value of this object is shown below: - -````js -{ - dataType: 'json', - type: 'POST', - contentType: 'application/json', - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } -} -```` - -So, if you want to change the default request type, you can do it as shown below: - -````js -abp.ajax.defaultOpts.type = 'GET'; -```` - -Write this code before all of your JavaScript code. You typically want to place such a configuration into a separate JavaScript file and add it to the layout using the global [bundle](../Bundling-Minification.md). - -### Log/Show Errors - -The following functions can be overridden to customize the logging and showing the error messages: - -* `abp.ajax.logError` function logs errors using the [abp.log.error(...)](Logging.md) by default. -* `abp.ajax.showError` function shows the error message using the [abp.message.error(...)](Message.md) by default. -* `abp.ajax.handleErrorStatusCode` handles different HTTP status codes and shows different messages based on the code. -* `abp.ajax.handleAbpErrorResponse` handles the errors sent with the standard ABP error format. -* `abp.ajax.handleNonAbpErrorResponse` handles the non-standard error responses. -* `abp.ajax.handleUnAuthorizedRequest` handles responses with `401` status code and redirect users to the home page of the application. - -**Example: Override the `logError` function** - -````js -abp.ajax.logError = function(error) { - //... -} -```` - -### Other Options - -* `abp.ajax.ajaxSendHandler` function is used to intercept the AJAX requests and add antiforgery token to the HTTP header. Note that this works for all AJAX requests, even if you don't use the `abp.ajax`. \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Auth.md b/docs/en/UI/AspNetCore/JavaScript-API/Auth.md deleted file mode 100644 index b50e077ba6..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Auth.md +++ /dev/null @@ -1,23 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Auth API - -Auth API allows you to check permissions (policies) for the current user in the client side. In this way, you can conditionally show/hide UI parts or perform your client side logic based on the current permissions. - -> This document only explains the JavaScript API. See the [authorization document](../../../Authorization.md) to understand the ABP authorization & permission system. - -## Basic Usage - -`abp.auth.isGranted(...)` function is used to check if a permission/policy has granted or not: - -````js -if (abp.auth.isGranted('DeleteUsers')) { - //TODO: Delete the user -} else { - alert("You don't have permission to delete a user!"); -} -```` - -## Other Fields & Functions - -* ` abp.auth.isAnyGranted(...)`: Gets one or more permission/policy names and returns `true` if at least one of them has granted. -* `abp.auth.areAllGranted(...)`: Gets one or more permission/policy names and returns `true` if all of them of them have granted. -* `abp.auth.grantedPolicies`: This is an object where its keys are the permission/policy names. You can find the granted permission/policy names here. \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Block-Busy.md b/docs/en/UI/AspNetCore/JavaScript-API/Block-Busy.md deleted file mode 100644 index 943a651432..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Block-Busy.md +++ /dev/null @@ -1,56 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript UI Block/Busy API - -UI Block API disables (blocks) the page or a part of the page. - -## Basic Usage - -**Example: Block (disable) the complete page** - -````js -abp.ui.block(); -```` - -**Example: Block (disable) an HTML element** - -````js -abp.ui.block('#MyContainer'); -```` - -**Example: Enables the previously blocked element or page:** - -````js -abp.ui.unblock(); -```` - -## Options - -`abp.ui.block()` method can get an options object which may contain the following fields: - -* `elm`: An optional selector to find the element to be blocked (e.g. `#MyContainerId`). If not provided, the entire page is blocked. The selector can also be directly passed to the `block()` method as shown above. -* `busy`: Set to `true` to show a progress indicator on the blocked area. -* `promise`: A promise object with `always` or `finally` callbacks. This can be helpful if you want to automatically unblock the blocked area when a deferred operation completes. - -**Example: Block an element with busy indicator** - -````js -abp.ui.block({ - elm: '#MySection', - busy: true -}); -```` - -The resulting UI will look like below: - -![ui-busy](../../../images/ui-busy.png) - -## setBusy - -`abp.ui.setBusy(...)` and `abp.ui.clearBusy()` are shortcut functions if you want to use the block with `busy` option. - -**Example: Block with busy** - -````js -abp.ui.setBusy('#MySection'); -```` - -Then you can use `abp.ui.clearBusy();` to re-enable the busy area/page. \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/JavaScript-API/CurrentUser.md b/docs/en/UI/AspNetCore/JavaScript-API/CurrentUser.md deleted file mode 100644 index 038bb9dff1..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/CurrentUser.md +++ /dev/null @@ -1,49 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript CurrentUser API - -`abp.currentUser` is an object that contains information about the current user of the application. - -> This document only explains the JavaScript API. See the [CurrentUser document](../../../CurrentUser.md) to get information about the current user in the server side. - -## Authenticated User - -If the user was authenticated, this object will be something like below: - -````js -{ - isAuthenticated: true, - id: "34f1f4a7-13cc-4b91-84d1-b91c87afa95f", - tenantId: null, - userName: "john", - name: "John", - surName: "Nash", - email: "john.nash@abp.io", - emailVerified: true, - phoneNumber: null, - phoneNumberVerified: false, - roles: ["moderator","supporter"] -} -```` - -So, `abp.currentUser.userName` returns `john` in this case. - -## Anonymous User - -If the user was not authenticated, this object will be something like below: - -````js -{ - isAuthenticated: false, - id: null, - tenantId: null, - userName: null, - name: null, - surName: null, - email: null, - emailVerified: false, - phoneNumber: null, - phoneNumberVerified: false, - roles: [] -} -```` - -You can check `abp.currentUser.isAuthenticated` to understand if the use was authenticated or not. \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/JavaScript-API/DOM.md b/docs/en/UI/AspNetCore/JavaScript-API/DOM.md deleted file mode 100644 index f1adab3f52..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/DOM.md +++ /dev/null @@ -1,116 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript DOM API - -`abp.dom` (Document Object Model) provides events that you can subscribe to get notified when elements dynamically added to and removed from the page (DOM). - -It is especially helpful if you want to initialize the new loaded elements. This is generally needed when you dynamically add elements to DOM (for example, get some HTML elements via AJAX) after page initialization. - -> ABP uses the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to observe the changes made on the DOM. - -## Node Events - -### onNodeAdded - -This event is triggered when an element is added to the DOM. Example: - -````js -abp.dom.onNodeAdded(function(args){ - console.log(args.$el); -}); -```` - -`args` object has the following fields; - -* `$el`: The JQuery selection to get the new element inserted to the DOM. - -### onNodeRemoved - -This event is triggered when an element is removed from the DOM. Example: - -````js -abp.dom.onNodeRemoved(function(args){ - console.log(args.$el); -}); -```` - -`args` object has the following fields; - -* `$el`: The JQuery selection to get the element removed from the DOM. - -## Pre-Build Initializers - -ABP Framework is using the DOM events to initialize some kind of HTML elements when they are added to the DOM after than the page was already initialized. - -> Note that the same initializers also work if these elements were already included in the initial DOM. So, whether they are initially or lazy loaded, they work as expected. - -### Form Initializer - -The Form initializer (defined as `abp.dom.initializers.initializeForms`) initializes the lazy loaded forms; - -* Automatically enabled the `unobtrusive` validation on the form. -* Can automatically show a confirmation message when you submit the form. To enable this feature, just add `data-confirm` attribute with a message (like `data-confirm="Are you sure?"`) to the `form` element. -* If the `form` element has `data-ajaxForm="true"` attribute, then automatically calls the `.abpAjaxForm()` on the `form` element, to make the form posted via AJAX. - -See the [Forms & Validation](../Forms-Validation.md) document for more. - -### Script Initializer - -Script initializer (`abp.dom.initializers.initializeScript`) can execute a JavaScript code for a DOM element. - -**Example: Lazy load a component and execute some code when the element has loaded** - -Assume that you've a container to load the element inside: - -````html -
    -```` - -And this is the component that will be loaded via AJAX from the server and inserted into the container: - -````html -
    -

    Sample message

    -
    -```` - -`data-script-class="MyCustomClass"` indicates the JavaScript class that will be used to perform some logic on this element: - -`MyCustomClass` is a global object defined as shown below: - -````js -MyCustomClass = function(){ - - function initDom($el){ - $el.css('color', 'red'); - } - - return { - initDom: initDom - } -}; -```` - -`initDom` is the function that is called by the ABP Framework. The `$el` argument is the loaded HTML element as a JQuery selection. - -Finally, you can load the component inside the container after an AJAX call: - -````js -$(function () { - setTimeout(function(){ - $.get('/get-my-element').then(function(response){ - $('#LazyComponent').html(response); - }); - }, 2000); -}); -```` - -Script Initialization system is especially helpful if you don't know how and when the component will be loaded into the DOM. This can be possible if you've developed a reusable UI component in a library and you want the application developer shouldn't care how to initialize the component in different use cases. - -> Script initialization doesn't work if the component was loaded in the initial DOM. In this case, you are responsible to initialize it. - -### Other Initializers - -The following Bootstrap components and libraries are automatically initialized when they are added to the DOM: - -* Tooltip -* Popover -* Timeago diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Events.md b/docs/en/UI/AspNetCore/JavaScript-API/Events.md deleted file mode 100644 index 3425355f44..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Events.md +++ /dev/null @@ -1,88 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Events API - -`abp.event` object is a simple service that is used to publish and subscribe to global events **in the browser**. - -> This API is not related to server side local or distributed events. It works in the browser boundaries to make the UI components (code parts) communicate in a loosely coupled way. - -## Basic Usage - -### Publishing Events - -Use `abp.event.trigger` to publish events. - -**Example: Publish a *Basket Updated* event** - -````js -abp.event.trigger('basketUpdated'); -```` - -This will trigger all the subscribed callbacks. - -### Subscribing to the Events - -Use `abp.event.on` to subscribe to events. - -**Example: Consume the *Basket Updated* event** - -````js -abp.event.on('basketUpdated', function() { - console.log('Handled the basketUpdated event...'); -}); -```` - -You start to get events after you subscribe to the event. - -### Unsubscribing from the Events - -If you need to unsubscribe from a pre-subscribed event, you can use the `abp.event.off(eventName, callback)` function. In this case, you have the callback as a separate function declaration. - -**Example: Subscribe & Unsubscribe** - -````js -function onBasketUpdated() { - console.log('Handled the basketUpdated event...'); -} - -//Subscribe -abp.event.on('basketUpdated', onBasketUpdated); - -//Unsubscribe -abp.event.off('basketUpdated', onBasketUpdated); -```` - -You don't get events after you unsubscribe from the event. - -## Event Arguments - -You can pass arguments (of any count) to the `trigger` method and get them in the subscription callback. - -**Example: Add the basket as the event argument** - -````js -//Subscribe to the event -abp.event.on('basketUpdated', function(basket) { - console.log('The new basket object: '); - console.log(basket); -}); - -//Trigger the event -abp.event.trigger('basketUpdated', { - items: [ - { - "productId": "123", - "count": 2 - }, - { - "productId": "832", - "count": 1 - } - ] -}); -```` - -### Multiple Arguments - -If you want to pass multiple arguments, you can pass like `abp.event.on('basketUpdated', arg0, arg1, agr2)`. Then you can add the same argument list to the callback function on the subscriber side. - -> **Tip:** Alternatively, you can send a single object that has a separate field for each argument. This makes easier to extend/change the event arguments in the future without breaking the subscribers. - diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Features.md b/docs/en/UI/AspNetCore/JavaScript-API/Features.md deleted file mode 100644 index 8d58347f49..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Features.md +++ /dev/null @@ -1,29 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Features API - -`abp.features` API allows you to check features or get the values of the features on the client side. You can read the current value of a feature in the client side only if it is allowed by the feature definition (on the server side). - -> This document only explains the JavaScript API. See the [Features](../../../Features.md) document to understand the ABP Features system. - -## Basic Usage - -````js -//Gets a value as string. -var value = abp.features.get('ExportingToExcel'); - -//Check the feature is enabled -var enabled = abp.features.isEnabled('ExportingToExcel.Enabled'); -```` - -## All Values - -`abp.features.values` can be used to access to the all feature values. - -An example value of this object is shown below: - -````js -{ - Identity.TwoFactor: "Optional", - ExportingToExcel.Enabled: "true", - ... -} -```` \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/JavaScript-API/GlobalFeatures.md b/docs/en/UI/AspNetCore/JavaScript-API/GlobalFeatures.md deleted file mode 100644 index 7b451addcd..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/GlobalFeatures.md +++ /dev/null @@ -1,24 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Global Features API - -`abp.globalFeatures` API allows you to get the enabled features of the [Global Features](../../../Global-Features.md) in the client side. - -> This document only explains the JavaScript API. See the [Global Features](../../../Global-Features.md) document to understand the ABP Global Features system. - -## Usage - -````js -//Gets all enabled global features. -> abp.globalFeatures.enabledFeatures - -[ 'Shopping.Payment', 'Ecommerce.Subscription' ] - - -//Check the global feature is enabled -> abp.globalFeatures.isEnabled('Ecommerce.Subscription') - -true - -> abp.globalFeatures.isEnabled('My.Subscription') - -false -```` diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Index.md b/docs/en/UI/AspNetCore/JavaScript-API/Index.md deleted file mode 100644 index 21a3eeed93..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Index.md +++ /dev/null @@ -1,20 +0,0 @@ -# JavaScript API - -ABP provides a set of JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform common application requirements easily in the client side and integrate to the server side. - -## APIs - -* [AJAX](Ajax.md) -* [Auth](Auth.md) -* [CurrentUser](CurrentUser.md) -* [DOM](DOM.md) -* [Events](Events.md) -* [Features](Features.md) -* [Global Features](GlobalFeatures.md) -* [Localization](Localization.md) -* [Logging](Logging.md) -* [ResourceLoader](ResourceLoader.md) -* [Settings](Settings.md) -* [UI Block/Busy](Block-Busy.md) -* [UI Message](Message.md) -* [UI Notification](Notify.md) \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Localization.md b/docs/en/UI/AspNetCore/JavaScript-API/Localization.md deleted file mode 100644 index 4932dc7696..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Localization.md +++ /dev/null @@ -1,146 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Localization API - -Localization API allows you to reuse the server side localization resources in the client side. - -> This document only explains the JavaScript API. See the [localization document](../../../Localization.md) to understand the ABP localization system. - -## Basic Usage - -`abp.localization.getResource(...)` function is used to get a localization resource: - -````js -var testResource = abp.localization.getResource('Test'); -```` - -Then you can localize a string based on this resource: - -````js -var str = testResource('HelloWorld'); -```` - -`abp.localization.localize(...)` function is a shortcut where you can both specify the text name and the resource name: - -````js -var str = abp.localization.localize('HelloWorld', 'Test'); -```` - -`HelloWorld` is the text to localize, where `Test` is the localization resource name here. - -### Fallback Logic - -If given texts was not localized, localization method returns the given key as the localization result. - -### Default Localization Resource - -If you don't specify the localization resource name, it uses the **default localization resource** defined on the `AbpLocalizationOptions` (see the [localization document](../../../Localization.md)). - -**Example: Using the default localization resource** - -````js -var str = abp.localization.localize('HelloWorld'); //uses the default resource -```` - -### Format Arguments - -If your localized string contains arguments, like `Hello {0}, welcome!`, you can pass arguments to the localization methods. Examples: - -````js -var testSource = abp.localization.getResource('Test'); -var str1 = testSource('HelloWelcomeMessage', 'John'); -var str2 = abp.localization.localize('HelloWelcomeMessage', 'Test', 'John'); -```` - -Assuming the `HelloWelcomeMessage` is localized as `Hello {0}, welcome!`, both of the samples above produce the output `Hello John, welcome!`. - -## Other Properties & Methods - -### abp.localization.resources - -`abp.localization.resources` property stores all the localization resources, keys and their values. - -### abp.localization.isLocalized - -Returns a boolean indicating that if the given text was localized or not. - -**Example** - -````js -abp.localization.isLocalized('ProductName', 'MyResource'); -```` - -Returns `true` if the `ProductName` text was localized for the `MyResource` resource. Otherwise, returns `false`. You can leave the resource name empty to use the default localization resource. - -### abp.localization.defaultResourceName - -`abp.localization.defaultResourceName` can be set to change the default localization resource. You normally don't set this since the ABP Framework automatically sets is based on the server side configuration. - -### abp.localization.currentCulture - -`abp.localization.currentCulture` returns an object to get information about the **currently selected language**. - -An example value of this object is shown below: - -````js -{ - "displayName": "English", - "englishName": "English", - "threeLetterIsoLanguageName": "eng", - "twoLetterIsoLanguageName": "en", - "isRightToLeft": false, - "cultureName": "en", - "name": "en", - "nativeName": "English", - "dateTimeFormat": { - "calendarAlgorithmType": "SolarCalendar", - "dateTimeFormatLong": "dddd, MMMM d, yyyy", - "shortDatePattern": "M/d/yyyy", - "fullDateTimePattern": "dddd, MMMM d, yyyy h:mm:ss tt", - "dateSeparator": "/", - "shortTimePattern": "h:mm tt", - "longTimePattern": "h:mm:ss tt" - } -} -```` - -### abp.localization.languages - -Used to get list of all **available languages** in the application. An example value of this object is shown below: - -````js -[ - { - "cultureName": "en", - "uiCultureName": "en", - "displayName": "English", - "flagIcon": null - }, - { - "cultureName": "fr", - "uiCultureName": "fr", - "displayName": "Français", - "flagIcon": null - }, - { - "cultureName": "pt-BR", - "uiCultureName": "pt-BR", - "displayName": "Português", - "flagIcon": null - }, - { - "cultureName": "tr", - "uiCultureName": "tr", - "displayName": "Türkçe", - "flagIcon": null - }, - { - "cultureName": "zh-Hans", - "uiCultureName": "zh-Hans", - "displayName": "简体中文", - "flagIcon": null - } -] -```` - -## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/localization) diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Logging.md b/docs/en/UI/AspNetCore/JavaScript-API/Logging.md deleted file mode 100644 index a46203b019..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Logging.md +++ /dev/null @@ -1,50 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Logging API - -`abp.log` API is used to write simple logs in the client side. - -> The logs are written to console, using the `console.log`, by default. - -> This document is for simple client side logging. See the [Logging](../../../Logging.md) document for server side logging system. - -## Basic Usage - -Use one of the `abp.log.xxx(...)` methods based on the severity of your log message. - -````js -abp.log.debug("Some debug log here..."); //Logging a simple debug message -abp.log.info({ name: "john", age: 42 }); //Logging an object as an information log -abp.log.warn("A warning message"); //Logging a warning message -abp.log.error('An error happens...'); //Error message -abp.log.fatal('Network connection has gone away!'); //Fatal error -```` - -## Log Levels - -There are 5 levels for a log message: - -* DEBUG = 1 -* INFO = 2 -* WARN = 3 -* ERROR = 4 -* FATAL = 5 - -These are defined in the `abp.log.levels` object (like `abp.log.levels.WARN`). - -### Changing the Current Log Level - -You can control the log level as shown below: - -````js -abp.log.level = abp.log.levels.WARN; -```` - -Default log level is `DEBUG`. - -### Logging with Specifying the Level - -Instead of calling `abp.log.info(...)` function, you can use the `abp.log.log` by specifying the log level as a parameter: - -````js -abp.log.log("log message...", abp.log.levels.INFO); -```` - diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Message.md b/docs/en/UI/AspNetCore/JavaScript-API/Message.md deleted file mode 100644 index 9bfb20f3c2..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Message.md +++ /dev/null @@ -1,128 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Message API - -Message API is used to show nice looking messages to the user as a blocking dialog. Message API is an abstraction provided by the ABP Framework and implemented using the [SweetAlert](https://sweetalert.js.org/) library by default. - -## Quick Example - -Use `abp.message.success(...)` function to show a success message: - -````js -abp.message.success('Your changes have been successfully saved!', 'Congratulations'); -```` - -It will show a dialog on the UI: - -![js-message-success](../../../images/js-message-success.png) - -## Informative Messages - -There are four types of informative message functions: - -* `abp.message.info(...)` -* `abp.message.success(...)` -* `abp.message.warn(...)` -* `abp.message.error(...)` - -All these methods get two parameters: - -* `message`: The message (`string`) to be shown. -* `title`: An optional (`string`) title. - -**Example: Show an error message** - -````js -abp.message.error('Your credit card number is not valid!'); -```` - -![js-message-error](../../../images/js-message-error.png) - -## Confirmation Message - -`abp.message.confirm(...)` function can be used to get a confirmation from the user. - -**Example** - -Use the following code to get a confirmation result from the user: - -````js -abp.message.confirm('Are you sure to delete the "admin" role?') -.then(function(confirmed){ - if(confirmed){ - console.log('TODO: deleting the role...'); - } -}); -```` - -The resulting UI will be like shown below: - -![js-message-confirm](../../../images/js-message-confirm.png) - -If user has clicked the `Yes` button, the `confirmed` argument in the `then` callback function will be `true`. - -> "*Are you sure?*" is the default title (localized based on the current language) and you can override it. - -### The Return Value - -The return value of the `abp.message.confirm(...)` function is a promise, so you can chain a `then` callback as shown above. - -### Parameters - -`abp.message.confirm(...)` function has the following parameters: - -* `message`: A message (string) to show to the user. -* `titleOrCallback` (optional): A title or a callback function. If you supply a string, it is shown as the title. If you supply a callback function (that gets a `bool` parameter) then it's called with the result. -* `callback` (optional): If you've passes a title to the second parameter, you can pass your callback function as the 3rd parameter. - -Passing a callback function is an alternative to the `then` callback shown above. - -**Example: Providing all the parameters and getting result with the callback function** - -````js -abp.message.confirm( - 'Are you sure to delete the "admin" role?', - 'Be careful!', - function(confirmed){ - if(confirmed){ - console.log('TODO: deleting the role...'); - } - }); -```` - -## SweetAlert Configuration - -The Message API is implemented using the [SweetAlert](https://sweetalert.js.org/) library by default. If you want to change its configuration, you can set the options in the `abp.libs.sweetAlert.config` object. The default configuration object is shown below: - -````js -{ - 'default': { - }, - info: { - icon: 'info' - }, - success: { - icon: 'success' - }, - warn: { - icon: 'warning' - }, - error: { - icon: 'error' - }, - confirm: { - icon: 'warning', - title: 'Are you sure?', - buttons: ['Cancel', 'Yes'] - } -} -```` - -> "Are you sure?", "Cancel" and "Yes" texts are automatically localized based on the current language. - -So, if you want to set the `warn` icon, you can set it like: - -````js -abp.libs.sweetAlert.config.warn.icon = 'error'; -```` - -See the [SweetAlert document](https://sweetalert.js.org/) for all the configuration options. - diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Notify.md b/docs/en/UI/AspNetCore/JavaScript-API/Notify.md deleted file mode 100644 index 371aa8d4b9..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Notify.md +++ /dev/null @@ -1,45 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Notify API - -Notify API is used to show toast style, auto disappearing UI notifications to the end user. It is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default. - -## Quick Example - -Use `abp.notify.success(...)` function to show a success message: - -````js -abp.notify.success( - 'The product "Acme Atom Re-Arranger" has been successfully deleted.', - 'Deleted the Product' -); -```` - -A notification message is shown at the bottom right of the page: - -![js-message-success](../../../images/js-notify-success.png) - -## Notification Types - -There are four types of pre-defined notifications; - -* `abp.notify.success(...)` -* `abp.notify.info(...)` -* `abp.notify.warn(...)` -* `abp.notify.error(...)` - -All of the methods above gets the following parameters; - -* `message`: A message (`string`) to show to the user. -* `title`: An optional title (`string`). -* `options`: Additional options to be passed to the underlying library, to the Toastr by default. - -## Toastr Configuration - -The notification API is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default. You can see its own configuration options. - -**Example: Show toast messages on the top right of the page** - -````js -toastr.options.positionClass = 'toast-top-right'; -```` - -> ABP sets this option to `toast-bottom-right` by default. You can override it just as shown above. \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/JavaScript-API/ResourceLoader.md b/docs/en/UI/AspNetCore/JavaScript-API/ResourceLoader.md deleted file mode 100644 index 10b8c36f59..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/ResourceLoader.md +++ /dev/null @@ -1,40 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Resource Loader API - -`abp.ResourceLoader` is a service that can load a JavaScript or CSS file on demand. It guarantees to load the file only once even if you request multiple times. - -## Loading Script Files - -`abp.ResourceLoader.loadScript(...)` function **loads** a JavaScript file from the server and **executes** it. - -**Example: Load a JavaScript file** - -````js -abp.ResourceLoader.loadScript('/Pages/my-script.js'); -```` - -### Parameters - -`loadScript` function can get three parameters; - -* `url` (required, `string`): The URL of the script file to be loaded. -* `loadCallback` (optional, `function`): A callback function that is called once the script is loaded & executed. In this callback you can safely use the code in the script file. This callback is called even if the file was loaded before. -* `failCallback` (optional, `function`): A callback function that is called if loading the script fails. - -**Example: Provide the `loadCallback` argument** - -````js -abp.ResourceLoader.loadScript('/Pages/my-script.js', function() { - console.log('successfully loaded :)'); -}); -```` - -## Loading Style Files - -`abp.ResourceLoader.loadStyle(...)` function adds a `link` element to the `head` of the document for the given URL, so the CSS file is automatically loaded by the browser. - -**Example: Load a CSS file** - -````js -abp.ResourceLoader.loadStyle('/Pages/my-styles.css'); -```` - diff --git a/docs/en/UI/AspNetCore/JavaScript-API/Settings.md b/docs/en/UI/AspNetCore/JavaScript-API/Settings.md deleted file mode 100644 index cf96cecdd0..0000000000 --- a/docs/en/UI/AspNetCore/JavaScript-API/Settings.md +++ /dev/null @@ -1,33 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: JavaScript Setting API - -Localization API allows you to get the values of the settings on the client side. You can read the current value of a setting in the client side only if it is allowed by the setting definition (on the server side). - -> This document only explains the JavaScript API. See the [settings document](../../../Settings.md) to understand the ABP setting system. - -## Basic Usage - -````js -//Gets a value as string. -var language = abp.setting.get('Abp.Localization.DefaultLanguage'); - -//Gets an integer value. -var requiredLength = abp.setting.getInt('Abp.Identity.Password.RequiredLength'); - -//Gets a boolean value. -var requireDigit = abp.setting.getBoolean('Abp.Identity.Password.RequireDigit'); -```` - -## All Values - -`abp.setting.values` can be used to obtain all the setting values as an object where the object properties are setting names and property values are the setting values. - -An example value of this object is shown below: - -````js -{ - Abp.Localization.DefaultLanguage: "en", - Abp.Timing.TimeZone: "UTC", - ... -} -```` - diff --git a/docs/en/UI/AspNetCore/Layout-Hooks.md b/docs/en/UI/AspNetCore/Layout-Hooks.md deleted file mode 100644 index 1c2df5e7be..0000000000 --- a/docs/en/UI/AspNetCore/Layout-Hooks.md +++ /dev/null @@ -1,105 +0,0 @@ -# ASP.NET Core MVC / Razor Pages: Layout Hooks - -ABP Framework theming system places the page layout into the [theme](Theming.md) NuGet packages. That means the final application doesn't include a `Layout.cshtml`, so you can't directly change the layout code to customize it. - -You copy the theme code into your solution. In this case you are completely free to customize it. However, then you won't be able to get automatic updates of the theme (by upgrading the theme NuGet package). - -ABP Framework provides different ways of [customizing the UI](Customization-User-Interface.md). - -The **Layout Hook System** allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Finally, you can add a **view component** into a hook point. - -## Example: Add Google Analytics Script - -Assume that you need to add the Google Analytics script to the layout (that will be available for all the pages). First, **create a view component** in your project: - -![bookstore-google-analytics-view-component](../../images/bookstore-google-analytics-view-component.png) - -**NotificationViewComponent.cs** - -````csharp -public class GoogleAnalyticsViewComponent : AbpViewComponent -{ - public IViewComponentResult Invoke() - { - return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml"); - } -} -```` - -**Default.cshtml** - -````html - -```` - -Change `UA-xxxxxx-1` with your own code. - -You can then add this component to any of the hook points in the `ConfigureServices` of your module: - -````csharp -Configure(options => -{ - options.Add( - LayoutHooks.Head.Last, //The hook name - typeof(GoogleAnalyticsViewComponent) //The component to add - ); -}); -```` - -Now, the GA code will be inserted in the `head` of the page as the last item. - -### Specifying the Layout - -The configuration above adds the `GoogleAnalyticsViewComponent` to all layouts. You may want to only add to a specific layout: - -````csharp -Configure(options => -{ - options.Add( - LayoutHooks.Head.Last, - typeof(GoogleAnalyticsViewComponent), - layout: StandardLayouts.Application //Set the layout to add - ); -}); -```` - -See the *Layouts* section below to learn more about the layout system. - -## Layout Hook Points - -There are some pre-defined layout hook points. The `LayoutHooks.Head.Last` used above was one of them. The standard hook points are; - -* `LayoutHooks.Head.First`: Used to add a component as the first item in the HTML head tag. -* `LayoutHooks.Head.Last`: Used to add a component as the last item in the HTML head tag. -* `LayoutHooks.Body.First`: Used to add a component as the first item in the HTML body tag. -* `LayoutHooks.Body.Last`: Used to add a component as the last item in the HTML body tag. -* `LayoutHooks.PageContent.First`: Used to add a component just before the page content (the `@RenderBody()` in the layout). -* `LayoutHooks.PageContent.Last`: Used to add a component just after the page content (the `@RenderBody()` in the layout). - -> You (or the modules you are using) can add **multiple items to the same hook point**. All of them will be added to the layout by the order they were added. - -## Layouts - -Layout system allows themes to define standard, named layouts and allows any page to select a proper layout for its purpose. There are three pre-defined layouts: - -* "**Application**": The main (and the default) layout for an application. It typically contains header, menu (sidebar), footer, toolbar... etc. -* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default. -* "**Empty**": Empty and minimal layout. - -These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are the standard layout names and implemented by all the themes out of the box. - -### Layout Location - -You can find the layout files [here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary. - -## See Also - -* [Customizing the User Interface](Customization-User-Interface.md) \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/Modals.md b/docs/en/UI/AspNetCore/Modals.md deleted file mode 100644 index 95eaf2e404..0000000000 --- a/docs/en/UI/AspNetCore/Modals.md +++ /dev/null @@ -1,484 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: Modals - -While you can continue to use the standard [Bootstrap way](https://getbootstrap.com/docs/4.5/components/modal/) to create, open and manage modals in your applications, ABP Framework provides a **flexible** way to manage modals by **automating common tasks** for you. - -**Example: A modal dialog to create a new role entity** - -![modal-manager-example-modal](../../images/modal-manager-example-modal.png) - -ABP Framework provides the following benefits for such a modal with a form inside it; - -* **Lazy loads** the modal HTML into the page and **removes** it from the DOM once its closed. This makes easy to consume a reusable modal dialog. Also, every time you open the modal, it will be a fresh new modal, so you don't have to deal with resetting the modal content. -* **Auto-focuses** the first input of the form once the modal has been opened. You can also specify it using a `function` or `jquery selector`. -* Automatically determines the **form** inside a modal and posts the form via **AJAX** instead of normal page post. -* Automatically checks if the form inside the modal **has changed, but not saved**. It warns the user in this case. -* Automatically **disables the modal buttons** (save & cancel) until the AJAX operation completes. -* Makes it easy to register a **JavaScript object that is initialized** once the modal has loaded. - -So, it makes you write less code when you deal with the modals, especially the modals with a form inside. - -## Basic Usage - -### Creating a Modal as a Razor Page - -To demonstrate the usage, we are creating a simple Razor Page, named `ProductInfoModal.cshtml`, under the `/Pages/Products` folder: - -![modal-page-on-rider](../../images/modal-page-on-rider.png) - -**ProductInfoModal.cshtml Content:** - -````html -@page -@model MyProject.Web.Pages.Products.ProductInfoModalModel -@{ - Layout = null; -} - - - -

    @Model.ProductName

    -
    - -
    -

    - @Model.ProductDescription -

    -

    - Reference: https://acme.com/catalog/ -

    -
    - -
    -```` - -* This page sets the `Layout` to `null` since we will show this as a modal. So, no need to wrap with a layout. -* It uses [abp-modal tag helper](Tag-Helpers/Modals.md) to simplify creating the modal HTML code. You can use the standard Bootstrap modal code if you prefer it. - -**ProductInfoModalModel.cshtml.cs Content:** - -```csharp -using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; - -namespace MyProject.Web.Pages.Products -{ - public class ProductInfoModalModel : AbpPageModel - { - public string ProductName { get; set; } - - public string ProductDescription { get; set; } - - public string ProductImageUrl { get; set; } - - public void OnGet() - { - ProductName = "Acme Indestructo Steel Ball"; - ProductDescription = "The ACME Indestructo Steel Ball is completely indestructible, there is nothing that can destroy it!"; - ProductImageUrl = "https://acme.com/catalog/acmeindestructo.jpg"; - } - } -} -``` - -You can surely get the product info from a database or API. We are setting the properties hard-coded for the sake of simplicity, - -### Defining the Modal Manager - -Once you have a modal, you can open it in any page using some simple **JavaScript** code. - -First, create an `abp.ModalManager` object by setting the `viewUrl`, in the JavaScript file of the page that will use the modal: - -````js -var productInfoModal = new abp.ModalManager({ - viewUrl: '/Products/ProductInfoModal' -}); -```` - -> If you only need to specify the `viewUrl`, you can directly pass it to the `ModalManager` constructor, as a shortcut. Example: `new abp.ModalManager('/Products/ProductInfoModal');` - -### Opening the Modal - -Then open the modal whenever you need: - -````js -productInfoModal.open(); -```` - -You typically want to open the modal when something happens; For example, when the user clicks a button: - -````js -$('#OpenProductInfoModal').click(function(){ - productInfoModal.open(); -}); -```` - -The resulting modal will be like that: - -![modal-example-product-info](../../images/modal-example-product-info.png) - -#### Opening the Modal with Arguments - -When you call the `open()` method, `ModalManager` loads the modal HTML by requesting it from the `viewUrl`. You can pass some **query string parameters** to this URL when you open the modal. - -**Example: Pass the product id while opening the modal** - -````js -productInfoModal.open({ - productId: 42 -}); -```` - -You can add a `productId` parameter to the get method: - -````csharp -using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; - -namespace MyProject.Web.Pages.Products -{ - public class ProductInfoModalModel : AbpPageModel - { - //... - - public async Task OnGetAsync(int productId) //Add productId parameter - { - //TODO: Get the product with database with the given productId - //... - } - } -} -```` - -In this way, you can use the `productId` to query the product from a data source. - -## Modals with Forms - -`abp.ModalManager` handles various common tasks (described in the introduction) when you want to use a form inside the modal. - -### Example Modal with a Form - -This section shows an example form to create a new product. - -#### Creating the Razor Page - -For this example, creating a new Razor Page, named `ProductCreateModal.cshtml`, under the `/Pages/Products` folder: - -![product-create-modal-page-on-rider](../../images/product-create-modal-page-on-rider.png) - -**ProductCreateModal.cshtml Content:** - -````html -@page -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model MyProject.Web.Pages.Products.ProductCreateModalModel -@{ - Layout = null; -} -
    - - - - - - - - - -
    -```` - -* The `abp-modal` has been wrapped by the `form`. This is needed to place the `Save` and the `Cancel` buttons into the form. In this way, the `Save` button acts as the `submit` button for the `form`. -* Used the [abp-input tag helpers](Tag-Helpers/Form-elements.md) to simplify to create the form elements. Otherwise, you need to write more HTML. - -**ProductCreateModal.cshtml.cs Content:** - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; - -namespace MyProject.Web.Pages.Products -{ - public class ProductCreateModalModel : AbpPageModel - { - [BindProperty] - public PoductCreationDto Product { get; set; } - - public async Task OnGetAsync() - { - //TODO: Get logic, if available - } - - public async Task OnPostAsync() - { - //TODO: Save the Product... - - return NoContent(); - } - } -} -``` - -* This is a simple `PageModal` class. The `[BindProperty]` make the form binding to the model when you post (submit) the form; The standard ASP.NET Core system. -* `OnPostAsync` returns `NoContent` (this method is defined by the base `AbpPageModel` class). Because we don't need to a return value in the client side, after the form post operation. - -**PoductCreationDto:** - -`ProductCreateModalModel` uses a `PoductCreationDto` class defined as shown below: - -````csharp -using System; -using System.ComponentModel.DataAnnotations; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; - -namespace MyProject.Web.Pages.Products -{ - public class PoductCreationDto - { - [Required] - [StringLength(128)] - public string Name { get; set; } - - [TextArea(Rows = 4)] - [StringLength(2000)] - public string Description { get; set; } - - [DataType(DataType.Date)] - public DateTime ReleaseDate { get; set; } - } -} -```` - -* `abp-input` Tag Helper can understand the data annotation attributes and uses them to shape and validate the form elements. See the [abp-input tag helpers](Tag-Helpers/Form-elements.md) document to learn more. - -#### Defining the Modal Manager - -Again, create an `abp.ModalManager` object by setting the `viewUrl`, in the JavaScript file of the page that will use the modal: - -````js -var productCreateModal = new abp.ModalManager({ - viewUrl: '/Products/ProductCreateModal' -}); -```` - -#### Opening the Modal - -Then open the modal whenever you need: - -````js -productCreateModal.open(); -```` - -You typically want to open the modal when something happens; For example, when the user clicks a button: - -````js -$('#OpenProductCreateModal').click(function(){ - productCreateModal.open(); -}); -```` - -So, the complete code will be something like that (assuming you have a `button` with `id` is `OpenProductCreateModal` on the view side): - -```js -$(function () { - - var productCreateModal = new abp.ModalManager({ - viewUrl: '/Products/ProductCreateModal' - }); - - $('#OpenProductCreateModal').click(function () { - productCreateModal.open(); - }); - -}); -``` - -The resulting modal will be like that: - -![modal-example-product-create](../../images/modal-example-product-create.png) - -#### Saving the Modal - -When you click to the `Save` button, the form is posted to the server. If the server returns a **success response**, then the `onResult` event is triggered with some arguments including the server response and the modal is automatically closed. - -An example callback that logs the arguments passed to the `onResult` method: - -````js -productCreateModal.onResult(function(){ - console.log(arguments); -}); -```` - -If the server returns a failed response, it shows the error message returned from the server and keeps the modal open. - -> See the *Modal Manager Reference* section below for other modal events. - -#### Canceling the Modal - -If you click to the Cancel button with some changes made but not saved, you get such a warning message: - -![modal-manager-cancel-warning](../../images/modal-manager-cancel-warning.png) - -If you don't want such a check & message, you can add `data-check-form-on-close="false"` attribute to your `form` element. Example: - -````html -
    -```` - -### Form Validation - -`ModalManager` automatically triggers the form validation when you click to the `Save` button or hit the `Enter` key on the form: - -![modal-manager-validation](../../images/modal-manager-validation.png) - -See the [Forms & Validation document](Forms-Validation.md) to learn more about the validation. - -## Modals with Script Files - -You may need to perform some logic for your modal. To do that, create a JavaScript file like below: - -````js -abp.modals.ProductInfo = function () { - - function initModal(modalManager, args) { - var $modal = modalManager.getModal(); - var $form = modalManager.getForm(); - - $modal.find('h3').css('color', 'red'); - - console.log('initialized the modal...'); - }; - - return { - initModal: initModal - }; -}; -```` - -* This code simply adds a `ProductInfo` class into the `abp.modals` namespace. The `ProductInfo` class exposes a single public function: `initModal`. -* `initModal` method is called by the `ModalManager` once the modal HTML is inserted to DOM and ready for the initialization logic. -* `modalManager` parameter is the `ModalManager` object related to this modal instance. So, you can use any function on it in your code. See the *ModalManager Reference* section. - -Then include this file to the page that you use the modal: - -````html - - -```` - -* We've use the `abp-script` Tag Helper here. See the [Bundling & Minification](Bundling-Minification.md) document if you want to understand it. You can use the standard `script` tag. It doesn't matter for this case. - -Finally, set the `modalClass` option while creating the `ModalManager` instance: - -````js -var productInfoModal = new abp.ModalManager({ - viewUrl: '/Products/ProductInfoModal', - modalClass: 'ProductInfo' //Matches to the abp.modals.ProductInfo -}); -```` - -### Lazy Loading the Script File - -Instead of adding the `ProductInfoModal.js` to the page you use the modal, you can configure it to lazy load the script file when the first time the modal is opened. - -Example: - -````js -var productInfoModal = new abp.ModalManager({ - viewUrl: '/Products/ProductInfoModal', - scriptUrl: '/Pages/Products/ProductInfoModal.js', //Lazy Load URL - modalClass: 'ProductInfo' -}); -```` - -* `scriptUrl` is used to set the URL to load the script file of the modal. -* In this case, you no longer need to include the `ProductInfoModal.js` to the page. It will be loaded on demand. - -#### Tip: Bundling & Minification - -While lazy loading seems cool at the beginning, it requires an additional call to the server when you first open the modal. - -Instead, you can use the [Bundling & Minification](Bundling-Minification.md) system to create a bundle (that is a single and minified file on production) for all the used script files for a page: - -````html - - - - -```` - -This is efficient if the script file is not large and frequently opened while users use the page. - -Alternatively, you can define the `abp.modals.ProductInfo` class in the page's main JavaScript file if the modal is only and always used in the same page. In this case, you don't need to another external script file at all. - -## ModalManager Reference - -### Options - -Options can be passed when you create a new `ModalManager` object: - -````js -var productInfoModal = new abp.ModalManager({ - viewUrl: '/Products/ProductInfoModal', - //...other options -}); -```` - -Here, the list of all available options; - -* `viewUrl` (required, `string`): The URL to lazy load the HTML of the modal. -* `scriptUrl` (optional, `string`): A URL to lazy load a JavaScript file. It is loaded only once, when the modal first opened. -* `modalClass` (optional, `string`): A JavaScript class defined in the `abp.modals` namespace that can be used to execute code related to the modal. -* `focusElement` (optional, `function or string`): Specifies the element that gets focus. - -### Functions - -When you create a new `ModalManager` object, you can use its functions to perform operations on the modal. Example: - -````js -var myModal = new abp.ModalManager({ - //...options -}); - -//Open the modal -myModal.open(); - -//Close the modal -myModal.close(); -```` - -Here, the list of all available functions of the `ModalManager` object; - -* `open([args])`: Opens the modal dialog. It can get an `args` object that is converted to query string while getting the `viewUrl` from the server. For example, if `args` is `{ productId: 42 }`, then the `ModalManager` passes `?productId=42` to the end of the `viewUrl` while loading the view from the server. -* `reopen()`: Opens the modal with the latest provided `args` for the `open()` method. So, it is a shortcut if you want to re-open the modal with the same `args`. -* `close()`: Closes the modal. The modal HTML is automatically removed from DOM once it has been closed. -* `getModalId()`: Gets the `id` attribute of the container that contains the view returned from the server. This is a unique id per modal and it doesn't change after you create the `ModalManager`. -* `getModal()`: Returns the modal wrapper DOM element (the HTML element with the `modal` CSS class) as a JQuery selection, so you can perform any JQuery method on it. -* `getForm()`: Returns the `form` HTML element as a JQuery selection, so you can perform any JQuery method on it. It returns `null` if the modal has no form inside it. -* `getArgs()` Gets the latest arguments object provided while opening the modal. -* `getOptions()`: Gets the options object passed to the `ModalManager` constructor. -* `setResult(...)`: Triggers the `onResult` event with the provided arguments. You can pass zero or more arguments those are directly passed to the `onResult` event. This function is generally called by the modal script to notify the page that uses the modal. - -### Events - -When you create a new `ModalManager` object, you can use its functions register to events of the modal. Examples: - -````js -var myModal = new abp.ModalManager({ - //...options -}); - -myModal.onOpen(function () { - console.log('opened the modal...'); -}); - -myModal.onClose(function () { - console.log('closed the modal...'); -}); -```` - -Here, the list of all available functions to register to events of the `ModalManager` object; - -* `onOpen(callback)`: Registers a callback function to get notified once the modal is opened. It is triggered when the modal is completely visible on the UI. -* `onClose(callback)`: Registers a callback function to get notified once the modal is closed. It is triggered when the modal is completely invisible on the UI. -* `onResult(callback)`: Registers a callback function that is triggered when the `setResult(...)` method is called. All the parameters sent to the `setResult` method is passed to the callback. diff --git a/docs/en/UI/AspNetCore/Navigation-Menu.md b/docs/en/UI/AspNetCore/Navigation-Menu.md deleted file mode 100644 index 60621f9906..0000000000 --- a/docs/en/UI/AspNetCore/Navigation-Menu.md +++ /dev/null @@ -1,288 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI: Navigation Menu - -Every application has a main menu to allow users to navigate to pages/screens of the application. Some applications may contain more than one menu in different sections of the UI. - -ABP Framework is a [modular](../../Module-Development-Basics.md) application development framework. **Every module may need to add items to the menu**. - -So, ABP Framework **provides a menu infrastructure** where; - -* The application or the modules can add items to a menu, without knowing how the menu is rendered. -* The [theme](Theming.md) properly renders the menu. - -## Adding Menu Items - -In order to add menu items (or manipulate the existing items) you need to create a class implementing the `IMenuContributor` interface. - -> The [application startup template](../../Startup-Templates/Application.md) already contains an implementation of the `IMenuContributor`. So, you can add items inside that class instead of creating a new one. - -**Example: Add a *CRM* menu item with *Customers* and *Orders* sub menu items** - -```csharp -using System.Threading.Tasks; -using MyProject.Localization; -using Volo.Abp.UI.Navigation; - -namespace MyProject.Web.Menus -{ - public class MyProjectMenuContributor : IMenuContributor - { - public async Task ConfigureMenuAsync(MenuConfigurationContext context) - { - if (context.Menu.Name == StandardMenus.Main) - { - await ConfigureMainMenuAsync(context); - } - } - - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - var l = context.GetLocalizer(); - - context.Menu.AddItem( - new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"]) - .AddItem(new ApplicationMenuItem( - name: "MyProject.Crm.Customers", - displayName: l["Menu:Customers"], - url: "/crm/customers") - ).AddItem(new ApplicationMenuItem( - name: "MyProject.Crm.Orders", - displayName: l["Menu:Orders"], - url: "/crm/orders") - ) - ); - } - } -} -``` - -* This example adds items only to the main menu (`StandardMenus.Main`: see the *Standard Menus* section below). -* It gets a `IStringLocalizer` from `context` to [localize](../../Localization.md) the display names of the menu items. -* Adds the Customers and Orders as children of the CRM menu. - -Once you create a menu contributor, you need to add it to the `AbpNavigationOptions` in the `ConfigureServices` method of your module: - -````csharp -Configure(options => -{ - options.MenuContributors.Add(new MyProjectMenuContributor()); -}); -```` - -This example uses some localization keys as display names those should be defined in the localization file: - -````json -"Menu:CRM": "CRM", -"Menu:Orders": "Orders", -"Menu:Customers": "Customers" -```` - -See the [localization document](../../Localization.md) to learn more about the localization. - -When you run the application, you will see the menu items added to the main menu: - -![nav-main-menu](../../images/nav-main-menu.png) - -> The menu is rendered by the current UI theme. So, the look of the main menu can be completely different based on your theme. - -Here, a few notes on the menu contributors; - -* ABP Framework calls the `ConfigureMenuAsync` method **whenever need to render** the menu. -* Every menu item can have **children**. So, you can add menu items with **unlimited depth** (however, your UI theme may not support unlimited depth). -* Only leaf menu items have `url`s normally. When you click to a parent menu, its sub menu is opened or closed, you don't navigate the `url` of a parent menu item. -* If a menu item has no children and has no `url` defined, then it is not rendered on the UI. This simplifies to authorize the menu items: You only authorize the child items (see the next section). If none of the children are authorized, then the parent automatically disappears. - -### Menu Item Properties - -There are more options of a menu item (the constructor of the `ApplicationMenuItem` class). Here, the list of all available options; - -* `name` (`string`, required): The **unique name** of the menu item. -* `displayName` (`string`, required): Display name/text of the menu item. You can [localize](../../Localization.md) this as shown before. -* `url` (`string`): The URL of the menu item. -* `icon` (`string`): An icon name. Free [Font Awesome](https://fontawesome.com/) icon classes are supported out of the box. Example: `fa fa-book`. You can use any CSS font icon class as long as you include the necessary CSS files to your application. -* `order` (`int`): The order of the menu item. Default value is `1000`. Items are sorted by the adding order unless you specify an order value. -* `customData` (`Dictionary`): A dictionary that allows storing custom objects that you can associate with the menu item and use it while rendering the menu item. -* `target` (`string`): Target of the menu item. Can be `null` (default), "\_*blank*", "\_*self*", "\_*parent*", "\_*top*" or a frame name for web applications. -* `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute. -* `cssClass` (`string`): Additional string classes for the menu item. -* `groupName` (`string`): Can be used to group menu items. - -### Authorization - -As seen above, a menu contributor contributes to the menu dynamically. So, you can perform any custom logic or get menu items from any source. - -One use case is the [authorization](../../Authorization.md). You typically want to add menu items by checking a permission. - -**Example: Check if the current user has a permission** - -````csharp -if (await context.IsGrantedAsync("MyPermissionName")) -{ - //...add menu items -} -```` - -For the authorization, you can use `RequirePermissions` extension method as a shortcut. It is also more performant, ABP optimizes the permission check for all the items. - -````csharp -context.Menu.AddItem( - new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"]) - .AddItem(new ApplicationMenuItem( - name: "MyProject.Crm.Customers", - displayName: l["Menu:Customers"], - url: "/crm/customers") - .RequirePermissions("MyProject.Crm.Customers") - ).AddItem(new ApplicationMenuItem( - name: "MyProject.Crm.Orders", - displayName: l["Menu:Orders"], - url: "/crm/orders") - .RequirePermissions("MyProject.Crm.Orders") - ) -); -```` - -> You can use `context.AuthorizationService` to directly access to the `IAuthorizationService`. - -### Resolving Dependencies - -`context.ServiceProvider` can be used to resolve any service dependency. - -**Example: Get a service** - -````csharp -var myService = context.ServiceProvider.GetRequiredService(); -//...use the service -```` - -> You don't need to care about releasing/disposing services. ABP Framework handles it. - -### The Administration Menu - -There is a special menu item in the menu menu that is added by the ABP Framework: The *Administration* menu. It is typically used by the pre-built admin [application modules](../../Modules/Index.md): - -![nav-main-menu-administration](../../images/nav-main-menu-administration.png) - -If you want to add menu items under the *Administration* menu item, you can use the `context.Menu.GetAdministration()` extension method: - -````csharp -context.Menu.GetAdministration().AddItem(...) -```` - -### Manipulating the Existing Menu Items - -ABP Framework executes the menu contributors by the [module dependency order](../../Module-Development-Basics.md). So, you can manipulate the menu items that your application or module (directly or indirectly) depends on. - -**Example: Set an icon for the `Users` menu item added by the [Identity Module](../../Modules/Identity.md)** - -````csharp -var userMenu = context.Menu.FindMenuItem(IdentityMenuNames.Users); -userMenu.Icon = "fa fa-users"; -```` - -> `context.Menu` gives you ability to access to all the menu items those have been added by the previous menu contributors. - -### Menu Groups - -You can define groups and associate menu items with a group. - -Example: - -```csharp -using System.Threading.Tasks; -using MyProject.Localization; -using Volo.Abp.UI.Navigation; - -namespace MyProject.Web.Menus -{ - public class MyProjectMenuContributor : IMenuContributor - { - public async Task ConfigureMenuAsync(MenuConfigurationContext context) - { - if (context.Menu.Name == StandardMenus.Main) - { - await ConfigureMainMenuAsync(context); - } - } - - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - var l = context.GetLocalizer(); - - context.Menu.AddGroup( - new ApplicationMenuGroup( - name: "Main", - displayName: l["Main"] - ) - ) - context.Menu.AddItem( - new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"], groupName: "Main") - .AddItem(new ApplicationMenuItem( - name: "MyProject.Crm.Customers", - displayName: l["Menu:Customers"], - url: "/crm/customers") - ).AddItem(new ApplicationMenuItem( - name: "MyProject.Crm.Orders", - displayName: l["Menu:Orders"], - url: "/crm/orders") - ) - ); - } - } -} -``` - -> The UI theme will decide whether to render the groups or not, and if it decides to render, the way it's rendered is up to the theme. Only the LeptonX theme implements the menu group. - -## Standard Menus - -A menu is a **named** component. An application may contain more than one menus with different, unique names. There are two pre-defined standard menus: - -* `Main`: The main menu of the application. Contains links to the page of the application. Defined as a constant: `Volo.Abp.UI.Navigation.StandardMenus.Main`. -* `User`: User profile menu. Defined as a constant: `Volo.Abp.UI.Navigation.StandardMenus.User`. - -The `Main` menu already covered above. The `User` menu is available when a user has logged in: - -![user-menu](../../images/user-menu.png) - -You can add items to the `User` menu by checking the `context.Menu.Name` as shown below: - -```csharp -if (context.Menu.Name == StandardMenus.User) -{ - //...add items -} -``` - -## IMenuManager - -`IMenuManager` is generally used by the UI [theme](Theming.md) to render the menu items on the UI. So, **you generally don't need to directly use** the `IMenuManager`. - -**Example: Getting the `Main` menu items** - -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Volo.Abp.UI.Navigation; - -namespace MyProject.Web.Pages -{ - public class IndexModel : PageModel - { - private readonly IMenuManager _menuManager; - - public IndexModel(IMenuManager menuManager) - { - _menuManager = menuManager; - } - - public async Task OnGetAsync() - { - var mainMenu = await _menuManager.GetAsync(StandardMenus.Main); - - foreach (var menuItem in mainMenu.Items) - { - //... - } - } - } -} -``` diff --git a/docs/en/UI/AspNetCore/Overall.md b/docs/en/UI/AspNetCore/Overall.md deleted file mode 100644 index 0c7e05c105..0000000000 --- a/docs/en/UI/AspNetCore/Overall.md +++ /dev/null @@ -1,160 +0,0 @@ -# ASP.NET Core MVC / Razor Pages UI - -## Introduction - -ABP Framework provides a convenient and comfortable way of creating web applications using the ASP.NET Core MVC / Razor Pages as the User Interface framework. - -> ABP doesn't offer a new/custom way of UI development. You can continue to use your current skills to create the UI. However, it offers a lot of features to make your development easier and have a more maintainable code base. - -### MVC vs Razor Pages - -ASP.NET Core provides two models for UI development: - -* **[MVC (Model-View-Controller)](https://docs.microsoft.com/en-us/aspnet/core/mvc/)** is the classic way that exists from the version 1.0. This model can be used to create UI pages/components and HTTP APIs. -* **[Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/)** was introduced with the ASP.NET Core 2.0 as a new way to create web pages. - -**ABP Framework supports both** of the MVC and the Razor Pages models. However, it is suggested to create the **UI pages with Razor Pages** approach and use the **MVC model to build HTTP APIs**. So, all the pre-build modules, samples and the documentation is based on the Razor Pages for the UI development, while you can always apply the MVC pattern to create your own pages. - -### Modularity - -[Modularity](../../Module-Development-Basics.md) is one of the key goals of the ABP Framework. It is not different for the UI; It is possible to develop modular applications and reusable application modules with isolated and reusable UI pages and components. - -The [application startup template](../../Startup-Templates/Application.md) comes with some application modules pre-installed. These modules have their own UI pages embedded into their own NuGet packages. You don't see their code in your solution, but they work as expected on runtime. - -## Theme System - -ABP Framework provides a complete [Theming](Theming.md) system with the following goals: - -* Reusable [application modules](../../Modules/Index.md) are developed **theme-independent**, so they can work with any UI theme. -* UI theme is **decided by the final application**. -* The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**. -* The final application can **customize** the selected theme. - -### Current Themes - -Currently, three themes are **officially provided**: - -* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. -* The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license. -* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/mvc) and [lite](../../Themes/LeptonXLite/AspNetCore.md) choices. - -There are also some community-driven themes for the ABP Framework (you can search on the web). - -### Base Libraries - -There are a set of standard JavaScript/CSS libraries that comes pre-installed and supported by all the themes: - -- [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. -- [JQuery](https://jquery.com/) for DOM manipulation. -- [DataTables.Net](https://datatables.net/) for data grids. -- [JQuery Validation](https://jqueryvalidation.org/) for client side & [unobtrusive](https://github.com/aspnet/jquery-validation-unobtrusive) validation -- [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. -- [SweetAlert](https://sweetalert.js.org/) to show fancy alert message and confirmation dialogs. -- [Toastr](https://github.com/CodeSeven/toastr) to show toast notifications. -- [Lodash](https://lodash.com/) as a utility library. -- [Luxon](https://moment.github.io/luxon/) for date/time operations. -- [JQuery Form](https://github.com/jquery-form/form) for AJAX forms. -- [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers. -- [Select2](https://select2.org/) for better select/combo boxes. -- [Timeago](http://timeago.yarp.com/) to show automatically updating fuzzy timestamps. -- [malihu-custom-scrollbar-plugin](https://github.com/malihu/malihu-custom-scrollbar-plugin) for custom scrollbars. - -You can use these libraries directly in your applications, without needing to manually import your page. - -### Layouts - -The themes provide the standard layouts. So, you have responsive layouts with the standard features already implemented. The screenshot below has taken from the Application Layout of the [Basic Theme](Basic-Theme.md): - -![basic-theme-application-layout](../../images/basic-theme-application-layout.png) - -See the [Theming](Theming.md) document for more layout options and other details. - -### Layout Parts - -A typical layout consists of multiple parts. The [Theming](Theming.md) system provides [menus](Navigation-Menu.md), [toolbars](Toolbars.md), [layout hooks](Layout-Hooks.md) and more to dynamically control the layout by your application and the modules you are using. - -## Features - -This section highlights some of the features provided by the ABP Framework for the ASP.NET Core MVC / Razor Pages UI. - -### Dynamic JavaScript API Client Proxies - -Dynamic JavaScript API Client Proxy system allows you to consume your server side HTTP APIs from your JavaScript client code, just like calling local functions. - -**Example: Get a list of authors from the server** - -````js -acme.bookStore.authors.author.getList({ - maxResultCount: 10 -}).then(function(result){ - console.log(result.items); -}); -```` - -`acme.bookStore.authors.author.getList` is an auto-generated function that internally makes an AJAX call to the server. - -See the [Dynamic JavaScript API Client Proxies](Dynamic-JavaScript-Proxies.md) document for more. - -### Bootstrap Tag Helpers - -ABP makes it easier & type safe to write Bootstrap HTML. - -**Example: Render a Bootstrap modal** - -````html - - - - Woohoo, you're reading this text in a modal! - - - -```` - -See the [Tag Helpers](Tag-Helpers/Index.md) document for more. - -### Forms & Validation - -ABP provides `abp-dynamic-form` and `abp-input` tag helpers to dramatically simplify to create a fully functional form that automates localization, validation and AJAX submission. - -**Example: Use `abp-dynamic-form` to create a complete form based on a model** - -````html - -```` - -See the [Forms & Validation](Forms-Validation.md) document for details. - -### Bundling & Minification / Client Side Libraries - -ABP provides a flexible and modular Bundling & Minification system to create bundles and minify style/script files on runtime. - -````html - - - - - - -```` - -Also, Client Side Package Management system offers a modular and consistent way of managing 3rd-party library dependencies. - -See the [Bundling & Minification](Bundling-Minification.md) and [Client Side Package Management](Client-Side-Package-Management.md) documents. - -### JavaScript APIs - -[JavaScript APIs](JavaScript-API/Index.md) provides a strong abstractions to the server side localization, settings, permissions, features... etc. They also provide a simple way to show messages and **notifications** to the user. - -### Modals, Alerts, Widgets and More - -ABP Framework provides a lot of built-in solutions to common application requirements; - -* [Widget System](Widgets.md) can be used to create reusable widgets & create dashboards. -* [Page Alerts](Page-Alerts.md) makes it easy to show alerts to the user. -* [Modal Manager](Modals.md) provides a simple way to build and use modals. -* [Data Tables](Data-Tables.md) integration makes straightforward to create data grids. - -## Customization - -There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](Customization-User-Interface.md). diff --git a/docs/en/UI/AspNetCore/Page-Alerts.md b/docs/en/UI/AspNetCore/Page-Alerts.md deleted file mode 100644 index cbf6eb493d..0000000000 --- a/docs/en/UI/AspNetCore/Page-Alerts.md +++ /dev/null @@ -1,84 +0,0 @@ -# ASP.NET Core MVC / Razor Pages: Page Alerts - -It is common to show error, warning or information alerts to inform the user. An example *Service Interruption* alert is shown below: - -![page-alert-example](../../images/page-alert-example.png) - -## Basic Usage - -If you directly or indirectly inherit from `AbpPageModel`, you can use the `Alerts` property to add alerts to be rendered after the request completes. - -**Example: Show a Warning alert** - -```csharp -namespace MyProject.Web.Pages -{ - public class IndexModel : MyProjectPageModel //or inherit from AbpPageModel - { - public void OnGet() - { - Alerts.Warning( - text: "We will have a service interruption between 02:00 AM and 04:00 AM at October 23, 2023!", - title: "Service Interruption" - ); - } - } -} -``` - -This usage renders an alert that was shown above. If you need to localize the messages, you can always use the standard [localization](../../Localization.md) system. - -### Exceptions / Invalid Model States - -It is typical to show alerts when you manually handle exceptions (with try/catch statements) or want to handle `!ModelState.IsValid` case and warn the user. For example, the Account Module shows a warning if user enters an incorrect username or password: - -![page-alert-account-layout](../../images/page-alert-account-layout.png) - -> Note that you generally don't need to manually handle exceptions since ABP Framework provides an automatic [exception handling](../../Exception-Handling.md) system. - -### Alert Types - -`Warning` is used to show a warning alert. Other common methods are `Info`, `Danger` and `Success`. - -Beside the standard methods, you can use the `Alerts.Add` method by passing an `AlertType` `enum` with one of these values: `Default`, `Primary`, `Secondary`, `Success`, `Danger`, `Warning`, `Info`, `Light`, `Dark`. - -### Dismissible - -All alert methods gets an optional `dismissible` parameter. Default value is `true` which makes the alert box dismissible. Set it to `false` to create a sticky alert box. - -## IAlertManager - -If you need to add alert messages from another part of your code, you can inject the `IAlertManager` service and use its `Alerts` list. - -**Example: Inject the `IAlertManager`** - -```csharp -using Volo.Abp.AspNetCore.Mvc.UI.Alerts; -using Volo.Abp.DependencyInjection; - -namespace MyProject.Web.Pages -{ - public class MyService : ITransientDependency - { - private readonly IAlertManager _alertManager; - - public MyService(IAlertManager alertManager) - { - _alertManager = alertManager; - } - - public void Test() - { - _alertManager.Alerts.Add(AlertType.Danger, "Test message!"); - } - } -} -``` - -## Notes - -### AJAX Requests - -Page Alert system was designed to be used in a regular full page request. It is not for AJAX/partial requests. The alerts are rendered in the page layout, so a full page refresh is needed. - -For AJAX requests, it is more proper to throw exceptions (e.g. `UserFriendlyException`). See the [exception handling](../../Exception-Handling.md) document. \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/Page-Header.md b/docs/en/UI/AspNetCore/Page-Header.md deleted file mode 100644 index e8c045cf08..0000000000 --- a/docs/en/UI/AspNetCore/Page-Header.md +++ /dev/null @@ -1,62 +0,0 @@ -# ASP.NET Core MVC / Razor Pages: Page Header - -`IPageLayout` service can be used to set the page title, selected menu item and the breadcrumb items for a page. It's the [theme](Theming.md)'s responsibility to render these on the page. - -## IPageLayout - -`IPageLayout` can be injected in any page/view to set the page header properties. - -### Page Title - -Page Title can be set as shown in the example below: - -```csharp -@inject IPageLayout PageLayout -@{ - PageLayout.Content.Title = "Book List"; -} -``` - -* The Page Title is set to the HTML `title` tag (in addition to the [brand/application name](Branding.md)). -* The theme may render the Page Title before the Page Content (not implemented by the Basic Theme). - -### Breadcrumb - -> **The [Basic Theme](Basic-Theme.md) currently doesn't implement the breadcrumbs.** -> -> The [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) supports breadcrumbs. - -Breadcrumb items can be added to the `PageLayout.Content.BreadCrumb`. - -**Example: Add Language Management to the breadcrumb items.** - -``` -PageLayout.Content.BreadCrumb.Add("Language Management"); -``` - -The theme then renders the breadcrumb. An example render result can be: - -![breadcrumbs-example](../../images/breadcrumbs-example.png) - -* The Home icon is rendered by default. Set `PageLayout.Content.BreadCrumb.ShowHome` to `false` to hide it. -* Current Page name (got from the `PageLayout.Content.Title`) is added as the last by default. Set `PageLayout.Content.BreadCrumb.ShowCurrent` to `false` to hide it. - -Any item that you add is inserted between Home and Current Page items. You can add as many item as you need. `BreadCrumb.Add(...)` method gets three parameters: - -* `text`: The text to show for the breadcrumb item. -* `url` (optional): A URL to navigate to, if the user clicks to the breadcrumb item. -* `icon` (optional): An icon class (like `fas fa-user-tie` for Font-Awesome) to show with the `text`. - -### The Selected Menu Item - -> **The [Basic Theme](Basic-Theme.md) currently doesn't implement the selected menu item since it is not applicable to the top menu which is the only option for the Basic Theme for now.** -> -> The [LeptonX Lite Theme](../../Themes/LeptonXLite/AspNetCore.md) supports selected menu item. - -You can set the Menu Item name related to this page: - -```csharp -PageLayout.Content.MenuItemName = "BookStore.Books"; -``` - -Menu item name should match a unique menu item name defined using the [Navigation / Menu](Navigation-Menu.md) system. In this case, it is expected from the theme to make the menu item "active" in the main menu. diff --git a/docs/en/UI/AspNetCore/Page-Toolbar-Extensions.md b/docs/en/UI/AspNetCore/Page-Toolbar-Extensions.md deleted file mode 100644 index db466bebdb..0000000000 --- a/docs/en/UI/AspNetCore/Page-Toolbar-Extensions.md +++ /dev/null @@ -1,163 +0,0 @@ -# Page Toolbar Extensions for ASP.NET Core UI - -Page toolbar system allows you to add components to the toolbar of any page. The page toolbar is the area right to the header of a page. A button ("Import users from excel") was added to the user management page below: - -![page-toolbar-button](../../images/page-toolbar-button.png) - -You can add any type of view component item to the page toolbar or modify existing items. - -## How to Set Up - -In this example, we will add an "Import users from excel" button and execute a JavaScript code for the user management page of the [Identity Module](../../Modules/Identity.md). - -### Add a New Button to the User Management Page - -Write the following code inside the `ConfigureServices` of your web module class: - -````csharp -Configure(options => -{ - options.Configure(toolbar => - { - toolbar.AddButton( - LocalizableString.Create("ImportFromExcel"), - icon: "file-import", - id: "ImportUsersFromExcel", - type: AbpButtonType.Secondary - ); - }); -}); -```` - -`AddButton` is a shortcut to simply add a button component. Note that you need to add the `ImportFromExcel` to your localization dictionary (json file) to localize the text. - -When you run the application, you will see the button added next to the current button list. There are some other parameters of the `AddButton` method (for example, use `order` to set the order of the button component relative to the other components). - -### Create a JavaScript File - -Now, we can go to the client side to handle click event of the new button. First, add a new JavaScript file to your solution. We added inside the `/Pages/Identity/Users` folder of the `.Web` project: - -![user-action-extension-on-solution](../../images/user-action-extension-on-solution.png) - -Here, the content of this JavaScript file: - -````js -$(function () { - $('#ImportUsersFromExcel').click(function (e) { - e.preventDefault(); - alert('TODO: import users from excel'); - }); -}); -```` - -In the `click` event, you can do anything you need to do. - -### Add the File to the User Management Page - -Then you need to add this JavaScript file to the user management page. You can take the power of the [Bundling & Minification system](Bundling-Minification.md). - -Write the following code inside the `ConfigureServices` of your module class: - -````csharp -Configure(options => -{ - options.ScriptBundles.Configure( - typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName, - bundleConfiguration => - { - bundleConfiguration.AddFiles( - "/Pages/Identity/Users/my-user-extensions.js" - ); - }); -}); -```` - -This configuration adds `my-user-extensions.js` to the user management page of the Identity Module. `typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName` is the name of the bundle in the user management page. This is a common convention used for all the ABP Commercial modules. - -## Advanced Use Cases - -While you typically want to add a button action to the page toolbar, it is possible to add any type of component. - -### Add View Component to a Page Toolbar - -First, create a new view component in your project: - -![page-toolbar-custom-component](../../images/page-toolbar-custom-component.png) - -For this example, we've created a `MyToolbarItem` view component under the `/Pages/Identity/Users/MyToolbarItem` folder. - -`MyToolbarItemViewComponent.cs` content: - -````csharp -public class MyToolbarItemViewComponent : AbpViewComponent -{ - public IViewComponentResult Invoke() - { - return View("~/Pages/Identity/Users/MyToolbarItem/Default.cshtml"); - } -} -```` - -`Default.cshtml` content: - -````xml - - - -```` - -* `.cshtml` file can contain any type of component(s). It is a typical view component. -* `MyToolbarItemViewComponent` can inject and use any service if you need. - -Then you can add the `MyToolbarItemViewComponent` to the user management page: - -````csharp -Configure(options => -{ - options.Configure( - toolbar => - { - toolbar.AddComponent(); - } - ); -}); -```` - -* If your component accepts arguments (in the `Invoke`/`InvokeAsync` method), you can pass them to the `AddComponent` method as an anonymous object. - -#### Permissions - -If your button/component should be available based on a [permission/policy](../../Authorization.md), you can pass the permission/policy name as the `requiredPolicyName` parameter to the `AddButton` and `AddComponent` methods. - -### Add a Page Toolbar Contributor - -If you perform advanced custom logic while adding an item to a page toolbar, you can create a class that implements the `IPageToolbarContributor` interface or inherits from the `PageToolbarContributor` class: - -````csharp -public class MyToolbarContributor : PageToolbarContributor -{ - public override Task ContributeAsync(PageToolbarContributionContext context) - { - context.Items.Insert(0, new PageToolbarItem(typeof(MyToolbarItemViewComponent))); - - return Task.CompletedTask; - } -} -```` - -* You can use `context.ServiceProvider` to resolve dependencies if you need. - -Then add your class to the `Contributors` list: - -````csharp -Configure(options => -{ - options.Configure( - toolbar => - { - toolbar.Contributors.Add(new MyToolbarContributor()); - } - ); -}); -```` - diff --git a/docs/en/UI/AspNetCore/Security-Headers.md b/docs/en/UI/AspNetCore/Security-Headers.md deleted file mode 100644 index c66f28aee1..0000000000 --- a/docs/en/UI/AspNetCore/Security-Headers.md +++ /dev/null @@ -1,99 +0,0 @@ -# Security Headers - -ABP Framework allows you to add frequently used security headers into your application. The following security headers will be added as response headers to your application if you use the `UseAbpSecurityHeaders` middleware: - -* `X-Content-Type-Options`: Tells the browser to not try and guess what a mime-type of a resource might be, and to just take what mime-type the server has returned. -* `X-XSS-Protection`: This is a feature of Internet Explorer, Chrome, and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks. -* `X-Frame-Options`: This header can be used to indicate whether or not a browser should be allowed to render a page in a `