Create PDF Portfolios in C# and VB.NET

PDF documents may specify how a PDF viewer's user interface presents collections of file attachments. Such a presentation is called a PDF portfolio.

A PDF portfolio specifies the viewing (view type, metadata columns, and sort settings) and organizational (files and folders) characteristics of a file attachment collection.

You can represent a PDF portfolio in GemBox.Pdf with the PdfPortfolio class. It can be accessed via the Portfolio property and set via the SetPortfolio() method of the PdfDocument class.

GemBox.PDF supports creating PDF portfolios, organized in folders and custom metadata fields, from either a file system or Streams, as shown in the following examples.

PDF portfolio created with GemBox.Pdf
Screenshot of PDF portfolio created with GemBox.Pdf

PDF portfolio from a file system

Creating a PDF portfolio from a file system is convenient and easy.

This example shows how to create a PDF portfolio, organized in folders and custom metadata fields, from file system paths of an extracted zip archive.

Upload your file (Drag file here)
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using GemBox.Pdf;
using GemBox.Pdf.Objects;
using GemBox.Pdf.Portfolios;

class Program
{
    static void Main()
    {
        // If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        using (var document = PdfDocument.Load("%InputFileName%"))
        {
            // Make the document a PDF portfolio (a collection of file attachments).
            var portfolio = document.SetPortfolio();

            // Extract all the files in the zip archive to a directory on the file system.
            ZipFile.ExtractToDirectory("%#Attachments.zip%", "Attachments");

            // Add files contained directly in the 'Attachments' directory to the portfolio files.
            foreach (var filePath in Directory.GetFiles("Attachments", "*", SearchOption.TopDirectoryOnly))
                portfolio.Files.Add(filePath);

            // Recursively add directories and their files contained in the 'Attachments' directory to the portfolio folders.
            foreach (var folderPath in Directory.GetDirectories("Attachments", "*", SearchOption.TopDirectoryOnly))
                portfolio.Folders.Add(folderPath, recursive: true);

            // Delete the directory where zip archive files were extracted to.
            Directory.Delete("Attachments", recursive: true);

            // Set the first PDF file contained in the portfolio to be initially presented in the user interface.
            // Note that all files contained in the portfolio are also contained in the PdfDocument.EmbeddedFiles.
            portfolio.InitialFile = document.EmbeddedFiles.Select(entry => entry.Value).FirstOrDefault(fileSpec => fileSpec.Name.EndsWith(".pdf", StringComparison.Ordinal));

            // Hide all existing portfolio fields except 'Size'.
            foreach (var portfolioFieldEntry in portfolio.Fields)
                portfolioFieldEntry.Value.Hidden = portfolioFieldEntry.Value.Name != "Size";

            // Add a new portfolio field with display name 'Full Name' and it should be in the first column.
            var portfolioFieldKeyAndValue = portfolio.Fields.Add(PdfPortfolioFieldDataType.String, "FullName");
            var portfolioField = portfolioFieldKeyAndValue.Value;
            portfolioField.Name = "Full Name";
            portfolioField.Order = 0;

            // For each file and folder in the portfolio, set FullName field value to the relative path of the file/folder in the zip archive.
            SetFullNameFieldValue(portfolio.Files, portfolio.Folders, string.Empty, portfolioFieldKeyAndValue.Key);

            document.Save("Portfolio from file system.pdf");
        }
    }

    static void SetFullNameFieldValue(PdfPortfolioFileCollection files, PdfPortfolioFolderCollection folders, string parentFolderFullName, PdfName portfolioFieldKey)
    {
        // Set FullName field value for all the fields.
        foreach (var fileSpecification in files)
            fileSpecification.PortfolioFieldValues.Add(portfolioFieldKey, new PdfPortfolioFieldValue(parentFolderFullName + fileSpecification.Name));

        foreach (var folder in folders)
        {
            // Set FullName field value for the folder.
            var folderFullName = parentFolderFullName + folder.Name + '/';
            folder.PortfolioFieldValues.Add(portfolioFieldKey, new PdfPortfolioFieldValue(folderFullName));

            // Recursively set FullName field value for all files and folders underneath the current portfolio folder.
            SetFullNameFieldValue(folder.Files, folder.Folders, folderFullName, portfolioFieldKey);
        }
    }
}
Imports System
Imports System.IO
Imports System.IO.Compression
Imports System.Linq
Imports GemBox.Pdf
Imports GemBox.Pdf.Objects
Imports GemBox.Pdf.Portfolios

Module Program

    Sub Main()

        ' If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

        Using document = PdfDocument.Load("%InputFileName%")

            ' Make the document a PDF portfolio (a collection of file attachments).
            Dim portfolio = document.SetPortfolio()

            ' Extract all the files in the zip archive to a directory on the file system.
            ZipFile.ExtractToDirectory("%#Attachments.zip%", "Attachments")

            ' Add files contained directly in the 'Attachments' directory to the portfolio files.
            For Each filePath In Directory.GetFiles("Attachments", "*", SearchOption.TopDirectoryOnly)
                portfolio.Files.Add(filePath)
            Next

            ' Recursively add directories and their files contained in the 'Attachments' directory to the portfolio folders.
            For Each folderPath In Directory.GetDirectories("Attachments", "*", SearchOption.TopDirectoryOnly)
                portfolio.Folders.Add(folderPath, recursive:=True)
            Next

            ' Delete the directory where zip archive files were extracted to.
            Directory.Delete("Attachments", recursive:=True)

            ' Set the first PDF file contained in the portfolio to be initially presented in the user interface.
            ' Note that all files contained in the portfolio are also contained in the PdfDocument.EmbeddedFiles.
            portfolio.InitialFile = document.EmbeddedFiles.Select(Function(entry) entry.Value).FirstOrDefault(Function(fileSpec) fileSpec.Name.EndsWith(".pdf", StringComparison.Ordinal))

            ' Hide all existing portfolio fields except 'Size'.
            For Each portfolioFieldEntry In portfolio.Fields
                portfolioFieldEntry.Value.Hidden = portfolioFieldEntry.Value.Name <> "Size"
            Next

            ' Add a new portfolio field with display name 'Full Name' and it should be in the first column.
            Dim portfolioFieldKeyAndValue = portfolio.Fields.Add(PdfPortfolioFieldDataType.String, "FullName")
            Dim portfolioField = portfolioFieldKeyAndValue.Value
            portfolioField.Name = "Full Name"
            portfolioField.Order = 0

            ' For each file and folder in the portfolio, set FullName field value to the relative path of the file/folder in the zip archive.
            SetFullNameFieldValue(portfolio.Files, portfolio.Folders, String.Empty, portfolioFieldKeyAndValue.Key)

            document.Save("Portfolio from file system.pdf")
        End Using
    End Sub

    Sub SetFullNameFieldValue(ByVal files As PdfPortfolioFileCollection, ByVal folders As PdfPortfolioFolderCollection, ByVal parentFolderFullName As String, ByVal portfolioFieldKey As PdfName)

        ' Set FullName field value for all the fields.
        For Each fileSpecification In files
            fileSpecification.PortfolioFieldValues.Add(portfolioFieldKey, New PdfPortfolioFieldValue(parentFolderFullName & fileSpecification.Name))
        Next

        For Each folder In folders

            ' Set FullName field value for the folder.
            Dim folderFullName = parentFolderFullName & folder.Name + "/"c
            folder.PortfolioFieldValues.Add(portfolioFieldKey, New PdfPortfolioFieldValue(folderFullName))

            ' Recursively set FullName field value for all files and folders underneath the current portfolio folder.
            SetFullNameFieldValue(folder.Files, folder.Folders, folderFullName, portfolioFieldKey)
        Next
    End Sub
End Module

PDF Portfolio from Streams

The following example shows how to create a PDF portfolio, organized in folders and custom metadata fields, from Streams of zip archive entries. This is usually needed when your application doesn't have access to a file system or uses some other source of portfolio files, such as dynamically created streams or streams embedded in an assembly.

Upload your file (Drag file here)
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using GemBox.Pdf;
using GemBox.Pdf.Objects;
using GemBox.Pdf.Portfolios;

class Program
{
    static void Main()
    {
        // If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        using (var document = PdfDocument.Load("%InputFileName%"))
        {
            // Make the document a PDF portfolio (a collection of file attachments).
            var portfolio = document.SetPortfolio();

            // Add all files and folders from the zip archive to the portfolio.
            using (var archiveStream = File.OpenRead("%#Attachments.zip%"))
            using (var archive = new ZipArchive(archiveStream, ZipArchiveMode.Read, leaveOpen: true))
                foreach (var entry in archive.Entries)
                {
                    // Get or create portfolio folder hierarchy from the zip entry full name.
                    var folder = GetOrAddFolder(portfolio, entry.FullName);

                    if (!string.IsNullOrEmpty(entry.Name))
                    {
                        // Zip archive entry is a file.
                        var files = folder == null ? portfolio.Files : folder.Files;

                        var embeddedFile = files.AddEmpty(entry.Name).EmbeddedFile;

                        // Set the portfolio file size and modification date.
                        if (entry.Length < int.MaxValue)
                            embeddedFile.Size = (int)entry.Length;
                        embeddedFile.ModificationDate = entry.LastWriteTime;

                        // Copy portfolio file contents from the zip archive entry.
                        // Portfolio file is compressed if its compressed size in the zip archive is less than its uncompressed size.
                        using (var entryStream = entry.Open())
                        using (var embeddedFileStream = embeddedFile.OpenWrite(compress: entry.CompressedLength < entry.Length))
                            entryStream.CopyTo(embeddedFileStream);
                    }
                    else
                        // Zip archive entry is a folder.
                        // Set the portfolio folder modification date.
                        folder.ModificationDate = entry.LastWriteTime;
                }

            // Set the first PDF file contained in the portfolio to be initially presented in the user interface.
            // Note that all files contained in the portfolio are also contained in the PdfDocument.EmbeddedFiles.
            portfolio.InitialFile = document.EmbeddedFiles.Select(entry => entry.Value).FirstOrDefault(fileSpec => fileSpec.Name.EndsWith(".pdf", StringComparison.Ordinal));

            // Hide all existing portfolio fields except 'Size'.
            foreach (var portfolioFieldEntry in portfolio.Fields)
                portfolioFieldEntry.Value.Hidden = portfolioFieldEntry.Value.Name != "Size";

            // Add a new portfolio field with display name 'Full Name' and it should be in the first column.
            var portfolioFieldKeyAndValue = portfolio.Fields.Add(PdfPortfolioFieldDataType.String, "FullName");
            var portfolioField = portfolioFieldKeyAndValue.Value;
            portfolioField.Name = "Full Name";
            portfolioField.Order = 0;

            // For each file and folder in the portfolio, set FullName field value to the relative path of the file/folder in the zip archive.
            SetFullNameFieldValue(portfolio.Files, portfolio.Folders, string.Empty, portfolioFieldKeyAndValue.Key);

            document.Save("Portfolio from Streams.pdf");
        }
    }

    static PdfPortfolioFolder GetOrAddFolder(PdfPortfolio portfolio, string fullName)
    {
        var folderNames = fullName.Split('/');

        PdfPortfolioFolder folder = null;
        var folders = portfolio.Folders;

        // Last name is the name of the file, so it is skipped.
        for (int i = 0; i < folderNames.Length - 1; ++i)
        {
            // Get or add folder with the specific name.
            var folderName = folderNames[i];
            folder = folders.FirstOrDefault(f => f.Name == folderName);
            if (folder == null)
                folder = folders.AddEmpty(folderName);

            folders = folder.Folders;
        }

        return folder;
    }

    static void SetFullNameFieldValue(PdfPortfolioFileCollection files, PdfPortfolioFolderCollection folders, string parentFolderFullName, PdfName portfolioFieldKey)
    {
        // Set FullName field value for all the fields.
        foreach (var fileSpecification in files)
            fileSpecification.PortfolioFieldValues.Add(portfolioFieldKey, new PdfPortfolioFieldValue(parentFolderFullName + fileSpecification.Name));

        foreach (var folder in folders)
        {
            // Set FullName field value for the folder.
            var folderFullName = parentFolderFullName + folder.Name + '/';
            folder.PortfolioFieldValues.Add(portfolioFieldKey, new PdfPortfolioFieldValue(folderFullName));

            // Recursively set FullName field value for all files and folders underneath the current portfolio folder.
            SetFullNameFieldValue(folder.Files, folder.Folders, folderFullName, portfolioFieldKey);
        }
    }
}
Imports System
Imports System.IO
Imports System.IO.Compression
Imports System.Linq
Imports GemBox.Pdf
Imports GemBox.Pdf.Objects
Imports GemBox.Pdf.Portfolios

Module Program

    Sub Main()

        ' If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

        Using document = PdfDocument.Load("%InputFileName%")

            ' Make the document a PDF portfolio (a collection of file attachments).
            Dim portfolio = document.SetPortfolio()

            ' Add all files and folders from the zip archive to the portfolio.
            Using archiveStream = File.OpenRead("%#Attachments.zip%")
                Using archive = New ZipArchive(archiveStream, ZipArchiveMode.Read, leaveOpen:=True)
                    For Each entry In archive.Entries

                        ' Get or create portfolio folder hierarchy from the zip entry full name.
                        Dim folder = GetOrAddFolder(portfolio, entry.FullName)

                        If Not String.IsNullOrEmpty(entry.Name) Then

                            ' Zip archive entry is a file.
                            Dim files = If(folder Is Nothing, portfolio.Files, folder.Files)

                            Dim embeddedFile = files.AddEmpty(entry.Name).EmbeddedFile

                            ' Set the portfolio file size and modification date.
                            If entry.Length < Integer.MaxValue Then embeddedFile.Size = CInt(entry.Length)
                            embeddedFile.ModificationDate = entry.LastWriteTime

                            ' Copy portfolio file contents from the zip archive entry.
                            ' Portfolio file is compressed if its compressed size in the zip archive is less than its uncompressed size.
                            Using entryStream = entry.Open()
                                Using embeddedFileStream = embeddedFile.OpenWrite(compress:=entry.CompressedLength < entry.Length)
                                    entryStream.CopyTo(embeddedFileStream)
                                End Using
                            End Using
                        Else
                            ' Zip archive entry is a folder.
                            ' Set the portfolio folder modification date.
                            folder.ModificationDate = entry.LastWriteTime
                        End If
                    Next
                End Using
            End Using

            ' Set the first PDF file contained in the portfolio to be initially presented in the user interface.
            ' Note that all files contained in the portfolio are also contained in the PdfDocument.EmbeddedFiles.
            portfolio.InitialFile = document.EmbeddedFiles.Select(Function(entry) entry.Value).FirstOrDefault(Function(fileSpec) fileSpec.Name.EndsWith(".pdf", StringComparison.Ordinal))

            ' Hide all existing portfolio fields except 'Size'.
            For Each portfolioFieldEntry In portfolio.Fields
                portfolioFieldEntry.Value.Hidden = portfolioFieldEntry.Value.Name <> "Size"
            Next

            ' Add a new portfolio field with display name 'Full Name' and it should be in the first column.
            Dim portfolioFieldKeyAndValue = portfolio.Fields.Add(PdfPortfolioFieldDataType.String, "FullName")
            Dim portfolioField = portfolioFieldKeyAndValue.Value
            portfolioField.Name = "Full Name"
            portfolioField.Order = 0

            ' For each file and folder in the portfolio, set FullName field value to the relative path of the file/folder in the zip archive.
            SetFullNameFieldValue(portfolio.Files, portfolio.Folders, String.Empty, portfolioFieldKeyAndValue.Key)

            document.Save("Portfolio from Streams.pdf")
        End Using
    End Sub

    Function GetOrAddFolder(ByVal portfolio As PdfPortfolio, ByVal fullName As String) As PdfPortfolioFolder

        Dim folderNames = fullName.Split("/"c)

        Dim folder As PdfPortfolioFolder = Nothing
        Dim folders = portfolio.Folders

        ' Last name is the name of the file, so it is skipped.
        For i As Integer = 0 To folderNames.Length - 1 - 1

            ' Get or add folder with the specific name.
            Dim folderName = folderNames(i)
            folder = folders.FirstOrDefault(Function(f) f.Name = folderName)
            If folder Is Nothing Then folder = folders.AddEmpty(folderName)

            folders = folder.Folders
        Next

        Return folder
    End Function

    Sub SetFullNameFieldValue(ByVal files As PdfPortfolioFileCollection, ByVal folders As PdfPortfolioFolderCollection, ByVal parentFolderFullName As String, ByVal portfolioFieldKey As PdfName)

        ' Set FullName field value for all the fields.
        For Each fileSpecification In files
            fileSpecification.PortfolioFieldValues.Add(portfolioFieldKey, New PdfPortfolioFieldValue(parentFolderFullName & fileSpecification.Name))
        Next

        For Each folder In folders

            ' Set FullName field value for the folder.
            Dim folderFullName = parentFolderFullName & folder.Name + "/"c
            folder.PortfolioFieldValues.Add(portfolioFieldKey, New PdfPortfolioFieldValue(folderFullName))

            ' Recursively set FullName field value for all files and folders underneath the current portfolio folder.
            SetFullNameFieldValue(folder.Files, folder.Folders, folderFullName, portfolioFieldKey)
        Next
    End Sub
End Module

Extract PDF portfolio files

The example below shows how to extract PDF portfolio files and folders from an existing PDF portfolio to a zip archive.

Upload your file (Drag file here)
using System;
using System.IO;
using System.IO.Compression;
using GemBox.Pdf;
using GemBox.Pdf.Objects;
using GemBox.Pdf.Portfolios;

class Program
{
    static void Main()
    {
        // If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        // Add to zip archive all files and folders from a PDF portfolio.
        using (var document = PdfDocument.Load("%InputFileName%"))
        using (var archiveStream = File.Create("Portfolio Files and Folders.zip"))
        using (var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, leaveOpen: true))
        {
            var portfolio = document.Portfolio;
            if (portfolio != null)
                ExtractFilesAndFoldersToArchive(portfolio.Files, portfolio.Folders, archive, string.Empty, PdfName.Create("FullName"));
        }
    }

    static void ExtractFilesAndFoldersToArchive(PdfPortfolioFileCollection files, PdfPortfolioFolderCollection folders, ZipArchive archive, string parentFolderFullName, PdfName portfolioFieldKey)
    {
        foreach (var fileSpecification in files)
        {
            // Use the FullName field value or the resolved full name as the relative path of the entry in the zip archive.
            string entryFullName;
            if (fileSpecification.PortfolioFieldValues.TryGet(portfolioFieldKey, out PdfPortfolioFieldValue fullNameValue))
                entryFullName = fullNameValue.ToString();
            else
                entryFullName = parentFolderFullName + fileSpecification.Name;

            var embeddedFile = fileSpecification.EmbeddedFile;

            // Create zip archive entry.
            // Zip archive entry is compressed if the portfolio embedded file's compressed size is less than its uncompressed size.
            bool compress = embeddedFile.Size == null || embeddedFile.CompressedSize < embeddedFile.Size.GetValueOrDefault();
            var entry = archive.CreateEntry(entryFullName, compress ? CompressionLevel.Optimal : CompressionLevel.NoCompression);

            // Set the modification date, if it is specified in the portfolio embedded file.
            var modificationDate = embeddedFile.ModificationDate;
            if (modificationDate != null)
                entry.LastWriteTime = modificationDate.GetValueOrDefault();

            // Copy embedded file contents to the zip archive entry.
            using (var embeddedFileStream = embeddedFile.OpenRead())
            using (var entryStream = entry.Open())
                embeddedFileStream.CopyTo(entryStream);
        }

        foreach (var folder in folders)
        {
            // Use the FullName field value or the resolved full name as the relative path of the entry in the zip archive.
            string folderFullName;
            if (folder.PortfolioFieldValues.TryGet(portfolioFieldKey, out PdfPortfolioFieldValue fullNameValue))
                folderFullName = fullNameValue.ToString();
            else
                folderFullName = parentFolderFullName + folder.Name + '/';

            // Set the modification date, if it is specified in the portfolio folder.
            var modificationDate = folder.ModificationDate;
            if (modificationDate.HasValue)
                archive.CreateEntry(folderFullName).LastWriteTime = modificationDate.GetValueOrDefault();

            // Recursively add to zip archive all files and folders underneath the current portfolio folder.
            ExtractFilesAndFoldersToArchive(folder.Files, folder.Folders, archive, folderFullName, portfolioFieldKey);
        }
    }
}
Imports System
Imports System.IO
Imports System.IO.Compression
Imports GemBox.Pdf
Imports GemBox.Pdf.Objects
Imports GemBox.Pdf.Portfolios

Module Program

    Sub Main()

        ' If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

        ' Add to zip archive all files and folders from a PDF portfolio.
        Using document = PdfDocument.Load("%InputFileName%")
            Using archiveStream = File.Create("Portfolio Files and Folders.zip")
                Using archive = New ZipArchive(archiveStream, ZipArchiveMode.Create, leaveOpen:=True)

                    Dim portfolio = document.Portfolio
                    If portfolio IsNot Nothing Then ExtractFilesAndFoldersToArchive(portfolio.Files, portfolio.Folders, archive, String.Empty, PdfName.Create("FullName"))
                End Using
            End Using
        End Using
    End Sub

    Sub ExtractFilesAndFoldersToArchive(ByVal files As PdfPortfolioFileCollection, ByVal folders As PdfPortfolioFolderCollection, ByVal archive As ZipArchive, ByVal parentFolderFullName As String, ByVal portfolioFieldKey As PdfName)

        For Each fileSpecification In files

            ' Use the FullName field value or the resolved full name as the relative path of the entry in the zip archive.
            Dim entryFullName As String
            Dim fullNameValue As PdfPortfolioFieldValue
            If fileSpecification.PortfolioFieldValues.TryGet(portfolioFieldKey, fullNameValue) Then
                entryFullName = fullNameValue.ToString()
            Else
                entryFullName = parentFolderFullName & fileSpecification.Name
            End If

            Dim embeddedFile = fileSpecification.EmbeddedFile

            ' Create zip archive entry.
            ' Zip archive entry is compressed if the portfolio embedded file's compressed size is less than its uncompressed size.
            Dim compress As Boolean = embeddedFile.Size Is Nothing OrElse embeddedFile.CompressedSize < embeddedFile.Size.GetValueOrDefault()
            Dim entry = archive.CreateEntry(entryFullName, If(compress, CompressionLevel.Optimal, CompressionLevel.NoCompression))

            ' Set the modification date, if it is specified in the portfolio embedded file.
            Dim modificationDate = embeddedFile.ModificationDate
            If modificationDate IsNot Nothing Then entry.LastWriteTime = modificationDate.GetValueOrDefault()

            ' Copy embedded file contents to the zip archive entry.
            Using embeddedFileStream = embeddedFile.OpenRead()
                Using entryStream = entry.Open()
                    embeddedFileStream.CopyTo(entryStream)
                End Using
            End Using
        Next

        For Each folder In folders

            ' Use the FullName field value or the resolved full name as the relative path of the entry in the zip archive.
            Dim folderFullName As String
            Dim fullNameValue As PdfPortfolioFieldValue
            If folder.PortfolioFieldValues.TryGet(portfolioFieldKey, fullNameValue) Then
                folderFullName = fullNameValue.ToString()
            Else
                folderFullName = parentFolderFullName & folder.Name + "/"c
            End If

            ' Set the modification date, if it is specified in the portfolio folder.
            Dim modificationDate = folder.ModificationDate
            If modificationDate.HasValue Then archive.CreateEntry(folderFullName).LastWriteTime = modificationDate.GetValueOrDefault()

            ' Recursively add to zip archive all files and folders underneath the current portfolio folder.
            ExtractFilesAndFoldersToArchive(folder.Files, folder.Folders, archive, folderFullName, portfolioFieldKey)
        Next
    End Sub
End Module

Want more?

Next example GitHub

Check the next example or select an example from the menu. You can also download our examples from the GitHub.


Like it?

Download Buy

If you want to try the GemBox.Pdf yourself, you can download the free version. It delivers the same performance and set of features as the professional version, but with some operations limited. To remove the limitation, you need to purchase a license.