Esempio n. 1
0
        /// <summary>
        ///     Filters the model dataset by the primary key obtained from the request context
        /// </summary>
        /// <param name="context">The current API context</param>
        /// <param name="dataset">The current dataset to be filtered</param>
        /// <returns>The filtered dataset</returns>
        public virtual async Task <IQueryable <TModel> > FilterDataAsync(
            IApiContext <TModel, object> context,
            IQueryable <TModel> dataset)
        {
            // step one is just get the keys
            object?[] PrimaryKeyValues = this.Parameters.Select(
                (param, i) => {
                // retrieve value
                string ParameterValue = param.GetValue(context.Request)
                                        ?? throw new ConditionFailedException("No value provided for primary key");
                Type KeyType = this.PrimaryKey[i].PropertyInfo.PropertyType;

                // and parse
                return(ParameterResolver.ParseParameter(ParameterValue, KeyType));
            }).ToArray();

            // so that ef can still use sql queries, let's generate an expression tree for it to use
            ParameterExpression ModelParameter = Expression.Parameter(typeof(TModel));

            // generate expressions. boils down to: ModelParameter.Property == PrimaryKeyValues[i]
            Expression[] ComparisonExpressions = this.PrimaryKey.Select(
                (property, i) => (Expression)Expression.Equal(
                    Expression.Property(ModelParameter, property.PropertyInfo),
                    Expression.Constant(PrimaryKeyValues[i]))).ToArray();

            // && them all together
            Expression AggregateExpression = ComparisonExpressions.Aggregate(Expression.AndAlso);

            // and create the lambda
            Expression <Func <TModel, bool> > FilterExpression =
                Expression.Lambda <Func <TModel, bool> >(AggregateExpression, ModelParameter);

            return(dataset.Where(FilterExpression));
        }
Esempio n. 2
0
        /// <summary>
        ///     Filters this route's dataset by a parameter value
        /// </summary>
        /// <param name="property">An expression getting the property to filter for</param>
        /// <param name="retriever">A function that will get the value of the parameter for a given request</param>
        /// <typeparam name="T">The type of the parameter to filter with</typeparam>
        /// <returns>This <see cref="RestModelOptionsBuilder{TModel, TUser}" /> object, for chaining</returns>
        public RestModelOptionsBuilder <TModel, TUser> FilterByParameterEqual <T>(Expression <Func <TModel, T> > property, ParameterRetriever retriever)
        {
            PropertyInfo Info = RestModelOptionsBuilder <TModel, TUser> .ExtractProperty(property);

            ParameterExpression ModelParameter     = Expression.Parameter(typeof(TModel));
            MemberExpression    PropertyExpression = Expression.Property(ModelParameter, Info);
            Type PropertyType = typeof(T);

            this.Filter(
                (c, d) => {
                string?ParamValue = retriever.GetValue(c.Request);
                if (ParamValue == null)
                {
                    throw new ConditionFailedException("Failed to parse request parameter");
                }
                T Parsed = (T)ParameterResolver.ParseParameter(ParamValue, PropertyType);

                // ModelParameter.Property == ParamValue
                Expression ComparisonExpression = Expression.Equal(PropertyExpression, Expression.Constant(Parsed));

                // and create the lambda
                Expression <Func <TModel, bool> > FilterExpression =
                    Expression.Lambda <Func <TModel, bool> >(ComparisonExpression, ModelParameter);
                return(d.Where(FilterExpression));
            });
            return(this);
        }
        /// <summary>
        ///     Sets a value on parsed models before the operation occurs.
        /// </summary>
        /// <param name="property">The property to set the value on</param>
        /// <param name="retriever">A parameter retriever to use to get the value for a request</param>
        /// <returns>This <see cref="RestModelOptionsBuilder{TModel, TUser}" /> object, for chaining</returns>
        public RestModelOptionsBuilder <TModel, TUser> SetValue(PropertyInfo property, ParameterRetriever retriever)
        {
            Type PropertyType = property.PropertyType;

            return(this.SetValue(
                       property,
                       c => ParameterResolver.ParseParameter(retriever.GetValue(c.Request), PropertyType)));
        }
Esempio n. 4
0
 /// <summary>
 ///		Sets the value of all properties on this <see cref="Response{TModel}"/> that have the given attribute applied to them. If no such properties exist, no action will occur.
 /// </summary>
 /// <typeparam name="TAttribute">The attribute to match on the properties to set</typeparam>
 /// <param name="value">The string value to assign to that property</param>
 /// <remarks>
 ///		This method will attempt to convert the string value to a supported type if the type of the matching property is not string.
 /// </remarks>
 public void SetString <TAttribute>(string value) where TAttribute : Attribute
 {
     PropertyInfo[] Matching = this.Properties.Where(p => p.GetCustomAttribute <TAttribute>(false) != null).ToArray();
     foreach (PropertyInfo ToSet in Matching)
     {
         ToSet.GetSetMethod()?.Invoke(
             this,
             new[] { ParameterResolver.ParseParameter(value, ToSet.PropertyType) });
         this.SetProperties.Add(ToSet);
     }
 }
Esempio n. 5
0
 /// <summary>
 ///		Sets the value of all properties on this <see cref="Response{TModel}"/> that have the <see cref="ResponseValueAttribute"/> with the given name applied to them. If no such properties exist, no action will occur.
 /// </summary>
 /// <param name="name">The name given to the <see cref="ResponseValueAttribute"/> on the properties to set</param>
 /// <param name="value">The string value to assign to that property</param>
 /// <remarks>
 ///		This method will attempt to convert the string value to a supported type if the type of the matching property is not string.
 /// </remarks>
 public void SetString(string name, string value)
 {
     if (name == null)
     {
         throw new ArgumentNullException(nameof(name));
     }
     PropertyInfo[] Matching = this.Properties.Where(p => p.GetCustomAttribute <ResponseValueAttribute>()?.Name == name).ToArray();
     foreach (PropertyInfo ToSet in Matching)
     {
         ToSet.GetSetMethod()?.Invoke(
             this,
             new[] { ParameterResolver.ParseParameter(value, ToSet.PropertyType) });
         this.SetProperties.Add(ToSet);
     }
 }
Esempio n. 6
0
        /// <summary>
        ///     Updates the models specified in the request body by their primary key
        /// </summary>
        /// <param name="context">The current API context</param>
        /// <param name="dataset">The filtered dataset to operate on</param>
        /// <returns>The affected models</returns>
        public virtual async Task <IEnumerable <TModel> > OperateAsync(
            IApiContext <TModel, object> context,
            IQueryable <TModel> dataset)
        {
            TContext DatabaseContext = context.Services.GetRequiredService <TContext>();

            // todo: make better
            List <TModel> UpdatedList = new List <TModel>();

            if (context.Parsed == null)
            {
                throw new OperationFailedException("Must have a parsed body to update");
            }

            foreach (ParseResult <TModel> Result in context.Parsed)
            {
                // by default assume the properties were parsed with the rest of the model
                // todo: this is the ugliest thing i have ever seen
                object?[] Values = this.ParameterDelegates == null
                                                           ? this.Properties.Select(p => p.GetGetMethod()?.Invoke(Result.ParsedModel, null))
                                   .ToArray()
                                                           : this.Properties.Zip(this.ParameterDelegates).Select(
                    v => ParameterResolver.ParseParameter(
                        v.Second.GetValue(context.Request),
                        v.First.PropertyType)).ToArray();

                if (Values.Any(v => v == null))
                {
                    throw new OperationFailedException(
                              "Missing expected value for update operation",
                              new ArgumentNullException(
                                  this.Properties[Array.FindIndex(Values, v => v == null)].Name,
                                  "Parameter was null"), 400);
                }

                // so that ef can still use sql queries, let's generate an expression tree for it to use
                ParameterExpression ModelParameter = Expression.Parameter(typeof(TModel));

                // generate expressions. boils down to: ModelParameter.Property == Values[i]
                Expression[] ComparisonExpressions = this.Properties.Zip(Values).Select(
                    v => (Expression)Expression.Equal(
                        Expression.Property(ModelParameter, v.First),
                        Expression.Constant(v.Second))).ToArray();

                // && them all together
                Expression AggregateExpression = ComparisonExpressions.Aggregate(Expression.AndAlso);

                // and create the lambda
                Expression <Func <TModel, bool> > FilterExpression =
                    Expression.Lambda <Func <TModel, bool> >(AggregateExpression, ModelParameter);

                IEnumerable <TModel> ExistingModels = dataset.Where(FilterExpression);

                foreach (TModel Existing in ExistingModels)
                {
                    foreach (PropertyInfo Updated in Result.PresentProperties)
                    {
                        object?NewValue = Updated.GetGetMethod()?.Invoke(Result.ParsedModel, null);
                        Updated.GetSetMethod()?.Invoke(Existing, new[] { NewValue });
                    }

                    UpdatedList.Add(Existing);

                    // TODO: obviously it's 1000x better to update all at once. do that
                    await DatabaseContext.UpdateAsync(Existing);
                }
            }

            return(UpdatedList);
        }