Exemplo n.º 1
0
        /// <summary>
        /// Selects and returns the controller descriptor to invoke given the provided request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage">request</see> to get a controller descriptor for.</param>
        /// <returns>The <see cref="HttpControllerDescriptor">controller descriptor</see> that matches the specified <paramref name="request"/>.</returns>
        public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            Arg.NotNull(request, nameof(request));
            Contract.Ensures(Contract.Result <HttpControllerDescriptor>() != null);

            EnsureRequestHasValidApiVersion(request);

            var context = new ControllerSelectionContext(request, GetControllerName, controllerInfoCache);

            if (context.RequestedVersion == null)
            {
                if (options.AssumeDefaultVersionWhenUnspecified)
                {
                    context.RequestedVersion = options.ApiVersionSelector.SelectVersion(context.Request, context.AllVersions);
                }
            }

            var conventionRouteSelector = new ConventionRouteControllerSelector(controllerTypeCache);
            var conventionRouteResult   = default(ControllerSelectionResult);
            var exceptionFactory        = new HttpResponseExceptionFactory(request, new Lazy <ApiVersionModel>(() => context.AllVersions));

            if (context.RouteData == null)
            {
                conventionRouteResult = conventionRouteSelector.SelectController(context);

                if (conventionRouteResult.Succeeded)
                {
                    return(request.ApiVersionProperties().SelectedController = conventionRouteResult.Controller);
                }

                throw exceptionFactory.NewNotFoundOrBadRequestException(conventionRouteResult, default);
            }

            var directRouteSelector = new DirectRouteControllerSelector();
            var directRouteResult   = directRouteSelector.SelectController(context);

            if (directRouteResult.Succeeded)
            {
                return(request.ApiVersionProperties().SelectedController = directRouteResult.Controller);
            }

            conventionRouteResult = conventionRouteSelector.SelectController(context);

            if (conventionRouteResult.Succeeded)
            {
                return(request.ApiVersionProperties().SelectedController = conventionRouteResult.Controller);
            }

            throw exceptionFactory.NewNotFoundOrBadRequestException(conventionRouteResult, directRouteResult);
        }
Exemplo n.º 2
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");
        }
Exemplo n.º 3
0
        HttpResponseMessage CreateBadRequestForUnspecifiedApiVersionOrInvalidApiVersion(bool versionNeutral)
        {
            var requestedVersion = request.ApiVersionProperties().RawRequestedApiVersion;
            var message          = default(string);

            if (IsNullOrEmpty(requestedVersion))
            {
                if (versionNeutral)
                {
                    return(null);
                }

                message = SR.ApiVersionUnspecified;
                TraceWriter.Info(request, ControllerSelectorCategory, message);
                return(Options.ErrorResponses.BadRequest(request, ApiVersionUnspecified, message));
            }
            else if (TryParse(requestedVersion, out var parsedVersion))
            {
                return(null);
            }

            message = SR.VersionedResourceNotSupported.FormatDefault(request.RequestUri, requestedVersion);
            var messageDetail = SR.VersionedControllerNameNotFound.FormatDefault(request.RequestUri, requestedVersion);

            TraceWriter.Info(request, ControllerSelectorCategory, message);

            return(Options.ErrorResponses.BadRequest(request, InvalidApiVersion, message, messageDetail));
        }
Exemplo n.º 4
0
        /// <summary>
        /// Determines whether the route constraint matches the specified criteria.
        /// </summary>
        /// <param name="request">The current <see cref="HttpRequestMessage">HTTP request</see>.</param>
        /// <param name="route">The current <see cref="IHttpRoute">route</see>.</param>
        /// <param name="parameterName">The parameter name to match.</param>
        /// <param name="values">The current <see cref="IDictionary{TKey, TValue}">collection</see> of route values.</param>
        /// <param name="routeDirection">The <see cref="HttpRouteDirection">route direction</see> to match.</param>
        /// <returns>True if the route constraint is matched; otherwise, false.</returns>
        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary <string, object> values, HttpRouteDirection routeDirection)
        {
            if (IsNullOrEmpty(parameterName))
            {
                return(false);
            }

            var value      = default(string);
            var properties = request.ApiVersionProperties();

            if (values.TryGetValue(parameterName, out value))
            {
                properties.RouteParameterName = parameterName;
            }
            else
            {
                return(false);
            }

            if (routeDirection == UriGeneration)
            {
                return(!IsNullOrEmpty(value));
            }

            var requestedVersion = default(ApiVersion);

            if (TryParse(value, out requestedVersion))
            {
                properties.ApiVersion = requestedVersion;
                return(true);
            }

            return(false);
        }
    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
                      IDictionary <string, object> values, HttpRouteDirection routeDirection)
    {
        if (string.IsNullOrEmpty(parameterName))
        {
            return(false);
        }
        var properties    = request.ApiVersionProperties();
        var versionString = "";

        if (values.TryGetValue(parameterName, out object value))
        {
            //This is the real 'magic' here, just replacing the underscore with a period
            versionString = ((string)value).Replace('_', '.');

            properties.RawApiVersion = versionString;
        }
        else
        {
            return(false);
        }
        if (ApiVersion.TryParse(versionString, out var requestedVersion))
        {
            properties.ApiVersion = requestedVersion;
            return(true);
        }
        return(false);
    }
Exemplo n.º 6
0
        /// <summary>
        /// Determines whether the route constraint matches the specified criteria.
        /// </summary>
        /// <param name="request">The current <see cref="HttpRequestMessage">HTTP request</see>.</param>
        /// <param name="route">The current <see cref="IHttpRoute">route</see>.</param>
        /// <param name="parameterName">The parameter name to match.</param>
        /// <param name="values">The current <see cref="IDictionary{TKey, TValue}">collection</see> of route values.</param>
        /// <param name="routeDirection">The <see cref="HttpRouteDirection">route direction</see> to match.</param>
        /// <returns>True if the route constraint is matched; otherwise, false.</returns>
        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary <string, object?> values, HttpRouteDirection routeDirection)
        {
            if (values == null)
            {
                throw new ArgumentNullException(nameof(values));
            }

            if (IsNullOrEmpty(parameterName))
            {
                return(false);
            }

            if (!values.TryGetValue(parameterName, out string value))
            {
                return(false);
            }

            if (routeDirection == UriGeneration)
            {
                return(!IsNullOrEmpty(value));
            }

            var properties = request.ApiVersionProperties();

            properties.RawRequestedApiVersion = value;

            if (TryParse(value, out var requestedVersion))
            {
                properties.RequestedApiVersion = requestedVersion;
                return(true);
            }

            return(false);
        }
        /// <summary>
        /// Creates and returns a controller for the specified request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage">request</see> to create a controller for.</param>
        /// <returns>A new <see cref="IHttpController">controller</see> instance.</returns>
        /// <remarks>The default implementation matches the <see cref="ApiVersion">API version</see> specified in the
        /// <paramref name="request"/> to the <see cref="HttpControllerDescriptor">controller descriptor</see> with
        /// the matching, declared version. If a version was not specified in the <paramref name="request"/> or none of
        /// the <see cref="HttpControllerDescriptor">controller descriptors</see> match the requested version, then
        /// the <see cref="IHttpController">controller</see> is created using the first item in the group.</remarks>
        public override IHttpController CreateController(HttpRequestMessage request)
        {
            Arg.NotNull(request, nameof(request));

            var descriptor = request.ApiVersionProperties().SelectedController;

            return(descriptor.CreateController(request));
        }
        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);
        }
        public void create_controller_should_return_default_instance_when_versioned_controller_instance_is_not_found()
        {
            // arrange
            var expected      = new Mock <IHttpController>().Object;
            var configuration = new HttpConfiguration();
            var controller2   = new Mock <IHttpController>().Object;
            var descriptor1   = new Mock <HttpControllerDescriptor>()
            {
                CallBase = true
            };
            var descriptor2 = new Mock <HttpControllerDescriptor>()
            {
                CallBase = true
            };

            descriptor1.Setup(d => d.GetCustomAttributes <IApiVersionProvider>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionProvider>()
            {
                new ApiVersionAttribute("1.0")
            });
            descriptor1.Setup(d => d.GetCustomAttributes <IApiVersionNeutral>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionNeutral>());
            descriptor1.Setup(d => d.CreateController(It.IsAny <HttpRequestMessage>())).Returns(expected);
            descriptor1.Object.Configuration = configuration;
            descriptor1.Object.Properties[typeof(ApiVersionModel)] = new ApiVersionModel(new ApiVersion(1, 0));

            descriptor2.Setup(d => d.GetCustomAttributes <IApiVersionProvider>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionProvider>()
            {
                new ApiVersionAttribute("2.0")
            });
            descriptor2.Setup(d => d.GetCustomAttributes <IApiVersionNeutral>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionNeutral>());
            descriptor2.Setup(d => d.CreateController(It.IsAny <HttpRequestMessage>())).Returns(controller2);
            descriptor2.Object.Configuration = configuration;
            descriptor2.Object.Properties[typeof(ApiVersionModel)] = new ApiVersionModel(new ApiVersion(2, 0));

            var group = new HttpControllerDescriptorGroup(descriptor1.Object, descriptor2.Object)
            {
                Configuration = configuration
            };
            var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/test?api-version=3.0");

            request.ApiVersionProperties().SelectedController = descriptor1.Object;

            // act
            var controller = group.CreateController(request);

            // assert
            controller.Should().Be(expected);
            descriptor1.Verify(d => d.CreateController(request), Once());
            descriptor2.Verify(d => d.CreateController(request), Never());
        }
        /// <summary>
        /// Reads the service API version value from a request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage">HTTP request</see> to read the API version from.</param>
        /// <returns>The raw, unparsed service API version value read from the request or <c>null</c> if request does not contain an API version.</returns>
        /// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
        public virtual string?Read(HttpRequestMessage request)
        {
            if (reentrant)
            {
                return(null);
            }

            reentrant = true;
            var value = request.ApiVersionProperties().RawRequestedApiVersion;

            reentrant = false;

            return(value);
        }
 internal ControllerSelectionContext(
     HttpRequestMessage request,
     Func <HttpRequestMessage, string?> controllerName,
     Lazy <ConcurrentDictionary <string, HttpControllerDescriptorGroup> > controllerInfoCache)
 {
     Request                   = request;
     requestProperties         = request.ApiVersionProperties();
     this.controllerName       = new Lazy <string?>(() => controllerName(Request));
     this.controllerInfoCache  = controllerInfoCache;
     RouteData                 = request.GetRouteData();
     conventionRouteCandidates = new Lazy <CandidateAction[]?>(GetConventionRouteCandidates);
     directRouteCandidates     = new Lazy <CandidateAction[]?>(() => RouteData?.GetDirectRouteCandidates());
     allVersions               = new Lazy <ApiVersionModel>(CreateAggregatedModel);
 }
        /// <summary>
        /// Reads the service API version value from a request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage">HTTP request</see> to read the API version from.</param>
        /// <returns>The raw, unparsed service API version value read from the request or <c>null</c> if request does not contain an API version.</returns>
        /// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
        public virtual string Read(HttpRequestMessage request)
        {
            Arg.NotNull(request, nameof(request));

            if (reentrant)
            {
                return(null);
            }

            reentrant = true;
            var value = request.ApiVersionProperties().RawApiVersion;

            reentrant = false;

            return(value);
        }
        public void get_requested_api_version_should_return_expected_value_from_query_parameter()
        {
            // arrange
            var requestedVersion = new ApiVersion(1, 0);
            var configuration    = new HttpConfiguration();
            var request          = new HttpRequestMessage(Get, $"http://localhost/Tests?api-version={requestedVersion}");

            configuration.AddApiVersioning();
            request.SetConfiguration(configuration);

            // act
            var version = request.GetRequestedApiVersion();

            // assert
            version.Should().Be(requestedVersion);
            request.ApiVersionProperties().RequestedApiVersion.Should().Be(requestedVersion);
        }
        internal static ApiVersion?GetRequestedApiVersionOrReturnBadRequest(this HttpRequestMessage request)
        {
            var properties = request.ApiVersionProperties();

            try
            {
                return(properties.RequestedApiVersion);
            }
            catch (AmbiguousApiVersionException ex)
            {
                var error = new ODataError()
                {
                    ErrorCode = "AmbiguousApiVersion", Message = ex.Message
                };
                throw new HttpResponseException(request.CreateResponse(BadRequest, error));
            }
        }
        public void create_controller_should_return_first_instance_when_version_is_unspecified()
        {
            // arrange
            var expected    = new Mock <IHttpController>().Object;
            var controller2 = new Mock <IHttpController>().Object;
            var descriptor1 = new Mock <HttpControllerDescriptor>()
            {
                CallBase = true
            };
            var descriptor2 = new Mock <HttpControllerDescriptor>()
            {
                CallBase = true
            };
            var configuration = new HttpConfiguration();

            descriptor1.Setup(d => d.CreateController(It.IsAny <HttpRequestMessage>())).Returns(expected);
            descriptor1.Setup(d => d.GetCustomAttributes <IApiVersionProvider>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionProvider>());
            descriptor1.Setup(d => d.GetCustomAttributes <IApiVersionNeutral>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionNeutral>());
            descriptor1.Object.Configuration = configuration;
            descriptor1.Object.Properties[typeof(ApiVersionModel)] = ApiVersionModel.Neutral;

            descriptor2.Setup(d => d.CreateController(It.IsAny <HttpRequestMessage>())).Returns(controller2);
            descriptor2.Setup(d => d.GetCustomAttributes <IApiVersionProvider>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionProvider>());
            descriptor2.Setup(d => d.GetCustomAttributes <IApiVersionNeutral>(It.IsAny <bool>()))
            .Returns(() => new Collection <IApiVersionNeutral>());
            descriptor2.Object.Configuration = configuration;
            descriptor2.Object.Properties[typeof(ApiVersionModel)] = ApiVersionModel.Neutral;

            var group   = new HttpControllerDescriptorGroup(descriptor1.Object, descriptor2.Object);
            var request = new HttpRequestMessage();

            request.ApiVersionProperties().SelectedController = descriptor1.Object;

            // act
            var controller = group.CreateController(request);

            // assert
            controller.Should().Be(expected);
            descriptor1.Verify(d => d.CreateController(request), Once());
            descriptor2.Verify(d => d.CreateController(request), Never());
        }
        public void create_controller_should_return_expected_instance_when_count_eq_1()
        {
            // arrange
            var expected   = new Mock <IHttpController>().Object;
            var descriptor = new Mock <HttpControllerDescriptor>();

            descriptor.Setup(d => d.CreateController(It.IsAny <HttpRequestMessage>())).Returns(expected);

            var group   = new HttpControllerDescriptorGroup(descriptor.Object);
            var request = new HttpRequestMessage();

            request.ApiVersionProperties().SelectedController = descriptor.Object;

            // act
            var controller = group.CreateController(request);

            // assert
            controller.Should().Be(expected);
        }
Exemplo n.º 17
0
        /// <summary>
        /// Reads the service API version value from a request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage">HTTP request</see> to read the API version from.</param>
        /// <returns>The raw, unparsed service API version value read from the request or <c>null</c> if request does not contain an API version.</returns>
        /// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
        public virtual string?Read(HttpRequestMessage request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (reentrant)
            {
                return(null);
            }

            reentrant = true;
            var value = request.ApiVersionProperties().RawRequestedApiVersion;

            reentrant = false;

            return(value);
        }
        public void get_requested_api_version_should_return_expected_value_from_header(string headerName)
        {
            // arrange
            var requestedVersion = new ApiVersion(1, 0);
            var configuration    = new HttpConfiguration();
            var request          = new HttpRequestMessage();
            var versionReader    = ApiVersionReader.Combine(new QueryStringApiVersionReader(), new HeaderApiVersionReader(headerName));

            configuration.AddApiVersioning(o => o.ApiVersionReader = versionReader);
            request.SetConfiguration(configuration);
            request.Headers.Add(headerName, requestedVersion.ToString());

            // act
            var version = request.GetRequestedApiVersion();

            // assert
            version.Should().Be(requestedVersion);
            request.ApiVersionProperties().RequestedApiVersion.Should().Be(requestedVersion);
        }
Exemplo n.º 19
0
        /// <summary>
        /// Reads the service API version value from a request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage">HTTP request</see> to read the API version from.</param>
        /// <returns>The raw, unparsed service API version value read from the request or <c>null</c> if request does not contain an API version.</returns>
        /// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
        public virtual string Read(HttpRequestMessage request)
        {
            Arg.NotNull(request, nameof(request));

            var routeData = request.GetRouteData();

            if (routeData == null)
            {
                return(null);
            }

            var key          = request.ApiVersionProperties().RouteParameterName;
            var subRouteData = routeData.GetSubRoutes() ?? new[] { routeData };
            var value        = default(object);

            if (IsNullOrEmpty(key))
            {
                foreach (var subRouteDatum in subRouteData)
                {
                    key = GetRouteParameterNameFromConstraintNameInTemplate(subRouteDatum);

                    if (key != null && subRouteDatum.Values.TryGetValue(key, out value))
                    {
                        return(value.ToString());
                    }
                }
            }
            else
            {
                foreach (var subRouteDatum in subRouteData)
                {
                    if (subRouteDatum.Values.TryGetValue(key, out value))
                    {
                        return(value.ToString());
                    }
                }
            }

            return(null);
        }
        public override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary <string, object> values, HttpRouteDirection routeDirection)
        {
            Arg.NotNull(request, nameof(request));
            Arg.NotNull(values, nameof(values));

            if (routeDirection == UriGeneration)
            {
                return(base.Match(request, route, parameterName, values, routeDirection));
            }

            var properties       = request.ApiVersionProperties();
            var requestedVersion = GetRequestedApiVersionOrReturnBadRequest(request, properties);

            if (requestedVersion != null)
            {
                if (ApiVersion == requestedVersion && base.Match(request, route, parameterName, values, routeDirection))
                {
                    DecorateUrlHelperWithApiVersionRouteValueIfNecessary(request, values);
                    return(true);
                }

                return(false);
            }

            var options = request.GetApiVersioningOptions();

            if (options.DefaultApiVersion != ApiVersion)
            {
                return(false);
            }

            if (options.AssumeDefaultVersionWhenUnspecified || IsServiceDocumentOrMetadataRoute(values))
            {
                properties.ApiVersion = ApiVersion;
                return(base.Match(request, route, parameterName, values, routeDirection));
            }

            return(false);
        }
 /// <summary>
 /// Gets the current service API version requested.
 /// </summary>
 /// <param name="request">The <see cref="HttpRequestMessage">request</see> to get the API version for.</param>
 /// <returns>The requested <see cref="ApiVersion">API version</see>.</returns>
 /// <remarks>This method will return <c>null</c> no service API version was requested or the requested
 /// service API version is in an invalid format.</remarks>
 /// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
 public static ApiVersion?GetRequestedApiVersion(this HttpRequestMessage request) => request.ApiVersionProperties().RequestedApiVersion;
 public static ApiVersion GetRequestedApiVersion(this HttpRequestMessage request)
 {
     Arg.NotNull(request, nameof(request));
     return(request.ApiVersionProperties().RequestedApiVersion);
 }