Beispiel #1
0
        private IEnumerable <PropertyReference> ValidateIncludes(IEnumerable <PropertyReference> includes, Type sourceType)
        {
            if (includes.Any())
            {
                return(includes);
            }

            if (!Mappings.ContainsKey(sourceType))
            {
                // in the case of a blind object, default to source properties.  This is a bit dangerous!
                includes = sourceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                           .Select(p => new PropertyReference()
                {
                    PropertyName = p.Name
                });
            }

            // if this doesn't have any includes specified, use the default
            if (!includes.Any())
            {
                includes = PropertyReference.Parse(Mappings[sourceType].DefaultIncludes);
            }

            // if this STILL doesn't have any includes, that means include everything
            if (!includes.Any())
            {
                includes = Mappings[sourceType].DestinationType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                           .Select(p => new PropertyReference()
                {
                    PropertyName = p.Name
                });
            }

            return(includes);
        }
        /// <summary>
        /// Add an alternative mapping destination type, and provide the opportunity to customize that.
        /// </summary>
        /// <typeparam name="TNewDestType"></typeparam>
        /// <param name="defaultIncludes"></param>
        /// <param name="config"></param>
        /// <returns></returns>
        public ProjectionDefinitionConfiguration <TSourceType, TNewDestType> AlternativeMap <TNewDestType>(
            string defaultIncludes = null,
            Action <ProjectionDefinitionConfiguration <TSourceType, TNewDestType> > config = null)
        {
            var parsedDefaultIncludes = (defaultIncludes == null) ? new List <PropertyReference> {
            } : (List <PropertyReference>)PropertyReference.Parse(defaultIncludes);

            defaultIncludes = PropertyReference.CompareAndConstructDefaultIncludes(parsedDefaultIncludes, typeof(TNewDestType).GetTypeInfo());

            var newMapping = new ProjectionDefinitionConfiguration <TSourceType, TNewDestType>
            {
                InternalMappingDefinition    = InternalMappingDefinition,
                InternalProjectionDefinition = new ProjectionDefinition
                {
                    DefaultIncludes = defaultIncludes,
                    DestinationType = typeof(TNewDestType),
                }
            };

            InternalMappingDefinition.Destinations.Add(typeof(TNewDestType), newMapping.InternalProjectionDefinition);

            if (config != null)
            {
                config(newMapping);
            }

            return(newMapping);
        }
Beispiel #3
0
        /// <summary>
        /// Add a mapping of a data type to a projection type
        /// </summary>
        /// <typeparam name="TSourceType"></typeparam>
        /// <typeparam name="TDestType"></typeparam>
        /// <param name="defaultIncludes"></param>
        /// <param name="config"></param>
        /// <returns></returns>
        public PopcornConfiguration Map <TSourceType, TDestType>(
            string defaultIncludes = null,
            Action <MappingDefinitionConfiguration <TSourceType, TDestType> > config = null)
        {
            var sourceType = typeof(TSourceType);
            var destType   = typeof(TDestType);

            // Validate and construct the actual default includes from both attributes and those passed in at the time of the mapping
            var destTypeInfo          = typeof(TDestType).GetTypeInfo();
            var parsedDefaultIncludes = (defaultIncludes == null) ? new List <PropertyReference> {
            } : (List <PropertyReference>)PropertyReference.Parse(defaultIncludes);

            defaultIncludes = PropertyReference.CompareAndConstructDefaultIncludes(parsedDefaultIncludes, destTypeInfo);
            var mappingConfiguration = new MappingDefinitionConfiguration <TSourceType, TDestType> {
            };

            // Assign the existing mapping to be configured should it already exist
            if (_expander.Mappings.ContainsKey(sourceType))
            {
                // We will allow a client to reference the same mapping multiple times to add more translations etc,
                // but ONLY if the types remain consistent!
                if (_expander.Mappings[sourceType].DefaultDestinationType != destType)
                {
                    throw new InvalidOperationException($"Expander was default-mapped multiple times for {sourceType}.");
                }

                // Assign the existing mapping information
                mappingConfiguration.InternalMappingDefinition    = _expander.Mappings[sourceType];
                mappingConfiguration.InternalProjectionDefinition = _expander.Mappings[sourceType].Destinations[destType];
            }
            else
            {
                // Create the configuration starting with the 'default' mapping
                mappingConfiguration.InternalMappingDefinition = new MappingDefinition
                {
                    DefaultDestinationType = destType,
                };

                // And assign it a projecion definition
                mappingConfiguration.InternalProjectionDefinition = new ProjectionDefinition
                {
                    DefaultIncludes = defaultIncludes,
                    DestinationType = destType,
                };

                mappingConfiguration.InternalMappingDefinition.Destinations.Add(destType, mappingConfiguration.InternalProjectionDefinition);

                _expander.Mappings.Add(typeof(TSourceType), mappingConfiguration.InternalMappingDefinition);
            }

            if (config != null)
            {
                config(mappingConfiguration);
            }

            return(this);
        }
Beispiel #4
0
        /// <summary>
        /// Add a mapping of a data type to a projection type
        /// </summary>
        /// <typeparam name="TSourceType"></typeparam>
        /// <typeparam name="TDestType"></typeparam>
        /// <param name="defaultIncludes"></param>
        /// <param name="config"></param>
        /// <returns></returns>
        public PopcornConfiguration Map <TSourceType, TDestType>(
            string defaultIncludes = null,
            Action <MappingDefinitionConfiguration <TSourceType, TDestType> > config = null)
        {
            var sourceType = typeof(TSourceType);
            var destType   = typeof(TDestType);

            // Validate and construct the actual default includes from both attributes and those passed in at the time of the mapping
            var destTypeInfo          = typeof(TDestType).GetTypeInfo();
            var parsedDefaultIncludes = (defaultIncludes == null) ? new List <PropertyReference> {
            } : (List <PropertyReference>)PropertyReference.Parse(defaultIncludes);

            defaultIncludes = CompareAndConstructDefaultIncludes(parsedDefaultIncludes, destTypeInfo);

            var definition = new MappingDefinitionConfiguration <TSourceType, TDestType>
            {
                InternalDefinition = new MappingDefinition
                {
                    DestinationType = destType,
                    DefaultIncludes = defaultIncludes
                }
            };

            // We will allow a client to reference the same mapping multiple times to add more translations etc,
            // but ONLY if the types remain consistent!
            if (_expander.Mappings.ContainsKey(sourceType))
            {
                if (_expander.Mappings[sourceType].DestinationType != destType)
                {
                    throw new InvalidOperationException(
                              $"Expander was mapped multiple times but types do not match."
                              + " {sourceType} was previously mapped to {this.Mappings[sourceType].DestinationType} and attempted to remap to {destType}."
                              + "  Only one destination type can be specified.");
                }
                if (defaultIncludes != null)
                {
                    _expander.Mappings[sourceType].DefaultIncludes = defaultIncludes;
                }
                definition = new MappingDefinitionConfiguration <TSourceType, TDestType>
                {
                    InternalDefinition = _expander.Mappings[sourceType]
                };
            }
            else
            {
                _expander.Mappings.Add(typeof(TSourceType), definition.InternalDefinition);
            }

            if (config != null)
            {
                config(definition);
            }

            return(this);
        }
Beispiel #5
0
        private static IEnumerable <PropertyReference> CreatePropertyReferenceList(PropertyInfo destinationProperty, PropertyReference propertyReference)
        {
            // If the requestor didn't define any fields to include, check if the property has any default fields
            IEnumerable <PropertyReference> propertySubReferences = propertyReference.Children;

            if (!propertySubReferences.Any())
            {
                // check if there's a property attribute
                var includes = destinationProperty.GetCustomAttribute <SubPropertyIncludeByDefault>();
                if (includes != null)
                {
                    propertySubReferences = PropertyReference.Parse(includes.Includes);
                }
            }

            return(propertySubReferences);
        }
Beispiel #6
0
        public void OnActionExecuted(ActionExecutedContext context)
        {
            Exception exceptionResult = null;
            object    resultObject    = null;
            Type      destinationType = null;


            var filterDescriptor = context
                                   .ActionDescriptor
                                   .FilterDescriptors
                                   .SingleOrDefault(d => d.Filter.GetType() == typeof(ExpandResultAttribute));

            if (!_expandAllEndpoints)
            {
                //Cast the filter property to ExpandResultAttribute
                var attributeInstance = filterDescriptor?.Filter as ExpandResultAttribute;

                //If the attribute is null, i.e. not present, or false, it shouldn't expand and we return here
                if (!(attributeInstance?.ShouldExpand ?? false))
                {
                    return;
                }
            }

            if (filterDescriptor != null)
            {
                destinationType = ((ExpandResultAttribute)filterDescriptor.Filter).DestinationType;
            }

            var doNotExpandAttribute = context
                                       .ActionDescriptor
                                       .FilterDescriptors
                                       .SingleOrDefault(d => d.Filter.GetType() == typeof(DoNotExpandResultAttribute));

            if (doNotExpandAttribute != null)
            {
                return;
            }

            // Set the error out of the gate should something have gone wrong coming into Popcorn
            if (context.Exception != null)
            {
                exceptionResult = context.Exception;
                if (context.HttpContext.Response.StatusCode == 200)
                {
                    context.HttpContext.Response.StatusCode = 500;
                }
                context.ExceptionHandled = true;     // Setting this so the inspector is still respected
            }
            else if (context.Result is ObjectResult) // Disect the response if there is something to unfold and no exception
            {
                resultObject = ((ObjectResult)context.Result).Value;

                // Wrap the main work here in a try/catch that we can then pass to our inspector
                try
                {
                    if (_expander.WillExpand(resultObject))
                    {
                        // see if we can find some include statements
                        string includes = "[]";
                        if (context.HttpContext.Request.Query.ContainsKey("include"))
                        {
                            includes = context.HttpContext.Request.Query["include"];
                        }
                        else if (context.HttpContext.Request.Headers?.ContainsKey("API-INCLUDE") ?? false)
                        {
                            includes = context.HttpContext.Request.Headers["API-INCLUDE"];
                        }

                        // Use our expander and expand the object
                        resultObject = _expander.Expand(resultObject, _context, PropertyReference.Parse(includes), destinationTypeHint: destinationType);
                    }

                    // Sort should there be anything to sort
                    if (resultObject != null)
                    {
                        // Assign sortDirection where necessary, but default to Ascending if nothing passed in
                        SortDirection sortDirection = SortDirection.Ascending;
                        if (context.HttpContext.Request.Query.ContainsKey("sortDirection"))
                        {
                            // Assign the proper sort direction, but invalidate an invalid value
                            try
                            {
                                sortDirection = (SortDirection)Enum.Parse(typeof(SortDirection), context.HttpContext.Request.Query["sortDirection"]);
                            }
                            catch (ArgumentException)
                            {
                                throw new ArgumentException(context.HttpContext.Request.Query["sortDirection"]);
                            }
                        }

                        // Do any sorting as specified
                        if (context.HttpContext.Request.Query.ContainsKey("sort"))
                        {
                            resultObject = _expander.Sort(resultObject, context.HttpContext.Request.Query["sort"], sortDirection);
                        }
                    }
                }
                catch (Exception e)
                {
                    exceptionResult = e;
                    // Set the response code as appropriate for a caught error
                    context.HttpContext.Response.StatusCode = 500;
                }
            }
            else
            {
                return;
            }

            // Apply our inspector to the expanded content
            if (_inspector != null)
            {
                resultObject = _inspector(resultObject, _context, exceptionResult);
            }
            else if (exceptionResult != null) // Have to rethrow the error if there is no inspector set so as to not return false positives
            {
                throw exceptionResult;
            }

            context.Result = new JsonResult(resultObject, _jsonOptions);
        }
Beispiel #7
0
        /// <summary>
        /// Verify that we have the appropriate include list for a type, taking into account any requested,
        /// or otherwise defaults supplied.
        /// </summary>
        /// <param name="includes"></param>
        /// <param name="sourceType"></param>
        /// <param name="destType"></param>
        /// <returns></returns>
        private IEnumerable <PropertyReference> ConstructIncludes(IEnumerable <PropertyReference> includes, Type sourceType, Type destType)
        {
            // Out of the gate we want to first see if the only property to be included is a wildcard
            if ((includes.Count() == 1) && (includes.Any(i => i.PropertyName == "*")))
            {
                var wildCardIncludes = new List <PropertyReference> {
                };
                var mapDef           = new MappingDefinition();

                // Check to see if the object is to be blind expanded and make the destination the same as the source if it is
                if (destType == null)
                {
                    destType = sourceType;
                }
                else // in the case that the object isn't to be blind expanded get the proper mapping
                {
                    Mappings.TryGetValue(sourceType, out mapDef);
                }

                // Have all of the destination type properties set to be included
                foreach (PropertyInfo info in destType.GetTypeInfo().GetProperties())
                {
                    var matchingSourceProp = sourceType.GetTypeInfo().GetProperty(info.Name);

                    // Make sure that the property isn't marked as InternalOnly on the sourceType
                    // Which is only an issue if they marked the type to throw an error if it's requested
                    if (matchingSourceProp != null)
                    {
                        if (matchingSourceProp.GetCustomAttributes().Any(att => att.GetType() == typeof(InternalOnlyAttribute)))
                        {
                            // Only add the property if it isn't marked InternalOnly
                            continue;
                        }
                    }

                    // Also make sure that the property exists on the destination type in some capacity
                    // This will never be hit by a blindly expanded object as the source and destination type are identical
                    if (matchingSourceProp == null)
                    {
                        try
                        {
                            // Check to see if there are any translators that would apply the object to the projection ultimately
                            var transTest = mapDef.DefaultDestination().Translators[info.Name];
                        }
                        catch (Exception)
                        {
                            // This property isn't known to the projection at all and thus should not be included
                            continue;
                        }
                    }

                    wildCardIncludes.Add(new PropertyReference {
                        PropertyName = info.Name
                    });
                }

                return(wildCardIncludes);
            }

            if (includes.Any())
            {
                return(includes);
            }

            if (destType == null)
            {
                // in the case of a blind object, default to source properties.  This is a bit dangerous!
                includes = sourceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                           .Select(p => new PropertyReference()
                {
                    PropertyName = p.Name
                });
            }

            // if this doesn't have any includes specified, use the default
            if (!includes.Any())
            {
                includes = PropertyReference.Parse(Mappings[sourceType].DestinationForType(destType).DefaultIncludes);
            }

            // if this STILL doesn't have any includes, that means include everything
            if (!includes.Any())
            {
                includes = destType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                           .Select(p => new PropertyReference()
                {
                    PropertyName = p.Name
                });
            }

            return(includes);
        }
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            Exception exceptionResult = null;
            object    resultObject    = null;

            // Set the error out of the gate should something have gone wrong coming into Popcorn
            if (context.Exception != null)
            {
                exceptionResult = context.Exception;
                if (context.HttpContext.Response.StatusCode == 200)
                {
                    context.HttpContext.Response.StatusCode = 500;
                }
                context.ExceptionHandled = true;     // Setting this so the inspector is still respected
            }
            else if (context.Result is ObjectResult) // Disect the response if there is something to unfold and no exception
            {
                resultObject = ((ObjectResult)context.Result).Value;

                // Wrap the main work here in a try/catch that we can then pass to our inspector
                try
                {
                    if (_expander.WillExpand(resultObject))
                    {
                        // see if we can find some include statements
                        string includes = "[]";
                        if (context.HttpContext.Request.Query.ContainsKey("include"))
                        {
                            includes = context.HttpContext.Request.Query["include"];
                        }
                        else if (context.HttpContext.Request.Headers?.ContainsKey("API-INCLUDE") ?? false)
                        {
                            includes = context.HttpContext.Request.Headers["API-INCLUDE"];
                        }

                        // Use our expander and expand the object
                        resultObject = _expander.Expand(resultObject, _context, PropertyReference.Parse(includes));
                    }

                    // Sort should there be anything to sort
                    if (resultObject != null)
                    {
                        // Assign sortDirection where necessary, but default to Ascending if nothing passed in
                        SortDirection sortDirection = SortDirection.Ascending;
                        if (context.HttpContext.Request.Query.ContainsKey("sortDirection"))
                        {
                            // Assign the proper sort direction, but invalidate an invalid value
                            try
                            {
                                sortDirection = (SortDirection)Enum.Parse(typeof(SortDirection), context.HttpContext.Request.Query["sortDirection"]);
                            }
                            catch (ArgumentException)
                            {
                                throw new ArgumentException(context.HttpContext.Request.Query["sortDirection"]);
                            }
                        }

                        // Do any sorting as specified
                        if (context.HttpContext.Request.Query.ContainsKey("sort"))
                        {
                            resultObject = _expander.Sort(resultObject, context.HttpContext.Request.Query["sort"], sortDirection);
                        }
                    }
                } catch (Exception e)
                {
                    exceptionResult = e;
                    // Set the response code as appropriate for a caught error
                    context.HttpContext.Response.StatusCode = 500;
                }
            }

            // Apply our inspector to the expanded content
            if (_inspector != null)
            {
                resultObject = _inspector(resultObject, _context, exceptionResult);
            }
            else if (exceptionResult != null)   // Have to rethrow the error if there is no inspector set so as to not return false positives
            {
                throw exceptionResult;
            }

            context.Result = new JsonResult(resultObject,
                                            new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore
            });
            base.OnActionExecuted(context);
        }
Beispiel #9
0
 /// <summary>
 /// The entry point method for converting a type into its projection and selectively including data.
 /// This will work on either a Mapped Type or a collection of a Mapped Type.
 /// This version allows specification of the includes in string format
 /// </summary>
 public object Expand(object source, ContextType context, string includes, HashSet <int> visited = null, Type destinationTypeHint = null)
 {
     return(Expand(source, context, PropertyReference.Parse(includes), visited, destinationTypeHint));
 }
Beispiel #10
0
 /// <summary>
 /// A generic overload that automatically provides the type hint.
 /// This accepts a string include list of the form "[Prop1,Prop2[SubProp1]]"
 /// </summary>
 /// <typeparam name="TDestType"></typeparam>
 /// <param name="source"></param>
 /// <param name="includes"></param>
 /// <param name="context"></param>
 /// <param name="visited"></param>
 /// <returns></returns>
 public TDestType Expand <TDestType>(object source, string includes, ContextType context = null, HashSet <int> visited = null)
 {
     return((TDestType)Expand(source, context, PropertyReference.Parse(includes), visited, typeof(TDestType)));
 }