<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <title>ABP.IO Stories</title>
    <link>https://abp.io/community/articles</link>
    <description>A hub for ABP Framework, .NET, and software development. Access articles, tutorials, news, and contribute to the ABP community.</description>
    <lastBuildDate>Mon, 09 Mar 2026 23:04:21 Z</lastBuildDate>
    <generator>Community - ABP.IO</generator>
    <image>
      <url>https://abp.io/assets/favicon.ico/favicon-32x32.png</url>
      <title>ABP.IO Stories</title>
      <link>https://abp.io/community/articles</link>
    </image>
    <a10:link rel="self" type="application/rss+xml" title="self" href="https://abp.io/community/rss?member=bartvanhoey" />
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/containerization-blazor-wasm-jwt-web-api-docker-i3eirlsf</guid>
      <link>https://abp.io/community/posts/containerization-blazor-wasm-jwt-web-api-docker-i3eirlsf</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <title>Containerization: Blazor WASM + JWT Web API =&gt; Docker</title>
      <description>In this step-by-step guide, I will walk you through the process of **containerizing a .NET application**. The *ultimate goal** is to have a **Blazor WebAssembly** application and a **JWT-protected Web API up and running with a single command**.

This article is designed to be a **hands-on learning experience**, introducing **core Docker concepts** along the way. By following the complete guide, **readers will gain a solid understanding of containerization** and its practical applications.</description>
      <pubDate>Thu, 13 Feb 2025 19:58:01 Z</pubDate>
      <a10:updated>2026-03-09T08:10:22Z</a10:updated>
      <content:encoded><![CDATA[In this step-by-step guide, I will walk you through the process of **containerizing a .NET application**. The *ultimate goal** is to have a **Blazor WebAssembly** application and a **JWT-protected Web API up and running with a single command**.

This article is designed to be a **hands-on learning experience**, introducing **core Docker concepts** along the way. By following the complete guide, **readers will gain a solid understanding of containerization** and its practical applications.<br \><a href="https://github.com/bartvanhoey/BlazorWasmJwtWebApiToDocker" rel="nofollow noopener noreferrer" title="Go to the Post">Go to the Post</a>]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a181310-9c61-3129-ccd4-5e5319306836" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a181310-9c61-3129-ccd4-5e5319306836" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/create-a-generic-http-service-to-consume-a-web-api-yidme2kq</guid>
      <link>https://abp.io/community/posts/create-a-generic-http-service-to-consume-a-web-api-yidme2kq</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>web-api</category>
      <title>Create a Generic HTTP Service to consume a Web API</title>
      <description>This article will guide you through the process of **creating a Generic HTTP service in C#** that consumes a **.NET Core Web API**.  </description>
      <pubDate>Sat, 13 Jul 2024 08:38:41 Z</pubDate>
      <a10:updated>2026-03-09T21:05:21Z</a10:updated>
      <content:encoded><![CDATA[<h1>Create a Generic HTTP Service to Consume a Web API</h1>
<h2>Generics: Write Once, Use Every Time</h2>
<p>This article will guide you through the process of <strong>creating a Generic HTTP service in C#</strong> that consumes a <strong>.NET Core Web API</strong>.</p>
<p>To simplify things, we will create a <strong>.NET Core WEB API</strong> (BookStoreWebApi) and a <strong>.NET Core Console</strong> (BookStoreConsole) application to implement a <strong>generic HTTP Service</strong> that consumes a <strong>C# CRUD API</strong>.</p>
<p>You can find the <strong>BookStoreWebApi</strong> and <strong>BookStoreConsole</strong> sample applications in the <a href="https://github.com/bartvanhoey/AbpGenericHttpServiceRepo">GitHub repo</a></p>
<p>To consume an <strong>ABP Framework API</strong>, have a look at the <a href="https://github.com/bartvanhoey/AbpGenericHttpServiceRepo">mobile .NET MAUI app</a> and <a href="https://github.com/bartvanhoey/AbpGenericHttpServiceRepo">.NET Core Console app</a>
in the Repo.</p>
<p><strong>Warning:</strong> The Generic Http Service created in this article is definitely not production-ready, as you typically will need to add Logging, Exception Handling, Retry Logic, and ...</p>
<h2>Prerequisites</h2>
<ul>
<li>.NET 8.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
</ul>
<h2>Development</h2>
<h3>Setting Up the .NET Core Web API</h3>
<p>First, open a terminal and create a  <strong>.NET Core API</strong> with a <strong>BooksController</strong> with the standard CRUD endpoints.</p>
<pre><code class="language-bash">dotnet new webapi --use-controllers -o BookStoreWebApi
</code></pre>
<h3>Copy Data/Infra/Dtos folders</h3>
<p>Copy/paste the <strong>Data/Infra and Dtos</strong> folders of the <strong>BookstoreWebApi</strong> sample project into the root of the newly created API.
In the Dtos folder you find the <strong>Data Transfer Objects</strong> for sending/receiving data to/from the API.</p>
<h3>Add a BooksController class to the Controllers folder</h3>
<pre><code class="language-csharp">using BookStoreWebApi.Dtos.Books;
using BookStoreWebApi.Infra;
using Microsoft.AspNetCore.Mvc;
using static BookStoreWebApi.Data.BooksResolver;

namespace BookStoreWebApi.Controllers
{
    [Route(&quot;api/app/book&quot;)]
    [ApiController]
    public class BooksController : ControllerBase
    {
        [HttpGet]
        public PagedResultDto&lt;BookDto&gt; Get([FromQuery]GetBooksDto getBooksDto) =&gt; new() { Items = BookItems, TotalCount = BookItems.Count };

        [HttpGet(&quot;{id}&quot;)]
        public BookDto? Get(Guid id) =&gt; BookItems.FirstOrDefault(x =&gt; x.Id == id);

        [HttpPost]
        public BookDto Create([FromBody] CreateBookDto createBookDto)
        {
            BookItems.Add(new BookDto(createBookDto.Name,createBookDto.Type,createBookDto.Price,  createBookDto.PublishDate, createBookDto.Id));
            return BookItems.Single(x =&gt; x.Id == createBookDto.Id);
        }

        [HttpPut(&quot;{id}&quot;)]
        public BookDto? Put(Guid id, [FromBody] UpdateBookDto updateBookDto)
        {
            var bookDto = BookItems.FirstOrDefault(x =&gt; x.Id == id);
            if (bookDto == null) return bookDto;
            bookDto.Name = updateBookDto.Name;
            bookDto.Price = updateBookDto.Price;
            bookDto.PublishDate = updateBookDto.PublishDate;
            bookDto.Type = updateBookDto.Type;
            return bookDto;
        }

        [HttpDelete(&quot;{id}&quot;)]
        public void Delete(Guid id)
        {
            var bookDto = BookItems.FirstOrDefault(x =&gt; x.Id == id);
            if (bookDto != null) BookItems.Remove(bookDto);
        }
    }
}
</code></pre>
<h3>Run the API</h3>
<p>Press <code>F5</code> to run the API. It will be hosted on <code>https://localhost:xxxxx</code>.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpGenericHttpServiceRepo/main/images/swagger_bookscontroller.png" alt="Swagger Api Endpoints BooksController" /></p>
<h2>Creating a Generic HTTP Service</h2>
<h3>Create a new Console app</h3>
<p>Open a terminal and run the command below to create a new console app.</p>
<pre><code class="language-bash">dotnet new console -o BookStoreConsole
</code></pre>
<h3>Add Dependency Injection Nuget Package</h3>
<p>Open a terminal in the root of the <code>Console app</code> and install the <code>Microsoft.Extensions.DependencyInjection</code> NuGet package.</p>
<pre><code class="language-bash">dotnet add package Microsoft.Extensions.DependencyInjection
</code></pre>
<h2>IHttpService interface</h2>
<p>Create a <strong>Services/Http</strong> folder in the root of your console application.</p>
<p>Copy/Paste the <strong>Infra</strong> folder of the <strong>BookStoreConsole</strong> sample application into the <strong>Services/Http</strong> folder.</p>
<p>Create a <strong>IHttpService.cs</strong> interface with the standard CRUD method definitions in the <strong>Services/Http</strong> folder.</p>
<pre><code class="language-csharp">using BookStoreConsole.Services.Http.Infra;

namespace BookStoreConsole.Services.Http;

public interface IHttpService&lt;T, in TC, in TU, in TG, in TD&gt;
{
    Task&lt;ListResultDto&lt;T&gt;&gt; GetListAsync(string uri, TG? getListRequestDto = default);
    Task&lt;ListResultDto&lt;T&gt;&gt; UpdateAsync(string uri, TU updateInputDto);
    Task&lt;T&gt; CreateAsync(string uri, TC createInputDto);
    Task CreateManyAsync(string uri, IEnumerable&lt;TC&gt; createManyInputDto);
    Task&lt;T&gt; GetAsync(string uri);
    Task DeleteAsync(string uri, TD id);
}
</code></pre>
<p>Create a <strong>HttpService.cs</strong> class in the <strong>Http</strong> folder that implements the <strong>IHttpService interface</strong></p>
<pre><code class="language-csharp">using System.Net.Http.Json;
using BookStoreConsole.Services.Http.Infra;

namespace BookStoreConsole.Services.Http;

public class HttpService&lt;T, TC, TU, TL, TD&gt; : HttpServiceBase&lt;TL&gt;, IHttpService&lt;T, TC, TU, TL, TD&gt;
    where T : class 
    where TC : class
    where TU : class
    where TL : class
{
    public async Task&lt;ListResultDto&lt;T&gt;&gt; GetListAsync(string uri, TL? getListRequestDto = default)
    {
        if (getListRequestDto == null) return new ListResultDto&lt;T&gt;();
        var httpResponse = await (await GetHttpClientAsync()).Value.GetAsync(ComposeUri(uri, getListRequestDto));
        httpResponse.EnsureSuccessStatusCode();
        var json = await httpResponse.Content.ReadAsStringAsync();
        if (json == &quot;[]&quot; || json.IsNullOrWhiteSpace()) return new ListResultDto&lt;T&gt;();
        if (getListRequestDto is IPagedRequestDto)
        {
            var pagedResultDto = json.ToType&lt;PagedResultDto&lt;T&gt;&gt;();
            return new PagedResultDto&lt;T&gt;(pagedResultDto.TotalCount,pagedResultDto.Items);
        }
        return new ListResultDto&lt;T&gt;(json.ToType&lt;List&lt;T&gt;&gt;());
    }

    public async Task&lt;ListResultDto&lt;T&gt;&gt; UpdateAsync(string uri, TU updateInputDto)
    {
        var httpResponse = await (await GetHttpClientAsync()).Value.PutAsJsonAsync($&quot;{uri}&quot;, updateInputDto);
        var json = await httpResponse.Content.ReadAsStringAsync();
        if (json == &quot;[]&quot; || json.IsNullOrWhiteSpace()) return new ListResultDto&lt;T&gt;();

        if (json.StartsWith(&quot;{&quot;) &amp;&amp; json.EndsWith(&quot;}&quot;))
            return new ListResultDto&lt;T&gt;(new List&lt;T&gt; { json.ToType&lt;T&gt;() });

        return new ListResultDto&lt;T&gt;(json.ToType&lt;List&lt;T&gt;&gt;());
    }

    public async Task&lt;T&gt; CreateAsync(string uri, TC createInputDto)
    {
        var httpResponse = await (await GetHttpClientAsync()).Value.PostAsJsonAsync(uri, createInputDto);
        return (await httpResponse.Content.ReadAsStringAsync()).ToType&lt;T&gt;();
    }

    public async Task CreateManyAsync(string uri, IEnumerable&lt;TC&gt; createInputDto)
    {
        var httpResponse = await (await GetHttpClientAsync()).Value.PostAsJsonAsync($&quot;{uri}/many&quot;, createInputDto);
    }

    public async Task&lt;T&gt; GetAsync(string uri)
    {
        var httpResponse = await (await GetHttpClientAsync()).Value.GetAsync(uri);
        return (await httpResponse.Content.ReadAsStringAsync()).ToType&lt;T&gt;();
    }

    public async Task DeleteAsync(string uri, TD id)
    {
        var httpResponse = await (await GetHttpClientAsync()).Value.DeleteAsync($&quot;{uri}/{id}&quot;);
    }
}
</code></pre>
<h3>Create an IBookService interface</h3>
<p>Create a <strong>Books</strong> folder in the <strong>Services</strong> folder of your project.</p>
<p>Copy/Paste the <strong>Services/Books/Dtos</strong> folder of the <strong>BookStoreConsole</strong> sample application into the <strong>Services/Books</strong> folder.</p>
<p>Create an <strong>IBookService.cs</strong> interface in the <strong>Services/Books</strong> folder.</p>
<pre><code class="language-csharp">using BookStoreConsole.Services.Books.Dtos;

namespace BookStoreConsole.Services.Books;

public interface IBookService
{
    Task&lt;IEnumerable&lt;BookDto&gt;&gt; GetBooksAsync();
    Task&lt;BookDto?&gt; CreateBookAsync(CreateBookDto bookDto);
    // Find other method definitions in the BookStoreConsole sample project ...
}
</code></pre>
<p>Create a <strong>BookService class</strong> in the <strong>Services/Books</strong> folder.
The BookService class gets the correct HttpService via Constructor Dependency Injection.</p>
<pre><code class="language-csharp">using BookStoreConsole.Services.Books.Dtos;
using BookStoreConsole.Services.Http;

namespace BookStoreConsole.Services.Books;

public class BookService(
    IHttpService&lt;BookDto, CreateBookDto, UpdateBookDto, GetBooksDto, Guid&gt; httpService)
    : IBookService
{
    // REPLACE &lt;the-api-port-number-here&gt; with the port number the API is running on !!!

    const string BookApiUrl = &quot;https://localhost:&lt;the-api-port-number-here&gt;/api/app/book&quot;; 
    
    public async Task&lt;IEnumerable&lt;BookDto&gt;&gt; GetBooksAsync() 
        =&gt; (await httpService.GetListAsync($&quot;{BookApiUrl}&quot;, new GetBooksDto())).Items;

    public async Task&lt;BookDto?&gt; CreateBookAsync(CreateBookDto bookDto) 
        =&gt; await httpService.CreateAsync($&quot;{BookApiUrl}&quot;, bookDto);

    // Find other methods in the BookStoreConsole sample project ...

}
</code></pre>
<h2>Test the Generic HTTP Service</h2>
<h3>Program.cs</h3>
<p>Copy/Paste the content below in the <strong>Program.cs</strong> file and hit <code>F5</code> to run the console app.</p>
<pre><code class="language-csharp">using BookStoreConsole.Services.Books;
using BookStoreConsole.Services.Books.Dtos;
using BookStoreConsole.Services.Http;
using Microsoft.Extensions.DependencyInjection;

// First set up the Dependency Injection System to register the Book Http Service and the BookService
var services = new ServiceCollection();

// Register the Book HttpService to the Dependency Injection system
services.AddTransient&lt;IHttpService&lt;BookDto, CreateBookDto, UpdateBookDto, GetBooksDto, Guid&gt;,
    HttpService&lt;BookDto, CreateBookDto, UpdateBookDto, GetBooksDto, Guid&gt;&gt;();

// Register the BookService to the Dependency Injection system
services.AddTransient&lt;IBookService, BookService&gt;();

// Get the BookService from the Dependency Injection System
// The Book service becomes via its constructor the Book HttpService (Constructor Dependency Injection) and is ready to use.
var bookService = services.BuildServiceProvider().GetRequiredService&lt;IBookService&gt;();

// Create a book
var createdBook = await bookService.CreateBookAsync(new CreateBookDto(&quot;New Book3&quot;, BookType.Adventure, DateTime.Now, 10.0f));

// Get a list of books =&gt; The result should be a list of 3 books. 
var books = await bookService.GetBooksAsync();

Console.ReadLine(); // Set here a breakpoint to see the results
</code></pre>
<h2>Another Use Case: Authors</h2>
<p>When you have a working use case, the Generic Book HTTP Service, things get a lot easier because the heavy lifting is done.</p>
<p>Imagine, you have another use case where you need to Get or Create Authors from the API.</p>
<p>The only things you need to do are:</p>
<h3>Create the DTOS (CreateAuthorDto, GetAuthorsDto, ...)</h3>
<pre><code class="language-csharp">namespace BookStoreWebApi.Dtos.Authors;

public class AuthorDto
{
    public Guid Id { get; set; }
    public string? Name { get; set; }
    public DateTime BirthDate { get; set; }
    public string? ShortBio { get; set; }
}
</code></pre>
<h3>Create an IAuthorService interface and AuthorService class</h3>
<pre><code class="language-csharp">public interface IAuthorService
{
    Task&lt;IEnumerable&lt;AuthorDto&gt;&gt; GetAuthorsAsync();
    Task&lt;AuthorDto?&gt; CreateAuthorAsync(CreateAuthorDto bookDto);
    // other method definitions here
}

public class AuthorService : IAuthorService
{
    // implementation here
}
</code></pre>
<h3>Register the Author Http Service to the Dependency Injection System</h3>
<pre><code class="language-csharp">services.AddTransient&lt;IHttpService&lt;AuthorDto, CreateAuthorDto, UpdateAuthorDto, GetAuthorsDto, Guid&gt;,
    HttpService&lt;AuthorDto, CreateAuthorDto, UpdateAuthorDto, GetAuthorsDto, Guid&gt;&gt;();
</code></pre>
<h3>Register the AuthorService to the Dependency Injection System</h3>
<pre><code class="language-csharp">services.AddTransient&lt;IAuthorService, AuthorService&gt;();
</code></pre>
<h3>Call the CRUD methods you need to call</h3>
<pre><code class="language-csharp">await authorService.CreateAuthorAsync(new CreateAuthorDto(){ ... });

var authors = await authorService.GetAuthorsAsync();
</code></pre>
<p>Get the <a href="https://github.com/bartvanhoey/AbpGenericHttpServiceRepo">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a13bd6b-64f3-9395-d465-0d368381c089" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a13bd6b-64f3-9395-d465-0d368381c089" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/tunnel-your-local-host-address-to-a-public-url-with-ngrok-4cywnocj</guid>
      <link>https://abp.io/community/posts/tunnel-your-local-host-address-to-a-public-url-with-ngrok-4cywnocj</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>ngrok</category>
      <category>maui</category>
      <title>Tunnel your local host address to a public URL with ngrok</title>
      <description>Tunnel your localhost address to a public URL with ngrok and
have the Authority URL in the appsettings.json file of a  **.NET MAUI**  app replaced with the generated URL.</description>
      <pubDate>Sun, 23 Jun 2024 18:50:01 Z</pubDate>
      <a10:updated>2026-03-09T21:31:04Z</a10:updated>
      <content:encoded><![CDATA[<h1>Tunnel your local host address to a public URL</h1>
<p>In this blog post, I will show you, on a Windows machine, how to <strong>tunnel your local host address to a public URL</strong> with <strong>ngrok</strong>.</p>
<p>In my case, I use ngrok to expose an <strong>ABP Framework back-and API localhost address</strong> to a public internet URL so that I can consume an <strong>ABP Framework API</strong> by a <strong>.NET MAUI</strong> mobile app running on my Android phone.</p>
<p>The problem is, that you need to copy the public URL generated by <strong>ngrok</strong> and replace the Authority URL in the appsettings.json file of the <strong>.NET MAUI</strong> Mobile app manually. This is very cumbersome and time-consuming.</p>
<p>So I automated the process by writing a batch-script that starts <strong>ngrok</strong> and also automatically replaces the Authority URL in the appsettings.json file of the <strong>.NET MAUI</strong> mobile app.</p>
<p>The script is called <code>ngrok.bat</code> and is located at the project's root.</p>
<p>Although the script is written for an <strong>ABP Framework project</strong> with <strong>.NET MAUI</strong> mobile app, the same principle can be applied to many other projects too.</p>
<h2>Step-by-step</h2>
<h3>Install and setup ngrok</h3>
<ol>
<li><p>Download and install ngrok</p>
<p>Get the <a href="https://ngrok.com/download">ngrok</a> on the ngrok official website.</p>
</li>
<li><p>Get an auth token</p>
<p>After you have downloaded and installed ngrok, you need to get an authtoken to use ngrok. You can get an authtoken by signing up on the ngrok website.</p>
<p>Go to the ngrok <a href="https://dashboard.ngrok.com/signup">Sign up</a> page and follow the instructions to get an authtoken.</p>
</li>
<li><p>Add authtoken to your ngrok.yml file</p>
<p>Run the following command to add your authtoken to your ngrok.yml file:</p>
</li>
</ol>
<pre><code class="language-bash">
     ngrok authtoken your-authtoken

</code></pre>
<h3>Install jq</h3>
<p>jq is a lightweight and flexible command-line JSON processor. You can use jq to parse JSON data in the terminal.</p>
<p>Get the <a href="https://jqlang.github.io/jq/download/">jq</a> on the jq official website and install it.</p>
<h3>Copy the ngrok.bat script to the root of the ABP Framework  project</h3>
<pre><code class="language-bash">@echo off

set targetFile=&quot;C:\&lt;your-path-to-the-maui-mobile-aap-appsettings-file-here&gt;\appsettings.json&quot;
set apiLocalhostPortNumber=&lt;api-port-number-here&gt;

setlocal disabledelayedexpansion

start ngrok.exe http https://localhost:%apiLocalhostPortNumber%/

timeout 5 &gt; NUL

if not exist &quot;C:\TEMP_NGROK\&quot; mkdir &quot;C:\TEMP_NGROK\&quot;

for /F %%I in ('curl -s http://127.0.0.1:4040/api/tunnels ^| jq -r .tunnels[0].public_url') do set ngrokTunnel=%%I
echo ngroktunnel: %ngrokTunnel%
echo  %ngrokTunnel%/.well-known/openid-configuration

for /f &quot;tokens=1* delis=]&quot; %%a in ('find /n /v &quot;&quot; %targetFile%') do (
  echo %%b|finds /rc:&quot;\ *\&quot;AuthorityUrl\&quot;.*\:\ \&quot;.*\&quot;&quot; &gt;nul &amp;&amp; (
    for /f &quot;delis=:&quot; %%c in (&quot;%%b&quot;) do echo %%c: &quot;%ngrokTunnel%&quot;,
 ) || echo/%%b
)&gt;&gt;C:\TEMP_NGROK\temp.json 2&gt;nul

for %%I in (C:\TEMP_NGROK\temp.json) do for /f &quot;delis=, tokens=* skip=1&quot; %%x in (%%I) do echo %%x &gt;&gt; &quot;%%I.new&quot;

move /Y &quot;C:\TEMP_NGROK\temp.json.new&quot; %targetFile% &gt; nul

del C:\TEMP_NGROK\temp.json
rmdir /s /q &quot;C:\TEMP_NGROK\&quot;

</code></pre>
<h3>Start the ABP Framework back-end API</h3>
<h3>Start the batch-script</h3>
<p>Open a terminal in the root of the ABP Framework project and run the following command:</p>
<pre><code class="language-bash">    ngrok.exe
</code></pre>
<p>When you run the script, ngrok will tunnel your localhost address to a public URL,
and will replace the Authority URL in the appsettings.json file of the <strong>.NET MAUI</strong> Mobile app with the public URL generated by ngrok.</p>
<p>Enjoy and happy coding!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a13589b-e679-fa82-d94f-3ff74e45de16" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a13589b-e679-fa82-d94f-3ff74e45de16" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/adding-a-module-to-an-abp-project-made-simple-a8zw0j2m</guid>
      <link>https://abp.io/community/posts/adding-a-module-to-an-abp-project-made-simple-a8zw0j2m</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>module</category>
      <category>module-integration</category>
      <title>Adding a Module to an ABP project made simple</title>
      <description>Create and integrate your own module, in this case a simple PdfGenerator, into an ABP Framework application</description>
      <pubDate>Sat, 09 Mar 2024 08:34:48 Z</pubDate>
      <a10:updated>2026-03-09T12:30:45Z</a10:updated>
      <content:encoded><![CDATA[<p><a href="https://github.com/bartvanhoey/AddAbpModule/actions/workflows/dotnet.yml"><img src="https://raw.githubusercontent.com/bartvanhoey/AddAbpModule/actions/workflows/dotnet.yml/badge.svg" alt=".NET" /></a></p>
<h2>Adding a Module to an ABP project made simple</h2>
<p>As you can read in the <strong>ABP Framework</strong> documentation about <a href="https://docs.abp.io/en/abp/6.0/Module-Development-Basics">Modularity</a>, the ABP Framework was designed to support to build fully <a href="https://www.techopedia.com/definition/24772/modularity">modular</a> applications.</p>
<p>Today I will show you how to <strong>create and integrate your own module</strong>, in this case a simple <strong>PdfGenerator</strong>, into an <strong>ABP Framework</strong> application.</p>
<h3>Source code</h3>
<p>The source code of both projects is <a href="https://github.com/bartvanhoey/AddAbpModule">available on GitHub</a></p>
<h3>Requirements</h3>
<p>The following tools are needed to be able to run the solution and follow along.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
<li>ABP CLI 8.0.0</li>
</ul>
<h3>Create a new ABP Framework application</h3>
<pre><code class="language-bash">    abp new BookStore -u blazor -o BookStore
</code></pre>
<ul>
<li>Run the DbMigrator project to apply the database migrations</li>
<li>Start both the HttpApi.Host project and Blazor project</li>
<li>Stop both the HttpApi.Host project and Blazor project</li>
</ul>
<h3>Create a PdfGenerator ABP Module</h3>
<p>Add a <strong>modules</strong> folder to the root of the project</p>
<p>Open a <strong>command prompt</strong> in the <strong>modules</strong> folder of the project and <strong>add a new class library</strong></p>
<pre><code class="language-bash">    dotnet new classlib -n PdfGenerator
</code></pre>
<h3>Add PdfGenerator project to solution</h3>
<p>Go to the <strong>root of your ABP project</strong> and run the command below:</p>
<pre><code class="language-bash">    dotnet sln add modules\PdfGenerator\PdfGenerator.csproj
</code></pre>
<h3>Install Nuget packages</h3>
<p>Open a <strong>command prompt</strong> in the <strong>root of the PdfGenerator</strong> class library and  install the Nuget packages below.</p>
<pre><code class="language-bash">    abp add-package Volo.Abp.Core
    dotnet add package PdfSharpCore
</code></pre>
<h3>Add a PdfGeneratorSettings section to the appsettings.json file in hte HttpApi.Host project</h3>
<pre><code class="language-bash">  &quot;PdfGeneratorSettings&quot;: {
        &quot;UserName&quot;: &quot;your-username&quot;,
        &quot;EmailAddress&quot;: &quot;your-username@hotmail.com&quot;
    }
</code></pre>
<h3>Add a PdfGeneratorSettingsOptions class to the PdfGenerator class library project</h3>
<pre><code class="language-csharp">namespace PdfGenerator
{
    public class PdfGeneratorSettingsOptions
    {
        public string? UserName { get; set; }
        public string? EmailAddress { get; set; }
    }
}
</code></pre>
<h3>Add a MyPdfGeneratorModule class to the PdfGenerator class library project</h3>
<pre><code class="language-csharp">using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;

namespace PdfGenerator
{
    public class MyPdfGeneratorModule :  AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var configuration = context.Services.GetConfiguration();
            Configure&lt;PdfGeneratorSettingsOptions&gt;(configuration.GetSection(&quot;PdfGeneratorSettings&quot;));
            context.Services.AddTransient&lt;IPdfGeneratorService, PdfGeneratorService&gt;();
        }
    }
}
</code></pre>
<h3>Add an IPdfGeneratorService interface and a PdfGeneratorService class to the PdfGenerator class library project</h3>
<pre><code class="language-csharp">using Microsoft.Extensions.Options;
using PdfSharpCore.Drawing;
using PdfSharpCore.Fonts;
using PdfSharpCore.Pdf;
using PdfSharpCore.Utils;

namespace PdfGenerator
{
    public interface IPdfGeneratorService
    {
        Task&lt;byte[]&gt; Generate();
    }

    public class PdfGeneratorService(IOptions&lt;PdfGeneratorSettingsOptions&gt; options) : IPdfGeneratorService
    {
        private readonly PdfGeneratorSettingsOptions _options = options.Value;

        public async Task&lt;byte[]&gt; Generate()
        {
            if (GlobalFontSettings.FontResolver is not FontResolver)
                GlobalFontSettings.FontResolver = new FontResolver();

            var document = new PdfDocument();
            var page = document.AddPage();

            var gfx = XGraphics.FromPdfPage(page);
            var font = new XFont(&quot;Arial&quot;, 20, XFontStyle.Bold);

            var textColor = XBrushes.Black;
            var layout = new XRect(20, 20, page.Width, page.Height);
            var format = XStringFormats.Center;

            gfx.DrawString($&quot;Pdf created by {_options.UserName}!&quot;, font, textColor, layout, format);

            byte[] fileContents;
            using (var stream = new MemoryStream())
            {
                document.Save(stream, true);
                fileContents = stream.ToArray();
            }
            return await Task.FromResult(fileContents);
        }
    }
}
</code></pre>
<h3>Add a project reference to the PdfGeneratorModule class library project in the Application project</h3>
<p>Open a <strong>command prompt</strong> in the <strong>root of the Application</strong> project</p>
<pre><code class="language-bash">   dotnet add reference ../../modules/PdfGenerator/PdfGenerator.csproj
</code></pre>
<h3>Attribute DependsOn in the BookStoreApplicationModule class in the Application project</h3>
<p>Add a <strong>typeof(MyPdfGeneratorModule)</strong> entry in the <strong>DependsOnAttribute</strong></p>
<pre><code class="language-csharp">[DependsOn(
    typeof(BookStoreDomainModule),
    typeof(AbpAccountApplicationModule),
    typeof(BookStoreApplicationContractsModule),
    typeof(AbpIdentityApplicationModule),
    typeof(AbpPermissionManagementApplicationModule),
    typeof(AbpTenantManagementApplicationModule),
    typeof(AbpFeatureManagementApplicationModule),
    typeof(AbpSettingManagementApplicationModule),
    typeof(MyPdfGeneratorModule)
    )]
</code></pre>
<h3>Add A IExportPdfAppService interface the Application.Contracts project</h3>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace BookStore.Application.Contracts
{
    public interface IExportPdfAppService : IApplicationService
    {
        Task&lt;byte[]&gt; GeneratePdf();
    }
}
</code></pre>
<h3>Add A ExportPdfAppService class to test the Application project</h3>
<pre><code class="language-csharp">using System.Threading.Tasks;
@using BookStore.Application.Contracts;
using PdfGenerator;
using Volo.Abp.Application.Services;

namespace BookStore
{
    public class ExportPdfAppService(IPdfGeneratorService pdfGeneratorService) : ApplicationService, IExportPdfAppService
    {
        private readonly IPdfGeneratorService _pdfService = pdfGeneratorService;
        public async Task&lt;byte[]&gt; GeneratePdf() =&gt; await _pdfService.Generate();
    }
}
</code></pre>
<h3>add a exporttopdf.js file to the wwwroot/js folder of the Blazor project</h3>
<pre><code class="language-bash">function saveAsFile(filename, bytesBase64) {
    var link = document.createElement('a');
    link.download = filename;
    link.href = 'data:application/octet-stream;base64,' + bytesBase64;
    document.body.appendChild(link); // Needed for Firefox
    link.click();
    document.body.removeChild(link);
}
</code></pre>
<h3>add a reference to exporttopdf.js in the index.html file in the Blazor project</h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;!-- other code here ... --&gt;
    &lt;script src=&quot;js/exporttopdf.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3>Replace contents from Index.razor page in the Blazor project</h3>
<pre><code class="language-html">@page &quot;/&quot;
@using BookStore.Application.Contracts;
@using Microsoft.JSInterop
@inject IExportPdfAppService ExportPdfAppService
@inject IJSRuntime JsRuntime

&lt;Row Class=&quot;d-flex px-0 mx-0 mb-1&quot;&gt;
    &lt;Button Clicked=&quot;@(ExportPdf)&quot; class=&quot;p-0 ml-auto mr-2&quot; style=&quot;background-color: transparent&quot;
            title=&quot;Download&quot;&gt;
        &lt;span class=&quot;fa fa-file-pdf fa-lg m-0&quot; style=&quot;color: #008000; background-color: white;&quot;
              aria-hidden=&quot;true&quot;&gt;
        &lt;/span&gt;
        Generate Pdf!
    &lt;/Button&gt;
&lt;/Row&gt;

@code {
    private async Task ExportPdf()
    {
        var pdfBytes = await ExportPdfAppService.GeneratePdf();
        await JsRuntime.InvokeVoidAsync(&quot;saveAsFile&quot;, $&quot;test_{DateTime.Now.ToString(&quot;yyyyMMdd_HHmmss&quot;)}.pdf&quot;,
            Convert.ToBase64String(pdfBytes));
    }
}
</code></pre>
<h3>Test the PdfGenerator module</h3>
<p>Start both the <strong>Blazor</strong> and the <strong>HttpApi.Host</strong> project to run the application
and test out the <strong>PdfGenerator module</strong> you just created.</p>
<p>Get the source code on <a href="https://github.com/bartvanhoey/AddAbpModule">GitHub</a>.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a113486-8c8e-c848-9bcc-b6ac7cd6c20c" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a113486-8c8e-c848-9bcc-b6ac7cd6c20c" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/abp-framework-goes-azure-ub4u5ax5</guid>
      <link>https://abp.io/community/posts/abp-framework-goes-azure-ub4u5ax5</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>azure</category>
      <title>ABP Framework goes Azure</title>
      <description>ABP Framework goes Azure

A step-by-step tutorial on how to set up Continuous Deployment of an ABP Framework application
</description>
      <pubDate>Sat, 10 Feb 2024 17:09:16 Z</pubDate>
      <a10:updated>2026-03-09T17:40:29Z</a10:updated>
      <content:encoded><![CDATA[<h2>ABP Framework goes Azure</h2>
<p>A <strong>step-by-step tutorial</strong> on how to set up <strong>Continuous Deployment</strong> of an <strong>ABP Framework</strong> application.</p>
<h3>Continuous Deployment to Azure with Azure DevOps</h3>
<p><em>Continuous deployment</em> is a <em>software engineering approach</em> in which <em>software functionalities are delivered frequently and through automated deployments</em>. (Wikipedia)</p>
<h3>Source Code</h3>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.
The same principles apply for other <strong>UI frameworks like Angular, MVC or databases like MongoDb, MySql, Progress</strong>, ...</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpGoesAzure.git">source code</a> on GitHub.</p>
<p>ATTENTION: <strong>This tutorial is by no means production-ready</strong> and just serves as a guide to point you in the right direction.</p>
<h3>Requirements</h3>
<p>The following tools are needed to be able to run the solution.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
<li>Microsoft Server SQL Management Studio</li>
<li>ABP CLI version 8.0.0</li>
</ul>
<h4>Step-by-step</h4>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../1.create-a-new-github-repository.md">1. Create a new GitHub repository</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../2.create-a-new-abp-framework-application.md">2. Create a new ABP Framework application</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../3.create-a-sql-database-in-azure.md">3. Create an SQL Database in Azure</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../4.set-up-a-build-pipeline-in-azuredevops.md">4. Set up the Build pipeline in AzureDevops</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../5.create-a-web-app-in-the-azure-portal-for-the-api-project.md">5. Create a Web App in the Azure Portal for the API</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../6.create-a-release-pipeline-and-deploy-httpapi-host-project.md">6. Create a Release pipeline to deploy the HttpApi.Host project</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../7.deployment-succeeded-web-app-not-working-fix-the-issues.md">7. API Deployment succeeded. Web App not working. Fix the issues!</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../8.create-a-web-app-in-the-azure-portal-for-the-blazor-project.md">8. Create a Web App in the Azure Portal for the Blazor project</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../9.add-an-extra-stage-in-the-release-pipeline-for-the-blazor-project.md">9. Add a stage in the Release pipeline to deploy the Blazor project</a></p>
<p><a href="https://github.com/bartvanhoey/AbpGoesAzure/blob/main/tutorial/tutorial/../10.deployment-blazor-project-succeeded-web-app-still-not-working-fix-the-issues.md">10. Blazor Deployment succeeded. Web App not working. Fix the issues!</a></p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a10a62b-7e6d-d27e-40f8-cea21ad3e7f5" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a10a62b-7e6d-d27e-40f8-cea21ad3e7f5" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/consume-an-abp-api-openiddict-from-a-.net-core-console-application-hw067beu</guid>
      <link>https://abp.io/community/posts/consume-an-abp-api-openiddict-from-a-.net-core-console-application-hw067beu</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>openiddict</category>
      <title>Consume an ABP API (OpenIddict) from a .NET Core console Application</title>
      <description>From version 6.0.0 ABP will start to use OpenIddict instead of IdentityServer. In this article, I will show you how you can connect to an OpenIddict protected ABP Framework API from a .NET Core console Application.</description>
      <pubDate>Tue, 09 Aug 2022 19:56:49 Z</pubDate>
      <a10:updated>2026-03-09T17:43:31Z</a10:updated>
      <content:encoded><![CDATA[<h2>Consume an ABP API (OpenIddict) from a .NET Core console Application</h2>
<h2>Introduction</h2>
<p>From version 6.0.0 the ABP Framework will start to use OpenIddict instead of IdentityServer. In this article I will show you how you can connect to an OpenIddict protected ABP Framework API from a .NET Core console Application.</p>
<p>The sample <strong>BookStore ABP Framework</strong> application in this article has been developed with <strong>Blazor</strong> as UI Framework and <strong>SQL Server</strong> as database provider.</p>
<p>The <strong>BookStoreConsole</strong> application is a regular <strong>.NET Core console application</strong>.</p>
<p>I tried to keep this article as simple as possible, as such, there is still some room for code improvement.</p>
<h3>Source code</h3>
<p>The source code of both projects is <a href="https://github.com/bartvanhoey/AbpAddCustomClaimToAccessToken">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution and follow along.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
<li>ABP CLI 8.0.0</li>
</ul>
<h3>Create a new ABP Framework application</h3>
<pre><code class="language-bash">  abp new BookStore -u blazor -o BookStore
</code></pre>
<h3>Implement the Web Application Development tutorial (part1-5)</h3>
<p>To follow along make sure you have a protected BookAppService in the BookStore application. For this article I followed the <strong>Web Application Development tutorial</strong> till <strong>part 5: Authorization</strong>.</p>
<h3>Add the section below in the appsettings.json file of the DbMigrator project</h3>
<pre><code class="language-bash">    &quot;BookStore_Console&quot;: {
        &quot;ClientId&quot;: &quot;BookStore_Console&quot;,
        &quot;ClientSecret&quot;: &quot;1q2w3e*&quot;,
        &quot;RootUrl&quot;: &quot;https://localhost:&lt;your-api-portnumber&gt;&quot;
    }
</code></pre>
<h3>Add a BookStoreConsole client in the OpenIddictDataSeedContributor class of the Domain project</h3>
<pre><code class="language-bash">    // BookStoreConsole Client
    var bookStoreConsoleClientId = configurationSection[&quot;BookStore_Console:ClientId&quot;];
    if (!bookStoreConsoleClientId.IsNullOrWhiteSpace())
    {
        var bookStoreConsoleRootUrl = configurationSection[&quot;BookStore_Console:RootUrl&quot;]?.TrimEnd('/');
        await CreateApplicationAsync(
            name: bookStoreConsoleClientId,
            type: OpenIddictConstants.ClientTypes.Confidential,
            consentType: OpenIddictConstants.ConsentTypes.Implicit,
            displayName: &quot;BookStore Console Application&quot;,
            scopes: commonScopes,
            grantTypes: new List&lt;string&gt;
            {
                OpenIddictConstants.GrantTypes.AuthorizationCode,
                OpenIddictConstants.GrantTypes.Password,
                OpenIddictConstants.GrantTypes.ClientCredentials,
                OpenIddictConstants.GrantTypes.RefreshToken
            },
            secret: configurationSection[&quot;BookStore_Console:ClientSecret&quot;] ?? &quot;1q2w3e*&quot;,
            redirectUri: $&quot;{bookStoreConsoleRootUrl}/authentication/login-callback&quot;
        );
    }
</code></pre>
<h3>Run DbMigrator project</h3>
<p>To apply the settings above you need to run the DbMigrator project. After, check the <strong>OpenIddictApplications</strong> table of the database to see if the <strong>BookStore_Console</strong> client has been added.</p>
<h2>.NET Core console application</h2>
<h3>Create a new .NET Core console application</h3>
<pre><code class="language-bash">    dotnet new console -n BookStoreConsole
</code></pre>
<h3>Install OidcClient nuget package (in terminal window or nuget package manager)</h3>
<pre><code class="language-bash">  dotnet add package IdentityModel.OidcClient --version 5.2.1
</code></pre>
<h3>Add a HttpService class in the root of the project</h3>
<p>When you want to consume a protected API the user has to be <strong>authenticated (username+password)</strong> and <strong>authorized(has the right permissions)</strong>. So, when you call the BookAppService GetListAsync method, in the <strong>header of the request</strong> you need to send <strong>the accesstoken</strong> with.</p>
<p>To obtain the <strong>accesstoken</strong> you can make use of the <strong>nuget package IdentityModel.OidcClient</strong>. All the heavy lifting occurs in the <strong>GetTokensFromBookStoreApi</strong> method (See below). These method <strong>sends a request</strong> to the <strong>disco.TokenEndpoint</strong> of the BookStoreApi and <strong>obtains a TokenResponse</strong>. If the correct properties are sent and the API is running, you should obtain a <strong>TokenResponse (AccessToken, IdentityToken, Scope, ...)</strong></p>
<p>Afterwards the obtained accesstoken is used in the <strong>SetBearerToken()</strong> of the httpClient.</p>
<p>When you make a request now to the protected BookStore API with the httpClient, the accesstoken is sent with. The BookStore API receives this request and checks the <strong>validity of the accesstoken</strong> and the <strong>permissions</strong>. If these conditions are met, the GetListAsync method of the BookAppService returns the list of books.</p>
<pre><code class="language-csharp">using IdentityModel.Client;

public class HttpService
{
    public async Task&lt;Lazy&lt;HttpClient&gt;&gt; GetHttpClientAsync(bool setBearerToken, string apiEndpoint)
    {
        var client = new Lazy&lt;HttpClient&gt;(() =&gt; new HttpClient());

        if (setBearerToken) client.Value.SetBearerToken(await GetAccessToken(apiEndpoint));

        client.Value.BaseAddress = new Uri(apiEndpoint);
        return await Task.FromResult(client);
    }
    private static async Task&lt;TokenResponse&gt; GetTokensFromBookStoreApi(string apiEndpoint)
    {
        var discoveryCache = new DiscoveryCache(apiEndpoint);
        var disco = await discoveryCache.GetAsync();
        var httpClient = new Lazy&lt;HttpClient&gt;(() =&gt; new HttpClient());
        var response = await httpClient.Value.RequestPasswordTokenAsync(new PasswordTokenRequest
        {
            Address = disco.TokenEndpoint, // apiEndpoint/connect/token
            ClientId = &quot;BookStore_Console&quot;,
            ClientSecret = &quot;1q2w3e*&quot;,
            UserName = &quot;admin&quot;,
            Password = &quot;1q2w3E*&quot;,
            Scope = &quot;openid offline_access address email phone profile roles BookStore&quot;,
        });
        return response.IsError ? new TokenResponse() : response;
    }
    private static async Task&lt;string&gt; GetAccessToken(string apiEndpoint) =&gt; (await GetTokensFromBookStoreApi(apiEndpoint)).AccessToken;
}

</code></pre>
<h3>Main Method</h3>
<p>Below you see the content of the <strong>Program.cs</strong> file. A new <strong>HttpService</strong> gets created and the <strong>GetHttpClientAsync</strong> method is called to get a httpClient.
Next, we make a request to the BookStore API to obtain the list of books.</p>
<p>Do not forget to change the apiEndpoint to the correct ABP Framework API endpoint (Swagger pager).</p>
<pre><code class="language-csharp">using System.Text.Json;
using static System.Console;

// if setBearerToken = false, should throw JsonReaderException: 'json cannot be serialized.'
// if setBearerToken = true, API should be called an list of books should be returned
const bool setBearerToken = false;
const string apiEndpoint = &quot;https://localhost:44388/&quot;;

try
{
    var httpClient = await new HttpService().GetHttpClientAsync(setBearerToken, apiEndpoint);

    var response = await httpClient.Value.GetAsync($&quot;{apiEndpoint}api/app/book&quot;);
    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadAsStringAsync();

    ListResultDto&lt;BookDto&gt;? books = new();


    books = JsonSerializer.Deserialize&lt;ListResultDto&lt;BookDto&gt;&gt;(json);
    WriteLine(&quot;====================================&quot;);
    if (books?.Items != null)
        foreach (var book in books.Items)
            WriteLine(book.Name);

}
catch (HttpRequestException)
{
    WriteLine(&quot;Is apiEndpoint correct?&quot;);
}
catch (JsonException)
{
    WriteLine(&quot;setBearerToken to true&quot;);
}
WriteLine(&quot;====================================&quot;);

</code></pre>
<h2>Run API and .NET Core console application</h2>
<p>Run the <strong>BookStore.HttpApi.Host</strong> of the ABP Framework application first. Start the .NET Core console application next. Below is the result when the accesstoken is successfully set.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpAddCustomClaimToAccessToken/gh-pages/Images/books_returned_from_api.jpg" alt="Books returned from API" /></p>
<p>If you set the variable <strong>setBearerToken</strong> to false, you will obtain a response from the API that <strong>cannot be deserialized</strong> and a <strong>JsonReaderException</strong> will be thrown.</p>
<p>Congratulations, you can now consume an OpenIddict protected ABP Framework API from a .NET Core console application!</p>
<p>Check out the <a href="https://github.com/bartvanhoey/AbpAddCustomClaimToAccessToken">source code</a> of this article on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/a-baseefcorerepository-class-that-inherits-from-efcorerepository-vdd1s0h2</guid>
      <link>https://abp.io/community/posts/a-baseefcorerepository-class-that-inherits-from-efcorerepository-vdd1s0h2</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>EfCore</category>
      <title>A BaseEfCoreRepository class that inherits from EfCoreRepository</title>
      <description>In this article,  I explain how you can create a BaseEfCoreRepository class that inherits from the EfCoreRepository class of the ABP Framework.</description>
      <pubDate>Fri, 05 Aug 2022 15:10:23 Z</pubDate>
      <a10:updated>2026-03-09T09:04:28Z</a10:updated>
      <content:encoded><![CDATA[<h2>A BaseEfCoreRepository class that inherits from EfCoreRepository</h2>
<h2>Introduction</h2>
<p>Today, I wanted to know if the content of a table in the database had changed or not. I ended up with creating a <strong>ComputeHash</strong> method that returns a different hash when the content of a table in the database has changed.</p>
<p>After I created this method, I asked myself, how can I expose this method to my other Repository classes? This is the solution that I came up with.</p>
<p>Keep in mind that, the code in this article is <strong>by no means production-ready</strong>, as I wanted to keep the example simple to understand. And the ComputeHash method has also room for improvement.</p>
<h2>Source Code</h2>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpBaseEfCoreRepository">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to run the solution.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>Vscode, Visual Studio 2022, or another compatible IDE.</li>
<li>ABP CLI Version 8.0.0</li>
</ul>
<h2>Development</h2>
<h3>Create a new ABP Framework Application</h3>
<ul>
<li>Install or update the ABP CLI</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new BookStore -u blazor -o BookStore
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>BookStore.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>BookStore.HttpApi.Host</code> application to start the server-side.</li>
</ul>
<p>To follow along, you will need to have the first part of the <a href="https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=Blazor&amp;DB=EF">BookStore tutorial</a> ready.</p>
<h2>Create a BaseEfCoreRepository class</h2>
<p>Create a folder <strong>BaseEfCoreRepo</strong> in the <strong>EntityFrameworkCore</strong> project and add a class <strong>BaseEfCoreRepository.cs</strong>.
As you can see below, the class inherits from <strong>EfCoreRepository</strong> and is made abstract. The ComputeHash method calculates a Hash for a given list of string input.</p>
<p>You also need to implement the DeleteAsync, the DeleteManyAsync, the FindAsync and the GetAsync methods.</p>
<pre><code class="language-csharp">using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BookStore.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using static System.String;
using static System.Text.Encoding;

namespace BookStore.BaseEfCoreRepo
{
    public abstract class BaseEfCoreRepository&lt;TEntity, TKey&gt;(IDbContextProvider&lt;BookStoreDbContext&gt; dbContextProvider)
        : EfCoreRepository&lt;BookStoreDbContext, TEntity&gt;(dbContextProvider),
            IBasicRepository&lt;TEntity, TKey&gt;
        where TEntity : class, IEntity&lt;TKey&gt;
    {
        protected string ComputeHash(IEnumerable&lt;string&gt; strings)
        {
            var items = strings.ToList();
            if (items.Count == 0)
            {
                return Empty;
            }

            using var sha1 = SHA1.Create();
            var bytes = sha1.ComputeHash(UTF8.GetBytes(Concat(items)));
            var builder = new StringBuilder(bytes.Length * 2);
            foreach (var b in bytes) // can be &quot;x2&quot; if you want lowercase
            {
                builder.Append(b.ToString(&quot;X2&quot;));
            }

            return builder.ToString();
        }

        public async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
            =&gt; await base.DeleteAsync(x =&gt; x.Id.Equals(id), autoSave, cancellationToken);

        public async Task DeleteManyAsync(IEnumerable&lt;TKey&gt; ids, bool autoSave = false,
            CancellationToken cancellationToken = default)
        {
            var entities = await (await GetDbSetAsync()).Where(x =&gt; ids.Contains(x.Id)).ToListAsync(cancellationToken);
            await base.DeleteManyAsync(entities, autoSave, cancellationToken);
        }

        public async Task&lt;TEntity?&gt; FindAsync(TKey id, bool includeDetails = true,
            CancellationToken cancellationToken = default)
            =&gt; await base.FindAsync(x =&gt; x.Id.Equals(id), includeDetails, cancellationToken);

        public async Task&lt;TEntity&gt; GetAsync(TKey id, bool includeDetails = true,
            CancellationToken cancellationToken = default)
            =&gt; await base.GetAsync(x =&gt; x.Id.Equals(id), includeDetails, cancellationToken);
    }
}

</code></pre>
<h2>Interface <strong>IHaveGetHashAsyncRepository.cs</strong></h2>
<p>Create a folder <strong>Interfaces</strong> in the <strong>Domain project</strong> and add the interface <strong>IHaveGetHashAsyncRepository.cs</strong></p>
<p>Every Repository that implements the <strong>IHaveGetHashAsyncRepository</strong> interface will need to implement the <strong>GetHashAsync</strong> method</p>
<pre><code class="language-csharp">using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;

namespace BookStore.Interfaces
{
    namespace BookStore.Domain.Interfaces
    {
        public interface IHaveGetHashAsyncRepository&lt;TEntity, TKey&gt; : IRepository&lt;TEntity, TKey&gt;
            where TEntity : class, IEntity&lt;TKey&gt;
        {
            Task&lt;string&gt; GetHashAsync(Guid? tenantId);
        }
    }
}
</code></pre>
<h2>IBookRepository in the <strong>Domain project</strong></h2>
<p>Create an <strong>IBookRepository</strong> interface in the <strong>Books</strong> folder of the <strong>Domain</strong> project that implements the <strong>IHaveGetHashAsyncRepository</strong> interface</p>
<pre><code class="language-csharp">using System;
using BookStore.Interfaces.BookStore.Domain.Interfaces;

namespace BookStore.Books
{
    public interface IBookRepository : IHaveGetHashAsyncRepository&lt;Book, Guid&gt;;
}
</code></pre>
<h2>BookRepository in the <strong>EntityFrameworkCore project</strong></h2>
<p>Create a <strong>BookRepository</strong> class in the <strong>Books</strong> folder of the <strong>EntityFrameworkCore</strong> project.
Let the <strong>BookRepository</strong> class inherit from the <strong>BaseEfCoreRepository</strong> class you created, and also implement the <strong>IBookRepository</strong> interface.</p>
<p>When you implement the <strong>IBookRepository</strong> interface you will need to have a <strong>GetHashAsync</strong> method in your <strong>BookRepository</strong> class.
In method you can now call the <strong>ComputeHash</strong> method from the <strong>BaseEfCoreRepository</strong> class.</p>
<pre><code class="language-csharp">using System;
using System.Linq;
using System.Threading.Tasks;
using BookStore.BaseEfCoreRepo;
using BookStore.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;

namespace BookStore.Books
{
    public class BookRepository(IDbContextProvider&lt;BookStoreDbContext&gt; dbContextProvider)
        : BaseEfCoreRepository&lt;Book, Guid&gt;(dbContextProvider), IBookRepository
    {
        public async Task&lt;string&gt; GetHashAsync(Guid? tenantId)
        {
            using var disposable = CurrentTenant.Change(tenantId);
            return ComputeHash(
                (await GetDbContextAsync()).Books.Select(x =&gt; $&quot;{x.Id}{x.Name}{x.Type}{x.PublishDate}{x.Price}&quot;));
        }
    }
}
</code></pre>
<h2>GetHashDto class and GetHashAsync interface method definition</h2>
<p>Create a <strong>GetHasDto</strong> class in the <strong>Books</strong> folder of the <strong>Application.Contracts</strong></p>
<pre><code class="language-csharp">using System;

namespace BookStore.Books
{
    public class GetHashDto
    {
        public Guid? TenantId { get; set; }
    }
}
</code></pre>
<p>In the <strong>IBookAppService</strong> interface add a <strong>GetHashAsync</strong> method definition to the</p>
<pre><code class="language-csharp">using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace BookStore.Books
{
    public interface IBookAppService : ICrudAppService&lt;BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto&gt;
    {        
         Task&lt;string&gt; GetHashAsync(GetHashDto input);
    }
}
</code></pre>
<h2>BookAppService in the Application project</h2>
<p>Update the content of <strong>BookAppService</strong> class in the <strong>Application</strong> project.</p>
<pre><code class="language-csharp">using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace BookStore.Books
{
    public class BookAppService(IBookRepository repository)
        : CrudAppService&lt;Book, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto&gt;(repository),
            IBookAppService
    {
        public async Task&lt;string&gt; GetHashAsync(GetHashDto input) 
            =&gt; await repository.GetHashAsync(input.TenantId);
    }
}
</code></pre>
<p>I added the <strong>GetHashAsync</strong> method in the BookAppService only for testing purposes.</p>
<h2>Testing</h2>
<p>Start the <strong>BookStore.HttpApi.Host</strong> project to have the <strong>Swagger</strong> page launched.
Navigate to the <strong>api/app/book/hash endpoint</strong> in the Swagger page.</p>
<p>Click first on the <strong>Try it out</strong> button and then on the <strong>Execute</strong> button.</p>
<p>The <strong>GetHashAsync</strong> method in the <strong>BookAppService</strong> will be hit and should return every time the same hash string when you have the same 2 books in the database.</p>
<p>If you delete one of them, the method will return another hash.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpBaseEfCoreRepository/main/images/swaggerhashendpoint.jpg" alt="ImportUser endpoint" /></p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpBaseEfCoreRepository.git">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/abp-framework-consumed-by-a-.net-maui-app-e74fmblw</guid>
      <link>https://abp.io/community/posts/abp-framework-consumed-by-a-.net-maui-app-e74fmblw</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>maui</category>
      <title>ABP Framework consumed by a .NET MAUI app</title>
      <description>In this article, I will show you how you can consume an ABP Framework application with a .NET Maui app.</description>
      <pubDate>Thu, 10 Feb 2022 23:29:27 Z</pubDate>
      <a10:updated>2026-03-09T18:24:01Z</a10:updated>
      <content:encoded><![CDATA[<h2>ABP Framework consumed by .NET MAUI</h2>
<p>In this article, I will show you how you can consume an <strong>ABP Framework</strong> application with a <strong>.NET Maui</strong> app.</p>
<p>The ABP Framework application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<h2>Source Code</h2>
<p>The source code of the ABP Framework and the .NET Maui application is <a href="https://github.com/bartvanhoey/AbpMaui">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to run the solution and follow along. You will also need to have Visual Studio 2022 Preview set up for .NET Maui development.</p>
<ul>
<li>.NET 6.0 SDK</li>
<li>Visual Studio 2022 Preview</li>
<li>Ngrok</li>
</ul>
<h2>Create ABP Framework application</h2>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpMauiApi -u blazor -o AbpMauiApi
</code></pre>
<h3>IdentityServer Settings</h3>
<h4>Add AbpMauiApi_Maui section in appsettings.json file of the AbpMauiApi.DbMigrator project</h4>
<pre><code class="language-json">    // change the &lt;replace-me-with-the-abp-api-port&gt; with the port where the Swagger page is running on
    &quot;AbpMauiApi_Maui&quot;: {
        &quot;ClientId&quot;: &quot;AbpMauiApi_Maui&quot;,
        &quot;ClientSecret&quot;: &quot;1q2w3e*&quot;,
        &quot;RootUrl&quot;: &quot;https://localhost:&lt;replace-me-with-the-abp-api-port&gt;/&quot;
    }
</code></pre>
<h4>Add Maui client IdentityServer configuration</h4>
<p>In the <strong>CreateClientAsync</strong> method in class <strong>IdentityServerDataSeedContributor</strong> of the <strong>AbpMauiApi.Domain</strong> project.</p>
<pre><code class="language-csharp">    // Maui Client
    var MauiClientId = configurationSection[&quot;AbpMauiApi_Maui:ClientId&quot;];
    if (!MauiClientId.IsNullOrWhiteSpace())
    {
        var MauiRootUrl = configurationSection[&quot;AbpMauiApi_Maui:RootUrl&quot;].TrimEnd('/');
        await CreateClientAsync(
            name: MauiClientId,
            scopes: commonScopes,
            grantTypes: new[] { &quot;authorization_code&quot;, &quot;password&quot; },
            secret: configurationSection[&quot;AbpMauiApi_Maui:ClientSecret&quot;]?.Sha256(),
            requireClientSecret: false,
            redirectUri: &quot;mauiclients:/authenticated&quot;,
            postLogoutRedirectUri: &quot;mauiclients:/signout-callback-oidc&quot;,
            corsOrigins: new[] { MauiRootUrl.RemovePostFix(&quot;/&quot;) }
        );
    }
</code></pre>
<h3>Apply Migrations and Run the Application</h3>
<ul>
<li>Run the <code>AbpMauiApi.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpMauiApi.HttpApi.Host</code> application to start the API.</li>
</ul>
<h2>Ngrok to the rescue</h2>
<p>When you are running the ABP Framework API on your local computer, the endpoints are reachable on <a href="https://localhost:%3Cyour-port-number%3E/api/%3Cpath%3E">https://localhost:&lt;your-port-number&gt;/api/&lt;path&gt;</a>.</p>
<p>Although you can test out these endpoints on your local machine, it will fail in a .NET Maui application.
A .NET Maui app considers localhost as its own localhost address (mobile device or emulator) and not that of your computer.</p>
<p>To overcome this problem you can <strong>make use of ngrok</strong>. With ngrok you can <strong>mirror your localhost address to a publicly available url</strong>.</p>
<h3>Download and install ngrok</h3>
<p>Go to the <a href="https://ngrok.com/">ngrok page</a>, create an account, and download and install Ngrok.</p>
<h3>Run the Ngrok command</h3>
<p>Open a command prompt and enter the command below to start ngrok</p>
<pre><code class="language-bash"> // change the &lt;replace-me-with-the-abp-api-port&gt; with the port where the Swagger page is running on
ngrok.exe http -region eu https://localhost:&lt;replace-with-the-abp-api-port-number&gt;/
</code></pre>
<p>After running this command, you will receive the following output:
The API is now publicly available on <a href="https://f7db-2a02-810d-98c0-576c-647e-cd22-5b-e9a3.eu.ngrok.io">https://f7db-2a02-810d-98c0-576c-647e-cd22-5b-e9a3.eu.ngrok.io</a></p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpMaui/main/Images/ngrok.jpg" alt="ngrok in action" /></p>
<h3>Copy the ngrok url</h3>
<p>Copy the <strong>lower forwarding url</strong> as you will need it for use in the .NET MAUI app.</p>
<h2>Create a .NET Maui app</h2>
<h3>Nuget packages</h3>
<p>Open the <strong>Package Manager Console</strong> window and run the commands below:</p>
<pre><code class="language-bash">    dotnet add package Refractored.MvvmHelpers --version 1.6.2
    dotnet add package System.IdentityModel.Tokens.Jwt --version 6.15.1 
</code></pre>
<h3>MainPage.xaml</h3>
<p>Replace the content of the MainPage.xaml with the content below:</p>
<pre><code class="language-html">&lt;ContentPage
  xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;
  xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
  xmlns:local=&quot;clr-namespace:AbpMauiApp.ViewModels;assembly=AbpMauiApp&quot;
  x:Class=&quot;AbpMauiApp.MainPage&quot;
  BackgroundColor=&quot;{DynamicResource SecondaryColor}&quot;&gt;

    &lt;ContentPage.BindingContext&gt;
        &lt;local:MainViewModel /&gt;
    &lt;/ContentPage.BindingContext&gt;

    &lt;StackLayout Padding=&quot;10&quot;&gt;
        &lt;Label HorizontalOptions=&quot;Center&quot;  Margin=&quot;5,15,5,15&quot; Text=&quot;Hi, ABP Framework! Nice to meet you!&quot; TextColor=&quot;#7b5fdf&quot;  FontSize=&quot;18&quot;
            FontAttributes=&quot;Bold&quot;/&gt;
        &lt;Image Grid.Row=&quot;4&quot;
                Source=&quot;dotnet_bot.png&quot;
                SemanticProperties.Description=&quot;Cute dot net bot waving hi to you!&quot;
                WidthRequest=&quot;250&quot;
                HeightRequest=&quot;310&quot;
                HorizontalOptions=&quot;Center&quot; /&gt;

        &lt;Entry Text=&quot;{Binding LoginUserName}&quot; Placeholder=&quot;Enter user name...&quot; /&gt;
        &lt;Entry Text=&quot;{Binding LoginPassword}&quot;  IsPassword=&quot;true&quot; Placeholder=&quot;Enter password...&quot; /&gt;
        &lt;Button Text=&quot;Login&quot;  FontAttributes=&quot;Bold&quot; Command=&quot;{Binding LoginUserCommand}&quot; HorizontalOptions=&quot;FillAndExpand&quot; /&gt;
        &lt;Label Margin=&quot;20&quot; HorizontalOptions=&quot;Center&quot;  Text=&quot;{Binding LoginUserMessage}&quot; TextColor=&quot;Green&quot;  FontSize=&quot;18&quot;
            FontAttributes=&quot;Bold&quot;/&gt;
    &lt;/StackLayout&gt;

&lt;/ContentPage&gt;
</code></pre>
<h3>MainViewModel.cs</h3>
<p>Add a <strong>MainViewModel</strong> class in a <strong>ViewModels</strong> folder.</p>
<pre><code class="language-csharp">using AbpMauiApp.Services;
using MvvmHelpers;
using MvvmHelpers.Commands;
using System.Windows.Input;

namespace AbpMauiApp.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        private string _loginUserMessage, _loginUserName, _loginPassword;
        private AsyncCommand _loginUserCommand;

        public ICommand LoginUserCommand =&gt; _loginUserCommand ??=new AsyncCommand(LoginUserAsync);

        private async Task LoginUserAsync() 
            =&gt; LoginUserMessage= await new IdentityService().LoginAsync(LoginUserName, LoginPassword);

        public string LoginUserMessage
        {
            get =&gt; _loginUserMessage;
            set =&gt; SetProperty(ref _loginUserMessage, value);
        }

        public string LoginUserName
        {
            get =&gt; _loginUserName;
            set =&gt; SetProperty(ref _loginUserName, value);
        }

        public string LoginPassword
        {
            get =&gt; _loginPassword;
            set =&gt; SetProperty(ref _loginPassword, value);
        }
    }
}

</code></pre>
<h3>IdentityService.cs</h3>
<p>Add an <strong>IdentityService</strong> class to a <strong>Services</strong> folder</p>
<pre><code class="language-csharp">using System.Text;
using System.Text.Json;

namespace AbpMauiApp.Services
{
    public class IdentityService
    {
        public async Task&lt;string&gt; LoginAsync(string userName, string password)
        {
            string clientId = &quot;AbpMauiApi_Maui&quot;;
            string clientSecret = &quot;1q2w3e*&quot;;
            string scope = &quot;email openid profile role phone address AbpMauiApi&quot;; ;
            string ngrokUrl = &quot;&lt;replace-me-with-the-ngrok-url&gt;&quot;;

            var data = $&quot;grant_type=password&amp;username={userName}&amp;password={password}&amp;client_id={clientId}&amp;client_secret={clientSecret}&amp;scope={scope}&quot;;

            var content = new StringContent(data, Encoding.UTF8, &quot;application/x-www-form-urlencoded&quot;);

            var httpClient = new HttpClient(GetHttpClientHandler());
            var response = await httpClient.PostAsync($&quot;{ngrokUrl}/connect/token&quot;, content);
            response.EnsureSuccessStatusCode();

            var stringResult = await response.Content.ReadAsStringAsync();
            var loginResult = JsonSerializer.Deserialize&lt;IdentityDto&gt;(stringResult, Options);

            if (string.IsNullOrWhiteSpace(loginResult.access_token)) return &quot;UnAuthorized&quot;;
            return &quot;AccessToken received!&quot;;
            
        }

        private HttpClientHandler GetHttpClientHandler()
        {
            // EXCEPTION: Javax.Net.Ssl.SSLHandshakeException: 'java.security.cert.CertPathValidatorException:
            // Trust anchor for certification path not found.'
            // SOLUTION: 
            // ATTENTION: DO NOT USE IN PRODUCTION 

            var httpClientHandler = new HttpClientHandler
            {
                ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =&gt; { return true; }
            };

            return httpClientHandler;
        }

        private JsonSerializerOptions Options =&gt; new()
        {
            WriteIndented = true,
            PropertyNameCaseInsensitive = true 
        };

    }

    public class IdentityDto
    {
        public string access_token { get; set; }
        public int expires_in { get; set; }
        public string token_type { get; set; }
        public string scope { get; set; }
        public string error { get; set; }
        public string error_description { get; set; }
    }

}


</code></pre>
<h2>Test the result</h2>
<p>Run the <strong>HttpApi.Host</strong> project and make sure <strong>Ngrok</strong> is running too.
Start the <strong>.NET Maui app</strong>, enter the credentials (user name: <strong>admin</strong> - password: <strong>1q2w3E</strong>*) and click the <strong>Login</strong> button.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpMaui/main/Images/accesstoken_received.jpg" alt="Access token received" /></p>
<p>Et voilà! As you can see, you received an access token from the <strong>ABP Framework API</strong>. Now you can start consuming the API!</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpMaui">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/import-external-users-into-the-users-table-from-an-abp-framework-application-7lnyw415</guid>
      <link>https://abp.io/community/posts/import-external-users-into-the-users-table-from-an-abp-framework-application-7lnyw415</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>external-users</category>
      <title>Import External Users into the Users Table from an ABP Framework application</title>
      <description>In this article, I will explain to you how you can import users from an external system into the users' table of an ABP Framework application.</description>
      <pubDate>Thu, 23 Dec 2021 21:24:17 Z</pubDate>
      <a10:updated>2026-03-09T07:34:22Z</a10:updated>
      <content:encoded><![CDATA[<h2>Import Users from an External System into the Users Table from an ABP Framework application</h2>
<h2>Introduction</h2>
<p>In this article, I will show you a way to import users from an external system into the <strong>AbpUsers</strong> table of an <strong>ABP Framework</strong> application. Watch out, the code in this article is <strong>not production-ready</strong>, as I wanted to keep the example simple to understand.</p>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<h2>Source Code</h2>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpUserImport">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to run the solution.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>Vscode, Visual Studio 2022, or another compatible IDE.</li>
<li>ABP Cli 8.0.0</li>
</ul>
<h2>Development</h2>
<h3>Create a new ABP Framework Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpUserImport -u blazor -o AbpUserImport
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpUserImport.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpUserImport.HttpApi.Host</code> application to start the API.</li>
</ul>
<h2>Add an extra ImportUserId property to the IdentityUser</h2>
<p>Open the file <strong>AbpUserImportEfCoreEntityExtensionMappings.cs</strong> in folder <strong>EntityFrameworkCore</strong> of the <strong>EntityFrameworkCore</strong> project and update its content.</p>
<pre><code class="language-csharp">using System;
using Volo.Abp.Identity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Threading;

namespace AbpUserImport.EntityFrameworkCore;

public static class AbpUserImportEfCoreEntityExtensionMappings
{
    private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

    public static void Configure()
    {
        AbpUserImportGlobalFeatureConfigurator.Configure();
        AbpUserImportModuleExtensionConfigurator.Configure();

        OneTimeRunner.Run(() =&gt;
        {
            // Other code here ...
            // This piece of code adds an extra ImportUserId property to the IdentityUser class
            ObjectExtensionManager.Instance
            .MapEfCoreProperty&lt;IdentityUser, Guid?&gt;(
                &quot;ImportUserId&quot;,
                (_, propertyBuilder) =&gt;
                {
                    propertyBuilder.HasMaxLength(36);
                }
            );
        });
    }
}
</code></pre>
<h2>Run DbMigrator project to apply Migrations</h2>
<p>In the previous step, we changed our Model, so we need to tell it to EntityFrameworkCore.
Open a command prompt in the EntityFrameworkCore project and enter the command below:</p>
<pre><code class="language-bash">dotnet ef migrations add ImportUserIdAdded
</code></pre>
<p>When this command has succeeded, <strong>run the DbMigrator project</strong> to <strong>apply the Database Migrations</strong>.</p>
<h2>IImportUserAppService in Application.Contracts project</h2>
<p>Create an <strong>ImportUsers</strong> folder in the <strong>Application.Contracts</strong> project and add an <strong>IImportUserAppService.cs</strong> interface file.
The IImportUserAppService has 1 Method Definition that takes a <strong>CreateImportUserDto</strong>. See below.</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;

namespace AbpUserImport.ImportUsers
{
    public interface IImportUserAppService
    {
        Task CreateManyAsync(CreateImportUserDto input);
    }

    public class CreateImportUserDto
    {
        public List&lt;ImportUserDto&gt; Items { get; set; } = [];
        public Guid? TenantId { get; set; }
    }

    public class ImportUserDto
    {
        [Required, DataType(DataType.EmailAddress)] public string Email { get; set; } = &quot;&quot;;
        [Required] public string Password { get; set; } = &quot;&quot;;
        public Guid ImportUserId { get; set; }
    }
}
</code></pre>
<h2>ImportUserAppService in the Application project</h2>
<p>Create an <strong>ImportUsers</strong> folder in the <strong>Application</strong> project and add an <strong>ImportUserAppService.cs</strong> class file and paste it into the code below.</p>
<p>In the CreateManyAsync method, we first call the <strong>SetIdentityOptions</strong> method to set the different options for User and Password. Then we loop over every user and call the <strong>InsertImportUserInDatabaseAsync</strong>. In this method, a new IdentityUser is instantiated and the <strong>ImportUserId is set</strong> afterward. Finally, the user is created by calling the <strong>CreateAsync</strong> method of the <strong>IdentityUserManager</strong>.</p>
<pre><code class="language-csharp">using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Data;
using Volo.Abp.Guids;
using Volo.Abp.Identity;

namespace AbpUserImport.ImportUsers
{
    public class ImportUserAppService(IGuidGenerator guidGenerator, IdentityUserManager identityUserManager)
        : AbpUserImportAppService, IImportUserAppService
    {
        public async Task CreateManyAsync(CreateImportUserDto input)
        {
            using (CurrentTenant.Change(input.TenantId))
            {
                SetIdentityOptions();
                foreach (var item in input.Items)
                {
                    await InsertImportUserInDatabaseAsync(item, input.TenantId);
                }
            }
        }

        // You will probably need to adapt this method to your needs
        private void SetIdentityOptions()
        {
            identityUserManager.Options.User.RequireUniqueEmail = true;
            identityUserManager.Options.User.AllowedUserNameCharacters = $&quot;{identityUserManager.Options.User.AllowedUserNameCharacters}&quot;; // add special characters here!
            identityUserManager.Options.Password.RequireDigit = false;
            identityUserManager.Options.Password.RequireUppercase = false;
            identityUserManager.Options.Password.RequireNonAlphanumeric = false;
            identityUserManager.Options.Password.RequireLowercase = false;
            identityUserManager.Options.Password.RequiredLength = 1;
        }

        private async Task InsertImportUserInDatabaseAsync(ImportUserDto user, Guid? tenantId)
        {
            var identityUser = new IdentityUser(guidGenerator.Create(), user.Email, user.Email, tenantId);
            identityUser.SetProperty(&quot;ImportUserId&quot;, user.ImportUserId.ToString());
            var createdUser = await identityUserManager.CreateAsync(identityUser, user.Password, true);
            createdUser.CheckErrors();
        }
    }
}
</code></pre>
<h2>Testing</h2>
<p>Start the <strong>AbpUserImport.HttpApi.Host</strong> project to have the <strong>Swagger</strong> page launched.
Navigate to the <strong>ImportUser endpoint</strong> in the Swagger page.</p>
<p>Click first on the <strong>Try it out</strong> button and then on the <strong>Execute</strong> button. The <strong>CreateManyAsync</strong> method in the <strong>ImportUserAppService</strong> will be hit and 1 user will be added to the <strong>AbpUsers</strong> table in the database.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpUserImport/gh-pages/AbpFramework/images/Swagger_ImportUser_Endpoint.png" alt="ImportUser endpoint" /></p>
<p>Et voilà! You can now <strong>import users from an external system</strong> into the <strong>AbpUsers</strong> table of the ABP Framework.</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpUserImport.git">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/how-to-filter-a-standard-paged-list-in-the-abp-framework-bl31rsn6</guid>
      <link>https://abp.io/community/posts/how-to-filter-a-standard-paged-list-in-the-abp-framework-bl31rsn6</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>filtering</category>
      <category>Blazorise</category>
      <category>blazor</category>
      <title>How to Filter a standard paged list in the ABP Framework</title>
      <description>In this article, I will show you how to filter a paged list (Blazorise DataGrid component) in an ABP Framework application with a Blazor user interface.</description>
      <pubDate>Sat, 11 Dec 2021 17:15:42 Z</pubDate>
      <a10:updated>2026-03-09T17:14:42Z</a10:updated>
      <content:encoded><![CDATA[<h2>How to filter a standard paged list in the ABP Framework</h2>
<h2>Introduction</h2>
<p>In this article, I will show you how to filter a paged list (<strong>Blazorise DataGrid component</strong>) in an ABP Framework application with a Blazor user interface.</p>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<h2>Source Code</h2>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpFilter">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to run the solution.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>vscode, Visual Studio 2022, or another compatible IDE.</li>
<li>ABP Cli 8.0.0</li>
</ul>
<h2>Development</h2>
<h3>Create a new ABP Framework Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpFilter -u blazor -o AbpFilter
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Run the <code>AbpFilter.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpFilter.HttpApi.Host</code> application to start the server-side.</li>
<li>Run the <code>AbpFilter.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<h3>BookStore</h3>
<p>To have a simple BookStore application to test with, add the code from the <a href="https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=Blazor&amp;DB=EF">BookStore Tutorial</a> (Part1-2).</p>
<h2>Domain layer</h2>
<h3>BookFilter class</h3>
<p>Add a <strong>BookFilter</strong> class to the Books folder of the <strong>Domain</strong> project</p>
<pre><code class="language-csharp">// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace AbpFilter.Books
{
    public class BookFilter
    {
        public string? Id { get; set; }
        public string? Name { get; set; }
        public string? PublishDate { get; set; }
        public string? Price { get; set; }
    }
}
</code></pre>
<h3>IBookRepository interface</h3>
<p>Add an <strong>IBookRepository</strong> interface to the Books folder of the <strong>Domain</strong> project.</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AbpFilter.Domain.Books;
using Volo.Abp.Domain.Repositories;

namespace AbpFilter.Books
{
    public interface IBookRepository : IRepository&lt;Book, Guid&gt;
    {
        Task&lt;List&lt;Book&gt;&gt; GetListAsync(int skipCount, int maxResultCount, string sorting = &quot;Name&quot;, BookFilter? filter = null);
        Task&lt;int&gt; GetTotalCountAsync(BookFilter filter);
    }
}
</code></pre>
<h2>Database layer</h2>
<h2>BookRepository class</h2>
<p>Add a <strong>BookRepository</strong> class to the Books folder of the <strong>EntityFrameworkCore</strong> project.</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using AbpFilter.Domain.Books;
using AbpFilter.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;

namespace AbpFilter.Books
{
    public class BookRepository(IDbContextProvider&lt;AbpFilterDbContext&gt; dbContextProvider)
        : EfCoreRepository&lt;AbpFilterDbContext, Book, Guid&gt;(dbContextProvider), IBookRepository
    {
        public async Task&lt;List&lt;Book&gt;&gt; GetListAsync(int skipCount, int maxResultCount, string sorting = &quot;Name&quot;, BookFilter filter = null)
        {
            var dbSet = await GetDbSetAsync();
            var books = await dbSet
                .WhereIf(!filter.Id.IsNullOrWhiteSpace(), x =&gt; x.Id.ToString().Contains(filter.Id))
                .WhereIf(!filter.Name.IsNullOrWhiteSpace(), x =&gt; x.Name.Contains(filter.Name))
                .WhereIf(!filter.Price.IsNullOrWhiteSpace(), x =&gt; x.Price.ToString().Contains(filter.Price))
                .WhereIf(!filter.PublishDate.IsNullOrWhiteSpace(), x =&gt; x.PublishDate.ToString().Contains(filter.PublishDate))
                .OrderBy(sorting)
                .Skip(skipCount)
                .Take(maxResultCount)
                .ToListAsync();
            return books;
        }

        public async Task&lt;int&gt; GetTotalCountAsync(BookFilter filter)
        {
            var dbSet = await GetDbSetAsync();
            var books = await dbSet
                .WhereIf(!filter.Id.IsNullOrWhiteSpace(), x =&gt; x.Id.ToString().Contains(filter.Id))
                .WhereIf(!filter.Name.IsNullOrWhiteSpace(), x =&gt; x.Name.Contains(filter.Name))
                .WhereIf(!filter.Price.IsNullOrWhiteSpace(), x =&gt; x.Price.ToString().Contains(filter.Price))
                .WhereIf(!filter.PublishDate.IsNullOrWhiteSpace(), x =&gt; x.PublishDate.ToString().Contains(filter.PublishDate))
                .ToListAsync();
            return books.Count;
        }
    }
}
</code></pre>
<h2>Application layer</h2>
<h3>BookPagedAndSortedResultRequestDto class</h3>
<p>Add a <strong>BookPagedAndSortedResultRequestDto</strong> class to the Books folder of the <strong>Application.Contracts</strong> project.
This class inherits the <strong>PagedAndSortedResultRequestDto</strong> class.</p>
<pre><code class="language-csharp">using Volo.Abp.Application.Dtos;

namespace AbpFilter.Books
{
    public class BookPagedAndSortedResultRequestDto : PagedAndSortedResultRequestDto
    {
        public string? Id { get; set; } = &quot;&quot;;
        public string? Name { get; set; } = &quot;&quot;;
        public string? PublishDate { get; set; } = &quot;&quot;;
        public string? Price { get; set; } = &quot;&quot;;
    }
}
</code></pre>
<h3>IBookAppService interface</h3>
<p>Update the <strong>IBookAppService</strong> interface in the Books folder of the <strong>Application.Contracts</strong> project.</p>
<pre><code class="language-csharp">using System;
using Volo.Abp.Application.Services;

namespace AbpFilter.Books
{
    public interface IBookAppService : ICrudAppService&lt;BookDto, Guid, BookPagedAndSortedResultRequestDto, CreateUpdateBookDto&gt;;
}
</code></pre>
<h3>BookAutoMapperProfile class</h3>
<p>Add a <strong>BookAutoMapperProfile</strong> class to the Books folder of the <strong>Application</strong> project.</p>
<pre><code class="language-csharp">using AbpFilter.Application.Contracts.Books;
using AbpFilter.Domain.Books;
using AutoMapper;

namespace AbpFilter.Application.Books
{
    public class BookAutoMapperProfile : Profile
    {
        public BookAutoMapperProfile()
        {
            CreateMap&lt;BookPagedAndSortedResultRequestDto, BookFilter&gt;();
        }
    }
}
</code></pre>
<h3>BookAppService class</h3>
<p>Override the <strong>GetListAsync</strong> method of the <strong>BookAppService</strong> class in the Books folder of the <strong>Application</strong> project, as in the code below:</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AbpFilter.Domain.Books;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace AbpFilter.Books
{
    public class BookAppService(IBookRepository bookRepository)
        : CrudAppService&lt;Book, BookDto, Guid, BookPagedAndSortedResultRequestDto, CreateUpdateBookDto&gt;(bookRepository),
            IBookAppService
    {
        public override async Task&lt;PagedResultDto&lt;BookDto&gt;&gt; GetListAsync(BookPagedAndSortedResultRequestDto input)
        {
            var filter = ObjectMapper.Map&lt;BookPagedAndSortedResultRequestDto, BookFilter&gt;(input);

            var sorting = (string.IsNullOrEmpty(input.Sorting) ? &quot;Name DESC&quot; : input.Sorting).Replace(&quot;ShortName&quot;, &quot;Name&quot;);

            var books = await bookRepository.GetListAsync(input.SkipCount, input.MaxResultCount, sorting, filter);
            var totalCount = await bookRepository.GetTotalCountAsync(filter);

            return new PagedResultDto&lt;BookDto&gt;(totalCount,ObjectMapper.Map&lt;List&lt;Book&gt;, List&lt;BookDto&gt;&gt;(books));
        }
    }
}
</code></pre>
<h3>Books.razor page</h3>
<p>Override the <strong>DataGrid</strong> component of the <strong>Books.razor</strong> page in the <strong>Blazor</strong> project and Make sure you add the <strong>Filterable</strong> attribute and set it to <strong>true</strong></p>
<pre><code class="language-html">@page &quot;/books&quot;
@using AbpFilter.Books
@inherits AbpCrudPageBase&lt;AbpFilter.Books.IBookAppService, AbpFilter.Books.BookDto, Guid, AbpFilter.Books.BookPagedAndSortedResultRequestDto, AbpFilter.Books.CreateUpdateBookDto&gt;

&lt;Card&gt;
  &lt;CardBody&gt;
    &lt;DataGrid TItem=&quot;BookDto&quot; Filterable=&quot;true&quot; Data=&quot;Entities&quot; ReadData=&quot;OnDataGridReadAsync&quot; TotalItems=&quot;TotalCount&quot; ShowPager=&quot;true&quot; PageSize=&quot;PageSize&quot;&gt;
      &lt;DataGridColumns&gt;
        &lt;DataGridColumn TItem=&quot;BookDto&quot; Field=&quot;@nameof(BookDto.Id)&quot; Caption=&quot;Id&quot;&gt;&lt;/DataGridColumn&gt;
        &lt;DataGridColumn TItem=&quot;BookDto&quot; Field=&quot;@nameof(BookDto.Name)&quot; Caption=&quot;Name&quot;&gt;&lt;/DataGridColumn&gt;
        &lt;DataGridColumn TItem=&quot;BookDto&quot; Field=&quot;@nameof(BookDto.PublishDate)&quot; Caption=&quot;Publish date&quot;&gt;&lt;/DataGridColumn&gt;
        &lt;DataGridColumn TItem=&quot;BookDto&quot; Field=&quot;@nameof(BookDto.Price)&quot; Caption=&quot;Price&quot;&gt;&lt;/DataGridColumn&gt;
      &lt;/DataGridColumns&gt;
    &lt;/DataGrid&gt;
  &lt;/CardBody&gt;
&lt;/Card&gt;
</code></pre>
<h3>Books.razor.cs class</h3>
<p>Override the <strong>UpdateGetListInputAsync</strong> and <strong>OnDataGridReadAsync</strong> methods as you can see below:</p>
<pre><code class="language-csharp">using System.Linq;
using System.Threading.Tasks;
using AbpFilter.Books;
using Blazorise.DataGrid;
using Volo.Abp.Application.Dtos;
using static System.String;

namespace AbpFilter.Blazor.Pages
{
    public partial class Books
    {
        protected override Task UpdateGetListInputAsync()
        {
            if (GetListInput is ISortedResultRequest sortedResultRequestInput)
            {
                sortedResultRequestInput.Sorting = CurrentSorting;
            }

            if (GetListInput is IPagedResultRequest pagedResultRequestInput)
            {
                pagedResultRequestInput.SkipCount = (CurrentPage - 1) * PageSize;
            }

            if (GetListInput is ILimitedResultRequest limitedResultRequestInput)
            {
                limitedResultRequestInput.MaxResultCount = PageSize;

            }
            return Task.CompletedTask;
        }

        protected override Task OnDataGridReadAsync(DataGridReadDataEventArgs&lt;BookDto&gt; e)
        {
            var id = e.Columns.FirstOrDefault(c =&gt; c.SearchValue != null &amp;&amp; c.Field == &quot;Id&quot;);
            GetListInput.Id = id != null &amp;&amp; !IsNullOrEmpty(id.SearchValue.ToString()) ? id.SearchValue.ToString(): Empty;
            
            var name = e.Columns.FirstOrDefault(c =&gt; c.SearchValue != null &amp;&amp; c.Field == &quot;Name&quot;);
            GetListInput.Name = name != null &amp;&amp; !IsNullOrEmpty(name.SearchValue.ToString()) ? name.SearchValue.ToString() : Empty;

            var publishDate = e.Columns.FirstOrDefault(c =&gt; c.SearchValue != null &amp;&amp; c.Field == &quot;PublishDate&quot;);
            GetListInput.PublishDate = publishDate != null &amp;&amp; !IsNullOrEmpty(publishDate.SearchValue.ToString()) ? publishDate.SearchValue.ToString() : Empty;
            
            var price = e.Columns.FirstOrDefault(c =&gt; c.SearchValue != null &amp;&amp; c.Field == &quot;Price&quot;);
            GetListInput.Price =  price != null &amp;&amp; !IsNullOrEmpty(price.SearchValue.ToString()) ? price.SearchValue.ToString() :Empty;

            return base.OnDataGridReadAsync(e);
        }
    }
}
</code></pre>
<h2>Test the result</h2>
<h3>Start both the Blazor and the <strong>HttpApi.Host</strong> project to run the application</h3>
<p>Et voilà! You can now filter a standard paged list (<strong>Blazorise DataGrid component</strong>) in the ABP Framework.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFilter/main/images/recording.gif" alt="Filtering in action" /></p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpFilter.git">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/hide-the-tenant-switch-of-the-login-page-4foaup7p</guid>
      <link>https://abp.io/community/posts/hide-the-tenant-switch-of-the-login-page-4foaup7p</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>hiding-tenant-switch</category>
      <category>login-page</category>
      <title>Hide the Tenant Switch of the Login Page</title>
      <description>In this step-by-step guide, I will explain how you can hide the tenant switch on the login page of an ABP Framework application. After implementing all the steps below a user should be able to log in with an email address and password without losing multitenancy.</description>
      <pubDate>Wed, 13 Oct 2021 16:44:23 Z</pubDate>
      <a10:updated>2026-03-09T20:13:10Z</a10:updated>
      <content:encoded><![CDATA[<h2>Hide Tenant Switch from an ABP Framework Login page</h2>
<h2>Introduction</h2>
<p>In this step-by-step guide I will explain how you can hide the tenant switch on the login page of an <strong>ABP Framework</strong> application. After implementing all the steps below a user should be able to login with email address and password without losing multitenancy.</p>
<h2>Source Code</h2>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpHideTenantSwitchRepo">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to run the solution.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
<li>ABP CLI 8.0.0 or higher</li>
</ul>
<h2>Development</h2>
<h3>Create a new ABP Framework Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpHideTenantSwitch -u blazor -o AbpHideTenantSwitch
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpHideTenantSwitch.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpHideTenantSwitch.HttpApi.Host</code> application to start the server side.</li>
<li>Run the <code>AbpHideTenantSwitch.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<h3>Open HttpApi.Host.csproj and comment out the line below</h3>
<pre><code class="language-html">&lt;!-- &lt;PackageReference Include=&quot;Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite&quot; Version=&quot;3.0.*-*&quot; /&gt; --&gt;
</code></pre>
<h3>Add Volo.BasicTheme module to the project</h3>
<ul>
<li>Open a command prompt in the root of the project and run the command abp add-module</li>
</ul>
<pre><code class="language-bash">    abp add-module Volo.BasicTheme --with-source-code --add-to-solution-file
</code></pre>
<h3>Build Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic project</h3>
<p>Open a <strong>command prompt</strong> in the <strong>Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic</strong> project and run command below:</p>
<pre><code class="language-bash">    dotnet build
</code></pre>
<h3>Add reference to Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic</h3>
<p>Open a <strong>command prompt</strong> in the <strong>HttpApi.Host</strong> project and add a reference to Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic in the HttpApi.Host.csproj file by running command below</p>
<pre><code class="language-bash">   dotnet add reference ../../modules/Volo.BasicTheme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj
</code></pre>
<h3>Replace AbpAspNetCoreMvcUiLeptonXLiteThemeModule</h3>
<p>Replace <strong>typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule)</strong>, with <strong>typeof(AbpAspNetCoreMvcUiBasicThemeModule)</strong> in the DependsOn section of the HttpApiHostModule.cs file in the HttpApi.Host project</p>
<h3>Hide Tenant Switch in Account.cshtml file</h3>
<p>Goto the Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Themes\Basic\Themes\Basic\Layouts\Account.cshtml file in the BasicTheme module</p>
<p>Comment out if statement below to hide Tenant Switch.</p>
<pre><code class="language-html">@* @if (MultiTenancyOptions.Value.IsEnabled &amp;&amp;
(TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true ||
TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(QueryStringTenantResolveContributor.ContributorName) == true))
{
&lt;div class=&quot;card shadow-sm rounded mb-3&quot;&gt;
    &lt;div class=&quot;card-body px-5&quot;&gt;
        &lt;div class=&quot;row&quot;&gt;
            &lt;div class=&quot;col&quot;&gt;
                &lt;span style=&quot;font-size: .8em;&quot; class=&quot;text-uppercase text-muted&quot;&gt;@MultiTenancyStringLocalizer[&quot;Tenant&quot;]&lt;/span&gt;&lt;br /&gt;
                &lt;h6 class=&quot;m-0 d-inline-block&quot;&gt;
                    @if (CurrentTenant.Id == null)
                    {
                    &lt;span&gt;
                                                @MultiTenancyStringLocalizer[&quot;NotSelected&quot;]
                                            &lt;/span&gt;
                    }
                    else
                    {
                    &lt;strong&gt;@(CurrentTenant.Name ?? CurrentTenant.Id.Value.ToString())&lt;/strong&gt;
                    }
                &lt;/h6&gt;
            &lt;/div&gt;
            &lt;div class=&quot;col-auto&quot;&gt;
                &lt;a id=&quot;AbpTenantSwitchLink&quot; href=&quot;javascript:;&quot; class=&quot;btn btn-sm mt-3 btn-outline-primary&quot;&gt;@MultiTenancyStringLocalizer[&quot;Switch&quot;]&lt;/a&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
} *@
</code></pre>
<h3>Add ConfigureTenantResolver() method in HttpApiHostModule of HttpApi.Host project</h3>
<p>Add method ConfigureTenantResolver right under the <strong>ConfigureServices</strong> method in the <strong>HttpApiHostModule</strong> class of the <strong>HttpApi.Host project</strong></p>
<pre><code class="language-csharp">// import using statements
// using Volo.Abp.MultiTenancy;

private void ConfigureTenantResolver(ServiceConfigurationContext context, IConfiguration configuration)
{
    Configure&lt;AbpTenantResolveOptions&gt;(options =&gt;
    {
        options.TenantResolvers.Clear();
        options.TenantResolvers.Add(new CurrentUserTenantResolveContributor());
});
}
</code></pre>
<h3>Call ConfigureTenantResolver() method from ConfigureServices() method</h3>
<pre><code class="language-csharp">public override void ConfigureServices(ServiceConfigurationContext context)
{
    // other code here ...
    ConfigureTenantResolver(context, configuration);
}
</code></pre>
<h3>Add a Pages/Account folder to HttpApi.Host project</h3>
<h3>Add a CustomLoginModel.cs class to the Account folder</h3>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Identity;
using Volo.Abp.TenantManagement;
using IdentityUser = Volo.Abp.Identity.IdentityUser;

namespace AbpHideTenantSwitch.HttpApi.Host.Pages.Account
{
    public class CustomLoginModel : LoginModel
    {
        private readonly ITenantRepository _tenantRepository;

        public CustomLoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions&lt;AbpAccountOptions&gt; accountOptions, IOptions&lt;IdentityOptions&gt; identityOptions, ITenantRepository tenantRepository, IdentityDynamicClaimsPrincipalContributorCache contributorCache)
        : base(schemeProvider, accountOptions, identityOptions, contributorCache)
        {
            _tenantRepository = tenantRepository;
        }

        public override async Task&lt;IActionResult&gt; OnPostAsync(string action)
        {
            var user = await FindUserAsync(LoginInput.UserNameOrEmailAddress);
            using (CurrentTenant.Change(user?.TenantId))
            {
                return await base.OnPostAsync(action);
            }
        }

        protected virtual async Task&lt;IdentityUser&gt; FindUserAsync(string uniqueUserNameOrEmailAddress)
        {
            IdentityUser user = null;
            using (CurrentTenant.Change(null))
            {
                user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
                       await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);

                if (user != null)
                {
                    return user;
                }
            }

            foreach (var tenant in await _tenantRepository.GetListAsync())
            {
                using (CurrentTenant.Change(tenant.Id))
                {
                    user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
                           await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);

                    if (user != null)
                    {
                        return user;
                    }
                }
            }
            return null;
        }
    }
}

</code></pre>
<h3>Add a Login.cshtml file to the Account folder</h3>
<pre><code class="language-html">@page
@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 
@using Microsoft.AspNetCore.Mvc.Localization 
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Settings 
@using Volo.Abp.Settings 
@model AbpHideTenantSwitch.HttpApi.Host.Pages.Account.CustomLoginModel 
@inject IHtmlLocalizer&lt;AccountResource&gt; L
@inject Volo.Abp.Settings.ISettingProvider SettingProvider

    &lt;div class=&quot;card text-center mt-3 shadow-sm rounded&quot;&gt;
        &lt;div class=&quot;card-body abp-background p-5&quot;&gt;
            &lt;div class=&quot;form-group&quot;&gt;
                &lt;img
                    src=&quot;https://raw.githubusercontent.com/bartvanhoey/AbpHideTenantSwitch/main/src/AbpHideTenantSwitch.HttpApi.Host/wwwroot/images/thumbs-up.png&quot;
                    alt=&quot;ThumbsUp&quot;
                    width=&quot;100%&quot;
                /&gt;
            &lt;/div&gt;
            @if (Model.EnableLocalLogin) {
            &lt;form method=&quot;post&quot; class=&quot;mt-4 text-left&quot;&gt;
                &lt;input asp-for=&quot;ReturnUrl&quot; /&gt;
                &lt;input asp-for=&quot;ReturnUrlHash&quot; /&gt;
                &lt;div class=&quot;form-group&quot;&gt;
                    &lt;label&gt;Email address&lt;/label&gt;
                    &lt;input
                        asp-for=&quot;LoginInput.UserNameOrEmailAddress&quot;
                        class=&quot;form-control&quot;
                    /&gt;
                    &lt;span
                        asp-validation-for=&quot;LoginInput.UserNameOrEmailAddress&quot;
                        class=&quot;text-danger&quot;
                    &gt;&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;form-group&quot;&gt;
                    &lt;label asp-for=&quot;LoginInput.Password&quot;&gt;&lt;/label&gt;
                    &lt;input asp-for=&quot;LoginInput.Password&quot; class=&quot;form-control&quot; /&gt;
                    &lt;span
                        asp-validation-for=&quot;LoginInput.Password&quot;
                        class=&quot;text-danger&quot;
                    &gt;&lt;/span&gt;
                &lt;/div&gt;
                &lt;abp-button
                    type=&quot;submit&quot;
                    button-type=&quot;Danger&quot;
                    name=&quot;Action&quot;
                    value=&quot;Login&quot;
                    class=&quot;btn-block btn-lg mt-3&quot;
                    &gt;@L[&quot;Login&quot;]&lt;/abp-button
                &gt;
                @if (Model.ShowCancelButton) {
                &lt;abp-button
                    type=&quot;submit&quot;
                    button-type=&quot;Secondary&quot;
                    formnovalidate=&quot;formnovalidate&quot;
                    name=&quot;Action&quot;
                    value=&quot;Cancel&quot;
                    class=&quot;btn-block btn-lg mt-3&quot;
                    &gt;@L[&quot;Cancel&quot;]&lt;/abp-button
                &gt;
                }
            &lt;/form&gt;
            }
        &lt;/div&gt;
    &lt;/div&gt;
&gt;
</code></pre>
<h3>Custom styles Login page</h3>
<p>Add a <strong>custom-login-styles.css</strong> file to the <strong>wwwroot</strong> folder of the <strong>HttpApi.Host</strong> project</p>
<pre><code class="language-html">    .abp-background { background-color: yellow !important; }
</code></pre>
<h3>Add custom styles to Bundle</h3>
<p>Open file <strong>AbpHideTenantSwitchHttpApiHostModule.cs</strong> of the <strong>HttpApi.Host</strong> project and add custom-login-styles.css to bundle.</p>
<pre><code class="language-csharp">private void ConfigureBundles()
   {
     Configure&lt;AbpBundlingOptions&gt;(options =&gt;
     {
       options.StyleBundles.Configure(
                 BasicThemeBundles.Styles.Global,
                 bundle =&gt;
             {
               bundle.AddFiles(&quot;/global-styles.css&quot;);
               bundle.AddFiles(&quot;/custom-login-styles.css&quot;);
             }
             );
     });
   }
</code></pre>
<h3>Add an Image</h3>
<p>Add an <strong>assets/images</strong> folder to the <strong>wwwroot</strong> folder of the <strong>HttpApi.Host</strong> project and copy/paste an image into <strong>images</strong> folder.</p>
<h3>Start both the Blazor and the <strong>HttpApi.Host</strong> project to run the application</h3>
<p>Et voilà! The Login page <strong>without a Tenant switch</strong>!</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpHideTenantSwitch/main/Images/hidetenantswitchonloginpage.png" alt="Login page without tenant switch" /></p>
<p>A user can now login with email address and username without specifying the tenant name.</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpHideTenantSwitchRepo.git">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/consume-an-abp-framework-api-from-a-.net-core-console-application-5b8o2lrw</guid>
      <link>https://abp.io/community/posts/consume-an-abp-framework-api-from-a-.net-core-console-application-5b8o2lrw</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>consume-abp-api</category>
      <title>Consume an ABP Framework API from a .NET Core console Application</title>
      <description>Consume an ABP Framework API from a .NET Core console Application</description>
      <pubDate>Thu, 05 Aug 2021 19:13:59 Z</pubDate>
      <a10:updated>2026-03-09T20:28:47Z</a10:updated>
      <content:encoded><![CDATA[<h2>Consume an ABP Framework API from a .NET Core console Application</h2>
<h2>Introduction</h2>
<p>In this article I will show you how to connect to a protected ABP Framework API from a .NET Core console Application using the <strong>IdentityModel.OidcClient nuget package</strong>.</p>
<p>The sample <strong>BookStore ABP Framework</strong> application in this article has been developed with <strong>Blazor</strong> as UI Framework and <strong>SQL Server</strong> as database provider.</p>
<p>The <strong>BookStoreConsole</strong> application is a standard <strong>.NET Core console application</strong>.</p>
<p>As I tried to keep this article as simple as possible, you will see there is still some room for code improvements.</p>
<h3>Source code</h3>
<p>The source code of both projects is <a href="https://github.com/bartvanhoey/AbpAddCustomClaimToAccessToken">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution and follow along.</p>
<ul>
<li>.NET 6.0 SDK</li>
<li>VsCode, Visual Studio 2019 Version 16.10.4+ or another compatible IDE</li>
</ul>
<h2>ABP Framework application</h2>
<h3>Create a new ABP Framework application</h3>
<pre><code class="language-bash">  abp new BookStore -u blazor -o BookStore
</code></pre>
<h3>Implement the Web Application Development tutorial (part1-5)</h3>
<p>To follow along make sure you have a protected BookAppService in the BookStore application. For this article I followed the <strong>Web Application Development tutorial</strong> till <strong>part 5: Authorization</strong>.</p>
<h3>Add the section below in the appsettings.json file of the DbMigrator project</h3>
<pre><code class="language-bash">&quot;BookStore_Console&quot;: {
    &quot;ClientId&quot;: &quot;BookStore_Console&quot;,
    &quot;ClientSecret&quot;: &quot;1q2w3e*&quot;,
    &quot;RootUrl&quot;: &quot;https://localhost:44368&quot;
}
</code></pre>
<h3>Add a BookStoreConsole client in the IdentityServerDataSeedContributor class of the Domain project</h3>
<pre><code class="language-bash">// BookStoreConsole Client
var bookStoreConsoleClientId = configurationSection[&quot;BookStore_Console:ClientId&quot;];
if (!bookStoreConsoleClientId.IsNullOrWhiteSpace())
{
    var bookStoreConsoleRootUrl = configurationSection[&quot;BookStore_Console:RootUrl&quot;].TrimEnd('/');
    await CreateClientAsync(
        name: bookStoreConsoleClientId,
        scopes: commonScopes,
        grantTypes: new[] { &quot;password&quot;, &quot;client_credentials&quot;&quot;authorization_code&quot; },
        secret: configurationSection[&quot;BookStore_Console:ClientSecret&quot;]?.Sha256(),
        requireClientSecret: false,
        redirectUri: $&quot;{bookStoreConsoleRootUrl}/authentication/login-callback&quot;,
    corsOrigins: new[] { bookStoreConsoleRootUrl.RemovePostFix(&quot;/&quot;) }
    );
}
</code></pre>
<h3>Run DbMigrator project</h3>
<p>To apply the settings above you need to run the DbMigrator project. After, you can check the <strong>IdentityServerClients</strong> table of the database to see if the <strong>BookStore_Console</strong> client has been added.</p>
<h2>.NET Core console application</h2>
<h3>Create a new .NET Core console application</h3>
<pre><code class="language-bash">    dotnet new console -n BookStoreConsole
</code></pre>
<h3>Install nuget packages (in terminal window or nuget package manager)</h3>
<pre><code class="language-bash">  dotnet add package IdentityModel.OidcClient --version 5.0.0-preview.1
  dotnet add package Newtonsoft.Json --version 13.0.1
</code></pre>
<h3>Add a HttpService class in the root of the project</h3>
<p>When you want to consume a protected API the user has to be <strong>authenticated (username+password)</strong> and <strong>authorized(has the right permissions)</strong>. So, when you call the BookAppService GetListAsync method, in the header of the request you need to send the accesstoken with.</p>
<p>To obtain the <strong>accesstoken</strong> you can make use of the <strong>nuget package IdentityModel.OidcClient</strong>. All the heavy lifting occurs in the <strong>GetTokensFromBookStoreApi</strong> method (See below). These method <strong>sends a request</strong> to the <strong>disco.TokenEndpoint</strong> of the BookStoreApi and <strong>obtains a TokenResponse</strong>. If the correct properties are sent and the API is running, you should obtain a TokenResponse (AccessToken, IdentityToken, Scope, ...)</p>
<p>Afterwards the obtained accesstoken is used in the <strong>SetBearerToken()</strong> of the httpClient.</p>
<p>When you make a request now to the protected BookStore API with the httpClient, the accesstoken is sent with. The BookStore API receives this request and checks the <strong>validity of the accesstoken</strong> and the <strong>permissions</strong>. If these conditions are met, the GetListAsync method of the BookAppService returns the list of books.</p>
<pre><code class="language-csharp">public class HttpService
{
    public async Task&lt;Lazy&lt;HttpClient&gt;&gt; GetHttpClientAsync(bool setBearerToken)
    {
        var client = new Lazy&lt;HttpClient&gt;(() =&gt; new HttpClient());
        var accessToken = await GetAccessToken();
        if (setBearerToken)
        {
            client.Value.SetBearerToken(accessToken);
        }
        client.Value.BaseAddress = new Uri(&quot;https://localhost:44388/&quot;); //
        return await Task.FromResult(client);
    }

    private static async Task&lt;TokenResponse&gt; GetTokensFromBookStoreApi()
    {
        var authority = &quot;https://localhost:44388/&quot;;
        var discoveryCache = new DiscoveryCache(authority);
        var disco = await discoveryCache.GetAsync();
        var httpClient = new Lazy&lt;HttpClient&gt;(() =&gt; new HttpClient());
        var response = await httpClient.Value.RequestPasswordTokenAsync(new PasswordTokenRequest
        {
            Address = disco.TokenEndpoint, // https://localhost:44388/connect/token
            ClientId = &quot;BookStore_Console&quot;,
            ClientSecret = &quot;1q2w3e&quot;,
            UserName = &quot;admin&quot;,
            Password = &quot;1q2w3E*&quot;,
            Scope = &quot;email openid profile role phone address BookStore&quot;,
        });
        if (response.IsError) throw new Exception(response.Error);
        return response;
    }

    private async Task&lt;string&gt; GetAccessToken()
    {
        var accessToken = (await GetTokensFromBookStoreApi()).AccessToken;
        return accessToken;
    }

}
</code></pre>
<h3>Main Method</h3>
<p>Below you see the <strong>Main method</strong> of the <strong>Program.cs</strong> file. A new HttpService gets created and the GetHttpClientAsync method is called to get a httpClient.</p>
<p>Next, we make a request to the BookStore API to obtain the list of books.</p>
<pre><code class="language-csharp">static async Task Main()
{
    // if setBearerToken = false, should throw HttpRequestException: 'Response status code does not indicate success: 401 (Unauthorized).'
    // if setBearerToken = true, API should be called an list of books should be returned
    const bool setBearerToken = true;

    var httpService = new HttpService();
    var httpClient = await httpService.GetHttpClientAsync(setBearerToken);

    var response = await httpClient.Value.GetAsync(&quot;https://localhost:44388/api/app/book&quot;);
    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadAsStringAsync();

    var books = JsonConvert.DeserializeObject&lt;ListResultDto&lt;BookDto&gt;&gt;(json);

    Console.WriteLine(&quot;====================================&quot;);
    if (books?.Items != null)
        foreach (var book in books.Items)
            Console.WriteLine(book.Name);
    Console.WriteLine(&quot;====================================&quot;);
    Console.ReadKey();
}
</code></pre>
<h2>Run API and .NET Core console application</h2>
<p>Run the <strong>BookStore.HttpApi.Host</strong> of the ABP Framework application first. Start the .NET Core console application next. Below is the result when the accesstoken is successfully set.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpAddCustomClaimToAccessToken/gh-pages/Images/books_returned_from_api.jpg" alt="Books returned from API" /></p>
<p>If you set the variable <strong>setBearerToken</strong> in the <strong>Main</strong> method to false, you will get a <strong>401 (Unauthorized)</strong></p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpAddCustomClaimToAccessToken/gh-pages/Images/unauthorized_exception.jpg" alt="Unauthorized Exception" /></p>
<p>Congratulations, you can now connect to an ABP Framework API form a .NET Core console application! Check out the <a href="https://github.com/bartvanhoey/AbpAddCustomClaimToAccessToken">source code</a> of this article on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/abp-framework-api-consumed-by-xamarin.forms-application-rkdfzz66</guid>
      <link>https://abp.io/community/posts/abp-framework-api-consumed-by-xamarin.forms-application-rkdfzz66</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>xamarin</category>
      <title>ABP Framework API consumed by Xamarin.Forms application</title>
      <description>This article is about how to consume an ABP Framework API by a Xamarin.Forms application.</description>
      <pubDate>Wed, 17 Feb 2021 21:06:27 Z</pubDate>
      <a10:updated>2026-03-09T20:33:13Z</a10:updated>
      <content:encoded><![CDATA[<h2>ABP Framework consumed by a Xamarin.Forms application</h2>
<h2>Introduction</h2>
<p>In this article, I will explain <strong>how to consume an ABP Framework API with Xamarin.Forms</strong>.</p>
<p>The article is a complete rewrite of an older one that you can find here <a href="https://github.com/bartvanhoey/AbpApiConsumedByXamarin/blob/main/AbpIo/ConsumeAbpFrameworkApiFromXamarinForms_old.md">here</a>.</p>
<h2>Source Code</h2>
<p>The sample application has been developed with Blazor as UI framework and SQL Server as database provider.</p>
<p>The Source code of the completed application is <a href="https://github.com/bartvanhoey/AbpApiConsumedByXamarin/tree/main/XamarinForms">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution and follow along.
You will also need to have your editor set up for Xamarin.Forms development.</p>
<ul>
<li>.NET 5.0 SDK</li>
<li>VsCode, Visual Studio 2019, or another compatible IDE.</li>
</ul>
<h2>Create a new ABP Framework application</h2>
<pre><code class="language-bash">    abp new AbpApi -u blazor -o AbpApi
</code></pre>
<h3>BookAppService (optional)</h3>
<p>To have a simple API that you can consume with the Xamarin.Forms app, add the Books Bookstore code from the BookStore Tutorial (Part1-5).</p>
<h3>Add AbpApi_Xamarin section in appsettings.json file of the AbpApi.DbMigrator project</h3>
<pre><code class="language-json">    // change the &lt;replace-me-with-the-abp-api-port&gt; with the port were the Swagger page is running on
    &quot;AbpApi_Xamarin&quot;: {
        &quot;ClientId&quot;: &quot;AbpApi_Xamarin&quot;,
        &quot;ClientSecret&quot;: &quot;1q2w3e*&quot;,
        &quot;RootUrl&quot;: &quot;https://localhost:&lt;replace-me-with-the-abp-api-port&gt;/&quot; 
    }
</code></pre>
<h3>Add Xamarin client IdentityServer configuration</h3>
<p>In the CreateClientAsync method in class IdentityServerDataSeedContributor of the AbpApi.Domain project.</p>
<pre><code class="language-csharp">    // Xamarin Client
    var xamarinClientId = configurationSection[&quot;AbpApi_Xamarin:ClientId&quot;];
    if (!xamarinClientId.IsNullOrWhiteSpace())
    {
        var xamarinRootUrl = configurationSection[&quot;AbpApi_Xamarin:RootUrl&quot;].TrimEnd('/');
        await CreateClientAsync(
            name: xamarinClientId,
            scopes: commonScopes,
            grantTypes: new[] { &quot;authorization_code&quot; },
            secret: configurationSection[&quot;AbpApi_Xamarin:ClientSecret&quot;]?.Sha256(),
            requireClientSecret: false,
            redirectUri: &quot;xamarinformsclients:/authenticated&quot;,
            postLogoutRedirectUri: &quot;xamarinformsclients:/signout-callback-oidc&quot;,
            corsOrigins: new[] { xamarinRootUrl.RemovePostFix(&quot;/&quot;) }
        );
    }
</code></pre>
<h3>Insert XamarinClient setting into Database</h3>
<p>Run AbpApi.DbMigrator project to execute the IdentityServerDataSeedContributor to insert the XamarinClient settings into the database.</p>
<h3>Start API and Blazor project</h3>
<p>Start API and Blazor project to see if all projects are running successfully. Keep the API running!</p>
<h2>Download &amp; setup ngrok</h2>
<p>With ngrok, you can mirror your localhost API endpoint to a worldwide available API endpoint.
In this way, you can overcome the problem of Xamarin.Forms app mixing up localhost from the API with localhost from the Xamarin.Forms app.</p>
<h3>Open a command prompt in the root of ABP Framework application and run the command below</h3>
<pre><code class="language-bash">    -- specify another region when needed
    ngrok http -region eu https://localhost:&lt;replace-me-with-the-abp-api-port&gt;/ 
</code></pre>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpApiConsumedByXamarin/main/Images/ngrok_localhost_port_forwarding.jpg" alt="Ngrok port forwarding" /></p>
<h3>Copy and remember Ngrok Forwarding HTTPS endpoint</h3>
<pre><code class="language-bash">    &quot;https://&lt;your-ngrok-generated-generated-number-here&gt;.eu.ngrok.io&quot;
</code></pre>
<h2>Create a new Xamarin.Forms application</h2>
<h3>Create a new Xamarin app in Visual Studio (Flyout template)</h3>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpApiConsumedByXamarin/main/Images/create_new_mobile_app.jpg" alt="Create a new Xamarin.Forms app" /></p>
<h3>Update Nuget Packages</h3>
<p>I updated the following NuGet packages in the Xamarin.core project and the Android.project.</p>
<pre><code class="language-bash">    Xamarin.Forms&quot; Version=&quot;5.0.0.2244
    Xamarin.Essentials&quot; Version=&quot;1.7.0
</code></pre>
<h3>Add a FlyoutItem in file AppShell.xaml of the AbpXamarinForms core project</h3>
<pre><code class="language-html">    &lt;FlyoutItem Title=&quot;Login&quot; Icon=&quot;icon_about.png&quot;&gt;
        &lt;ShellContent Route=&quot;LoginPage&quot; ContentTemplate=&quot;{DataTemplate local:LoginPage}&quot; /&gt;
    &lt;/FlyoutItem&gt;
    // ... other FlyoutItems here
</code></pre>
<h3>Run XamarinForms application</h3>
<p>Start the Android the Xamarin.Forms application and stop it again when it runs successfully.</p>
<h2>Connect to AbpApi IdentityServer</h2>
<h3>Install IdentityModel and IdentityModel.OidcClient nuget packages</h3>
<p>Open the Nuget Package Manager and install <strong>IdentityModel</strong>, <strong>IdentityModel.OidcClient</strong>  and <strong>Newtonsoft.json</strong> nuget packages in the core project.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpApiConsumedByXamarin/main/Images/installed_nuget_packages_in_xamarin_forms.jpg" alt="Installed nuget packages" /></p>
<h3>Add a WebAuthenticatorBrowser class to the Services folder in the Core project</h3>
<p>This class is needed to open a browser page in your Xamarin.Forms application.</p>
<pre><code class="language-csharp">using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using IdentityModel.OidcClient.Browser;
using Xamarin.Essentials;

namespace AbpXamarinForms.Services
{
    internal class WebAuthenticatorBrowser : IBrowser
    {
        private readonly string _callbackUrl;

        public WebAuthenticatorBrowser(string callbackUrl = null) =&gt; _callbackUrl = callbackUrl ?? &quot;&quot;;

        public async Task&lt;BrowserResult&gt; InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
        {
            try
            {
                var callbackUrl = string.IsNullOrEmpty(_callbackUrl) ? options.EndUrl : _callbackUrl;
                var authResult =
                    await WebAuthenticator.AuthenticateAsync(new Uri(options.StartUrl), new Uri(callbackUrl));
                var authorizeResponse = ToRawIdentityUrl(options.EndUrl, authResult);
                return new BrowserResult
                {
                    Response = authorizeResponse
                };
            }
            catch (Exception exception)
            {
                
                return new BrowserResult
                {
                    ResultType = BrowserResultType.UnknownError,
                    Error = exception.ToString()
                };
            }
        }

        private static string ToRawIdentityUrl(string redirectUrl, WebAuthenticatorResult result)
        {
            var parameters = result.Properties.Select(pair =&gt; $&quot;{pair.Key}={pair.Value}&quot;);
            var values = string.Join(&quot;&amp;&quot;, parameters);
            return $&quot;{redirectUrl}#{values}&quot;;
        }
    }
}
</code></pre>
<h3>Add a LoginService class to the Services folder</h3>
<pre><code class="language-csharp">using IdentityModel.OidcClient;
using System.Threading.Tasks;

namespace AbpXamarinForms.Services
{
    public class LoginService
    {
        private const string _authorityUrl = &quot;https://&lt;your-ngrok-generated-generated-number-here&gt;.eu.ngrok.io&quot;;
        private const string _redirectUrl = &quot;xamarinformsclients:/authenticated&quot;;
        private const string _postLogoutRedirectUrl = &quot;xamarinformsclients:/signout-callback-oidc&quot;;
        private const string _scopes = &quot;email openid profile role phone address AbpApi&quot;;
        private const string _clientSecret = &quot;1q2w3e*&quot;;
        private const string _clientId = &quot;AbpApi_Xamarin&quot;;


        private OidcClient CreateOidcClient()
        {
            var options = new OidcClientOptions
            {
                Authority = _authorityUrl,
                ClientId = _clientId,
                Scope = _scopes,
                RedirectUri = _redirectUrl,
                ClientSecret = _clientSecret,
                PostLogoutRedirectUri = _postLogoutRedirectUrl,
                Browser = new WebAuthenticatorBrowser()
            };
            return new OidcClient(options);
        }

         public async Task&lt;string&gt; AuthenticateAsync()
        {
            var oidcClient = CreateOidcClient();
            var loginResult = await oidcClient.LoginAsync(new LoginRequest());
            return loginResult.AccessToken;
        }
    }
}

</code></pre>
<h3>Update content of the LoginViewModel.cs class</h3>
<pre><code class="language-csharp">using AbpXamarinForms.Services;
using IdentityModel.Client;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Xamarin.Forms;

namespace AbpXamarinForms.ViewModels
{
    public class LoginViewModel : BaseViewModel
    {
        private readonly LoginService _loginService = new LoginService();
        public Command LoginCommand { get; }

        public LoginViewModel()
        {
            LoginCommand = new Command(OnLoginClicked);
        }

        private async void OnLoginClicked(object obj)
        {
            var ngRokUrl = &quot;https://&lt;your-ngrok-generated-generated-number-here&gt;.eu.ngrok.io&quot;;
            var accessToken = await _loginService.AuthenticateAsync();
            Console.WriteLine($&quot;accesstoken: {accessToken}&quot;);

            var httpClient = GetHttpClient(accessToken);
            var response = await httpClient.Value.GetAsync($&quot;{ngRokUrl}/api/app/book&quot;);
            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync();
                var booksResult = JsonConvert.DeserializeObject&lt;BooksResult&gt;(content);

                var book = booksResult.Items.FirstOrDefault();
                Console.WriteLine($&quot;book: {book.Name} - price: {book.Price}&quot;);
            }
            // Set a breakpoint on the line below
            Console.ReadLine();
        }

        private Lazy&lt;HttpClient&gt; GetHttpClient(string accessToken)
        {
            var httpClient = new Lazy&lt;HttpClient&gt;(() =&gt; new HttpClient(GetHttpClientHandler()));
            httpClient.Value.SetBearerToken(accessToken);
            return httpClient;
        }

        private HttpClientHandler GetHttpClientHandler()
        {
            //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            // EXCEPTION : Javax.Net.Ssl.SSLHandshakeException: 'java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.'
            // SOLUTION :
            var httpClientHandler = new HttpClientHandler
            {
                ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =&gt; true
            };
            return httpClientHandler;
        }

    }

    public class BooksResult
    {
        public int TotalCount { get; set; }
        public List&lt;BookDto&gt; Items { get; set; }
    }

    public class BookDto
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public BookType Type { get; set; }
        public DateTime PublishDate { get; set; }
        public float Price { get; set; }
        public DateTime? LastModificationTime { get; set; }
        public Guid? LastModifierId { get; set; }
    }

    public enum BookType
    {
        Undefined,
        Adventure,
        Biography,
        Dystopia,
        Fantastic,
        Horror,
        Science,
        ScienceFiction,
        Poetry
    }
}
</code></pre>
<h3>Add a WebAuthenticationCallbackActivity class in the root of the Android project</h3>
<pre><code class="language-csharp">using Android.App;
using Android.Content;
using Android.Content.PM;

namespace AbpXamarinForms.Droid
{
    [Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
    [IntentFilter(new[] { Intent.ActionView },
        Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, DataScheme = &quot;xamarinformsclients&quot;)]
    public class WebAuthenticationCallbackActivity : Xamarin.Essentials.WebAuthenticatorCallbackActivity
    {
    }
}
</code></pre>
<h2>Run both API and Xamarin.Forms application</h2>
<ul>
<li>Update the <strong>_authorityUrl</strong> field in the LoginService class with the correct <strong>ngrok Forwarding https url</strong></li>
<li>Update the <strong>ngRokUrl</strong> variable in the <strong>OnLoginClicked</strong> method of the <strong>LoginViewModel</strong> class</li>
<li>Start the <strong>AbpApi</strong> application and make sure <strong>ngrok is running</strong></li>
<li>Run the <strong>AbpXamarinForms</strong> application on an emulator or physical device.</li>
<li>Click the <strong>Login button</strong> and enter the <strong>administrator credentials</strong> (admin, 1q2w3E*)</li>
</ul>
<p><strong>WARNING</strong>: The API will probably throw a <strong>SecurityTokenInvalidIssuerException</strong>.</p>
<h2>Fix SecurityTokenInvalidIssuerException: IDX10205: Issuer validation failed</h2>
<pre><code class="language-bash">Failed to validate the token.

Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException: IDX10205: Issuer validation failed. Issuer: 'System.String'. Did not match: validationParameters.ValidIssuer: 'System.String' or validationParameters.ValidIssuers: 'System.String'.
   at Microsoft.IdentityModel.Tokens.Validators.ValidateIssuer(String issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateIssuer(String issuer, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
</code></pre>
<h3>Update the ConfigureAuthentication method in the AbpApiHostModule of the AbpApi.HttpApi.Host project</h3>
<pre><code class="language-csharp"> private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
    context.Services.AddAuthentication()
        .AddJwtBearer(options =&gt;
        {
            options.Authority = configuration[&quot;AuthServer:Authority&quot;];
            options.RequireHttpsMetadata = Convert.ToBoolean(configuration[&quot;AuthServer:RequireHttpsMetadata&quot;]);
            options.Audience = &quot;AbpApi&quot;;
            options.BackchannelHttpHandler = new HttpClientHandler
            {
                ServerCertificateCustomValidationCallback =
                    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
            };
            // Add the line below 
            options.TokenValidationParameters.ValidIssuers = configuration.GetSection(&quot;AuthServer:ValidIssuers&quot;).Get&lt;string[]&gt;();
            
            // Alternatively the line below would also fix the problem.
            // options.TokenValidationParameters.ValidateIssuer = false;
        });
}
</code></pre>
<h3>Add ValidIssuers to the AuthServer section of the appsettings.json file in the AbpApi.HttpApi.Host project</h3>
<pre><code class="language-json">&quot;AuthServer&quot;: {
    &quot;Authority&quot;: &quot;https://localhost:&lt;replace-me-with-the-abp-api-port&gt;&quot;,
    &quot;RequireHttpsMetadata&quot;: &quot;false&quot;,
    &quot;SwaggerClientId&quot;: &quot;AbpApi_Swagger&quot;,
    &quot;SwaggerClientSecret&quot;: &quot;1q2w3e*&quot;,
    &quot;ValidIssuers&quot;: [
      &quot;https://&lt;your-ngrok-generated-generated-number-here&gt;.eu.ngrok.io&quot;
    ]
  },
</code></pre>
<h2>Start both the AbpApi and the AbpXamarinForms applications</h2>
<p>If all goes well, your XamarinForms application opens the ABP login page.  Enter the administrator credentials (admin - 1q2w3E*) and confirm.
Once logged in, the app sends an Http-request to the ABP Framework API to get the books from the database.</p>
<p>Et voilà! The Xamarin.Forms app connects to the IdentityServer4 successfully and gets the books from the ABP Framework API.</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpApiConsumedByXamarin/tree/main/XamarinForms">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/uploaddownload-files-to-azure-storage-with-the-abp-framework-sr7t3w4p</guid>
      <link>https://abp.io/community/posts/uploaddownload-files-to-azure-storage-with-the-abp-framework-sr7t3w4p</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>download-file</category>
      <category>azure-storage</category>
      <category>upload-file</category>
      <title>Upload/Download Files to Azure Storage with the ABP Framework</title>
      <description>This step-by-step article explains how to use the BLOB Storing system of the ABP Framework to upload a file to Azure Storage.</description>
      <pubDate>Wed, 10 Feb 2021 09:50:57 Z</pubDate>
      <a10:updated>2026-03-09T22:29:31Z</a10:updated>
      <content:encoded><![CDATA[<h2>Upload/Download Files to Azure Storage with the ABP Framework</h2>
<h2>Introduction</h2>
<p>In this article you will learn how to setup and use the <strong>Blob Storing Azure Provider</strong> to <strong>Upload/Download files to Azure Storage</strong> in a <strong>Blazor APB Framework</strong> application.</p>
<p>To keep this article as simple as possible, I will only show the steps to upload a file to Azure Storage. In the accompanying source code you can find the code you need to Download or Delete a file from Azure Storage.</p>
<p>I made use of <strong>Azurite</strong> (an emulator for local Storage Development), so you don't need to set up an Azure Storage account in Azure to follow along. If you want to upload/download files to a real Azure Storage account in Azure,  you need to replace the connection string in the AzureStorageAccountSettings section of the appsettings.json file in the HttpApi.Host project.</p>
<h3>Source code</h3>
<p>The Source code of the completed application is <a href="https://github.com/bartvanhoey/AbpFileUploadToAzureStorage">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution and follow along.</p>
<ul>
<li>.NET 5.0 SDK</li>
<li>VsCode, Visual Studio 2019 16.8.0+ or another compatible IDE</li>
<li>Node.js (version 8.0 or later)</li>
<li>Microsoft Azure Storage Explorer. Download it <a href="https://azure.microsoft.com/en-us/features/storage-explorer/">here</a></li>
<li>Azurite (Emulator for local Azure Storage development). Read more about it <a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite">here</a>.</li>
</ul>
<h2>Tools needed</h2>
<h3>Install and run Azurite</h3>
<ul>
<li>Open a command prompt and install <strong>Azurite</strong> by using NPM.</li>
</ul>
<pre><code class="language-bash">  npm install -g azurite
</code></pre>
<ul>
<li>Create an azurite folder in your c-drive (c:\azurite).</li>
<li>start <strong>Azurite emulator</strong> from a command prompt with admin privileges.</li>
</ul>
<pre><code class="language-bash">  azurite --silent --location c:\azurite --debug c:\azurite\debug.log
</code></pre>
<p>Alternatively you could also run Azurite as a docker container (see <a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite">docs</a> how to proceed).</p>
<h3>Install and configure Microsoft Azure Storage Explorer</h3>
<ul>
<li>Download and install <a href="https://azure.microsoft.com/en-us/features/storage-explorer/">Azure Storage Explorer</a>. A free tool to easily manage your Azure cloud storage resources anywhere, from Windows, macOS, or Linux.</li>
<li>Click on the <strong>Open Connect Dialog</strong> icon in the left menu.</li>
<li>Select <strong>Attach to a local emulator</strong> and click <strong>Next</strong>, keep default settings, click <strong>Connect</strong>.</li>
<li>Do not forget to start your emulator, Storage Explorer will not start it for you.</li>
</ul>
<h2>Create and run Application</h2>
<h3>Creating a new ABP Framework Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpFileUploadToAzureStorage -o AbpFileUploadToAzureStorage -u blazor
</code></pre>
<h3>Open &amp; run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpFileUploadToAzureStorage.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpFileUploadToAzureStorage.HttpApi.Host</code> application to start the server side.</li>
<li>Run the <code>AbpFileUploadToAzureStorage.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<h2>Development</h2>
<h3>Install Volo.Abp.BlobStoring.Azure NuGet package to Domain project</h3>
<ul>
<li>Open a command prompt in the directory of the <strong>Domain</strong> project.</li>
<li>Run the command below to install <strong>Volo.Abp.BlobStoring.Azure</strong> NuGet package</li>
</ul>
<pre><code class="language-bash">  abp add-package Volo.Abp.BlobStoring.Azure
</code></pre>
<h3>Create a class AzureStorageAccountOptions to retrieve Azure Storage settings</h3>
<ul>
<li>Create an <strong>AzureStorage</strong> folder in the <strong>Domain</strong> project of your application.</li>
<li>Add a <strong>AzureStorageAccountOptions.cs</strong> file to the <strong>AzureStorage</strong> folder.</li>
</ul>
<pre><code class="language-csharp">namespace AbpFileUploadToAzureStorage
{
  public class AzureStorageAccountOptions
  {
    public string ConnectionString { get; set; }
    public string AccountUrl { get; set; }
  }
}
</code></pre>
<h3>Add AzureStorageAccountSettings to the appsettings.json file in the HttpApi.Host project</h3>
<pre><code class="language-json">{
  // other settings here

  &quot;AzureStorageAccountSettings&quot; : {
    &quot;ConnectionString&quot; : &quot;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;&quot;,
    &quot;AccountUrl&quot; : &quot;http://127.0.0.1:10000/devstoreaccount1/&quot;
  }
}
</code></pre>
<p>The connection string above serves as connection to Azurite (local Azure Storage emulator). You will need to replace the connection string when you want to upload files to Azure Storage in the cloud.</p>
<h3>Create a ConfigureAzureStorageAccountOptions method in the AbpFileUploadToAzureStorageDomainModule class of the Domain project</h3>
<pre><code class="language-csharp">    // Comment out and place in using section page
    // using Microsoft.Extensions.Configuration;
    
    private void ConfigureAzureStorageAccountOptions(ServiceConfigurationContext context, IConfiguration configuration)
    {
      Configure&lt;AzureStorageAccountOptions&gt;(options =&gt;
      {
        var azureStorageConnectionString = configuration[&quot;AzureStorageAccountSettings:ConnectionString&quot;];
        var azureStorageAccountUrl = configuration[&quot;AzureStorageAccountSettings:AccountUrl&quot;];

        options.ConnectionString = azureStorageConnectionString;
        options.AccountUrl = azureStorageAccountUrl;
      });
    }
</code></pre>
<h3>Call the ConfigureAzureStorageAccountOptions method from the ConfigureServices method in the AbpFileUploadToAzureStorageDomainModule</h3>
<pre><code class="language-csharp">    public override void ConfigureServices(ServiceConfigurationContext context)
    {
      // ...

      var configuration = context.Services.GetConfiguration();
      ConfigureAzureStorageAccountOptions(context, configuration);
    }
</code></pre>
<h3>Add a PizzaPictureContainer class in the Domain project</h3>
<ul>
<li>Add a <strong>PizzaPictureContainer.cs</strong> file to the <strong>AzureStorage/Pizzas</strong> folder of the <strong>Domain</strong> project.</li>
</ul>
<pre><code class="language-csharp">using Volo.Abp.BlobStoring;

namespace AbpFileUploadToAzureStorage.Domain.AzureStorage
{
    [BlobContainerName(&quot;pizza-picture-container&quot;)]
    public class PizzaPictureContainer
    {
        
    }
}
</code></pre>
<h3>Create a ConfigureAbpBlobStoringOptions method in the AbpFileUploadToAzureStorageDomainModule of the Domain project</h3>
<pre><code class="language-csharp">    // Comment out and place in using section page
    // using Volo.Abp.BlobStoring;
    // using AbpFileUploadToAzureStorage.Domain.AzureStorage;


    private void ConfigureAbpBlobStoringOptions(IConfiguration configuration)
    {
      Configure&lt;AbpBlobStoringOptions&gt;(options =&gt;
      {
        var azureStorageConnectionString = configuration[&quot;AzureStorageAccountSettings:ConnectionString&quot;];
        options.Containers.Configure&lt;PizzaPictureContainer&gt;(container =&gt;
        {
          container.UseAzure(azure =&gt;
                {
                  azure.ConnectionString = azureStorageConnectionString;
                  azure.CreateContainerIfNotExists = true;
                });
        });
      });
    }
</code></pre>
<h3>Call the ConfigureAbpBlobStoringOptions method from the ConfigureServices method in the AbpFileUploadToAzureStorageDomainModule</h3>
<pre><code class="language-csharp">    public override void ConfigureServices(ServiceConfigurationContext context)
    {
      // ...

      var configuration = context.Services.GetConfiguration();
      ConfigureAzureStorageAccountOptions(context, configuration);
      
      ConfigureAbpBlobStoringOptions(configuration);
    }
</code></pre>
<h3>Create PizzaPictureContainerManager class in folder AzureStorage/Pizzas of the Domain project</h3>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.BlobStoring;
using Volo.Abp.Domain.Services;

namespace AbpFileUploadToAzureStorage.Domain.AzureStorage.Pizzas
{
  public class PizzaPictureContainerManager : DomainService
  {
    private readonly IBlobContainer&lt;PizzaPictureContainer&gt; _pizzaPictureContainer;
    private readonly AzureStorageAccountOptions _azureStorageAccountOptions;

    public PizzaPictureContainerManager(IBlobContainer&lt;PizzaPictureContainer&gt; pizzaPictureContainer, IOptions&lt;AzureStorageAccountOptions&gt; azureStorageAccountOptions)
    {
      _pizzaPictureContainer = pizzaPictureContainer;
      _azureStorageAccountOptions = azureStorageAccountOptions.Value;
    }

    public async Task&lt;string&gt; SaveAsync(string fileName, byte[] byteArray, bool overrideExisting = false)
    {
      var extension = Path.GetExtension(fileName);
      var storageFileName = $&quot;{Path.GetFileNameWithoutExtension(fileName)}_{Guid.NewGuid()}{extension}&quot;;
      await _pizzaPictureContainer.SaveAsync(storageFileName, byteArray, overrideExisting);
      return storageFileName;
    }
  }
}
</code></pre>
<h3>Add IPizzaAppService, SavePizzaPictureDto and SavedPizzaPictureDto to the Application.Contracts project</h3>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace AbpFileUploadToAzureStorage.Application.Contracts.AzureStorage.Pizzas
{
  public interface IPizzaAppService : IApplicationService
  {
    Task&lt;SavedPizzaPictureDto&gt; SavePizzaPicture(SavePizzaPictureDto input);
  }
}
</code></pre>
<pre><code class="language-csharp">namespace AbpFileUploadToAzureStorage.Application.Contracts.AzureStorage.Pizzas
{
  public class SavePizzaPictureDto
  {
    public string FileName { get; set; }
    public byte[] Content { get; set; }
  }
}
</code></pre>
<pre><code class="language-csharp">namespace AbpFileUploadToAzureStorage.Application.Contracts.AzureStorage.Pizzas
{
  public class SavedPizzaPictureDto
  {
    public string StorageFileName { get; set; }
  }
}
</code></pre>
<h3>Add a PizzaAppService class in the Application project and implement IPizzaAppService interface</h3>
<pre><code class="language-csharp">using System.Threading.Tasks;
using AbpFileUploadToAzureStorage.Application.Contracts.AzureStorage.Pizzas;
using AbpFileUploadToAzureStorage.Domain.AzureStorage.Pizzas;
using Volo.Abp.Application.Services;

namespace AbpFileUploadToAzureStorage.Application.AzureStorage.Pizzas
{

  public class PizzaAppService : ApplicationService, IPizzaAppService
  {
    private readonly PizzaPictureContainerManager _pizzaPictureContainerManager;

    public PizzaAppService(PizzaPictureContainerManager pizzaPictureContainerManager)
    {
      _pizzaPictureContainerManager = pizzaPictureContainerManager;
    }

    public async Task&lt;SavedPizzaPictureDto&gt; SavePizzaPicture(SavePizzaPictureDto input)
    {
      var storageFileName = await _pizzaPictureContainerManager.SaveAsync(input.FileName, input.Content, true);
      return new SavedPizzaPictureDto { StorageFileName = storageFileName };
    }
  }
}
</code></pre>
<h3>Replace content of Index.razor with code below</h3>
<pre><code class="language-html">@page &quot;/&quot;
@inherits AbpFileUploadToAzureStorageComponentBase
&lt;div class=&quot;container&quot;&gt;
    &lt;CardDeck&gt;
        &lt;div class=&quot;card mt-4 mb-5&quot;&gt;
            &lt;div class=&quot;card-body&quot;&gt;
                &lt;div class=&quot;col-lg-12&quot;&gt;
                    &lt;div class=&quot;p-12&quot;&gt;
                        &lt;h5&gt;&lt;i class=&quot;fas fa-file-upload text-secondary pr-2 my-2 fa-2x&quot;&gt;&lt;/i&gt;Upload
                            File to Azure Storage
                        &lt;/h5&gt;
                        &lt;p&gt;
                            &lt;InputFile OnChange=&quot;@OnInputFileChange&quot; /&gt;
                        &lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;card mt-4 mb-5&quot;&gt;
            &lt;div class=&quot;card-body&quot;&gt;
                &lt;CardImage Source=&quot;@PictureUrl&quot; Alt=&quot;Pizza picture will be displayed here!&quot;&gt;&lt;/CardImage&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/CardDeck&gt;
&lt;/div&gt;
</code></pre>
<h3>Replace content of Index.razor.cs with code below</h3>
<pre><code class="language-csharp">using System;
using System.Linq;
using System.Threading.Tasks;
using AbpFileUploadToAzureStorage.Application.Contracts.AzureStorage.Pizzas;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace AbpFileUploadToAzureStorage.Blazor.Pages
{
  public partial class Index
  {
    [Inject] protected IPizzaAppService PizzaAppService { get; set; }
    public SavedPizzaPictureDto SavedPizzaPictureDto { get; set; } = new SavedPizzaPictureDto();
    protected string PictureUrl;

    protected async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
      var file = e.GetMultipleFiles(1).FirstOrDefault();
      var byteArray = new byte[file.Size];
      await file.OpenReadStream().ReadAsync(byteArray);

      SavedPizzaPictureDto = await PizzaAppService.SavePizzaPicture(new SavePizzaPictureDto { Content = byteArray, FileName = file.Name }); ;

      var format = &quot;image/png&quot;;
      var imageFile = (e.GetMultipleFiles(1)).FirstOrDefault();
      var resizedImageFile = await imageFile.RequestImageFileAsync(format, 100, 100);
      var buffer = new byte[resizedImageFile.Size];
      await resizedImageFile.OpenReadStream().ReadAsync(buffer);
      PictureUrl = $&quot;data:{format};base64,{Convert.ToBase64String(buffer)}&quot;;
    }
  }
}

</code></pre>
<h2>Test uploading a picture to Azure Storage</h2>
<ul>
<li>Start both the Blazor and the HttpApi.Host project.</li>
<li>Choose a pizza picture in the <strong>File Upload to Azure Storage</strong> section.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFileUploadToAzureStorage/gh-pages/images/index.jpg" alt="Upload file to Azure Storage" /></p>
<p>Et voilà! As you can see in the <strong>Azure Storage Explorer</strong>, the pizza picture has been successfully stored in <strong>Azure Storage</strong>.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFileUploadToAzureStorage/gh-pages/images/pizza_in_azure_storage_explorer.jpg" alt="File uploaded to  Azure Storage" /></p>
<p>Congratulations, you can upload a file to Azure Storage by now! Check out the source code of this article to see my implementation of Uploading/Deleting a file to Azure Storage.</p>
<p>Find more information about the <strong>ABP Framework Blob Storing System</strong> <a href="https://docs.abp.io/en/abp/latest/Blob-Storing">here</a> and <strong>BLOB Storing Azure Provider</strong> <a href="https://docs.abp.io/en/abp/latest/Blob-Storing-Azure">here</a>.</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpFileUploadToAzureStorage">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/replace-basic-bootstrap-theme-with-custom-bootstrap-theme-in-abp-blazor-applications.-n3548bgl</guid>
      <link>https://abp.io/community/posts/replace-basic-bootstrap-theme-with-custom-bootstrap-theme-in-abp-blazor-applications.-n3548bgl</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>replacing-theme</category>
      <category>blazor</category>
      <title>Replace Basic Bootstrap theme with  Custom Bootstrap theme in ABP Blazor applications.</title>
      <description>In this article, I will show how you can replace the Basic Bootstrap theme with a free Custom Bootstrap theme in  Blazor ABP Framework applications.</description>
      <pubDate>Fri, 05 Feb 2021 11:32:59 Z</pubDate>
      <a10:updated>2026-03-09T22:08:30Z</a10:updated>
      <content:encoded><![CDATA[<h2>Replace Basic Bootstrap theme with a free custom Bootstrap theme</h2>
<h2>Introduction</h2>
<p>In this article, I will show how you can replace the <strong>Basic Bootstrap theme</strong> with a <strong>free Custom Bootstrap theme</strong> in a <strong>Blazor ABP Framework</strong> application.</p>
<h3>Source Code</h3>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpReplaceBasicTheme.git">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution.</p>
<ul>
<li>.NET 5.0 SDK</li>
<li>VsCode, Visual Studio 2019 16.8.0+ or another compatible IDE</li>
</ul>
<h2>Development</h2>
<h3>Creating a new Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">   dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">   abp new AbpReplaceBasicTheme -u blazor
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpReplaceBasicTheme.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpReplaceBasicTheme.HttpApi.Host</code> application to start the server-side.</li>
<li>Run the <code>AbpReplaceBasicTheme.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<h2>Copy BasicTheme to the src folder of your project</h2>
<ul>
<li>Open a command prompt and clone the <a href="https://github.com/abpframework/abp">apb repository</a> into your computer.</li>
</ul>
<pre><code class="language-bash">   git clone https://github.com/abpframework/abp
</code></pre>
<ul>
<li>Once the cloning is done, navigate to the <code>framework\src</code> folder of the repository.</li>
<li>Copy the <code>Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme</code> project into the <code>src</code> folder of your project.</li>
<li>Copy the <code>Volo.Abp.AspNetCore.Components.WebAssembly.Theming</code> project into the <code>src</code> folder of your project.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpReplaceBasicTheme/gh-pages/images/src_folder_structure.jpg" alt="src folder structure" /></p>
<h2>Remove the Package Reference of the Basic Theme in the Blazor project</h2>
<ul>
<li>Open the <strong>Blazor.csproj</strong> file and remove or comment out the <strong>BasicTheme</strong> package reference.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpReplaceBasicTheme/gh-pages/images/remove_or_comment_out_in_blazor_csproj.jpg" alt="Remove or Comment out" /></p>
<h2>Fix the build errors in WebAssembly.Theming project</h2>
<ul>
<li>Open file <strong>Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj</strong> and remove or comment out the following lines.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpReplaceBasicTheme/gh-pages/images/remove_or_comment_out_in_theming_csproj.jpg" alt="Remove or Comment out" /></p>
<ul>
<li><p>Open a command prompt in the <strong>WebAssembly.Theming</strong> project and run <code>dotnet build</code>. The build will fail because of missing NuGet packages.</p>
</li>
<li><p>Run the command below to install the missing NuGet packages.</p>
</li>
</ul>
<pre><code class="language-bash">   abp add-package Volo.Abp.BlazoriseUI
   abp add-package Volo.Abp.Http.Client.IdentityModel.WebAssembly
   abp add-package Volo.Abp.UI.Navigation
</code></pre>
<ul>
<li>Running <code>dotnet build</code> again in the <strong>WebAssembly.Theming</strong> project should succeed by now!</li>
</ul>
<h2>Build the BasicTheme project and fix the build errors</h2>
<ul>
<li>Open file <strong>Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj</strong> and remove or comment out the following lines.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpReplaceBasicTheme/gh-pages/images/remove_or_comment_out_in_basictheme_csproj.jpg" alt="Remove or Comment out" /></p>
<ul>
<li>Open a command prompt in the <strong>WebAssembly.BasicTheme</strong> project and run <code>dotnet build</code>. The build should succeed.</li>
</ul>
<h2>Add a project reference to the BasicTheme project in the Blazor.csproj file</h2>
<ul>
<li>Open a command prompt in the <strong>Blazor</strong> project of your application and add a project reference to the <strong>BasicTheme</strong> project.</li>
</ul>
<pre><code class="language-bash">   dotnet add reference ../../src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj
</code></pre>
<ul>
<li>Run <code>dotnet build</code> in the <strong>Blazor</strong> project to see if the project builds successfully!</li>
</ul>
<h2>Replace Basic Bootstrap theme with Custom Bootstrap theme</h2>
<ul>
<li><p>Find yourself a free Bootstrap theme on the internet. In this example I use the  <a href="https://bootswatch.com/superhero/">SuperHero Bootstrap Theme</a> I found on <a href="https://bootswatch.com/">bootswatch.com</a>.</p>
</li>
<li><p>Download and replace file <strong>wwwroot\libs\bootstrap\css\bootstrap.min.css</strong> in the <strong>Volo.Abp.AspNetCore.Components.WebAssembly.Theming</strong> project.</p>
</li>
</ul>
<h2>Bundling Blazor project</h2>
<ul>
<li>Open a command prompt in the <strong>root</strong> of your application to <strong>clean</strong> and <strong>build</strong> your project.</li>
</ul>
<pre><code class="language-bash">   dotnet clean
   dotnet build
</code></pre>
<ul>
<li>Open a command prompt in the <strong>Blazor</strong> project enter the command below to bundle the <strong>SuperHero Bootstrap theme</strong>.</li>
</ul>
<pre><code class="language-bash">   abp bundle
</code></pre>
<h2>Start the application by running both the Blazor and HttpApi.Host project</h2>
<p>Et voilà! This is the result.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpReplaceBasicTheme/gh-pages/images/superhero_bootstrap_theme.jpg" alt="SuperHero Bootstrap Theme up and running!" /></p>
<p>You can now use your custom Bootstrap theme instead of the basic Bootstrap theme.</p>
<p>Find more about adding global styles/scripts and other fundamentals about ABP theming <a href="https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming">here</a>.</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpReplaceBasicTheme">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/deploying-abp-applications-to-azure-ztvp6p57</guid>
      <link>https://abp.io/community/posts/deploying-abp-applications-to-azure-ztvp6p57</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>azure</category>
      <category>deployment</category>
      <title>Deploying ABP Applications to Azure</title>
      <description>A step-by-step tutorial on how to set up Continuous Deployment in Azure DevOps of an ABP Framework application. Find the source code on https://github.com/bartvanhoey/AbpIoAzureDevopsRepo
</description>
      <pubDate>Thu, 21 Jan 2021 14:17:11 Z</pubDate>
      <a10:updated>2026-03-05T13:13:46Z</a10:updated>
      <content:encoded><![CDATA[A step-by-step tutorial on how to set up Continuous Deployment in Azure DevOps of an ABP Framework application. Find the source code on https://github.com/bartvanhoey/AbpIoAzureDevopsRepo
<br \><a href="https://abpioazuredevopsblazor.azurewebsites.net/" rel="nofollow noopener noreferrer" title="Go to the Post">Go to the Post</a>]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/setup-facebook-registration-and-email-confirmation-on-user-registration-h3pamdcy</guid>
      <link>https://abp.io/community/posts/setup-facebook-registration-and-email-confirmation-on-user-registration-h3pamdcy</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>social-external-login</category>
      <title>Setup Facebook Registration and Email Confirmation on User Registration</title>
      <description>In this article, I will show you first how to set up Facebook registration in an ABP Framework application. Thereafter how to get a user who registered with Facebook verified by email.</description>
      <pubDate>Tue, 19 Jan 2021 22:12:20 Z</pubDate>
      <a10:updated>2026-03-09T07:32:38Z</a10:updated>
      <content:encoded><![CDATA[<h2>Facebook Registration and Email Confirmation on User Registration</h2>
<h2>Introduction</h2>
<p>In this article, I will show you first how to set up <strong>Facebook registration</strong> in an <strong>ABP Framework</strong> application. Afterwards, I will show you how this <strong>Facebook</strong> registered user gets verified by email.</p>
<p>The best way to follow along is to start from scratch and start with the article <a href="https://community.abp.io/articles/setup-email-confirmation-on-user-registration-q0vgxang">Setup Email Confirmation on User Registration</a> as it serves as the base for this article. Alternatively, you can clone the <a href="https://github.com/bartvanhoey/AbpUserVerificationByEmail">repository</a> of the project.</p>
<pre><code class="language-bash">git clone https://github.com/bartvanhoey/AbpUserVerificationByEmail.git
</code></pre>
<h2>Source Code</h2>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpFacebookRegistration">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution.</p>
<ul>
<li>.NET 5.0 SDK</li>
<li>VsCode, Visual Studio 2019 16.8.0+ or another compatible IDE</li>
</ul>
<p>You also need a <strong>Gmail</strong> account to follow along.</p>
<h2>Set up Facebook registration</h2>
<h3>Create the app in FACEBOOK for Developers</h3>
<ul>
<li>Navigate to <a href="https://developers.facebook.com/apps/">FACEBOOK for Developers</a> and sign in.</li>
<li>Click on the <strong>Create App</strong> button.</li>
<li>Choose <strong>Build Connected Experiences</strong> in the <strong>Create an App</strong> window. Continue.</li>
<li>Fill in <strong>App Display Name</strong> and click on the <strong>Create App</strong> button.</li>
<li>In the <strong>Add Products to Your App</strong> section, find <strong>Facebook Login</strong> and click <strong>Set Up</strong>.</li>
<li>Goto the <strong>Facebook Login Settings</strong> and update the settings as in the image below.</li>
<li>Replace the port number with the port number on which the <strong>SwaggerUI</strong> gets served. You can also find it in file <strong>launchsettings.json</strong> of the <strong>AbpFacebookRegistration.HttpApi.Host</strong> project.</li>
<li>Copy <strong>App ID</strong> and <strong>App Secret</strong> in the <strong>Basic Settings</strong> windows.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFacebookRegistration/gh-pages/images/FacebookLoginSettings.jpg" alt="Facebook Login Settings" /></p>
<h2>Development</h2>
<h3>Creating a new Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpFacebookRegistration -u blazor -o AbpFacebookRegistration
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpFacebookRegistration.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpFacebookRegistration.HttpApi.Host</code> application to start the server-side.</li>
<li>Run the <code>AbpFacebookRegistration.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<h3>Add the Facebook NuGet Package to HttpApi.Host project</h3>
<ul>
<li>Open a command prompt in the <strong>HttpApi.Host</strong> project and run the command below:</li>
</ul>
<pre><code class="language-bash">dotnet add package Microsoft.AspNetCore.Authentication.Facebook
</code></pre>
<h3>Add an <strong>Authentication</strong> section to the <strong>appsettings.json</strong> file of the <strong>HttpApi.Host</strong> project</h3>
<p><strong>WARNING</strong>: Make sure you <strong>don't publish</strong> your <strong>Credentials</strong> to <strong>GitHub</strong> or <strong>another Versioning System</strong>.</p>
<pre><code class="language-json"> &quot;Authentication&quot;: {
    &quot;Facebook&quot;: {
      &quot;AppId&quot; : &quot;YourAppIdHere&quot;,
      &quot;AppSecret&quot;: &quot;YourAppSecretHere&quot;
    }
}
</code></pre>
<h3>Configure the Provider in the HttpApiHostModule of the HttpApi.Host</h3>
<p>Add the <strong>AddFacebook</strong> extension method to the <strong>ConfigureAuthentication</strong> method in the <strong>HttpApiHostModule</strong> of the <strong>HttpApi.Host</strong></p>
<pre><code class="language-csharp">    private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
    {
      //...  

      context.Services.AddAuthentication()
            .AddFacebook(facebook =&gt;
            {
                facebook.AppId = configuration[&quot;Authentication:Facebook:AppId&quot;];
                facebook.AppSecret = configuration[&quot;Authentication:Facebook:AppSecret&quot;];
                facebook.Scope.Add(&quot;email&quot;);
                facebook.Scope.Add(&quot;public_profile&quot;);
            });
    }
</code></pre>
<h2>Open &amp; Run the Application</h2>
<ul>
<li>Run the <code>AbpFacebookRegistration.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpFacebookRegistration.HttpApi.Host</code> application to start the server-side.</li>
<li>Run the <code>AbpFacebookRegistration.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<p>When you navigate to the <strong>login page</strong> of your application, you already see the <strong>Facebook</strong> button. DO NOT CLICK THE FACEBOOK BUTTON YET! <strong>Although Facebook Registration ALREADY works</strong>, I would like to show you the Email Confirmation part too. :-)</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFacebookRegistration/gh-pages/images/LoginScreen.jpg" alt="Login page with Facebook button" /></p>
<h2>Gmail Credentials</h2>
<h3>Setting up Google SMTP Server in appsettings.json</h3>
<ul>
<li><p>Open file <strong>EncryptGmailPasswordAppService.cs</strong> in the <strong>AbpFacebookRegistration.Application</strong> project and <strong>replace YourGmailPasswordHere</strong> with your Gmail password.</p>
</li>
<li><p>Run the <strong>AbpFacebookRegistration.HttpApi.Host</strong> project and navigate to the <strong>SwaggerUI</strong>.</p>
</li>
<li><p>Find the <strong>EncryptGmailPassword</strong> method and click on <strong>Try it out</strong> first, <strong>Execute</strong> after.</p>
</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFacebookRegistration/gh-pages/images/EncryptGmailPassword.jpg" alt="Encrypt Gmail Password" /></p>
<ul>
<li><p>Copy the <strong>encrypted Gmail password</strong> in the response body.</p>
</li>
<li><p>Open file <strong>appsettings.json</strong> in project <strong>HttpApi.Host</strong> and update the <strong>Smtp Settings</strong> with the correct values.</p>
</li>
</ul>
<pre><code class="language-json">&quot;Settings&quot;: {
    &quot;Abp.Mailing.Smtp.Host&quot;: &quot;smtp.gmail.com&quot;,
    &quot;Abp.Mailing.Smtp.Port&quot;: &quot;587&quot;,
    &quot;Abp.Mailing.Smtp.UserName&quot;: &quot;your-gmail-email-address-here&quot;,
    &quot;Abp.Mailing.Smtp.Password&quot;: &quot;your-encrypted-gmail-password-here&quot;,
    &quot;Abp.Mailing.Smtp.Domain&quot;: &quot;&quot;,
    &quot;Abp.Mailing.Smtp.EnableSsl&quot;: &quot;true&quot;,
    &quot;Abp.Mailing.Smtp.UseDefaultCredentials&quot;: &quot;false&quot;,
    &quot;Abp.Mailing.DefaultFromAddress&quot;: &quot;your-gmail-email-address-here&quot;,
    &quot;Abp.Mailing.DefaultFromDisplayName&quot;: &quot;Your-Custom-Text-Here&quot;
  }
</code></pre>
<h2>Pages/Account folder of the HttpApi.Host project</h2>
<p>In the <strong>Pages/Account</strong> folder, you find the files needed for the Email Confirmation after User Registration. I copied/pasted these files from the <a href="https://github.com/abpframework/abp/tree/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account">Account Module</a> of the <strong>ABP Framework</strong> and adapted them to my needs. By doing so, you override the original files of the Account Module, and you can hook in into the registration flow.</p>
<p>The heavy-lifting happens mainly in the <strong>RegisterModel</strong> file. When an unregistered user clicks on the <strong>Facebook</strong> button to log in, the <strong>OnPostAsync</strong> method gets executed.  The <strong>RegisterExternalUserAsync</strong> method to create the user is called next. At the end of the RegisterExternalUserAsync method, an email is sent by the <strong>SendEmailToAskForEmailConfirmationAsync</strong> method.</p>
<h2>Test the Facebook Registration flow and User Email Verification</h2>
<ul>
<li>Start both the <strong>Blazor</strong> and <strong>HttpApi.Host</strong> project to run the application.</li>
<li>Navigate to the <strong>Login</strong> page and click on the <strong>Facebook</strong> button.</li>
<li>You will see a <strong>Facebook</strong> screen that says your app will receive your name, profile picture, and email address.</li>
<li>Next, you will land on the <strong>My Customer Register Page</strong>.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFacebookRegistration/gh-pages/images/CustomRegisterPage.jpg" alt="Custom Register Page" /></p>
<ul>
<li>Click on the <strong>Register</strong> button to complete the <strong>Facebook registration</strong> (Email sent in this step).</li>
<li>Go to your email inbox and click on the <a href="https://localhost:44367/">clicking here</a> link to confirm your account.</li>
<li>Navigate to the <strong>Login</strong> page and click on the <strong>Facebook</strong> button.</li>
</ul>
<p>Et voilà, this is the result. <strong>The user's email address is successfully verified</strong>!</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpFacebookRegistration/gh-pages/images/userverifiedbyemail.jpg" alt="User verified by email" /></p>
<p>You can now force a user who registered with Facebook to confirm his email address before he can use your application.</p>
<p>Click on the links to find more about <a href="https://docs.abp.io/en/abp/latest/Emailing">Sending Emails</a> and the <a href="https://docs.abp.io/en/abp/latest/Modules/Account">Account Module</a></p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpFacebookRegistration">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/code-faster-with-abpx-in-visual-studio-code-f0pzw9h4</guid>
      <link>https://abp.io/community/posts/code-faster-with-abpx-in-visual-studio-code-f0pzw9h4</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>visual-studio-code-extensions</category>
      <title>Code Faster with ABPx in Visual Studio Code</title>
      <description>In this article, I would like to introduce you to ABPx, code snippets that start with an x. ABPx is a VsCode Extension for the ABP Framework, available in the Visual Studio Marketplace. Its main goal is to speed up the Development Process of ABP Framework applications in Visual Studio Code Check it out!</description>
      <pubDate>Mon, 18 Jan 2021 16:34:27 Z</pubDate>
      <a10:updated>2026-03-09T19:50:12Z</a10:updated>
      <content:encoded><![CDATA[<h2>Code faster with ABPx in Visual Studio Code</h2>
<p><strong>ABPx</strong> - code snippets that <strong>start with an x</strong></p>
<h2>Introduction</h2>
<p>In this article, I would like to introduce you to <strong>ABPx</strong>, a <strong>VsCode Extension</strong> for the ABP Framework, available in the <a href="https://marketplace.visualstudio.com/items?itemName=BartVanHoey.abpx">Visual Studio Marketplace</a>.</p>
<p>The <strong>main goal of ABPx</strong> is to <strong>speed up the development process</strong> of <strong>ABP applications</strong> in <strong>Visual Studio Code</strong>.</p>
<p>When I started learning the ABP Framework, I noticed that the ABP Framework has a <strong>convention over configuration</strong> approach and you often end up writing the same code over and over again. That's why I started to develop a VsCode Extension with <strong>useful code snippets</strong> to make the life of an <strong>ABP Framework developer</strong> a little easier.</p>
<h2>ABPx Code Snippets in Action</h2>
<p>Below you will see 3 examples of how to use ABPx code snippets in practice. At the moment of this writing, there are <strong>about 180 ABPx code snippets for C#, razor and jsonc files</strong> that you can use in your daily ABP Framework coding work.</p>
<h3>Generate VsCode Launch Configurations for your ABP application</h3>
<p>Starting with an ABP project in VsCode can be a bit of hassle. <strong>ABPx makes it a lot easier</strong> to get you up and running by <strong>generating the launch configurations needed</strong>, as you can see below.</p>
<p>After you opened an ABP application in <strong>Visual Studio Code</strong> hit CTRL+SHIFT+P (.NET Generate Assets for Build and Debug). This will add a .vscode folder to the root of the project with a launch.json and tasks.json file.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/ABPx/master/images/RequiredAssetsMissing.jpg%20%22Required%20Assets%20Missing!%22" alt="Required Assets Missing!" /></p>
<ul>
<li>Click <strong>Yes</strong> to add the <em>required assets to build and debug</em> your application. Select the <em>HttpApi.Host</em> project in the <em>Select the project to launch</em> dropdown.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/ABPx/master/images/launchconfigurations.gif%20%22Generate%20Launch%20Configurations%20needed%20for%20your%20project!%22" alt="Generate Launch Configurations!" /></p>
<h3>Generate an AppService class that inherits from the CrudAppService base class</h3>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/ABPx/master/images/crudappservice.gif%20%22Generate%20an%20AppService%20class%20that%20inherits%20from%20the%20CrudAppService%20base%20class!%22" alt="CrudAppService snippet!" /></p>
<h3>Create a Permission Group, add Permissions to Permission Group and generate Translations</h3>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/ABPx/master/images/abpx_in_action.gif%20%22Create%20a%20Permission%20Group%20and%20generate%20Translations!%22" alt="Permissions Added!" /></p>
<h2>ABPx Source Code</h2>
<p>Get the <a href="https://github.com/bartvanhoey/ABPx">source code</a> of the <strong>ABPx VsCode Extension</strong> on GitHub.</p>
<h2>Install ABPx</h2>
<p>Please feel free to install the <strong>ABPx extension in VsCode</strong>, and if you see room for improvement or you have a snippet in mind you want to have included? <a href="https://github.com/bartvanhoey/ABPx/issues/new">Create an issue</a> in the ABPx repository. I will see what I can do! :-)</p>
<p><strong>ABPx</strong> - code snippets that <strong>start with an x</strong></p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/setup-email-confirmation-on-user-registration-q0vgxang</guid>
      <link>https://abp.io/community/posts/setup-email-confirmation-on-user-registration-q0vgxang</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>email-confirmation</category>
      <title>Setup Email Confirmation on User Registration</title>
      <description>How to setup email verification after a user fills in the registration form in an ABP application.</description>
      <pubDate>Mon, 11 Jan 2021 14:27:26 Z</pubDate>
      <a10:updated>2026-03-09T16:18:05Z</a10:updated>
      <content:encoded><![CDATA[<h2>Setup Email Confirmation on User Registration</h2>
<h2>Introduction</h2>
<p>In this article, I will show you how to get a user verified by email after he fills in the registration form in an <strong>ABP Framework</strong> application.</p>
<p>In this article I make use of the free <strong>Google SMTP Server</strong> for sending emails,  in a real-world application, however, you probably would choose another <strong>Email Delivery Service</strong> like <strong>SendGrid, Mailjet, etc.</strong></p>
<h3>Source Code</h3>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpUserVerificationByEmail">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution.</p>
<ul>
<li>.NET 6.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
<li>ABP CLI version 6.0.0</li>
</ul>
<p>You also need a <strong>Gmail</strong> account to follow along.</p>
<h2>Development</h2>
<h3>Create a new Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpUserVerificationByEmail -u blazor -o AbpUserVerificationByEmail
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpUserVerificationByEmail.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpUserVerificationByEmail.HttpApi.Host</code> application to start the server-side.</li>
<li>Run the <code>AbpUserVerificationByEmail.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<h2>Get your Google App Password</h2>
<ol>
<li>Navigate to wwww.google.com</li>
<li>Click on your <strong>profile photo</strong> of your <strong>Google account</strong> in the upper right corner of the page</li>
<li>Click Manage <strong>your Google Account</strong></li>
<li>Click on the <strong>Security</strong> tab</li>
<li>In the <strong>Signing in to Google</strong> section, click on <strong>App Passwords</strong>. Enter your password and click Next</li>
<li>In the App passwords page, <strong>Select app</strong> (or Custom name) and <strong>Select Device</strong> (or Custom name)</li>
<li>Click <strong>Generate</strong> to generate your App password</li>
<li>Copy/paste your app password (remove blank spaces)</li>
</ol>
<h2>Create a basic EmailService</h2>
<h3>EmailService class</h3>
<ul>
<li>Create a folder <strong>Email</strong> in the <strong>Domain</strong> project of your application.</li>
<li>Add an <strong>EmailService.cs</strong> class to the <strong>Email</strong> folder. Copy/paste code below.</li>
</ul>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;
using Volo.Abp.Security.Encryption;

namespace AbpUserVerificationByEmail.Domain.Email
{
    public class EmailService : ITransientDependency
    {
        private readonly IEmailSender _emailSender;
        public IStringEncryptionService _encryptionService { get; set; }

        public EmailService(IEmailSender emailSender) =&gt; _emailSender = emailSender;

        public async Task SendEmailAsync()
        {
            var encryptedGoogleAppPassword = _encryptionService.Encrypt(&quot;your-Google-App-Password-here&quot;);
            await _emailSender.SendAsync(&quot;recipient-email-here&quot;, &quot;Email subject&quot;, &quot;This is the email body...&quot;);
        }
    }
}
</code></pre>
<h3>CustomRegisterModel class</h3>
<ul>
<li>Create a folder structure <strong>Pages/Account</strong> in the <strong>HttpApi.Host</strong> project of your application.</li>
<li>Add a <strong>RegisterModel.cs</strong> file to the <strong>Account</strong> folder and paste in code below.</li>
</ul>
<pre><code class="language-csharp">using System.Threading.Tasks;
using AbpUserVerificationByEmail.Domain.Email;
using Volo.Abp.Account;
using Volo.Abp.Account.Web.Pages.Account;

namespace AbpUserVerificationByEmail.HttpApi.Host.Pages.Account
{
    public class CustomRegisterModel : RegisterModel
    {
        private readonly EmailService _emailService;

        public CustomRegisterModel(IAccountAppService accountAppService, EmailService emailService) : base(accountAppService) =&gt; _emailService = emailService;

        protected override async Task RegisterLocalUserAsync()
        {
            await _emailService.SendEmailAsync();
            await base.RegisterLocalUserAsync();
        }
    }
}
</code></pre>
<h3>Register.cshtml</h3>
<ul>
<li>Add a <strong>Register.cshtml</strong> file to the <strong>Account</strong> folder.</li>
</ul>
<pre><code class="language-html">@page
@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

@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization

@model AbpUserVerificationByEmail.HttpApi.Host.Pages.Account.CustomRegisterModel
@inject IHtmlLocalizer&lt;AccountResource&gt; L

&lt;div class=&quot;card mt-3 shadow-sm rounded&quot;&gt;
    &lt;div class=&quot;card-body p-5&quot;&gt;
        @* &lt;h4&gt;@L[&quot;Register&quot;]&lt;/h4&gt; *@
        &lt;h4&gt;My Custom Register Page&lt;/h4&gt;
        &lt;strong&gt;
            @L[&quot;AlreadyRegistered&quot;]
            &lt;a href=&quot;@Url.Page(&quot;./Login&quot;, new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})&quot; class=&quot;text-decoration-none&quot;&gt;@L[&quot;Login&quot;]&lt;/a&gt;
        &lt;/strong&gt;
        &lt;form method=&quot;post&quot; class=&quot;mt-4&quot;&gt;
            @if (!Model.IsExternalLogin)
            {
                &lt;abp-input asp-for=&quot;Input.UserName&quot; auto-focus=&quot;true&quot;/&gt;
            }

            &lt;abp-input asp-for=&quot;Input.EmailAddress&quot;/&gt;

            @if (!Model.IsExternalLogin)
            {
                &lt;abp-input asp-for=&quot;Input.Password&quot;/&gt;
            }
            &lt;abp-button button-type=&quot;Primary&quot; type=&quot;submit&quot; class=&quot;btn-lg btn-block mt-4&quot;&gt;@L[&quot;Register&quot;]&lt;/abp-button&gt;
        &lt;/form&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h3>Comment out the statement that injects class NullEmailSender in the <strong>Domain</strong> project</h3>
<ul>
<li>In file <strong>AbpUserVerificationByEmailDomainModule.cs</strong>  comment out the statement below.</li>
</ul>
<pre><code class="language-csharp">// #if DEBUG
//    context.Services.Replace(ServiceDescriptor.Singleton&lt;IEmailSender, NullEmailSender&gt;());
// #endif
</code></pre>
<p>If we don't comment out the statement above, the application will not send emails as class NullEmailSender will be injected by the Dependency Injection.</p>
<h3>Get the encrypted Gmail password</h3>
<ul>
<li>In the EmailService, set a breakpoint on the line <em>await _emailSender.SendAsync(&quot;...&quot;);</em></li>
<li>Replace <em>your-Google-App-Password-here</em> with your generate <strong>Google App Password</strong>.</li>
<li>Replace <em>recipient-email-here</em> with your email address.</li>
<li>Start both the <strong>Blazor</strong> and <strong>HttpApi.Host</strong> project to run the application.</li>
<li>Navigate to the <strong>Login</strong> page and click on the  <a href="https://localhost:44367/">Register</a> link.</li>
<li>Fill in the form of the <strong>My Custom Register Page</strong> and click the <strong>Register</strong> button.</li>
<li>Copy the value of the <strong>encryptedGoogleAppPassword</strong> when the breakpoint gets hit.</li>
<li>Stop both the Blazor and the HttpApi.Host project.</li>
<li>Open file <strong>appsettings.json</strong> in project <strong>HttpApi.Host</strong></li>
<li>Add a <strong>Settings</strong> section and update the <strong>Smtp Settings</strong> with the correct values.</li>
</ul>
<pre><code class="language-json">&quot;Settings&quot;: {
    &quot;Abp.Mailing.Smtp.Host&quot;: &quot;smtp.gmail.com&quot;,
    &quot;Abp.Mailing.Smtp.Port&quot;: &quot;587&quot;,
    &quot;Abp.Mailing.Smtp.UserName&quot;: &quot;your-gmail-email-address-here&quot;,
    &quot;Abp.Mailing.Smtp.Password&quot;: &quot;your-Google-App-Password-here&quot;,
    &quot;Abp.Mailing.Smtp.Domain&quot;: &quot;&quot;,
    &quot;Abp.Mailing.Smtp.EnableSsl&quot;: &quot;true&quot;,
    &quot;Abp.Mailing.Smtp.UseDefaultCredentials&quot;: &quot;false&quot;,
    &quot;Abp.Mailing.DefaultFromAddress&quot;: &quot;your-gmail-email-address-here&quot;,
    &quot;Abp.Mailing.DefaultFromDisplayName&quot;: &quot;Your-Custom-Text-Here&quot;
  }
</code></pre>
<h3>Check if you can send and receive an email with the EmailService</h3>
<ul>
<li>Remove the <strong>IStringEncryptionService</strong> from the <strong>EmailService</strong> class as no longer needed.</li>
</ul>
<pre><code class="language-csharp">public class EmailService : ITransientDependency
{
    private readonly IEmailSender _emailSender;

   public EmailService(IEmailSender emailSender) =&gt; _emailSender = emailSender;

    public async Task SendEmailAsync()
    {
      // TODO replace recipient-email-here
      await _emailSender.SendAsync(&quot;recipient-email-here&quot;, &quot;Email subject&quot;, &quot;This is the email body...&quot;);
    }
 }
</code></pre>
<ul>
<li>If you already registered the user, delete this user first in table <strong>AbpUsers</strong> in the database.</li>
<li>Start both the <strong>Blazor</strong> and <strong>HttpApi.Host</strong> project to run the application.</li>
<li>Navigate to the <strong>Login</strong> page again and click on the  <a href="https://localhost:44367/">Register</a> link.</li>
<li>Fill in the form of the <strong>My Custom Register Page</strong> and click the <strong>Register</strong> button.</li>
<li>If all goes well, you should <strong>receive an email</strong> sent by the EmailService and the <strong>user should have been registered</strong>.</li>
</ul>
<p><strong>WARNING</strong>: Make sure you <strong>don't publish</strong> your <strong>Google Credentials</strong> to <strong>GitHub</strong> or <strong>another Versioning System</strong>.</p>
<h2>Change Index.razor of the Blazor project</h2>
<ul>
<li>Open <strong>Index.razor</strong> and update <strong>div class=&quot;container&quot;</strong> with the code below.</li>
</ul>
<pre><code class="language-html">&lt;div class=&quot;container&quot;&gt;
    &lt;div class=&quot;p-5 text-center&quot;&gt;
        @if (!CurrentUser.IsAuthenticated)
        {
            &lt;Badge Color=&quot;Color.Danger&quot; class=&quot;mb-4&quot;&gt;
                &lt;h5 class=&quot;m-1&quot;&gt; &lt;i class=&quot;fas fa-email&quot;&gt;&lt;/i&gt; &lt;strong&gt;User is NOT Authenticated!&lt;/strong&gt;&lt;/h5&gt;
            &lt;/Badge&gt;
        }

        @if (CurrentUser.IsAuthenticated &amp;&amp; !CurrentUser.EmailVerified)
        {
            &lt;Badge Color=&quot;Color.Warning&quot; class=&quot;mb-4&quot;&gt;
                &lt;h5 class=&quot;m-1&quot;&gt; &lt;i class=&quot;fas fa-email&quot;&gt;&lt;/i&gt; &lt;strong&gt;User has not NOT been verified by email yet!&lt;/strong&gt;&lt;/h5&gt;
            &lt;/Badge&gt;
        }

        @if (CurrentUser.IsAuthenticated &amp;&amp; CurrentUser.EmailVerified)
        {
            &lt;Badge Color=&quot;Color.Success&quot; class=&quot;mb-4&quot;&gt;
                &lt;h5 class=&quot;m-1&quot;&gt; &lt;i class=&quot;fas fa-email&quot;&gt;&lt;/i&gt; &lt;strong&gt;User is successfully verified by email!&lt;/strong&gt;&lt;/h5&gt;
            &lt;/Badge&gt;
        }
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li>Start both the <strong>Blazor</strong> and <strong>HttpApi.Host</strong> project to run the application.</li>
<li>Go to the <strong>Login</strong> form and login with the credentials of the new user. The user is <strong>logged in</strong> but <strong>not email verified</strong>.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpUserVerificationByEmail/gh-pages/images/usernotverifiedbyemail.jpg" alt="User has not been email verified yet" /></p>
<h2>Update SignIn IdentityOptions so a user has to confirm his email address</h2>
<ul>
<li>Open file <strong>AbpUserVerificationByEmailHttpApiModule.cs</strong> in the <strong>AbpUserVerificationByEmail.HttpApi.Host</strong> project</li>
<li>Add <code>ConfigureIdentityOptions(context);</code> as last statement in the <strong>ConfigureServices</strong> method.</li>
<li>Add a private method <strong>ConfigureIdentityOptions</strong> just beneath the <strong><strong>ConfigureServices</strong></strong> method.</li>
</ul>
<pre><code class="language-csharp">// import using statements
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;

public override void ConfigureServices(ServiceConfigurationContext context)
{
    ConfigureLocalization();
    ConfigureIdentityOptions(context);
}

private void ConfigureIdentityOptions(ServiceConfigurationContext context)
{
    context.Services.Configure&lt;IdentityOptions&gt;(options =&gt;
    {
      options.SignIn.RequireConfirmedAccount = true;
      options.SignIn.RequireConfirmedEmail = true;
      options.SignIn.RequireConfirmedPhoneNumber = false;
    });
}
</code></pre>
<h2>Complete user registration flow</h2>
<h3>Add <code>RegisterConfirmation</code> and <code>ConfirmEmail</code> pages to the Pages\Account folder of the <strong>HttpApi.Host</strong> project</h3>
<ul>
<li>Create a new file <strong>RegisterConfirmationModel.cs</strong> to the <strong>Pages\Account</strong> folder and copy/paste the code below.</li>
</ul>
<pre><code class="language-csharp">using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Volo.Abp.Emailing;
using Volo.Abp.Identity;

namespace AbpUserVerificationByEmail.HttpApi.Host.Pages.Account
{

  [AllowAnonymous]
  public class CustomRegisterConfirmationModel : PageModel
  {
    private readonly IdentityUserManager _userManager;
    private readonly IEmailSender _sender;
    public bool DisplayConfirmAccountLink { get; set; }
    public string EmailConfirmationUrl { get; set; }

    public CustomRegisterConfirmationModel(IdentityUserManager userManager, IEmailSender sender)
    {
      _userManager = userManager;
      _sender = sender;
    }

    public async Task&lt;IActionResult&gt; OnGetAsync(string email, string returnUrl = null)
    {
      if (email.IsNullOrWhiteSpace()) return RedirectToPage(&quot;/Index&quot;);

      var user = await _userManager.FindByEmailAsync(email);
      if (user == null) return NotFound($&quot;Unable to load user with email '{email}'.&quot;);

      // TODO Set to true if you want to display the Account/ConfirmEmail page
      DisplayConfirmAccountLink = false;
      if (DisplayConfirmAccountLink)
      {
        var userId = await _userManager.GetUserIdAsync(user);
        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
        EmailConfirmationUrl = Url.Page(
            &quot;/Account/ConfirmEmail&quot;,
            pageHandler: null,
            values: new { userId = userId, code = code },
            protocol: Request.Scheme);
      }
      return Page();
    }
  }
}
</code></pre>
<ul>
<li>Create a new file <strong>RegisterConfirmation.cshtml</strong> to the <strong>Pages\Account</strong>  folder and copy/paste the code below.</li>
</ul>
<pre><code class="language-html">@page
@model AbpUserVerificationByEmail.HttpApi.Host.Pages.Account.CustomRegisterConfirmationModel
@{
    ViewData[&quot;Title&quot;] = &quot;Register confirmation&quot;;
}

&lt;div class=&quot;card mt-3 shadow-sm rounded&quot;&gt;
    &lt;div class=&quot;card-body p-5&quot;&gt;
        &lt;h4&gt;@ViewData[&quot;Title&quot;]&lt;/h4&gt;
        @{
            if (@Model.DisplayConfirmAccountLink)
            {
                &lt;p&gt;
                    This app does not currently have a real email sender registered, see &lt;a
                        href=&quot;https://aka.ms/aspaccountconf&quot;&gt;these docs&lt;/a&gt; for how to configure a real email sender.
                    Normally this would be emailed: &lt;a id=&quot;confirm-link&quot; href=&quot;@Model.EmailConfirmationUrl&quot;&gt;Click here to
                        confirm your account&lt;/a&gt;
                &lt;/p&gt;
            }
            else
            {
                &lt;p&gt;
                    Please check your email to confirm your account.
                &lt;/p&gt;
            }
        }
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li>Create a new file <strong>ConfirmEmailModel.cs</strong> to the <strong>Pages\Account</strong> folder and copy/paste the code below.</li>
</ul>
<pre><code class="language-csharp">using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Volo.Abp.Identity;

namespace AbpUserVerificationByEmail.HttpApi.Host.Pages.Account
{
    [AllowAnonymous]
    public class CustomConfirmEmailModel : PageModel
    {
        private readonly IdentityUserManager _userManager;

        public CustomConfirmEmailModel(IdentityUserManager userManager) =&gt; _userManager = userManager;

        public async Task&lt;IActionResult&gt; OnGetAsync(string userId, string code)
        {
            if (userId.IsNullOrWhiteSpace()|| code.IsNullOrWhiteSpace()) return RedirectToPage(&quot;/Index&quot;);

            var user = await _userManager.FindByIdAsync(userId);
            if (user == null) return NotFound($&quot;Unable to load user with ID '{userId}'.&quot;);

            code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
            var result = await _userManager.ConfirmEmailAsync(user, code);
            return Page();
        }
    }
}
</code></pre>
<ul>
<li>Create a new file <strong>ConfirmEmail.cshtml</strong> to the <strong>Pages\Account</strong> folder and copy/paste the code below.</li>
</ul>
<pre><code class="language-html">@page
@model AbpUserVerificationByEmail.HttpApi.Host.Pages.Account.CustomConfirmEmailModel
@{
    ViewData[&quot;Title&quot;] = &quot;Email Confirmed&quot;;
}

&lt;div class=&quot;card mt-3 shadow-sm rounded&quot;&gt;
    &lt;div class=&quot;card-body p-5&quot;&gt;
        &lt;h4&gt;@ViewData[&quot;Title&quot;]&lt;/h4&gt;
        &lt;hr&gt;
        Email Successfully Confirmed
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h3>Update file RegisterModel.cs in HttpApi.Host project</h3>
<pre><code class="language-csharp">using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Emailing;

namespace AbpUserVerificationByEmail.HttpApi.Host.Pages.Account
{
  public class CustomRegisterModel : RegisterModel
  {
    private readonly IAccountAppService _accountAppService;
    private readonly IEmailSender _emailSender;
    private Volo.Abp.Identity.IdentityUser _abpIdentityUser;

    public CustomRegisterModel(IAccountAppService accountAppService, IEmailSender emailSender) : base(accountAppService)
    {
      _emailSender = emailSender;
      _accountAppService = accountAppService;
    }

    public override async Task&lt;IActionResult&gt; OnPostAsync()
    {
      try
      {
        await CheckSelfRegistrationAsync();

        if (IsExternalLogin)
        {
          var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
          if (externalLoginInfo == null)
          {
            Logger.LogWarning(&quot;External login info is not available&quot;);
            return RedirectToPage(&quot;./Login&quot;);
          }
          await RegisterExternalUserAsync(externalLoginInfo, Input.EmailAddress);
        }
        else
        {
          await RegisterLocalUserAsync();
        }

        if (UserManager.Options.SignIn.RequireConfirmedAccount)
        {
          return RedirectToPage(&quot;RegisterConfirmation&quot;, new { email = Input.EmailAddress, returnUrl = ReturnUrl });
        }
        else
        {
          await SignInManager.SignInAsync(_abpIdentityUser, isPersistent: true);
          return LocalRedirect(ReturnUrl);
        }
       // return Redirect(ReturnUrl ?? &quot;~/&quot;); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow!
      }
      catch (BusinessException e)
      {
        Alerts.Danger(e.Message);
        return Page();
      }
    }

    protected override async Task RegisterLocalUserAsync()
    {
      ValidateModel();

      var userDto = await AccountAppService.RegisterAsync(
          new RegisterDto
          {
            AppName = &quot;YourAppName Here&quot;,
            EmailAddress = Input.EmailAddress,
            Password = Input.Password,
            UserName = Input.UserName
          }
      );

      _abpIdentityUser = await UserManager.GetByIdAsync(userDto.Id);

      // Send user an email to confirm email address      
      await SendEmailToAskForEmailConfirmationAsync(_abpIdentityUser);
    }

    protected override async Task RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string emailAddress)
    {
      await IdentityOptions.SetAsync();

      var user = new Volo.Abp.Identity.IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id);

      (await UserManager.CreateAsync(user)).CheckErrors();
      (await UserManager.AddDefaultRolesAsync(user)).CheckErrors();

      var userLoginAlreadyExists = user.Logins.Any(x =&gt;
          x.TenantId == user.TenantId &amp;&amp;
          x.LoginProvider == externalLoginInfo.LoginProvider &amp;&amp;
          x.ProviderKey == externalLoginInfo.ProviderKey);

      if (!userLoginAlreadyExists)
      {
        (await UserManager.AddLoginAsync(user, new UserLoginInfo(
            externalLoginInfo.LoginProvider,
            externalLoginInfo.ProviderKey,
            externalLoginInfo.ProviderDisplayName
        ))).CheckErrors();
      }

      await SendEmailToAskForEmailConfirmationAsync(user);
    }


    private async Task SendEmailToAskForEmailConfirmationAsync(Volo.Abp.Identity.IdentityUser user)
    {
      var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
      code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
      var callbackUrl = Url.Page(&quot;/Account/ConfirmEmail&quot;, pageHandler: null, values: new { userId = user.Id, code = code }, protocol: Request.Scheme);

      // TODO use EmailService instead of using IEmailSender directly
      await _emailSender.SendAsync(Input.EmailAddress, &quot;Confirm your email&quot;,
          $&quot;Please confirm your account by &lt;a href='https://github.com/bartvanhoey/AbpUserVerificationByEmail/blob/gh-pages/{HtmlEncoder.Default.Encode(callbackUrl)}'&gt;clicking here&lt;/a&gt;.&quot;);
    }

  }
}
</code></pre>
<h2>Test Registration flow and User Email Verification</h2>
<ul>
<li>If you already registered a user, delete it first in table <strong>AbpUsers</strong> in the database.</li>
<li>Start both the <strong>Blazor</strong> and <strong>HttpApi.Host</strong> project to run the application.</li>
<li>Navigate to the <strong>Login</strong> page and click on the  <a href="https://localhost:44367/">Register</a> link.</li>
<li>Fill in the form of the <strong>My Custom Register Page</strong> and click the <strong>Register</strong> button.</li>
<li>Go to your email inbox and click on the <a href="https://localhost:44367/">clicking here</a> link to confirm your account.</li>
<li>Navigate to the <strong>Login</strong> page and enter your credentials. Click <strong>Login</strong>.</li>
</ul>
<p>Et voilà! This is the result. <strong>The user's email address is successfully verified</strong>!</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpUserVerificationByEmail/gh-pages/images/userverifiedbyemail.jpg" alt="User verified by email" /></p>
<p>You can now force a user to confirm his email address before he can use your application.</p>
<p>Click on the links to find more about <a href="https://docs.abp.io/en/abp/latest/Emailing">Sending Emails</a> and the <a href="https://docs.abp.io/en/abp/latest/Modules/Account">Account Module</a></p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpUserVerificationByEmail">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/b1df1392-d64e-f529-812b-39fa04050bdd" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/b1df1392-d64e-f529-812b-39fa04050bdd" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/how-to-customize-the-login-page-of-an-abp-blazor-application-by4o9yms</guid>
      <link>https://abp.io/community/posts/how-to-customize-the-login-page-of-an-abp-blazor-application-by4o9yms</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>login-page</category>
      <category>blazor</category>
      <title>How to customize the login page of an ABP blazor application</title>
      <description>How to customize the login page of an ABP blazor application</description>
      <pubDate>Tue, 22 Dec 2020 21:42:51 Z</pubDate>
      <a10:updated>2026-03-09T19:05:50Z</a10:updated>
      <content:encoded><![CDATA[<h2>How to customize the login page of an ABP Blazor application</h2>
<h2>Introduction</h2>
<p>In this article, I will show you how to customize the login page of a <strong>Blazor APB application</strong></p>
<h2>Source Code</h2>
<p>The sample application has been developed with <strong>Blazor</strong> as UI framework and <strong>SQL Server</strong> as database provider.</p>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpBlazorCustomizeLoginPage">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
<li>ABP CLI version 8.0.0</li>
</ul>
<h2>Development</h2>
<h3>Creating a new Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command to create a new Blazor ABP application:</li>
</ul>
<pre><code class="language-bash">abp new AbpBlazorCustomizeLoginPage -u blazor -o AbpBlazorCustomizeLoginPage
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpBlazorCustomizeLoginPage.DbMigrator</code> application to apply the migrations and seed the initial data.</li>
<li>Run the <code>AbpBlazorCustomizeLoginPage.HttpApi.Host</code> application to start the server-side.</li>
<li>Run the <code>AbpBlazorCustomizeLoginPage.Blazor</code> application to start the Blazor UI project.</li>
</ul>
<h2>Create a CustomLoginModel</h2>
<ul>
<li>Create a folder structure <strong>Pages/Account</strong> in the <strong>HttpApi.Host</strong> project of your application.</li>
<li>Add a <strong>CustomLoginModel.cs</strong> class to the <strong>Account</strong> folder.</li>
</ul>
<pre><code class="language-csharp">using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;

namespace AbpBlazorCustomizeLoginPage.HttpApi.Host.Pages.Account
{
  public class CustomLoginModel : LoginModel
  {
    public CustomLoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions&lt;AbpAccountOptions&gt; accountOptions, IOptions&lt;IdentityOptions&gt; identityOptions, IdentityDynamicClaimsPrincipalContributorCache contributorCache)
        : base(schemeProvider, accountOptions, identityOptions, contributorCache) { }
  }
}
</code></pre>
<ul>
<li>Add a <strong>Login.cshtml</strong> file to the <strong>Account</strong> folder.</li>
</ul>
<pre><code class="language-html">@page
@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

@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings

@model AbpBlazorCustomizeLoginPage.HttpApi.Host.Pages.Account.CustomLoginModel

@inject IHtmlLocalizer&lt;AccountResource&gt; L
@inject Volo.Abp.Settings.ISettingProvider SettingProvider

&lt;div class=&quot;card text-center mt-3 shadow-sm rounded&quot;&gt;
    &lt;div class=&quot;card-body abp-background p-5&quot;&gt;
        &lt;img class=&quot;mb-4&quot; src=&quot;https://raw.githubusercontent.com/bartvanhoey/AbpBlazorCustomizeLoginPage/main/~/images/abp-logo-light.svg&quot; alt=&quot;ABP logo&quot; width=&quot;115&quot; height=&quot;55&quot;&gt;
        &lt;h4&gt;@L[&quot;Login&quot;]&lt;/h4&gt;
        @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
        {
            &lt;strong&gt;
                @L[&quot;AreYouANewUser&quot;]
                &lt;a href=&quot;@Url.Page(&quot;./Register&quot;, new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})&quot; class=&quot;text-decoration-none&quot;&gt;@L[&quot;Register&quot;]&lt;/a&gt;
            &lt;/strong&gt;
        }
        @if (Model.EnableLocalLogin)
        {
            &lt;form method=&quot;post&quot; class=&quot;mt-4 text-left&quot;&gt;
                &lt;input asp-for=&quot;ReturnUrl&quot; /&gt;
                &lt;input asp-for=&quot;ReturnUrlHash&quot; /&gt;
                &lt;div class=&quot;form-group&quot;&gt;
                    &lt;input asp-for=&quot;LoginInput.UserNameOrEmailAddress&quot; class=&quot;form-control&quot; placeholder=&quot;Username&quot; /&gt;
                    &lt;span asp-validation-for=&quot;LoginInput.UserNameOrEmailAddress&quot; class=&quot;text-danger&quot;&gt;&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;form-group&quot;&gt;
                    &lt;input asp-for=&quot;LoginInput.Password&quot; class=&quot;form-control&quot; placeholder=&quot;Password&quot;/&gt;
                    &lt;span asp-validation-for=&quot;LoginInput.Password&quot; class=&quot;text-danger&quot;&gt;&lt;/span&gt;
                &lt;/div&gt;
                &lt;abp-row&gt;
                    &lt;abp-column&gt;
                        &lt;abp-input asp-for=&quot;LoginInput.RememberMe&quot; class=&quot;mb-4&quot; /&gt;
                    &lt;/abp-column&gt;
                    &lt;abp-column class=&quot;text-right&quot;&gt;
                        &lt;a href=&quot;@Url.Page(&quot;./ForgotPassword&quot;, new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})&quot;&gt;@L[&quot;ForgotPassword&quot;]&lt;/a&gt;
                    &lt;/abp-column&gt;
                &lt;/abp-row&gt;
                &lt;abp-button type=&quot;submit&quot; button-type=&quot;Primary&quot; name=&quot;Action&quot; value=&quot;Login&quot; class=&quot;btn-block btn-lg mt-3&quot;&gt;@L[&quot;Login&quot;]&lt;/abp-button&gt;
                @if (Model.ShowCancelButton)
                {
                    &lt;abp-button type=&quot;submit&quot; button-type=&quot;Secondary&quot; formnovalidate=&quot;formnovalidate&quot; name=&quot;Action&quot; value=&quot;Cancel&quot; class=&quot;btn-block btn-lg mt-3&quot;&gt;@L[&quot;Cancel&quot;]&lt;/abp-button&gt;
                }
            &lt;/form&gt;
        }
        @if (Model.VisibleExternalProviders.Any())
        {
            &lt;div class=&quot;mt-2&quot;&gt;
                &lt;h5&gt;@L[&quot;OrLoginWith&quot;]&lt;/h5&gt;
                &lt;form asp-page=&quot;./Login&quot; asp-page-handler=&quot;ExternalLogin&quot; asp-route-returnUrl=&quot;@Model.ReturnUrl&quot; asp-route-returnUrlHash=&quot;@Model.ReturnUrlHash&quot; method=&quot;post&quot;&gt;
                    &lt;input asp-for=&quot;ReturnUrl&quot; /&gt;
                    &lt;input asp-for=&quot;ReturnUrlHash&quot; /&gt;
                    @foreach (var provider in Model.VisibleExternalProviders)
                    {
                        &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary m-1&quot; name=&quot;provider&quot; value=&quot;@provider.AuthenticationScheme&quot; title=&quot;@L[&quot;GivenTenantIsNotAvailable&quot;, provider.DisplayName]&quot;&gt;@provider.DisplayName&lt;/button&gt;
                    }
                &lt;/form&gt;
            &lt;/div&gt;
        }
        @if (!Model.EnableLocalLogin &amp;&amp; !Model.VisibleExternalProviders.Any())
        {
            &lt;div class=&quot;alert alert-warning&quot;&gt;
                &lt;strong&gt;@L[&quot;InvalidLoginRequest&quot;]&lt;/strong&gt;
                @L[&quot;ThereAreNoLoginSchemesConfiguredForThisClient&quot;]
            &lt;/div&gt;
        }
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h2>Add some custom styles and images to the HttpApi.Host project</h2>
<ul>
<li>add a file <strong>login.css</strong> to the <strong>wwwroot</strong> folder of the <strong>HttpApi.Host</strong> project.</li>
</ul>
<pre><code class="language-css">.abp-background {
    background-color:  #e90052 !important;
}
</code></pre>
<ul>
<li>Open file <strong>AbpBlazorCustomizeLoginPageHttpApiHostModule.cs</strong> and update the <strong>ConfigureBundles()</strong> method.</li>
</ul>
<pre><code class="language-csharp"> private void ConfigureBundles()
 {
    Configure&lt;AbpBundlingOptions&gt;(options =&gt;
    {
        options.StyleBundles.Configure(
            LeptonXLiteThemeBundles.Styles.Global,
            bundle =&gt;
            {
                bundle.AddFiles(&quot;/global-styles.css&quot;);
                bundle.AddFiles(&quot;/login.css&quot;);
            }
        );
    });
}
</code></pre>
<ul>
<li>add an <strong>assets/images</strong> folder to the <strong>wwwroot</strong> folder of the <strong>HttpApi.Host</strong> project and copy/paste the <strong>abp logo</strong> in the <strong>images</strong> folder.  You can find a copy of the logo <a href="https://github.com/bartvanhoey/AbpBlazorCustomizeLoginPage/blob/main/src/AbpBlazorCustomizeLoginPage.HttpApi.Host/wwwroot/images/abp-logo-light.svg">here</a>.</li>
</ul>
<h2>Start both the Blazor and the HttpApi.Host project to run the application</h2>
<p>Et voilà! This is the result.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpBlazorCustomizeLoginPage/main/images/loginpage.jpg" alt="Blazor Up and Running with customized login page" /></p>
<p>You can now modify the login page, add your custom styles,  custom images, etc.</p>
<p>Find more about ASP.NET Core (MVC/Razor Pages) User Interface Customization Guide <a href="https://docs.abp.io/en/abp/4.1/UI/AspNetCore/Customization-User-Interface">here</a>.</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpBlazorCustomizeLoginPage">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/32f02d83-ce52-aca8-1955-39f99e947e63" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/32f02d83-ce52-aca8-1955-39f99e947e63" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/how-to-export-excel-files-from-the-abp-framework-wm7nnw3n</guid>
      <link>https://abp.io/community/posts/how-to-export-excel-files-from-the-abp-framework-wm7nnw3n</link>
      <a10:author>
        <a10:name>bartvanhoey</a10:name>
        <a10:uri>https://abp.io/community/members/bartvanhoey</a10:uri>
      </a10:author>
      <category>excel-export</category>
      <title>How to export Excel files from the ABP framework</title>
      <description>In this article, I will show you a way on how you can export data to an Excel file from an ABP framework with Blazor front end..</description>
      <pubDate>Sun, 06 Dec 2020 08:42:51 Z</pubDate>
      <a10:updated>2026-03-09T21:36:37Z</a10:updated>
      <content:encoded><![CDATA[<h1>How to export Excel files from an ABP Blazor application</h1>
<h2>Introduction</h2>
<p>In this article, I will show you  how can export data to an Excel file from the ABP framework.</p>
<h3>Source Code</h3>
<p>The sample application has been developed with <strong>Blazor</strong> as the UI framework and <strong>SQL Server</strong> as the database provider.</p>
<p>The source code of the completed application is <a href="https://github.com/bartvanhoey/AbpToExcelRepo">available on GitHub</a>.</p>
<h2>Requirements</h2>
<p>The following tools are needed to be able to run the solution.</p>
<ul>
<li>.NET 8.0 SDK</li>
<li>VsCode, Visual Studio 2022 or another compatible IDE</li>
<li>ABP 8.0.0</li>
</ul>
<h2>Development</h2>
<h3>Create a new Application</h3>
<ul>
<li>Install or update the ABP CLI:</li>
</ul>
<pre><code class="language-bash">dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
</code></pre>
<ul>
<li>Use the following ABP CLI command:</li>
</ul>
<pre><code class="language-bash">abp new AbpToExcel -u blazor -o AbpToExcel
</code></pre>
<h3>Open &amp; Run the Application</h3>
<ul>
<li>Open the solution in Visual Studio (or your favorite IDE).</li>
<li>Run the <code>AbpToExcel.DbMigrator</code> application to seed the initial data.</li>
<li>Run the <code>AbpToExcel.HttpApi.Host</code> application that starts the API.</li>
<li>Run the <code>AbpToExcel.Blazor</code> application to start the UI.</li>
</ul>
<h3>Create the ExportToExcelAppService ApplicationService</h3>
<ul>
<li><p>Create a new folder <strong>ExcelExport</strong> in the <strong>Application.Contracts</strong> project.</p>
</li>
<li><p>Add a new <strong>IExportToExcelAppService.cs</strong> file to the <strong>ExcelExport</strong> folder with following content.</p>
</li>
</ul>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace AbpToExcel.ExcelExport
{
    public interface IExportToExcelAppService :  IApplicationService
    {
        Task&lt;byte[]&gt; ExportToExcel();
    }
}

</code></pre>
<ul>
<li>Open a command prompt in the <strong>Application</strong> project and run the following command to install the necessary NuGet packages.</li>
</ul>
<pre><code class="language-bash">    dotnet add package documentformat.openxml
</code></pre>
<ul>
<li><p>Create a new <strong>ExcelExport</strong> folder in the <strong>Application</strong> project.</p>
</li>
<li><p>Add a new file <strong>ExcelFileGenerator.cs</strong> to the <strong>ExcelExport</strong> folder and copy/paste code below.</p>
</li>
</ul>
<pre><code class="language-csharp">using System.IO;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;

namespace AbpToExcel.ExcelExport
{
    public static class ExcelFileGenerator
    {
        public static byte[] GenerateExcelFile()
        {
            var memoryStream = new MemoryStream();

            using var document = SpreadsheetDocument.Create(memoryStream, SpreadsheetDocumentType.Workbook);
            var workbookPart = document.AddWorkbookPart();
            workbookPart.Workbook = new Workbook();

            var worksheetPart = workbookPart.AddNewPart&lt;WorksheetPart&gt;();
            worksheetPart.Worksheet = new Worksheet(new SheetData());

            var sheets = workbookPart.Workbook.AppendChild(new Sheets());

            sheets.AppendChild(new Sheet
            {
                Id = workbookPart.GetIdOfPart(worksheetPart),
                SheetId = 1,
                Name = &quot;Sheet 1&quot;
            });

            var sheetData = worksheetPart.Worksheet.GetFirstChild&lt;SheetData&gt;();

            var row1 = new Row();
            row1.AppendChild(
                new Cell
                {
                    CellValue = new CellValue(&quot;Abp Framework&quot;),
                    DataType = CellValues.String
                }

            );
            
            sheetData?.AppendChild(row1);

            var row2 = new Row();
            row2.AppendChild(
                new Cell
                {
                    CellValue = new CellValue(&quot;Open Source&quot;),
                    DataType = CellValues.String
                }
            );
            sheetData?.AppendChild(row2);

            var row3 = new Row();
            row3.AppendChild(
                new Cell
                {
                    CellValue = new CellValue(&quot;WEB APPLICATION FRAMEWORK&quot;),
                    DataType = CellValues.String
                }
            );
            sheetData?.AppendChild(row3);

            document.Save();

            return memoryStream.ToArray();
        }
    }
}
</code></pre>
<ul>
<li>Add a new <strong>ExportToExcelAppService.cs</strong> file to the <strong>Application</strong> project and copy/paste the code below.</li>
</ul>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using static AbpToExcel.ExcelExport.ExcelFileGenerator;

namespace AbpToExcel.ExcelExport
{
    public class ExportToExcelAppService : ApplicationService, IExportToExcelAppService
    {
        public Task&lt;byte[]&gt; ExportToExcel() =&gt; Task.FromResult(GenerateExcelFile());
    }
}
</code></pre>
<p>Run the <strong>HttpApi.Host</strong> application to see the export-to-excel endpoint in SwaggerUI.</p>
<p><img src="https://raw.githubusercontent.com/bartvanhoey/AbpToExcelRepo/main/exporttoexcel.jpg" alt="swagger-ui" /></p>
<h2>Some JavaScript to download Excel files</h2>
<ul>
<li><p>Create a <strong>js</strong> folder to the <strong>wwwwroot</strong> folder of the <strong>Blazor</strong> project.</p>
</li>
<li><p>Add an <strong>exporttoexcel.js</strong> file to the <strong>js</strong> folder and copy/paste the code below.</p>
</li>
</ul>
<pre><code class="language-javascript">function saveAsFile(filename, bytesBase64) {
    var link = document.createElement('a');
    link.download = filename;
    link.href = 'data:application/octet-stream;base64,' + bytesBase64;
    document.body.appendChild(link); // Needed for Firefox
    link.click();
    document.body.removeChild(link);
}
</code></pre>
<ul>
<li>Open the <strong>index.html</strong> file in the <strong>wwwroot</strong> folder of the <strong>Blazor</strong> project and add this line of code at the end.</li>
</ul>
<pre><code class="language-bash">&lt;script src=&quot;js/exporttoexcel.js&quot;&gt;&lt;/script&gt;
</code></pre>
<h2>Call the IExportToExcelAppService from the Blazor project</h2>
<ul>
<li>Replace the content in the <strong>index.razor</strong> file with the following code.</li>
</ul>
<pre><code class="language-razor">@page &quot;/&quot;
@using AbpToExcel.ExcelExport
@inject IExportToExcelAppService ExportToExcelAppService
@inject IJSRuntime JsRuntime

&lt;Row Class=&quot;d-flex px-0 mx-0 mb-1&quot;&gt;
    &lt;Button Clicked=&quot;@(ExportToExcel)&quot; class=&quot;p-0 ml-auto mr-2&quot; style=&quot;background-color: transparent&quot;
            title=&quot;Download&quot;&gt;
        &lt;span class=&quot;fa fa-file-excel fa-lg m-0&quot; style=&quot;color: #008000; background-color: white;&quot;
              aria-hidden=&quot;true&quot;&gt;
        &lt;/span&gt;
        Click Me!
    &lt;/Button&gt;
&lt;/Row&gt;

@code {
    private async Task ExportToExcel()
    {
        var excelBytes = await ExportToExcelAppService.ExportToExcel();
        await JsRuntime.InvokeVoidAsync(&quot;saveAsFile&quot;, $&quot;test_{DateTime.Now.ToString(&quot;yyyyMMdd_HHmmss&quot;)}.xlsx&quot;,
            Convert.ToBase64String(excelBytes));
    }
}
</code></pre>
<h3>Start both the Blazor and the<strong>HttpApi.Host</strong>project to run the application</h3>
<p>Run both the <strong>HttpApi.Host</strong> and <strong>Blazor</strong> applications and test the export to Excel file function.</p>
<p>Get the <a href="https://github.com/bartvanhoey/AbpToExcelRepo.git">source code</a> on GitHub.</p>
<p>Enjoy and have fun!</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/b90b94f9-5924-be3e-7bf3-39f949649f8f" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/b90b94f9-5924-be3e-7bf3-39f949649f8f" medium="image" />
    </item>
  </channel>
</rss>