Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / ndp / fx / src / DataWeb / Server / System / Data / Services / DataServiceConfiguration.cs / 1 / DataServiceConfiguration.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides an implementation of the IDataServiceConfiguration
// interface.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Data.Services.Providers;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#endregion Namespaces.
/// Use this class to manage the configuration data for a data service.
internal class DataServiceConfiguration : IDataServiceConfiguration
{
#region Private fields.
/// Whether this configuration has been sealed.
private bool configurationSealed;
/// Maximum number of change sets and query operations in a batch.
private int maxBatchCount;
/// Maximum number of changes in a change set.
private int maxChangeSetCount;
/// Maximum number of segments to be expanded allowed in a request.
private int maxExpandCount;
/// Maximum number of segments in a single $expand path.
private int maxExpandDepth;
/// Maximum number of elements in each returned collection (top-level or expanded).
private int maxResultsPerCollection;
/// maximum number of objects that can be referred in a single insert request.
private int maxObjectCountOnInsert;
/// The provider for the web service.
private IDataServiceProvider provider;
/// Rights used for unspecified resource containers.
private EntitySetRights rightsForUnspecifiedResourceContainer;
/// Rights used for unspecified service operations.
private ServiceOperationRights rightsForUnspecifiedServiceOperation;
/// A lookup of containers to their rights.
private Dictionary resourceRights;
/// A lookup of service operations to their rights.
private Dictionary serviceRights;
/// A list of known types.
private List knownTypes;
/// Whether verbose errors should be returned by default.
private bool useVerboseErrors;
#endregion Private fields.
#region Constructor.
///
/// Initializes a new with
/// the specified .
///
/// Non-null provider for this configuration.
internal DataServiceConfiguration(IDataServiceProvider provider)
{
Debug.Assert(provider != null, "provider != null");
this.provider = provider;
this.resourceRights = new Dictionary();
this.serviceRights = new Dictionary();
this.rightsForUnspecifiedResourceContainer = EntitySetRights.None;
this.rightsForUnspecifiedServiceOperation = ServiceOperationRights.None;
this.knownTypes = new List();
this.maxBatchCount = Int32.MaxValue;
this.maxChangeSetCount = Int32.MaxValue;
this.maxExpandCount = Int32.MaxValue;
this.maxExpandDepth = Int32.MaxValue;
this.maxResultsPerCollection = Int32.MaxValue;
this.maxObjectCountOnInsert = Int32.MaxValue;
}
#endregion Constructor.
#region IDataServiceConfiguration implementation.
/// Maximum number of change sets and query operations in a batch.
public int MaxBatchCount
{
get { return this.maxBatchCount; }
set { this.maxBatchCount = this.CheckNonNegativeProperty(value, "MaxBatchCount"); }
}
/// Maximum number of changes in a change set.
public int MaxChangesetCount
{
get { return this.maxChangeSetCount; }
set { this.maxChangeSetCount = this.CheckNonNegativeProperty(value, "MaxChangesetCount"); }
}
/// Maximum number of segments to be expanded allowed in a request.
public int MaxExpandCount
{
get { return this.maxExpandCount; }
set { this.maxExpandCount = this.CheckNonNegativeProperty(value, "MaxExpandCount"); }
}
/// Maximum number of segments in a single $expand path.
public int MaxExpandDepth
{
get { return this.maxExpandDepth; }
set { this.maxExpandDepth = this.CheckNonNegativeProperty(value, "MaxExpandDepth"); }
}
/// Maximum number of elements in each returned collection (top-level or expanded).
public int MaxResultsPerCollection
{
get { return this.maxResultsPerCollection; }
set { this.maxResultsPerCollection = this.CheckNonNegativeProperty(value, "MaxResultsPerCollection"); }
}
/// Maximum number of objects that can be referred in a single POST request.
public int MaxObjectCountOnInsert
{
get { return this.maxObjectCountOnInsert; }
set { this.maxObjectCountOnInsert = this.CheckNonNegativeProperty(value, "MaxObjectCountOnInsert"); }
}
/// Gets or sets whether verbose errors should be used by default.
///
/// This property sets the default for the whole service; individual responses may behave differently
/// depending on the value of the VerboseResponse property of the arguments to the HandleException
/// method on the class.
///
public bool UseVerboseErrors
{
get
{
return this.useVerboseErrors;
}
set
{
this.CheckNotSealed();
this.useVerboseErrors = value;
}
}
/// Sets the access rights on the specified resource container.
///
/// Name of resource container to set; '*' to indicate all
/// resource containers not otherwise specified.
///
/// Rights to be granted to this resource.
public void SetEntitySetAccessRule(string name, EntitySetRights rights)
{
this.CheckNotSealed();
if (name == null)
{
throw Error.ArgumentNull("name");
}
WebUtil.CheckResourceContainerRights(rights, "rights");
if (name == "*")
{
this.rightsForUnspecifiedResourceContainer = rights;
}
else
{
ResourceContainer container = this.provider.TryResolveContainerName(name);
if (container == null)
{
throw new ArgumentException(Strings.DataServiceConfiguration_ResourceNameNotFound(name), "name");
}
this.resourceRights[container] = rights;
}
}
/// Sets the access rights on the specified service operation.
///
/// Name of service operation to set; '*' to indicate all
/// service operations not otherwise specified.
///
/// Rights to be granted to this operation.
public void SetServiceOperationAccessRule(string name, ServiceOperationRights rights)
{
this.CheckNotSealed();
if (name == null)
{
throw new ArgumentNullException("name");
}
WebUtil.CheckServiceOperationRights(rights, "rights");
ServiceOperation serviceOperation = this.provider.TryResolveServiceOperation(name);
if (name == "*")
{
this.rightsForUnspecifiedServiceOperation = rights;
}
else
{
if (serviceOperation == null)
{
throw new ArgumentException(Strings.DataServiceConfiguration_ServiceNameNotFound(name), "name");
}
this.serviceRights[serviceOperation] = rights;
}
}
///
/// Add the type to the list of known types. If there is a type that needs to be added since
/// Astoria runtime can't detect it using the default set of rules, providers can add them using
/// this method
///
/// type which needs to be added to the known types collection
public void RegisterKnownType(Type type)
{
this.CheckNotSealed();
this.knownTypes.Add(type);
}
#endregion IDataServiceConfiguration implementation.
#region Internal methods.
/// Composes all query interceptors into a single expression.
/// Web service instance.
/// Container for which interceptors should run.
/// An expression the filter for query interceptors, possibly null.
internal static Expression ComposeQueryInterceptors(IDataService service, ResourceContainer container)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(container != null, "container != null");
MethodInfo[] methods = container.ReadAuthorizationMethods;
if (methods == null || methods.Length == 0)
{
return null;
}
LambdaExpression filter = null;
for (int i = 0; i < methods.Length; i++)
{
Expression predicate = (Expression)methods[i].Invoke(service.Instance, WebUtil.EmptyObjectArray);
if (predicate == null)
{
throw new InvalidOperationException(Strings.DataService_AuthorizationReturnedNullQuery(methods[i].Name, methods[i].DeclaringType.FullName));
}
Debug.Assert(predicate is LambdaExpression, "predicate is LambdaExpression -- otherwise signature check missed something.");
if (filter == null)
{
filter = (LambdaExpression)predicate;
}
else
{
ParameterExpression parameter = filter.Parameters[0];
Expression adjustedPredicate = ParameterReplacerVisitor.Replace(
((LambdaExpression)predicate).Body, // expression
((LambdaExpression)predicate).Parameters[0], // oldParameter
parameter); // newParameter
filter = Expression.Lambda(Expression.And(filter.Body, adjustedPredicate), parameter);
}
}
return filter;
}
///
/// Composes the specified for the
/// given with authorization
/// callbacks.
///
/// Data service on which to invoke method.
/// Resource container to compose with.
/// Query to compose.
/// The resulting composed query.
internal static IQueryable ComposeResourceContainer(IDataService service, ResourceContainer container, IQueryable query)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(container != null, "container != null");
Debug.Assert(query != null, "query != null");
MethodInfo[] methods = container.ReadAuthorizationMethods;
if (methods != null)
{
for (int i = 0; i < methods.Length; i++)
{
Expression predicate = (Expression)methods[i].Invoke(service.Instance, WebUtil.EmptyObjectArray);
if (predicate == null)
{
throw new InvalidOperationException(Strings.DataService_AuthorizationReturnedNullQuery(methods[i].Name, methods[i].DeclaringType.FullName));
}
Debug.Assert(predicate is LambdaExpression, "predicate is LambdaExpression -- otherwise signature check missed something.");
query = RequestUriProcessor.InvokeWhereForType(query, (LambdaExpression)predicate);
}
}
return query;
}
/// Applies the configuration to the current provider.
/// Instance of the data source for the provider.
/// This method should be called exactly once.
internal void ApplyToProvider(object dataSourceInstance)
{
Debug.Assert(this.provider != null, "this.provider != null");
Debug.Assert(this.resourceRights != null, "this.resourceRights != null - otherwise, ApplyToProvider has been called multiple times.");
this.provider.ApplyConfiguration(this, dataSourceInstance);
this.resourceRights = null;
this.serviceRights = null;
this.knownTypes = null;
}
/// Checks whether this request has the specified rights.
/// Container to check.
/// Required rights.
/// Thrown if aren't available.
internal void CheckResourceRights(ResourceContainer container, EntitySetRights requiredRights)
{
Debug.Assert(container != null, "container != null");
Debug.Assert(requiredRights != EntitySetRights.None, "requiredRights != EntitySetRights.None");
EntitySetRights effectiveRights = this.GetResourceContainerRights(container);
if ((requiredRights & effectiveRights) == 0)
{
throw DataServiceException.CreateForbidden();
}
}
/// Checks whether this request has the specified reading rights.
/// Container to check.
/// Whether a single or multiple resources are requested.
/// Thrown if aren't available.
internal void CheckResourceRightsForRead(ResourceContainer container, bool singleResult)
{
Debug.Assert(container != null, "container != null");
EntitySetRights requiredRights = singleResult ? EntitySetRights.ReadSingle : EntitySetRights.ReadMultiple;
this.CheckResourceRights(container, requiredRights);
}
/// Checks whether this request has the specified rights.
/// Operation to check.
/// Required rights.
/// Thrown if aren't available.
internal void CheckServiceRights(ServiceOperation operation, ServiceOperationRights requiredRights)
{
Debug.Assert(operation != null, "operation != null");
Debug.Assert(requiredRights != ServiceOperationRights.None, "requiredRights != EntitySetRights.None");
ServiceOperationRights effectiveRights = this.GetServiceOperationRights(operation);
if ((requiredRights & effectiveRights) == 0)
{
throw DataServiceException.CreateForbidden();
}
}
/// Checks whether this request has the specified rights.
/// Operation to check.
/// Whether a single or multiple resources are requested.
/// Thrown if aren't available.
internal void CheckServiceRights(ServiceOperation operation, bool singleResult)
{
Debug.Assert(operation != null, "operation != null");
if (operation.ResultKind != ServiceOperationResultKind.Nothing)
{
ServiceOperationRights requiredRights = singleResult ? ServiceOperationRights.ReadSingle : ServiceOperationRights.ReadMultiple;
this.CheckServiceRights(operation, requiredRights);
}
}
/// Gets a string with methods allowed on the target for the .
/// Description with target.
/// A string with methods allowed on the description; possibly null.
internal string GetAllowedMethods(RequestDescription description)
{
Debug.Assert(description != null, "description != null");
Debug.Assert(
description.TargetKind != RequestTargetKind.Nothing,
"description.TargetKind != RequestTargetKind.Nothing - otherwise it hasn't been determined yet");
Debug.Assert(
description.TargetKind != RequestTargetKind.VoidServiceOperation,
"description.TargetKind != RequestTargetKind.VoidServiceOperation - this method is only for containers");
if (description.TargetKind == RequestTargetKind.Metadata ||
description.TargetKind == RequestTargetKind.ServiceDirectory)
{
return XmlConstants.HttpMethodGet;
}
else if (description.TargetKind == RequestTargetKind.Batch)
{
return XmlConstants.HttpMethodPost;
}
else
{
int index = description.GetIndexOfTargetEntityResource();
ResourceContainer container = description.SegmentInfos[index].TargetContainer;
return this.GetAllowedMethods(container, description.IsSingleResult);
}
}
///
/// Gets a string representation of allowed methods on the container (with the specified target cardinality),
/// suitable for an 'Allow' header.
///
/// Targetted container, possibly null.
/// true if a single entity is checked for rights; false otherwise.
/// A value for an 'Allow' header; null if is null.
internal string GetAllowedMethods(ResourceContainer container, bool single)
{
if (container == null)
{
return null;
}
else
{
System.Text.StringBuilder result = new System.Text.StringBuilder();
EntitySetRights rights = this.GetResourceContainerRights(container);
if (single)
{
AppendRight(rights, EntitySetRights.ReadSingle, XmlConstants.HttpMethodGet, result);
AppendRight(rights, EntitySetRights.WriteDelete, XmlConstants.HttpMethodDelete, result);
AppendRight(rights, EntitySetRights.WriteReplace, XmlConstants.HttpMethodPut, result);
AppendRight(rights, EntitySetRights.WriteMerge, XmlConstants.HttpMethodMerge, result);
}
else
{
AppendRight(rights, EntitySetRights.ReadMultiple, XmlConstants.HttpMethodGet, result);
AppendRight(rights, EntitySetRights.WriteAppend, XmlConstants.HttpMethodPost, result);
}
return result.ToString();
}
}
/// Gets the effective rights on the specified container.
/// Container to get rights for.
/// The effective rights as per this configuration.
internal EntitySetRights GetResourceContainerRights(ResourceContainer container)
{
Debug.Assert(container != null, "container != null");
EntitySetRights result;
if (this.resourceRights == null)
{
result = container.Rights;
}
else
{
if (!this.resourceRights.TryGetValue(container, out result))
{
result = this.rightsForUnspecifiedResourceContainer;
}
}
return result;
}
/// Gets the effective rights on the specified operation.
/// Operation to get rights for.
/// The effective rights as per this configuration.
internal ServiceOperationRights GetServiceOperationRights(ServiceOperation operation)
{
Debug.Assert(operation != null, "operation != null");
ServiceOperationRights result;
if (this.serviceRights == null)
{
result = operation.Rights;
}
else
{
if (!this.serviceRights.TryGetValue(operation, out result))
{
result = this.rightsForUnspecifiedServiceOperation;
}
}
return result;
}
/// Returns the list of types registered by the data service.
/// The list of types as registered by the data service
internal IEnumerable GetKnownTypes()
{
return this.knownTypes;
}
///
/// Invokes the static service initialization methods on the
/// specified type family.
///
/// Type of service to initialize for.
internal void InvokeStaticInitialization(Type type)
{
Debug.Assert(type != null, "type != null");
// Build a stack going from most-specific to least-specific type.
BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public;
while (type != null)
{
MethodInfo[] methods = type.GetMethods(flags);
for (int i = 0; i < methods.Length; i++)
{
MethodInfo method = methods[i];
Debug.Assert(method.IsStatic, "method.IsStatic");
if (method.Name == XmlConstants.ClrServiceInitializationMethodName &&
method.ReturnType == typeof(void))
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 1 &&
parameters[0].ParameterType == typeof(IDataServiceConfiguration) &&
!parameters[0].IsOut)
{
object[] initializeParameters = new object[] { this };
try
{
method.Invoke(null, initializeParameters);
}
catch (TargetInvocationException exception)
{
ErrorHandler.HandleTargetInvocationException(exception);
throw;
}
return;
}
}
}
type = type.BaseType;
}
}
///
/// Register authorization callbacks specified on the given
/// .
///
/// Type of web data service to check.
internal void RegisterCallbacks(Type type)
{
Debug.Assert(type != null, "type != null");
BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
while (type != null)
{
MethodInfo[] methods = type.GetMethods(flags);
for (int i = 0; i < methods.Length; i++)
{
MethodInfo method = methods[i];
QueryInterceptorAttribute[] queryAttributes = (QueryInterceptorAttribute[])
method.GetCustomAttributes(typeof(QueryInterceptorAttribute), true /* inherit */);
foreach (QueryInterceptorAttribute attribute in queryAttributes)
{
ResourceContainer container = this.provider.TryResolveContainerName(attribute.EntitySetName);
if (container == null)
{
string message = Strings.DataService_AttributeEntitySetNotFound(
attribute.EntitySetName,
method.Name,
type.FullName);
throw new InvalidOperationException(message);
}
CheckQueryInterceptorSignature(type, method, container);
if (!method.IsAbstract)
{
container.AddReadAuthorizationMethod(method);
}
}
ChangeInterceptorAttribute[] changeAttributes = (ChangeInterceptorAttribute[])
method.GetCustomAttributes(typeof(ChangeInterceptorAttribute), true /* inherit */);
foreach (ChangeInterceptorAttribute attribute in changeAttributes)
{
ResourceContainer container = this.provider.TryResolveContainerName(attribute.EntitySetName);
if (container == null)
{
string message = Strings.DataService_AttributeEntitySetNotFound(
attribute.EntitySetName,
method.Name,
type.FullName);
throw new InvalidOperationException(message);
}
// Check the signature.
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 2)
{
string message = Strings.DataService_ChangeInterceptorIncorrectParameterCount(
method.Name,
type.FullName,
parameters.Length);
throw new InvalidOperationException(message);
}
CheckParameterIsNotOut(method, parameters[0]);
CheckParameterIsNotOut(method, parameters[1]);
Type elementParameterType = parameters[0].ParameterType;
if (!elementParameterType.IsAssignableFrom(container.ElementType))
{
string message = Strings.DataService_AuthorizationParameterNotAssignable(
parameters[0].Name,
method.Name,
type.FullName,
elementParameterType.FullName,
container.ElementType);
throw new InvalidOperationException(message);
}
Type actionParameterType = parameters[1].ParameterType;
if (actionParameterType != typeof(UpdateOperations))
{
string message = Strings.DataService_AuthorizationParameterNotResourceAction(
parameters[1].Name,
method.Name,
type.FullName,
typeof(UpdateOperations).FullName);
throw new InvalidOperationException(message);
}
Type returnType = method.ReturnType;
if (returnType != typeof(void))
{
string message = Strings.DataService_AuthorizationMethodNotVoid(
method.Name,
type.FullName,
returnType.FullName);
throw new InvalidOperationException(message);
}
if (!method.IsAbstract)
{
container.AddWriteAuthorizationMethod(method);
}
}
}
type = type.BaseType;
}
}
/// Seals this configuration instance and prevents further changes.
///
/// This method should be called after the configuration has been set up and before it's placed on the
/// metadata cache for sharing.
///
internal void Seal()
{
Debug.Assert(!this.configurationSealed, "!configurationSealed - otherwise .Seal is invoked multiple times");
this.configurationSealed = true;
this.provider = null;
}
#endregion Internal methods.
#region Private methods.
///
/// Appends the of a right if the right is enabled
/// on .
///
/// Rights to be checked.
/// Right being looked for.
/// Name of right to append.
/// Comma-separated list of right names to append to.
private static void AppendRight(EntitySetRights entitySetRights, EntitySetRights test, string name, System.Text.StringBuilder builder)
{
Debug.Assert(builder != null, "builder != null");
if (0 != (entitySetRights & test))
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(name);
}
}
/// Checks that the specified has a correct signature.
/// Service type.
/// Method to check.
/// Container associated with the interceptor.
private static void CheckQueryInterceptorSignature(Type type, MethodInfo method, ResourceContainer container)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(method != null, "method != null");
Debug.Assert(container != null, "container != null");
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 0)
{
throw new InvalidOperationException(Strings.DataService_QueryInterceptorIncorrectParameterCount(method.Name, type.FullName, parameters.Length));
}
Type lambdaType = typeof(Func<,>).MakeGenericType(container.ElementType, typeof(bool));
Type expectedReturnType = typeof(Expression<>).MakeGenericType(lambdaType);
Type returnType = method.ReturnType;
if (returnType == typeof(void))
{
throw new InvalidOperationException(Strings.DataService_AuthorizationMethodVoid(method.Name, type.FullName, expectedReturnType));
}
else if (!expectedReturnType.IsAssignableFrom(returnType))
{
Type nullableLambdaType = typeof(Func<,>).MakeGenericType(container.ElementType, typeof(bool?));
if (!(typeof(Expression<>).MakeGenericType(nullableLambdaType).IsAssignableFrom(returnType)))
{
throw new InvalidOperationException(
Strings.DataService_AuthorizationReturnTypeNotAssignable(method.Name, type.FullName, returnType.FullName, expectedReturnType.FullName));
}
}
}
/// Verifies that the specified is not an [out] parameter.
/// Method with parameter to check.
/// Parameter to check.
private static void CheckParameterIsNotOut(MethodInfo method, ParameterInfo parameter)
{
Debug.Assert(method != null, "method != null");
Debug.Assert(parameter != null, "parameter != null");
if (parameter.IsOut)
{
throw new InvalidOperationException(Strings.DataService_ParameterIsOut(method.DeclaringType.FullName, method.Name, parameter.Name));
}
}
///
/// Checks that the specified for the named property is not negative and that the
/// configuration isn't sealed.
///
/// Value to check.
/// Parameter name.
/// The to set.
///
/// This method is typically used in properties with the following pattern:
/// public int Foo { get {... } set { this.foo = this.CheckNonNegativeProperty(value, "Foo"); } }
///
private int CheckNonNegativeProperty(int value, string propertyName)
{
this.CheckNotSealed();
if (value < 0)
{
throw new ArgumentOutOfRangeException("value", value, Strings.PropertyRequiresNonNegativeNumber(propertyName));
}
return value;
}
/// Checks that this configuration hasn't been sealed yet.
private void CheckNotSealed()
{
if (this.configurationSealed)
{
string message = Strings.DataServiceConfiguration_NoChangesAllowed(XmlConstants.ClrServiceInitializationMethodName);
throw new InvalidOperationException(message);
}
}
#endregion Private methods.
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides an implementation of the IDataServiceConfiguration
// interface.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Data.Services.Providers;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#endregion Namespaces.
/// Use this class to manage the configuration data for a data service.
internal class DataServiceConfiguration : IDataServiceConfiguration
{
#region Private fields.
/// Whether this configuration has been sealed.
private bool configurationSealed;
/// Maximum number of change sets and query operations in a batch.
private int maxBatchCount;
/// Maximum number of changes in a change set.
private int maxChangeSetCount;
/// Maximum number of segments to be expanded allowed in a request.
private int maxExpandCount;
/// Maximum number of segments in a single $expand path.
private int maxExpandDepth;
/// Maximum number of elements in each returned collection (top-level or expanded).
private int maxResultsPerCollection;
/// maximum number of objects that can be referred in a single insert request.
private int maxObjectCountOnInsert;
/// The provider for the web service.
private IDataServiceProvider provider;
/// Rights used for unspecified resource containers.
private EntitySetRights rightsForUnspecifiedResourceContainer;
/// Rights used for unspecified service operations.
private ServiceOperationRights rightsForUnspecifiedServiceOperation;
/// A lookup of containers to their rights.
private Dictionary resourceRights;
/// A lookup of service operations to their rights.
private Dictionary serviceRights;
/// A list of known types.
private List knownTypes;
/// Whether verbose errors should be returned by default.
private bool useVerboseErrors;
#endregion Private fields.
#region Constructor.
///
/// Initializes a new with
/// the specified .
///
/// Non-null provider for this configuration.
internal DataServiceConfiguration(IDataServiceProvider provider)
{
Debug.Assert(provider != null, "provider != null");
this.provider = provider;
this.resourceRights = new Dictionary();
this.serviceRights = new Dictionary();
this.rightsForUnspecifiedResourceContainer = EntitySetRights.None;
this.rightsForUnspecifiedServiceOperation = ServiceOperationRights.None;
this.knownTypes = new List();
this.maxBatchCount = Int32.MaxValue;
this.maxChangeSetCount = Int32.MaxValue;
this.maxExpandCount = Int32.MaxValue;
this.maxExpandDepth = Int32.MaxValue;
this.maxResultsPerCollection = Int32.MaxValue;
this.maxObjectCountOnInsert = Int32.MaxValue;
}
#endregion Constructor.
#region IDataServiceConfiguration implementation.
/// Maximum number of change sets and query operations in a batch.
public int MaxBatchCount
{
get { return this.maxBatchCount; }
set { this.maxBatchCount = this.CheckNonNegativeProperty(value, "MaxBatchCount"); }
}
/// Maximum number of changes in a change set.
public int MaxChangesetCount
{
get { return this.maxChangeSetCount; }
set { this.maxChangeSetCount = this.CheckNonNegativeProperty(value, "MaxChangesetCount"); }
}
/// Maximum number of segments to be expanded allowed in a request.
public int MaxExpandCount
{
get { return this.maxExpandCount; }
set { this.maxExpandCount = this.CheckNonNegativeProperty(value, "MaxExpandCount"); }
}
/// Maximum number of segments in a single $expand path.
public int MaxExpandDepth
{
get { return this.maxExpandDepth; }
set { this.maxExpandDepth = this.CheckNonNegativeProperty(value, "MaxExpandDepth"); }
}
/// Maximum number of elements in each returned collection (top-level or expanded).
public int MaxResultsPerCollection
{
get { return this.maxResultsPerCollection; }
set { this.maxResultsPerCollection = this.CheckNonNegativeProperty(value, "MaxResultsPerCollection"); }
}
/// Maximum number of objects that can be referred in a single POST request.
public int MaxObjectCountOnInsert
{
get { return this.maxObjectCountOnInsert; }
set { this.maxObjectCountOnInsert = this.CheckNonNegativeProperty(value, "MaxObjectCountOnInsert"); }
}
/// Gets or sets whether verbose errors should be used by default.
///
/// This property sets the default for the whole service; individual responses may behave differently
/// depending on the value of the VerboseResponse property of the arguments to the HandleException
/// method on the class.
///
public bool UseVerboseErrors
{
get
{
return this.useVerboseErrors;
}
set
{
this.CheckNotSealed();
this.useVerboseErrors = value;
}
}
/// Sets the access rights on the specified resource container.
///
/// Name of resource container to set; '*' to indicate all
/// resource containers not otherwise specified.
///
/// Rights to be granted to this resource.
public void SetEntitySetAccessRule(string name, EntitySetRights rights)
{
this.CheckNotSealed();
if (name == null)
{
throw Error.ArgumentNull("name");
}
WebUtil.CheckResourceContainerRights(rights, "rights");
if (name == "*")
{
this.rightsForUnspecifiedResourceContainer = rights;
}
else
{
ResourceContainer container = this.provider.TryResolveContainerName(name);
if (container == null)
{
throw new ArgumentException(Strings.DataServiceConfiguration_ResourceNameNotFound(name), "name");
}
this.resourceRights[container] = rights;
}
}
/// Sets the access rights on the specified service operation.
///
/// Name of service operation to set; '*' to indicate all
/// service operations not otherwise specified.
///
/// Rights to be granted to this operation.
public void SetServiceOperationAccessRule(string name, ServiceOperationRights rights)
{
this.CheckNotSealed();
if (name == null)
{
throw new ArgumentNullException("name");
}
WebUtil.CheckServiceOperationRights(rights, "rights");
ServiceOperation serviceOperation = this.provider.TryResolveServiceOperation(name);
if (name == "*")
{
this.rightsForUnspecifiedServiceOperation = rights;
}
else
{
if (serviceOperation == null)
{
throw new ArgumentException(Strings.DataServiceConfiguration_ServiceNameNotFound(name), "name");
}
this.serviceRights[serviceOperation] = rights;
}
}
///
/// Add the type to the list of known types. If there is a type that needs to be added since
/// Astoria runtime can't detect it using the default set of rules, providers can add them using
/// this method
///
/// type which needs to be added to the known types collection
public void RegisterKnownType(Type type)
{
this.CheckNotSealed();
this.knownTypes.Add(type);
}
#endregion IDataServiceConfiguration implementation.
#region Internal methods.
/// Composes all query interceptors into a single expression.
/// Web service instance.
/// Container for which interceptors should run.
/// An expression the filter for query interceptors, possibly null.
internal static Expression ComposeQueryInterceptors(IDataService service, ResourceContainer container)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(container != null, "container != null");
MethodInfo[] methods = container.ReadAuthorizationMethods;
if (methods == null || methods.Length == 0)
{
return null;
}
LambdaExpression filter = null;
for (int i = 0; i < methods.Length; i++)
{
Expression predicate = (Expression)methods[i].Invoke(service.Instance, WebUtil.EmptyObjectArray);
if (predicate == null)
{
throw new InvalidOperationException(Strings.DataService_AuthorizationReturnedNullQuery(methods[i].Name, methods[i].DeclaringType.FullName));
}
Debug.Assert(predicate is LambdaExpression, "predicate is LambdaExpression -- otherwise signature check missed something.");
if (filter == null)
{
filter = (LambdaExpression)predicate;
}
else
{
ParameterExpression parameter = filter.Parameters[0];
Expression adjustedPredicate = ParameterReplacerVisitor.Replace(
((LambdaExpression)predicate).Body, // expression
((LambdaExpression)predicate).Parameters[0], // oldParameter
parameter); // newParameter
filter = Expression.Lambda(Expression.And(filter.Body, adjustedPredicate), parameter);
}
}
return filter;
}
///
/// Composes the specified for the
/// given with authorization
/// callbacks.
///
/// Data service on which to invoke method.
/// Resource container to compose with.
/// Query to compose.
/// The resulting composed query.
internal static IQueryable ComposeResourceContainer(IDataService service, ResourceContainer container, IQueryable query)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(container != null, "container != null");
Debug.Assert(query != null, "query != null");
MethodInfo[] methods = container.ReadAuthorizationMethods;
if (methods != null)
{
for (int i = 0; i < methods.Length; i++)
{
Expression predicate = (Expression)methods[i].Invoke(service.Instance, WebUtil.EmptyObjectArray);
if (predicate == null)
{
throw new InvalidOperationException(Strings.DataService_AuthorizationReturnedNullQuery(methods[i].Name, methods[i].DeclaringType.FullName));
}
Debug.Assert(predicate is LambdaExpression, "predicate is LambdaExpression -- otherwise signature check missed something.");
query = RequestUriProcessor.InvokeWhereForType(query, (LambdaExpression)predicate);
}
}
return query;
}
/// Applies the configuration to the current provider.
/// Instance of the data source for the provider.
/// This method should be called exactly once.
internal void ApplyToProvider(object dataSourceInstance)
{
Debug.Assert(this.provider != null, "this.provider != null");
Debug.Assert(this.resourceRights != null, "this.resourceRights != null - otherwise, ApplyToProvider has been called multiple times.");
this.provider.ApplyConfiguration(this, dataSourceInstance);
this.resourceRights = null;
this.serviceRights = null;
this.knownTypes = null;
}
/// Checks whether this request has the specified rights.
/// Container to check.
/// Required rights.
/// Thrown if aren't available.
internal void CheckResourceRights(ResourceContainer container, EntitySetRights requiredRights)
{
Debug.Assert(container != null, "container != null");
Debug.Assert(requiredRights != EntitySetRights.None, "requiredRights != EntitySetRights.None");
EntitySetRights effectiveRights = this.GetResourceContainerRights(container);
if ((requiredRights & effectiveRights) == 0)
{
throw DataServiceException.CreateForbidden();
}
}
/// Checks whether this request has the specified reading rights.
/// Container to check.
/// Whether a single or multiple resources are requested.
/// Thrown if aren't available.
internal void CheckResourceRightsForRead(ResourceContainer container, bool singleResult)
{
Debug.Assert(container != null, "container != null");
EntitySetRights requiredRights = singleResult ? EntitySetRights.ReadSingle : EntitySetRights.ReadMultiple;
this.CheckResourceRights(container, requiredRights);
}
/// Checks whether this request has the specified rights.
/// Operation to check.
/// Required rights.
/// Thrown if aren't available.
internal void CheckServiceRights(ServiceOperation operation, ServiceOperationRights requiredRights)
{
Debug.Assert(operation != null, "operation != null");
Debug.Assert(requiredRights != ServiceOperationRights.None, "requiredRights != EntitySetRights.None");
ServiceOperationRights effectiveRights = this.GetServiceOperationRights(operation);
if ((requiredRights & effectiveRights) == 0)
{
throw DataServiceException.CreateForbidden();
}
}
/// Checks whether this request has the specified rights.
/// Operation to check.
/// Whether a single or multiple resources are requested.
/// Thrown if aren't available.
internal void CheckServiceRights(ServiceOperation operation, bool singleResult)
{
Debug.Assert(operation != null, "operation != null");
if (operation.ResultKind != ServiceOperationResultKind.Nothing)
{
ServiceOperationRights requiredRights = singleResult ? ServiceOperationRights.ReadSingle : ServiceOperationRights.ReadMultiple;
this.CheckServiceRights(operation, requiredRights);
}
}
/// Gets a string with methods allowed on the target for the .
/// Description with target.
/// A string with methods allowed on the description; possibly null.
internal string GetAllowedMethods(RequestDescription description)
{
Debug.Assert(description != null, "description != null");
Debug.Assert(
description.TargetKind != RequestTargetKind.Nothing,
"description.TargetKind != RequestTargetKind.Nothing - otherwise it hasn't been determined yet");
Debug.Assert(
description.TargetKind != RequestTargetKind.VoidServiceOperation,
"description.TargetKind != RequestTargetKind.VoidServiceOperation - this method is only for containers");
if (description.TargetKind == RequestTargetKind.Metadata ||
description.TargetKind == RequestTargetKind.ServiceDirectory)
{
return XmlConstants.HttpMethodGet;
}
else if (description.TargetKind == RequestTargetKind.Batch)
{
return XmlConstants.HttpMethodPost;
}
else
{
int index = description.GetIndexOfTargetEntityResource();
ResourceContainer container = description.SegmentInfos[index].TargetContainer;
return this.GetAllowedMethods(container, description.IsSingleResult);
}
}
///
/// Gets a string representation of allowed methods on the container (with the specified target cardinality),
/// suitable for an 'Allow' header.
///
/// Targetted container, possibly null.
/// true if a single entity is checked for rights; false otherwise.
/// A value for an 'Allow' header; null if is null.
internal string GetAllowedMethods(ResourceContainer container, bool single)
{
if (container == null)
{
return null;
}
else
{
System.Text.StringBuilder result = new System.Text.StringBuilder();
EntitySetRights rights = this.GetResourceContainerRights(container);
if (single)
{
AppendRight(rights, EntitySetRights.ReadSingle, XmlConstants.HttpMethodGet, result);
AppendRight(rights, EntitySetRights.WriteDelete, XmlConstants.HttpMethodDelete, result);
AppendRight(rights, EntitySetRights.WriteReplace, XmlConstants.HttpMethodPut, result);
AppendRight(rights, EntitySetRights.WriteMerge, XmlConstants.HttpMethodMerge, result);
}
else
{
AppendRight(rights, EntitySetRights.ReadMultiple, XmlConstants.HttpMethodGet, result);
AppendRight(rights, EntitySetRights.WriteAppend, XmlConstants.HttpMethodPost, result);
}
return result.ToString();
}
}
/// Gets the effective rights on the specified container.
/// Container to get rights for.
/// The effective rights as per this configuration.
internal EntitySetRights GetResourceContainerRights(ResourceContainer container)
{
Debug.Assert(container != null, "container != null");
EntitySetRights result;
if (this.resourceRights == null)
{
result = container.Rights;
}
else
{
if (!this.resourceRights.TryGetValue(container, out result))
{
result = this.rightsForUnspecifiedResourceContainer;
}
}
return result;
}
/// Gets the effective rights on the specified operation.
/// Operation to get rights for.
/// The effective rights as per this configuration.
internal ServiceOperationRights GetServiceOperationRights(ServiceOperation operation)
{
Debug.Assert(operation != null, "operation != null");
ServiceOperationRights result;
if (this.serviceRights == null)
{
result = operation.Rights;
}
else
{
if (!this.serviceRights.TryGetValue(operation, out result))
{
result = this.rightsForUnspecifiedServiceOperation;
}
}
return result;
}
/// Returns the list of types registered by the data service.
/// The list of types as registered by the data service
internal IEnumerable GetKnownTypes()
{
return this.knownTypes;
}
///
/// Invokes the static service initialization methods on the
/// specified type family.
///
/// Type of service to initialize for.
internal void InvokeStaticInitialization(Type type)
{
Debug.Assert(type != null, "type != null");
// Build a stack going from most-specific to least-specific type.
BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public;
while (type != null)
{
MethodInfo[] methods = type.GetMethods(flags);
for (int i = 0; i < methods.Length; i++)
{
MethodInfo method = methods[i];
Debug.Assert(method.IsStatic, "method.IsStatic");
if (method.Name == XmlConstants.ClrServiceInitializationMethodName &&
method.ReturnType == typeof(void))
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 1 &&
parameters[0].ParameterType == typeof(IDataServiceConfiguration) &&
!parameters[0].IsOut)
{
object[] initializeParameters = new object[] { this };
try
{
method.Invoke(null, initializeParameters);
}
catch (TargetInvocationException exception)
{
ErrorHandler.HandleTargetInvocationException(exception);
throw;
}
return;
}
}
}
type = type.BaseType;
}
}
///
/// Register authorization callbacks specified on the given
/// .
///
/// Type of web data service to check.
internal void RegisterCallbacks(Type type)
{
Debug.Assert(type != null, "type != null");
BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
while (type != null)
{
MethodInfo[] methods = type.GetMethods(flags);
for (int i = 0; i < methods.Length; i++)
{
MethodInfo method = methods[i];
QueryInterceptorAttribute[] queryAttributes = (QueryInterceptorAttribute[])
method.GetCustomAttributes(typeof(QueryInterceptorAttribute), true /* inherit */);
foreach (QueryInterceptorAttribute attribute in queryAttributes)
{
ResourceContainer container = this.provider.TryResolveContainerName(attribute.EntitySetName);
if (container == null)
{
string message = Strings.DataService_AttributeEntitySetNotFound(
attribute.EntitySetName,
method.Name,
type.FullName);
throw new InvalidOperationException(message);
}
CheckQueryInterceptorSignature(type, method, container);
if (!method.IsAbstract)
{
container.AddReadAuthorizationMethod(method);
}
}
ChangeInterceptorAttribute[] changeAttributes = (ChangeInterceptorAttribute[])
method.GetCustomAttributes(typeof(ChangeInterceptorAttribute), true /* inherit */);
foreach (ChangeInterceptorAttribute attribute in changeAttributes)
{
ResourceContainer container = this.provider.TryResolveContainerName(attribute.EntitySetName);
if (container == null)
{
string message = Strings.DataService_AttributeEntitySetNotFound(
attribute.EntitySetName,
method.Name,
type.FullName);
throw new InvalidOperationException(message);
}
// Check the signature.
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 2)
{
string message = Strings.DataService_ChangeInterceptorIncorrectParameterCount(
method.Name,
type.FullName,
parameters.Length);
throw new InvalidOperationException(message);
}
CheckParameterIsNotOut(method, parameters[0]);
CheckParameterIsNotOut(method, parameters[1]);
Type elementParameterType = parameters[0].ParameterType;
if (!elementParameterType.IsAssignableFrom(container.ElementType))
{
string message = Strings.DataService_AuthorizationParameterNotAssignable(
parameters[0].Name,
method.Name,
type.FullName,
elementParameterType.FullName,
container.ElementType);
throw new InvalidOperationException(message);
}
Type actionParameterType = parameters[1].ParameterType;
if (actionParameterType != typeof(UpdateOperations))
{
string message = Strings.DataService_AuthorizationParameterNotResourceAction(
parameters[1].Name,
method.Name,
type.FullName,
typeof(UpdateOperations).FullName);
throw new InvalidOperationException(message);
}
Type returnType = method.ReturnType;
if (returnType != typeof(void))
{
string message = Strings.DataService_AuthorizationMethodNotVoid(
method.Name,
type.FullName,
returnType.FullName);
throw new InvalidOperationException(message);
}
if (!method.IsAbstract)
{
container.AddWriteAuthorizationMethod(method);
}
}
}
type = type.BaseType;
}
}
/// Seals this configuration instance and prevents further changes.
///
/// This method should be called after the configuration has been set up and before it's placed on the
/// metadata cache for sharing.
///
internal void Seal()
{
Debug.Assert(!this.configurationSealed, "!configurationSealed - otherwise .Seal is invoked multiple times");
this.configurationSealed = true;
this.provider = null;
}
#endregion Internal methods.
#region Private methods.
///
/// Appends the of a right if the right is enabled
/// on .
///
/// Rights to be checked.
/// Right being looked for.
/// Name of right to append.
/// Comma-separated list of right names to append to.
private static void AppendRight(EntitySetRights entitySetRights, EntitySetRights test, string name, System.Text.StringBuilder builder)
{
Debug.Assert(builder != null, "builder != null");
if (0 != (entitySetRights & test))
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(name);
}
}
/// Checks that the specified has a correct signature.
/// Service type.
/// Method to check.
/// Container associated with the interceptor.
private static void CheckQueryInterceptorSignature(Type type, MethodInfo method, ResourceContainer container)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(method != null, "method != null");
Debug.Assert(container != null, "container != null");
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 0)
{
throw new InvalidOperationException(Strings.DataService_QueryInterceptorIncorrectParameterCount(method.Name, type.FullName, parameters.Length));
}
Type lambdaType = typeof(Func<,>).MakeGenericType(container.ElementType, typeof(bool));
Type expectedReturnType = typeof(Expression<>).MakeGenericType(lambdaType);
Type returnType = method.ReturnType;
if (returnType == typeof(void))
{
throw new InvalidOperationException(Strings.DataService_AuthorizationMethodVoid(method.Name, type.FullName, expectedReturnType));
}
else if (!expectedReturnType.IsAssignableFrom(returnType))
{
Type nullableLambdaType = typeof(Func<,>).MakeGenericType(container.ElementType, typeof(bool?));
if (!(typeof(Expression<>).MakeGenericType(nullableLambdaType).IsAssignableFrom(returnType)))
{
throw new InvalidOperationException(
Strings.DataService_AuthorizationReturnTypeNotAssignable(method.Name, type.FullName, returnType.FullName, expectedReturnType.FullName));
}
}
}
/// Verifies that the specified is not an [out] parameter.
/// Method with parameter to check.
/// Parameter to check.
private static void CheckParameterIsNotOut(MethodInfo method, ParameterInfo parameter)
{
Debug.Assert(method != null, "method != null");
Debug.Assert(parameter != null, "parameter != null");
if (parameter.IsOut)
{
throw new InvalidOperationException(Strings.DataService_ParameterIsOut(method.DeclaringType.FullName, method.Name, parameter.Name));
}
}
///
/// Checks that the specified for the named property is not negative and that the
/// configuration isn't sealed.
///
/// Value to check.
/// Parameter name.
/// The to set.
///
/// This method is typically used in properties with the following pattern:
/// public int Foo { get {... } set { this.foo = this.CheckNonNegativeProperty(value, "Foo"); } }
///
private int CheckNonNegativeProperty(int value, string propertyName)
{
this.CheckNotSealed();
if (value < 0)
{
throw new ArgumentOutOfRangeException("value", value, Strings.PropertyRequiresNonNegativeNumber(propertyName));
}
return value;
}
/// Checks that this configuration hasn't been sealed yet.
private void CheckNotSealed()
{
if (this.configurationSealed)
{
string message = Strings.DataServiceConfiguration_NoChangesAllowed(XmlConstants.ClrServiceInitializationMethodName);
throw new InvalidOperationException(message);
}
}
#endregion Private methods.
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- GeneralTransform.cs
- TextChange.cs
- SourceSwitch.cs
- FormsAuthenticationEventArgs.cs
- Boolean.cs
- ReadOnlyAttribute.cs
- PathParser.cs
- SerializationException.cs
- FontWeightConverter.cs
- _TLSstream.cs
- XmlRawWriter.cs
- TextRangeEditLists.cs
- SerializationInfoEnumerator.cs
- ActivityAction.cs
- WeakReferenceList.cs
- ListComponentEditor.cs
- SqlGenerator.cs
- TreeView.cs
- HttpResponseWrapper.cs
- HtmlTextArea.cs
- QilGeneratorEnv.cs
- Compilation.cs
- RSAPKCS1KeyExchangeDeformatter.cs
- HMAC.cs
- ObjectDataSourceMethodEventArgs.cs
- SafeTokenHandle.cs
- InputGestureCollection.cs
- XamlNamespaceHelper.cs
- ObjectFactoryCodeDomTreeGenerator.cs
- DbCommandDefinition.cs
- SafeRightsManagementPubHandle.cs
- XmlBinaryReader.cs
- HwndSubclass.cs
- DocumentApplication.cs
- HttpStreamFormatter.cs
- XmlElementAttributes.cs
- CollectionViewSource.cs
- CellPartitioner.cs
- ThicknessKeyFrameCollection.cs
- InputScopeConverter.cs
- Region.cs
- IdnMapping.cs
- ImageField.cs
- XmlSchemaSimpleTypeUnion.cs
- FastEncoder.cs
- __Filters.cs
- DispatcherTimer.cs
- MessageBox.cs
- IDReferencePropertyAttribute.cs
- PeerToPeerException.cs
- TimeStampChecker.cs
- MetaTableHelper.cs
- ThreadExceptionDialog.cs
- TypeBuilder.cs
- ContainerTracking.cs
- PropertyMapper.cs
- GenericWebPart.cs
- InternalDispatchObject.cs
- FontStretches.cs
- CompositeActivityValidator.cs
- ArrangedElementCollection.cs
- CapabilitiesAssignment.cs
- DataColumnMappingCollection.cs
- coordinatorfactory.cs
- PersonalizationStateInfo.cs
- ConfigXmlSignificantWhitespace.cs
- EntityClassGenerator.cs
- CommandPlan.cs
- ListItemViewAttribute.cs
- ListViewItemEventArgs.cs
- OwnerDrawPropertyBag.cs
- StorageScalarPropertyMapping.cs
- HttpRequestBase.cs
- xmlglyphRunInfo.cs
- X500Name.cs
- AxHost.cs
- DoWorkEventArgs.cs
- TreeNodeStyle.cs
- EventLogTraceListener.cs
- SystemFonts.cs
- TextDecorationCollection.cs
- InputLanguage.cs
- QfeChecker.cs
- ContentElement.cs
- SoapObjectReader.cs
- ImageConverter.cs
- NodeLabelEditEvent.cs
- PostBackOptions.cs
- _FtpControlStream.cs
- SafeThemeHandle.cs
- MailDefinition.cs
- FixedSOMContainer.cs
- ComponentDispatcherThread.cs
- HiddenFieldPageStatePersister.cs
- GifBitmapDecoder.cs
- ComponentRenameEvent.cs
- RichTextBox.cs
- EditorPartCollection.cs
- DetailsViewRowCollection.cs
- HitTestDrawingContextWalker.cs