Burgyn / MMLib.SwaggerForOcelot

This repo contains swagger extension for ocelot.
MIT License
354 stars 94 forks source link

A bug when using .NET8-Ocelot[20.*] #282

Closed MateralCMX closed 1 year ago

MateralCMX commented 1 year ago

Using .NET8-Ocelot[20.*] will cause an exception:

Method not found: 'System.Threading.Tasks.Task`1<System.Collections.Generic.List`1<Ocelot.Values.Service>> Ocelot.ServiceDiscovery.Providers.IServiceDiscoveryProvider.Get()'.

The reason is that .NET8-Ocelot[20.*] renamed the [Ocelot.ServiceDiscovery.Providers.IServiceDiscoveryProvider.Get] method to [Ocelot.ServiceDiscovery.Providers.IServiceDiscoveryProvider.GetAsync].

Modify line 97 in the file 'Src\MMLib.SwaggerForOcelot\ServiceDiscovery\SwaggerServiceDiscoveryProvider.cs'

ServiceHostAndPort service = (await serviceProvider.Data.GetAsync()).FirstOrDefault()?.HostAndPort;

to

ServiceHostAndPort service = (await serviceProvider.Data.Get()).FirstOrDefault()?.HostAndPort;

fix the error.

The temporary fix is as follows:

    /// <summary>
    /// In your project, create a new class and implement the interface ISwaggerServiceDiscoveryProvider.
    /// </summary>
    public class MySwaggerServiceDiscoveryProvider : ISwaggerServiceDiscoveryProvider
    {
        private readonly IServiceDiscoveryProviderFactory _serviceDiscovery;
        private readonly IServiceProviderConfigurationCreator _configurationCreator;
        private readonly IOptionsMonitor<FileConfiguration> _options;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IOptions<SwaggerOptions> _swaggerOptions;
        public MySwaggerServiceDiscoveryProvider(
            IServiceDiscoveryProviderFactory serviceDiscovery,
            IServiceProviderConfigurationCreator configurationCreator,
            IOptionsMonitor<FileConfiguration> options,
            IHttpContextAccessor httpContextAccessor,
            IOptions<SwaggerOptions> swaggerOptions)
        {
            _serviceDiscovery = serviceDiscovery;
            _configurationCreator = configurationCreator;
            _options = options;
            _httpContextAccessor = httpContextAccessor;
            _swaggerOptions = swaggerOptions;
        }

        /// <inheritdoc />
        public async Task<Uri> GetSwaggerUriAsync(SwaggerEndPointConfig endPoint, RouteOptions route)
        {
            if (endPoint.Version == "aggregates" || endPoint.Version == "gateway")
            {
                return GetGatewayItSelfSwaggerPath(endPoint);
            }
            else if (!endPoint.Url.IsNullOrEmpty())
            {
                return new Uri(endPoint.Url);
            }
            else
            {
                return await GetSwaggerUri(endPoint, route);
            }
        }

        private Uri GetGatewayItSelfSwaggerPath(SwaggerEndPointConfig endPoint)
        {
            if(_httpContextAccessor.HttpContext is null) throw new InvalidOperationException(GetErrorMessage(endPoint));
            var builder = new UriBuilder(
                _httpContextAccessor.HttpContext.Request.Scheme,
                _httpContextAccessor.HttpContext.Request.Host.Host)
            {
                Path = _swaggerOptions.Value
                    .RouteTemplate.Replace("{documentName}", endPoint.Version).Replace("{json|yaml}", "json")
            };

            if (_httpContextAccessor.HttpContext.Request.Host.Port.HasValue && _httpContextAccessor.HttpContext.Request.Host.Port is not null)
            {
                builder.Port = _httpContextAccessor.HttpContext.Request.Host.Port.Value;
            }

            return builder.Uri;
        }

        private async Task<Uri> GetSwaggerUri(SwaggerEndPointConfig endPoint, RouteOptions route)
        {
            var conf = _configurationCreator.Create(_options.CurrentValue.GlobalConfiguration);

            var downstreamRoute = new DownstreamRouteBuilder()
                .WithUseServiceDiscovery(true)
                .WithServiceName(endPoint.Service.Name)
                .WithServiceNamespace(route?.ServiceNamespace)
                .Build();

            Response<IServiceDiscoveryProvider> serviceProvider = _serviceDiscovery.Get(conf, downstreamRoute);

            if (serviceProvider.IsError)
            {
                throw new InvalidOperationException(GetErrorMessage(endPoint));
            }

            ServiceHostAndPort? service = ((await serviceProvider.Data.GetAsync()).FirstOrDefault()?.HostAndPort) ?? throw new InvalidOperationException(GetErrorMessage(endPoint));
            var builder = new UriBuilder(GetScheme(service, route), service.DownstreamHost, service.DownstreamPort)
            {
                Path = endPoint.Service.Path
            };
            return builder.Uri;
        }

        private string GetScheme(ServiceHostAndPort service, RouteOptions route)
            => (route != null && !route.DownstreamScheme.IsNullOrEmpty())
            ? route.DownstreamScheme
            : !service.Scheme.IsNullOrEmpty()
            ? service.Scheme
            : service.DownstreamPort
            switch
            {
                443 => Uri.UriSchemeHttps,
                80 => Uri.UriSchemeHttp,
                _ => string.Empty,
            };

        private static string GetErrorMessage(SwaggerEndPointConfig endPoint) => $"Service with swagger documentation '{endPoint.Service.Name}' cann't be discovered";
    }
services.AddSwaggerForOcelot(builder.Configuration);
//Add below the injection of MMLib.SwaggerForOcelot.
services.Replace(new ServiceDescriptor(typeof(ISwaggerServiceDiscoveryProvider), typeof(MySwaggerServiceDiscoveryProvider), ServiceLifetime.Transient));