/// <summary> /// Portable serialization text is intended to be usable to send complete objects to a web client where we can have client-side JS framework awareness of the format, etc. /// We send back extended details including schema details that would not normally be necessary as such, hence the "portability". (Without this, we would need to know the schema ahead of time.) /// Options on this method support serializing a subset of properties, among other things that would be relevant when working with client-side logic. /// Notably things like row-state are *not* included which differs from some over-wire strategies - we expect to pair use of this method with ApplyChangesFromPortableText() in order to "apply changes" against a re-retrieved set, which has value for security, for example. /// The Portable being called out in the name is intended to be more explicit than making an option on the non-Portable function. /// </summary> /// <param name="mode"></param> /// <returns></returns> public string GetPortableText(PortableSerializationOptions options = null) { if (options == null) { options = new PortableSerializationOptions(); } StringBuilder sb = new StringBuilder(4096); var actmode = options.Mode.GetValueOrDefault(Globals.PortableJSONMode.GetValueOrDefault(CEF.CurrentServiceScope.Settings.SerializationMode)); CEF.CurrentServiceScope.ReconcileModifiedState(null); using (var jw = new JsonTextWriter(new StringWriter(sb))) { jw.FloatFormatHandling = FloatFormatHandling.DefaultValue; // All contained within an object jw.WriteStartObject(); jw.WritePropertyName("schema"); jw.WriteStartArray(); var c = typeof(T).FastGetAllProperties(true, (actmode & SerializationMode.IncludeReadOnlyProps) == 0 ? true : new bool?()).ToList(); // Use a top x sample of entries in collection to determine if there are any extended properties to serialize if (options.IncludeExtended.GetValueOrDefault(Globals.PortableJSONIncludeExtended) && options.ExtendedPropertySampleSize.GetValueOrDefault(Globals.PortableJSONExtendedPropertySampleSize) > 0) { foreach (T i in this.Take(options.ExtendedPropertySampleSize.GetValueOrDefault(Globals.PortableJSONExtendedPropertySampleSize))) { c = c.Union(from a in i.AsInfraWrapped().GetAllPreferredTypes() select(a.Key, a.Value, true, true)).ToList(); } } // Explicitly remove audit if needed if (options.ExcludeAudit.GetValueOrDefault(Globals.PortableJSONExcludeAudit)) { if (!string.IsNullOrEmpty(CEF.CurrentAuditService()?.LastUpdatedByField)) { var trem = (from a in c where string.Compare(a.name, CEF.CurrentAuditService()?.LastUpdatedByField, true) == 0 select a); if (trem.Any()) { c.Remove(trem.First()); } } if (!string.IsNullOrEmpty(CEF.CurrentAuditService()?.LastUpdatedDateField)) { var trem = (from a in c where string.Compare(a.name, CEF.CurrentAuditService()?.LastUpdatedDateField, true) == 0 select a); if (trem.Any()) { c.Remove(trem.First()); } } if (!string.IsNullOrEmpty(CEF.CurrentAuditService()?.IsDeletedField)) { var trem = (from a in c where string.Compare(a.name, CEF.CurrentAuditService()?.IsDeletedField, true) == 0 select a); if (trem.Any()) { c.Remove(trem.First()); } } } // Apply column name filters if needed if (options.IncludeColumns != null) { c = (from a in c where (from b in options.IncludeColumns where string.Compare(a.name, b, true) == 0 select b).Any() select a).ToList(); } if (options.ExcludeColumns != null) { c = (from a in c where !(from b in options.ExcludeColumns where string.Compare(a.name, b, true) == 0 select b).Any() select a).ToList(); } // Get any available key for this type var keydef = KeyService.ResolveKeyDefinitionForType(typeof(T)); List <string> finalName = new List <string>(); List <Type> finalType = new List <Type>(); // Actual schema write based on distinct list of columns and types foreach (var prop in (from n in (from a in c select a.name).Distinct() select new { Name = n, Type = (from t in c where string.Compare(t.name, n, true) == 0 orderby(t.type == null ? 1 : 0) select t.type).First() })) { var restype = prop.Type; var req = !(prop.Type.IsGenericType && prop.Type.GetGenericTypeDefinition() == typeof(Nullable <>)); if (!req) { restype = Nullable.GetUnderlyingType(prop.Type); } var rp = ValidationService.GetRequiredFor(typeof(T), prop.Name); if (rp.HasValue) { req = rp.Value; } // Ignore non-primative ref types - things like property classes in generated code should interop with the base instance if (restype.IsPrimitive || restype.IsValueType || restype.IsSerializable) { jw.WriteStartObject(); jw.WritePropertyName("cn"); jw.WriteValue(prop.Name); if ((actmode & SerializationMode.IncludeType) != 0) { jw.WritePropertyName("dt"); jw.WriteValue(restype.Name.ToLower().Replace("system.", "")); jw.WritePropertyName("key"); jw.WriteValue((from a in keydef where string.Compare(a, prop.Name, true) == 0 select a).Any()); jw.WritePropertyName("req"); jw.WriteValue(req); // If there's a maxlength setting available, write it var ml = ValidationService.GetMaxLengthFor(typeof(T), prop.Name); if (ml.HasValue) { jw.WritePropertyName("maxlen"); jw.WriteValue(ml); } } jw.WriteEndObject(); finalName.Add(prop.Name); finalType.Add(restype); } } // end schema jw.WriteEndArray(); // Start data jw.WritePropertyName("rows"); jw.WriteStartArray(); var cdates = options.ConvertDates.GetValueOrDefault(Globals.PortableJSONConvertDates); IEnumerable <ICEFInfraWrapper> list = this.AllAsInfraWrapped(); if (options.SortSpec != null) { list = list.OrderBy(options.SortSpec); } if (options.FilterSpec != null) { list = list.Where(options.FilterSpec); } foreach (var iw in list) { if ((actmode & SerializationMode.OnlyChanged) == 0 || iw.GetRowState() != ObjectState.Unchanged) { jw.WriteStartArray(); for (int i = 0; i < finalName.Count; ++i) { var cv = iw.GetValue(finalName[i]); if (cv == null) { string s = null; jw.WriteValue(s); } else { if (finalType[i] == typeof(DateTime)) { var asdate = Convert.ToDateTime(cv); if (cdates == DateConversionMode.ToGMTAlways || (cdates == DateConversionMode.ToGMTWhenHasTime && asdate.TimeOfDay.Seconds > 0)) { asdate = asdate.ToUniversalTime(); } var d = Convert.ToInt64(asdate.Ticks - 621355968000000000L) / 10000L; jw.WriteValue(d); } else { var cvt = cv.GetType(); // For non-primative ref types, need to "flatten" properties if (cvt.IsValueType || cvt.IsPrimitive || cvt.IsSerializable) { jw.WriteValue(cv); } } } } jw.WriteEndArray(); } } // end data jw.WriteEndArray(); jw.WriteEndObject(); } return(sb.ToString()); }
public IEnumerable <T> GetItemsFromSerializationText <T>(string json, JsonSerializationSettings settings) where T : class, new() { if (settings == null) { settings = new JsonSerializationSettings(); } switch (settings.SerializationType) { case SerializationType.Array: { var setArray = JArray.Parse(json); foreach (var i in setArray.Children()) { if (i.Type == JTokenType.Object) { yield return(CEF.Deserialize <T>(i.ToString())); } } break; } case SerializationType.ObjectWithSchemaType1AndRows: { // Read schema Dictionary <string, Type> schema = new Dictionary <string, Type>(); var root = JObject.Parse(json); // Read schema foreach (var propInfo in root.GetValue(settings.SchemaName).ToArray()) { if (propInfo is JObject jo) { if (jo.Count < 2 || jo.Count > 3) { throw new CEFInvalidOperationException("Invalid JSON format."); } JProperty pn = (from a in jo.Children() let b = a as JProperty where b != null && b.Name.Equals(settings.SchemaFieldNameName) select b).FirstOrDefault(); JProperty pt = (from a in jo.Children() let b = a as JProperty where b != null && b.Name.Equals(settings.SchemaFieldTypeName) select b).FirstOrDefault(); JProperty pr = (from a in jo.Children() let b = a as JProperty where b != null && b.Name.Equals(settings.SchemaFieldRequiredName) select b).FirstOrDefault(); if (pn == null || pt == null) { throw new CEFInvalidOperationException("Invalid JSON format."); } var t = settings.GetDataType(pt.Value.ToString()); var torig = t; // Assume that any property might be omitted/missing which means everything should be considered nullable if (t.IsValueType && !(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable <>))) { t = typeof(Nullable <>).MakeGenericType(t); } var name = pn.Value.ToString(); schema[name] = t; // If is required, we add a validation for this if (pr != null && bool.TryParse(pr.Value.ToString(), out bool prv) && prv) { ValidationService.RegisterRequired <T>(torig, name); } } } // Read objects, using the schema as the "basis" where missing/omitted properties are still carried through foreach (var itemInfo in root.GetValue(settings.DataRootName).ToArray()) { var obj = CEF.Deserialize <T>(itemInfo.ToString()); var iw = obj.AsInfraWrapped(); // We need to apply property type settings after-the-fact var allProp = iw.GetAllValues(); foreach (var propInfo in schema) { var existingInfo = (from a in allProp where a.Key == propInfo.Key select(propInfo.Value, a.Value)); if (existingInfo.Any()) { iw.SetValue(propInfo.Key, existingInfo.First().Item2, existingInfo.First().Item1); } else { iw.SetValue(propInfo.Key, null, propInfo.Value); } } yield return(obj); } break; } } }