示例#1
0
        /// <summary>
        /// Initialize this class via reflection
        /// </summary>
        private void InitializeViaReflection(ServiceEndpointOptions service)
        {
            // Is there an options() method that can be called?
            List <KeyValuePair <String, Type> > resourceTypes = null;
            var            optionsMethod  = service.Behavior.Type.GetRuntimeMethod("Options", Type.EmptyTypes);
            ServiceOptions serviceOptions = null;

            if (optionsMethod != null)
            {
                var behaviorInstance = Activator.CreateInstance(service.Behavior.Type);
                serviceOptions = optionsMethod.Invoke(behaviorInstance, null) as ServiceOptions;
                if (serviceOptions != null) // Remove unused resources
                {
                    resourceTypes = serviceOptions.Resources.Select(o => new KeyValuePair <string, Type>(o.ResourceName, o.ResourceType)).ToList();
                }
            }
            else
            {
                // Resource types
                resourceTypes = service.Contracts.SelectMany(c => c.Type.GetCustomAttributes <ServiceKnownResourceAttribute>().Select(o => new KeyValuePair <String, Type>(o.Type.GetCustomAttribute <XmlRootAttribute>()?.ElementName, o.Type)).Where(o => !String.IsNullOrEmpty(o.Key))).ToList();
            }

            // Construct the paths
            var operations = service.Contracts.SelectMany(c => c.Type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                                                          .Select(o => new { Rest = o.GetCustomAttribute <RestInvokeAttribute>(), ContractMethod = o, BehaviorMethod = service.Behavior.Type.GetMethod(o.Name, o.GetParameters().Select(p => p.ParameterType).ToArray()) }))
                             .Where(o => o.Rest != null);

            // Create tags
            if (operations.Any(o => o.Rest.UriTemplate.Contains("{resourceType}")))
            {
                this.Tags = resourceTypes.Select(o => new SwaggerTag(o.Key, MetadataComposerUtil.GetElementDocumentation(o.Value, MetaDataElementType.Summary))).ToList();
            }

            // Process operations
            foreach (var operation in operations.GroupBy(o => o.Rest.UriTemplate.StartsWith("/") ? o.Rest.UriTemplate : "/" + o.Rest.UriTemplate))
            {
                // If the path does not contain {resourceType} and there are no ServiceKnownTypes then proceed
                var path = new SwaggerPath();
                foreach (var val in operation)
                {
                    // Process operations
                    var pathDefinition = new SwaggerPathDefinition(val.BehaviorMethod, val.ContractMethod);
                    path.Add(val.Rest.Method.ToLower(), pathDefinition);
                    if (pathDefinition.Consumes.Count == 0)
                    {
                        pathDefinition.Consumes.AddRange(this.Consumes);
                    }
                    if (pathDefinition.Produces.Count == 0)
                    {
                        pathDefinition.Produces.AddRange(this.Produces);
                    }

                    // Any faults?
                    foreach (var flt in service.Contracts.SelectMany(t => t.Type.GetCustomAttributes <ServiceFaultAttribute>()))
                    {
                        pathDefinition.Responses.Add(flt.StatusCode, new SwaggerSchemaElement()
                        {
                            Description = flt.Condition,
                            Schema      = new SwaggerSchemaDefinition()
                            {
                                Reference = $"#/definitions/{MetadataComposerUtil.CreateSchemaReference(flt.FaultType)}",
                                NetType   = flt.FaultType
                            }
                        });
                    }
                }

                if (operation.Key.Contains("{resourceType}"))
                {
                    foreach (var resource in resourceTypes)
                    {
                        var resourcePath = operation.Key.Replace("{resourceType}", resource.Key);
                        if (this.Paths.ContainsKey(resourcePath) ||
                            resourcePath.Contains("history") && !typeof(IVersionedEntity).IsAssignableFrom(resource.Value))
                        {
                            continue;
                        }

                        // Create a copy of the path and customize it to the resource
                        List <String> unsupportedVerbs = new List <string>();

                        // Get the resource options for this resource
                        ServiceResourceOptions resourceOptions = serviceOptions?.Resources.FirstOrDefault(o => o.ResourceName == resource.Key);

                        var subPath = new SwaggerPath(path);
                        foreach (var v in subPath)
                        {
                            // Check that this resource is supported
                            var resourceCaps = resourceOptions?.Capabilities.FirstOrDefault(c => c.Capability == MetadataComposerUtil.VerbToCapability(v.Key, v.Value.Parameters.Count));
                            if (resourceOptions != null && resourceCaps == null &&
                                v.Key != "head" && v.Key != "options" && v.Key != "search")
                            {
                                unsupportedVerbs.Add(v.Key);
                                continue;
                            }

                            // Add tags for resource
                            v.Value.Tags.Add(resource.Key);
                            v.Value.Parameters.RemoveAll(o => o.Name == "resourceType");

                            // Security?
                            if (this.SecurityDefinitions.Count > 0 && resourceCaps?.Demand.Length > 0)
                            {
                                v.Value.Security = new List <SwaggerPathSecurity>()
                                {
                                    new SwaggerPathSecurity()
                                    {
                                        { "oauth_user", resourceCaps.Demand.Distinct().ToList() }
                                    }
                                };
                            }

                            // Query parameters?
                            if ((v.Key == "get" || v.Key == "head"))
                            {
                                // Build query parameters
                                v.Value.Parameters.AddRange(resource.Value.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                                            .Where(o => o.GetCustomAttributes <XmlElementAttribute>().Any() || o.GetCustomAttribute <QueryParameterAttribute>() != null)
                                                            .Select(o => new SwaggerParameter(o))
                                                            );

                                //v.Value.Parameters.AddRange(new SwaggerParameter[]
                                //{
                                //    new SwaggerParameter()
                                //    {
                                //        Name = "_offset",
                                //        Type = SwaggerSchemaElementType.number,
                                //        Description = "Offset of query results",
                                //        Location = SwaggerParameterLocation.query
                                //    },
                                //    new SwaggerParameter()
                                //    {
                                //        Name = "_count",
                                //        Type = SwaggerSchemaElementType.number,
                                //        Description = "Count of query results to include in result set",
                                //        Location = SwaggerParameterLocation.query
                                //    },
                                //    new SwaggerParameter()
                                //    {
                                //        Name = "_lean",
                                //        Type = SwaggerSchemaElementType.boolean,
                                //        Description = "When true, the server will only return minimal data by removing duplicates from the bundle's resource[] property. NOTE: This means that the .count parameter may not match the number of items in resource[], however you should continue to use the .count parameter to increase your offset",
                                //        Location = SwaggerParameterLocation.query
                                //    },
                                //    new SwaggerParameter()
                                //    {
                                //        Name = "_orderBy",
                                //        Type = SwaggerSchemaElementType.@string,
                                //        Description = "Indicates a series of parameters to order the result set by",
                                //        Location = SwaggerParameterLocation.query
                                //    },
                                //    new SwaggerParameter()
                                //    {
                                //        Name = "_queryId",
                                //        Type = SwaggerSchemaElementType.@string,
                                //        Description = "The unique identifier for the query (for continuation)",
                                //        Location = SwaggerParameterLocation.query
                                //    },
                                //    new SwaggerParameter()
                                //    {
                                //        Name = "_viewModel",
                                //        Type = SwaggerSchemaElementType.@string,
                                //        Description = "When using the view-model content-type, the view model to use",
                                //        Location = SwaggerParameterLocation.query,
                                //        Enum = new List<string>()
                                //        {
                                //            "min",
                                //            "max"
                                //        }
                                //    }
                                //});
                            }

                            // Replace the response if necessary
                            var resourceSchemaRef = new SwaggerSchemaDefinition()
                            {
                                NetType   = resource.Value,
                                Reference = $"#/definitions/{MetadataComposerUtil.CreateSchemaReference(resource.Value)}"
                            };
                            SwaggerSchemaElement schema = null;
                            if (v.Value.Responses.TryGetValue(200, out schema))
                            {
                                schema.Schema = resourceSchemaRef;
                            }
                            if (v.Value.Responses.TryGetValue(201, out schema))
                            {
                                schema.Schema = resourceSchemaRef;
                            }

                            // Replace the body if necessary
                            var bodyParm = v.Value.Parameters.FirstOrDefault(o => o.Location == SwaggerParameterLocation.body && o.Schema?.NetType?.IsAssignableFrom(resource.Value) == true);
                            if (bodyParm != null)
                            {
                                bodyParm.Schema = resourceSchemaRef;
                            }
                        } // foreach subpath

                        // Add the resource path?
                        foreach (var nv in unsupportedVerbs)
                        {
                            subPath.Remove(nv);
                        }

                        // Child resources? we want to multiply them if so
                        if ((resourcePath.Contains("{childResourceType}") || resourcePath.Contains("{operationName}")) && resourceOptions.ChildResources.Count > 0)
                        {
                            foreach (var sp in subPath)
                            {
                                sp.Value.Parameters.RemoveAll(o => o.Name == "childResourceType" || o.Name == "operationName");
                            }

                            // Correct for child resources - rewriting the schema and opts
                            foreach (var cp in resourceOptions.ChildResources)
                            {
                                // Bound to
                                if (resourcePath.Contains("{id}") && !cp.Scope.HasFlag(ChildObjectScopeBinding.Instance) ||
                                    !resourcePath.Contains("{id}") && !cp.Scope.HasFlag(ChildObjectScopeBinding.Class) ||
                                    resourcePath.Contains("$") && !cp.Classification.HasFlag(ChildObjectClassification.RpcOperation) ||
                                    !resourcePath.Contains("$") && !cp.Classification.HasFlag(ChildObjectClassification.Resource))
                                {
                                    continue;
                                }

                                var newPath = new SwaggerPath();
                                foreach (var v in subPath)
                                {
                                    var childCaps = cp.Capabilities.FirstOrDefault(c => c.Capability == MetadataComposerUtil.VerbToCapability(v.Key, v.Value.Parameters.Count));
                                    if (childCaps != null)
                                    {
                                        newPath.Add(v.Key, v.Value);
                                    }
                                }

                                this.Paths.Add(resourcePath.Replace("{childResourceType}", cp.ResourceName).Replace("{operationName}", cp.ResourceName), newPath);
                                foreach (var sp in newPath)
                                {
                                    // Replace the response if necessary
                                    var resourceSchemaRef = new SwaggerSchemaDefinition()
                                    {
                                        NetType   = cp.ResourceType,
                                        Reference = $"#/definitions/{MetadataComposerUtil.CreateSchemaReference(cp.ResourceType)}"
                                    };
                                    SwaggerSchemaElement schema = null;
                                    if (sp.Value.Responses.TryGetValue(200, out schema))
                                    {
                                        schema.Schema = resourceSchemaRef;
                                    }
                                    if (sp.Value.Responses.TryGetValue(201, out schema))
                                    {
                                        schema.Schema = resourceSchemaRef;
                                    }

                                    // Replace the body if necessary
                                    var bodyParm = sp.Value.Parameters.FirstOrDefault(o => o.Location == SwaggerParameterLocation.body && o.Schema?.NetType?.IsAssignableFrom(resource.Value) == true);
                                    if (bodyParm != null)
                                    {
                                        bodyParm.Schema = resourceSchemaRef;
                                    }
                                }
                            }
                        }
                        else if (!resourcePath.Contains("{childResourceType}"))
                        {
                            this.Paths.Add(resourcePath, subPath);
                        }
                    }
                }
                else
                {
                    this.Paths.Add(operation.Key, path);
                }
            }
        }
示例#2
0
        /// <summary>
        /// Create new swagger document
        /// </summary>
        public SwaggerDocument(ServiceEndpointOptions service) : this()
        {
            var listen = new Uri(service.BaseUrl.First());

            this.BasePath = listen.AbsolutePath;
            listen        = new Uri(ApplicationServiceContext.Current.GetService <IConfigurationManager>().GetSection <MetadataConfigurationSection>().ApiHost ?? listen.ToString());
            this.Info     = new SwaggerServiceInfo()
            {
                Title       = MetadataComposerUtil.GetElementDocumentation(service.Behavior.Type, MetaDataElementType.Summary) ?? service.Behavior.Type.GetCustomAttribute <ServiceBehaviorAttribute>()?.Name,
                Description = MetadataComposerUtil.GetElementDocumentation(service.Behavior.Type, MetaDataElementType.Remarks),
                Version     = $"{service.Behavior.Type.Assembly.GetName().Version.ToString()} ({service.Behavior.Type.Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion})"
            };
            this.Host = $"{listen.Host}:{listen.Port}".Replace("0.0.0.0", RestOperationContext.Current.IncomingRequest.Url.Host);

            // Get the schemes
            this.Schemes = new List <string>()
            {
                listen.Scheme
            };

            // Construct the paths
            var operations = service.Contracts.SelectMany(c => c.Type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                                                          .Select(o => new { Rest = o.GetCustomAttribute <RestInvokeAttribute>(), ContractMethod = o, BehaviorMethod = service.Behavior.Type.GetMethod(o.Name, o.GetParameters().Select(p => p.ParameterType).ToArray()) }))
                             .Where(o => o.Rest != null);

            // What this option produces
            this.Produces.AddRange(service.Contracts.SelectMany(c => c.Type.GetCustomAttributes <ServiceProducesAttribute>().Select(o => o.MimeType)));
            this.Consumes.AddRange(service.Contracts.SelectMany(c => c.Type.GetCustomAttributes <ServiceConsumesAttribute>().Select(o => o.MimeType)));

            // Security requires us to peek into the runtime environment ...
            var serviceCaps = MetadataComposerUtil.GetServiceCapabilities(service.ServiceType);

            if (serviceCaps.HasFlag(ServiceEndpointCapabilities.BearerAuth))
            {
                var bauth    = AuthenticationContext.Current;
                var tokenUrl = new Uri(MetadataComposerUtil.ResolveService("acs").BaseUrl.FirstOrDefault());
                if (tokenUrl.Host == "0.0.0.0") // Host is vanialla
                {
                    tokenUrl = new Uri($"{listen.Scheme}://{listen.Host}:{listen.Port}{tokenUrl.AbsolutePath}");
                }

                AuthenticationContext.Current = new AuthenticationContext(AuthenticationContext.SystemPrincipal);
                this.SecurityDefinitions      = new Dictionary <string, SwaggerSecurityDefinition>()
                {
                    { "oauth_user", new SwaggerSecurityDefinition()
                      {
                          Flow     = SwaggerSecurityFlow.password,
                          Scopes   = ApplicationServiceContext.Current.GetService <IRepositoryService <SecurityPolicy> >()?.Find(o => o.ObsoletionTime == null).ToDictionary(o => o.Oid, o => o.Name),
                          TokenUrl = $"{tokenUrl.ToString().Replace("0.0.0.0", RestOperationContext.Current.IncomingRequest.Url.Host)}/oauth2_token",
                          Type     = SwaggerSecurityType.oauth2
                      } }
                };
                AuthenticationContext.Current = bauth;
            }

            // Is there an options() method that can be called?
            List <KeyValuePair <String, Type> > resourceTypes = null;

            var            optionsMethod  = service.Behavior.Type.GetRuntimeMethod("Options", Type.EmptyTypes);
            ServiceOptions serviceOptions = null;

            if (optionsMethod != null)
            {
                var behaviorInstance = Activator.CreateInstance(service.Behavior.Type);
                serviceOptions = optionsMethod.Invoke(behaviorInstance, null) as ServiceOptions;
                if (serviceOptions != null) // Remove unused resources
                {
                    resourceTypes = serviceOptions.Resources.Select(o => new KeyValuePair <string, Type>(o.ResourceName, o.ResourceType)).ToList();
                }
            }
            else
            {
                // Resource types
                resourceTypes = service.Contracts.SelectMany(c => c.Type.GetCustomAttributes <ServiceKnownResourceAttribute>().Select(o => new KeyValuePair <String, Type>(o.Type.GetCustomAttribute <XmlRootAttribute>()?.ElementName, o.Type)).Where(o => !String.IsNullOrEmpty(o.Key))).ToList();
            }

            // Create tags
            if (operations.Any(o => o.Rest.UriTemplate.Contains("{resourceType}")))
            {
                this.Tags = resourceTypes.Select(o => new SwaggerTag(o.Key, MetadataComposerUtil.GetElementDocumentation(o.Value, MetaDataElementType.Summary))).ToList();
            }

            // Process operations
            foreach (var operation in operations.GroupBy(o => o.Rest.UriTemplate.StartsWith("/") ? o.Rest.UriTemplate : "/" + o.Rest.UriTemplate))
            {
                // If the path does not contain {resourceType} and there are no ServiceKnownTypes then proceed
                var path = new SwaggerPath();
                foreach (var val in operation)
                {
                    // Process operations
                    var pathDefinition = new SwaggerPathDefinition(val.BehaviorMethod, val.ContractMethod);
                    path.Add(val.Rest.Method.ToLower(), pathDefinition);
                    if (pathDefinition.Consumes.Count == 0)
                    {
                        pathDefinition.Consumes.AddRange(this.Consumes);
                    }
                    if (pathDefinition.Produces.Count == 0)
                    {
                        pathDefinition.Produces.AddRange(this.Produces);
                    }



                    // Any faults?
                    foreach (var flt in service.Contracts.SelectMany(t => t.Type.GetCustomAttributes <ServiceFaultAttribute>()))
                    {
                        pathDefinition.Responses.Add(flt.StatusCode, new SwaggerSchemaElement()
                        {
                            Description = flt.Condition,
                            Schema      = new SwaggerSchemaDefinition()
                            {
                                Reference = $"#/definitions/{MetadataComposerUtil.CreateSchemaReference(flt.FaultType)}",
                                NetType   = flt.FaultType
                            }
                        });
                    }
                }

                if (operation.Key.Contains("{resourceType}"))
                {
                    foreach (var resource in resourceTypes)
                    {
                        var resourcePath = operation.Key.Replace("{resourceType}", resource.Key);
                        if (this.Paths.ContainsKey(resourcePath) ||
                            resourcePath.Contains("history") && !typeof(IVersionedEntity).IsAssignableFrom(resource.Value))
                        {
                            continue;
                        }

                        // Create a copy of the path and customize it to the resource
                        List <String> unsupportedVerbs = new List <string>();

                        // Get the resource options for this resource
                        ServiceResourceOptions resourceOptions = serviceOptions?.Resources.FirstOrDefault(o => o.ResourceName == resource.Key);

                        var subPath = new SwaggerPath(path);
                        foreach (var v in subPath)
                        {
                            // Check that this resource is supported
                            var resourceCaps = resourceOptions?.Capabilities.FirstOrDefault(c => c.Capability == MetadataComposerUtil.VerbToCapability(v.Key, v.Value.Parameters.Count));
                            if (resourceOptions != null && resourceCaps == null)
                            {
                                unsupportedVerbs.Add(v.Key);
                                continue;
                            }

                            // Add tags for resource
                            v.Value.Tags.Add(resource.Key);
                            v.Value.Parameters.RemoveAll(o => o.Name == "resourceType");

                            // Security?
                            if (this.SecurityDefinitions.Count > 0 && resourceCaps?.Demand.Length > 0)
                            {
                                v.Value.Security = new List <SwaggerPathSecurity>()
                                {
                                    new SwaggerPathSecurity()
                                    {
                                        { "oauth_user", resourceCaps.Demand.Distinct().ToList() }
                                    }
                                };
                            }

                            // Query parameters?
                            if ((v.Key == "get" || v.Key == "head") && v.Value.Parameters.Count == 0)
                            {
                                // Build query parameters
                                v.Value.Parameters = resource.Value.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                                     .Where(o => o.GetCustomAttributes <XmlElementAttribute>().Any() || o.GetCustomAttribute <QueryParameterAttribute>() != null)
                                                     .Select(o => new SwaggerParameter(o))
                                                     .ToList();
                                v.Value.Parameters.AddRange(new SwaggerParameter[]
                                {
                                    new SwaggerParameter()
                                    {
                                        Name        = "_offset",
                                        Type        = SwaggerSchemaElementType.number,
                                        Description = "Offset of query results",
                                        Location    = SwaggerParameterLocation.query
                                    },
                                    new SwaggerParameter()
                                    {
                                        Name        = "_count",
                                        Type        = SwaggerSchemaElementType.number,
                                        Description = "Count of query results to include in result set",
                                        Location    = SwaggerParameterLocation.query
                                    },
                                    new SwaggerParameter()
                                    {
                                        Name        = "_lean",
                                        Type        = SwaggerSchemaElementType.boolean,
                                        Description = "When true, the server will only return minimal data by removing duplicates from the bundle's resource[] property. NOTE: This means that the .count parameter may not match the number of items in resource[], however you should continue to use the .count parameter to increase your offset",
                                        Location    = SwaggerParameterLocation.query
                                    },
                                    new SwaggerParameter()
                                    {
                                        Name        = "_orderBy",
                                        Type        = SwaggerSchemaElementType.@string,
                                        Description = "Indicates a series of parameters to order the result set by",
                                        Location    = SwaggerParameterLocation.query
                                    },
                                    new SwaggerParameter()
                                    {
                                        Name        = "_queryId",
                                        Type        = SwaggerSchemaElementType.@string,
                                        Description = "The unique identifier for the query (for continuation)",
                                        Location    = SwaggerParameterLocation.query
                                    },
                                    new SwaggerParameter()
                                    {
                                        Name        = "_viewModel",
                                        Type        = SwaggerSchemaElementType.@string,
                                        Description = "When using the view-model content-type, the view model to use",
                                        Location    = SwaggerParameterLocation.query,
                                        Enum        = new List <string>()
                                        {
                                            "min",
                                            "max"
                                        }
                                    }
                                });
                            }

                            // Replace the response if necessary
                            var resourceSchemaRef = new SwaggerSchemaDefinition()
                            {
                                NetType   = resource.Value,
                                Reference = $"#/definitions/{MetadataComposerUtil.CreateSchemaReference(resource.Value)}"
                            };
                            SwaggerSchemaElement schema = null;
                            if (v.Value.Responses.TryGetValue(200, out schema))
                            {
                                schema.Schema = resourceSchemaRef;
                            }
                            if (v.Value.Responses.TryGetValue(201, out schema))
                            {
                                schema.Schema = resourceSchemaRef;
                            }

                            // Replace the body if necessary
                            var bodyParm = v.Value.Parameters.FirstOrDefault(o => o.Location == SwaggerParameterLocation.body && o.Schema?.NetType?.IsAssignableFrom(resource.Value) == true);
                            if (bodyParm != null)
                            {
                                bodyParm.Schema = resourceSchemaRef;
                            }
                        } // foreach subpath

                        // Add the resource path?
                        foreach (var nv in unsupportedVerbs)
                        {
                            subPath.Remove(nv);
                        }
                        this.Paths.Add(resourcePath, subPath);
                    }
                }
                else
                {
                    this.Paths.Add(operation.Key, path);
                }
            }

            // Now we want to add a definition for all references
            // This LINQ expression allows for scanning of any properties where there is currently no definition for a particular type
            var missingDefns = this.Paths.AsParallel().SelectMany(
                o => o.Value.SelectMany(p => p.Value?.Parameters.Select(r => r.Schema))
                .Union(o.Value.SelectMany(p => p.Value?.Responses?.Values?.Select(r => r.Schema))))
                               .Union(this.Definitions.AsParallel().Where(o => o.Value.Properties != null).SelectMany(o => o.Value.Properties.Select(p => p.Value.Items ?? p.Value.Schema)))
                               .Union(this.Definitions.AsParallel().Where(o => o.Value.AllOf != null).SelectMany(o => o.Value.AllOf))
                               .Select(s => s?.NetType)
                               .Where(o => o != null && !this.Definitions.ContainsKey(MetadataComposerUtil.CreateSchemaReference(o)))
                               .Distinct();

            while (missingDefns.Count() > 0)
            {
                foreach (var def in missingDefns.AsParallel().ToList())
                {
                    var name = MetadataComposerUtil.CreateSchemaReference(def);
                    if (!this.Definitions.ContainsKey(name))
                    {
                        this.Definitions.Add(name, new SwaggerSchemaDefinition(def));
                    }
                }
            }

            // Create the definitions
        }