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> /// Given a value, attempt to set it to a property on a destination object. This may involve changing the object, /// such as converting an int to a double, or an int to an int?, or expanding an object into its projection /// </summary> /// <param name="originalValue"></param> /// <param name="destinationProperty"></param> /// <param name="destinationObject"></param> /// <param name="context"></param> /// <param name="propertyReference"></param> /// <param name="visited">todo: describe visited parameter on SetValueToProperty</param> /// <returns></returns> private bool SetValueToProperty(object originalValue, PropertyInfo destinationProperty, object destinationObject, ContextType context, PropertyReference propertyReference, HashSet <int> visited) { // If it is null then just do a direct assignment if (originalValue == null) { destinationProperty.SetValue(destinationObject, null); return(true); } // Try to do an assignment including any conversion needed if (destinationProperty.TrySetValueHandleConvert(destinationObject, originalValue)) { return(true); } if (WillExpand(originalValue)) { IEnumerable <PropertyReference> propertySubReferences = CreatePropertyReferenceList(destinationProperty, propertyReference); var expandedValue = Expand(originalValue, context, propertySubReferences, visited, destinationProperty.PropertyType); if (destinationProperty.TrySetValueHandleConvert(destinationObject, expandedValue)) { return(true); } } return(false); }
/// <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))); }