예제 #1
0
        /// <summary>
        /// Handles a DELETE request to delete an entity.
        /// </summary>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The task object that contains the deletion result.</returns>
        public async Task <IHttpActionResult> Delete(CancellationToken cancellationToken)
        {
            ODataPath     path      = this.GetPath();
            IEdmEntitySet entitySet = path.NavigationSource as IEdmEntitySet;

            if (entitySet == null)
            {
                throw new NotImplementedException(Resources.DeleteOnlySupportedOnEntitySet);
            }

            DataModificationEntry deleteEntry = new DataModificationEntry(
                entitySet.Name,
                path.EdmType.FullTypeName(),
                RestierQueryBuilder.GetPathKeyValues(path),
                this.GetOriginalValues(),
                null);

            RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet();

            if (changeSetProperty == null)
            {
                ChangeSet changeSet = new ChangeSet();
                changeSet.Entries.Add(deleteEntry);

                SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken);
            }
            else
            {
                changeSetProperty.ChangeSet.Entries.Add(deleteEntry);

                await changeSetProperty.OnChangeSetCompleted();
            }

            return(this.StatusCode(HttpStatusCode.NoContent));
        }
예제 #2
0
        public async Task TestEntityFilterReturnsTask()
        {
            TestEntityFilterReturnsTaskApi   api            = new TestEntityFilterReturnsTaskApi();
            DataModificationEntry <Customer> createCustomer = new DataModificationEntry <Customer>(
                "Customers",
                "Customer",
                null,
                null,
                new Dictionary <string, object>()
            {
                { "CustomerID", "NEW01" },
                { "CompanyName", "New Cust" },
            });

            await api.SubmitAsync(new ChangeSet(new ChangeSetEntry[] { createCustomer }));

            NorthwindContext ctx = new NorthwindContext();

#if EF7
            Customer newCustomer = await ctx.Customers.FirstOrDefaultAsync(e => e.CustomerID == "NEW01");
#else
            Customer newCustomer = await ctx.Customers.FindAsync("NEW01");
#endif
            // The "OnInserting" should have been appended by the OnInsertingCustomers filter
            Assert.Equal("New CustOnInserting", newCustomer.CompanyName);

            ctx.Customers.Remove(newCustomer);
            await ctx.SaveChangesAsync();
        }
예제 #3
0
        private static string GetAuthorizeMethodName(ChangeSetEntry entry)
        {
            switch (entry.Type)
            {
            case ChangeSetEntryType.DataModification:
                DataModificationEntry dataModification = (DataModificationEntry)entry;
                string operationName = null;
                if (dataModification.IsNew)
                {
                    operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationInsert;
                }
                else if (dataModification.IsUpdate)
                {
                    operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationUpdate;
                }
                else if (dataModification.IsDelete)
                {
                    operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationDelete;
                }

                return(operationName + dataModification.EntitySetName);

            case ChangeSetEntryType.ActionInvocation:
                ActionInvocationEntry actionEntry = (ActionInvocationEntry)entry;
                return(ConventionBasedChangeSetConstants.AuthorizeMethodActionInvocationExecute +
                       actionEntry.ActionName);

            default:
                throw new InvalidOperationException(string.Format(
                                                        CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, entry.Type));
            }
        }
예제 #4
0
        private static async Task <object> FindEntity(SubmitContext context, DataModificationEntry entry, CancellationToken cancellationToken)
        {
            IQueryable query = Domain.Source(context.DomainContext, entry.EntitySetName);

            query = entry.ApplyTo(query);

            QueryResult result = await Domain.QueryAsync(context.DomainContext, new QueryRequest(query), cancellationToken);

            object entity = result.Results.SingleOrDefault();

            if (entity == null)
            {
                // TODO GitHubIssue#38 : Handle the case when entity is resolved
                // there are 2 cases where the entity is not found:
                // 1) it doesn't exist
                // 2) concurrency checks have failed
                // we should account for both - I can see 3 options:
                // a. always return "PreConditionFailed" result - this is the canonical behavior of WebAPI OData (see http://blogs.msdn.com/b/webdev/archive/2014/03/13/getting-started-with-asp-net-web-api-2-2-for-odata-v4-0.aspx)
                //  - this makes sense because if someone deleted the record, then you still have a concurrency error
                // b. possibly doing a 2nd query with just the keys to see if the record still exists
                // c. only query with the keys, and then set the DbEntityEntry's OriginalValues to the ETag values, letting the save fail if there are concurrency errors

                //throw new EntityNotFoundException
                throw new InvalidOperationException(Resources.ResourceNotFound);
            }

            return(entity);
        }
예제 #5
0
        private static void SetValues(EntityEntry dbEntry, DataModificationEntry entry)
        {
            foreach (KeyValuePair <string, object> propertyPair in entry.LocalValues)
            {
                PropertyEntry propertyEntry = dbEntry.Property(propertyPair.Key);
                object        value         = propertyPair.Value;
                if (value == null)
                {
                    // If the property value is null, we set null in the entry too.
                    propertyEntry.CurrentValue = null;
                    continue;
                }

                Type type = TypeHelper.GetUnderlyingTypeOrSelf(propertyEntry.Metadata.ClrType);
                value = ConvertToEfValue(type, value);
                if (value != null && !type.IsInstanceOfType(value))
                {
                    var dic = value as IReadOnlyDictionary <string, object>;
                    if (dic == null)
                    {
                        throw new NotSupportedException(string.Format(
                                                            CultureInfo.InvariantCulture,
                                                            Resources.UnsupportedPropertyType,
                                                            propertyPair.Key));
                    }

                    value = Activator.CreateInstance(type);
                    SetValues(value, type, dic);
                }

                propertyEntry.CurrentValue = value;
            }
        }
예제 #6
0
        private static string GetMethodName(ChangeSetEntry entry, string suffix)
        {
            switch (entry.Type)
            {
            case ChangeSetEntryType.DataModification:
                DataModificationEntry dataModification = (DataModificationEntry)entry;
                string operationName = null;
                if (dataModification.IsNew)
                {
                    operationName = "Insert";
                }
                else if (dataModification.IsUpdate)
                {
                    operationName = "Updat";
                }
                else if (dataModification.IsDelete)
                {
                    operationName = "Delet";
                }

                return("On" + operationName + suffix + dataModification.EntitySetName);

            case ChangeSetEntryType.ActionInvocation:
                ActionInvocationEntry actionEntry = (ActionInvocationEntry)entry;
                return("OnExecut" + suffix + actionEntry.ActionName);

            default:
                throw new InvalidOperationException(string.Format(
                                                        CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, entry.Type));
            }
        }
예제 #7
0
        /// <summary>	Searches for the first entity. </summary>
        ///
        /// <exception cref="InvalidOperationException">	Thrown when the requested operation is
        ///                                                 invalid. </exception>
        ///
        /// <param name="context">				The submit context. </param>
        /// <param name="entry">				The entry. </param>
        /// <param name="cancellationToken">	A cancellation token. </param>
        ///
        /// <returns>	The found entity. </returns>
        private static async Task <object> FindEntity(SubmitContext context, DataModificationEntry entry, CancellationToken cancellationToken)
        {
            IQueryable query = Api.Source(context.ApiContext, entry.EntitySetName);

            query = entry.ApplyTo(query);

            QueryResult result = await Api.QueryAsync(
                context.ApiContext,
                new QueryRequest(query),
                cancellationToken);

            object entity = result.Results.SingleOrDefault();

            if (entity == null)
            {
                // TODO GitHubIssue#38 : Handle the case when entity is resolved
                // there are 2 cases where the entity is not found:
                // 1) it doesn't exist
                // 2) concurrency checks have failed
                // we should account for both - I can see 3 options:
                // a. always return "PreConditionFailed" result
                //  - this is the canonical behavior of WebAPI OData, see the following post:
                //    "Getting started with ASP.NET Web API 2.2 for OData v4.0" on http://blogs.msdn.com/b/webdev/.
                //  - this makes sense because if someone deleted the record, then you still have a concurrency error
                // b. possibly doing a 2nd query with just the keys to see if the record still exists
                // c. only query with the keys, and then set the DbEntityEntry's OriginalValues to the ETag values,
                //    letting the save fail if there are concurrency errors

                ////throw new EntityNotFoundException
                throw new InvalidOperationException("Could not find the specified resource.");
            }

            return(entity);
        }
예제 #8
0
        public async Task ComplexTypeUpdate()
        {
            // Arrange
            var libraryApi = new LibraryApi();
            var entry      = new DataModificationEntry(
                "Readers",
                "Person",
                new Dictionary <string, object> {
                { "Id", new Guid("53162782-EA1B-4712-AF26-8AA1D2AC0461") }
            },
                new Dictionary <string, object>(),
                new Dictionary <string, object> {
                { "Addr", new Dictionary <string, object> {
                      { "Zip", "332" }
                  } }
            });
            var changeSet = new ChangeSet(new[] { entry });
            var sc        = new SubmitContext(libraryApi.Context, changeSet);

            // Act
            var changeSetPreparer = libraryApi.Context.Configuration.GetApiService <IChangeSetPreparer>();
            await changeSetPreparer.PrepareAsync(sc, CancellationToken.None);

            var person = entry.Entity as Person;

            // Assert
            Assert.NotNull(person);
            Assert.Equal("332", person.Addr.Zip);
        }
예제 #9
0
        private static async Task PrepareEntry <TEntity>(
            SubmitContext context, DbContext dbContext, DataModificationEntry entry, DbSet <TEntity> set, CancellationToken cancellationToken)
            where TEntity : class
        {
            Type    entityType = typeof(TEntity);
            TEntity entity;

            if (entry.IsNew)
            {
                // TODO: See if Create method is in DbSet<> in future EF7 releases, as the one EF6 has.
                entity = (TEntity)Activator.CreateInstance(typeof(TEntity));

                ChangeSetPreparer.SetValues(entity, entityType, entry.LocalValues);
                set.Add(entity);
            }
            else if (entry.IsDelete)
            {
                entity = (TEntity)await ChangeSetPreparer.FindEntity(context, entry, cancellationToken);

                set.Remove(entity);
            }
            else if (entry.IsUpdate)
            {
                entity = (TEntity)await ChangeSetPreparer.FindEntity(context, entry, cancellationToken);

                var dbEntry = dbContext.Update(entity);
                ChangeSetPreparer.SetValues(dbEntry, entry, entityType);
            }
            else
            {
                throw new NotSupportedException(Resources.DataModificationMustBeCUD);
            }

            entry.Entity = entity;
        }
예제 #10
0
        /// <summary>	Sets the values. </summary>
        ///
        /// <exception cref="NotSupportedException">	Thrown when the requested operation is not
        ///                                             supported. </exception>
        ///
        /// <param name="dbEntry">      The database entry. </param>
        /// <param name="entry">	    The entry. </param>
        /// <param name="entityType">	Type of the entity. </param>
        private static void SetValues(DbEntityEntry dbEntry, DataModificationEntry entry, Type entityType)
        {
            if (entry.IsFullReplaceUpdate)
            {
                // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource
                // in WCF DS, and works as follows:
                //  - Create a new, blank instance of the entity.
                //  - Copy over the key values and set any updated values from the client on the new instance.
                //  - Then apply all the properties of the new instance to the instance to be updated.
                //    This will set any unspecified properties to their default value.
                object newInstance = Activator.CreateInstance(entityType);

                SetValues(newInstance, entityType, entry.EntityKey);
                SetValues(newInstance, entityType, entry.LocalValues);

                dbEntry.CurrentValues.SetValues(newInstance);
            }
            else
            {
                foreach (KeyValuePair <string, object> propertyPair in entry.LocalValues)
                {
                    DbPropertyEntry propertyEntry = dbEntry.Property(propertyPair.Key);
                    object          value         = propertyPair.Value;
                    if (value == null)
                    {
                        // If the property value is null, we set null in the entry too.
                        propertyEntry.CurrentValue = null;
                        continue;
                    }

                    Type type = typeof(string);
                    if (propertyEntry.EntityEntry != null && propertyEntry.EntityEntry.Entity != null)
                    {
                        type = propertyEntry.EntityEntry.Entity.GetType().GetProperty(propertyPair.Key).PropertyType;
                    }
                    else if (propertyEntry.CurrentValue != null)
                    {
                        type = propertyEntry.CurrentValue.GetType();
                    }

                    if (propertyEntry is DbComplexPropertyEntry)
                    {
                        var dic = value as IReadOnlyDictionary <string, object>;
                        if (dic == null)
                        {
                            throw new NotSupportedException(string.Format("Unsupported type for property: {0}.", propertyPair.Key));
                        }

                        value = Activator.CreateInstance(type);
                        SetValues(value, type, dic);
                    }

                    propertyEntry.CurrentValue = ConvertToEfValue(type, value);
                }
            }
        }
예제 #11
0
        private static object CreateFullUpdateInstance(DataModificationEntry entry, Type entityType)
        {
            // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource
            // in WCF DS, and works as follows:
            //  - Create a new, blank instance of the entity.
            //  - Copy over the key values and set any updated values from the client on the new instance.
            //  - Then apply all the properties of the new instance to the instance to be updated.
            //    This will set any unspecified properties to their default value.
            object newInstance = Activator.CreateInstance(entityType);

            ChangeSetPreparer.SetValues(newInstance, entityType, entry.EntityKey);
            ChangeSetPreparer.SetValues(newInstance, entityType, entry.LocalValues);

            return(newInstance);
        }
        /// <inheritdoc/>
        public Task ValidateEntityAsync(
            SubmitContext context,
            ChangeSetEntry entry,
            ValidationResults validationResults,
            CancellationToken cancellationToken)
        {
            Ensure.NotNull(validationResults, "validationResults");
            DataModificationEntry dataModificationEntry = entry as DataModificationEntry;

            if (dataModificationEntry != null)
            {
                object entity = dataModificationEntry.Entity;

                // TODO GitHubIssue#50 : should this PropertyDescriptorCollection be cached?
                PropertyDescriptorCollection properties =
                    new DataAnnotations.AssociatedMetadataTypeTypeDescriptionProvider(entity.GetType())
                    .GetTypeDescriptor(entity).GetProperties();

                DataAnnotations.ValidationContext validationContext = new DataAnnotations.ValidationContext(entity);

                foreach (PropertyDescriptor property in properties)
                {
                    validationContext.MemberName = property.Name;

                    IEnumerable <DataAnnotations.ValidationAttribute> validationAttributes =
                        property.Attributes.OfType <DataAnnotations.ValidationAttribute>();
                    foreach (DataAnnotations.ValidationAttribute validationAttribute in validationAttributes)
                    {
                        object value = property.GetValue(entity);
                        DataAnnotations.ValidationResult validationResult =
                            validationAttribute.GetValidationResult(value, validationContext);
                        if (validationResult != DataAnnotations.ValidationResult.Success)
                        {
                            validationResults.Add(new ValidationResult()
                            {
                                Id           = validationAttribute.GetType().FullName,
                                Message      = validationResult.ErrorMessage,
                                Severity     = ValidationSeverity.Error,
                                Target       = entity,
                                PropertyName = property.Name
                            });
                        }
                    }
                }
            }

            return(Task.WhenAll());
        }
        private static object[] GetParameters(ChangeSetEntry entry)
        {
            switch (entry.Type)
            {
            case ChangeSetEntryType.DataModification:
                DataModificationEntry dataModification = (DataModificationEntry)entry;
                return(new object[] { dataModification.Entity });

            case ChangeSetEntryType.ActionInvocation:
                ActionInvocationEntry actionEntry = (ActionInvocationEntry)entry;
                return(actionEntry.GetArgumentArray());

            default:
                throw new InvalidOperationException(string.Format(
                                                        CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, entry.Type));
            }
        }
예제 #14
0
        private static void SetValues(DbEntityEntry dbEntry, DataModificationEntry entry, Type entityType)
        {
            if (entry.IsFullReplaceUpdate)
            {
                // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource
                // in WCF DS, and works as follows:
                //  - Create a new, blank instance of the entity.
                //  - Copy over the key values and set any updated values from the client on the new instance.
                //  - Then apply all the properties of the new instance to the instance to be updated.
                //    This will set any unspecified properties to their default value.
                object newInstance = Activator.CreateInstance(entityType);

                SetValues(newInstance, entityType, entry.EntityKey);
                SetValues(newInstance, entityType, entry.LocalValues);

                dbEntry.CurrentValues.SetValues(newInstance);
            }
            else
            {
                foreach (KeyValuePair <string, object> propertyPair in entry.LocalValues)
                {
                    DbPropertyEntry propertyEntry = dbEntry.Property(propertyPair.Key);
                    object          value         = propertyPair.Value;

                    if (propertyEntry is DbComplexPropertyEntry)
                    {
                        var dic = value as IReadOnlyDictionary <string, object>;
                        if (dic == null)
                        {
                            // TODO GitHubIssue#103 : Choose property error message for unknown type
                            throw new NotSupportedException("Unsupported type for property:" + propertyPair.Key);
                        }

                        var type = propertyEntry.CurrentValue.GetType();
                        value = Activator.CreateInstance(type);
                        SetValues(value, type, dic);
                    }

                    propertyEntry.CurrentValue = ConvertToEfDateTimeIfValueIsEdmDate(value);
                }
            }
        }
예제 #15
0
        /// <summary>
        /// Handles a POST request to create an entity.
        /// </summary>
        /// <param name="edmEntityObject">The entity object to create.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The task object that contains the creation result.</returns>
        public async Task <IHttpActionResult> Post(EdmEntityObject edmEntityObject, CancellationToken cancellationToken)
        {
            if (!this.ModelState.IsValid)
            {
                return(BadRequest(this.ModelState));
            }

            ODataPath     path      = this.GetPath();
            IEdmEntitySet entitySet = path.NavigationSource as IEdmEntitySet;

            if (entitySet == null)
            {
                throw new NotImplementedException(Resources.InsertOnlySupportedOnEntitySet);
            }

            DataModificationEntry postEntry = new DataModificationEntry(
                entitySet.Name,
                path.EdmType.FullTypeName(),
                null,
                null,
                edmEntityObject.CreatePropertyDictionary());

            RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet();

            if (changeSetProperty == null)
            {
                ChangeSet changeSet = new ChangeSet();
                changeSet.Entries.Add(postEntry);

                SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken);
            }
            else
            {
                changeSetProperty.ChangeSet.Entries.Add(postEntry);

                await changeSetProperty.OnChangeSetCompleted();
            }

            return(this.CreateCreatedODataResult(postEntry.Entity));
        }
예제 #16
0
        private async Task <IHttpActionResult> Update(
            EdmEntityObject edmEntityObject,
            bool isFullReplaceUpdate,
            CancellationToken cancellationToken)
        {
            ODataPath     path      = this.GetPath();
            IEdmEntitySet entitySet = path.NavigationSource as IEdmEntitySet;

            if (entitySet == null)
            {
                throw new NotImplementedException(Resources.UpdateOnlySupportedOnEntitySet);
            }

            DataModificationEntry updateEntry = new DataModificationEntry(
                entitySet.Name,
                path.EdmType.FullTypeName(),
                RestierQueryBuilder.GetPathKeyValues(path),
                this.GetOriginalValues(),
                edmEntityObject.CreatePropertyDictionary());

            updateEntry.IsFullReplaceUpdate = isFullReplaceUpdate;

            RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet();

            if (changeSetProperty == null)
            {
                ChangeSet changeSet = new ChangeSet();
                changeSet.Entries.Add(updateEntry);

                SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken);
            }
            else
            {
                changeSetProperty.ChangeSet.Entries.Add(updateEntry);

                await changeSetProperty.OnChangeSetCompleted();
            }

            return(this.CreateUpdatedODataResult(updateEntry.Entity));
        }
예제 #17
0
        private static void SetValues(EntityEntry dbEntry, DataModificationEntry entry, Type entityType)
        {
            //StateEntry stateEntry = ((IAccessor<InternalEntityEntry>) dbEntry.StateEntry;
            IEntityType edmType = dbEntry.Metadata;

            if (entry.IsFullReplaceUpdate)
            {
                // The algorithm for a "FullReplaceUpdate" is taken from WCF DS ObjectContextServiceProvider.ResetResource, and is as follows:
                // Create a new, blank instance of the entity.  Copy over the key values, and set any updated values from the client on the new instance.
                // Then apply all the properties of the new instance to the instance to be updated.  This will set any unspecified
                // properties to their default value.

                object newInstance = Activator.CreateInstance(entityType);

                ChangeSetPreparer.SetValues(newInstance, entityType, entry.EntityKey);
                ChangeSetPreparer.SetValues(newInstance, entityType, entry.LocalValues);

                foreach (var property in edmType.GetProperties())
                {
                    object val;
                    if (!entry.LocalValues.TryGetValue(property.Name, out val))
                    {
                        PropertyInfo propertyInfo = entityType.GetProperty(property.Name);
                        val = propertyInfo.GetValue(newInstance);
                    }
                    //stateEntry[property] = val;
                    dbEntry.Property(property.Name).CurrentValue = val;
                }
            }
            else
            {
                // For some properties like DateTimeOffset, the backing EF property could be of a different type like DateTime, so we can't just
                // copy every property pair in DataModificationEntry to EF StateEntry, instead we let the entity type to do the conversion, by
                // first setting the EDM property (in DataModificationEntry) to a entity instance, then getting the EF mapped property from the
                // entity instance and set to StateEntry.

                object instance = null;

                foreach (var property in edmType.GetProperties())
                {
                    object val;

                    var edmPropName = (string)property["EdmPropertyName"];
                    if (edmPropName != null && entry.LocalValues.TryGetValue(edmPropName, out val))
                    {
                        if (instance == null)
                        {
                            instance = Activator.CreateInstance(entityType);
                        }
                        PropertyInfo edmPropInfo = entityType.GetProperty(edmPropName);
                        edmPropInfo.SetValue(instance, val);

                        PropertyInfo propertyInfo = entityType.GetProperty(property.Name);
                        val = propertyInfo.GetValue(instance);
                    }
                    else if (!entry.LocalValues.TryGetValue(property.Name, out val))
                    {
                        continue;
                    }
                    //stateEntry[property] = val;
                    dbEntry.Property(property.Name).CurrentValue = val;
                }
            }
        }