Activities of "dhaoo"

Answer

Must it be of svg type

Thanks for your reply.

Answer

hi

You said: I find that yarp will automatically update swagger when the configuration file is changed?

Does the abp template project not update swagger automatically?

If so. Can you share a sample code of yarp that works? I will compare the code with the abp template project.

I checked the document https://microsoft.github.io/reverse-proxy/articles/config-providers.html

abp will always set reloadOnChange to ture for appsettings.json.

Thanks.

The code I shared above is the code in my current project, is reloadOnChange only for appsettings.json? I changed the yarp configuration file from yarp.json to memory in the code, I put in my middleware yarp configuration through the httpContext. RequestServices. GetRequiredService < InMemoryConfigProvider > (), Update (routes, clusters); Method was assigned and I checked that IProxyConfig had a value, but swagger didn't refresh. Do you know why

Answer

I am in configProvider.Update(routes, clusters); Then check that the value of ProxyConfig in IProxyConfigProvider has been attached

Answer
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using LCFC.MOM.Shared.Hosting.AspNetCore;
using LCFC.MOM.Shared.Hosting.Gateways;
using Swashbuckle.AspNetCore.SwaggerUI;
using Volo.Abp;
using Volo.Abp.Modularity;
using Yarp.ReverseProxy.Configuration;
using Consul;
using Autofac.Core;

namespace LCFC.MOM.WebGateway;

[DependsOn(
    typeof(MOMSharedHostingGatewaysModule)
)]
public class MOMWebGatewayModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        // Enable if you need hosting environment
        // var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();
        var hostingEnvironment = context.Services.GetHostingEnvironment();

        //context.Services.AddSingleton<ISwaggerConfigHelper, SwaggerConfigHelper>();
       
        context.Services.AddSingleton<IConsulClient>(provider =>
        {
            var consulAddress = configuration["Consul:Address"] ?? "http://localhost:8500"; // 默认 Consul 地址
            return new ConsulClient(config => config.Address = new Uri(consulAddress));
        });
        UpdateRoutesFromConsul("http://localhost:8500", context);
        var consulClient = context.Services.BuildServiceProvider().GetRequiredService<IConsulClient>();
        SwaggerConfigurationHelper
            .ConfigureWithOidc(
                context: context,
                authority: configuration["AuthServer:Authority"]!,
                scopes: consulClient.Agent.Services().Result.Response.Select(i=>i.Value.Service+"Service").ToArray(),
                apiTitle: "Web Gateway API",
                discoveryEndpoint: configuration["AuthServer:MetadataAddress"]
            );
        //context.Services.AddSingleton<IProxyConfigProvider, ProxyConfigProvider>();
        // context.Services.AddSingleton<ProxyConfigProvider>();
        // context.Services.AddSingleton<IProxyConfigProvider>(provider => provider.GetRequiredService<ProxyConfigProvider>());
        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.Trim().RemovePostFix("/"))
                            .ToArray() ?? Array.Empty<string>()
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });
        //context.Services.AddHostedService<ConsulServiceWatcher>();
    }
    private List<AgentService> GetServicesFromConsul(string serviceName,ServiceConfigurationContext context)
    {
        var services = new List<AgentService>();
        var result = context.Services.BuildServiceProvider().GetRequiredService<IConsulClient>()
            .Agent.Services().Result.Response;

        foreach (var service in result)
        {
            services.Add(service.Value);
        }

        return services;
    }
    public void UpdateRoutesFromConsul(string serviceName,ServiceConfigurationContext context)
    {
        var services = GetServicesFromConsul(serviceName,context);

        var routes = new List<Yarp.ReverseProxy.Configuration.RouteConfig>();
        var clusters = new List<ClusterConfig>();

        foreach (var service in services)
        {
            // 更新 YARP 路由配置
            routes.Add(new Yarp.ReverseProxy.Configuration. RouteConfig
            {
                RouteId = service.Service,
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/api/{service.Service}-service/{{**catch-all}}" }
            });
            routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
            {
                RouteId = service.Service+ "Swagger",
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/swagger-json/{service.Service}/swagger/v1/swagger.json" },
                Transforms=new List<IReadOnlyDictionary<string, string>>()
                {
                    new Dictionary<string, string>
                    {
                        { "PathRemovePrefix", $"/swagger-json/{service.Service}" }
                    }
                }
            });
            // 更新 YARP 集群配置
            clusters.Add(new ClusterConfig
            {
                ClusterId = service.Service,
                Destinations = new Dictionary<string, Yarp.ReverseProxy.Configuration.DestinationConfig>
                {
                    { service.Service, new Yarp.ReverseProxy.Configuration.DestinationConfig { Address = $"{service.Address}:{service.Port}/" } }
                }
            });
        }
        context.Services.AddReverseProxy()
             .LoadFromMemory(routes,clusters);
        // 更新代理配置
        //_proxyConfig.UpdateConfig(routes, clusters);
    }
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();
        var configuration = context.GetConfiguration();
        app.UseMiddleware<ConsulServiceWatcher>(); // 注册自定义中间件
        var proxyConfig = app.ApplicationServices.GetRequiredService<IProxyConfigProvider>().GetConfig();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseCorrelationId();
        app.UseStaticFiles();
        app.UseCors();
        app.UseRouting();
        app.UseAuthorization();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options => { ConfigureSwaggerUI(proxyConfig, options, configuration); });
        app.UseRewriter(CreateSwaggerRewriteOptions());
        app.UseAbpSerilogEnrichers();
        app.UseEndpoints(endpoints => endpoints.MapReverseProxy());

    }

    private static void ConfigureSwaggerUI(
        IProxyConfig proxyConfig,
        SwaggerUIOptions options,
        IConfiguration configuration)
    {
        foreach (var cluster in proxyConfig.Clusters)
        {
            options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json",
                $"{cluster.ClusterId} API");
        }

        options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
        options.OAuthScopes(proxyConfig.Clusters.Select(i=>i.ClusterId+"Service").ToArray()
        );
    }

    private static RewriteOptions CreateSwaggerRewriteOptions()
    {
        var rewriteOptions = new RewriteOptions();
        rewriteOptions.AddRedirect("^(|\\|\\s+)$", "/swagger"); // Regex for "/" and "" (whitespace)
        return rewriteOptions;
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using LCFC.MOM.Shared.Hosting.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
using Volo.Abp.Modularity;
using Yarp.ReverseProxy.Configuration;

namespace LCFC.MOM.WebGateway;

public class ConsulServiceWatcher 
{
    private readonly IConsulClient _consulClient;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private List<AgentService> _lastServices = new List<AgentService>();
    private Timer _timer;
    private IConfiguration _configuration;
    private readonly RequestDelegate _next;
    private readonly InMemoryConfigProvider _inMemoryConfigProvider;
    public ConsulServiceWatcher(RequestDelegate next,IConsulClient consulClient, 
        IConfiguration configuration,IHttpContextAccessor httpContextAccessor)
    {
        _consulClient = consulClient;
        _httpContextAccessor = httpContextAccessor;
        _next = next;
        _configuration = configuration;
    }
    private void UpdateServices(object state)
    {
        // 获取 Consul 中的服务列表
        var services = GetServicesFromConsul();

        // 更新代理配置
        if (!services.Select(i=>i.Service).SequenceEqual(_lastServices.Select(i=>i.Service)))
        {
            // 如果服务有变化,更新路由
            _lastServices = services;
            UpdateRoutesFromConsul(services);
        }
    }
    public async Task InvokeAsync(HttpContext context)
    {
        // 初始化服务监控
        // 每隔 10 秒更新一次服务配置
        //_timer = new Timer(UpdateServices, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
        UpdateServices(null);
        await _next(context);
    }

    private void UpdateRoutesFromConsul(List<AgentService> services)
    {
        var routes = new List<Yarp.ReverseProxy.Configuration.RouteConfig>();
        var clusters = new List<ClusterConfig>();

        foreach (var service in services)
        {
            // 更新 YARP 路由配置
            routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
            {
                RouteId = service.Service,
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/api/{service.Service}-service/{{**catch-all}}" }
            });

            routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
            {
                RouteId = service.Service + "Swagger",
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/swagger-json/{service.Service}/swagger/v1/swagger.json" },
                Transforms = new List<IReadOnlyDictionary<string, string>>()
                {
                    new Dictionary<string, string>
                    {
                        { "PathRemovePrefix", $"/swagger-json/{service.Service}" }
                    }
                }
            });

            clusters.Add(new ClusterConfig
            {
                ClusterId = service.Service,
                Destinations = new Dictionary<string, Yarp.ReverseProxy.Configuration.DestinationConfig>
                {
                    { service.Service, new Yarp.ReverseProxy.Configuration.DestinationConfig { Address = $"{service.Address}:{service.Port}/" } }
                }
            });
        }
        
        var configProvider = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<InMemoryConfigProvider>();
        configProvider.Update(routes, clusters);
        //configProvider.GetConfig()
        // 手动触发 ChangeToken

        var updatedConfig =_httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IProxyConfigProvider>().GetConfig();
        //UpdateOAuthScopes(services.Select(i=>i.Service+"Service").ToArray());
        //UpdateSwaggerUI(updatedConfig);
    }
    private List<AgentService> GetServicesFromConsul()
    {
        var services = new List<AgentService>();
        var result = _consulClient.Agent.Services().Result.Response;

        foreach (var service in result)
        {
            services.Add(service.Value);
        }

        return services;
    }
    private void UpdateSwaggerUI(IProxyConfig proxyConfig)
    {
        if (proxyConfig.Clusters.Count == 0)
        {
            return;
        }
        // 动态更新 Swagger UI
        var app = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IApplicationBuilder>();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            foreach (var cluster in proxyConfig.Clusters)
            {
                options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json", $"{cluster.ClusterId} API");
            }
        });
    }


}
Answer

hi

This is not abp scope. Microservice uses the yarp, you can check its document.

Thanks.

hi, About yarp, abp uses the default to read the configuration file, but when I debug it, I find that yarp will automatically update swagger when the configuration file is changed. Is abp framework processed or yarp processed automatically? Now that I have changed yarp to use memory, I see when debugging that yarp's cluster has been updated, but swagger is not automatically updated

ok,thanks!

I see, let's name the new view TenantSwitchModal, thank you! Now I want to display the user tenant information in the red-framed picture after the user logs in. I need to rewrite which pages?

I cut the file to the folder as you did and now run an exception: Multiple handlers matched. The following handlers matched route data and had all constraints satisfied. Multiple handlers matched. The following handlers matched route data and had all constraints satisfied

Now I have completed the rewrite, but when I return to the view, I still jump to the view of TenantSwitchModalModel, but I do not jump to the new view. What should I do to overwrite the original view

Showing 11 to 20 of 33 entries
Learn More, Pay Less
33% OFF
All Trainings!
Get Your Deal
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on September 15, 2025, 14:41