Pixata.AspNetCore 1.9.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Pixata.AspNetCore --version 1.9.0
                    
NuGet\Install-Package Pixata.AspNetCore -Version 1.9.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Pixata.AspNetCore" Version="1.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Pixata.AspNetCore" Version="1.9.0" />
                    
Directory.Packages.props
<PackageReference Include="Pixata.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Pixata.AspNetCore --version 1.9.0
                    
#r "nuget: Pixata.AspNetCore, 1.9.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Pixata.AspNetCore@1.9.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Pixata.AspNetCore&version=1.9.0
                    
Install as a Cake Addin
#tool nuget:?package=Pixata.AspNetCore&version=1.9.0
                    
Install as a Cake Tool

Pixata.AspNetCore Pixata.AspNetCore Nuget package

Pixata

Important

As the validation extension in this package is only designed to be used in server-side projects, you should reference this package in a server-side project. If you have a WASM project, adding a reference to this package will cause errors.

Registering services

The code in this package requires certain dependencies to be registered in the DI container. In order to make this easier, there is an extension method to add them all. In Program.cs add this line...

builder.Services.AddPixataAspNetCore<ContactModel>();

...where ContactModel is any type in your project. If you are using the validation filter (see below), then it is used here to point the framework to the assembly containing your models.

This registers everything in the package. If you only want some of it, you can say so...

builder.Services.AddPixataAspNetCore<ContactModel>(o => {
  o.RegisterPdfConverter = false;
  o.RegisterDocumentTemplateHelper = false;
});

The options are...

Option Default What it registers
RegisterPdfConverter true The wkhtmltopdf IConverter used by DocumentTemplateHelper to generate PDFs. Set this to false if your app generates PDFs some other way (QuestPDF, for example), and you don't want the native wkhtmltopdf library anywhere near your app
RegisterDocumentTemplateHelper true DocumentTemplateHelper, along with the HtmlRenderer and IHttpContextAccessor that it needs
RegisterValidation true Your FluentValidation validators (from the assembly containing the type you pass in) and the ValidationEndpointFilter

The converter is registered as a factory, so wkhtmltopdf isn't loaded until something actually asks for it. Prior to v1.9.0 it was constructed while services were being registered, which meant that every app referencing this package loaded the native library at startup, even if it never generated a PDF.

As DocumentTemplateHelper takes an IConverter, setting RegisterPdfConverter to false whilst leaving RegisterDocumentTemplateHelper set to true will throw an exception when you register the services, unless you have registered an IConverter of your own first. This is deliberate, as it's a lot easier to fix than the error you'd otherwise get when something tries to resolve the helper.

If you don't use the validation filter, there is a non-generic overload, which registers everything apart from the validation services...

builder.Services.AddPixataAspNetCore(o => o.RegisterPdfConverter = false);

If you want to use the route dump feature (see below) then you'll also need to the following...

app.MapPixataAspNetCoreApiEndpoints();

Auditing entities

When investigating bug reports from customers, I often find that the issue is nothing to do with my code, it's that they have changed something in the database, and I need to find out what they changed, and when.

Adding auditing manually can be done, but means you end up writing the same intrusive code in every app. To combat this, I have added some auditing functionality to this repo. This consist of two parts...

  • An EF Core interceptor that adds audit entries for every change to entities in your DbContext
  • A Blazor component that allows you to browse the audit information easily.

See the AuditViewer readme for more information.

Route dumping

When writing API endpoints, it can be hard to keep track of all the routes you have defined, and what they are. To help with this, I have added a feature that will dump all the routes in your app to the console when the app starts.

All you need to do is call MapPixataAspNetCoreApiEndpoints() as shown above, and then navigate to /dump-routes.

By default, it ignores routes that start with any of "/_blazor", "/_framework" or "/_content", as these are not usually of interest. You can override this by passing an array of routes to ignore...

app.MapPixataAspNetCoreApiEndpoints(["/_blazor", "/_framework", "/_content", "/hello"]);

Note that you need to include the default ones if you want to ignore them. If you pass in an empty array, then all routes will be dumped.

Also note that any routes that start with any of the specified routes will be ignored. So if you specify "/hello", then "/hello-world" will also be ignored.

DocumentTemplateHelper

I often find myuself generating documents, either for conversion to PDF, or for emailing. This has always been a painful process, so I decided that a helper was needed. This class contains two methods, one for generating HTML from a Blazor component, and another for generating a PDF from a Blazor component.

If you didn't register the services as explained above, then you need to register a Microsoft dependency and the Pixata template helper in Program.cs...

builder.Services.AddScoped<HtmlRenderer>();
builder.Services.AddScoped<DocumentTemplateHelper>();

If you haven't already got it, then you will also need to add the following line...

builder.Services.AddHttpContextAccessor();

Note that you only need this if you registered the services yourself. As of v1.9.0, AddPixataAspNetCore registers the IHttpContextAccessor for you.

Then, you create a Blazor component that will be the template for the document you wish to generate. It needs to accept two parameters as follows...

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    
  </head>
  <body>
    <div>
      <h2><img src="@BaseUrl/images/logo.png" width="70" height="70" /> Thank you for contacting us</h2>
      <div>Your message has been received, and we'll get back to you as soon as possible.</div>
      <div>
        <h3>Your message</h3>
        <HtmlRaw Html="@(Model.Message.Replace("\n", "<br/>"))" />
      </div>
      <h3>The Fab Ferret Emporium team</h3>
    </div>
  </body>
</html>
@code {

  [Parameter]
  public string BaseUrl { get; set; } = "";

  [Parameter]
  public ContactModel Model { get; set; } = null!;

}

The BaseUrl parameter is populated by the template helper, and allows you to pull in images from your web site, as you can see above. The Model parameter is the model that you want to use to populate the template, and can be any class.

With that in place, you can inject a DocumentTemplateHelper into your code, and use it as follows...

// Generate HTML for use as an email body...
ContactModel model = new ContactModel { Name = "Billy Shears", Email = "billy@shears.co.uk" };
string html = await documentTemplateHelper
  .CreateHtmlFromTemplate<EmailFromContactPageTemplate>((nameof(EmailFromContactPageTemplate.Model), model));

// Generate PDF for attaching to an email...
InvoiceModel model = new InvoiceModel { /* set properties */ };
byte[] bytes = await documentTemplateHelper
  .CreatePdfFromTemplate<InvoiceTemplate>((nameof(InvoiceTemplate.Model), model));

RequestLoggingMiddleware

When writing API endpoints, it can be hard to debug 400 errors, which are often caused by incorrect or mismatched paths, or invalid data in the request. You often don't get much clue as to what actually happened.

To help with this, I have added a piece of middleware that will log every incoming request to the app (subject to configuration choices, see below). This will log the path, the query string, the headers and the body of the request. This can be very helpful for debugging, as it allows you to see exactly what was sent to the server.

You need to register the middleware in your Program.cs file, as follows...

builder.Services.AddRequestLogging();

This will use the default configuration, which is to include the request headers and body in the logging, and ignore any requests whose path starts with any of...

  • "_framework"
  • "_blazor"
  • "_content"
  • ".well-known"

Redaction

Logging whole requests is only useful if you can leave the logging on, and you can't do that if the log fills up with authentication cookies and passwords. Anyone who can read your logs would then be able to impersonate your users.

Since v1.9.0, the middleware redacts anything that looks sensitive before it writes to the log...

  • Headers - the value of any header listed in RedactedHeaders is replaced. By default that's Authorization, Proxy-Authorization, Cookie, Set-Cookie, X-Api-Key, Api-Key, X-Auth-Token, X-Access-Token, X-CSRF-Token, X-XSRF-Token and RequestVerificationToken
  • Query string parameters, form fields and JSON properties - the value of anything whose name is listed in RedactedFields is replaced. By default that covers the usual suspects (password, newPassword, token, accessToken, refreshToken, secret, clientSecret, apiKey, cardNumber, cvv and friends). JSON is redacted at any depth, including inside arrays
  • Bodies of unexpected content types aren't logged at all. Only the content types listed in LoggedBodyContentTypes (JSON, XML, plain text and form data) are written to the log, so a file upload no longer ends up as several megabytes of binary in your log file
  • Long bodies are truncated to MaxBodyLength characters (4096 by default). Only that much of the body is read, so a large upload isn't pulled into memory just to be thrown away

Matching of header and field names is case-insensitive.

If the body claims to be JSON but doesn't parse (which is often exactly the sort of thing you're trying to debug), it's logged as it came in, but with anything that looks like a sensitive property redacted.

Redaction is a safety net, not a guarantee. If your app posts sensitive data in a field with a name I haven't thought of, add it to RedactedFields.

Options

You can override any of the options as follows...

builder.Services.AddRequestLogging(o => {
  o.IgnoredPaths = ["_framework", "health"]; // Or whatever you want to ignore
  o.LogBody = false;
  o.LogHeaders = false;
  o.RedactedHeaders.Add("X-My-Custom-Auth-Header");
  o.RedactedFields.Add("mothersMaidenName");
  o.MaxBodyLength = 1024;
  o.RedactionPlaceholder = "***";
});

To log all requests, set o.IgnoredPaths to an empty array [].

The defaults are exposed as RequestLoggingOptions.DefaultRedactedHeaders and RequestLoggingOptions.DefaultRedactedFields, so you can build your own list from them if you'd rather replace the sets than add to them. Bear in mind that if you do replace them, anything you leave out is no longer redacted.

You then need to register the middleware...

app.UseRequestLogging();

This should be after any authentication/authorisation middleware, but before any endpoint mapping.

ValidationEndpointFilter

When using fluent validation in Blazor server-side, the chances of anyone bypassing your validation are small enough that they can be ignored for most cases. However, when running in client-side (WASM), validation is handled in the WASM, and the data is then sent to the server via API endpoints. This means that anyone can modify the request, or write a script to mimic it, and bypass your validation.

The obvious (and correct) solution to this is to validate your incoming models on the server before doing anything with the data. Generally, this is a bit of a pain, as it involves duplicating validation code.

To avoid this, you can add the ValidationEndpointFilter to your API endpoints. This will run the same validation as in the client, but on the server, so if anyone tries to bypass the client-side validation, they will be stopped by the server-side validation. This allows you to protect your endpoints without adding much extra code.

As explained above, the validation extension requires services to be registered in the DI container. To make this easier, you can use the AddPixataAspNetCore extension method in your Program.cs file:

builder.Services.AddPixataAspNetCore<ContactModel>();

...where ContactModel is any model, it is used here to point the framework to the assembly containing your models.

Basic usage is very simple. Once you've registered the dependencies (see above), you just add the AddEndpointFilter extension method to any API endpoints that need validation.

You change...

app.MapPost("/contact-api", async (GeneralServiceInterface service, ContactModel model) =>
  await service.Contact(model));

...to...

app.MapPost("/contact-api", async (GeneralServiceInterface service, ContactModel model) =>
  await service.Contact(model))
    .AddValidationEndpointFilter();

When the endpoint is hit, the appropriate validator will be found and applied to the incoming model.

If there were any validation errors, then the filter will return an ApiResponse with a State of ApiResponseStates.Failure and a Message containing a formatted string of the validation errors. By default, the errors are formatted as a comma-delimited string of the form $"{e.PropertyName}: {e.ErrorMessage}", which would produce something like... "Validation errors - Name: Required, Email: Invalid". This can be overriden as explained below.

The filter has two optional parameters.

You can pass in a Func<ValidationFailure, string> to format the validation errors. For example, if you wanted to include the error code in the message, you could do something like this...

app.MapPost(RoutesHelper.ApiContact, async (GeneralServiceInterface service, ContactModel model) =>
  await service.Contact(model))
    .AddEndpointFilter(new ValidationEndpointFilter(err => $"({err.ErrorCode}) {err.ErrorMessage} for {err.PropertyName}"));

This would produce a message of the form "Validation errors - (NotEmptyValidator) Required for Name, (EmailValidator) Invalid for Email".

By default, the filter will pick up any a validator for any class in the assembly you specified when registering (with the AddValidatorsFromAssemblyContaining method, see above). You may wish to restrict this further, and specify that only classes within a certain namepsace should be validated. You can do this as follows...

app.MapPost("/contact-api", async (GeneralServiceInterface service, ContactModel model) =>
  await service.Contact(model)).AddEndpointFilter(new ValidationEndpointFilter(nameSpace: typeof(ContactModel).Namespace);

This is not a common scenario, but is included for those odd cases.

Auditing

This package includes a comprehensive entity auditing system that automatically captures all entity changes via an EF Core SaveChangesInterceptor. It stores full snapshots and property-level diffs for every create, update, and delete operation.

Setup

1. Register auditing services in Program.cs:

builder.Services.AddAuditing<MyDbContext>();

2. Add the interceptor to your DbContext registration:

builder.Services.AddDbContext<MyDbContext>((serviceProvider, options) =>
  options.UseSqlServer(connectionString)
         .AddAuditingInterceptor(serviceProvider));

3. Add the Audit DbSet to your DbContext:

using Pixata.AspNetCore.Auditing.Models;

public class MyDbContext : DbContext {
  public DbSet<Audit> Audits { get; set; }
  // ... other DbSets
}

4. Create and run an EF Core migration:

dotnet ef migrations add AddAuditing
dotnet ef database update

Data model

Each audit entry stores:

  • EntityType — fully qualified type name
  • EntityId — JSON-serialised primary key (handles composite keys)
  • Operation — Created, Updated, or Deleted
  • ChangedBy — username from Identity or custom identifier
  • ChangedAt — UTC timestamp
  • FullSnapshot — complete JSON of the entity at that point in time
  • ChangedProperties — JSON of changed properties only (null for Create/Delete), stored as { "PropertyName": [oldValue, newValue] }

Opting out of auditing

Entities can opt out of auditing by applying the [NoAudit] attribute:

using Pixata.AspNetCore.Auditing.Attributes;

[NoAudit]
public class SensitiveEntity {
  // This entity will not be audited
}

Custom user identification

By default, the auditing system identifies users via HttpContext.User.Identity.Name, falling back to "System" if no user is available. You can override this by injecting AuditUserContextInterface and setting the UserIdentifier property:

public class SprocketController(AuditUserContextInterface auditContext) : ControllerBase {
  [AllowAnonymous]
  public async Task<IActionResult> NotifyFromSprocket() {
    auditContext.UserIdentifier = "SprocketNotificationEndpoint";
    // Any changes saved in this request will use this identifier
  }
}

Blazor audit viewer

The Pixata.Blazor package includes an AuditViewer component that provides a UI for browsing audit history. To use it, add this to a Blazor page:

@page "/audit-viewer"
<AuditViewer TContext="MyDbContext" />

The viewer provides:

  • Entity type selector (discovers DbSet<T> properties via reflection)
  • Searchable/pageable entity grid
  • Timeline view showing property changes with diff highlighting
  • Filters for date range, user, and operation type
  • URL querystring integration — refreshing the page preserves your current view

Retention policy

By default, audit entries are retained forever. You can configure automatic cleanup by specifying a retention period:

builder.Services.AddAuditing<MyDbContext>(options => {
  options.RetentionPeriod = TimeSpan.FromDays(90); // Delete entries older than 90 days
  options.CleanupInterval = TimeSpan.FromHours(6); // Check every 6 hours (default: daily)
});

When a retention period is set, a background service runs periodically and deletes audit entries older than the configured period.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.10.0 51 9/3/2026
1.9.0 104 8/25/2026
1.8.0 134 8/17/2026
1.6.0 161 6/9/2026
1.5.2 125 6/1/2026
1.5.1 130 5/31/2026
1.5.0 135 5/28/2026
1.4.0 147 5/27/2026
1.3.2 134 4/21/2026
1.3.1 118 4/21/2026
1.3.0 117 4/21/2026
1.2.0 158 3/8/2026
1.1.0 129 3/5/2026
1.0.3 124 2/23/2026
1.0.2 124 2/23/2026
1.0.1 117 2/23/2026
1.0.0 131 2/19/2026