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); }
/// <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); }
/// <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); }
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); }
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); }
/// <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); }
/// <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)); }
/// <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))); }