protected static async Task <IList <T> > ParseResultsAsync <T>(string response, Func <JsonElement, JsonElement?> rootResultsSelector) where T : TransientObject, new() { var personProps = new List <T>(); var json = JsonSerializer.Deserialize <JsonElement>(response); var results = rootResultsSelector(json); if (results == null) { throw new ClientException(ErrorType.Unsupported, PnPCoreResources.Exception_Json_Unexpected); } var dataRows = results.Value; if (dataRows.GetArrayLength() == 0) { return(personProps); } var entityInfo = EntityManager.Instance.GetStaticClassInfo(typeof(T)); foreach (var row in dataRows.EnumerateArray()) { var prop = new T(); await JsonMappingHelper.FromJson(prop, entityInfo, new ApiResponse(new ApiCall(String.Empty, ApiType.SPORest), row, Guid.Empty)).ConfigureAwait(false); personProps.Add(prop); } return(personProps); }
private static void AddExpandableSelectRest(StringBuilder sb, EntityFieldInfo field, EntityFieldExpandInfo expandFields, string path) { EntityInfo collectionEntityInfo = null; if (expandFields == null) { collectionEntityInfo = EntityManager.Instance.GetStaticClassInfo(field.ExpandFieldInfo.Type); expandFields = field.ExpandFieldInfo; } else { if (expandFields.Type != null) { collectionEntityInfo = EntityManager.Instance.GetStaticClassInfo(expandFields.Type); } } if (collectionEntityInfo != null) { foreach (var expandableField in expandFields.Fields.OrderBy(p => p.Expandable)) { var expandableFieldInfo = collectionEntityInfo.Fields.First(p => p.Name == expandableField.Name); if (!expandableFieldInfo.SharePointExpandable) { sb.Append($"{JsonMappingHelper.GetRestField(field)}{path}/{JsonMappingHelper.GetRestField(expandableFieldInfo)},"); } else { path = path + "/" + JsonMappingHelper.GetRestField(expandableFieldInfo); AddExpandableSelectRest(sb, field, expandableField, path); path = ""; } } } }
public async Task <IFollowingInfo> GetFollowingInfoAsync() { var apiCall = new ApiCall($"{Uri}/my", ApiType.SPORest) { Interactive = true, }; var response = await RawRequestAsync(apiCall, HttpMethod.Get).ConfigureAwait(false); var document = JsonSerializer.Deserialize <JsonElement>(response.Json); var entityInfo = EntityManager.Instance.GetStaticClassInfo(typeof(FollowingInfo)); var socialInfo = new FollowingInfo(); await JsonMappingHelper.FromJson(socialInfo, entityInfo, new ApiResponse(apiCall, document, Guid.Empty)).ConfigureAwait(false); var socialActorNode = document.Get(nameof(socialInfo.SocialActor)); if (socialActorNode != null) { var socialActor = new SocialActor(); socialInfo.SocialActor = socialActor; entityInfo = EntityManager.Instance.GetStaticClassInfo(typeof(SocialActor)); await JsonMappingHelper.FromJson(socialActor, entityInfo, new ApiResponse(apiCall, socialActorNode.Value, Guid.Empty)).ConfigureAwait(false); } return(socialInfo); }
private static void ProcessChangeElement(object pnpObject, IDataModelParent parent, PnPContext context, JsonElement element, Guid batchRequestId) { var metadataBasedObject = pnpObject as IMetadataExtensible; var entity = EntityManager.GetClassInfo <IChange>(pnpObject.GetType(), null); SetBatchRequestId(pnpObject as TransientObject, batchRequestId); // Enumerate the received properties and try to map them to the model foreach (var property in element.EnumerateObject()) { // Find the model field linked to this field EntityFieldInfo entityField = entity.Fields.FirstOrDefault(p => p.SharePointName == property.Name); // Entity field should be populated for the actual fields we've requested if (entityField != null) { if (entityField.PropertyInfo.PropertyType == typeof(IChangeToken)) // Special case { var changeToken = GetConcreteInstance(property.Value); ProcessChangeElement(changeToken, parent, context, property.Value, batchRequestId); entityField.PropertyInfo?.SetValue(pnpObject, changeToken); } else if (entityField.PropertyInfo.PropertyType == typeof(IContentType)) { // Since this is an SP.ContentTypeId and NOT an SP.ContentType, it's not a perfect entity match; we'll do it manually var concreteInstance = (IContentType)EntityManager.GetEntityConcreteInstance(entityField.PropertyInfo.PropertyType, parent, context); var contentTypeId = property.Value.GetProperty("StringValue").GetString(); concreteInstance.SetSystemProperty(ct => ct.Id, contentTypeId); concreteInstance.SetSystemProperty(ct => ct.StringId, contentTypeId); (concreteInstance as IMetadataExtensible).Metadata.Add(PnPConstants.MetaDataType, "SP.ContentTypeId"); (concreteInstance as IMetadataExtensible).Metadata.Add(PnPConstants.MetaDataId, contentTypeId); (concreteInstance as IMetadataExtensible).Metadata.Add(PnPConstants.MetaDataRestId, contentTypeId); concreteInstance.Requested = true; entityField.PropertyInfo?.SetValue(pnpObject, concreteInstance); } else // Simple property mapping { // Set the object property value taken from the JSON payload entityField.PropertyInfo?.SetValue(pnpObject, JsonMappingHelper.GetJsonFieldValue(null, entityField.Name, property.Value, entityField.DataType, entityField.SharePointUseCustomMapping, null)); } } else { // Let's keep track of the object metadata, useful when creating new requests if (property.Name == "__metadata") { JsonMappingHelper.TrackSharePointMetaData(metadataBasedObject, property); } } } }
protected async Task <IPersonProperties> ParsePersonPropertiesResultAsync(ApiCallResponse response) { var results = new List <IPersonProperties>(); var json = JsonSerializer.Deserialize <JsonElement>(response.Json); var entityInfo = EntityManager.Instance.GetStaticClassInfo(typeof(PersonProperties)); var prop = new PersonProperties(); await JsonMappingHelper.FromJson(prop, entityInfo, new ApiResponse(new ApiCall(String.Empty, ApiType.SPORest), json, Guid.Empty)).ConfigureAwait(false); return(prop); }
private void Swap(IDictionary <string, object> a, IDictionary <string, object> b) { a.Clear(); foreach (var pair in b) { a[pair.Key] = pair.Value; // Also commit the changes in the TransientDictionary if needed if (a[pair.Key] is TransientDictionary) { (a[pair.Key] as TransientDictionary).Commit(); } // Also commit the changes in the ComplexTypeModel classes else if (a[pair.Key] != null && JsonMappingHelper.IsTypeWithoutGet(a[pair.Key].GetType())) { (a[pair.Key] as TransientObject).Commit(); } } changes.Clear(); }
/// <summary> /// Translates model type into a set of classes that are used to drive CRUD operations /// </summary> /// <param name="type">The reference model type</param> /// <returns>Entity model class describing this model instance</returns> internal EntityInfo GetStaticClassInfo(Type type) { type = GetEntityConcreteType(type); // Check if we can deliver this entity from cache if (entityCache.TryGetValue(type, out EntityInfo entityInfoFromCache)) { return(entityInfoFromCache); } else { // Load and process type attributes var sharePointTypeAttributes = type.GetCustomAttributes <SharePointTypeAttribute>(false); var graphTypeAttributes = type.GetCustomAttributes <GraphTypeAttribute>(false); if (sharePointTypeAttributes.Any() || graphTypeAttributes.Any()) { EntityInfo classInfo = new EntityInfo { UseOverflowField = type.ImplementsInterface(typeof(IExpandoDataModel)) }; if (sharePointTypeAttributes.Any()) { foreach (var sharePointTypeAttribute in sharePointTypeAttributes) { var sharePointTargetToAdd = new EntitySharePointTypeInfo { Type = sharePointTypeAttribute.Type, Target = sharePointTypeAttribute.Target ?? type, Uri = sharePointTypeAttribute.Uri, Get = !string.IsNullOrEmpty(sharePointTypeAttribute.Get) ? sharePointTypeAttribute.Get : sharePointTypeAttribute.Uri, LinqGet = !string.IsNullOrEmpty(sharePointTypeAttribute.LinqGet) ? sharePointTypeAttribute.LinqGet : sharePointTypeAttribute.Uri, OverflowProperty = sharePointTypeAttribute.OverflowProperty, Update = !string.IsNullOrEmpty(sharePointTypeAttribute.Update) ? sharePointTypeAttribute.Update : sharePointTypeAttribute.Uri, Delete = !string.IsNullOrEmpty(sharePointTypeAttribute.Delete) ? sharePointTypeAttribute.Delete : sharePointTypeAttribute.Uri, }; classInfo.SharePointTargets.Add(sharePointTargetToAdd); } } if (graphTypeAttributes.Any()) { foreach (var graphTypeAttribute in graphTypeAttributes) { var graphTargetToAdd = new EntityGraphTypeInfo { Target = graphTypeAttribute.Target ?? type, Id = !string.IsNullOrEmpty(graphTypeAttribute.Id) ? graphTypeAttribute.Id : "id", Get = !string.IsNullOrEmpty(graphTypeAttribute.Get) ? graphTypeAttribute.Get : graphTypeAttribute.Uri, LinqGet = !string.IsNullOrEmpty(graphTypeAttribute.LinqGet) ? graphTypeAttribute.LinqGet : graphTypeAttribute.Uri, OverflowProperty = graphTypeAttribute.OverflowProperty, Update = !string.IsNullOrEmpty(graphTypeAttribute.Update) ? graphTypeAttribute.Update : graphTypeAttribute.Uri, Delete = !string.IsNullOrEmpty(graphTypeAttribute.Delete) ? graphTypeAttribute.Delete : graphTypeAttribute.Uri, Beta = graphTypeAttribute.Beta, }; classInfo.GraphTargets.Add(graphTargetToAdd); } } string keyPropertyName = null; foreach (var property in type.GetProperties()) { EntityFieldInfo classField = null; var propertyAttributes = property.GetCustomAttributes(); bool skipField = false; foreach (var attribute in propertyAttributes) { switch (attribute) { // Field metadata case SharePointPropertyAttribute sharePointPropertyAttribute: { classField = EnsureClassField(type, property, classInfo); classField.SharePointName = !string.IsNullOrEmpty(sharePointPropertyAttribute.FieldName) ? sharePointPropertyAttribute.FieldName : property.Name; classField.ExpandableByDefault = sharePointPropertyAttribute.ExpandByDefault; classField.SharePointUseCustomMapping = sharePointPropertyAttribute.UseCustomMapping; classField.SharePointJsonPath = sharePointPropertyAttribute.JsonPath; break; } case GraphPropertyAttribute graphPropertyAttribute: { classField = EnsureClassField(type, property, classInfo); classField.GraphName = !string.IsNullOrEmpty(graphPropertyAttribute.FieldName) ? graphPropertyAttribute.FieldName : ToCamelCase(property.Name); classField.GraphExpandable = graphPropertyAttribute.Expandable; classField.ExpandableByDefault = graphPropertyAttribute.ExpandByDefault; classField.GraphUseCustomMapping = graphPropertyAttribute.UseCustomMapping; classField.GraphJsonPath = graphPropertyAttribute.JsonPath; classField.GraphGet = graphPropertyAttribute.Get; classField.GraphBeta = graphPropertyAttribute.Beta; break; } case KeyPropertyAttribute keyPropertyAttribute: { keyPropertyName = keyPropertyAttribute.KeyPropertyName; skipField = true; break; } case SystemPropertyAttribute systemPropertyAttribute: { skipField = true; break; } } } if (!skipField) { if (classField == null) { classField = EnsureClassField(type, property, classInfo); // Property was not decorated with attributes if (!classInfo.SharePointTargets.Any()) { // This is a Graph only property classField.GraphName = ToCamelCase(property.Name); } else { // This is SharePoint/Graph property, we're not setting the GraphName here because in "mixed" objects the Graph properties must be explicitely marked with the GraphProperty attribute classField.SharePointName = property.Name; } } // Automatically determine "expand" value for SharePoint properties if (!string.IsNullOrEmpty(classField.SharePointName)) { if (JsonMappingHelper.IsModelCollection(classField.PropertyInfo.PropertyType) || JsonMappingHelper.IsModelType(classField.PropertyInfo.PropertyType)) { classField.SharePointExpandable = true; } } } } // Find the property set as key field and mark it as such if (!string.IsNullOrEmpty(keyPropertyName)) { // Store the actual key property name classInfo.ActualKeyFieldName = keyPropertyName; // Process the SharePoint and Graph ID fields var keyProperty = classInfo.Fields.FirstOrDefault(p => p.Name == keyPropertyName); if (keyProperty != null) { if (classInfo.SharePointTargets.Any()) { keyProperty.IsSharePointKey = true; } keyProperty.IsGraphKey = true; // If a property is defined as graph key then ensure the GraphName is correctly set if (string.IsNullOrEmpty(keyProperty.GraphName) && classInfo.GraphTargets.Any()) { keyProperty.GraphName = ToCamelCase(keyProperty.Name); } } } // Update the field used for overflow, this field was added (as it's part of the ExpandoBaseDataModel base data class), // But since there's no field to set properties on the fieldname property comes from the class mapping attribute if (classInfo.UseOverflowField) { var overflowField = classInfo.Fields.FirstOrDefault(p => p.Name == ExpandoBaseDataModel <IExpandoDataModel> .OverflowFieldName); if (!classInfo.SharePointTargets.Any()) { // This is a Graph only property overflowField.GraphName = classInfo.GraphTargets.First().OverflowProperty; } else { // This is SharePoint/Graph property overflowField.SharePointName = classInfo.SharePointTargets.First().OverflowProperty; overflowField.GraphName = classInfo.GraphTargets.FirstOrDefault()?.OverflowProperty; } } // Add to our cache to speed up future retrievals entityCache.TryAdd(type, classInfo); return(classInfo); } else { throw new ClientException(ErrorType.ModelMetadataIncorrect, PnPCoreResources.Exception_ModelMetadataIncorrect_MissingClassMapping); } } }
private static async Task <ApiCallRequest> BuildGetAPICallRestAsync <TModel>(BaseDataModel <TModel> model, EntityInfo entity, ODataQuery <TModel> oDataQuery, ApiCall apiOverride, bool useLinqGet, bool loadPages) { string getApi = useLinqGet ? entity.SharePointLinqGet : entity.SharePointGet; IEnumerable <EntityFieldInfo> fields = entity.Fields.Where(p => p.Load); Dictionary <string, string> urlParameters = new Dictionary <string, string>(); StringBuilder sb = new StringBuilder(); // Only add select statement whenever there was a filter specified if (entity.SharePointFieldsLoadedViaExpression) { // $select foreach (var field in fields) { // If there was a selection on which fields to include in an expand (via the QueryProperties() option) then add those fields if (field.SharePointExpandable && field.ExpandFieldInfo != null) { AddExpandableSelectRest(sb, field, null, ""); } else { sb.Append($"{JsonMappingHelper.GetRestField(field)},"); } } urlParameters.Add("$select", sb.ToString().TrimEnd(new char[] { ',' })); sb.Clear(); } // $expand foreach (var field in fields.Where(p => p.SharePointExpandable)) { if (entity.SharePointFieldsLoadedViaExpression) { sb.Append($"{JsonMappingHelper.GetRestField(field)},"); // If there was a selection on which fields to include in an expand (via the Include() option) and the included field was expandable itself then add it if (field.ExpandFieldInfo != null) { string path = ""; AddExpandableExpandRest(sb, field, null, path); } } else { if (field.ExpandableByDefault) { sb.Append($"{JsonMappingHelper.GetRestField(field)},"); } } } urlParameters.Add("$expand", sb.ToString().TrimEnd(new char[] { ',' })); oDataQuery.AddODataToUrlParameters(urlParameters, ODataTargetPlatform.SPORest); // REST apis do not apply a default top // In order to not receive all items in one request, we apply a default top // We don't change the original ODataQuery to avoid side effects if (useLinqGet && !urlParameters.ContainsKey(ODataQuery <TModel> .TopKey)) { urlParameters.Add(ODataQuery <TModel> .TopKey, model.PnPContext.GlobalOptions.HttpSharePointRestDefaultPageSize.ToString()); } sb.Clear(); // Build the API call string baseApiCall = ""; if (apiOverride.Equals(default(ApiCall))) { baseApiCall = $"{model.PnPContext.Uri.AbsoluteUri.TrimEnd(new char[] { '/' })}/{getApi}"; } else { baseApiCall = $"{model.PnPContext.Uri.AbsoluteUri.TrimEnd(new char[] { '/' })}/{apiOverride.Request}"; } // Parse tokens in the base api call baseApiCall = await ApiHelper.ParseApiCallAsync(model, baseApiCall).ConfigureAwait(false); sb.Append(baseApiCall); // Build the querystring parameters NameValueCollection queryString = HttpUtility.ParseQueryString(string.Empty); foreach (var urlParameter in urlParameters.Where(i => !string.IsNullOrEmpty(i.Value))) { // Add key and value, which will be automatically URL-encoded, if needed queryString.Add(urlParameter.Key, urlParameter.Value); } // Build the whole URL if (queryString.AllKeys.Length > 0) { // In .NET Framework to ToString() of a NameValueCollection will use HttpUtility.UrlEncodeUnicode under // the covers resulting in issues. So we decode and encode again as a workaround. This code produces the // same result when used under .NET5/Core versus .NET Framework sb.Append($"?{queryString.ToEncodedString()}"); } // Create ApiCall instance and call the override option if needed var call = new ApiCallRequest(new ApiCall(sb.ToString(), ApiType.SPORest, loadPages: loadPages)); if (model.GetApiCallOverrideHandler != null) { call = await model.GetApiCallOverrideHandler.Invoke(call).ConfigureAwait(false); } return(call); }