diff --git a/src/Application/Program.cs b/src/Application/Program.cs
index 45a74ae9e..8f5d2c53f 100644
--- a/src/Application/Program.cs
+++ b/src/Application/Program.cs
@@ -5,6 +5,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Yarp.ReverseProxy.Configuration;
// Load configuration
@@ -26,13 +27,12 @@
// Configure YARP
builder.AddServiceDefaults();
-builder.Services.AddServiceDiscovery();
+builder.Services.AddServiceDiscovery()
+ .AddOutputCache(builder.Configuration.GetSection("OutputCache"));
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddServiceDiscoveryDestinationResolver();
-Console.WriteLine(builder.Configuration.GetSection("ReverseProxy").Value);
-
var app = builder.Build();
app.MapReverseProxy();
diff --git a/src/Application/Yarp.Application.csproj b/src/Application/Yarp.Application.csproj
index f29c6450d..949706e02 100644
--- a/src/Application/Yarp.Application.csproj
+++ b/src/Application/Yarp.Application.csproj
@@ -7,13 +7,13 @@
enable
enable
yarp
+ true
-
@@ -25,4 +25,8 @@
+
+
+
+
diff --git a/src/ReverseProxy/Configuration/Middlewares/OutputCacheConfig.cs b/src/ReverseProxy/Configuration/Middlewares/OutputCacheConfig.cs
new file mode 100644
index 000000000..88638c809
--- /dev/null
+++ b/src/ReverseProxy/Configuration/Middlewares/OutputCacheConfig.cs
@@ -0,0 +1,122 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.OutputCaching;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Yarp.ReverseProxy.Configuration;
+
+///
+/// Configuration for
+///
+public sealed record OutputCacheConfig
+{
+ ///
+ public long SizeLimit { get; set; } = 100 * 1024 * 1024;
+
+ ///
+ public long MaximumBodySize { get; set; } = 64 * 1024 * 1024;
+
+ ///
+ public TimeSpan DefaultExpirationTimeSpan { get; set; } = TimeSpan.FromSeconds(60);
+
+ ///
+ public bool UseCaseSensitivePaths { get; set; }
+
+ ///
+ /// Policies that will be added with
+ ///
+ public IDictionary NamedPolicies { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+}
+
+///
+/// Configuration for
+///
+public sealed record NamedCacheConfig
+{
+ ///
+ /// Flag to exclude or not the default policy
+ ///
+ public bool ExcludeDefaultPolicy { get; set; }
+
+ ///
+ public TimeSpan? ExpirationTimeSpan { get; set; }
+
+ ///
+ public bool NoCache { get; set; }
+
+ ///
+ public string[]? VaryByQueryKeys { get; set; }
+
+ ///
+ public string[]? VaryByHeaders { get; set; }
+}
+
+///
+/// Collections of extensions to configure OutputCache
+///
+public static class OutputCacheConfigExtensions
+{
+ ///
+ /// Add and configure OuputCache
+ ///
+ public static IServiceCollection AddOutputCache(this IServiceCollection services, IConfiguration config)
+ {
+ if (config == null)
+ {
+ return services;
+ }
+
+ var outputCacheConfig = config.Get();
+
+ if (outputCacheConfig != null)
+ {
+ services.AddOutputCache(outputCacheConfig);
+ }
+
+ return services;
+ }
+
+ ///
+ /// Add and configure OuputCache
+ ///
+ public static IServiceCollection AddOutputCache(this IServiceCollection services, OutputCacheConfig config)
+ {
+ return services.AddOutputCache(options =>
+ {
+ options.SizeLimit = config.SizeLimit;
+ options.MaximumBodySize = config.MaximumBodySize;
+ options.DefaultExpirationTimeSpan = config.DefaultExpirationTimeSpan;
+ options.UseCaseSensitivePaths = config.UseCaseSensitivePaths;
+
+ foreach (var policy in config.NamedPolicies)
+ {
+ options.AddPolicy(policy.Key,
+ builder => PolicyBuilder(builder, policy.Value),
+ policy.Value.ExcludeDefaultPolicy);
+ }
+ });
+ }
+
+ private static void PolicyBuilder(OutputCachePolicyBuilder builder, NamedCacheConfig policy)
+ {
+ if (policy.ExpirationTimeSpan.HasValue)
+ builder.Expire(policy.ExpirationTimeSpan.Value);
+
+ if (policy.NoCache)
+ builder.NoCache();
+
+ if (policy.VaryByQueryKeys != null)
+ builder.SetVaryByQuery(policy.VaryByQueryKeys);
+
+ if (policy.VaryByHeaders != null)
+ builder.SetVaryByHeader(policy.VaryByHeaders);
+ }
+}
diff --git a/src/ReverseProxy/Yarp.ReverseProxy.csproj b/src/ReverseProxy/Yarp.ReverseProxy.csproj
index eab27bee7..81e3615f6 100644
--- a/src/ReverseProxy/Yarp.ReverseProxy.csproj
+++ b/src/ReverseProxy/Yarp.ReverseProxy.csproj
@@ -10,6 +10,8 @@
true
README.md
yarp;dotnet;reverse-proxy;aspnetcore
+ true
+ $(InterceptorsNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration
diff --git a/test/ReverseProxy.Tests/Configuration/OutputCacheConfigTests.cs b/test/ReverseProxy.Tests/Configuration/OutputCacheConfigTests.cs
new file mode 100644
index 000000000..575584f02
--- /dev/null
+++ b/test/ReverseProxy.Tests/Configuration/OutputCacheConfigTests.cs
@@ -0,0 +1,93 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Yarp.ReverseProxy.Configuration;
+
+public class OutputCacheConfigTests
+{
+ [Fact]
+ public async Task All_Options_Added()
+ {
+ var config = new OutputCacheConfig();
+ config.DefaultExpirationTimeSpan = TimeSpan.FromSeconds(1);
+ config.MaximumBodySize = 10;
+ config.SizeLimit = 20;
+ config.UseCaseSensitivePaths = true;
+ config.NamedPolicies.Add("test1", new NamedCacheConfig { ExpirationTimeSpan = TimeSpan.FromSeconds(5), ExcludeDefaultPolicy = true });
+ config.NamedPolicies.Add("test2", new NamedCacheConfig { ExpirationTimeSpan = TimeSpan.FromSeconds(15), ExcludeDefaultPolicy = false });
+ config.NamedPolicies.Add("test3", new NamedCacheConfig { ExpirationTimeSpan = TimeSpan.FromSeconds(3), ExcludeDefaultPolicy = true, VaryByHeaders = new[] { "X-SomeHeader" } });
+
+ var builder = WebApplication.CreateBuilder();
+ builder.Services.AddOutputCache(config)
+ .AddReverseProxy();
+
+ var app = builder.Build();
+
+ var policies = app.Services.GetRequiredService();
+ var test1 = await policies.GetPolicyAsync("test1");
+ var test2 = await policies.GetPolicyAsync("test2");
+ var test3 = await policies.GetPolicyAsync("test3");
+
+ Assert.NotNull(test1);
+ Assert.NotNull(test2);
+ Assert.NotNull(test3);
+ }
+
+ [Fact]
+ public async Task All_Options_Added_Json()
+ {
+ var json =
+ """
+ {
+ "OutputCache": {
+ "DefaultExpirationTimeSpan": "00:05:00",
+ "MaximumBodySize": 10,
+ "SizeLimit": 20,
+ "UseCaseSensitivePaths": true,
+ "NamedPolicies": {
+ "test1": {
+ "ExpirationTimeSpan": "00:05:00",
+ "ExcludeDefaultPolicy": true
+ },
+ "test2": {
+ "ExpirationTimeSpan": "00:15:00",
+ "ExcludeDefaultPolicy": false
+ },
+ "test3": {
+ "ExpirationTimeSpan": "00:03:00",
+ "ExcludeDefaultPolicy": true,
+ "VaryByHeaders": [ "X-SomeHeader" ]
+ }
+ }
+ }
+ }
+ """;
+ var configBuilder = new ConfigurationBuilder();
+ var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
+ var config = configBuilder.AddJsonStream(stream).Build();
+
+ var builder = WebApplication.CreateBuilder();
+ builder.Services.AddOutputCache(config.GetSection("OutputCache"))
+ .AddReverseProxy();
+
+ var app = builder.Build();
+
+ var policies = app.Services.GetRequiredService();
+ var test1 = await policies.GetPolicyAsync("test1");
+ var test2 = await policies.GetPolicyAsync("test2");
+ var test3 = await policies.GetPolicyAsync("test3");
+
+ Assert.NotNull(test1);
+ Assert.NotNull(test2);
+ Assert.NotNull(test3);
+ }
+}