Codebelt
v10.5.5

Cuemon.AspNetCore.App

One package reference that brings together Cuemon's ASP.NET Core web stack, from authentication and cacheable MVC results to Razor Pages and serializer formatters.

.NET 10.0 / .NET 9.0 MIT 49,759 downloads

Overview

Cuemon.AspNetCore.App is a metadata-only convenience package that brings the ASP.NET Core-focused parts of Cuemon under one install. It is aimed at applications that want authentication helpers, cache-busting support, cacheable MVC results, Razor TagHelpers, Razor Pages helpers, and JSON or XML formatter integrations available immediately.

The package does not declare its own public types in source evidence. The value is the bundled surface area from the referenced packages, so consumers install one package and then work directly with the APIs owned by those referenced libraries.

Key APIs

Cuemon.AspNetCore.App does not add public or protected APIs of its own, so the usable entry points come from the packages it aggregates, including DynamicCacheBusting, BasicAuthorizationHeaderBuilder, WithLastModifiedHeader, AddBasic, GetAppUrl, and the JSON or XML formatter and converter types exposed by the referenced ASP.NET Core packages.

Basic usage

These examples show the package areas that become available when you install Cuemon.AspNetCore.App.

Cuemon.AspNetCore

using System;
using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Configuration;
using Microsoft.Extensions.Options;
using Xunit;

namespace MyProject.Tests;

public class DynamicCacheBustingTest : Test
{
    public DynamicCacheBustingTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldReuseVersionWithinConfiguredTtl()
    {
        var sut = new DynamicCacheBusting(Options.Create(new DynamicCacheBustingOptions
        {
            PreferredLength = 10,
            TimeToLive = TimeSpan.FromMinutes(5)
        }));

        var first = sut.Version;
        var second = sut.Version;

        TestOutput.WriteLine($"Cache-busting token: {first}");

        Assert.Equal(first, second);
        Assert.Equal(10, first.Length);
    }
}

Cuemon.AspNetCore.Authentication

using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Authentication.Basic;
using Xunit;

namespace MyProject.Tests;

public class BasicAuthorizationHeaderBuilderTest : Test
{
    public BasicAuthorizationHeaderBuilderTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldBuildBasicAuthorizationHeader()
    {
        var sut = new BasicAuthorizationHeaderBuilder();
        BasicAuthorizationHeader header = sut
            .AddUserName("agent")
            .AddPassword("secret123")
            .Build();

        TestOutput.WriteLine(header.ToString());

        Assert.Equal("agent", header.UserName);
        Assert.Equal("secret123", header.Password);
        Assert.StartsWith("Basic ", header.ToString());
    }
}

Cuemon.AspNetCore.Mvc

using System;
using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Mvc;
using Cuemon.Data.Integrity;
using Cuemon.Extensions.AspNetCore.Mvc;
using Xunit;

namespace MyProject.Tests;

public class CacheableMvcResultTest : Test
{
    public CacheableMvcResultTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldWrapValueWithLastModifiedMetadata()
    {
        var payload = "asset-manifest";
        var cacheable = payload.WithLastModifiedHeader(o =>
        {
            o.TimestampProvider = _ => DateTime.MinValue;
            o.ChangedTimestampProvider = _ => DateTime.MaxValue;
        });
        var timestamps = Assert.IsAssignableFrom<IEntityDataTimestamp>(cacheable);

        TestOutput.WriteLine($"Created: {timestamps.Created:o}, Modified: {timestamps.Modified:o}");

        Assert.Equal(payload, cacheable.Value);
        Assert.Equal(DateTime.MinValue, timestamps.Created);
        Assert.Equal(DateTime.MaxValue, timestamps.Modified);
    }
}

Cuemon.AspNetCore.Razor.TagHelpers

using System.Threading.Tasks;
using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Configuration;
using Cuemon.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Options;
using Xunit;

namespace MyProject.Tests;

public class AppImageTagHelperTest : Test
{
    public AppImageTagHelperTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldUseConfiguredBaseUrlForApplicationAssets()
    {
        var sut = new AppImageTagHelper(Options.Create(new AppTagHelperOptions
        {
            BaseUrl = "static.example.com",
            Scheme = ProtocolUriScheme.Relative
        }), new FixedCacheBusting());

        TestOutput.WriteLine(sut.Options.GetFormattedBaseUrl());

        Assert.Equal("static.example.com", sut.Options.BaseUrl);
        Assert.Equal("//static.example.com/", sut.Options.GetFormattedBaseUrl());
    }

    private sealed class FixedCacheBusting : ICacheBusting
    {
        public string Version => "00000000000000000000000000000000";
    }
}

Cuemon.Extensions.AspNetCore

using Codebelt.Extensions.Xunit;
using Cuemon.Extensions.AspNetCore.Configuration;
using Microsoft.Extensions.Options;
using Xunit;

namespace MyProject.Tests;

public class AssemblyCacheBustingTest : Test
{
    public AssemblyCacheBustingTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldComputeVersionFromAssembly()
    {
        var sut = new AssemblyCacheBusting(Options.Create(new AssemblyCacheBustingOptions
        {
            Assembly = typeof(AssemblyCacheBustingTest).Assembly
        }));

        TestOutput.WriteLine(sut.Version);

        Assert.NotNull(sut.Version);
        Assert.NotEmpty(sut.Version);
    }
}

Cuemon.Extensions.AspNetCore.Authentication

using System.Security.Claims;
using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Authentication.Basic;
using Cuemon.Extensions.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;

namespace MyProject.Tests;

public class AuthenticationBuilderExtensionsTest : Test
{
    public AuthenticationBuilderExtensionsTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldRegisterBasicAuthenticationScheme()
    {
        var services = new ServiceCollection();
        services
            .AddAuthentication(BasicAuthorizationHeader.Scheme)
            .AddBasic(o => o.Authenticator = (username, password) =>
                new ClaimsPrincipal(new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.Name, username)
                }, BasicAuthorizationHeader.Scheme)));

        var provider = services.BuildServiceProvider();
        var options = provider.GetRequiredService<IOptionsMonitor<BasicAuthenticationOptions>>()
            .Get(BasicAuthorizationHeader.Scheme);

        TestOutput.WriteLine($"Realm: {options.Realm}");

        Assert.NotNull(options.Authenticator);
        Assert.Equal("AuthenticationServer", options.Realm);
    }
}

Cuemon.Extensions.AspNetCore.Mvc

using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Mvc;
using Cuemon.Data.Integrity;
using Cuemon.Extensions.AspNetCore.Mvc;
using Xunit;

namespace MyProject.Tests;

public class CacheableObjectResultExtensionsTest : Test
{
    public CacheableObjectResultExtensionsTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldWrapValueWithEntityTagMetadata()
    {
        var payload = "asset-manifest";
        var checksum = new byte[] { 1, 2, 3, 4 };
        var cacheable = payload.WithEntityTagHeader(o =>
        {
            o.ChecksumProvider = _ => checksum;
            o.WeakChecksumProvider = _ => false;
        });
        var integrity = Assert.IsAssignableFrom<IEntityDataIntegrity>(cacheable);

        TestOutput.WriteLine($"Validation: {integrity.Validation}");

        Assert.Equal(payload, cacheable.Value);
        Assert.Equal(EntityDataIntegrityValidation.Strong, integrity.Validation);
        Assert.NotNull(integrity.Checksum);
    }
}

Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json

using Codebelt.Extensions.Xunit;
using Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json;
using Cuemon.Extensions.Text.Json.Formatters;
using Xunit;

namespace MyProject.Tests;

public class JsonSerializationOutputFormatterTest : Test
{
    public JsonSerializationOutputFormatterTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldExposeConfiguredJsonMediaTypes()
    {
        var options = new JsonFormatterOptions();
        var sut = new JsonSerializationOutputFormatter(options);

        TestOutput.WriteLine($"JSON media types: {string.Join(", ", options.SupportedMediaTypes)}");

        Assert.IsType<JsonSerializationOutputFormatter>(sut);
        Assert.Contains(options.SupportedMediaTypes, mediaType => mediaType.ToString() == "application/json");
    }
}

Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml

using Codebelt.Extensions.Xunit;
using Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml;
using Cuemon.Xml.Serialization.Formatters;
using Xunit;

namespace MyProject.Tests;

public class XmlSerializationOutputFormatterTest : Test
{
    public XmlSerializationOutputFormatterTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldExposeConfiguredXmlMediaTypes()
    {
        var options = new XmlFormatterOptions();
        var sut = new XmlSerializationOutputFormatter(options);

        TestOutput.WriteLine($"XML media types: {string.Join(", ", options.SupportedMediaTypes)}");

        Assert.IsType<XmlSerializationOutputFormatter>(sut);
        Assert.Contains(options.SupportedMediaTypes, mediaType => mediaType.ToString() == "application/xml");
    }
}

Cuemon.Extensions.AspNetCore.Mvc.RazorPages

using Codebelt.Extensions.Xunit;
using Cuemon.AspNetCore.Configuration;
using Cuemon.AspNetCore.Razor.TagHelpers;
using Cuemon.Extensions.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace MyProject.Tests;

public class PageBaseExtensionsTest : Test
{
    public PageBaseExtensionsTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldBuildApplicationAssetUrlFromPageModel()
    {
        var services = new ServiceCollection();
        services.Configure<AppTagHelperOptions>(o =>
        {
            o.Scheme = ProtocolUriScheme.Relative;
            o.BaseUrl = "static.example.com";
        });
        services.AddSingleton<ICacheBusting>(new FixedCacheBusting());

        var page = new StubPage
        {
            PageContext = new PageContext
            {
                HttpContext = new DefaultHttpContext
                {
                    RequestServices = services.BuildServiceProvider()
                }
            }
        };

        var url = page.GetAppUrl("css/site.css");

        TestOutput.WriteLine(url);

        Assert.Equal("//static.example.com/css/site.css?v=00000000000000000000000000000000", url);
    }

    private sealed class StubPage : PageBase
    {
        public override Task ExecuteAsync() => Task.CompletedTask;
    }

    private sealed class FixedCacheBusting : ICacheBusting
    {
        public string Version => "00000000000000000000000000000000";
    }
}

Cuemon.Extensions.AspNetCore.Text.Json

using System.Collections.Generic;
using System.Text.Json.Serialization;
using Codebelt.Extensions.Xunit;
using Cuemon.Extensions.AspNetCore.Text.Json.Converters;
using Xunit;

namespace MyProject.Tests;

public class JsonConverterCollectionExtensionsTest : Test
{
    public JsonConverterCollectionExtensionsTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldRegisterStringValuesJsonConverter()
    {
        var converters = new List<JsonConverter>();
        var returned = converters.AddStringValuesConverter();

        TestOutput.WriteLine($"Registered converters: {converters.Count}");

        Assert.Same(converters, returned);
        Assert.Single(converters);
    }
}

Cuemon.Extensions.AspNetCore.Xml

using System.Collections.Generic;
using Codebelt.Extensions.Xunit;
using Cuemon.Extensions.AspNetCore.Xml.Converters;
using Cuemon.Xml.Serialization.Converters;
using Xunit;

namespace MyProject.Tests;

public class XmlConverterExtensionsTest : Test
{
    public XmlConverterExtensionsTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void ShouldRegisterStringValuesXmlConverter()
    {
        var converters = new List<XmlConverter>();
        var returned = converters.AddStringValuesConverter();

        TestOutput.WriteLine($"Registered converters: {converters.Count}");

        Assert.Same(converters, returned);
        Assert.Single(converters);
    }
}

Installing Cuemon.AspNetCore.App gives you one NuGet reference, but the APIs shown above are owned by the referenced packages rather than by the aggregate package itself. Use the aggregate package when you want this ASP.NET Core-focused surface together, and move to the smaller packages directly when you want tighter dependency selection.

Installation

dotnet add package Cuemon.AspNetCore.App

Usage guidance

Choose Cuemon.AspNetCore.App when an application wants the broader Cuemon ASP.NET Core stack available through one package reference, especially when authentication helpers, MVC caching primitives, Razor asset helpers, and JSON or XML formatter integrations all belong in the same solution. If you only need one of those areas, reference the smaller package directly so your project only carries the APIs and dependencies it actually uses.

Family packages