protected override IList <JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { var properties = base.CreateProperties(type, memberSerialization); var declaredPropertiesCopy = TypeCache.GetDeclaredProperties(type).ToDictionary(p => p.Key, p => p.Value); foreach (var property in properties) { if (property.PropertyName is string name) { declaredPropertiesCopy.Remove(name); } } var additionalProperties = declaredPropertiesCopy .Values .Where(p => !p.Hidden) .Select(p => new JsonProperty { PropertyType = p.Type, PropertyName = p.Name, Readable = p.IsReadable, Writable = p.IsWritable, ValueProvider = new DefaultValueProvider(p), ObjectCreationHandling = p.ReplaceOnUpdate ? ObjectCreationHandling.Replace : ObjectCreationHandling.Reuse, NullValueHandling = p.HiddenIfNull ? NullValueHandling.Ignore : NullValueHandling.Include, Order = p.Order }); return(properties.Union(additionalProperties).ToList()); }
internal BinaryResource(BinarySelector <T> binarySelector, TypeCache typeCache) { Name = typeof(T).GetRESTableTypeName() ?? throw new Exception(); Type = typeof(T); AvailableMethods = new[] { Method.GET }; var attribute = typeof(T).GetCustomAttribute <RESTableAttribute>(); IsInternal = attribute is RESTableInternalAttribute; InterfaceType = typeof(T).GetRESTableInterfaceType(); ResourceKind = ResourceKind.BinaryResource; (_, ConditionBindingRule) = typeof(T).GetDynamicConditionHandling(attribute); if (attribute != null) { Description = attribute.Description; BinarySelector = binarySelector; Members = typeCache.GetDeclaredProperties(typeof(T)); GETAvailableToAll = attribute.GETAvailableToAll; } var typeName = typeof(T).FullName; if (typeName?.Contains("+") == true) { IsInnerResource = true; var location = typeName.LastIndexOf('+'); ParentResourceName = typeName.Substring(0, location).Replace('+', '.'); Name = typeName.Replace('+', '.'); } }
/// <summary> /// All types covered by RESTable are selected and validated here /// /// Resources /// entity /// regular /// wrapper /// terminal /// binary /// Views /// /// </summary> /// <returns></returns> private void ValidateAndBuildTypeLists(out List <Type> regularTypes, out List <Type> wrapperTypes, out List <Type> terminalTypes, out List <Type> binaryTypes, out List <Type> eventTypes) { var allTypes = typeof(object).GetSubclasses().ToList(); var resourceTypes = allTypes.Where(t => t.HasAttribute <RESTableAttribute>(out var a) && a is not RESTableProceduralAttribute).ToArray(); var viewTypes = allTypes.Where(t => t.HasAttribute <RESTableViewAttribute>()).ToArray(); if (resourceTypes.Union(viewTypes).ContainsDuplicates(t => t.GetRESTableTypeName()?.ToLower() ?? "unknown", out var dupe)) { throw new InvalidResourceDeclarationException("Types used by RESTable must have unique case insensitive names. Found " + $"multiple types with case insensitive name '{dupe}'."); } void ValidateViewTypes(ICollection <Type> _viewTypes) { foreach (var viewType in _viewTypes) { var resource = viewType.DeclaringType; if (!viewType.IsClass || !viewType.IsNestedPublic || resource == null) { throw new InvalidResourceViewDeclarationException(viewType, "Resource view types must be declared as public classes nested within the the " + "resource type they are views for"); } if (viewType.IsSubclassOf(resource)) { throw new InvalidResourceViewDeclarationException(viewType, "Views cannot inherit from their resource types"); } if (typeof(IResourceWrapper).IsAssignableFrom(resource)) { var wrapped = resource.GetWrappedType(); if (!viewType.ImplementsGenericInterface(typeof(ISelector <>), out var param) || param[0] != wrapped) { throw new InvalidResourceViewDeclarationException(viewType, $"Expected view type to implement ISelector<{wrapped.GetRESTableTypeName()}>"); } } else if (!viewType.ImplementsGenericInterface(typeof(ISelector <>), out var param) || param[0] != resource) { throw new InvalidResourceViewDeclarationException(viewType, $"Expected view type to implement ISelector<{resource.GetRESTableTypeName()}>"); } var resourceProperties = TypeCache.GetDeclaredProperties(resource); foreach (var property in TypeCache.FindAndParseDeclaredProperties(viewType).Where(prop => resourceProperties.ContainsKey(prop.Name))) { throw new InvalidResourceViewDeclarationException(viewType, $"Invalid property '{property.Name}'. Resource view types must not contain any public instance " + "properties with the same name (case insensitive) as a property of the corresponding resource. " + "All properties in the resource are automatically inherited for use in conditions with the view."); } } } (regularTypes, wrapperTypes, terminalTypes, binaryTypes, eventTypes) = ResourceValidator.Validate(resourceTypes); ValidateViewTypes(viewTypes); }
internal View(Type viewType, TypeCache typeCache) { var viewAttribute = viewType.GetCustomAttribute <RESTableViewAttribute>(); Type = viewType; if (viewAttribute != null) { Name = viewAttribute.CustomName ?? viewType.Name; ViewSelector = DelegateMaker.GetDelegate <ViewSelector <TResource> >(viewType); AsyncViewSelector = DelegateMaker.GetDelegate <AsyncViewSelector <TResource> >(viewType); Members = typeCache.GetDeclaredProperties(viewType); Description = viewAttribute.Description; ConditionBindingRule = viewAttribute.AllowDynamicConditions ? TermBindingRule.DeclaredWithDynamicFallback : TermBindingRule.OnlyDeclared; } }
internal static bool IsValid(IEntityResource resource, TypeCache typeCache, out string reason) { if (resource.InterfaceType != null) { var interfaceName = resource.InterfaceType.GetRESTableTypeName(); var members = typeCache.GetDeclaredProperties(resource.InterfaceType); if (members.ContainsKey("objectno")) { reason = $"Invalid Interface '{interfaceName}' assigned to resource '{resource.Name}'. " + "Interface contained a property with a reserved name: 'ObjectNo'"; return(false); } if (members.ContainsKey("objectid")) { reason = $"Invalid Interface '{interfaceName}' assigned to resource '{resource.Name}'. " + "Interface contained a property with a reserved name: 'ObjectID'"; return(false); } } reason = null; return(true); }
internal TerminalResource(TypeCache typeCache) { Name = typeof(T).GetRESTableTypeName() ?? throw new Exception(); Type = typeof(T); AvailableMethods = new[] { Method.GET }; IsInternal = false; IsGlobal = true; var attribute = typeof(T).GetCustomAttribute <RESTableAttribute>(); InterfaceType = typeof(T).GetRESTableInterfaceType(); ResourceKind = ResourceKind.TerminalResource; (_, ConditionBindingRule) = typeof(T).GetDynamicConditionHandling(attribute); Description = attribute?.Description; Members = typeCache.GetDeclaredProperties(typeof(T)); Constructor = typeof(T).GetConstructors().First(); ConstructorParameterIndexes = new Dictionary <string, int>(StringComparer.OrdinalIgnoreCase); ConstructorParameterInfos = Constructor.GetParameters(); for (var i = 0; i < ConstructorParameterInfos.Length; i += 1) { HasConstructorParameters = true; var parameter = ConstructorParameterInfos[i]; ConstructorParameterIndexes[parameter.Name] = i; } GETAvailableToAll = attribute?.GETAvailableToAll == true; IsDynamicTerminal = typeof(IDictionary <string, object>).IsAssignableFrom(typeof(T)); var typeName = typeof(T).FullName; if (typeName?.Contains('+') == true) { IsInnerResource = true; var location = typeName.LastIndexOf('+'); ParentResourceName = typeName.Substring(0, location).Replace('+', '.'); Name = typeName.Replace('+', '.'); } }
/// <inheritdoc /> public override async Task <long> SerializeCollection <T>(IAsyncEnumerable <T> collection, Stream stream, IRequest request, CancellationToken cancellationToken) where T : class { if (collection == null) { return(0); } try { using var package = new ExcelPackage(stream); var currentRow = 1; var worksheet = package.Workbook.Worksheets.Add(request?.Resource.Name ?? "Sheet1"); async Task writeEntities(IAsyncEnumerable <object> entities) { switch (entities) { case IAsyncEnumerable <IDictionary <string, object> > dicts: var columns = new Dictionary <string, int>(StringComparer.OrdinalIgnoreCase); await foreach (var dict in dicts.ConfigureAwait(false)) { currentRow += 1; foreach (var(key, value) in dict) { if (!columns.TryGetValue(key, out var column)) { column = columns.Count + 1; columns[key] = column; var cell = worksheet.Cells[1, column]; cell.Style.Font.Bold = true; cell.Value = key; } WriteExcelCell(worksheet.Cells[currentRow, column], value); } } break; case IAsyncEnumerable <JObject> jobjects: var _columns = new Dictionary <string, int>(StringComparer.OrdinalIgnoreCase); await foreach (var jobject in jobjects.ConfigureAwait(false)) { currentRow += 1; foreach (var(key, value) in jobject) { if (!_columns.TryGetValue(key, out var column)) { column = _columns.Count + 1; _columns[key] = column; var cell = worksheet.Cells[1, column]; cell.Style.Font.Bold = true; cell.Value = key; } WriteExcelCell(worksheet.Cells[currentRow, column], value.ToObject <object>()); } } break; default: var properties = TypeCache.GetDeclaredProperties(typeof(T)).Values.Where(p => !p.Hidden).ToList(); var columnIndex = 1; foreach (var property in properties) { var cell = worksheet.Cells[1, columnIndex]; cell.Style.Font.Bold = true; cell.Value = property.Name; columnIndex += 1; } await foreach (var entity in entities.ConfigureAwait(false)) { currentRow += 1; columnIndex = 1; foreach (var property in properties) { WriteExcelCell(worksheet.Cells[currentRow, columnIndex], GetCellValue(property, entity)); columnIndex += 1; } } break; } } await writeEntities(collection).ConfigureAwait(false); if (currentRow == 1) { return(0); } worksheet.Cells.AutoFitColumns(0); package.Save(); return((long)currentRow - 1); } catch (Exception e) { throw new ExcelFormatException(e.Message, e); } }
public (List <Type> regular, List <Type> wrappers, List <Type> terminals, List <Type> binaries, List <Type> events) Validate(params Type[] types) { var entityTypes = types .Where(t => !typeof(Terminal).IsAssignableFrom(t) && !typeof(IEvent).IsAssignableFrom(t) && !t.ImplementsGenericInterface(typeof(IBinary <>))) .ToList(); var regularTypes = entityTypes .Where(t => !typeof(IResourceWrapper).IsAssignableFrom(t)) .ToList(); var wrapperTypes = entityTypes .Where(t => typeof(IResourceWrapper).IsAssignableFrom(t)) .ToList(); var terminalTypes = types .Where(t => typeof(Terminal).IsAssignableFrom(t)) .ToList(); var binaryTypes = types .Where(t => t.ImplementsGenericInterface(typeof(IBinary <>))) .ToList(); var eventTypes = types .Where(t => !t.IsAbstract && typeof(IEvent).IsAssignableFrom(t)) .ToList(); void ValidateCommon(Type type) { #region Check general stuff if (type.FullName == null) { throw new InvalidResourceDeclarationException( "Encountered an unknown type. No further information is available."); } if (type.IsGenericTypeDefinition) { throw new InvalidResourceDeclarationException( $"Found a generic resource type '{type.GetRESTableTypeName()}'. RESTable resource types must be non-generic"); } if (type.FullName.Count(c => c == '+') >= 2) { throw new InvalidResourceDeclarationException($"Invalid resource '{type.GetRESTableTypeName()}'. " + "Inner resources cannot have their own inner resources"); } if (type.HasAttribute <RESTableViewAttribute>()) { throw new InvalidResourceDeclarationException( $"Invalid resource type '{type.GetRESTableTypeName()}'. Resource types cannot be " + "decorated with the 'RESTableViewAttribute'"); } if (type.Namespace == null) { throw new InvalidResourceDeclarationException($"Invalid type '{type.GetRESTableTypeName()}'. Unknown namespace"); } if (Configuration.ReservedNamespaces.Contains(type.Namespace.ToLower()) && type.Assembly != typeof(RESTableConfigurator).Assembly) { throw new InvalidResourceDeclarationException( $"Invalid namespace for resource type '{type.GetRESTableTypeName()}'. Namespace '{type.Namespace}' is reserved by RESTable"); } if ((!type.IsClass || !type.IsPublic && !type.IsNestedPublic) && type.Assembly != typeof(Resource).Assembly) { throw new InvalidResourceDeclarationException( $"Invalid type '{type.GetRESTableTypeName()}'. Resource types must be public classes"); } if (type.GetRESTableInterfaceType() is Type interfaceType) { if (!interfaceType.IsInterface) { throw new InvalidResourceDeclarationException( $"Invalid Interface of type '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " + "Type is not an interface"); } if (interfaceType.GetProperties() .Select(p => p.Name) .ContainsDuplicates(StringComparer.OrdinalIgnoreCase, out var interfacePropDupe)) { throw new InvalidResourceMemberException( $"Invalid Interface of type '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " + $"Interface contained properties with duplicate names matching '{interfacePropDupe}' (case insensitive)."); } var interfaceName = interfaceType.GetRESTableTypeName(); foreach (var method in type.GetInterfaceMap(interfaceType).TargetMethods) { if (!method.IsSpecialName) { continue; } var interfaceProperty = interfaceType .GetProperties() .First(p => p.GetGetMethod()?.Name is string getname && method.Name.EndsWith(getname) || p.GetSetMethod()?.Name is string setname && method.Name.EndsWith(setname)); Type propertyType = null; if (method.IsPrivate && method.Name.StartsWith($"{interfaceName}.get_") || method.Name.StartsWith("get_")) { propertyType = method.ReturnType; } else if (method.IsPrivate && method.Name.StartsWith($"{interfaceName}.set_") || method.Name.StartsWith("set_")) { propertyType = method.GetParameters()[0].ParameterType; } if (propertyType == null) { throw new InvalidResourceDeclarationException( $"Invalid implementation of interface '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " + $"Unable to determine the type for interface property '{interfaceProperty.Name}'"); } PropertyInfo projectedProperty; if (method.Name.StartsWith($"{interfaceName}.get_")) { projectedProperty = method.GetInstructions() .Select(i => i.OpCode == OpCodes.Call && i.Operand is MethodInfo calledMethod && method.IsSpecialName ? type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .FirstOrDefault(p => p.GetGetMethod() == calledMethod) : null) .LastOrDefault(p => p != null); } else if (method.Name.StartsWith($"{interfaceName}.set_")) { projectedProperty = method.GetInstructions() .Select(i => i.OpCode == OpCodes.Call && i.Operand is MethodInfo calledMethod && method.IsSpecialName ? type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .FirstOrDefault(p => p.GetSetMethod() == calledMethod) : null) .LastOrDefault(p => p != null); } else { continue; } if (projectedProperty == null) { throw new InvalidResourceDeclarationException( $"Invalid implementation of interface '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " + $"RESTable was unable to determine which property of '{type.GetRESTableTypeName()}' that is exposed by interface " + $"property '{interfaceProperty.Name}'. For getters, RESTable will look for the last IL instruction " + "in the method body that fetches a property value from the resource type. For setters, RESTable will look " + "for the last IL instruction in the method body that sets a property value in the resource type."); } if (projectedProperty.PropertyType != propertyType) { throw new InvalidResourceDeclarationException( $"Invalid implementation of interface '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " + $"RESTable matched interface property '{interfaceProperty.Name}' with resource property '{projectedProperty.Name}' " + "using the interface property matching rules, but these properties have a type mismatch. Expected " + $"'{projectedProperty.PropertyType.GetRESTableTypeName()}' but found '{propertyType.GetRESTableTypeName()}' in interface"); } } } #endregion #region Check for invalid IDictionary implementation var validTypes = new[] { typeof(string), typeof(object) }; if (type.ImplementsGenericInterface(typeof(IDictionary <,>), out var typeParams) && !type.IsSubclassOf(typeof(JObject)) && !typeParams.SequenceEqual(validTypes)) { throw new InvalidResourceDeclarationException( $"Invalid resource declaration for type '{type.GetRESTableTypeName()}'. All resource types implementing " + "the generic 'System.Collections.Generic.IDictionary`2' interface must either be subclasses of " + "Newtonsoft.Json.Linq.JObject or have System.String as first type parameter and System.Object as " + $"second type parameter. Found {typeParams[0].GetRESTableTypeName()} and {typeParams[1].GetRESTableTypeName()}"); } #endregion #region Check for invalid IEnumerable implementation if ((type.ImplementsGenericInterface(typeof(IEnumerable <>)) || typeof(IEnumerable).IsAssignableFrom(type)) && !type.ImplementsGenericInterface(typeof(IDictionary <,>))) { throw new InvalidResourceDeclarationException( $"Invalid resource declaration for type '{type.GetRESTableTypeName()}'. The type has an invalid imple" + $"mentation of an IEnumerable interface. The resource '{type.GetRESTableTypeName()}' (or any of its base types) " + "cannot implement the \'System.Collections.Generic.IEnumerable`1\' or \'System.Collections.IEnume" + "rable\' interfaces without also implementing the \'System.Collections.Generic.IDictionary`2\' interface." ); } #endregion #region Check for public instance fields var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); if (fields.Any()) { throw new InvalidResourceMemberException( $"A RESTable resource cannot have public instance fields, only properties. Resource: '{type.GetRESTableTypeName()}' had " + $"fields: {string.Join(", ", fields.Select(f => $"'{f.Name}'"))} in resource '{type.GetRESTableTypeName()}'" ); } #endregion #region Check for properties with duplicate case insensitive names if (TypeCache.FindAndParseDeclaredProperties(type).ContainsDuplicates(DeclaredProperty.NameComparer, out var duplicate)) { throw new InvalidResourceMemberException( $"Invalid properties for resource '{type.GetRESTableTypeName()}'. Names of public instance properties must " + $"be unique (case insensitive). Two or more property names were equivalent to '{duplicate.Name}'." ); } #endregion } void ValidateEntityDeclarations(List <Type> regularResources) { foreach (var type in regularResources) { ValidateCommon(type); } } void ValidateWrapperDeclaration(List <Type> wrappers) { if (wrappers.Select(type => (type, wrapped: type.GetWrappedType())).ContainsDuplicates(pair => pair.wrapped, out var dupe)) { throw new InvalidResourceWrapperException(dupe, "must wrap unique types. Found multiple wrapper declarations for " + $"wrapped type '{dupe.wrapped.GetRESTableTypeName()}'."); } foreach (var wrapper in wrappers) { var wrapped = wrapper.GetWrappedType(); var _types = (wrapper, wrapped); var members = wrapper.GetMembers(BindingFlags.Public | BindingFlags.Instance); if (members.OfType <PropertyInfo>().Any() || members.OfType <FieldInfo>().Any()) { throw new InvalidResourceWrapperException(_types, "cannot contain public instance properties or fields"); } ValidateCommon(wrapper); if (wrapper.GetInterfaces() .Where(i => typeof(IOperationsInterface).IsAssignableFrom(i)) .Any(i => i.IsGenericType && i.GenericTypeArguments[0] != wrapped)) { throw new InvalidResourceWrapperException(_types, "cannot implement operations interfaces for types other than " + $"'{wrapped.GetRESTableTypeName()}'."); } if (wrapped.FullName?.Contains("+") == true) { throw new InvalidResourceWrapperException(_types, "cannot wrap types that are declared within the scope of some other class."); } if (wrapped.HasAttribute <RESTableAttribute>()) { throw new InvalidResourceWrapperException(_types, "cannot wrap types already decorated with the 'RESTableAttribute' attribute"); } if (wrapper.Assembly == typeof(RESTableConfigurator).Assembly) { throw new InvalidResourceWrapperException(_types, "cannot wrap RESTable types"); } } } void ValidateTerminalDeclarations(List <Type> terminals) { foreach (var terminal in terminals) { ValidateCommon(terminal); var constructors = terminal.GetConstructors(); if (constructors.Length != 1) { throw new InvalidTerminalDeclarationException(terminal, "must have exactly one public constructor. Found " + constructors.Length); } var constructorParameterNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var properties = TypeCache.GetDeclaredProperties(terminal); foreach (var parameter in constructors[0].GetParameters()) { if (!constructorParameterNames.Add(parameter.Name)) { throw new InvalidTerminalDeclarationException(terminal, "must not define multiple constructor parameters with the same case " + $"insensitive parameter name. Found duplicate of '{parameter.Name.ToLowerInvariant()}'"); } if (!properties.ContainsKey(parameter.Name)) { throw new InvalidTerminalDeclarationException(terminal, "must not define a constructor parameter with a name that does not equal the name of a " + "public instance property on the same type (case insensitive). Found parameter " + $"'{parameter.Name.ToLowerInvariant()}' with no matching public instance property."); } } if (terminal.ImplementsGenericInterface(typeof(IEnumerable <>))) { throw new InvalidTerminalDeclarationException(terminal, "must not be collections"); } if (terminal.HasResourceProviderAttribute()) { throw new InvalidTerminalDeclarationException(terminal, "must not be decorated with a resource provider attribute"); } if (typeof(IOperationsInterface).IsAssignableFrom(terminal)) { throw new InvalidTerminalDeclarationException(terminal, "must not implement any other RESTable operations interfaces"); } } } void ValidateBinaryDeclarations(List <Type> binaries) { foreach (var binary in binaries) { ValidateCommon(binary); if (binary.ImplementsGenericInterface(typeof(IEnumerable <>))) { throw new InvalidBinaryDeclarationException(binary, "must not be collections"); } if (binary.HasResourceProviderAttribute()) { throw new InvalidBinaryDeclarationException(binary, "must not be decorated with a resource provider attribute"); } if (typeof(IOperationsInterface).IsAssignableFrom(binary)) { throw new InvalidBinaryDeclarationException(binary, "must not implement any other RESTable operations interfaces"); } } } void ValidateEventDeclarations(List <Type> events) { foreach (var @event in events) { ValidateCommon(@event); if (!typeof(IEvent).IsAssignableFrom(@event)) { throw new InvalidEventDeclarationException(@event, "must inherit from 'RESTable.Resources.Event<T>'"); } if (@event.ImplementsGenericInterface(typeof(IEnumerable <>))) { throw new InvalidEventDeclarationException(@event, "must not be collections"); } if (@event.HasResourceProviderAttribute()) { throw new InvalidEventDeclarationException(@event, "must not be decorated with a resource provider attribute"); } if (typeof(IOperationsInterface).IsAssignableFrom(@event)) { throw new InvalidEventDeclarationException(@event, "must not implement any RESTable operations interfaces"); } } } ValidateEntityDeclarations(entityTypes); ValidateWrapperDeclaration(wrapperTypes); ValidateTerminalDeclarations(terminalTypes); ValidateBinaryDeclarations(binaryTypes); ValidateEventDeclarations(eventTypes); return(regularTypes, wrapperTypes, terminalTypes, binaryTypes, eventTypes); }
public static Metadata GetMetadata(MetadataLevel level, AccessRights rights, RootAccess rootAccess, ResourceCollection resourceCollection, TypeCache typeCache) { var domain = rights?.Keys ?? resourceCollection.AllResources; var entityResources = domain .OfType <IEntityResource>() .Where(r => r.IsGlobal) .OrderBy(r => r.Name) .ToList(); var terminalResources = domain .OfType <ITerminalResource>() .ToList(); if (level == MetadataLevel.OnlyResources) { return new Metadata { CurrentAccessScope = new Dictionary <IResource, Method[]>(rights ?? rootAccess), EntityResources = entityResources.ToArray(), TerminalResources = terminalResources.ToArray() } } ; var checkedTypes = new HashSet <Type>(); void parseType(Type type) { switch (type) { case var _ when type.IsEnum: checkedTypes.Add(type); break; case var _ when type.IsNullable(out var baseType): parseType(baseType); break; case var _ when type.ImplementsGenericInterface(typeof(IEnumerable <>), out var param) && param.Any(): if (param[0].ImplementsGenericInterface(typeof(IEnumerable <>))) { break; } parseType(param[0]); break; case var _ when type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair <,>): case var _ when IsPrimitive(type): case var _ when type == typeof(object): break; case var _ when checkedTypes.Add(type): { var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.RESTableIgnored()) .Select(p => p.FieldType); foreach (var field in fields) { parseType(field); } var properties = typeCache.GetDeclaredProperties(type).Values .Where(p => !p.Hidden) .Select(p => p.Type); foreach (var property in properties) { parseType(property); } break; } } } var entityTypes = entityResources.Select(r => r.Type).ToHashSet(); var terminalTypes = terminalResources.Select(r => r.Type).ToHashSet(); foreach (var type in entityTypes) { parseType(type); } checkedTypes.ExceptWith(entityTypes); foreach (var type in terminalTypes) { parseType(type); } checkedTypes.ExceptWith(terminalTypes); return(new Metadata { CurrentAccessScope = new Dictionary <IResource, Method[]>(rights ?? rootAccess), EntityResources = entityResources.ToArray(), TerminalResources = terminalResources.ToArray(), EntityResourceTypes = new ReadOnlyDictionary <Type, Member[]>(entityTypes.ToDictionary(t => t, type => typeCache.GetDeclaredProperties(type).Values.Cast <Member>().ToArray())), PeripheralTypes = new ReadOnlyDictionary <Type, Member[]>(checkedTypes.ToDictionary(t => t, type => { var props = typeCache.GetDeclaredProperties(type).Values; var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.RESTableIgnored()) .Select(f => new Field(f)); return props.Union <Member>(fields).ToArray(); })) }); }