/// <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); }
/// <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"); }
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)); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }