Overview
Cuemon.AspNetCore.Mvc extends Microsoft.AspNetCore.Mvc and builds on Cuemon.AspNetCore to turn MVC responses into cache-aware, diagnostics-aware HTTP responses. Its main job is to wrap controller payloads in cache metadata, translate exceptions into structured fault documents, and attach request-time protections such as server timing, header sentinels, and throttling without repeating the same response logic in every action.
The package also exposes the MVC-facing extension model behind those behaviors: configurable filter base classes, stream-based input and output formatter bases, model-binding suppression for streaming endpoints, and concrete result types such as SeeOtherResult, ForbiddenObjectResult, and TooManyRequestsObjectResult for status codes that need more than the stock MVC defaults.
Key APIs
CacheableFactory is the main entry point for cache-aware responses. CreateHttpLastModified, CreateHttpEntityTag, and Create wrap a payload in ICacheableObjectResult using timestamp and checksum delegates so later filters can emit HTTP validators without hand-written header code in each controller action.
HttpCacheableFilter coordinates the cacheable response pipeline. It runs the configured ICacheableAsyncResultFilter instances, applies Cache-Control when configured, and then unwraps the original ObjectResult.Value so MVC still serializes the payload instead of the cache wrapper.
HttpEntityTagHeaderFilter turns integrity metadata into an ETag header. By default it uses HttpEntityTagHeaderOptions.EntityTagProvider when the result value implements IEntityDataIntegrity, and it can fall back to parsing the response body when UseEntityTagResponseParser is enabled.
HttpLastModifiedHeaderFilter projects timestamp metadata into the Last-Modified header. Its default LastModifiedProvider uses timestamp.Modified ?? timestamp.Created, which makes it a small companion filter for cacheable results that only need timestamp-based revalidation.
FaultDescriptorFilter converts MVC exceptions into structured fault responses. It resolves an ExceptionDescriptor, honors ExceptionDescriptorAttribute decorations on the action method, and emits either ExceptionDescriptorResult or ProblemDetails according to MvcFaultDescriptorOptions.
ServerTimingAttribute is the attribute-oriented entry point for request profiling. It creates a ServerTimingFilter, lets the action override metric name, description, threshold, log level, and environment suppression, and marks attribute-created filters so a global server-timing filter does not emit duplicate metrics.
ThrottlingSentinelAttribute is the package's extension point for per-action rate limiting. Derived attributes provide UniqueContextResolver(HttpContext), while the base attribute builds a ThrottlingSentinelFilter from an IThrottlingCache, a ThrottleQuota, retry-after behavior, and the outgoing rate-limit header names.
Basic usage
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Mvc;
using Cuemon.AspNetCore.Mvc.Filters.Cacheable;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Xunit;
namespace MyProject.Tests;
public sealed class HttpCacheableFilterTest : Test
{
public HttpCacheableFilterTest(ITestOutputHelper output) : base(output) { }
[Fact]
public async Task ShouldPromoteCacheMetadata_IntoMvcResponseHeaders()
{
var snapshot = new ReleaseSnapshot("feed.xml", new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc), new DateTime(2026, 5, 29, 9, 30, 0, DateTimeKind.Utc));
var objectResult = new ObjectResult(CacheableFactory.CreateHttpLastModified(snapshot, o =>
{
o.TimestampProvider = x => x.Created;
o.ChangedTimestampProvider = x => x.Updated;
}));
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = HttpMethods.Get;
httpContext.Response.StatusCode = StatusCodes.Status200OK;
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var context = new ResultExecutingContext(actionContext, new List<IFilterMetadata>(), objectResult, new object());
var filter = new HttpCacheableFilter(Options.Create(new HttpCacheableOptions { Filters = { new HttpLastModifiedHeaderFilter() } }));
await filter.OnResultExecutionAsync(context, () => Task.FromResult(new ResultExecutedContext(actionContext, new List<IFilterMetadata>(), objectResult, new object())));
Assert.Same(snapshot, objectResult.Value);
Assert.Equal(snapshot.Updated, httpContext.Response.GetTypedHeaders().LastModified?.UtcDateTime);
Assert.NotNull(httpContext.Response.GetTypedHeaders().CacheControl);
TestOutput.WriteLine($"Last-Modified: {httpContext.Response.Headers["Last-Modified"]}");
}
private sealed record ReleaseSnapshot(string Path, DateTime Created, DateTime Updated);
}
Use this pattern when an MVC action should return a normal model while still participating in HTTP revalidation through Last-Modified and Cache-Control headers. It matters because CacheableFactory keeps timestamp selection close to the payload while HttpCacheableFilter consumes that metadata and restores the original result before MVC writes the response body.
Installation
dotnet add package Cuemon.AspNetCore.Mvc
Usage guidance
Use Cuemon.AspNetCore.Mvc when your ASP.NET Core MVC layer needs reusable result and filter building blocks for cache validators, structured fault responses, server-timing metrics, request sentinels, throttling, or stream-based formatter implementations. If you only need stock MVC results and filters, stay with Microsoft.AspNetCore.Mvc; if you want service-registration helpers and higher-level cache-busting configuration on top of these primitives, move up to Cuemon.Extensions.AspNetCore.Mvc.
Family packages
- 🌐Cuemon.AspNetCore
- 🏭Cuemon.AspNetCore.App
- 🌐Cuemon.AspNetCore.Authentication
- 🌐Cuemon.AspNetCore.Razor.TagHelpers
- 📦Cuemon.Core
- 🏭Cuemon.Core.App
- 🗄️Cuemon.Data
- 🗄️Cuemon.Data.Integrity
- 🗄️Cuemon.Data.SqlClient
- 🩺Cuemon.Diagnostics
- 🌐Cuemon.Extensions.AspNetCore
- 🌐Cuemon.Extensions.AspNetCore.Authentication
- 🌐Cuemon.Extensions.AspNetCore.Mvc
- 🌐Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json
- 🌐Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml
- 🌐Cuemon.Extensions.AspNetCore.Mvc.RazorPages
- 🌐Cuemon.Extensions.AspNetCore.Text.Json
- 🌐Cuemon.Extensions.AspNetCore.Xml
- 📦Cuemon.Extensions.Collections.Generic
- 📦Cuemon.Extensions.Collections.Specialized
- 📦Cuemon.Extensions.Core
- 🗄️Cuemon.Extensions.Data
- 🗄️Cuemon.Extensions.Data.Integrity
- 📦Cuemon.Extensions.DependencyInjection
- 🩺Cuemon.Extensions.Diagnostics
- 🏗️Cuemon.Extensions.Hosting
- 📦Cuemon.Extensions.IO
- 📦Cuemon.Extensions.Net
- 📦Cuemon.Extensions.Reflection
- 📦Cuemon.Extensions.Runtime.Caching
- 📝Cuemon.Extensions.Text
- 📝Cuemon.Extensions.Text.Json
- 📦Cuemon.Extensions.Threading
- 📦Cuemon.Extensions.Xml
- 📦Cuemon.IO
- ⚙️Cuemon.Kernel
- 📦Cuemon.Net
- 📦Cuemon.Resilience
- 📦Cuemon.Runtime.Caching
- 🔐Cuemon.Security.Cryptography
- 📦Cuemon.Threading
- 📦Cuemon.Xml