I am trying to show a simple PDF in our web view based MAUI app on an Android device. It’s seems harder than necessary. A version with canvas does work, but performance is terrible.
Internet and AI suggests PDF.js as solution and using a WebView as base. However, it always shows as a white blank view when running. Also, Visual Studio for Mac also marks the WebView (or BlazorWebView) as unavailable making me think it just ignores this tag.
The warning is: “Found markup element with unexpected name ‘BlazorWebViewHostPage’. If this is intended
to be a component, add a @using directive for its namespace”
@using Microsoft.AspNetCore.Components.WebView.Maui
@inject TemporaryFileService TemporaryFileService
@inject NavigationManager Navigation
@using ProjectNameMobileMaui.Services
<BlazorWebView HostPage="wwwroot/index.html">
<title>PDF Viewer</title>
<base href="_content/YourAssemblyName/" />
<script src="_framework/blazor.webassembly.js"></script>
<BlazorWebView Source="/pdfViewer.html?filePath=/sample.pdf" />
public string FileName { get; set; }
protected override void OnParametersSet()
Console.WriteLine("OnParametersSet called");
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
FileName = query["fileName"];
Console.WriteLine($"FileName parameter: {FileName}");
if (!string.IsNullOrEmpty(FileName))
FilePath = Path.Combine(TemporaryFileService.GetTempDirectory(), FileName);
Console.WriteLine($"Calculated FilePath: {FilePath}");
Console.WriteLine("FileName parameter is null or empty");
<code>@page "/pdfviewer"
@using Microsoft.AspNetCore.Components.WebView.Maui
@inject TemporaryFileService TemporaryFileService
@inject NavigationManager Navigation
@using ProjectNameMobileMaui.Services
<BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebViewHostPage>
<html>
<head>
<title>PDF Viewer</title>
<base href="_content/YourAssemblyName/" />
</head>
<body>
<app>Loading...</app>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
</BlazorWebViewHostPage>
<BlazorWebView Source="/pdfViewer.html?filePath=/sample.pdf" />
</BlazorWebView>
@code {
[Parameter]
public string FileName { get; set; }
private string FilePath;
protected override void OnParametersSet()
{
Console.WriteLine("OnParametersSet called");
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
FileName = query["fileName"];
Console.WriteLine($"FileName parameter: {FileName}");
if (!string.IsNullOrEmpty(FileName))
{
FilePath = Path.Combine(TemporaryFileService.GetTempDirectory(), FileName);
Console.WriteLine($"Calculated FilePath: {FilePath}");
}
else
{
Console.WriteLine("FileName parameter is null or empty");
}
}
}
</code>
@page "/pdfviewer"
@using Microsoft.AspNetCore.Components.WebView.Maui
@inject TemporaryFileService TemporaryFileService
@inject NavigationManager Navigation
@using ProjectNameMobileMaui.Services
<BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebViewHostPage>
<html>
<head>
<title>PDF Viewer</title>
<base href="_content/YourAssemblyName/" />
</head>
<body>
<app>Loading...</app>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
</BlazorWebViewHostPage>
<BlazorWebView Source="/pdfViewer.html?filePath=/sample.pdf" />
</BlazorWebView>
@code {
[Parameter]
public string FileName { get; set; }
private string FilePath;
protected override void OnParametersSet()
{
Console.WriteLine("OnParametersSet called");
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
FileName = query["fileName"];
Console.WriteLine($"FileName parameter: {FileName}");
if (!string.IsNullOrEmpty(FileName))
{
FilePath = Path.Combine(TemporaryFileService.GetTempDirectory(), FileName);
Console.WriteLine($"Calculated FilePath: {FilePath}");
}
else
{
Console.WriteLine("FileName parameter is null or empty");
}
}
}
Any leads?
Parts of my csproj file:
<code><Project Sdk="Microsoft.NET.Sdk.Razor">
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<SkipValidateMauiImplicitPackageReferences>true</SkipValidateMauiImplicitPackageReferences>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultCssItems>false</EnableDefaultCssItems>
<Nullable>enable</Nullable>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>3</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android|AnyCPU'">
<AndroidDexTool>d8</AndroidDexTool>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<PackageReference Include="CommunityToolkit.Maui" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.40" />
<code><Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<UseMaui>true</UseMaui>
<SkipValidateMauiImplicitPackageReferences>true</SkipValidateMauiImplicitPackageReferences>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultCssItems>false</EnableDefaultCssItems>
<Nullable>enable</Nullable>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>3</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android|AnyCPU'">
<AndroidDexTool>d8</AndroidDexTool>
<AndroidPackageFormat>apk</AndroidPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.40" />
</ItemGroup>
</code>
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<UseMaui>true</UseMaui>
<SkipValidateMauiImplicitPackageReferences>true</SkipValidateMauiImplicitPackageReferences>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultCssItems>false</EnableDefaultCssItems>
<Nullable>enable</Nullable>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>3</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android|AnyCPU'">
<AndroidDexTool>d8</AndroidDexTool>
<AndroidPackageFormat>apk</AndroidPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.40" />
</ItemGroup>
And pdfViewer.html:
<title>PDF Viewer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
<div id="pdfContainer">PDF Viewer Loaded</div>
<script>console.log("PDF Viewer script started");
document.getElementById('pdfContainer').innerHTML += "<br>Script Started";
const urlParams = new URLSearchParams(window.location.search);
const filePath = urlParams.get('filePath');
console.log(`filePath from URL: ${filePath}`);
document.getElementById('pdfContainer').innerHTML += `<br>filePath from URL: ${filePath}`;
console.error('No file path found');
document.getElementById('pdfContainer').innerHTML += "<br>No file path found";
console.log("Fetch response received, status:", response.status);
document.getElementById('pdfContainer').innerHTML += `<br>Fetch response status: ${response.status}`;
throw new Error("Network response was not ok");
return response.arrayBuffer();
console.log("PDF data loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF data loaded";
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';
return pdfjsLib.getDocument({ data: pdfData }).promise;
console.log("PDF document loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF document loaded";
console.log("PDF page loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF page loaded";
const viewport = page.getViewport({ scale: scale });
const container = document.getElementById('pdfContainer');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
container.appendChild(canvas);
return page.render(renderContext).promise;
console.log("PDF page rendered");
document.getElementById('pdfContainer').innerHTML += "<br>PDF page rendered";
console.error('Error loading PDF:', error);
document.getElementById('pdfContainer').innerHTML += `<br>Error loading PDF: ${error.message}`;
<code><!DOCTYPE html>
<html>
<head>
<title>PDF Viewer</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#pdfContainer {
width: 100vw;
height: 100vh;
touch-action: none;
}
canvas {
touch-action: none;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
</head>
<body>
<div id="pdfContainer">PDF Viewer Loaded</div>
<script>console.log("PDF Viewer script started");
document.getElementById('pdfContainer').innerHTML += "<br>Script Started";
const urlParams = new URLSearchParams(window.location.search);
const filePath = urlParams.get('filePath');
console.log(`filePath from URL: ${filePath}`);
document.getElementById('pdfContainer').innerHTML += `<br>filePath from URL: ${filePath}`;
if (!filePath) {
console.error('No file path found');
document.getElementById('pdfContainer').innerHTML += "<br>No file path found";
} else {
fetch(filePath)
.then(response => {
console.log("Fetch response received, status:", response.status);
document.getElementById('pdfContainer').innerHTML += `<br>Fetch response status: ${response.status}`;
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.arrayBuffer();
})
.then(pdfData => {
console.log("PDF data loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF data loaded";
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';
return pdfjsLib.getDocument({ data: pdfData }).promise;
})
.then(pdf => {
console.log("PDF document loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF document loaded";
return pdf.getPage(1);
})
.then(page => {
console.log("PDF page loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF page loaded";
const scale = 1.5;
const viewport = page.getViewport({ scale: scale });
const container = document.getElementById('pdfContainer');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
container.appendChild(canvas);
const renderContext = {
canvasContext: context,
viewport: viewport
};
return page.render(renderContext).promise;
})
.then(() => {
console.log("PDF page rendered");
document.getElementById('pdfContainer').innerHTML += "<br>PDF page rendered";
})
.catch(error => {
console.error('Error loading PDF:', error);
document.getElementById('pdfContainer').innerHTML += `<br>Error loading PDF: ${error.message}`;
});
}</script>
</body>
</html>
</code>
<!DOCTYPE html>
<html>
<head>
<title>PDF Viewer</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#pdfContainer {
width: 100vw;
height: 100vh;
touch-action: none;
}
canvas {
touch-action: none;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
</head>
<body>
<div id="pdfContainer">PDF Viewer Loaded</div>
<script>console.log("PDF Viewer script started");
document.getElementById('pdfContainer').innerHTML += "<br>Script Started";
const urlParams = new URLSearchParams(window.location.search);
const filePath = urlParams.get('filePath');
console.log(`filePath from URL: ${filePath}`);
document.getElementById('pdfContainer').innerHTML += `<br>filePath from URL: ${filePath}`;
if (!filePath) {
console.error('No file path found');
document.getElementById('pdfContainer').innerHTML += "<br>No file path found";
} else {
fetch(filePath)
.then(response => {
console.log("Fetch response received, status:", response.status);
document.getElementById('pdfContainer').innerHTML += `<br>Fetch response status: ${response.status}`;
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.arrayBuffer();
})
.then(pdfData => {
console.log("PDF data loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF data loaded";
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';
return pdfjsLib.getDocument({ data: pdfData }).promise;
})
.then(pdf => {
console.log("PDF document loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF document loaded";
return pdf.getPage(1);
})
.then(page => {
console.log("PDF page loaded");
document.getElementById('pdfContainer').innerHTML += "<br>PDF page loaded";
const scale = 1.5;
const viewport = page.getViewport({ scale: scale });
const container = document.getElementById('pdfContainer');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
container.appendChild(canvas);
const renderContext = {
canvasContext: context,
viewport: viewport
};
return page.render(renderContext).promise;
})
.then(() => {
console.log("PDF page rendered");
document.getElementById('pdfContainer').innerHTML += "<br>PDF page rendered";
})
.catch(error => {
console.error('Error loading PDF:', error);
document.getElementById('pdfContainer').innerHTML += `<br>Error loading PDF: ${error.message}`;
});
}</script>
</body>
</html>