Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/aspnetwebstack.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoryoussefm <youssefm@microsoft.com>2012-10-12 00:58:28 +0400
committeryoussefm <youssefm@microsoft.com>2012-10-12 05:38:45 +0400
commit29fa9e0f81ccbd59fdcfb7950a63a864d9be19cb (patch)
treef548b21907d58b8f3d80dc43cf9f06b8fbb8d958 /src
parent45c1223c1d410755b6948407ec70bb6818f15bf9 (diff)
[OData] Add new HttpConfiguration.EnableQuerySupport to enable querying globally for the server
Also [Queryable] refatoring and adding an error message when the ObjectContent is not of ObjectType IEnumerable, IQueryable, IEnumerable<T>, or IQueryable<T>
Diffstat (limited to 'src')
-rw-r--r--src/System.Web.Http.OData/HttpConfigurationExtensions.cs31
-rw-r--r--src/System.Web.Http.OData/OData/Query/QueryableFilterProvider.cs57
-rw-r--r--src/System.Web.Http.OData/Properties/SRResources.Designer.cs9
-rw-r--r--src/System.Web.Http.OData/Properties/SRResources.resx3
-rw-r--r--src/System.Web.Http.OData/QueryableAttribute.cs143
-rw-r--r--src/System.Web.Http.OData/System.Web.Http.OData.csproj1
6 files changed, 192 insertions, 52 deletions
diff --git a/src/System.Web.Http.OData/HttpConfigurationExtensions.cs b/src/System.Web.Http.OData/HttpConfigurationExtensions.cs
index 9516fcaa..2aeb591d 100644
--- a/src/System.Web.Http.OData/HttpConfigurationExtensions.cs
+++ b/src/System.Web.Http.OData/HttpConfigurationExtensions.cs
@@ -2,9 +2,11 @@
using System.ComponentModel;
using System.Linq;
+using System.Web.Http.Filters;
using System.Web.Http.OData;
using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Properties;
+using System.Web.Http.OData.Query;
using Microsoft.Data.Edm;
namespace System.Web.Http
@@ -170,5 +172,34 @@ namespace System.Web.Http
}
configuration.Properties[ODataActionResolverKey] = resolver;
}
+
+ /// <summary>
+ /// Enables query support for actions with an <see cref="IQueryable"/> or <see cref="IQueryable{T}"/> return type.
+ /// </summary>
+ /// <param name="configuration">The server configuration.</param>
+ public static void EnableQuerySupport(this HttpConfiguration configuration)
+ {
+ if (configuration == null)
+ {
+ throw Error.ArgumentNull("configuration");
+ }
+
+ configuration.Services.Add(typeof(IFilterProvider), new QueryableFilterProvider());
+ }
+
+ /// <summary>
+ /// Enables query support for actions with an <see cref="IQueryable"/> or <see cref="IQueryable{T}"/> return type.
+ /// </summary>
+ /// <param name="configuration">The server configuration.</param>
+ /// <param name="resultLimit">The maximum number of results to return.</param>
+ public static void EnableQuerySupport(this HttpConfiguration configuration, int resultLimit)
+ {
+ if (configuration == null)
+ {
+ throw Error.ArgumentNull("configuration");
+ }
+
+ configuration.Services.Add(typeof(IFilterProvider), new QueryableFilterProvider() { ResultLimit = resultLimit });
+ }
}
}
diff --git a/src/System.Web.Http.OData/OData/Query/QueryableFilterProvider.cs b/src/System.Web.Http.OData/OData/Query/QueryableFilterProvider.cs
new file mode 100644
index 00000000..10b272e6
--- /dev/null
+++ b/src/System.Web.Http.OData/OData/Query/QueryableFilterProvider.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+
+namespace System.Web.Http.OData.Query
+{
+ /// <summary>
+ /// An implementation of <see cref="IFilterProvider"/> that applies the <see cref="QueryableAttribute"/> to
+ /// any action with an <see cref="IQueryable"/> or <see cref="IQueryable{T}"/> return type that doesn't bind
+ /// a parameter of type <see cref="ODataQueryOptions"/>.
+ /// </summary>
+ public class QueryableFilterProvider : IFilterProvider
+ {
+ /// <summary>
+ /// Gets or sets the maximum number of query results to return.
+ /// </summary>
+ /// <value>
+ /// The maximum number of query results to return, or <c>null</c> if there is no limit.
+ /// </value>
+ public int? ResultLimit { get; set; }
+
+ /// <summary>
+ /// Provides filters to apply to the specified action.
+ /// </summary>
+ /// <param name="configuration">The server configuration.</param>
+ /// <param name="actionDescriptor">The action descriptor for the action to provide filters for.</param>
+ /// <returns>
+ /// The filters to apply to the specified action.
+ /// </returns>
+ public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
+ {
+ // Actions with a bound parameter of type ODataQueryOptions do not support the [Queryable] attribute
+ // The assumption is that the action will handle the querying within the action implementation
+ if (actionDescriptor != null && IsIQueryable(actionDescriptor.ReturnType) &&
+ !actionDescriptor.GetParameters().Any(parameter => parameter.ParameterType == typeof(ODataQueryOptions)))
+ {
+ QueryableAttribute filter = new QueryableAttribute();
+ if (ResultLimit.HasValue)
+ {
+ filter.ResultLimit = ResultLimit.Value;
+ }
+ return new FilterInfo[] { new FilterInfo(filter, FilterScope.Global) };
+ }
+
+ return Enumerable.Empty<FilterInfo>();
+ }
+
+ private static bool IsIQueryable(Type type)
+ {
+ return type == typeof(IQueryable) ||
+ (type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IQueryable<>));
+ }
+ }
+}
diff --git a/src/System.Web.Http.OData/Properties/SRResources.Designer.cs b/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
index db2eb2f6..f40dfa62 100644
--- a/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
+++ b/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
@@ -502,6 +502,15 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to The action &apos;{0}&apos; on controller &apos;{1}&apos; with return type &apos;{2}&apos; cannot support querying. Ensure the type of the returned content is IEnumerable, IQueryable, or a generic form of either interface..
+ /// </summary>
+ internal static string InvalidReturnTypeForQuerying {
+ get {
+ return ResourceManager.GetString("InvalidReturnTypeForQuerying", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The item must be of type &apos;{0}&apos;..
/// </summary>
internal static string ItemMustBeOfType {
diff --git a/src/System.Web.Http.OData/Properties/SRResources.resx b/src/System.Web.Http.OData/Properties/SRResources.resx
index 1f76d9a4..8344fa9c 100644
--- a/src/System.Web.Http.OData/Properties/SRResources.resx
+++ b/src/System.Web.Http.OData/Properties/SRResources.resx
@@ -438,4 +438,7 @@
<data name="BinaryOperatorNotSupported" xml:space="preserve">
<value>A binary operator with incompatible types was detected. Found operand types '{0}' and '{1}' for operator kind '{2}'.</value>
</data>
+ <data name="InvalidReturnTypeForQuerying" xml:space="preserve">
+ <value>The action '{0}' on controller '{1}' with return type '{2}' cannot support querying. Ensure the type of the returned content is IEnumerable, IQueryable, or a generic form of either interface.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/System.Web.Http.OData/QueryableAttribute.cs b/src/System.Web.Http.OData/QueryableAttribute.cs
index f61809a2..ffc3d50b 100644
--- a/src/System.Web.Http.OData/QueryableAttribute.cs
+++ b/src/System.Web.Http.OData/QueryableAttribute.cs
@@ -118,65 +118,23 @@ namespace System.Web.Http
HttpResponseMessage response = actionExecutedContext.Response;
- IEnumerable query;
- IQueryable queryable = null;
- if (response != null && response.IsSuccessStatusCode && response.TryGetContentValue(out query))
+ if (response != null && response.IsSuccessStatusCode)
{
+ ObjectContent responseContent = response.Content as ObjectContent;
+ ValidateReturnType(responseContent.ObjectType, actionDescriptor);
+
// Apply the query if there are any query options or if there is a result limit set
- if (request.RequestUri != null && (!String.IsNullOrWhiteSpace(request.RequestUri.Query) || _resultLimit.HasValue))
+ if (responseContent != null && responseContent.Value != null && request.RequestUri != null &&
+ (!String.IsNullOrWhiteSpace(request.RequestUri.Query) || _resultLimit.HasValue))
{
ValidateQuery(request);
try
{
- Type originalQueryType = query.GetType();
- Type entityClrType = TypeHelper.GetImplementedIEnumerableType(originalQueryType);
-
- if (entityClrType == null)
- {
- // The element type cannot be determined because the type of the content
- // is not IEnumerable<T> or IQueryable<T>.
- throw Error.InvalidOperation(
- SRResources.FailedToRetrieveTypeToBuildEdmModel,
- this.GetType().Name,
- actionDescriptor.ActionName,
- actionDescriptor.ControllerDescriptor.ControllerName,
- originalQueryType.FullName);
- }
-
- ODataQueryContext queryContext = CreateQueryContext(entityClrType, configuration, actionDescriptor);
- ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, request);
-
- // Filter and OrderBy require entity sets. Top and Skip may accept primitives.
- if (queryContext.IsPrimitiveClrType && (queryOptions.Filter != null || queryOptions.OrderBy != null))
- {
- // An attempt to use a query option not allowed for primitive types
- // generates a BadRequest with a general message that avoids information disclosure.
- actionExecutedContext.Response = request.CreateErrorResponse(
- HttpStatusCode.BadRequest,
- SRResources.OnlySkipAndTopSupported);
- return;
- }
-
- // apply the query
- queryable = query as IQueryable;
- if (queryable == null)
- {
- queryable = query.AsQueryable();
- }
-
- ODataQuerySettings querySettings = new ODataQuerySettings
- {
- EnsureStableOrdering = EnsureStableOrdering,
- HandleNullPropagation = HandleNullPropagation,
- ResultLimit = _resultLimit
- };
-
- queryable = queryOptions.ApplyTo(queryable, querySettings);
-
- Contract.Assert(queryable != null);
- // we don't support shape changing query composition
- ((ObjectContent)response.Content).Value = queryable;
+ IEnumerable query = responseContent.Value as IEnumerable;
+ Contract.Assert(query != null, "ValidateResponseContent should have ensured the responseContent implements IEnumerable");
+ IQueryable queryResults = ExecuteQuery(query, request, configuration, actionDescriptor);
+ responseContent.Value = queryResults;
}
catch (ODataException e)
{
@@ -190,6 +148,87 @@ namespace System.Web.Http
}
}
+ private static void ValidateReturnType(Type responseContentType, HttpActionDescriptor actionDescriptor)
+ {
+ if (!IsSupportedReturnType(responseContentType))
+ {
+ throw Error.InvalidOperation(
+ SRResources.InvalidReturnTypeForQuerying,
+ actionDescriptor.ActionName,
+ actionDescriptor.ControllerDescriptor.ControllerName,
+ responseContentType.FullName);
+ }
+ }
+
+ private static bool IsSupportedReturnType(Type objectType)
+ {
+ Contract.Assert(objectType != null);
+
+ if (objectType == typeof(IEnumerable) || objectType == typeof(IQueryable))
+ {
+ return true;
+ }
+
+ if (objectType.IsGenericType)
+ {
+ Type genericTypeDefinition = objectType.GetGenericTypeDefinition();
+ if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(IQueryable<>))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Response disposed after being sent.")]
+ private IQueryable ExecuteQuery(IEnumerable query, HttpRequestMessage request, HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
+ {
+ Type originalQueryType = query.GetType();
+ Type entityClrType = TypeHelper.GetImplementedIEnumerableType(originalQueryType);
+
+ if (entityClrType == null)
+ {
+ // The element type cannot be determined because the type of the content
+ // is not IEnumerable<T> or IQueryable<T>.
+ throw Error.InvalidOperation(
+ SRResources.FailedToRetrieveTypeToBuildEdmModel,
+ this.GetType().Name,
+ actionDescriptor.ActionName,
+ actionDescriptor.ControllerDescriptor.ControllerName,
+ originalQueryType.FullName);
+ }
+
+ ODataQueryContext queryContext = CreateQueryContext(entityClrType, configuration, actionDescriptor);
+ ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, request);
+
+ // Filter and OrderBy require entity sets. Top and Skip may accept primitives.
+ if (queryContext.IsPrimitiveClrType && (queryOptions.Filter != null || queryOptions.OrderBy != null))
+ {
+ // An attempt to use a query option not allowed for primitive types
+ // generates a BadRequest with a general message that avoids information disclosure.
+ throw new HttpResponseException(request.CreateErrorResponse(
+ HttpStatusCode.BadRequest,
+ SRResources.OnlySkipAndTopSupported));
+ }
+
+ // apply the query
+ IQueryable queryable = query as IQueryable;
+ if (queryable == null)
+ {
+ queryable = query.AsQueryable();
+ }
+
+ ODataQuerySettings querySettings = new ODataQuerySettings
+ {
+ EnsureStableOrdering = EnsureStableOrdering,
+ HandleNullPropagation = HandleNullPropagation,
+ ResultLimit = _resultLimit
+ };
+
+ return queryOptions.ApplyTo(queryable, querySettings);
+ }
+
private static ODataQueryContext CreateQueryContext(Type entityClrType, HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
// Primitive types do not construct an EDM model and deal only with the CLR Type
diff --git a/src/System.Web.Http.OData/System.Web.Http.OData.csproj b/src/System.Web.Http.OData/System.Web.Http.OData.csproj
index b2c65769..1abd50f1 100644
--- a/src/System.Web.Http.OData/System.Web.Http.OData.csproj
+++ b/src/System.Web.Http.OData/System.Web.Http.OData.csproj
@@ -199,6 +199,7 @@
<Compile Include="OData\Query\ODataRawQueryOptions.cs" />
<Compile Include="OData\Formatter\Serialization\ODataPayloadKindHelper.cs" />
<Compile Include="OData\ODataResult.cs" />
+ <Compile Include="OData\Query\QueryableFilterProvider.cs" />
<Compile Include="OData\Query\TopQueryOption.cs" />
<Compile Include="OData\Delta.cs" />
<Compile Include="OData\ExpressionHelperMethods.cs" />