<?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>Thu, 05 Nov 2026 23:33:19 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=jfistelmann" />
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/abp-and-maildev-gy13cr1p</guid>
      <link>https://abp.io/community/posts/abp-and-maildev-gy13cr1p</link>
      <a10:author>
        <a10:name>jfistelmann</a10:name>
        <a10:uri>https://abp.io/community/members/jfistelmann</a10:uri>
      </a10:author>
      <title>ABP and maildev</title>
      <description>In this article, we’ll show you how to easily view emails sent by your ABP application with minimal effort.  This can be helpful during the development phase for obvious reasons.
We’ll use maildev for this. However, what we’re demonstrating should work with similar tools as well.</description>
      <pubDate>Tue, 15 Jul 2025 17:32:04 Z</pubDate>
      <a10:updated>2026-04-17T18:49:18Z</a10:updated>
      <content:encoded><![CDATA[In this article, we’ll show you how to easily view emails sent by your ABP application with minimal effort.  This can be helpful during the development phase for obvious reasons.
We’ll use maildev for this. However, what we’re demonstrating should work with similar tools as well.<br \><a href="https://chrobyte.de/blogs/default/abp-and-maildev" 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/3a1b2151-9e04-4ec9-35a1-d3fdc210ac8a" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a1b2151-9e04-4ec9-35a1-d3fdc210ac8a" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/poc-of-using-grapesjs-for-abps-cms-kit-1rmv4q41</guid>
      <link>https://abp.io/community/posts/poc-of-using-grapesjs-for-abps-cms-kit-1rmv4q41</link>
      <a10:author>
        <a10:name>jfistelmann</a10:name>
        <a10:uri>https://abp.io/community/members/jfistelmann</a10:uri>
      </a10:author>
      <category>abp</category>
      <title>PoC of using GrapesJS for ABPs CMS Kit</title>
      <description>A proof of concept on how to get started using GrapesJS as an editor for CMS Kit.</description>
      <pubDate>Wed, 11 Sep 2024 13:50:50 Z</pubDate>
      <a10:updated>2026-04-16T15:29:09Z</a10:updated>
      <content:encoded><![CDATA[A proof of concept on how to get started using GrapesJS as an editor for CMS Kit.<br \><a href="https://chrobyte.de/blogs/default/poc-for-abp-cmskit-using-grapesjs" 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/abp-modules-and-entity-dependencies-hn7wr093</guid>
      <link>https://abp.io/community/posts/abp-modules-and-entity-dependencies-hn7wr093</link>
      <a10:author>
        <a10:name>jfistelmann</a10:name>
        <a10:uri>https://abp.io/community/members/jfistelmann</a10:uri>
      </a10:author>
      <category>abp</category>
      <category>module-development</category>
      <category>entity-synchronizer</category>
      <title>ABP Modules and Entity Dependencies</title>
      <description>This post briefly discusses abp module definition and provides an example of how to handle entity synchronization between modules.</description>
      <pubDate>Sat, 12 Aug 2023 12:25:42 Z</pubDate>
      <a10:updated>2026-04-15T05:18:40Z</a10:updated>
      <content:encoded><![CDATA[This post briefly discusses abp module definition and provides an example of how to handle entity synchronization between modules.<br \><a href="https://chrobyte.de/blogs/default/abp-module-development-and-cross-module-dependencies" 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/using-switch-startup-project-extension-for-visual-studio-52yyw27v</guid>
      <link>https://abp.io/community/posts/using-switch-startup-project-extension-for-visual-studio-52yyw27v</link>
      <a10:author>
        <a10:name>jfistelmann</a10:name>
        <a10:uri>https://abp.io/community/members/jfistelmann</a10:uri>
      </a10:author>
      <category>visual-studio</category>
      <category>extensions</category>
      <title>Using Switch Startup Project Extension for Visual Studio</title>
      <description>In this article, I want to show how I use the Switch Startup Project Extension to make development of an ABP Framework application a little bit faster.</description>
      <pubDate>Sun, 17 Apr 2022 12:20:51 Z</pubDate>
      <a10:updated>2026-11-05T22:10:46Z</a10:updated>
      <content:encoded><![CDATA[<h1>Using Switch Startup Project Extension</h1>
<p>In this article, I want to show how I use the <a href="https://heptapod.host/thirteen/switchstartupproject/-/tree/branch/current">Switch Startup Project Extension</a> to make development of an ABP Framework application a little bit faster.</p>
<blockquote>
<p>I am not the developer of the extension.</p>
</blockquote>
<h2>Problem and assumptions made</h2>
<p>When developing an ABP Framework application, you'll often have to start and debug multiple projects at once.</p>
<p>Let's assume you haven't set up containerization/ orchestration and you have a setup where your stack is somehow tiered (like blazor wasm, where you have to start the WebAssembly Project and the HttpApi.Host at once).</p>
<p>Normally you would do something like this:</p>
<p><img src="https://raw.githubusercontent.com/nebula2/abp-switchstartupproject-extension/master/images/set-startup-projects-1.png" alt="" /></p>
<p><img src="https://raw.githubusercontent.com/nebula2/abp-switchstartupproject-extension/master/images/set-startup-projects-2.png" alt="" /></p>
<p>And every time you need to execute something different, you change your startup projects accordingly.</p>
<h2>How the extension tackles this</h2>
<p>With the extenmsion installed, you can simplify this process.</p>
<p>Configure the extension for your project by clicking this button:</p>
<p><img src="https://raw.githubusercontent.com/nebula2/abp-switchstartupproject-extension/master/images/configure.png" alt="" /></p>
<p>A new window will open with a json file, where you can set everything as you need.</p>
<pre><code class="language-json">/*
    This is a configuration file for the SwitchStartupProject Visual Studio Extension
    See https://heptapod.host/thirteen/switchstartupproject/blob/branch/current/Configuration.md
*/
{
  /*  Configuration File Version  */
  &quot;Version&quot;: 3,
    
  /*  Create an item in the dropdown list for each project in the solution?  */
  &quot;ListAllProjects&quot;: false,

  &quot;MultiProjectConfigurations&quot;: {
    &quot;Web App&quot;: {
      &quot;Projects&quot;: {
        &quot;RackManager.HttpApi.Host&quot;: {},
        &quot;RackManager.Blazor&quot;: {}
      }
    },
    &quot;ERP Connector Console&quot;: {
      &quot;Projects&quot;: {
        &quot;RackManager.HttpApi.Host&quot;: {},
        &quot;RackManager.ERPConnector.ConsoleApp&quot;: {}
      }
    },
    &quot;Android (mobile app only)&quot;: {
      &quot;Projects&quot;: {
        &quot;RackManager.MobileApp.Android&quot;: {}
      }
    },
    &quot;Android&quot;: {
      &quot;Projects&quot;: {
        &quot;RackManager.HttpApi.Host&quot;: {},
        &quot;RackManager.MobileApp.Android&quot;: {}
      }
    },
    &quot;DbMigrator&quot;: {
      &quot;Projects&quot;: {
        &quot;RackManager.DbMigrator&quot;: {}
      }
    }
  }
}

</code></pre>
<p>The process is self explaining. You'll end up with a configuration file which is valid for your current solution. You can add it to your source control to allow every consumer to have the same experience (as long as they have this extension installed).</p>
<p>And that's it. You can now pick what you need from the dropdown without ever having to think about setting things up again.</p>
<p><img src="https://raw.githubusercontent.com/nebula2/abp-switchstartupproject-extension/master/images/result.png" alt="" /></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/generate-pdfs-in-an-abp-framework-project-using-scryber.core-x9yh1vfa</guid>
      <link>https://abp.io/community/posts/generate-pdfs-in-an-abp-framework-project-using-scryber.core-x9yh1vfa</link>
      <a10:author>
        <a10:name>jfistelmann</a10:name>
        <a10:uri>https://abp.io/community/members/jfistelmann</a10:uri>
      </a10:author>
      <category>pdf-generation</category>
      <category>Scryber.Core</category>
      <title>Generate PDF's in an ABP Framework Project using Scryber.Core</title>
      <description>In this article, we'll create a basic application with blazor-server for UI to demonstrate generating and downloading PDF's can be implemented in an ABP project. To archieve this, we will use Scryber.Core.</description>
      <pubDate>Sun, 17 Apr 2022 11:04:03 Z</pubDate>
      <a10:updated>2026-11-05T22:51:33Z</a10:updated>
      <content:encoded><![CDATA[<h1>Generate PDF's in an ABP Framework Project using Scryber.Core</h1>
<p>In this article, we'll create a basic application with blazor-server for UI to demonstrate how generating PDF's can be implemented in an ABP project.
To archieve this, we will use <a href="https://github.com/richard-scryber/scryber.core">Scryber.Core</a>.</p>
<p>For the sake of simplicity I will reduce everything to the bare minimum just so that you get a picture of how everything works together.
PDF's will be generated server side. This post will show how to download the generated PDF file. If you are using a different UI layer, you just need to implement your download a bit different.</p>
<p>The source code for this can be found <a href="https://github.com/nebula2/abp-pdf-with-scryber">here</a>.</p>
<h2>Creating the Solution</h2>
<p>For this article, we will create a simple application with blazor-server for our UI.
Feel free to use something different. I'll showcase a download of the pdf file</p>
<p>We can create a new startup template with EF Core as a database provider and MVC for the UI Framework.</p>
<blockquote>
<p>If you already have a project, you don't need to create a new startup template, you can directly implement the following steps to your project. So you can skip this section.
We can create a new startup template by using the <a href="https://docs.abp.io/en/abp/latest/CLI">ABP CLI</a>.</p>
</blockquote>
<pre><code class="language-bash">abp new BookStorePdf -t app -u blazor-server -d ef
</code></pre>
<p>After running the above command, our project boilerplate will be downloaded. Then we can open the solution and start the development.</p>
<h2>Starting the Development</h2>
<p>First of all we add the initial migration. There are multiple ways doing that. I like to do it like this:</p>
<ol>
<li>Set <code>BookStorePdf.EntityFrameworkCore</code> as startup project</li>
<li>Open Package Manager Console and ensure Default Project is the same
<img src="https://raw.githubusercontent.com/nebula2/abp-pdf-with-scryber/master/docs/images/initial-migration.png" alt="" /></li>
<li>Execute <code>Add-Migration &quot;Initial&quot;</code></li>
<li>Execute <code>BookStorePdf.DbMigrator</code></li>
</ol>
<p>We can now start up <code>BookStorePdf.Blazor</code> and ensure that everything works correctly.
<img src="https://raw.githubusercontent.com/nebula2/abp-pdf-with-scryber/master/docs/images/initial-start.png" alt="" /></p>
<h2>Add Scryber.core</h2>
<p>We are now ready to install Scryber.Core.</p>
<p>Add the <code>Scryber.Core</code> NuGet-Package to <code>BookStorePdf.Application</code>.</p>
<p><code>BookStorePdf.Application.csproj</code> now looks like this:</p>
<pre><code class="language-xml">&lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;

  &lt;Import Project=&quot;..\..\common.props&quot; /&gt;

  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net6.0&lt;/TargetFramework&gt;
    &lt;RootNamespace&gt;BookStorePdf&lt;/RootNamespace&gt;
  &lt;/PropertyGroup&gt;

  &lt;ItemGroup&gt;
    &lt;ProjectReference Include=&quot;..\BookStorePdf.Domain\BookStorePdf.Domain.csproj&quot; /&gt;
    &lt;ProjectReference Include=&quot;..\BookStorePdf.Application.Contracts\BookStorePdf.Application.Contracts.csproj&quot; /&gt;
  &lt;/ItemGroup&gt;

  &lt;ItemGroup&gt;
    &lt;PackageReference Include=&quot;Scryber.Core&quot; Version=&quot;5.0.7&quot; /&gt;
    &lt;PackageReference Include=&quot;Volo.Abp.Account.Application&quot; Version=&quot;5.2.0&quot; /&gt;
    &lt;PackageReference Include=&quot;Volo.Abp.Identity.Application&quot; Version=&quot;5.2.0&quot; /&gt;
    &lt;PackageReference Include=&quot;Volo.Abp.PermissionManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
    &lt;PackageReference Include=&quot;Volo.Abp.TenantManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
    &lt;PackageReference Include=&quot;Volo.Abp.FeatureManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
    &lt;PackageReference Include=&quot;Volo.Abp.SettingManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
  &lt;/ItemGroup&gt;

&lt;/Project&gt;
</code></pre>
<h2>Managing PDF templates</h2>
<p>To generate PDF's with scryber, we need to have templates.
For this tutorial we will use html files which we ship along with the application.
You can however write a complete ecosystem around this to be able to have the templates changeable by your users.</p>
<p>Let's implement everything we need to store and serve our html file.</p>
<p>Create <code>IResourceAppService.cs</code> in the <code>BookStorePdf.Application.Contracts</code> project with the following content:</p>
<pre><code class="language-csharp">using Microsoft.Extensions.FileProviders;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;

namespace BookStorePdf;

public interface IResourceAppService : IApplicationService, ITransientDependency
{
    IFileInfo GetFileInfo(string path);
}
</code></pre>
<p>After that you create <code>ResourceAppService</code> in the <code>BookStorePdf.Application</code> project with this content:</p>
<pre><code class="language-csharp">using Microsoft.Extensions.FileProviders;
using Volo.Abp.VirtualFileSystem;

namespace BookStorePdf;

public class ResourceAppService : BookStorePdfAppService, IResourceAppService
{
    private readonly IVirtualFileProvider _virtualFileProvider;

    public ResourceAppService(IVirtualFileProvider virtualFileProvider)
    {
        _virtualFileProvider = virtualFileProvider;
    }

    public IFileInfo GetFileInfo(string path)
    {
        return _virtualFileProvider.GetFileInfo(path);
    }
}

</code></pre>
<p>Change <code>BookStorePdfApplicationModule.cs</code> like this:</p>
<pre><code class="language-csharp">using Volo.Abp.Account;
using Volo.Abp.AutoMapper;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Identity;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SettingManagement;
using Volo.Abp.TenantManagement;
using Volo.Abp.VirtualFileSystem;

namespace BookStorePdf;

[DependsOn(
    typeof(BookStorePdfDomainModule),
    typeof(AbpAccountApplicationModule),
    typeof(BookStorePdfApplicationContractsModule),
    typeof(AbpIdentityApplicationModule),
    typeof(AbpPermissionManagementApplicationModule),
    typeof(AbpTenantManagementApplicationModule),
    typeof(AbpFeatureManagementApplicationModule),
    typeof(AbpSettingManagementApplicationModule)
    )]
public class BookStorePdfApplicationModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure&lt;AbpAutoMapperOptions&gt;(options =&gt;
        {
            options.AddMaps&lt;BookStorePdfApplicationModule&gt;();
        });

        // add this here
        Configure&lt;AbpVirtualFileSystemOptions&gt;(options =&gt;
        {
            options.FileSets.AddEmbedded&lt;BookStorePdfApplicationModule&gt;();
        });
    }
}

</code></pre>
<p>Change <code>BookStorePdf.Application.csproj</code>:</p>
<pre><code class="language-xml">&lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;

  &lt;Import Project=&quot;..\..\common.props&quot; /&gt;

  &lt;PropertyGroup&gt;
      &lt;TargetFramework&gt;net6.0&lt;/TargetFramework&gt;
      &lt;RootNamespace&gt;BookStorePdf&lt;/RootNamespace&gt;
      
	  &lt;!-- add this line --&gt;
	  &lt;GenerateEmbeddedFilesManifest&gt;true&lt;/GenerateEmbeddedFilesManifest&gt;
  &lt;/PropertyGroup&gt;

  &lt;ItemGroup&gt;
    &lt;ProjectReference Include=&quot;..\BookStorePdf.Domain\BookStorePdf.Domain.csproj&quot; /&gt;
    &lt;ProjectReference Include=&quot;..\BookStorePdf.Application.Contracts\BookStorePdf.Application.Contracts.csproj&quot; /&gt;
  &lt;/ItemGroup&gt;

  &lt;ItemGroup&gt;
	  &lt;!-- add this line --&gt;
	  &lt;PackageReference Include=&quot;Microsoft.Extensions.FileProviders.Embedded&quot; Version=&quot;6.0.3&quot; /&gt;
      
	  &lt;PackageReference Include=&quot;Scryber.Core&quot; Version=&quot;5.0.7&quot; /&gt;
      &lt;PackageReference Include=&quot;Volo.Abp.Account.Application&quot; Version=&quot;5.2.0&quot; /&gt;
      &lt;PackageReference Include=&quot;Volo.Abp.Identity.Application&quot; Version=&quot;5.2.0&quot; /&gt;
      &lt;PackageReference Include=&quot;Volo.Abp.PermissionManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
      &lt;PackageReference Include=&quot;Volo.Abp.TenantManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
      &lt;PackageReference Include=&quot;Volo.Abp.FeatureManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
      &lt;PackageReference Include=&quot;Volo.Abp.SettingManagement.Application&quot; Version=&quot;5.2.0&quot; /&gt;
  &lt;/ItemGroup&gt;

	&lt;!-- add this --&gt;
  &lt;ItemGroup&gt;
  	&lt;EmbeddedResource Include=&quot;MyResources\**\*.*&quot; /&gt;
  &lt;/ItemGroup&gt;

	&lt;!-- and this --&gt;
	&lt;Choose&gt;
		&lt;When Condition=&quot;'$(Configuration)'=='Debug'&quot;&gt;
			&lt;ItemGroup&gt;
				&lt;Content Include=&quot;MyResources\**\*.*&quot;&gt;
					&lt;CopyToOutputDirectory&gt;Always&lt;/CopyToOutputDirectory&gt;
				&lt;/Content&gt;
			&lt;/ItemGroup&gt;
		&lt;/When&gt;
		&lt;When Condition=&quot;'$(Configuration)'!='Debug'&quot;&gt;
			&lt;ItemGroup&gt;
				&lt;Content Remove=&quot;MyResources\**\*.*&quot; /&gt;
			&lt;/ItemGroup&gt;
		&lt;/When&gt;
	&lt;/Choose&gt;
	
&lt;/Project&gt;

</code></pre>
<p>This is needed to setup our file provider. We store our templates in the <code>MyResources</code> directory. If we debug, we want the templates to be copied to the output directory so that we can change them on runtime and see changes.</p>
<p>The debugging-part is possible because we have the following code in <code>BookStorePdfBlazorModule.cs</code>:</p>
<pre><code class="language-csharp">private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
    {
        if (hostingEnvironment.IsDevelopment())
        {
            Configure&lt;AbpVirtualFileSystemOptions&gt;(options =&gt;
            {
                options.FileSets.ReplaceEmbeddedByPhysical&lt;BookStorePdfDomainSharedModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, $&quot;..{Path.DirectorySeparatorChar}BookStorePdf.Domain.Shared&quot;));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;BookStorePdfDomainModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, $&quot;..{Path.DirectorySeparatorChar}BookStorePdf.Domain&quot;));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;BookStorePdfApplicationContractsModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, $&quot;..{Path.DirectorySeparatorChar}BookStorePdf.Application.Contracts&quot;));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;BookStorePdfApplicationModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, $&quot;..{Path.DirectorySeparatorChar}BookStorePdf.Application&quot;));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;BookStorePdfBlazorModule&gt;(hostingEnvironment.ContentRootPath);
            });
        }
    }
</code></pre>
<p>When we deploy the application, we want to have everything stored as an embedded resource.</p>
<p>Now we create a folder <code>MyResources</code> and create a template file called <code>Test.html</code> there for our PDF's.</p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html lang='en' xmlns='http://www.w3.org/1999/xhtml'&gt;
&lt;head&gt;
    &lt;title&gt;Hello World&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div style='padding:10px'&gt;Hello World from scryber.&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><img src="https://raw.githubusercontent.com/nebula2/abp-pdf-with-scryber/master/docs/images/testhtml.png" alt="" /></p>
<h2>Create the printing service</h2>
<p>Now we start to implement a service which we can consume to generate a pdf.
We will start with something simple.</p>
<p>Create <code>IPrintingAppService.cs</code> in the <code>BookStorePdf.Application.Contracts</code> project with the following content:</p>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;

namespace BookStorePdf;

public interface IPrintingAppService : IApplicationService, ITransientDependency
{
    Task&lt;byte[]&gt; PrintAsync();
}

</code></pre>
<p>After that you create <code>PrintingAppService</code> in the <code>BookStorePdf.Application</code> project with this content:</p>
<pre><code class="language-csharp">using Scryber.Components;
using System;
using System.IO;
using System.Threading.Tasks;

namespace BookStorePdf;

public class PrintingAppService : BookStorePdfAppService, IPrintingAppService
{
    private readonly IResourceAppService _resourceAppService;

    public PrintingAppService(IResourceAppService resourceAppService)
    {
        _resourceAppService = resourceAppService;
    }

    public async Task&lt;byte[]&gt; PrintAsync()
    {
        // get desired template from resource service into a stream
        var info = _resourceAppService.GetFileInfo(&quot;/MyResources/Test.html&quot;);
        using var stream = info.CreateReadStream();

        // use scryber to parse the stream into a document
        using var doc = Document.ParseDocument(stream, Scryber.ParseSourceType.Resource);
        
        // create some file to write the pdf to.
        // in a real world scenario you would remove the file after you're done
        var tmpDir = System.IO.Path.GetTempPath();
        var name = Guid.NewGuid().ToString() + &quot;.pdf&quot;;
        var targetFilename = System.IO.Path.Combine(tmpDir, name);

        // let scryber write the pdf file
        doc.SaveAsPDF(targetFilename, FileMode.OpenOrCreate);
        
        // read created pdf file into bytes and return to consumer
        return await File.ReadAllBytesAsync(targetFilename);
    }
}

</code></pre>
<h2>Download PDF</h2>
<p>The last step is getting our UI ready to consume the IPrintingAppService and download our pdf.</p>
<p>Add this to your Blazor project:</p>
<p><img src="https://raw.githubusercontent.com/nebula2/abp-pdf-with-scryber/master/docs/images/myscripts.png" alt="" /></p>
<pre><code class="language-js">export async function downloadFileFromStream(fileName, contentStreamReference) {
    const arrayBuffer = await contentStreamReference.arrayBuffer();
    const blob = new Blob([arrayBuffer]);
    const url = URL.createObjectURL(blob);

    triggerFileDownload(fileName, url);

    URL.revokeObjectURL(url);
}

function triggerFileDownload(fileName, url) {
    const anchorElement = document.createElement('a');
    anchorElement.href = url;
    anchorElement.download = fileName ?? '';
    anchorElement.click();
    anchorElement.remove();
}
</code></pre>
<p>Change <code>Index.razor</code> to this:</p>
<pre><code>@page &quot;/&quot;

@inherits BookStorePdfComponentBase

&lt;Button Color=&quot;Color.Primary&quot; Clicked=&quot;PrintTestAsync&quot;&gt;Test&lt;/Button&gt;
</code></pre>
<p>Change <code>Index.razor.cs</code> to this:</p>
<pre><code class="language-csharp">using Microsoft.JSInterop;
using System.IO;
using System.Threading.Tasks;

namespace BookStorePdf.Blazor.Pages;

public partial class Index
{
    private readonly IJSRuntime _jsRuntime;
    private readonly IPrintingAppService _printingAppService;
    private IJSObjectReference _jsModule;

    public Index(IJSRuntime jsRuntime, IPrintingAppService printingAppService)
    {
        _jsRuntime = jsRuntime;
        _printingAppService = printingAppService;
    }

    protected override async Task OnInitializedAsync()
    {
        _jsModule = await _jsRuntime.InvokeAsync&lt;IJSObjectReference&gt;(&quot;import&quot;, &quot;./scripts/myscripts.js&quot;);
    }

    private async Task PrintTestAsync()
    {
        var result = await _printingAppService.PrintAsync();

        var fileName = &quot;Test.pdf&quot;;

        using var fileStream = new MemoryStream(result);
        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await _jsModule.InvokeVoidAsync(&quot;downloadFileFromStream&quot;, fileName, streamRef);
    }
}

</code></pre>
<h2>Result</h2>
<p><img src="https://raw.githubusercontent.com/nebula2/abp-pdf-with-scryber/master/docs/images/BookStorePdf-Blazor.gif" alt="" /></p>
<h2>Further Reading</h2>
<p>This tutorial scratched the surface of generating a PDF.
Let me know if you're interested in another part where I show how to pass objects which then get printed.</p>
<ul>
<li><a href="https://docs.abp.io/en/abp/latest/Virtual-File-System">Virtual File System</a></li>
<li><a href="https://scrybercore.readthedocs.io/en/latest/">Scryber.Core Documentation</a></li>
</ul>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/d2c921df-0fa6-67ed-373b-3a03495e83b8" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/d2c921df-0fa6-67ed-373b-3a03495e83b8" medium="image" />
    </item>
  </channel>
</rss>