private async Task <IEnumerable <Dependency> > ReadDependenciesAsync() { Dependency[] dependencies; if (!DependenciesExists) { return(Enumerable.Empty <Dependency>()); } using (var reader = new StreamReader(Filename)) { var dependenciesJson = await reader.ReadToEndAsync().ConfigureAwait(false); dependencies = JsonConvert.DeserializeObject <Dependency[]>(dependenciesJson); reader.Close(); } foreach (var dependency in dependencies) { var dependencySegments = dependency.Resource.Split('/'); var resource = dependencySegments[2]; dependency.Namespace = dependencySegments[1].ToLower(); dependency.Resource = CompositeTermInflector.MakeSingular(resource).ToLower(); } return(dependencies.OrderBy(o => o.Order).GroupBy(x => x.Resource) .Select(GetFirstDependencySetPriorityToDependencyWithEdFiNamespace).AsEnumerable()); }
protected override void Arrange() { _swaggerDocumentContext = new SwaggerDocumentContext( ResourceModelProvider.GetResourceModel()) { ProfileContext = new SwaggerProfileContext { ProfileName = "Test-ParentNonAbstractBaseClass-ExcludeOnly" }, RenderType = RenderType.GeneralizedExtensions }; string profileDefinition = @"<Profile name='Test-ParentNonAbstractBaseClass-ExcludeOnly'> <Resource name='StudentSpecialEducationProgramAssociation'> <ReadContentType memberSelection='ExcludeOnly'> <Property name='SpecialEducationHoursPerWeek'/> </ReadContentType> <WriteContentType memberSelection='IncludeAll'/> </Resource> <Resource name='StudentProgramAssociation'> <ReadContentType memberSelection='IncludeOnly'/> </Resource> </Profile>"; var profileResourceModel = new ProfileResourceModel( ResourceModelProvider.GetResourceModel(), XElement.Parse(profileDefinition)); var readableResourceModel = profileResourceModel.ResourceByName.Values.Where(r => r.Readable != null); var writableResourceModel = profileResourceModel.ResourceByName.Values.Where(r => r.Writable != null); _swaggerResources = readableResourceModel .Select( r => new SwaggerResource(r.Readable) { Name = $"{CompositeTermInflector.MakeSingular(r.Readable.Name)}_{ContentTypeUsage.Readable}" .ToCamelCase(), Readable = true, IsProfileResource = true }) .Concat( writableResourceModel.Select( r => new SwaggerResource(r.Writable) { Name = $"{CompositeTermInflector.MakeSingular(r.Writable.Name)}_{ContentTypeUsage.Writable}" .ToCamelCase(), Writable = true, IsProfileResource = true } )) .ToList(); }
/// <summary> /// Applies the composite resource's root resource to the build result using the supplied builder context. /// </summary> /// <param name="processorContext"></param> /// <param name="builderContext">The builder context.</param> public void ApplyRootResource(CompositeDefinitionProcessorContext processorContext, CompositeResourceModelBuilderContext builderContext) { string compositeName = processorContext.CurrentElement.Ancestors("Composite") .First() .AttributeValue("name"); string resourceName = CompositeTermInflector.MakeSingular(compositeName); builderContext.RootResource = new Resource(resourceName); //resource.Entity.Name); builderContext.CurrentResource = builderContext.RootResource; }
public void Should_include_queries_for_parent_entities_before_their_children_so_that_NHibernate_does_not_perform_lazy_loading() { var domainModel = _domainModelProvider.GetDomainModel(); var queryIndexByChildEntityName = _actualHqlQueries // Skip the aggregate root query .Skip(1) // Use Regex to match the child entity collection names from the HQL .Select( (hql, i) => new { Index = i, Matches = Regex.Matches( hql, "left join fetch [a-z]{1,2}\\.(?<EntityCollection>\\w+(?<!ReferenceData)) ") }) // Extract the child entity name from the HQL .Select( x => new { Index = x.Index, EntityName = CompositeTermInflector.MakeSingular( x.Matches[x.Matches.Count - 1] .Groups["EntityCollection"] .Value), } ) // Create a map of query indices by child entity name .ToDictionary(x => x.EntityName, x => x.Index); var aggregateFullName = new FullName(EdFiConventions.PhysicalSchemaName, "StudentEducationOrganizationAssociation"); Aggregate aggregate = domainModel.AggregateByName[aggregateFullName]; var aggregateRoot = aggregate.AggregateRoot; var aggregateChildEntities = aggregate.Members.Except(new[] { aggregateRoot }); // Assign the aggregate root to the known index (asserted by previous test) queryIndexByChildEntityName[aggregateRoot.Name] = 0; // Ensure that all child entity queries appear AFTER their parent's query in the batch // This prevents N+1 behavior where NHibernate performs lazy loading on child records Assert.That( aggregateChildEntities .Where(childEntity => queryIndexByChildEntityName[childEntity.Name] < queryIndexByChildEntityName[childEntity.Parent.Name]) .Select(childEntity => childEntity.FullName), Is.Empty, "Queries for some child entities in the aggregate appeared before their parents in the batch. This will cause NHibernate to perform lazy loading (i.e. the ORM \"n+1\" issue), breaking the optimized query batch behavior."); }
public static string CreateContentType( string resourceCollectionName, string profileName, ContentTypeUsage usage) { // ReSharper disable once UseStringInterpolation return(string.Format( "application/vnd.ed-fi{0}.{1}.{2}.{3}+json", string.Empty, CompositeTermInflector.MakeSingular(resourceCollectionName).ToLower(), profileName.ToLower(), usage.ToString().ToLower())); }
public static bool CompareTypeNames(string resource, string key, string separator, IEnumerable <string> schemaNames = null) { var resourceName = CompositeTermInflector.MakeSingular(resource.Split('/').Last()); if (schemaNames != null && schemaNames.Any()) { return(schemaNames.Any( schemaName => key.Equals( schemaName.Trim() + separator + resourceName, StringComparison.InvariantCultureIgnoreCase))); } return(key.Equals(resourceName, StringComparison.InvariantCultureIgnoreCase)); }
public string GetResourceName(Resource resource, IOpenApiMetadataResourceContext resourceContext) { var name = resourceContext.ContextualResource == null ? string.Format( @"{0}_{1}_{2}", resource.SchemaProperCaseName, CompositeTermInflector.MakeSingular(resource.Name), resourceContext.OperationNamingContext) .ToCamelCase() : string.Format( @"{0}_{1}_{2}_{3}", resource.SchemaProperCaseName, CompositeTermInflector.MakeSingular(resource.Name), resourceContext.ContextualResource.Name, resourceContext.OperationNamingContext); return(OpenApiMetadataDocumentHelper.CamelCaseSegments(name)); }
public string GetResourceName(OpenApiMetadataResource openApiMetadataResource, ContentTypeUsage contentTypeUsage) { var schemaPrefix = openApiMetadataResource.Resource.Entity.DomainModel.SchemaNameMapProvider .GetSchemaMapByPhysicalName(openApiMetadataResource.Resource.Entity.Schema) .ProperCaseName; var name = openApiMetadataResource.ContextualResource == null ? string.Format( "{0}_{1}_{2}", schemaPrefix, CompositeTermInflector.MakeSingular(openApiMetadataResource.Resource.Name), contentTypeUsage) : string.Format( "{0}_{1}_{2}_{3}", schemaPrefix, CompositeTermInflector.MakeSingular(openApiMetadataResource.Resource.Name), openApiMetadataResource.ContextualResource.Name, contentTypeUsage); return(OpenApiMetadataDocumentHelper.CamelCaseSegments(name)); }
protected override void Arrange() { _openApiMetadataDocumentContext = new OpenApiMetadataDocumentContext(ResourceModelProvider.GetResourceModel()) { ProfileContext = new OpenApiMetadataProfileContext() }; string profileDefinition = @"<Profile name='Test-ParentNonAbstractBaseClass-ExcludeOnly'> <Resource name='StudentSpecialEducationProgramAssociation'> <ReadContentType memberSelection='ExcludeOnly'> <Property name='SpecialEducationHoursPerWeek'/> </ReadContentType> <WriteContentType memberSelection='IncludeAll'/> </Resource> <Resource name='Staff'> <ReadContentType memberSelection='IncludeOnly'> <Extension name='GrandBend' memberSelection='IncludeOnly' logicalSchema='GrandBend'> <Property name='Tenured'/> </Extension> </ReadContentType> <WriteContentType memberSelection='IncludeAll' /> </Resource> <Resource name='StudentProgramAssociation'> <ReadContentType memberSelection='IncludeOnly'/> </Resource> </Profile>"; var profileResourceModel = new ProfileResourceModel( ResourceModelProvider.GetResourceModel(), XElement.Parse(profileDefinition)); var readableResourceModel = profileResourceModel.ResourceByName.Values.Where(r => r.Readable != null); var writableResourceModel = profileResourceModel.ResourceByName.Values.Where(r => r.Writable != null); var edFiSchemaPrefix = "edFi"; _openApiMetadataResources = readableResourceModel .Select( r => new OpenApiMetadataResource(r.Readable) { Name = $"{edFiSchemaPrefix}_{CompositeTermInflector.MakeSingular(r.Readable.Name)}_{ContentTypeUsage.Readable}" .ToCamelCase(), Readable = true, IsProfileResource = true }).Concat( writableResourceModel.Select( r => new OpenApiMetadataResource(r.Writable) { Name = $"{edFiSchemaPrefix}_{CompositeTermInflector.MakeSingular(r.Writable.Name)}_{ContentTypeUsage.Writable}" .ToCamelCase(), Writable = true, IsProfileResource = true })).ToList(); }
private string GetBaseResourceName(Resource baseResource, Resource resource, ContentTypeUsage contentTypeUsage) => $"{baseResource.Name}_{CompositeTermInflector.MakeSingular(resource.Name)}_{contentTypeUsage}".ToCamelCase();
public Task <HttpResponseMessage> ExecuteAuthorizationFilterAsync( HttpActionContext actionContext, CancellationToken cancellationToken, Func <Task <HttpResponseMessage> > continuation) { // check if the http method sent needs to be checked by this filter if (!(actionContext.Request.Method == HttpMethod.Put || actionContext.Request.Method == HttpMethod.Post || actionContext.Request.Method == HttpMethod.Get)) { return(continuation()); } // Try to get the current API key context var assignedProfiles = new List <string>(); var apiKeyContext = _apiKeyContextProvider.GetApiKeyContext(); // check that the ApiKeyContext is available so that we can get the assigned profiles if (apiKeyContext != null && apiKeyContext != ApiKeyContext.Empty) { assignedProfiles = _apiKeyContextProvider.GetApiKeyContext() .Profiles.ToList(); } // declare profile content type variable because it can be retrieved from the // accept header or the content-type header string profileContentType = null; ProfileContentTypeDetails profileContentTypeDetails = null; // get singluar spelling of the controller name for comparison to the resource in the profiles string resourceCollectionName = actionContext.ControllerContext.ControllerDescriptor.ControllerName.TrimSuffix("Controller"); string resourceItemName = CompositeTermInflector.MakeSingular(resourceCollectionName); // try to get the Profile Content type and parse if successful if (TryGetProfileContentType(actionContext, out profileContentType)) { profileContentTypeDetails = profileContentType.GetContentTypeDetails(); } // If the caller has not specified a profile specific content type and there are no assigned // profiles then allow the request to be processed (there is nothing left to check) if (string.IsNullOrEmpty(profileContentType) && !assignedProfiles.Any()) { return(continuation()); } // If the caller has not specified a profile specific content type but the targeted // resource is covered by an assigned profile then we must refuse the request if (string.IsNullOrEmpty(profileContentType) && IsResourceCoveredByAssignedProfile(assignedProfiles, resourceItemName)) { if (actionContext.Request.Method == HttpMethod.Get) { return (Task.FromResult( ForbiddenHttpResponseMessage( actionContext, string.Format( "One of the following profile-specific content types is required when requesting this resource: '{0}'.", string.Join( "', '", assignedProfiles.Select( p => ProfilesContentTypeHelper.CreateContentType( resourceCollectionName, p, ContentTypeUsage.Readable))))))); } // PUT or POST return (Task.FromResult( ForbiddenHttpResponseMessage( actionContext, string.Format( "Based on the assigned profiles, one of the following profile-specific content types is required when updating this resource: '{0}'.", string.Join( "', '", assignedProfiles.Select( p => ProfilesContentTypeHelper.CreateContentType(resourceCollectionName, p, ContentTypeUsage.Writable))))))); } // if there is no profile specific content at this point there are no more checks to make so proceed with request processing if (string.IsNullOrEmpty(profileContentType)) { return(continuation()); } // If the caller is "opting in" to a profile, and the targeted resource exists in the specified profile, proceed with request processing if (!assignedProfiles.Any() && IsTheResourceInTheProfile(resourceItemName, profileContentTypeDetails.Profile)) { return(continuation()); } // If the caller is not assigned any profiles that cover the targeted resource, then proceed with request processing. if (!AnyAssignedProfilesCoverTheResource(assignedProfiles, resourceItemName)) { return(continuation()); } // Check if the resource is covered under an assigned profile that is not the profile content type sent if (!IsResourceCoveredByAnotherAssignedProfile(assignedProfiles, resourceItemName, profileContentTypeDetails.Profile)) { // create the response based on the method if (actionContext.Request.Method == HttpMethod.Get) { return(Task.FromResult( ForbiddenHttpResponseMessage( actionContext, string.Format( "One of the following profile-specific content types is required when requesting this resource: '{0}'.", string.Join( "', '", GetApplicableContentTypes( assignedProfiles, resourceCollectionName, resourceItemName, actionContext.Request.Method)))))); } if (actionContext.Request.Method == HttpMethod.Put || actionContext.Request.Method == HttpMethod.Post) { return(Task.FromResult( ForbiddenHttpResponseMessage( actionContext, string.Format( "Based on the assigned profiles, one of the following profile-specific content types is required when updating this resource: '{0}'.", string.Join( "', '", GetApplicableContentTypes( assignedProfiles, resourceCollectionName, resourceItemName, actionContext.Request.Method)))))); } } return(continuation()); }
public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { if (_duplicates.Any()) { throw new HttpResponseException( request.CreateErrorResponse( HttpStatusCode.InternalServerError, $@"Duplicate controllers have been detected. Due to ambiguity, no requests will be served until this is resolved. The following controllers have been detected as duplicates: {string.Join(", ", _duplicates)}")); } IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } string controllerName = GetRouteVariable <string>(routeData, ControllerKey); // schema section of url ie /data/v3/{schema}/{resource} ex: /data/v3/ed-fi/schools string resourceSchema = GetRouteVariable <string>(routeData, ResourceOwnerKey); if (ShouldUseDefaultSelectController(controllerName, resourceSchema)) { // If there's no controller or schema name, defer to the base class for direct route handling // Also if the controller is a composite controller we defer to base class. return(base.SelectController(request)); } string resourceSchemaNamespace = string.Empty; if (controllerName.EqualsIgnoreCase("deletes")) { resourceSchemaNamespace = "EdFi.Ods.ChangeQueries.Controllers"; } else { try { string properCaseName = _schemaNameMapProvider .GetSchemaMapByUriSegment(resourceSchema) .ProperCaseName; // for now the check is including the ignore case due to the way the schema name map provider is implemented // this should be address on the changes in phase 4 where the map provider is using dictionaries. resourceSchemaNamespace = EdFiConventions.BuildNamespace( Namespaces.Api.Controllers, properCaseName, controllerName, !EdFiConventions.ProperCaseName.EqualsIgnoreCase(properCaseName)); } // Quietly ignore any failure to find the schema name, allowing NotFound response logic below to run catch (KeyNotFoundException) {} } // Verify org section matches a known resource owner derived from any assembly implementing ISchemaNameMap if (string.IsNullOrEmpty(resourceSchemaNamespace)) { throw new HttpResponseException( request.CreateErrorResponse( HttpStatusCode.NotFound, "Resource schema value provided in uri does not match any known values.")); } ProfileContentTypeDetails profileContentTypeDetails = null; // http method type determines where the profile specific content type will be sent. if (request.Method == HttpMethod.Get) { profileContentTypeDetails = (from a in request.Headers.Accept where a.MediaType.StartsWith("application/vnd.ed-fi.") let details = a.MediaType.GetContentTypeDetails() select details) .SingleOrDefault(); } else if (request.Method == HttpMethod.Put || request.Method == HttpMethod.Post) { if (request.Content != null) { var contentType = request.Content.Headers.ContentType; // check that there was a content type on the request if (contentType != null) { // check if the content type is a profile specific content type if (contentType.MediaType.StartsWith("application/vnd.ed-fi.")) { // parse the profile specific content type string into the object. profileContentTypeDetails = contentType.MediaType.GetContentTypeDetails(); // Was a profile-specific content type for the controller/resource found and was it parseable? // if it was not parsable but started with "application/vnd.ed-fi." then throw an error if (profileContentTypeDetails == null) { throw new HttpResponseException( BadRequestHttpResponseMessage( request, "Content type not valid on this resource.")); } } } } } HttpControllerDescriptor controllerDescriptor; string key; bool profileControllerNotFound = false; // Was a profile-specific content type for the controller/resource found? if (profileContentTypeDetails != null) { // Ensure that the content type resource matchs requested resource if (!profileContentTypeDetails.Resource .EqualsIgnoreCase(CompositeTermInflector.MakeSingular(controllerName))) { throw new HttpResponseException( BadRequestHttpResponseMessage( request, "The resource is not accessible through the profile specified by the content type.")); } // Ensure that if the method is get that the profile specific content type sent readable if (request.Method == HttpMethod.Get && profileContentTypeDetails.Usage != ContentTypeUsage.Readable) { throw new HttpResponseException( BadRequestHttpResponseMessage( request, "The resource is not accessible through the profile specified by the content type.")); } // Ensure that if the http method is PUT or POST that the profile specific type sent was writable if ((request.Method == HttpMethod.Put || request.Method == HttpMethod.Post) && profileContentTypeDetails.Usage != ContentTypeUsage.Writable) { throw new HttpResponseException( BadRequestHttpResponseMessage( request, "The resource is not accessible through the profile specified by the content type.")); } // Use the profile name as the namespace for controller matching string profileName = profileContentTypeDetails.Profile.Replace('-', '_'); // + "_" + profileContentTypeDetails.Usage; // Find a matching controller. // ex : EdFi.Ods.Api.Services.Controllers.AcademicHonorCategoryTypes.Academic_Week_Readable_Excludes_Optional_References.AcademicHonorCategoryTypes key = string.Format( CultureInfo.InvariantCulture, "{0}.{1}.{2}", resourceSchemaNamespace, profileName, controllerName); if (_controllers.Value.TryGetValue(key, out controllerDescriptor)) { return(controllerDescriptor); } profileControllerNotFound = true; } // Find a matching controller. // ex : EdFi.Ods.Api.Services.Controllers.AcademicHonorCategoryTypes.AcademicHonorCategoryTypes key = string.Format( CultureInfo.InvariantCulture, "{0}.{1}", resourceSchemaNamespace, controllerName); if (_controllers.Value.TryGetValue(key, out controllerDescriptor)) { // If there is a controller, just not with the content type specified, it's a bad request if (profileControllerNotFound) { // If the profile does not exist in this installation of the api throw an error dependent on the http method if (!ProfileExists(profileContentTypeDetails.Profile)) { if (request.Method == HttpMethod.Get) { throw new HttpResponseException(NotAcceptableHttpResponseMessage(request)); } if (request.Method == HttpMethod.Put || request.Method == HttpMethod.Post) { throw new HttpResponseException(UnsupportedMediaTypeHttpResponseMessage(request)); } } // The profile exists but the resource doesnt exist under it throw new HttpResponseException( BadRequestHttpResponseMessage( request, string.Format( "The '{0}' resource is not accessible through the '{1}' profile specified by the content type.", CompositeTermInflector.MakeSingular(controllerName), profileContentTypeDetails.Profile))); } return(controllerDescriptor); } throw new HttpResponseException(HttpStatusCode.NotFound); }
public virtual IActionResult Get() { if (!_isEnabled) { return(NotFound()); } object json = null; RESTError restError = null; try { var routeDataValues = Request.RouteValues; string organizationCode = (string)routeDataValues["organizationCode"]; string compositeCategory = (string)routeDataValues["compositeCategory"]; string compositeCollectionName = (string)routeDataValues["compositeName"]; string compositeResourceName = CompositeTermInflector.MakeSingular(compositeCollectionName); // Try to find the composite definition that matches the incoming URIs composite category/name if (!_compositeMetadataProvider.TryGetCompositeDefinition( organizationCode, compositeCategory, compositeResourceName, out XElement compositeDefinition)) { return(NotFound()); } // Prepare query string parameters var rawQueryStringParameters = ParseQuery(Request.QueryString.ToString()); var queryStringParameters = rawQueryStringParameters.Keys.ToDictionary <string, string, object>( // Replace underscores with periods for appropriate processing kvp => kvp.Replace('_', '.'), kvp => rawQueryStringParameters[kvp], StringComparer.InvariantCultureIgnoreCase); //respond quickly to DOS style requests (should we catch these earlier? e.g. attribute filter?) if (queryStringParameters.TryGetValue("limit", out object limitAsObject)) { if (int.TryParse(limitAsObject.ToString(), out int limit) && (limit <= 0 || limit > 100)) { return(BadRequest(ErrorTranslator.GetErrorMessage("Limit must be omitted or set to a value between 1 and 100."))); } } // Process specification for route and query string parameters var specificationParameters = GetCompositeSpecificationParameters(); // Ensure all matched route key values were used by the current composite var suppliedSpecificationParameters = routeDataValues .Where(kvp => !_standardApiRouteKeys.Contains(kvp.Key)) .ToList(); var unusedSpecificationParameters = suppliedSpecificationParameters .Where(kvp => !specificationParameters.ContainsKey(kvp.Key)) .ToList(); if (unusedSpecificationParameters.Any()) { return(NotFound()); } AddInherentSupportForIdParameter(); json = _compositeResourceResponseProvider.Get( compositeDefinition, specificationParameters, queryStringParameters, GetNullValueHandling()); void AddInherentSupportForIdParameter() { if (!Request.RouteValues.TryGetValue("id", out object idAsObject)) { return; } if (!Guid.TryParse(idAsObject.ToString(), out Guid id)) { throw new BadRequestException("The supplied resource identifier is invalid."); } specificationParameters.Add( "Id", new CompositeSpecificationParameter { FilterPath = "Id", Value = id }); } IDictionary <string, CompositeSpecificationParameter> GetCompositeSpecificationParameters() { var specificationElt = compositeDefinition.Element("Specification"); if (specificationElt == null) { return(new Dictionary <string, CompositeSpecificationParameter>()); } var specificationFilterByName = specificationElt .Elements("Parameter") .ToDictionary( p => p.AttributeValue("name"), p => new { FilterPath = p.AttributeValue("filterPath"), Queryable = p.AttributeValue("queryable") == "true" }, StringComparer.InvariantCultureIgnoreCase); // Identify relevant route values var matchingRouteValues = routeDataValues .Where(x => specificationFilterByName.ContainsKey(x.Key)); // Copy route values that match the specification to the parameter dictionary var parameters = matchingRouteValues.ToDictionary( kvp => kvp.Key, kvp => new CompositeSpecificationParameter { FilterPath = specificationFilterByName[kvp.Key] .FilterPath, Value = kvp.Value }); // Identify relevant query string values var matchingQueryStringParameters = queryStringParameters // Skip query string parameter matching if the key was already matched by the route .Where(x => !parameters.ContainsKey(x.Key)) .Where( x => specificationFilterByName.ContainsKey(x.Key) && specificationFilterByName[x.Key] .Queryable) .ToList(); // Copy route values that match the specification to the parameter dictionary foreach (var kvp in matchingQueryStringParameters) { // Guids aren't "coerced" by SqlParameter correctly object value = Guid.TryParse(kvp.Value as string, out Guid guidValue) ? guidValue : kvp.Value; parameters.Add( kvp.Key, new CompositeSpecificationParameter { FilterPath = specificationFilterByName[kvp.Key] .FilterPath, Value = value }); // Remove the processed Specification-based query string parameter queryStringParameters.Remove(kvp.Key); } return(parameters); } } catch (Exception ex) { _logger.Error(ex); restError = _restErrorProvider.GetRestErrorFromException(ex); } if (restError != null) { return(string.IsNullOrWhiteSpace(restError.Message) ? (IActionResult)StatusCode(restError.Code) : StatusCode(restError.Code, restError.Message)); } return(Ok(json)); NullValueHandling GetNullValueHandling() { // The custom 'IncludeNulls' header is supported for testing purposes. if (Request.Headers.TryGetValue("IncludeNulls", out StringValues headerValues) && headerValues.Contains("true")) { return(NullValueHandling.Include); } return(NullValueHandling.Ignore); } }
public virtual IHttpActionResult Get() { string json = null; RESTError restError = null; try { var routeDataValues = ActionContext.RequestContext.RouteData.Values; string organizationCode = (string)routeDataValues["organizationCode"]; string compositeCategory = (string)routeDataValues["compositeCategory"]; string compositeCollectionName = (string)routeDataValues["compositeName"]; string compositeResourceName = CompositeTermInflector.MakeSingular(compositeCollectionName); // Try to find the composite definition that matches the incoming URIs composite category/name if (!_compositeMetadataProvider.TryGetCompositeDefinition( organizationCode, compositeCategory, compositeResourceName, out XElement compositeDefinition)) { return(NotFound()); } // Prepare query string parameters var rawQueryStringParameters = ActionContext.Request.RequestUri.ParseQueryString(); var queryStringParameters = rawQueryStringParameters.AllKeys.ToDictionary <string, string, object>( // Replace underscores with periods for appropriate processing kvp => kvp.Replace('_', '.'), kvp => rawQueryStringParameters[kvp], StringComparer.InvariantCultureIgnoreCase); var defaultPageSizeLimit = _defaultPageSizeLimitProvider.GetDefaultPageSizeLimit(); //respond quickly to DOS style requests (should we catch these earlier? e.g. attribute filter?) if (queryStringParameters.TryGetValue("limit", out object limitAsObject)) { if (int.TryParse(limitAsObject.ToString(), out int limit) && (limit <= 0 || limit > defaultPageSizeLimit)) { return(BadRequest($"Limit must be omitted or set to a value between 1 and max value defined in configuration file (defaultPageSizeLimit).")); } } // Process specification for route and query string parameters var specificationParameters = GetCompositeSpecificationParameters( compositeDefinition, routeDataValues, queryStringParameters); // Ensure all matched route key values were used by the current composite var suppliedSpecificationParameters = routeDataValues .Where(kvp => !StandardApiRouteKeys.Contains(kvp.Key)); var unusedSpecificationParameters = suppliedSpecificationParameters .Where(kvp => !specificationParameters.ContainsKey(kvp.Key)); if (unusedSpecificationParameters.Any()) { return(new StatusCodeResult(HttpStatusCode.NotFound, this)); } AddInherentSupportForIdParameter(specificationParameters); json = _compositeResourceResponseProvider.GetJson( compositeDefinition, specificationParameters, queryStringParameters, GetNullValueHandling()); } catch (Exception ex) { _logger.Error(ex); restError = _restErrorProvider.GetRestErrorFromException(ex); } if (restError != null) { return(string.IsNullOrWhiteSpace(restError.Message) ? new StatusCodeResult((HttpStatusCode)restError.Code, this) : new StatusCodeResult((HttpStatusCode)restError.Code, this).WithError(restError.Message)); } return(new ResponseMessageResult( new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(json, Encoding.UTF8, "application/json") })); }