/// <summary> /// Verifies the extension rule. /// </summary> /// <param name="context">The Interop service context</param> /// <param name="info">out parameter to return violation information when rule does not pass</param> /// <returns>true if rule passes; false otherwise</returns> public override bool?Verify(ServiceContext context, out ExtensionRuleViolationInfo info) { if (context == null) { throw new ArgumentNullException("context"); } bool?passed = null; ExtensionRuleResultDetail detail = new ExtensionRuleResultDetail(this.Name); var restrictions = new Dictionary <string, Tuple <List <NormalProperty>, List <NavigProperty> > >(); if (!AnnotationsHelper.GetExpandRestrictions(context.MetadataDocument, context.VocCapabilities, ref restrictions)) { detail.ErrorMessage = "Cannot find any appropriate entity-sets which supports $expand system query options."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } if (!AnnotationsHelper.IsSuitableNavigationProperty(NavigationRoughType.CollectionValued, ref restrictions)) { detail.ErrorMessage = "Cannot find any collection-valued navigation properties in any entity-sets which supports $expand system query options."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } string entitySet = string.Empty; string navigProp = string.Empty; foreach (var r in restrictions) { if (string.IsNullOrEmpty(r.Key) || null == r.Value.Item1 || !r.Value.Item1.Any() || null == r.Value.Item2 || !r.Value.Item2.Any()) { continue; } entitySet = r.Key; foreach (var np in r.Value.Item2) { navigProp = np.NavigationPropertyName; string nEntityTypeShortName = np.NavigationPropertyType.RemoveCollectionFlag().GetLastSegment(); var rest = AnnotationsHelper.GetCountRestrictions(context.MetadataDocument, context.VocCapabilities); if (string.IsNullOrEmpty(rest.Item1) || null == rest.Item2 || !rest.Item2.Any() || null == rest.Item3 || !rest.Item3.Any()) { continue; } break; } break; } if (string.IsNullOrEmpty(entitySet)) { detail.ErrorMessage = "Cannot find an appropriate entity-set which supports $expand system query option."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } if (string.IsNullOrEmpty(navigProp)) { detail.ErrorMessage = "Cannot get expanded entities because cannot get collection type of navigation property from metadata"; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } string url = string.Format("{0}/{1}?$expand={2}($count=true)", context.ServiceBaseUri.OriginalString.TrimEnd('/'), entitySet, navigProp); Uri uri = new Uri(url); Response resp = WebHelper.Get(uri, Constants.V4AcceptHeaderJsonFullMetadata, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); detail = new ExtensionRuleResultDetail(this.Name, uri.AbsoluteUri, "GET", StringHelper.MergeHeaders(Constants.AcceptHeaderJson, context.RequestHeaders), resp); detail.URI = url; detail.ResponsePayload = resp.ResponsePayload; detail.ResponseHeaders = resp.ResponseHeaders; detail.HTTPMethod = "GET"; detail.ResponseStatusCode = resp.StatusCode.ToString(); if (resp != null && resp.StatusCode == HttpStatusCode.OK) { JObject feed; resp.ResponsePayload.TryToJObject(out feed); if (null == feed) { detail.ErrorMessage = "Cannot find any entity-sets in the service.\r\n"; info = new ExtensionRuleViolationInfo(uri, resp.ResponsePayload, detail); return(passed); } JObject entry = null; var entities = JsonParserHelper.GetEntries(feed); foreach (var e in entities) { if (null != e[navigProp] && JTokenType.Array == e[navigProp].Type) { entry = e as JObject; break; } } if (null == entry) { detail.ErrorMessage = "Cannot find an appropriate entity which contains at least one collection-valued navigation property.\r\n"; info = new ExtensionRuleViolationInfo(uri, resp.ResponsePayload, detail); return(passed); } if (null != entry && JTokenType.Object == entry.Type) { JArray jArr = entry[navigProp] as JArray; int actualAmount = 0; JsonParserHelper.GetEntitiesNumFromCollectionValuedNavigProp(entry[Constants.V4OdataId].ToString(), entry, navigProp, context.RequestHeaders, ref actualAmount); if (null != entry[navigProp + Constants.V4OdataCount] && actualAmount == (int)entry[navigProp + Constants.V4OdataCount]) { passed = true; } else { passed = false; detail.ErrorMessage = "The service does not execute an accurate result on the system query option '$count' for expanded properties."; } } } else { passed = false; detail.ErrorMessage = "The service does not support the system query option '$count' for expanded properties."; } info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); }
/// <summary> /// Verifies the extension rule. /// </summary> /// <param name="context">The Interop service context</param> /// <param name="info">out parameter to return violation information when rule does not pass</param> /// <returns>true if rule passes; false otherwise</returns> public override bool?Verify(ServiceContext context, out ExtensionRuleViolationInfo info) { if (context == null) { throw new ArgumentNullException("context"); } bool?passed = null; List <ExtensionRuleResultDetail> details = new List <ExtensionRuleResultDetail>(); ExtensionRuleResultDetail detail1 = new ExtensionRuleResultDetail(this.Name); ExtensionRuleResultDetail detail2 = new ExtensionRuleResultDetail(this.Name); var restrictions = new Dictionary <string, Tuple <List <NormalProperty>, List <NavigProperty> > >(); if (!AnnotationsHelper.GetExpandRestrictions(context.MetadataDocument, context.VocCapabilities, ref restrictions)) { detail1.ErrorMessage = "Cannot find any appropriate entity-sets which supports $expand system query options."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } if (!AnnotationsHelper.IsSuitableNavigationProperty(NavigationRoughType.CollectionValued, ref restrictions)) { detail1.ErrorMessage = "Cannot find any collection-valued navigation properties in any entity-sets which supports $expand system query options."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } string entitySet = string.Empty; string navigPropName = string.Empty; #region Verify the system query $top. foreach (var r in restrictions) { if (string.IsNullOrEmpty(r.Key) || null == r.Value.Item1 || !r.Value.Item1.Any() || null == r.Value.Item2 || !r.Value.Item2.Any()) { continue; } foreach (var np in r.Value.Item2) { string nEntityTypeShortName = np.NavigationPropertyType.RemoveCollectionFlag().GetLastSegment(); string nEntitySetName = nEntityTypeShortName.MapEntityTypeShortNameToEntitySetName(); if (false == nEntitySetName.IsEntitySetSupportTopQuery(context.MetadataDocument, new List <string>() { context.VocCapabilities })) { continue; } navigPropName = np.NavigationPropertyName; break; } entitySet = r.Key; break; } if (string.IsNullOrEmpty(entitySet)) { detail1.ErrorMessage = "Cannot find an appropriate entity-set which supports $expand system query option."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } if (string.IsNullOrEmpty(navigPropName)) { detail1.ErrorMessage = "Cannot find any collection-valued navigation properties which support system query options $top."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } bool? isTopQueryValidation = null; string url = string.Format("{0}/{1}?$expand={2}($top=1)", context.ServiceBaseUri, entitySet, navigPropName); var response = WebHelper.Get(new Uri(url), Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); detail1 = new ExtensionRuleResultDetail(this.Name, url, "GET", StringHelper.MergeHeaders(Constants.AcceptHeaderJson, context.RequestHeaders), response); if (response.StatusCode == HttpStatusCode.OK) { JObject feed; response.ResponsePayload.TryToJObject(out feed); if (feed != null && JTokenType.Object == feed.Type) { var entities = JsonParserHelper.GetEntries(feed); foreach (var entity in entities) { if (entity[navigPropName] != null) { if (JTokenType.Array == entity[navigPropName].Type && ((JArray)entity[navigPropName]).Count <= 1) { isTopQueryValidation = true; } else { passed = false; detail1.ErrorMessage = "The service does not execute an accurate result on the system query option '$top' for expanded properties."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } } } } } else { passed = false; detail1.ErrorMessage = "The service does not support the system query option '$top' for expanded properties."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } #endregion #region Verify the system query $skip. foreach (var r in restrictions) { if (string.IsNullOrEmpty(r.Key) || null == r.Value.Item1 || !r.Value.Item1.Any() || null == r.Value.Item2 || !r.Value.Item2.Any()) { continue; } foreach (var np in r.Value.Item2) { string nEntityTypeShortName = np.NavigationPropertyType.RemoveCollectionFlag().GetLastSegment(); string nEntitySetName = nEntityTypeShortName.MapEntityTypeShortNameToEntitySetName(); if (false == nEntitySetName.IsEntitySetSupportSkipQuery(context.MetadataDocument, new List <string>() { context.VocCapabilities })) { continue; } navigPropName = np.NavigationPropertyName; break; } entitySet = r.Key; break; } if (string.IsNullOrEmpty(entitySet)) { detail2.ErrorMessage = "Cannot find an appropriate entity-set which supports $expand system query option."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } if (string.IsNullOrEmpty(navigPropName)) { detail2.ErrorMessage = "Cannot find any collection-valued navigation properties which support system query options $skip."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } bool?isSkipQueryValidation = null; url = string.Format("{0}/{1}?$expand={2}($skip=1)", context.ServiceBaseUri, entitySet, navigPropName); response = WebHelper.Get(new Uri(url), Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); detail2 = new ExtensionRuleResultDetail(this.Name, url, "GET", StringHelper.MergeHeaders(Constants.AcceptHeaderJson, context.RequestHeaders), response); if (response.StatusCode == HttpStatusCode.OK) { JObject feed; response.ResponsePayload.TryToJObject(out feed); if (feed != null && JTokenType.Object == feed.Type) { var entities = JsonParserHelper.GetEntries(feed); foreach (var entity in entities) { if (entity[navigPropName] != null) { int actualAmount = 0; JsonParserHelper.GetEntitiesNumFromCollectionValuedNavigProp(context.DestinationBasePath, (JObject)entity, navigPropName, context.RequestHeaders, ref actualAmount); if (JTokenType.Array == entity[navigPropName].Type && ((JArray)entity[navigPropName]).Count <= actualAmount)// TODO: This should be justified by total count { isSkipQueryValidation = true; } else { isSkipQueryValidation = false; detail2.ErrorMessage = "The service does not execute an accurate result on the system query option '$skip' for expanded properties."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(isSkipQueryValidation); } } } } } else { passed = false; detail2.ErrorMessage = "The service does not support the system query option '$skip' for expanded properties."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail1); return(passed); } #endregion if (isTopQueryValidation == true && isSkipQueryValidation == true) { passed = true; } details.Add(detail1); details.Add(detail2); info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, details); return(passed); }