/// <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()); }
private void WriteSerializationText(JsonTextWriter tw, object o, SerializationMode mode, SerializationVisitTracker visits) { var iw = o.AsInfraWrapped(false); if (iw != null) { tw.WriteStartObject(); var wot = iw.GetWrappedObject()?.GetType() ?? iw.GetBaseType(); // We only really want/need to include type info on outermost objects (session scope level only), so reset this for all nested objects var nextmode = (SerializationMode)((int)mode & (-1 ^ (int)SerializationMode.IncludeType)); if ((mode & SerializationMode.IncludeType) != 0) { tw.WritePropertyName(Globals.SerializationTypePropertyName); tw.WriteValue(o.GetType().AssemblyQualifiedName); } // Attempt to push enumerable types to the "end" foreach (var kvp in (from a in iw.GetAllValues() orderby a.Value is IEnumerable ? 1 : 0 select a)) { // If it's enumerable, recurse each item // TODO - better way to detect primitive type like string w/o hardcoding?? if (kvp.Value is IEnumerable && kvp.Value.GetType() != typeof(string)) { tw.WritePropertyName(kvp.Key); tw.WriteStartArray(); var asEnum = (kvp.Value as IEnumerable).GetEnumerator(); while (asEnum.MoveNext()) { var i = asEnum.Current; // We only need to do this for tracked objects, for now we only do for value types or non-system (TODO) if (i == null || i.GetType().IsValueType || i.GetType().FullName.StartsWith("System.")) { tw.WriteValue(i); } else { if ((mode & SerializationMode.SingleLevel) == 0) { var iw2 = i.AsInfraWrapped(); if (iw2 != null) { SaveContents(tw, iw2, nextmode, visits); } else { if (i.GetType().IsSerializable) { tw.WriteValue(i); } } } } } tw.WriteEndArray(); } else { // If it's a tracked object, recurse if (kvp.Value != null && !kvp.Value.GetType().IsValueType&& CEF.CurrentServiceScope.GetTrackedByWrapperOrTarget(kvp.Value) != null) { if ((mode & SerializationMode.SingleLevel) == 0) { var iw2 = kvp.Value.AsInfraWrapped(); if (iw2 != null) { tw.WritePropertyName(kvp.Key); SaveContents(tw, iw2, nextmode, visits); } } } else { if ((mode & SerializationMode.ExtendedInfoAsShadowProps) != 0) { var rs = iw.GetRowState(); if (rs == ObjectState.Modified || rs == ObjectState.ModifiedPriority) { var ov = iw.GetOriginalValue(kvp.Key, false); if (ov != null) { tw.WritePropertyName("\\\\" + kvp.Key); tw.WriteValue(ov); } } // We write out schema metadata only when see a type for the first time if (!visits.Types.Contains(wot)) { var pb = wot.GetProperty(kvp.Key); // Preferred data type var pt = (from a in iw.GetAllPreferredTypes() where a.Key == kvp.Key select a.Value).FirstOrDefault() ?? pb?.PropertyType; if (pt != null) { tw.WritePropertyName("\\+" + kvp.Key); tw.WriteValue(pt.AssemblyQualifiedName); // Is writeable tw.WritePropertyName("\\-" + kvp.Key); tw.WriteValue(pb?.CanWrite); } } } if (kvp.Value != null || (mode & SerializationMode.IncludeNull) != 0) { if (((mode & SerializationMode.IncludeReadOnlyProps) != 0) || (wot.GetProperty(kvp.Key)?.CanWrite).GetValueOrDefault(true)) { if (((mode & SerializationMode.OnlyCLRProperties) == 0) || (wot.GetProperty(kvp.Key)?.CanRead).GetValueOrDefault(false)) { var aud = CEF.CurrentAuditService(); if (aud != null) { if (((mode & SerializationMode.OriginalForConcurrency) == 0) || (string.Compare(aud.LastUpdatedDateField, kvp.Key, true) != 0 && string.Compare(aud.IsDeletedField, kvp.Key, true) != 0)) { if (kvp.Value == null || kvp.Value.GetType().IsSerializable) { tw.WritePropertyName(kvp.Key); tw.WriteValue(kvp.Value); } } else { // Only need to send original date - do not send isdeleted at all if (string.Compare(aud.IsDeletedField, kvp.Key, true) != 0) { var rs = iw.GetRowState(); if (rs != ObjectState.Added && rs != ObjectState.Unlinked) { var val = iw.GetOriginalValue(kvp.Key, false); if (val != null) { tw.WritePropertyName(kvp.Key); tw.WriteValue(val); } } } } } } } } } } } if ((mode & SerializationMode.ExtendedInfoAsShadowProps) != 0) { tw.WritePropertyName("_ot_"); tw.WriteValue(wot.Name); } // Allows for inclusion of object state, etc. iw.FinalizeObjectContents(tw, mode); tw.WriteEndObject(); } else { tw.WriteValue(o); } }