A web application built with Umbraco CMS v13 for managing and displaying investment fund information, including historical NAV values, market prices, and performance metrics.
- Fund Management: View and manage investment fund details
- Historical NAV Data: Track Net Asset Value history with interactive charts
- Data Visualization: SVG bar charts aligned with tabular data
- Export Functionality:
- PDF export with customizable formatting, metadata, and height options
- Excel export with auto-sized columns, rich text formatting, and border options
- Download Security: Token-based download authentication with auto-refresh
- Performance: In-memory caching of funds.json data using MediaFileManager
- Responsive Design: Mobile-friendly interface
- Multi-line Headers: Support for complex report headers
- Bold Text Markup: Use
**text**for bold formatting in exports - ViewComponents: Modular, reusable components for funds, tables, and navigation history
- Framework: Umbraco CMS 13.10.0
- Backend: ASP.NET Core 8.0
- Frontend: Razor Views, JavaScript, Highcharts 5.0.7
- PDF Library: PdfSharp
- Excel Library: ClosedXML
- Media Services: Umbraco MediaFileManager & IMediaService
- Clone the repository
- Restore NuGet packages:
dotnet restore
- Build the project:
dotnet build
- Run the application:
dotnet run
Umbraco13/
├── Controllers/ # API controllers
├── Models/ # View models and data models
├── Services/ # Business logic services
│ ├── PdfExportService.cs
│ ├── ExcelExportService.cs
│ ├── FundsJsonService.cs # In-memory funds.json caching
│ ├── NavHistoryService.cs # Navigation history management
│ ├── DownloadTokenService.cs # Token-based download security
│ ├── FundService.cs
│ └── FundHistoricalNavService.cs
├── ViewComponents/ # Reusable view components
│ ├── FundsTableViewComponent.cs
│ ├── FundsJsonViewComponent.cs
│ ├── HistoricalNavTableViewComponent.cs
│ └── NavHistoryViewComponent.cs
├── Helpers/ # Helper utilities
│ └── FundTableConverter.cs
├── Views/ # Razor views
│ └── Shared/
│ └── Components/
│ └── HistoricalNavTable/
│ └── Default.highcharts.cshtml # Highcharts chart with alignment
├── wwwroot/ # Static assets
│ └── js/
│ └── scripts.js
└── appsettings.json # Configuration
var options = new PdfExportOptions
{
ReportTitle = "FUND SUMMARY REPORT",
ItemsPerPage = 25,
Disclaimer = "This report contains historical NAV prices and is for informational purposes only."
};
var pdfBytes = _pdfExportService.ExportToPdf(data, columns, options);var options = new ExcelExportOptions
{
ReportTitle = "Fund Data Export",
Disclaimer = "**Confidential** - For internal use only.",
AutoSizeColumns = true,
EnableRichTextFormatting = true
};
var excelBytes = _excelExportService.ExportToExcel(data, columns, options);// Automatically loads funds.json at startup and caches in memory
var fundsData = _fundsJsonService.GetFundsData();
// Retrieve NAV history for a specific fund
var navHistory = _fundsJsonService.GetNavHistory("TICKER123");// Get historical NAV data for a fund ticker
var history = await _navHistoryService.GetNavHistoryAsync("TICKER123");
foreach (var entry in history)
{
Console.WriteLine($"{entry.Date}: NAV={entry.NavPrice}, Market={entry.MarketPrice}");
}// Generate a secure token for PDF download (valid for 30 minutes)
var pdfToken = _downloadTokenService.GenerateDownloadToken("pdf");
// Validate token before allowing download
var isValid = _downloadTokenService.ValidateDownloadToken(token, "pdf");
if (isValid)
{
// Proceed with download
}// Invoke in Razor views
@await Component.InvokeAsync("FundsTable", new { tickerCode = "TICKER123" })
@await Component.InvokeAsync("NavHistory", new { tickerCode = "TICKER123" })
@await Component.InvokeAsync("FundsJson")/// FundsJsonService uses MediaFileManager to load funds.json from Umbraco media library
/// No direct instantiation needed - registered as singleton in DI container
public class FundsJsonService : IFundsJsonService
{
private readonly MediaFileManager _mediaFileManager;
private readonly IMediaService _mediaService;
// Automatically loads and caches funds.json at startup
// Uses MediaFileManager.FileSystem to access media files
// Uses IMediaService to locate funds.json in media library
}/**
* Render Highcharts bar chart with dynamic table column alignment
* Automatically calculates bar widths and positions to match table columns
*
* Features:
* - Dynamic width measurement from table cells
* - Responsive padding (mobile vs desktop)
* - Left margin to align with table data columns
* - Auto-resize on window resize
*/
function renderHighchartsBarChart(data, containerId, tableColumnWidth, labelColumnWidth) {
const isMobile = window.innerWidth < 768;
// Responsive padding: more space on mobile
const groupPadding = isMobile ? 0.30 : 0.20;
const pointPadding = isMobile ? 0.05 : 0.02;
// Calculate bar width as percentage of column
const barWidth = isMobile
? Math.floor(tableColumnWidth * 0.25)
: Math.floor(tableColumnWidth * 0.30);
Highcharts.chart(containerId, {
chart: {
type: 'column',
marginLeft: labelColumnWidth, // Skip label column
backgroundColor: '#f8f9fa',
borderRadius: 8
},
plotOptions: {
column: {
pointWidth: barWidth, // Dynamic width
groupPadding: groupPadding, // Space between date groups
pointPadding: pointPadding, // Space between NAV/Market bars
borderRadius: 3
}
},
series: [
{ name: 'NAV Price', data: navPrices, color: '#3b82f6' },
{ name: 'Market Price', data: marketPrices, color: '#22c55e' }
]
});
}
// Call with measured table dimensions
function renderChart(paginatedNavs) {
setTimeout(() => {
const table = document.getElementById('historical-nav-table');
const labelCell = table.querySelector('tbody tr td:first-child');
const dataCell = table.querySelector('tbody tr td:nth-child(2)');
renderHighchartsBarChart(
paginatedNavs,
'nav-chart',
dataCell?.offsetWidth || 120,
labelCell?.offsetWidth || 0
);
}, 50); // Wait for DOM render
}This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2025
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
✅ Commercial Use: You can use this software in commercial applications ✅ Modification: You can modify the source code to fit your needs ✅ Distribution: You can distribute copies of the software ✅ Sublicense: You can grant others the right to use this software ✅ Private Use: You can use this software privately
- Include the copyright notice and license text in your distributions
- Software is provided "as is" without warranty
For detailed implementation documentation, see the notes/ folder:
- Funds JSON Service - In-memory caching with MediaFileManager
- Navigation History Service - NAV data management
- Download Token Service - Secure token-based downloads
- Media Library Integration - MediaFileManager usage
- PDF Export Service Guide
- PDF Metadata Feature
- PDF Height Options
- Excel Rich Text Formatting
- Excel Export Height Options
- Historical NAV Table - Pivoted table layout
- Dynamic Table Sorting & Pagination
- Highcharts Integration - Interactive charts
- Highcharts Alignment - Bar-to-column alignment
- Border Options for Sections
For issues, questions, or contributions, please refer to the project repository.
Free to use for personal and commercial purposes.