private void SetValues(EntityEntry dbEntry, DataModificationItem item, Type resourceType) { if (item.IsFullReplaceUpdateRequest) { // 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. var newInstance = Activator.CreateInstance(resourceType); this.SetValues(newInstance, resourceType, item.ResourceKey); this.SetValues(newInstance, resourceType, item.LocalValues); dbEntry.CurrentValues.SetValues(newInstance); } else { foreach (var propertyPair in item.LocalValues) { var propertyEntry = dbEntry.Property(propertyPair.Key); var value = propertyPair.Value; if (value == null) { // If the property value is null, we set null in the item too. propertyEntry.CurrentValue = null; continue; } Type type = null; if (propertyEntry.CurrentValue != null) { type = propertyEntry.CurrentValue.GetType(); } else { // If property does not have value now, will get property type from model var propertyInfo = dbEntry.Entity.GetType().GetProperty(propertyPair.Key); type = propertyInfo.PropertyType; } // todo: complex property detection removed. Not sure whether IReadOnlyDictionary is enough. if (value is IReadOnlyDictionary <string, object> dic) { value = propertyEntry.CurrentValue; this.SetValues(value, type, dic); } propertyEntry.CurrentValue = this.ConvertToEfValue(type, value); } } }
/// <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 (edmEntityObject == null) { throw new ArgumentNullException(nameof(edmEntityObject)); } CheckModelState(); var path = GetPath(); if (!(path.NavigationSource is IEdmEntitySet entitySet)) { throw new NotImplementedException(Resources.InsertOnlySupportedOnEntitySet); } // In case of type inheritance, the actual type will be different from entity type var expectedEntityType = path.EdmType; var actualEntityType = path.EdmType as IEdmStructuredType; if (edmEntityObject.ActualEdmType != null) { expectedEntityType = edmEntityObject.ExpectedEdmType; actualEntityType = edmEntityObject.ActualEdmType; } var postItem = new DataModificationItem( entitySet.Name, expectedEntityType.GetClrType(Api.ServiceProvider), actualEntityType.GetClrType(Api.ServiceProvider), RestierEntitySetOperation.Insert, null, null, edmEntityObject.CreatePropertyDictionary(actualEntityType, api, true)); var changeSetProperty = Request.GetChangeSet(); if (changeSetProperty == null) { var changeSet = new ChangeSet(); changeSet.Entries.Add(postItem); var result = await Api.SubmitAsync(changeSet, cancellationToken).ConfigureAwait(false); } else { changeSetProperty.ChangeSet.Entries.Add(postItem); await changeSetProperty.OnChangeSetCompleted(Request).ConfigureAwait(false); } return(CreateCreatedODataResult(postItem.Resource)); }
/// <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); } // In case of type inheritance, the actual type will be different from entity type var expectedEntityType = path.EdmType; var actualEntityType = path.EdmType; if (edmEntityObject.ActualEdmType != null) { expectedEntityType = edmEntityObject.ExpectedEdmType; actualEntityType = edmEntityObject.ActualEdmType; } DataModificationItem postItem = new DataModificationItem( entitySet.Name, expectedEntityType.GetClrType(Api), actualEntityType.GetClrType(Api), DataModificationItemAction.Insert, null, null, edmEntityObject.CreatePropertyDictionary()); RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet(); if (changeSetProperty == null) { ChangeSet changeSet = new ChangeSet(); changeSet.Entries.Add(postItem); SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken); } else { changeSetProperty.ChangeSet.Entries.Add(postItem); await changeSetProperty.OnChangeSetCompleted(); } return(this.CreateCreatedODataResult(postItem.Entity)); }
public void CanConstruct() { var instance = new DataModificationItem( resourceSetName, expectedResourceType, actualResourceType, action, resourceKey, originalValues, localValues); instance.Should().NotBeNull(); }
/// <summary> /// Initializes a new instance of the <see cref="ConventionBasedChangeSetItemAuthorizerTests"/> class. /// </summary> public ConventionBasedChangeSetItemAuthorizerTests() { serviceProvider = new ServiceProviderMock().ServiceProvider.Object; dataModificationItem = new DataModificationItem( "Test", typeof(object), typeof(object), RestierEntitySetOperation.Insert, new Dictionary <string, object>(), new Dictionary <string, object>(), new Dictionary <string, object>()); Trace.Listeners.Add(testTraceListener); }
private static object[] GetParameters(ChangeSetItem item) { switch (item.Type) { case ChangeSetItemType.DataModification: DataModificationItem dataModification = (DataModificationItem)item; return(new object[] { dataModification.Resource }); default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, item.Type)); } }
private static object CreateFullUpdateInstance(DataModificationItem 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); ChangeSetInitializer.SetValues(newInstance, entityType, entry.ResourceKey); ChangeSetInitializer.SetValues(newInstance, entityType, entry.LocalValues); return(newInstance); }
/// <inheritdoc/> public Task ValidateChangeSetItemAsync( SubmitContext context, ChangeSetItem item, Collection <ChangeSetItemValidationResult> validationResults, CancellationToken cancellationToken) { Ensure.NotNull(validationResults, "validationResults"); DataModificationItem dataModificationItem = item as DataModificationItem; if (dataModificationItem != null) { object entity = dataModificationItem.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 ChangeSetItemValidationResult() { Id = validationAttribute.GetType().FullName, Message = validationResult.ErrorMessage, Severity = EventLevel.Error, Target = entity, PropertyName = property.Name }); } } } } return(Task.WhenAll()); }
public static void CanCallGetEntitySetMethodNameWithItemAndRestierPipelineState( RestierPipelineState pipelineState, RestierEntitySetOperation entitySetOperation, string expected) { var item = new DataModificationItem( "Tests", typeof(Test), typeof(Test), entitySetOperation, new Mock <IReadOnlyDictionary <string, object> >().Object, new Mock <IReadOnlyDictionary <string, object> >().Object, new Mock <IReadOnlyDictionary <string, object> >().Object); var result = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, pipelineState); result.Should().Be(expected); }
#pragma warning restore CA1062 // Validate public arguments /// <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 <IActionResult> Delete(CancellationToken cancellationToken) { EnsureInitialized(); var path = GetPath(); if (!(path.NavigationSource is IEdmEntitySet entitySet)) { throw new NotImplementedException(Resources.DeleteOnlySupportedOnEntitySet); } var propertiesInEtag = GetOriginalValues(entitySet); if (propertiesInEtag == null) { throw new StatusCodeException((HttpStatusCode)428, Resources.PreconditionRequired); } var model = api.GetModel(); var deleteItem = new DataModificationItem( entitySet.Name, path.EdmType.GetClrType(model), null, RestierEntitySetOperation.Delete, RestierQueryBuilder.GetPathKeyValues(path), propertiesInEtag, null); var changeSetProperty = HttpContext.GetChangeSet(); if (changeSetProperty == null) { var changeSet = new ChangeSet(); changeSet.Entries.Add(deleteItem); var result = await api.SubmitAsync(changeSet, cancellationToken).ConfigureAwait(false); } else { changeSetProperty.ChangeSet.Entries.Add(deleteItem); await changeSetProperty.OnChangeSetCompleted().ConfigureAwait(false); } return(StatusCode((int)HttpStatusCode.NoContent)); }
private static async Task PrepareEntry <TEntity>( SubmitContext context, DbContext dbContext, DataModificationItem entry, DbSet <TEntity> set, CancellationToken cancellationToken) where TEntity : class { Type entityType = typeof(TEntity); TEntity entity; if (entry.DataModificationItemAction == DataModificationItemAction.Insert) { // TODO: See if Create method is in DbSet<> in future EF7 releases, as the one EF6 has. entity = (TEntity)Activator.CreateInstance(typeof(TEntity)); ChangeSetInitializer.SetValues(entity, entityType, entry.LocalValues); set.Add(entity); } else if (entry.DataModificationItemAction == DataModificationItemAction.Remove) { entity = (TEntity)await ChangeSetInitializer.FindEntity(context, entry, cancellationToken); set.Remove(entity); } else if (entry.DataModificationItemAction == DataModificationItemAction.Update) { if (entry.IsFullReplaceUpdateRequest) { entity = (TEntity)ChangeSetInitializer.CreateFullUpdateInstance(entry, entityType); dbContext.Update(entity); } else { entity = (TEntity)await ChangeSetInitializer.FindEntity(context, entry, cancellationToken); var dbEntry = dbContext.Attach(entity); ChangeSetInitializer.SetValues(dbEntry, entry); } } else { throw new NotSupportedException(Resources.DataModificationMustBeCUD); } entry.Resource = entity; }
/// <summary> /// Initializes a new instance of the <see cref="DataModificationItemTests"/> class. /// </summary> public DataModificationItemTests() { resourceSetName = "Tests"; expectedResourceType = typeof(Test); actualResourceType = typeof(Test); action = RestierEntitySetOperation.Update; resourceKey = new Dictionary <string, object>(); originalValues = new Dictionary <string, object>(); localValues = new Dictionary <string, object>(); testClass = new DataModificationItem( resourceSetName, expectedResourceType, actualResourceType, action, resourceKey, originalValues, localValues); }
/// <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); } var propertiesInEtag = await this.GetOriginalValues(entitySet); if (propertiesInEtag == null) { throw new PreconditionRequiredException(Resources.PreconditionRequired); } DataModificationItem deleteItem = new DataModificationItem( entitySet.Name, path.EdmType.GetClrType(Api.ServiceProvider), null, DataModificationItemAction.Remove, RestierQueryBuilder.GetPathKeyValues(path), propertiesInEtag, null); RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet(); if (changeSetProperty == null) { ChangeSet changeSet = new ChangeSet(); changeSet.Entries.Add(deleteItem); SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken); } else { changeSetProperty.ChangeSet.Entries.Add(deleteItem); await changeSetProperty.OnChangeSetCompleted(this.Request); } return(this.StatusCode(HttpStatusCode.NoContent)); }
/// <summary> /// Initializes a new instance of the <see cref="ConventionBasedChangeSetItemValidatorTests"/> class. /// </summary> public ConventionBasedChangeSetItemValidatorTests() { serviceProvider = new ServiceProviderMock().ServiceProvider.Object; dataModificationItem = new DataModificationItem( "Test", typeof(object), typeof(object), RestierEntitySetOperation.Insert, new Dictionary <string, object>(), new Dictionary <string, object>(), new Dictionary <string, object>()) { Resource = new ValidatableEntity() { Property = "This is a test", Number = 1, }, }; Trace.Listeners.Add(testTraceListener); }
/// <summary> /// Generates the complete MethodName for a given <see cref="IEdmOperationImport"/>, <see cref="RestierPipelineState"/>, and <see cref="RestierEntitySetOperation"/>. /// </summary> /// <param name="item">The <see cref="DataModificationItem"/> that contains the details for the EntitySet and the Entities it holds.</param> /// <param name="restierPipelineState">The part of the Restier pipeline currently executing.</param> /// <returns>A string representing the fully-realized MethodName.</returns> /// <returns></returns> public static string GetEntitySetMethodName(DataModificationItem item, RestierPipelineState restierPipelineState) { if ((item.EntitySetOperation == RestierEntitySetOperation.Filter && ExcludedFilterStates.Contains(restierPipelineState)) || restierPipelineState == RestierPipelineState.Submit && ExcludedEntitySetSubmitOperations.Contains(item.EntitySetOperation)) { return(string.Empty); } var prefix = GetPipelinePrefixInternal(restierPipelineState); //RWM: If, for some reason, we don't have a prefix, then we don't have a method for this operation. So don't do anything. if (string.IsNullOrWhiteSpace(prefix)) { return(string.Empty); } var operationName = GetRestierOperationNameInternal(item.EntitySetOperation, restierPipelineState); var suffix = item.EntitySetOperation != RestierEntitySetOperation.Filter ? GetPipelineSuffixInternal(restierPipelineState) : string.Empty; var entityReferenceName = GetEntityReferenceNameInternal(item.EntitySetOperation, item.ResourceSetName, item.ExpectedResourceType.Name); return($"{prefix}{operationName}{suffix}{entityReferenceName}"); }
private static async Task <object> FindEntity( SubmitContext context, DataModificationItem item, CancellationToken cancellationToken) { IQueryable query = context.ApiContext.GetQueryableSource(item.EntitySetName); query = item.ApplyTo(query); QueryResult result = await context.ApiContext.QueryAsync(new QueryRequest(query), cancellationToken); object entity = result.Results.SingleOrDefault(); if (entity == null) { throw new ResourceNotFoundException(Resources.ResourceNotFound); } // This means no If-Match or If-None-Match header if (item.OriginalValues == null || item.OriginalValues.Count == 0) { return(entity); } var etagEntity = item.ApplyEtag(result.Results.AsQueryable()).SingleOrDefault(); if (etagEntity == null) { // If ETAG does not match, should return 412 Precondition Failed var message = string.Format( CultureInfo.InvariantCulture, Resources.PreconditionCheckFailed, new object[] { item.DataModificationItemAction, entity }); throw new PreconditionFailedException(message); } return(etagEntity); }
private static object FindResource( object instance, SubmitContext context, DataModificationItem item, CancellationToken cancellationToken) { var entitySetPropertyInfo = GetEntitySetPropertyInfoFromDataModificationItem(instance, item); var originSet = entitySetPropertyInfo.GetValue(instance); object resource = null; Type resourceType = null; var enumerableSet = originSet as IEnumerable <object>; if (enumerableSet != null) { resourceType = originSet.GetType().GetGenericArguments()[0]; foreach (var o in enumerableSet) { var foundFlag = true; foreach (var keyVal in item.ResourceKey) { var entityProp = o.GetType().GetProperty(keyVal.Key); if (entityProp != null) { foundFlag &= entityProp.GetValue(o).Equals(keyVal.Value); } else { foundFlag = false; } if (!foundFlag) { break; } } if (foundFlag) { resource = Convert.ChangeType(o, resourceType); break; } } } if (resource == null) { throw new ResourceNotFoundException("Resource Not Found"); } // This means no If-Match or If-None-Match header if (item.OriginalValues == null || item.OriginalValues.Count == 0) { return(resource); } // Make a list of resource as IQueryable to valid etag var listOfItemType = typeof(List <>).MakeGenericType(resourceType); var list = Activator.CreateInstance(listOfItemType); listOfItemType.GetMethod("Add").Invoke(list, new[] { resource }); var method = typeof(Queryable).GetMethod( "AsQueryable", BindingFlags.Static | BindingFlags.Public, null, new[] { listOfItemType }, null); resource = item.ValidateEtag(method.Invoke(list, new[] { list }) as IQueryable); return(resource); }
private async Task <IHttpActionResult> Update( EdmEntityObject edmEntityObject, bool isFullReplaceUpdate, CancellationToken cancellationToken) { CheckModelState(); var path = GetPath(); var entitySet = path.NavigationSource as IEdmEntitySet; if (entitySet == null) { throw new NotImplementedException(Resources.UpdateOnlySupportedOnEntitySet); } var propertiesInEtag = await GetOriginalValues(entitySet).ConfigureAwait(false); if (propertiesInEtag == null) { throw new PreconditionRequiredException(Resources.PreconditionRequired); } // In case of type inheritance, the actual type will be different from entity type // This is only needed for put case, and does not need for patch case // For put request, it will 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. var expectedEntityType = path.EdmType; var actualEntityType = path.EdmType as IEdmStructuredType; if (edmEntityObject.ActualEdmType != null) { expectedEntityType = edmEntityObject.ExpectedEdmType; actualEntityType = edmEntityObject.ActualEdmType; } var updateItem = new DataModificationItem( entitySet.Name, expectedEntityType.GetClrType(Api.ServiceProvider), actualEntityType.GetClrType(Api.ServiceProvider), RestierEntitySetOperation.Update, RestierQueryBuilder.GetPathKeyValues(path), propertiesInEtag, edmEntityObject.CreatePropertyDictionary(actualEntityType, api, false)) { IsFullReplaceUpdateRequest = isFullReplaceUpdate }; var changeSetProperty = Request.GetChangeSet(); if (changeSetProperty == null) { var changeSet = new ChangeSet(); changeSet.Entries.Add(updateItem); var result = await Api.SubmitAsync(changeSet, cancellationToken).ConfigureAwait(false); } else { changeSetProperty.ChangeSet.Entries.Add(updateItem); await changeSetProperty.OnChangeSetCompleted(Request).ConfigureAwait(false); } return(CreateUpdatedODataResult(updateItem.Resource)); }
private void SetValues(DbEntityEntry dbEntry, DataModificationItem item, Type resourceType) { if (item.IsFullReplaceUpdateRequest) { // 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(resourceType); SetValues(newInstance, resourceType, item.ResourceKey); SetValues(newInstance, resourceType, item.LocalValues); dbEntry.CurrentValues.SetValues(newInstance); } else { foreach (KeyValuePair <string, object> propertyPair in item.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 item too. propertyEntry.CurrentValue = null; continue; } Type type = null; if (propertyEntry.CurrentValue != null) { type = propertyEntry.CurrentValue.GetType(); } else { // If property does not have value now, will get property type from model var propertyInfo = dbEntry.Entity.GetType().GetProperty(propertyPair.Key); type = propertyInfo.PropertyType; } if (propertyEntry is DbComplexPropertyEntry) { var dic = value as IReadOnlyDictionary <string, object>; if (dic == null) { throw new NotSupportedException(string.Format( CultureInfo.InvariantCulture, Resources.UnsupportedPropertyType, propertyPair.Key)); } value = propertyEntry.CurrentValue; SetValues(value, type, dic); } propertyEntry.CurrentValue = ConvertToEfValue(type, value); } } }
private async Task HandleEntitySet <TEntity>(SubmitContext context, DbContext dbContext, DataModificationItem entry, Type resourceType, CancellationToken cancellationToken) where TEntity : class, new() { var set = dbContext.Set <TEntity>(); TEntity resource; if (entry.EntitySetOperation == RestierEntitySetOperation.Insert) { resource = new TEntity(); this.SetValues(resource, resourceType, entry.LocalValues); set.Add(resource); } else if (entry.EntitySetOperation == RestierEntitySetOperation.Delete) { resource = (await FindResource(context, entry, cancellationToken).ConfigureAwait(false)) as TEntity; set.Remove(resource); } else if (entry.EntitySetOperation == RestierEntitySetOperation.Update) { resource = (await FindResource(context, entry, cancellationToken).ConfigureAwait(false)) as TEntity; var dbEntry = dbContext.Entry(resource); this.SetValues(dbEntry, entry, resourceType); } else { throw new NotSupportedException(Resources.DataModificationMustBeCUD); } entry.Resource = resource; }