Example #1
0
        /// <summary>
        /// Asynchronously prepare the <see cref="ChangeSet"/>.
        /// </summary>
        /// <param name="context">The submit context class used for preparation.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The task object that represents this asynchronous operation.</returns>
        public async Task InitializeAsync(SubmitContext context, CancellationToken cancellationToken)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var dbContext = context.GetApiService <DbContext>();

            foreach (var entry in context.ChangeSet.Entries.OfType <DataModificationItem>())
            {
                var strongTypedDbSet = dbContext.GetType().GetProperty(entry.ResourceSetName).GetValue(dbContext);
                var resourceType     = strongTypedDbSet.GetType().GetGenericArguments()[0];

                // This means request resource is sub type of resource type
                if (entry.ActualResourceType != null && resourceType != entry.ActualResourceType)
                {
                    // Set type to derived type
                    resourceType = entry.ActualResourceType;
                }

                var set = dbContext.Set(resourceType);

                object resource;

                if (entry.EntitySetOperation == RestierEntitySetOperation.Insert)
                {
                    resource = set.Create();
                    SetValues(resource, resourceType, entry.LocalValues);
                    set.Add(resource);
                }
                else if (entry.EntitySetOperation == RestierEntitySetOperation.Delete)
                {
                    resource = await FindResource(context, entry, cancellationToken).ConfigureAwait(false);

                    set.Remove(resource);
                }
                else if (entry.EntitySetOperation == RestierEntitySetOperation.Update)
                {
                    resource = await FindResource(context, entry, cancellationToken).ConfigureAwait(false);

                    var dbEntry = dbContext.Entry(resource);
                    SetValues(dbEntry, entry, resourceType);
                }
                else
                {
                    throw new NotSupportedException(Resources.DataModificationMustBeCUD);
                }

                entry.Resource = resource;
            }
        }
        public async Task AuthorizeAsyncWithPrivateMethod()
        {
            testTraceListener.Clear();
            var api               = new PrivateMethodApi(serviceProvider);
            var testClass         = new ConventionBasedChangeSetItemAuthorizer(typeof(PrivateMethodApi));
            var context           = new SubmitContext(api, new ChangeSet());
            var cancellationToken = CancellationToken.None;
            var result            = await testClass.AuthorizeAsync(context, dataModificationItem, cancellationToken);

            result.Should().BeTrue("AuthorizeAsync should return true, because CanInsertObject is private.");
            testTraceListener.Messages.Should().Contain("inaccessible due to its protection level");
            api.InvocationCount.Should().Be(0);
        }
        public async Task AuthorizeAsyncWithWrongNumberOfArguments()
        {
            testTraceListener.Clear();
            var api               = new IncorrectArgumentsApi(serviceProvider);
            var testClass         = new ConventionBasedChangeSetItemAuthorizer(typeof(IncorrectArgumentsApi));
            var context           = new SubmitContext(api, new ChangeSet());
            var cancellationToken = CancellationToken.None;
            var result            = await testClass.AuthorizeAsync(context, dataModificationItem, cancellationToken);

            result.Should().BeTrue("AuthorizeAsync should return true, because the api type is incorrect.");
            testTraceListener.Messages.Should().Contain("incorrect number of arguments");
            api.InvocationCount.Should().Be(0);
        }
Example #4
0
        /// <summary>
        /// Asynchronously submits changes made using an API context.
        /// </summary>
        /// <param name="context">
        /// An API context.
        /// </param>
        /// <param name="changeSet">
        /// A change set, or <c>null</c> to submit existing pending changes.
        /// </param>
        /// <param name="cancellationToken">
        /// An optional cancellation token.
        /// </param>
        /// <returns>
        /// A task that represents the asynchronous
        /// operation whose result is a submit result.
        /// </returns>
        public static async Task <SubmitResult> SubmitAsync(
            this ApiContext context,
            ChangeSet changeSet = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            Ensure.NotNull(context, "context");

            var submitContext = new SubmitContext(context, changeSet);
            var model         = await context.GetModelAsync(cancellationToken);

            submitContext.Model = model;
            return(await DefaultSubmitHandler.SubmitAsync(submitContext, cancellationToken));
        }
        public async Task AuthorizeAsyncWithWrongReturnType()
        {
            testTraceListener.Clear();
            var api               = new WrongReturnTypeApi(serviceProvider);
            var testClass         = new ConventionBasedChangeSetItemAuthorizer(typeof(WrongReturnTypeApi));
            var context           = new SubmitContext(api, new ChangeSet());
            var cancellationToken = CancellationToken.None;
            var result            = await testClass.AuthorizeAsync(context, dataModificationItem, cancellationToken);

            result.Should().BeTrue("AuthorizeAsync should return true, because CanInsertObject returns an int.");
            testTraceListener.Messages.Should().Contain("does not return a boolean value");
            api.InvocationCount.Should().Be(0);
        }
Example #6
0
        /// <inheritdoc/>
        public Task <bool> AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken)
        {
            Ensure.NotNull(context, nameof(context));
            Ensure.NotNull(item, nameof(item));
            var result = true;

            var returnType       = typeof(bool);
            var dataModification = (DataModificationItem)item;
            var methodName       = ConventionBasedMethodNameFactory.GetEntitySetMethodName(dataModification, RestierPipelineState.Authorization);
            var method           = targetApiType.GetQualifiedMethod(methodName);

            if (method == null)
            {
                return(Task.FromResult(result));
            }

            if (!method.IsFamily && !method.IsFamilyOrAssembly)
            {
                Trace.WriteLine($"Restier Authorizer found '{methodName}' but it is unaccessible due to its protection level. Your method will not be called until you change it to 'protected internal'.");
                return(Task.FromResult(result));
            }

            if (method.ReturnType != returnType)
            {
                Trace.WriteLine($"Restier Authorizer found '{methodName}' but it does not return a boolean value. Your method will not be called until you correct the return type.");
                return(Task.FromResult(result));
            }

            object target = null;

            if (!method.IsStatic)
            {
                target = context.Api;
                if (!targetApiType.IsInstanceOfType(target))
                {
                    Trace.WriteLine("The Restier API is of the incorrect type.");
                    return(Task.FromResult(result));
                }
            }

            var parameters = method.GetParameters();

            if (parameters.Length == 0)
            {
                result = (bool)method.Invoke(target, null);
            }

            Trace.WriteLine($"Restier Authorizer found '{methodName}', but it has an incorrect number of arguments. The number of arguments should be 0.");
            return(Task.FromResult(result));
        }
Example #7
0
        /// <summary>
        /// Asynchronously prepare the <see cref="ChangeSet"/>.
        /// </summary>
        /// <param name="context">The submit context class used for preparation.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The task object that represents this asynchronous operation.</returns>
        public async Task InitializeAsync(
            SubmitContext context,
            CancellationToken cancellationToken)
        {
            DbContext dbContext = context.ApiContext.GetApiService <DbContext>();

            foreach (var entry in context.ChangeSet.Entries.OfType <DataModificationItem>())
            {
                object strongTypedDbSet = dbContext.GetType().GetProperty(entry.EntitySetName).GetValue(dbContext);
                Type   entityType       = strongTypedDbSet.GetType().GetGenericArguments()[0];

                // This means request entity is sub type of entity type
                if (entry.ActualEntityType != null && entityType != entry.ActualEntityType)
                {
                    entityType = entry.ActualEntityType;
                }

                DbSet set = dbContext.Set(entityType);

                object entity;

                if (entry.DataModificationItemAction == DataModificationItemAction.Insert)
                {
                    entity = set.Create();

                    SetValues(entity, entityType, entry.LocalValues);

                    set.Add(entity);
                }
                else if (entry.DataModificationItemAction == DataModificationItemAction.Remove)
                {
                    entity = await FindEntity(context, entry, cancellationToken);

                    set.Remove(entity);
                }
                else if (entry.DataModificationItemAction == DataModificationItemAction.Update)
                {
                    entity = await FindEntity(context, entry, cancellationToken);

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

                entry.Entity = entity;
            }
        }
        public void DefaultSubmitContext_CanLogNullOrEmptyExternalId(string title)
        {
            var submitContext = new SubmitContext()
            {
                CoreMetaData = new List <SubmissionMetaDataModel>()
            };

            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel()
            {
                Name  = Fields.ExternalId,
                Type  = nameof(String),
                Value = title
            });
            Assert.Contains(_expectedNoExternalIdMessage, submitContext.LogPrefix());
        }
Example #9
0
        private async Task AssertFiltered(SubmitContext submitContext, bool expectFiltered)
        {
            // We might reuse the same submit context id in the same run - Let's just reset
            // the invocations before we do a verify
            _mockNext.ResetCalls();

            if (expectFiltered)
            {
                await AssertFiltered(submitContext);
            }
            else
            {
                await AssertNotFiltered(submitContext);
            }
        }
Example #10
0
        private async Task SubmitAggregation(string externalId, CancellationToken cancellationToken)
        {
            var submitContext = new SubmitContext
            {
                TenantId                     = _connectorConfigModel.TenantIdAsGuid,
                ConnectorConfigId            = _connectorConfigModel.IdAsGuid,
                ApiClientFactorySettings     = ConnectorApiAuthHelper.GetApiClientFactorySettings(),
                AuthenticationHelperSettings = ConnectorApiAuthHelper.GetAuthenticationHelperSettings(_connectorConfigModel.TenantDomainName),
                CoreMetaData                 = new List <SubmissionMetaDataModel>(),
                SourceMetaData               = new List <SubmissionMetaDataModel>(),
                Filters           = _connectorConfigModel.Filters,
                CancellationToken = cancellationToken
            };

            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.ExternalId, type: nameof(String), value: externalId.ToString()));

            // Aggregations need to have the special "ItemTypeId" field set to "1" to identify them as being an aggregation.
            // Submissions that omit this field are identified as items.
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.ItemTypeId, type: nameof(Double), value: "1"));

            // Note we do not set the ParentExternalId of the aggregation - in most cases aggregations don't have a parent.

            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.SourceLastModifiedDate, type: nameof(DateTime), value: DateTime.UtcNow.ToString("O")));

            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.Title, type: nameof(String), value: $"Fake Record Folder from {_connectorConfigModel.Id}"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.Author, type: nameof(String), value: "Record ReferenceConnectorSF"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.SourceCreatedDate, type: nameof(DateTime), value: DateTime.UtcNow.ToString("O")));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.SourceCreatedBy, type: nameof(String), value: "ReferenceConnectorSF"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.SourceLastModifiedBy, type: nameof(String), value: "ReferenceConnectorSF"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.Location, type: nameof(String), value: "Fake Record Folder"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.MediaType, type: nameof(String), value: "Electronic"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.ContentVersion, type: nameof(String), value: "1.0"));

            submitContext.SourceMetaData.Add(new SubmissionMetaDataModel("Checkin Comments", type: nameof(String), value: "Some checkin comments"));

            // Submit the aggregation!
            try
            {
                await _aggregationSubmitPipeline.Submit(submitContext).ConfigureAwait(false);

                HandleSubmitPipelineResult(submitContext);
            }
            catch (Exception)
            {
                // Something went wrong trying to submit the item.
                // Dead-letter the item to a durable data store where it can be retried later. (e.g., a message broker).
            }
        }
Example #11
0
        /// <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 resource = dataModificationItem.Resource;

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

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

                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(resource);
                        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       = resource,
                                PropertyName = property.Name
                            });
                        }
                    }
                }
            }

            return(Task.WhenAll());
        }
        /// <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());
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="context"></param>
        /// <param name="item"></param>
        /// <param name="pipelineState"></param>
        /// <returns></returns>
        private Task InvokeProcessorMethodAsync(SubmitContext context, ChangeSetItem item, RestierPipelineState pipelineState)
        {
            var dataModification   = (DataModificationItem)item;
            var expectedMethodName = ConventionBasedMethodNameFactory.GetEntitySetMethodName(dataModification, pipelineState);
            var expectedMethod     = targetApiType.GetQualifiedMethod(expectedMethodName);

            if (!IsUsable(expectedMethod))
            {
                if (expectedMethod != null)
                {
                    Trace.WriteLine($"Restier Filter found '{expectedMethodName}' but it is unaccessible due to its protection level. Your method will not be called until you change it to 'protected internal'.");
                }
                else
                {
                    var actualMethodName = expectedMethodName.Replace(dataModification.ExpectedResourceType.Name, dataModification.ResourceSetName);
                    var actualMethod     = targetApiType.GetQualifiedMethod(actualMethodName);
                    if (actualMethod != null)
                    {
                        Trace.WriteLine($"BREAKING: Restier Filter expected'{expectedMethodName}' but found '{actualMethodName}'. Your method will not be called until you correct the method name.");
                    }
                }
            }
            else
            {
                object target = null;
                if (!expectedMethod.IsStatic)
                {
                    target = context.Api;
                    if (target == null || !targetApiType.IsInstanceOfType(target))
                    {
                        return(Task.WhenAll());
                    }
                }

                var parameters       = GetParameters(item);
                var methodParameters = expectedMethod.GetParameters();
                if (ParametersMatch(methodParameters, parameters))
                {
                    var result = expectedMethod.Invoke(target, parameters);
                    if (result is Task resultTask)
                    {
                        return(resultTask);
                    }
                }
            }

            return(Task.WhenAll());
        }
Example #14
0
        public static SubmissionMetaDataModel GetField(SubmitContext submitContext, SearchTermModel filter, string expectedFieldType)
        {
            // Format of a source field name in Records365: S|<ConnectorTypeId>|<DataType>|<FieldName>
            if (filter.FieldName.StartsWith(_sourceFieldPrefix))
            {
                // Avoiding String.Split due to performance concerns. Not using LastIndexOf in case a Field name contains a pipe.
                // We want to skip over S|<ConnectorTypeId>|, which has a known length (39 characters), then find the index of the first pipe following that.
                // After that pipe, the rest should be the Source property's field name
                var lastPipeIndex   = filter.FieldName.IndexOf(_separator, _indexAfterSeparator);
                var sourceFieldName = filter.FieldName.Substring(lastPipeIndex + 1);

                return(submitContext?.SourceMetaData?.FirstOrDefault(x => x.Name == sourceFieldName && x.Type == expectedFieldType));
            }

            return(submitContext?.CoreMetaData?.FirstOrDefault(x => x.Name == filter.FieldName));
        }
Example #15
0
        public Task OnChangeSetItemProcessedAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken)
        {
            var dataModificationItem = item as DataModificationItem;

            if (dataModificationItem != null)
            {
                object myEntity      = dataModificationItem.Resource;
                string entitySetName = dataModificationItem.ResourceSetName;
                var    operation     = dataModificationItem.DataModificationItemAction;

                // In case of insert, the request URL has no key, and request body may not have key neither as the key may be generated by database
                var keyAttrbiutes = new Dictionary <string, object>();
                var keyConvention = new Dictionary <string, object>();

                var            entityTypeName = myEntity.GetType().Name;
                PropertyInfo[] properties     = myEntity.GetType().GetProperties();

                foreach (PropertyInfo property in properties)
                {
                    var attribute = Attribute.GetCustomAttribute(property, typeof(KeyAttribute))
                                    as KeyAttribute;
                    var propName = property.Name;
                    // This is getting key with Key attribute defined
                    if (attribute != null) // This property has a KeyAttribute
                    {
                        // Do something, to read from the property:
                        object val = property.GetValue(myEntity);
                        keyAttrbiutes.Add(propName, val);
                    }
                    // This is getting key based on convention
                    else if (propName.ToLower().Equals("id") || propName.ToLower().Equals(entityTypeName.ToLower() + "id"))
                    {
                        object val = property.GetValue(myEntity);
                        keyConvention.Add(propName, val);
                    }
                }
                if (keyAttrbiutes.Count > 0)
                {
                    // Use property with key attribute as keys
                }
                else if (keyConvention.Count > 0)
                {
                    // Key is defined based on convention
                }
            }
            return(Inner.OnChangeSetItemProcessedAsync(context, item, cancellationToken));
        }
Example #16
0
        public async Task PrepareAsync(
            SubmitContext context,
            CancellationToken cancellationToken)
        {
            DbContext dbContext = context.ApiContext.GetProperty <DbContext>("DbContext");

            foreach (var entry in context.ChangeSet.Entries.OfType <DataModificationEntry>())
            {
                object     strongTypedDbSet   = dbContext.GetType().GetProperty(entry.EntitySetName).GetValue(dbContext);
                Type       entityType         = strongTypedDbSet.GetType().GetGenericArguments()[0];
                MethodInfo prepareEntryMethod = _prepareEntryGeneric.MakeGenericMethod(entityType);

                await(Task) prepareEntryMethod.Invoke(
                    obj: null,
                    parameters: new object[] { context, dbContext, entry, strongTypedDbSet, cancellationToken });
            }
        }
Example #17
0
        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;
        }
Example #18
0
        public override Task InitializeAsync(SubmitContext context, CancellationToken cancellationToken)
        {
            var changeSetEntry = context.ChangeSet.Entries.Single();

            if (changeSetEntry is DataModificationItem dataModificationEntry)
            {
                dataModificationEntry.Resource = new Product()
                {
                    Name = "var1",
                    Addr = new Address()
                    {
                        Zip = 330
                    }
                };
            }

            return(Task.FromResult <object>(null));
        }
        public void DefaultSubmitContext_CanLogNonNullExternalId()
        {
            var submitContext = new SubmitContext()
            {
                CoreMetaData = new List <SubmissionMetaDataModel>()
            };

            var externalId = Guid.NewGuid().ToString();

            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel()
            {
                Name  = Fields.ExternalId,
                Type  = nameof(String),
                Value = externalId
            });

            Assert.Contains(externalId, submitContext.LogPrefix());
        }
        public void DefaultSubmitContext_CanLogNonNullTitle()
        {
            var submitContext = new SubmitContext()
            {
                CoreMetaData = new List <SubmissionMetaDataModel>()
            };

            var title = Guid.NewGuid().ToString();

            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel()
            {
                Name  = Fields.Title,
                Type  = nameof(String),
                Value = title
            });

            Assert.Contains(title, submitContext.LogPrefix());
        }
Example #21
0
        /// <summary>
        /// Asynchronously prepare the <see cref="ChangeSet"/>.
        /// </summary>
        /// <param name="context">The submit context class used for preparation.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The task object that represents this asynchronous operation.</returns>
        public async Task PrepareAsync(
            SubmitContext context,
            CancellationToken cancellationToken)
        {
            DbContext dbContext = context.DomainContext.GetProperty <DbContext>("DbContext");

            foreach (var entry in context.ChangeSet.Entries.OfType <DataModificationEntry>())
            {
                object strongTypedDbSet = dbContext.GetType().GetProperty(entry.EntitySetName).GetValue(dbContext);
                Type   entityType       = strongTypedDbSet.GetType().GetGenericArguments()[0];
                DbSet  set = dbContext.Set(entityType);

                object entity;

                if (entry.IsNew)
                {
                    entity = set.Create();

                    SetValues(entity, entityType, entry.LocalValues);

                    set.Add(entity);
                }
                else if (entry.IsDelete)
                {
                    entity = await FindEntity(context, entry, cancellationToken);

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

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

                entry.Entity = entity;
            }
        }
Example #22
0
        /// <inheritdoc/>
        public Task ValidateChangeSetItemAsync(SubmitContext context, ChangeSetItem item, Collection <ChangeSetItemValidationResult> validationResults,
                                               CancellationToken cancellationToken)
        {
            Ensure.NotNull(validationResults, nameof(validationResults));
            Ensure.NotNull(context, nameof(context));
            Ensure.NotNull(item, nameof(item));

            if (item is DataModificationItem dataModificationItem)
            {
                var resource = dataModificationItem.Resource;

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

                var validationContext = new ValidationContext(resource);

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

                    var validationAttributes = property.Attributes.OfType <ValidationAttribute>();
                    foreach (var validationAttribute in validationAttributes)
                    {
                        var value            = property.GetValue(resource);
                        var validationResult = validationAttribute.GetValidationResult(value, validationContext);
                        if (validationResult != ValidationResult.Success)
                        {
                            validationResults.Add(new ChangeSetItemValidationResult()
                            {
                                ValidatorType = validationAttribute.GetType().FullName,
                                Message       = validationResult.ErrorMessage,
                                Severity      = EventLevel.Error,
                                Target        = resource,
                                PropertyName  = property.Name,
                            });
                        }
                    }
                }
            }

            return(Task.CompletedTask);
        }
Example #23
0
        public Task PrepareAsync(SubmitContext context, CancellationToken cancellationToken)
        {
            var changeSetEntry = context.ChangeSet.Entries.Single();

            var dataModificationEntry = changeSetEntry as DataModificationEntry;

            if (dataModificationEntry != null)
            {
                dataModificationEntry.Entity = new Product()
                {
                    Name = "var1",
                    Addr = new Address()
                    {
                        Zip = 330
                    }
                };
            }

            return(Task.FromResult <object>(null));
        }
Example #24
0
        private async Task SubmitAuditEvent(string itemExternalId, CancellationToken cancellationToken)
        {
            var submitContext = new SubmitContext
            {
                TenantId                     = _connectorConfigModel.TenantIdAsGuid,
                ConnectorConfigId            = _connectorConfigModel.IdAsGuid,
                ApiClientFactorySettings     = ConnectorApiAuthHelper.GetApiClientFactorySettings(),
                AuthenticationHelperSettings = ConnectorApiAuthHelper.GetAuthenticationHelperSettings(_connectorConfigModel.TenantDomainName),
                CoreMetaData                 = new List <SubmissionMetaDataModel>(),
                SourceMetaData               = new List <SubmissionMetaDataModel>(),
                Filters           = _connectorConfigModel.Filters,
                CancellationToken = cancellationToken
            };

            // Associate the audit event with the item with the item external id.
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.AuditEvent.ExternalId, type: nameof(String), value: itemExternalId));

            // Set the EventExternalId - this is an ID that uniquely identifies the audit event in the content source.
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.AuditEvent.EventExternalId, type: nameof(String), value: Guid.NewGuid().ToString()));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.AuditEvent.EventType, type: nameof(String), value: "Reference Audit Event"));

            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.AuditEvent.UserName, type: nameof(String), value: "Record ReferenceConnectorSF"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.AuditEvent.UserId, type: nameof(String), value: "Record ReferenceConnectorSF"));
            submitContext.CoreMetaData.Add(new SubmissionMetaDataModel(Fields.AuditEvent.Created, type: nameof(DateTime), value: DateTime.UtcNow.ToString("O")));

            // Set source metadata on the audit event
            submitContext.SourceMetaData.Add(new SubmissionMetaDataModel("Checkin Comments", type: nameof(String), value: "Some checkin comments"));

            // Submit the audit event!
            try
            {
                await _auditEventSubmitPipeline.Submit(submitContext).ConfigureAwait(false);

                HandleSubmitPipelineResult(submitContext);
            }
            catch (Exception)
            {
                // Something went wrong trying to submit the item.
                // Dead-letter the item to a durable data store where it can be retried later. (e.g., a message broker).
            }
        }
        private Task InvokeFilterMethodAsync(
            SubmitContext context,
            ChangeSetEntry entry,
            string methodNameSuffix)
        {
            string methodName = ConventionBasedChangeSetEntryFilter.GetMethodName(entry, methodNameSuffix);

            object[] parameters = ConventionBasedChangeSetEntryFilter.GetParameters(entry);

            MethodInfo method = this.targetType.GetQualifiedMethod(methodName);

            if (method != null &&
                (method.ReturnType == typeof(void) ||
                 typeof(Task).IsAssignableFrom(method.ReturnType)))
            {
                object target = null;
                if (!method.IsStatic)
                {
                    target = context.ApiContext.GetProperty(
                        typeof(Api).AssemblyQualifiedName);
                    if (target == null ||
                        !this.targetType.IsAssignableFrom(target.GetType()))
                    {
                        return(Task.WhenAll());
                    }
                }

                ParameterInfo[] methodParameters = method.GetParameters();
                if (ConventionBasedChangeSetEntryFilter.ParametersMatch(methodParameters, parameters))
                {
                    object result     = method.Invoke(target, parameters);
                    Task   resultTask = result as Task;
                    if (resultTask != null)
                    {
                        return(resultTask);
                    }
                }
            }

            return(Task.WhenAll());
        }
        private Task InvokeProcessorMethodAsync(
            SubmitContext context,
            ChangeSetItem item,
            string methodNameSuffix)
        {
            string methodName = GetMethodName(item, methodNameSuffix);

            object[] parameters = GetParameters(item);

            MethodInfo method = this.targetType.GetQualifiedMethod(methodName);

            if (method != null &&
                (method.ReturnType == typeof(void) ||
                 typeof(Task).IsAssignableFrom(method.ReturnType)))
            {
                object target = null;
                if (!method.IsStatic)
                {
                    target = context.GetApiService <ApiBase>();
                    if (target == null ||
                        !this.targetType.IsInstanceOfType(target))
                    {
                        return(Task.WhenAll());
                    }
                }

                ParameterInfo[] methodParameters = method.GetParameters();
                if (ParametersMatch(methodParameters, parameters))
                {
                    object result     = method.Invoke(target, parameters);
                    Task   resultTask = result as Task;
                    if (resultTask != null)
                    {
                        return(resultTask);
                    }
                }
            }

            return(Task.WhenAll());
        }
Example #27
0
        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);
        }
        public Task <SubmitResult> SubmitAsync(SubmitContext context, CancellationToken cancellationToken)
        {
            foreach (var entry in context.ChangeSet.Entries.OfType <DataModificationEntry>())
            {
                if (entry.LocalValues.All(l => l.Key != "Addr"))
                {
                    throw new Exception("Addr is required.");
                }

                entry.Entity = new Product
                {
                    Id   = 1,
                    Addr = new Address {
                        Zip = 0001
                    },
                    Addr2 = new Address {
                        Zip = 0002
                    }
                };
            }

            return(Task.FromResult(new SubmitResult(new ChangeSet())));
        }
Example #29
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 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(Resources.ResourceNotFound);
            }

            return(entity);
        }
Example #30
0
        /// <inheritdoc/>
        public Task <bool> AuthorizeAsync(
            SubmitContext context,
            ChangeSetEntry entry,
            CancellationToken cancellationToken)
        {
            Ensure.NotNull(context, "context");
            bool result = true;

            Type       returnType = typeof(bool);
            string     methodName = ConventionBasedChangeSetAuthorizer.GetAuthorizeMethodName(entry);
            MethodInfo method     = this.targetType.GetQualifiedMethod(methodName);

            if (method != null && method.IsFamily &&
                method.ReturnType == returnType)
            {
                object target = null;
                if (!method.IsStatic)
                {
                    target = context.ApiContext.GetProperty(
                        typeof(Api).AssemblyQualifiedName);
                    if (target == null ||
                        !this.targetType.IsAssignableFrom(target.GetType()))
                    {
                        return(Task.FromResult(result));
                    }
                }

                var parameters = method.GetParameters();
                if (parameters.Length == 0)
                {
                    result = (bool)method.Invoke(target, null);
                }
            }

            return(Task.FromResult(result));
        }