Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit c400469

Browse files
committed
Merge branch 'master' of https://github.com/ServiceStack/ServiceStack.Text into netcore
2 parents 6ba4529 + 5492bef commit c400469

File tree

10 files changed

+278
-2
lines changed

10 files changed

+278
-2
lines changed

lib/ServiceStack.Client.dll

0 Bytes
Binary file not shown.

lib/ServiceStack.Common.dll

512 Bytes
Binary file not shown.

lib/ServiceStack.Interfaces.dll

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

src/ServiceStack.Text/HttpUtils.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,88 @@ public static Task<string> PutCsvToUrlAsync(this string url, string csv,
371371
requestFilter: requestFilter, responseFilter: responseFilter);
372372
}
373373

374+
public static string PatchStringToUrl(this string url, string requestBody = null,
375+
string contentType = null, string accept = "*/*",
376+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
377+
{
378+
return SendStringToUrl(url, method: "PATCH",
379+
requestBody: requestBody, contentType: contentType,
380+
accept: accept, requestFilter: requestFilter, responseFilter: responseFilter);
381+
}
382+
383+
public static Task<string> PatchStringToUrlAsync(this string url, string requestBody = null,
384+
string contentType = null, string accept = "*/*",
385+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
386+
{
387+
return SendStringToUrlAsync(url, method: "PATCH",
388+
requestBody: requestBody, contentType: contentType,
389+
accept: accept, requestFilter: requestFilter, responseFilter: responseFilter);
390+
}
391+
392+
public static string PatchToUrl(this string url, string formData = null, string accept = "*/*",
393+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
394+
{
395+
return SendStringToUrl(url, method: "PATCH",
396+
contentType: MimeTypes.FormUrlEncoded, requestBody: formData,
397+
accept: accept, requestFilter: requestFilter, responseFilter: responseFilter);
398+
}
399+
400+
public static Task<string> PatchToUrlAsync(this string url, string formData = null, string accept = "*/*",
401+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
402+
{
403+
return SendStringToUrlAsync(url, method: "PATCH",
404+
contentType: MimeTypes.FormUrlEncoded, requestBody: formData,
405+
accept: accept, requestFilter: requestFilter, responseFilter: responseFilter);
406+
}
407+
408+
public static string PatchToUrl(this string url, object formData = null, string accept = "*/*",
409+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
410+
{
411+
string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null;
412+
413+
return SendStringToUrl(url, method: "PATCH",
414+
contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData,
415+
accept: accept, requestFilter: requestFilter, responseFilter: responseFilter);
416+
}
417+
418+
public static Task<string> PatchToUrlAsync(this string url, object formData = null, string accept = "*/*",
419+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
420+
{
421+
string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null;
422+
423+
return SendStringToUrlAsync(url, method: "PATCH",
424+
contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData,
425+
accept: accept, requestFilter: requestFilter, responseFilter: responseFilter);
426+
}
427+
428+
public static string PatchJsonToUrl(this string url, string json,
429+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
430+
{
431+
return SendStringToUrl(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json,
432+
requestFilter: requestFilter, responseFilter: responseFilter);
433+
}
434+
435+
public static Task<string> PatchJsonToUrlAsync(this string url, string json,
436+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
437+
{
438+
return SendStringToUrlAsync(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json,
439+
requestFilter: requestFilter, responseFilter: responseFilter);
440+
}
441+
442+
public static string PatchJsonToUrl(this string url, object data,
443+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
444+
{
445+
return SendStringToUrl(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json,
446+
requestFilter: requestFilter, responseFilter: responseFilter);
447+
}
448+
449+
public static Task<string> PatchJsonToUrlAsync(this string url, object data,
450+
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
451+
{
452+
return SendStringToUrlAsync(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json,
453+
requestFilter: requestFilter, responseFilter: responseFilter);
454+
}
455+
374456
public static string DeleteFromUrl(this string url, string accept = "*/*",
375457
Action<HttpWebRequest> requestFilter = null, Action<HttpWebResponse> responseFilter = null)
376458
{

src/ServiceStack.Text/ReflectionExtensions.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212

1313
using System;
14+
using System.Collections;
1415
using System.Collections.Concurrent;
1516
using System.Collections.Generic;
1617
using System.ComponentModel;
@@ -1987,6 +1988,43 @@ private static ObjectDictionaryDefinition CreateObjectDictionaryDefinition(Type
19871988
}
19881989
return def;
19891990
}
1991+
1992+
public static Dictionary<string, object> ToSafePartialObjectDictionary<T>(this T instance)
1993+
{
1994+
var to = new Dictionary<string, object>();
1995+
var propValues = instance.ToObjectDictionary();
1996+
if (propValues != null)
1997+
{
1998+
foreach (var entry in propValues)
1999+
{
2000+
var valueType = entry.Value != null
2001+
? entry.Value.GetType()
2002+
: null;
2003+
2004+
if (valueType == null || !valueType.IsClass() || valueType == typeof(string))
2005+
{
2006+
to[entry.Key] = entry.Value;
2007+
}
2008+
else if (!TypeSerializer.HasCircularReferences(entry.Value))
2009+
{
2010+
var enumerable = entry.Value as IEnumerable;
2011+
if (enumerable != null)
2012+
{
2013+
to[entry.Key] = entry.Value;
2014+
}
2015+
else
2016+
{
2017+
to[entry.Key] = entry.Value.ToSafePartialObjectDictionary();
2018+
}
2019+
}
2020+
else
2021+
{
2022+
to[entry.Key] = entry.Value.ToString();
2023+
}
2024+
}
2025+
}
2026+
return to;
2027+
}
19902028
}
19912029

19922030
}

src/ServiceStack.Text/StringExtensions.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,13 @@ public static string ToJsv<T>(this T obj)
511511
return TypeSerializer.SerializeToString(obj);
512512
}
513513

514+
public static string ToSafeJsv<T>(this T obj)
515+
{
516+
return TypeSerializer.HasCircularReferences(obj)
517+
? obj.ToSafePartialObjectDictionary().ToJsv()
518+
: obj.ToJsv();
519+
}
520+
514521
public static T FromJsv<T>(this string jsv)
515522
{
516523
return TypeSerializer.DeserializeFromString<T>(jsv);
@@ -523,6 +530,13 @@ public static string ToJson<T>(this T obj)
523530
: JsonSerializer.SerializeToString(obj);
524531
}
525532

533+
public static string ToSafeJson<T>(this T obj)
534+
{
535+
return TypeSerializer.HasCircularReferences(obj)
536+
? obj.ToSafePartialObjectDictionary().ToJson()
537+
: obj.ToJson();
538+
}
539+
526540
public static T FromJson<T>(this string json)
527541
{
528542
return JsonSerializer.DeserializeFromString<T>(json);
@@ -607,7 +621,7 @@ public static int IndexOfAny(this string text, int startIndex, params string[] n
607621
{
608622
foreach (var needle in needles)
609623
{
610-
var pos = text.IndexOf(needle, startIndex);
624+
var pos = text.IndexOf(needle, startIndex, StringComparison.Ordinal);
611625
if ((pos >= 0) && (firstPos == -1 || pos < firstPos))
612626
firstPos = pos;
613627
}

src/ServiceStack.Text/TypeSerializer.cs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212

1313
using System;
14+
using System.Collections;
1415
using System.Collections.Generic;
1516
using System.Globalization;
1617
using System.IO;
@@ -253,7 +254,9 @@ public static string SerializeAndFormat<T>(this T instance)
253254
if (fn != null)
254255
return Dump(fn);
255256

256-
var dtoStr = SerializeToString(instance);
257+
var dtoStr = !HasCircularReferences(instance)
258+
? SerializeToString(instance)
259+
: SerializeToString(instance.ToSafePartialObjectDictionary());
257260
var formatStr = JsvFormatter.Format(dtoStr);
258261
return formatStr;
259262
}
@@ -275,6 +278,62 @@ public static string Dump(this Delegate fn)
275278
StringBuilderThreadStatic.ReturnAndFree(sb));
276279
return info;
277280
}
281+
282+
public static bool HasCircularReferences(object value)
283+
{
284+
return HasCircularReferences(value, null);
285+
}
286+
287+
private static bool HasCircularReferences(object value, Stack<object> parentValues)
288+
{
289+
var type = value != null ? value.GetType() : null;
290+
291+
if (type == null || !type.IsClass() || value is string)
292+
return false;
293+
294+
if (parentValues == null)
295+
{
296+
parentValues = new Stack<object>();
297+
parentValues.Push(value);
298+
}
299+
300+
var valueEnumerable = value as IEnumerable;
301+
if (valueEnumerable != null)
302+
{
303+
foreach (var item in valueEnumerable)
304+
{
305+
if (HasCircularReferences(item, parentValues))
306+
return true;
307+
}
308+
}
309+
else
310+
{
311+
var props = type.GetSerializableProperties();
312+
313+
foreach (var pi in props)
314+
{
315+
if (pi.GetIndexParameters().Length > 0)
316+
continue;
317+
318+
var mi = pi.PropertyGetMethod();
319+
var pValue = mi != null ? mi.Invoke(value, null) : null;
320+
if (pValue == null)
321+
continue;
322+
323+
if (parentValues.Contains(pValue))
324+
return true;
325+
326+
parentValues.Push(pValue);
327+
328+
if (HasCircularReferences(pValue, parentValues))
329+
return true;
330+
331+
parentValues.Pop();
332+
}
333+
}
334+
335+
return false;
336+
}
278337
}
279338

280339
public class JsvStringSerializer : IStringSerializer
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using NUnit.Framework;
3+
4+
namespace ServiceStack.Text.Tests
5+
{
6+
public class DumpTests
7+
{
8+
public class Node
9+
{
10+
public Node(int id, params Node[] children)
11+
{
12+
Id = id;
13+
Children = children;
14+
}
15+
16+
public int Id { get; set; }
17+
18+
public Node[] Children { get; set; }
19+
}
20+
21+
[Test]
22+
public void Can_detect_Circular_References_in_models()
23+
{
24+
var node = new Node(1,
25+
new Node(11, new Node(111)),
26+
new Node(12, new Node(121)));
27+
28+
Assert.That(!TypeSerializer.HasCircularReferences(node));
29+
30+
var root = new Node(1,
31+
new Node(11));
32+
33+
var cyclicalNode = new Node(1, root);
34+
root.Children[0].Children = new[] { cyclicalNode };
35+
36+
Assert.That(TypeSerializer.HasCircularReferences(root));
37+
}
38+
39+
[Test]
40+
public void Can_PrintDump_ToSafeJson_ToSafeJsv_recursive_Node()
41+
{
42+
var node = new Node(1,
43+
new Node(11, new Node(111)),
44+
new Node(12, new Node(121)));
45+
46+
var root = new Node(1,
47+
new Node(11, new Node(111)),
48+
node);
49+
50+
var cyclicalNode = new Node(1, root);
51+
root.Children[0].Children[0].Children = new[] { cyclicalNode };
52+
53+
root.PrintDump();
54+
root.ToSafeJson().Print();
55+
root.ToSafeJsv().Print();
56+
}
57+
58+
public class CustomExecption : Exception
59+
{
60+
public string[] CustomData { get; set; }
61+
}
62+
63+
[Test]
64+
public void Can_PrintDump_ToSafeJson_ToSafeJsv_Exception()
65+
{
66+
try
67+
{
68+
throw new ArgumentException("param",
69+
new CustomExecption
70+
{
71+
CustomData = new[] { "A", "B", "C"}
72+
});
73+
}
74+
catch (Exception ex)
75+
{
76+
ex.PrintDump();
77+
ex.ToSafeJson().Print();
78+
ex.ToSafeJsv().Print();
79+
}
80+
}
81+
}
82+
}

tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@
186186
<ItemGroup>
187187
<Compile Include="AutoMappingObjectDictionaryTests.cs" />
188188
<Compile Include="CustomCultureInfoTests.cs" />
189+
<Compile Include="DumpTests.cs" />
189190
<Compile Include="DynamicModels\ODataTests.cs" />
190191
<Compile Include="EnumerableTests.cs" />
191192
<Compile Include="AttributeTests.cs" />

0 commit comments

Comments
 (0)