private static DocumentFilterContext GetDocumentFilterContext(ApiVersionModel versionModel)
            {
                var actionDescriptor = new ActionDescriptor();

                actionDescriptor.SetProperty(versionModel);

                var apiDescription = new ApiDescription
                {
                    ActionDescriptor = actionDescriptor
                };

                var apiDescriptions = new List <ApiDescription>
                {
                    apiDescription
                };

                var schemaGeneratorOptions = new SchemaGeneratorOptions();

                var jsonSerializerOptions = new JsonSerializerOptions();
                var jsonSerializerDataContractResolver = new JsonSerializerDataContractResolver(jsonSerializerOptions);

                var schemaGenerator = new SchemaGenerator(schemaGeneratorOptions, jsonSerializerDataContractResolver);

                var schemaRepository = new SchemaRepository();

                return(new DocumentFilterContext(apiDescriptions, schemaGenerator, schemaRepository));
            }
Exemplo n.º 2
0
        public void is_deprecated_should_match_model(int majorVersion, int minorVersion, bool expected)
        {
            // arrange
            var apiVersion = new ApiVersion(majorVersion, minorVersion);
            var model      = new ApiVersionModel(
                supportedVersions: new[] { new ApiVersion(1, 0) },
                deprecatedVersions: new[] { new ApiVersion(0, 9) });
            var description = new ApiDescription
            {
                ActionDescriptor = new ActionDescriptor()
                {
                    Properties = { [typeof(ApiVersionModel)] = model },
                },
                Properties =
                {
                    [typeof(ApiVersion)] = apiVersion,
                }
            };

            // act
            var deprecated = description.IsDeprecated();

            // assert
            deprecated.Should().Be(expected);
        }
Exemplo n.º 3
0
        public void api_descriptions_should_ignore_api_for_direct_route_action()
        {
            // arrange
            var configuration = new HttpConfiguration();
            var routeTemplate = "api/values";
            var model         = new ApiVersionModel(new ApiVersion(1, 0));
            var controller    = new HttpControllerDescriptor(configuration, "ApiExplorerValues", typeof(ApiExplorerValuesController));
            var actions       = new ReflectedHttpActionDescriptor[]
            {
                new ReflectedHttpActionDescriptor(controller, typeof(ApiExplorerValuesController).GetMethod("Get"))
                {
                    Properties = { [typeof(ApiVersionModel)] = model },
                },
                new ReflectedHttpActionDescriptor(controller, typeof(ApiExplorerValuesController).GetMethod("Post"))
                {
                    Properties = { [typeof(ApiVersionModel)] = model },
                },
            };

            configuration.Routes.Add("Route", CreateDirectRoute(routeTemplate, actions));

            IApiExplorer apiExplorer = new VersionedApiExplorer(configuration);

            // act
            var descriptions = apiExplorer.ApiDescriptions;

            // assert
            descriptions.Single().Should().Should().BeEquivalentTo(
                new { HttpMethod = Get, RelativePath = routeTemplate },
                options => options.ExcludingMissingMembers());
        }
Exemplo n.º 4
0
        public void api_descriptions_should_recognize_composite_routes()
        {
            // arrange
            var configuration        = new HttpConfiguration();
            var routeTemplate        = "api/values";
            var model                = new ApiVersionModel(new ApiVersion(1, 0));
            var controllerDescriptor = new HttpControllerDescriptor(configuration, "AttributeApiExplorerValues", typeof(AttributeApiExplorerValuesController));
            var action               = new ReflectedHttpActionDescriptor(controllerDescriptor, typeof(AttributeApiExplorerValuesController).GetMethod("Action"))
            {
                Properties = { [typeof(ApiVersionModel)] = model },
            };
            var actions         = new ReflectedHttpActionDescriptor[] { action };
            var routeCollection = new List <IHttpRoute>()
            {
                CreateDirectRoute(routeTemplate, actions)
            };
            var route = NewRouteCollectionRoute();

            route.EnsureInitialized(() => routeCollection);
            configuration.Routes.Add("Route", route);

            IApiExplorer apiExplorer = new VersionedApiExplorer(configuration);

            // act
            var descriptions = apiExplorer.ApiDescriptions;

            // assert
            descriptions.Single().Should().Should().BeEquivalentTo(
                new { HttpMethod = Get, RelativePath = routeTemplate, ActionDescriptor = action },
                options => options.ExcludingMissingMembers());
        }
Exemplo n.º 5
0
        /// <summary>
        /// Applies the filter to the specified operation using the given context.
        /// </summary>
        /// <param name="operation">The operation to apply the filter to.</param>
        /// <param name="context">The current operation filter context.</param>
        public void Apply(OpenApiOperation operation,
                          OperationFilterContext context)
        {
            ApiDescription  apiDescription = context.ApiDescription;
            ApiVersion      apiVersion     = apiDescription.GetApiVersion();
            ApiVersionModel model          = apiDescription.ActionDescriptor.GetApiVersionModel(Explicit | Implicit);

            operation.Deprecated = model.DeprecatedApiVersions.Contains(apiVersion);

            if (operation.Parameters == null)
            {
                return;
            }

            foreach (OpenApiParameter parameter in operation.Parameters)
            {
                ApiParameterDescription description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);

                if (parameter.Description == null)
                {
                    parameter.Description = description.ModelMetadata?.Description;
                }

                parameter.Required |= description.IsRequired;
            }
        }
Exemplo n.º 6
0
        void ApplyInheritedActionConventions(IReadOnlyList <HttpActionDescriptor> actions)
        {
            var noInheritedApiVersions = SupportedVersions.Count == 0 && DeprecatedVersions.Count == 0 && AdvertisedVersions.Count == 0 && DeprecatedAdvertisedVersions.Count == 0;

            if (noInheritedApiVersions)
            {
                return;
            }

            for (var i = 0; i < actions.Count; i++)
            {
                var action = actions[i];
                var model  = action.GetProperty <ApiVersionModel>();

                if (model == null || model.IsApiVersionNeutral)
                {
                    continue;
                }

                var supportedVersions  = model.SupportedApiVersions.Union(SupportedVersions);
                var deprecatedVersions = model.DeprecatedApiVersions.Union(DeprecatedVersions);

                model = new ApiVersionModel(
                    declaredVersions: model.DeclaredApiVersions,
                    supportedVersions,
                    deprecatedVersions,
                    AdvertisedVersions,
                    DeprecatedAdvertisedVersions);

                action.SetProperty(model);
            }
        }
        /// <summary>
        /// Aggregates the current version information with other version information.
        /// </summary>
        /// <param name="version">The <see cref="ApiVersionModel">API version information</see> that is the basis
        /// of the aggregation.</param>
        /// <param name="otherVersions">A <see cref="IEnumerable{T}">sequence</see> of other
        /// <see cref="ApiVersionModel">API version information</see> to aggregate.</param>
        /// <returns>A new <see cref="ApiVersionModel"/> that is the aggregated result of the
        /// <paramref name="otherVersions">other version information</paramref> and the current version information.</returns>
        public static ApiVersionModel Aggregate(this ApiVersionModel version, IEnumerable <ApiVersionModel> otherVersions)
        {
            if (version == null)
            {
                throw new ArgumentNullException(nameof(version));
            }

            if (otherVersions == null)
            {
                throw new ArgumentNullException(nameof(otherVersions));
            }

            var implemented = new HashSet <ApiVersion>();
            var supported   = new HashSet <ApiVersion>();
            var deprecated  = new HashSet <ApiVersion>();

            implemented.UnionWith(version.ImplementedApiVersions);
            supported.UnionWith(version.SupportedApiVersions);
            deprecated.UnionWith(version.DeprecatedApiVersions);

            foreach (var otherVersion in otherVersions)
            {
                implemented.UnionWith(otherVersion.ImplementedApiVersions);
                supported.UnionWith(otherVersion.SupportedApiVersions);
                deprecated.UnionWith(otherVersion.DeprecatedApiVersions);
            }

            deprecated.ExceptWith(supported);

            return(new ApiVersionModel(version, implemented.ToSortedReadOnlyList(), supported.ToSortedReadOnlyList(), deprecated.ToSortedReadOnlyList()));
        }
        public async Task on_action_executing_should_add_version_headers()
        {
            // arrange
            var supported       = new[] { new ApiVersion(1, 0), new ApiVersion(2, 0) };
            var deprecated      = new[] { new ApiVersion(0, 5) };
            var model           = new ApiVersionModel(supported, deprecated);
            var onStartResponse = new List <(Func <object, Task>, object)>();
            var context         = CreateContext(model, onStartResponse);
            var attribute       = new ReportApiVersionsAttribute();

            // act
            attribute.OnActionExecuting(context);

            for (var i = 0; i < onStartResponse.Count; i++)
            {
                var(callback, state) = onStartResponse[i];
                await callback(state);
            }

            // assert
            var headers = context.HttpContext.Response.Headers;

            headers["api-supported-versions"].Single().Should().Be("1.0, 2.0");
            headers["api-deprecated-versions"].Single().Should().Be("0.5");
        }
Exemplo n.º 9
0
        /// <summary>
        /// Selects the controller for OData requests.
        /// </summary>
        /// <param name="odataPath">The OData path.</param>
        /// <param name="request">The request.</param>
        /// <returns>The name of the selected controller or <c>null</c> if the request isn't handled by this convention.</returns>
        public virtual string?SelectController(ODataPath odataPath, HttpRequestMessage request)
        {
            if (odataPath == null)
            {
                throw new ArgumentNullException(nameof(odataPath));
            }

            if (odataPath.PathTemplate != "~" && odataPath.PathTemplate != "~/$metadata")
            {
                return(null);
            }

            var properties = request.ApiVersionProperties();

            // the service document and metadata endpoints are special, but they are not neutral. if the client doesn't
            // specify a version, they may not know to. assume a default version by policy, but it's always allowed.
            // a client might also send an OPTIONS request to determine which versions are available (ex: tooling)
            if (string.IsNullOrEmpty(properties.RawRequestedApiVersion))
            {
                var modelSelector   = request.GetRequestContainer().GetRequiredService <IEdmModelSelector>();
                var versionSelector = request.GetApiVersioningOptions().ApiVersionSelector;
                var model           = new ApiVersionModel(modelSelector.ApiVersions, Enumerable.Empty <ApiVersion>());

                properties.RequestedApiVersion = versionSelector.SelectVersion(request, model);
            }

            return("VersionedMetadata");
        }
        public void is_deprecated_should_return_expected_result_for_deprecated_version(int majorVersion, bool expected)
        {
            // arrange
            var provider = new DefaultApiVersionDescriptionProvider(
                new Mock <IActionDescriptorCollectionProvider>().Object,
                new Mock <IApiVersionGroupNameFormatter>().Object,
                new OptionsWrapper <ApiVersioningOptions>(new ApiVersioningOptions()));
            var action     = new ActionDescriptor();
            var controller = new ControllerModel(typeof(Controller).GetTypeInfo(), new object[0]);
            var model      = new ApiVersionModel(
                declaredVersions: new[] { new ApiVersion(1, 0), new ApiVersion(2, 0) },
                supportedVersions: new[] { new ApiVersion(2, 0) },
                deprecatedVersions: new[] { new ApiVersion(1, 0) },
                advertisedVersions: Empty <ApiVersion>(),
                deprecatedAdvertisedVersions: Empty <ApiVersion>());

            controller.SetProperty(model);
            action.SetProperty(controller);

            // act
            var result = provider.IsDeprecated(action, new ApiVersion(majorVersion, 0));

            // assert
            result.Should().Be(expected);
        }
        public void aggregate_should_merge_api_version_info_sequence()
        {
            // arrange
            var model       = new ApiVersionModel(new[] { new ApiVersion(1, 0) }, new[] { new ApiVersion(0, 9) });
            var otherModels = new[]
            {
                new ApiVersionModel(new[] { new ApiVersion(2, 0) }, Enumerable.Empty <ApiVersion>()),
                new ApiVersionModel(new[] { new ApiVersion(3, 0) }, new[] { new ApiVersion(3, 0, "Alpha") })
            };
            var expected = new ApiVersionModel(
                new[] { new ApiVersion(1, 0), new ApiVersion(2, 0), new ApiVersion(3, 0) },
                new[] { new ApiVersion(0, 9), new ApiVersion(3, 0, "Alpha") });

            // act
            var aggregatedModel = model.Aggregate(otherModels);

            // assert
            aggregatedModel.Should().BeEquivalentTo(
                new
            {
                expected.IsApiVersionNeutral,
                expected.DeclaredApiVersions,
                expected.ImplementedApiVersions,
                expected.SupportedApiVersions,
                expected.DeprecatedApiVersions
            });
        }
Exemplo n.º 12
0
        public ApiVersion SelectVersion(HttpRequest request, ApiVersionModel model)
        {
            Random     random       = new Random();
            int        randomNumber = random.Next(0, model.SupportedApiVersions.Count());
            ApiVersion version      = model.SupportedApiVersions[randomNumber];

            return(version);
        }
Exemplo n.º 13
0
        /// <summary>
        /// Aggregates the current version information with other version information.
        /// </summary>
        /// <param name="version">The <see cref="ApiVersionModel">API version information</see> that is the basis
        /// of the aggregation.</param>
        /// <param name="otherVersion">The other <see cref="ApiVersionModel">API version information</see> to aggregate.</param>
        /// <returns>A new <see cref="ApiVersionModel"/> that is the aggregated result of the
        /// <paramref name="otherVersion">other version information</paramref> and the current version information.</returns>
        public static ApiVersionModel Aggregate(this ApiVersionModel version, ApiVersionModel otherVersion)
        {
            Arg.NotNull(version, nameof(version));
            Arg.NotNull(otherVersion, nameof(otherVersion));
            Contract.Ensures(Contract.Result <ApiVersionModel>() != null);

            return(version.Aggregate(new[] { otherVersion }));
        }
Exemplo n.º 14
0
        public void Report(HttpResponseHeaders headers, ApiVersionModel apiVersionModel)
        {
            Arg.NotNull(headers, nameof(headers));
            Arg.NotNull(apiVersionModel, nameof(apiVersionModel));

            AddApiVersionHeader(headers, ApiSupportedVersions, apiVersionModel.SupportedApiVersions);
            AddApiVersionHeader(headers, ApiDeprecatedVersions, apiVersionModel.DeprecatedApiVersions);
        }
        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary <string, object> values, HttpRouteDirection routeDirection)
        {
            if (routeDirection == UriGeneration)
            {
                return(true);
            }

            if (!MatchAnyVersion && apiVersion != request.GetRequestedApiVersion())
            {
                return(false);
            }

            var properties = request.ApiVersionProperties();

            // determine whether this constraint can match any api version and no api version has otherwise been matched
            if (MatchAnyVersion && properties.RequestedApiVersion == null)
            {
                var options = request.GetApiVersioningOptions();

                // is implicitly matching an api version allowed?
                if (options.AssumeDefaultVersionWhenUnspecified || IsServiceDocumentOrMetadataRoute(values))
                {
                    var odata               = request.ODataApiVersionProperties();
                    var model               = new ApiVersionModel(odata.MatchingRoutes.Keys, Enumerable.Empty <ApiVersion>());
                    var selector            = options.ApiVersionSelector;
                    var requestedApiVersion = properties.RequestedApiVersion = selector.SelectVersion(request, model);

                    // if an api version is selected, determine if it corresponds to a route that has been previously matched
                    if (requestedApiVersion != null && odata.MatchingRoutes.TryGetValue(requestedApiVersion, out var routeName))
                    {
                        // create a new versioned path constraint on the fly and evaluate it. this sets up the underlying odata
                        // infrastructure such as the container, edm, etc. this has no bearing the action selector which will
                        // already select the correct action. without this the response may be incorrect, even if the correct
                        // action is selected and executed.
                        var constraint = new VersionedODataPathRouteConstraint(routeName, requestedApiVersion);
                        return(constraint.Match(request, route, parameterName, values, routeDirection));
                    }
                }
            }
            else if (!MatchAnyVersion && properties.RequestedApiVersion != apiVersion)
            {
                return(false);
            }

            request.DeleteRequestContainer(true);

            // by evaluating the remaining unversioned constraints, this will ultimately determine whether 400 or 404
            // is returned for an odata request
            foreach (var constraint in innerConstraints)
            {
                if (constraint.Match(request, route, parameterName, values, routeDirection))
                {
                    return(true);
                }
            }

            return(false);
        }
        void ApplyControllerConventions(HttpControllerDescriptor controllerDescriptor)
        {
            Contract.Requires(controllerDescriptor != null);

            MergeAttributesWithConventions(controllerDescriptor);
            var model = new ApiVersionModel(VersionNeutral, supportedVersions, deprecatedVersions, advertisedVersions, deprecatedAdvertisedVersions);

            controllerDescriptor.SetConventionsApiVersionModel(model);
        }
Exemplo n.º 17
0
        public ApiEntityListPage <ApiVersionModel> Get()
        {
            ApiVersionModel v1 = new ApiVersionModel("v1", true, true);

            ApiEntityListPage <ApiVersionModel> result =
                new ApiEntityListPage <ApiVersionModel>(new List <ApiVersionModel> {
                v1
            },
                                                        HttpContext.Request.Path.ToString());

            return(result);
        }
Exemplo n.º 18
0
        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (routeDirection == UrlGeneration)
            {
                return(true);
            }

            var feature = httpContext.Features.Get <IApiVersioningFeature>();

            // determine whether this constraint can match any api version and no api version has otherwise been matched
            if (MatchAnyVersion && feature.RequestedApiVersion == null)
            {
                var options = httpContext.RequestServices.GetRequiredService <IOptions <ApiVersioningOptions> >().Value;

                // is implicitly matching an api version allowed?
                if (options.AssumeDefaultVersionWhenUnspecified || IsServiceDocumentOrMetadataRoute(values))
                {
                    var odata               = httpContext.ODataVersioningFeature();
                    var model               = new ApiVersionModel(odata.MatchingRoutes.Keys, Array.Empty <ApiVersion>());
                    var selector            = httpContext.RequestServices.GetRequiredService <IApiVersionSelector>();
                    var requestedApiVersion = feature.RequestedApiVersion = selector.SelectVersion(httpContext.Request, model);

                    // if an api version is selected, determine if it corresponds to a route that has been previously matched
                    if (requestedApiVersion != null && odata.MatchingRoutes.TryGetValue(requestedApiVersion, out var routeName))
                    {
                        // create a new versioned path constraint on the fly and evaluate it. this sets up the underlying odata
                        // infrastructure such as the container, edm, etc. this has no bearing the action selector which will
                        // already select the correct action. without this the response may be incorrect, even if the correct
                        // action is selected and executed.
                        var constraint = new VersionedODataPathRouteConstraint(routeName, requestedApiVersion);
                        return(constraint.Match(httpContext, route, routeKey, values, routeDirection));
                    }
                }
            }
            else if (!MatchAnyVersion && feature.RequestedApiVersion != apiVersion)
            {
                return(false);
            }

            httpContext.Request.DeleteRequestContainer(true);

            // by evaluating the remaining unversioned constraints, this will ultimately determine whether 400 or 404
            // is returned for an odata request
            foreach (var constraint in innerConstraints)
            {
                if (constraint.Match(httpContext, route, routeKey, values, routeDirection))
                {
                    return(true);
                }
            }

            return(false);
        }
        private ControllerVersionInfo ApplyControllerConventions(ControllerModel controllerModel)
        {
            Contract.Requires(controllerModel != null);
            Contract.Ensures(Contract.Result <ControllerVersionInfo>() != null);

            MergeAttributesWithConventions(controllerModel);

            var model = new ApiVersionModel(VersionNeutral, supportedVersions, deprecatedVersions, advertisedVersions, deprecatedAdvertisedVersions);

            controllerModel.SetProperty(model);

            return(new ControllerVersionInfo(supportedVersions, deprecatedVersions, advertisedVersions, deprecatedAdvertisedVersions));
        }
Exemplo n.º 20
0
        public void action_should_be_mapped_to_specific_api_version()
        {
            // arrange
            var action  = new ActionDescriptor();
            var version = new ApiVersion(42, 0);
            var model   = new ApiVersionModel(version);

            // act
            action.SetProperty(model);

            // assert
            action.IsMappedTo(version).Should().BeTrue();
        }
Exemplo n.º 21
0
        public void action_should_be_api_versionX2Dneutral_with_a_versioned_model()
        {
            // arrange
            var action = new ActionDescriptor();
            var model  = new ApiVersionModel(new ApiVersion(42, 0));

            action.SetProperty(model);

            // act
            var result = action.IsApiVersionNeutral();

            // assert
            result.Should().BeFalse();
        }
        static ActionExecutedContext CreateContext(ApiVersionModel model)
        {
            var headers       = new HeaderDictionary();
            var response      = new Mock <HttpResponse>();
            var httpContext   = new Mock <HttpContext>();
            var action        = new ActionDescriptor();
            var actionContext = new ActionContext(httpContext.Object, new RouteData(), action);

            response.SetupGet(r => r.Headers).Returns(headers);
            httpContext.SetupGet(c => c.Response).Returns(response.Object);
            action.SetProperty(model);

            return(new ActionExecutedContext(actionContext, new IFilterMetadata[0], null));
        }
Exemplo n.º 23
0
        public virtual ApiVersion SelectVersion(HttpRequest request, ApiVersionModel model)
        {
            switch (model.ImplementedApiVersions.Count)
            {
            case 0:
                return(options.DefaultApiVersion);

            case 1:
                var version = model.ImplementedApiVersions[0];
                return(version.Status == null ? version : options.DefaultApiVersion);
            }

            return(model.ImplementedApiVersions.Where(v => v.Status == null).Min(v => v) ?? options.DefaultApiVersion);
        }
Exemplo n.º 24
0
        public void convention_should_apply_api_version_model()
        {
            // arrange
            var supported  = new[] { new ApiVersion(1, 0), new ApiVersion(2, 0), new ApiVersion(3, 0) };
            var deprecated = new[] { new ApiVersion(0, 9) };
            var model      = new ApiVersionModel(supported, deprecated);
            var type       = typeof(object);
            var attributes = new object[]
            {
                new ApiVersionAttribute("1.0"),
                new ApiVersionAttribute("2.0"),
                new ApiVersionAttribute("3.0"),
                new ApiVersionAttribute("0.9")
                {
                    Deprecated = true
                }
            };
            var actionMethod = type.GetRuntimeMethod(nameof(object.ToString), EmptyTypes);
            var controller   = new ControllerModel(type.GetTypeInfo(), attributes)
            {
                Actions =
                {
                    new ActionModel(actionMethod, attributes)
                }
            };
            var convention = (ApiVersionAttribute)attributes[0];

            // act
            convention.Apply(controller);

            // assert
            controller.GetProperty <ApiVersionModel>().ShouldBeEquivalentTo(
                new
            {
                IsApiVersionNeutral    = false,
                DeclaredApiVersions    = deprecated.Union(supported).ToArray(),
                ImplementedApiVersions = deprecated.Union(supported).ToArray(),
                SupportedApiVersions   = supported,
                DeprecatedApiVersions  = deprecated
            });
            controller.Actions.Single().GetProperty <ApiVersionModel>().ShouldBeEquivalentTo(
                new
            {
                IsApiVersionNeutral    = false,
                DeclaredApiVersions    = deprecated.Union(supported).ToArray(),
                ImplementedApiVersions = deprecated.Union(supported).ToArray(),
                SupportedApiVersions   = supported,
                DeprecatedApiVersions  = deprecated
            });
        }
Exemplo n.º 25
0
        public void action_should_not_be_implicitly_versioned_when_api_versions_have_been_mapped()
        {
            // arrange
            var version = new ApiVersion(42, 0);
            var action  = new ActionDescriptor();
            var model   = new ApiVersionModel(version);

            action.SetProperty(model);

            // act
            var result = action.IsImplicitlyMappedTo(version);

            // assert
            result.Should().BeFalse();
        }
        public void action_should_be_mapped_to_specific_api_version()
        {
            // arrange
            var action  = new ActionDescriptor();
            var version = new ApiVersion(42, 0);
            var model   = new ApiVersionModel(version);

            action.SetProperty(model);

            // act
            var result = action.MappingTo(version);

            // assert
            result.Should().Be(Explicit);
        }
Exemplo n.º 27
0
        public virtual ApiVersion SelectVersion( HttpRequest request, ApiVersionModel model )
        {
            Arg.NotNull( request, nameof( request ) );
            Arg.NotNull( model, nameof( model ) );

            switch ( model.ImplementedApiVersions.Count )
            {
                case 0:
                    return options.DefaultApiVersion;
                case 1:
                    var version = model.ImplementedApiVersions[0];
                    return version.Status == null ? version : options.DefaultApiVersion;
            }

            return model.ImplementedApiVersions.Where( v => v.Status == null ).Max( v => v ) ?? options.DefaultApiVersion;
        }
        public void on_action_executing_should_add_version_headers()
        {
            // arrange
            var supported  = new[] { new ApiVersion(1, 0), new ApiVersion(2, 0) };
            var deprecated = new[] { new ApiVersion(0, 5) };
            var model      = new ApiVersionModel(supported, deprecated);
            var context    = CreateContext(model);
            var attribute  = new ReportApiVersionsAttribute();

            // act
            attribute.OnActionExecuting(context);

            // assert
            context.HttpContext.Response.Headers["api-supported-versions"].Single().Should().Be("1.0, 2.0");
            context.HttpContext.Response.Headers["api-deprecated-versions"].Single().Should().Be("0.5");
        }
        public virtual void ApplyTo(HttpActionDescriptor actionDescriptor)
        {
            if (actionDescriptor == null)
            {
                throw new ArgumentNullException(nameof(actionDescriptor));
            }

            var attributes = new List <object>();

            attributes.AddRange(actionDescriptor.GetCustomAttributes <IApiVersionNeutral>(inherit: true));
            attributes.AddRange(actionDescriptor.GetCustomAttributes <IApiVersionProvider>(inherit: false));
            MergeAttributesWithConventions(attributes);

            if (VersionNeutral)
            {
                actionDescriptor.SetProperty(ApiVersionModel.Neutral);
                return;
            }

            ApiVersionModel?versionModel;

            if (MappedVersions.Count == 0)
            {
                var declaredVersions = SupportedVersions.Union(DeprecatedVersions);

                versionModel = new ApiVersionModel(
                    declaredVersions,
                    SupportedVersions,
                    DeprecatedVersions,
                    AdvertisedVersions,
                    DeprecatedAdvertisedVersions);
            }
            else
            {
                var emptyVersions = Empty <ApiVersion>();

                versionModel = new ApiVersionModel(
                    declaredVersions: MappedVersions,
                    supportedVersions: emptyVersions,
                    deprecatedVersions: emptyVersions,
                    advertisedVersions: emptyVersions,
                    deprecatedAdvertisedVersions: emptyVersions);
            }

            actionDescriptor.SetProperty(versionModel);
        }
        static ActionExecutingContext CreateContext(ApiVersionModel model)
        {
            var headers         = new HeaderDictionary();
            var response        = new Mock <HttpResponse>();
            var httpContext     = new Mock <HttpContext>();
            var action          = new ActionDescriptor();
            var actionContext   = new ActionContext(httpContext.Object, new RouteData(), action);
            var filters         = new IFilterMetadata[0];
            var actionArguments = new Dictionary <string, object>();
            var controller      = default(object);

            response.SetupGet(r => r.Headers).Returns(headers);
            httpContext.SetupGet(c => c.Response).Returns(response.Object);
            action.SetProperty(model);

            return(new ActionExecutingContext(actionContext, filters, actionArguments, controller));
        }