/// <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 = AnnotationsHelper.GetExpandRestrictions(context.MetadataDocument, context.VocCapabilities); if (string.IsNullOrEmpty(restrictions.Item1) || null == restrictions.Item3 || !restrictions.Item3.Any()) { detail.ErrorMessage = "Cannot find any appropriate entity-sets which supports $expand system query options."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } // Set a expected level number as 2, and store it in the parameter expectedLevel. int expectedLevels = 2; string entitySetName = restrictions.Item1; string entityTypeShortName = entitySetName.MapEntitySetNameToEntityTypeShortName(); string[] navigPropNames = MetadataHelper.GetNavigPropNamesRecurseByLevels(entityTypeShortName, context.MetadataDocument, expectedLevels); string url = string.Format("{0}/{1}?$expand=*($levels={2})", context.ServiceBaseUri.OriginalString.TrimEnd('/'), entitySetName, expectedLevels); var resp = WebHelper.Get(new Uri(url), Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); detail = new ExtensionRuleResultDetail(this.Name, url, "GET", StringHelper.MergeHeaders(Constants.AcceptHeaderJson, context.RequestHeaders), resp); if (resp != null && resp.StatusCode == HttpStatusCode.OK) { JObject feed; resp.ResponsePayload.TryToJObject(out feed); var entities = JsonParserHelper.GetEntries(feed); if (null == entities || !entities.Any()) { detail.ErrorMessage = string.Format("Cannot find any entities from the entity-set '{0}'", entitySetName); info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } bool isFirstLevelEmpty = false; int levelsCounter = 0; JObject entry = null; foreach (var en in entities) { levelsCounter = 0; entry = en as JObject; isFirstLevelEmpty = false; for (int i = 0; i < expectedLevels; i++) { if (entry != null && JTokenType.Object == entry.Type) { var navigPropVal = entry[navigPropNames[i]]; if (navigPropVal != null) { levelsCounter++; entry = navigPropVal.Type == JTokenType.Array ? navigPropVal.First as JObject : navigPropVal as JObject; if (entry == null) { isFirstLevelEmpty = true; break; } } } } if (expectedLevels == levelsCounter) { passed = true; } else if (!(levelsCounter < expectedLevels && isFirstLevelEmpty)) // If not no data { passed = false; detail.ErrorMessage = "The service does not execute an accurate result on the system query option '$levels' for expanded properties."; break; } } } else { passed = false; detail.ErrorMessage = "The service does not support the system query option '$levels' 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; ExtensionRuleResultDetail detail = new ExtensionRuleResultDetail(this.Name); var expandRestrictions = AnnotationsHelper.GetExpandRestrictions(context.MetadataDocument, context.VocCapabilities); if (string.IsNullOrEmpty(expandRestrictions.Item1) || null == expandRestrictions.Item3 || !expandRestrictions.Item3.Any()) { detail.ErrorMessage = "Cannot find an appropriate entity-set which supports $expand system query options."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } string entitySet = expandRestrictions.Item1; string navigProp = expandRestrictions.Item3.First().NavigationPropertyName; string url = string.Format("{0}/{1}", context.ServiceBaseUri, entitySet); var resp = WebHelper.Get(new Uri(url), Constants.V4AcceptHeaderJsonFullMetadata, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); if (null == resp || HttpStatusCode.OK != resp.StatusCode) { detail.ErrorMessage = JsonParserHelper.GetErrorMessage(resp.ResponsePayload); info = new ExtensionRuleViolationInfo(new Uri(url), resp.ResponsePayload, detail); return(passed); } JObject feed; resp.ResponsePayload.TryToJObject(out feed); var entities = JsonParserHelper.GetEntries(feed); if (null == entities || 0 == entities.Count) { detail.ErrorMessage = string.Format("The entity-set {0} has no entity.", entitySet); info = new ExtensionRuleViolationInfo(new Uri(url), resp.ResponsePayload, detail); return(passed); } var entity = entities.First; string entityURL = null != entity[Constants.V4OdataId] ? entity[Constants.V4OdataId].ToString() : string.Empty; if (string.IsNullOrEmpty(entityURL)) { detail.ErrorMessage = "Cannot find the annotation @odata.id in the current entity."; info = new ExtensionRuleViolationInfo(new Uri(url), resp.ResponsePayload, detail); return(passed); } url = string.Format("{0}?$expand={1}/$ref", entityURL, navigProp); resp = WebHelper.Get(new Uri(url), Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); detail = new ExtensionRuleResultDetail(this.Name, url, "GET", StringHelper.MergeHeaders(Constants.AcceptHeaderJson, context.RequestHeaders), resp); if (resp.StatusCode == HttpStatusCode.OK) { JObject entry; resp.ResponsePayload.TryToJObject(out entry); if (entry != null && JTokenType.Object == entry.Type) { entity = entry[navigProp].First; url = entity[Constants.V4OdataId].ToString(); resp = WebHelper.Get(new Uri(url), Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); if (resp.StatusCode == HttpStatusCode.OK) { passed = true; } else { passed = false; detail.ErrorMessage = "The service does not execute an accurate result, because the value of the annotation '@odata.id' is a bad link."; } } } else { passed = false; detail.ErrorMessage = "The service does not support the '$ref' segment 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; 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 <string> vocDocs = new List <string>() { context.VocCapabilities, context.VocCore, context.VocMeasures }; List <string> expectedTypes = new List <string>() { "Edm.String", "Edm.Int32", "Edm.Int16", "Edm.Single", "Edm.Double", "Edm.Boolean", "Edm.DateTimeOffset", "Edm.Guid" }; List <ExtensionRuleResultDetail> details = new List <ExtensionRuleResultDetail>(); 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 navigPropName = string.Empty; string primitivePropertyName = string.Empty; string primitivePropertyType = 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; } foreach (var np in r.Value.Item2) { string nEntityTypeShortName = np.NavigationPropertyType.RemoveCollectionFlag().GetLastSegment(); string nEntitySetName = nEntityTypeShortName.MapEntityTypeShortNameToEntitySetName(); var funcs = new List <Func <string, string, string, List <NormalProperty>, List <NavigProperty>, bool> >() { AnnotationsHelper.GetFilterRestrictions }; var rest = nEntitySetName.GetRestrictions(context.MetadataDocument, context.VocCapabilities, funcs, expectedTypes); if (string.IsNullOrEmpty(rest.Item1) || null == rest.Item2 || !rest.Item2.Any() || null == rest.Item3 || !rest.Item3.Any()) { continue; } navigPropName = np.NavigationPropertyName; primitivePropertyName = rest.Item2.First().PropertyName; primitivePropertyType = rest.Item2.First().PropertyType; break; } entitySet = r.Key; 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(navigPropName)) { 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); } if (string.IsNullOrEmpty(primitivePropertyName) || string.IsNullOrEmpty(primitivePropertyType)) { detail.ErrorMessage = "Cannot get an appropriate primitive property from navigation properties."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } string struri = string.Format("{0}/{1}?$expand={2}", context.ServiceBaseUri.OriginalString.TrimEnd('/'), entitySet, navigPropName); Uri uri = new Uri(struri); var response = WebHelper.Get(uri, Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); if (HttpStatusCode.OK != response.StatusCode) { passed = false; detail.ErrorMessage = JsonParserHelper.GetErrorMessage(response.ResponsePayload); info = new ExtensionRuleViolationInfo(uri, response.ResponsePayload, detail); return(passed); } JObject feed; response.ResponsePayload.TryToJObject(out feed); if (feed != null && JTokenType.Object == feed.Type) { var entities = JsonParserHelper.GetEntries(feed); var navigProp = entities[0][navigPropName] as JArray; if (navigProp != null && navigProp.Count != 0) { string propVal = navigProp[0][primitivePropertyName].ToString(); string compareVal = propVal; if (primitivePropertyType.Equals("Edm.String")) { compareVal = "'" + propVal + "'"; } string url = string.Format("{0}/{1}?$expand={2}($filter={3} eq {4})", context.ServiceBaseUri.OriginalString.TrimEnd('/'), entitySet, navigPropName, primitivePropertyName, compareVal); var resp = WebHelper.Get(new Uri(url), Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); detail = new ExtensionRuleResultDetail(this.Name, url, "GET", StringHelper.MergeHeaders(Constants.AcceptHeaderJson, context.RequestHeaders), resp); if (resp.StatusCode == HttpStatusCode.OK) { JObject jObj; resp.ResponsePayload.TryToJObject(out jObj); if (jObj != null && JTokenType.Object == jObj.Type) { var entries = JsonParserHelper.GetEntries(jObj).ToList(); foreach (var entry in entries) { if (entry[navigPropName] != null && ((JArray)entry[navigPropName]).Count == 0) { continue; } else if (entry[navigPropName] != null && ((JArray)entry[navigPropName]).Count > 0) { var temp = entry[navigPropName].ToList() .FindAll(en => propVal == en[primitivePropertyName].ToString()) .Select(en => en); if (entry[navigPropName].ToList().Count == temp.Count()) { passed = true; } else { passed = false; detail.ErrorMessage = string.Format("The service does not execute an accurate result on system query option '$filter' (Actual Value: {0}, Expected Value: {1}).", entry[navigPropName].ToList().Count, temp.Count()); break; } } } } } else { passed = false; detail.ErrorMessage = string.Format("The service does not support system query option '$filter' on expanded entities."); } } } 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; 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 navigPropName = string.Empty; string primitivePropertyName = 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; } foreach (var np in r.Value.Item2) { string nEntityType = np.NavigationPropertyType.RemoveCollectionFlag().GetLastSegment(); var funcs = new List <Func <string, string, string, List <NormalProperty>, List <NavigProperty>, bool> >() { AnnotationsHelper.GetFilterRestrictions }; var expectedTypes = new List <string>() { "Edm.String" }; var props = MetadataHelper.GetNormalProperties(context.MetadataDocument, nEntityType); if (null == props || !props.Any()) { continue; } var targetProps = props.Where(p => expectedTypes.Contains(p.PropertyType)).Select(p => p); if (!targetProps.Any()) { continue; } navigPropName = np.NavigationPropertyName; primitivePropertyName = targetProps.First().PropertyName; break; } entitySet = r.Key; if (!string.IsNullOrEmpty(navigPropName) && !string.IsNullOrEmpty(primitivePropertyName)) { 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(navigPropName)) { 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); } if (string.IsNullOrEmpty(primitivePropertyName)) { detail.ErrorMessage = "Cannot get an appropriate primitive property from navigation properties."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } string url = string.Format("{0}/{1}?$expand={2}", context.ServiceBaseUri, entitySet, navigPropName); var resp = WebHelper.Get(new Uri(url), Constants.V4AcceptHeaderJsonFullMetadata, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); if (null == resp || HttpStatusCode.OK != resp.StatusCode) { detail.ErrorMessage = JsonParserHelper.GetErrorMessage(resp.ResponsePayload); info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } JObject feed; resp.ResponsePayload.TryToJObject(out feed); var entities = JsonParserHelper.GetEntries(feed); if (null == entities || !entities.Any()) { detail.ErrorMessage = string.Format("Cannot find any entities from the entity-set '{0}'", entitySet); info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } string searchVal = string.Empty; foreach (var en in entities) { if (null != en[navigPropName] || JTokenType.Array == en[navigPropName].Type || en[navigPropName].Any()) { if (JTokenType.Object != en[navigPropName].First.Type) { break; } var nEntity = en[navigPropName].First as JObject; searchVal = nEntity[primitivePropertyName].ToString().Contains(" ") ? string.Format("\"{0}\"", nEntity[primitivePropertyName].ToString()) : nEntity[primitivePropertyName].ToString(); if (!string.IsNullOrEmpty(searchVal)) { break; } } } if (string.IsNullOrEmpty(searchVal)) { detail.ErrorMessage = "Cannot find any appropriate search values in the expanded entities."; info = new ExtensionRuleViolationInfo(context.Destination, context.ResponsePayload, detail); return(passed); } url = string.Format("{0}/{1}?$expand={2}($search={3})", context.ServiceBaseUri, entitySet, navigPropName, searchVal); resp = WebHelper.Get(new Uri(url), Constants.V4AcceptHeaderJsonFullMetadata, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders); detail = new ExtensionRuleResultDetail(this.Name, url, "GET", String.Empty, resp); if (null != resp && resp.StatusCode == HttpStatusCode.OK) { resp.ResponsePayload.TryToJObject(out feed); if (null != feed && JTokenType.Object == feed.Type) { entities = JsonParserHelper.GetEntries(feed); foreach (var e in entities) { var navigEntities = e[navigPropName].ToList(); if (null == navigEntities || !navigEntities.Any()) { continue; } foreach (var ne in navigEntities) { passed = null; if (searchVal.StripOffDoubleQuotes() == ne[primitivePropertyName].ToString()) { passed = true; } if (passed == null) { passed = false; detail.ErrorMessage = "The service does not execute an accurate result on the system query option '$search' for expanded properties."; } } if (passed == false) { break; } } if (null == passed) { detail.ErrorMessage = "Cannot find any appropriate data to verify this rule."; } } else { detail.ErrorMessage = "The service does not return an correct response payload."; } } else { passed = false; detail.ErrorMessage = "The service does not support the system query option '$search' 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); }