-
Notifications
You must be signed in to change notification settings - Fork 351
/
Copy pathODataMessageWriterSettings.cs
574 lines (506 loc) · 25.7 KB
/
ODataMessageWriterSettings.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
//---------------------------------------------------------------------
// <copyright file="ODataMessageWriterSettings.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------
using Microsoft.OData.Evaluation;
namespace Microsoft.OData
{
#region Namespaces
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData.Buffers;
using Microsoft.OData.UriParser;
#endregion Namespaces
/// <summary>
/// Configuration settings for OData message writers.
/// </summary>
public sealed class ODataMessageWriterSettings
{
/// <summary>
/// The acceptable charsets used to the determine the encoding of the message.
/// This is a comma separated list of charsets as specified in RFC 2616, Section 14.2
/// </summary>
private string acceptCharSets;
/// <summary>
/// The acceptable media types used to determine the content type of the message.
/// This is a comma separated list of content types as specified in RFC 2616, Section 14.1
/// </summary>
private string acceptMediaTypes;
/// <summary>
/// The base uri used in payload.
/// </summary>
private Uri baseUri;
/// <summary>
/// The format to use when writing the payload; this replaces the 'AcceptHeader' and 'AcceptCharSetHeader'
/// fields and uses the default values for the respective format. If null is specified
/// the default format and the default media type will be picked depending on the writer these settings are used with.
/// </summary>
private ODataFormat format;
/// <summary>Quotas to use for limiting resource consumption when writing an OData message.</summary>
private ODataMessageQuotas messageQuotas;
/// <summary>
/// The parse result of request Uri
/// </summary>
private ODataUri odataUri;
/// <summary>
/// true if the Format property should be used to compute the media type;
/// false if AcceptableMediaTypes and AcceptableCharsets should be used.
/// null if neither the format nor the acceptable media types/charsets have been set.
/// </summary>
private bool? useFormat;
/// <summary>
/// Validation settings.
/// </summary>
private ValidationKinds validations;
/// <summary>
/// Default setting for writing control information without the 'odata' prefix.
/// </summary>
private bool enableWritingODataAnnotationWithoutPrefix;
/// <summary>
/// OData 4.0-specific setting for writing control information without the 'odata' prefix.
/// </summary>
private bool omitODataPrefix40 = false;
/// <summary>
/// OData 4.01 and greater setting for writing control information without the 'odata' prefix.
/// </summary>
private bool omitODataPrefix = true;
/// <summary>Initializes a new instance of the <see cref="Microsoft.OData.ODataMessageWriterSettings" /> class with default settings.</summary>
public ODataMessageWriterSettings()
: this(null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Microsoft.OData.ODataMessageWriterSettings" /> class with default settings for the
/// specified OData version.
/// </summary>
/// <param name="version">OData version for which to create default settings.</param>
public ODataMessageWriterSettings(ODataVersion? version)
{
this.EnableMessageStreamDisposal = true;
this.EnableCharactersCheck = false;
this.Validations = ValidationKinds.All;
this.Validator = new WriterValidator(this);
this.LibraryCompatibility = ODataLibraryCompatibility.None;
this.MultipartNewLine = "\r\n";
this.AlwaysAddTypeAnnotationsForDerivedTypes = false;
this.BufferSize = ODataConstants.DefaultOutputBufferSize;
this.EnableWritingKeyAsSegment = false;
this.Version = version;
if (version == null || version < ODataVersion.V401)
{
this.enableWritingODataAnnotationWithoutPrefix = this.omitODataPrefix40;
}
else
{
this.enableWritingODataAnnotationWithoutPrefix = this.omitODataPrefix;
}
}
/// <summary>
/// Gets or sets library compatibility version. Default value is <see cref="Microsoft.OData.ODataLibraryCompatibility.None"/>,
/// </summary>
public ODataLibraryCompatibility LibraryCompatibility { get; set; }
/// <summary>
/// Gets or sets validations to perform. Default value is <see cref="Microsoft.OData.ValidationKinds.All"/>,
/// </summary>
public ValidationKinds Validations
{
get
{
return validations;
}
set
{
validations = value;
ThrowIfTypeConflictsWithMetadata = (validations & ValidationKinds.ThrowIfTypeConflictsWithMetadata) != 0;
ThrowOnDuplicatePropertyNames = (validations & ValidationKinds.ThrowOnDuplicatePropertyNames) != 0;
ThrowOnUndeclaredPropertyForNonOpenType = (validations & ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType) != 0;
}
}
/// <summary>Gets or sets the document base URI which is used as base for all relative URIs. </summary>
/// <returns>The document base URI which is used as base for all relative URIs.</returns>
/// <remarks>
/// Base URI is context URI; if the URI does not end with a slash, a slash would be appended automatically.
/// </remarks>
public Uri BaseUri
{
get
{
return this.baseUri;
}
set
{
this.baseUri = UriUtils.EnsureTaillingSlash(value);
}
}
/// <summary>Gets or sets a value that indicates whether the message stream will be disposed after finishing writing with the message.</summary>
/// <returns>true if the message stream will be disposed after finishing writing with the message; otherwise false. The default value is true.</returns>
public bool EnableMessageStreamDisposal { get; set; }
/// <summary>
/// Flag to control whether the writer should check for valid Xml characters or not.
/// </summary>
public bool EnableCharactersCheck { get; set; }
/// <summary>Gets or sets a callback function use to wrap the response from server.</summary>
/// <returns>The callback function used to wrap the response from server.</returns>
/// <remarks>If it has a value and we are writing a JSON response, then we will wrap the entirety of the response in
/// the provided function name and parenthesis for JSONP. Otherwise this value is ignored.</remarks>
[Obsolete("This will be dropped in the 9.x release.")]
public string JsonPCallback { get; set; }
/// <summary>
/// Get/sets the character buffer pool.
/// </summary>
public ICharArrayPool ArrayPool { get; set; }
/// <summary>
/// Gets or sets the size of the buffer used to buffer writes to the output
/// stream. Note that this is a hint and may be disregarded where deemed fit.
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Quotas to use for limiting resource consumption when writing an OData message.
/// </summary>
public ODataMessageQuotas MessageQuotas
{
get
{
if (this.messageQuotas == null)
{
this.messageQuotas = new ODataMessageQuotas();
}
return this.messageQuotas;
}
set
{
this.messageQuotas = value;
}
}
/// <summary>
/// The OData Uri of an incoming request. Call <see cref="ODataUriParser"/>'s methods,
/// and assign properties (e.g., <see cref="ODataPath"/>) to <see cref="ODataUri"/>.
/// </summary>
public ODataUri ODataUri
{
get { return this.odataUri ?? (this.odataUri = new ODataUri()); }
set { this.odataUri = value; }
}
/// <summary>Gets or sets the OData protocol version to be used for writing payloads. </summary>
/// <returns>The OData protocol version to be used for writing payloads.</returns>
public ODataVersion? Version { get; set; }
/// <summary>
/// Informs the metadata builder which properties, functions, actions, links to omit.
/// </summary>
public ODataMetadataSelector MetadataSelector { get; set; }
/// <summary>
/// Gets or sets the new line character sequence used when writing multipart messages
/// see https://tools.ietf.org/html/rfc2046#section-5.1.1
/// A TextWriter uses OS specific newline but rfc2046 requires it to be CRLF.
/// </summary>
public string MultipartNewLine { get; set; }
/// <summary>
/// When set, type annotations will be added for derived types, even when the metadata level is set to "None".
/// </summary>
public bool AlwaysAddTypeAnnotationsForDerivedTypes { get; set; }
/// <summary>
/// Func to evaluate whether an annotation should be written by the writer. This is useful when you want to force the writer
/// to write annotations that would have otherwise been skipped (e.g. writing annotations that are not part of the odata.include-annotations filter).
/// </summary>
/// <remarks>
/// Note that this returning false does not guarantee that the annotation will not be written. For example, if an annotation was included in the preference
/// header annotations filter, it may still be written even if this func returns false.
/// </remarks>
public Func<string, bool> ShouldIncludeAnnotation { get; set; }
/// <summary>
/// Gets the validator corresponding to the validation settings.
/// </summary>
internal IWriterValidator Validator { get; private set; }
/// <summary>
/// Returns whether ThrowIfTypeConflictsWithMetadata validation should be performed.
/// </summary>
internal bool ThrowIfTypeConflictsWithMetadata { get; private set; }
/// <summary>
/// Returns whether ThrowOnDuplicatePropertyNames validation setting is enabled.
/// </summary>
internal bool ThrowOnDuplicatePropertyNames { get; private set; }
/// <summary>
/// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled.
/// </summary>
internal bool ThrowOnUndeclaredPropertyForNonOpenType { get; private set; }
/// <summary>
/// The acceptable media types used to determine the content type of the message.
/// This is a comma separated list of content types as specified in RFC 2616, Section 14.1
/// </summary>
/// <remarks>A null or empty accept header means that all content types are acceptable.</remarks>
/// <remarks>For response messages this is usually the 'Accept' header of the request message.</remarks>
internal string AcceptableMediaTypes
{
get
{
return this.acceptMediaTypes;
}
}
/// <summary>
/// isIEEE754Compatible is used to determine if the message follows IEEE 754 standard.
/// If this is set to true then long and decimals will be serialized as strings.
/// </summary>
internal bool IsIeee754Compatible { get; set; }
/// <summary>
/// The acceptable charsets used to the determine the encoding of the message.
/// This is a comma separated list of charsets as specified in RFC 2616, Section 14.2
/// </summary>
/// <remarks>A null or empty accept charset header means that all charsets are acceptable.</remarks>
/// <remarks>For response messages this is usually the 'Accept-Charset' header of the request message.</remarks>
internal string AcceptableCharsets
{
get
{
return this.acceptCharSets;
}
}
/// <summary>
/// The format to use when writing the payload; this replaces the 'AcceptHeader' and 'AcceptCharSetHeader'
/// properties and uses the default values for the respective format. If null is specified
/// the default format and the default media type will be picked depending on the writer these settings are used with.
/// </summary>
internal ODataFormat Format
{
get
{
return this.format;
}
}
/// <summary>
/// Gets the value indicating whether the payload represents an individual property
/// </summary>
internal bool IsIndividualProperty
{
get
{
return this.ODataUri.Path != null && this.ODataUri.Path.IsIndividualProperty();
}
}
/// <summary>
/// Gets the metadata document URI that has been set on the settings, or null if it has not been set.
/// </summary>
internal Uri MetadataDocumentUri
{
get
{
return this.ODataUri.MetadataDocumentUri;
}
}
/// <summary>
/// true if the Format property should be used to compute the media type;
/// false if AcceptableMediaTypes and AcceptableCharsets should be used.
/// null if neither the format nor the acceptable media types/charsets have been set.
/// </summary>
internal bool? UseFormat
{
get
{
return this.useFormat;
}
}
/// <summary>
/// Gets the SelectExpand clause used when generating metadata links.
/// </summary>
internal SelectExpandClause SelectExpandClause
{
get
{
return this.ODataUri.SelectAndExpand;
}
}
/// <summary>
/// Gets the SelectedPropertiesNode clause generated from SelectExpandClause.
/// </summary>
internal SelectedPropertiesNode SelectedProperties
{
get
{
return this.SelectExpandClause != null
? SelectedPropertiesNode.Create(this.SelectExpandClause)
: new SelectedPropertiesNode(SelectedPropertiesNode.SelectionType.EntireSubtree);
}
}
/// <summary>
/// Func to evaluate whether an annotation should be written by the writer based on the annotations filtered in the odata.include-annotations header and OData standard.
/// The func should return true if the annotation should
/// be written and false if the annotation should be skipped.
/// </summary>
/// <remarks>
/// This property is internal and automatically set based on annotation filters. The developer can use the <see cref="ShouldIncludeAnnotation"/> property
/// to tell the writer to write annotations even if they were not included in the annotations filters.
/// </remarks>
internal Func<string, bool> ShouldIncludeAnnotationInternal { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether the writer should put key values in their own URI segment when automatically building URIs.
/// If this value is false, automatically-generated URLs will take the form "../EntitySet('KeyValue')/..".
/// If this value is true, automatically-generated URLs will take the form "../EntitySet/KeyValue/..".
/// This setting only applies to URLs that are automatically generated by the <see cref="ODataMessageWriter" /> and does not modify URLs explicitly provided by the user.
/// </summary>
public bool EnableWritingKeyAsSegment { get; set; }
/// <summary>
/// Get whether to write OData control information without the 'odata' prefix.
/// The default value is false for OData 4.0 and true for OData 4.01.
/// </summary>
/// <returns>true if control information should be written with the 'odata' prefix, otherwise false.</returns>
public bool GetOmitODataPrefix()
{
return this.enableWritingODataAnnotationWithoutPrefix;
}
/// <summary>
/// Returns a value indicating whether control information should be written with the 'odata' prefix for the specified OData version.
/// The default value is false for OData 4.0 and true for OData 4.01.
/// </summary>
/// <param name="version">The OData version.</param>
/// <returns>true if control information should be written with the 'odata' prefix, otherwise false</returns>
public bool GetOmitODataPrefix(ODataVersion version)
{
if (version >= ODataVersion.V401)
{
return this.omitODataPrefix;
}
return this.omitODataPrefix40;
}
/// <summary>
/// Sets a value indicating whether control information should be written with or without the 'odata' prefix.
/// </summary>
/// <remarks>
/// This method updates the setting for both OData version 4.0 and 4.01. If you want to target a specific version instead,
/// use the <see cref="SetOmitODataPrefix(bool, ODataVersion)"/> overload.
/// </remarks>
/// <param name="value">true to write control information with the 'odata' prefix, otherwise false.</param>
public void SetOmitODataPrefix(bool value)
{
this.enableWritingODataAnnotationWithoutPrefix =
this.omitODataPrefix =
this.omitODataPrefix40 = value;
}
/// <summary>
/// Sets a value indicating whether control information should be written with or without the 'odata' prefix for the specified OData version
/// </summary>
/// <param name="value">true to write control information with the 'odata' prefix, otherwise false.</param>
/// <param name="version">The OData version for which to set the omit prefix behavior.</param>
public void SetOmitODataPrefix(bool value, ODataVersion version)
{
if (version == ODataVersion.V4)
{
this.omitODataPrefix40 = value;
}
else
{
this.omitODataPrefix = value;
}
}
/// <summary>
/// Creates a shallow copy of this <see cref="ODataMessageWriterSettings"/>.
/// </summary>
/// <returns>A shallow copy of this <see cref="ODataMessageWriterSettings"/>.</returns>
public ODataMessageWriterSettings Clone()
{
var copy = new ODataMessageWriterSettings();
copy.CopyFrom(this);
return copy;
}
/// <summary>Sets the acceptable media types and character sets from which the content type will be computed when writing the payload.</summary>
/// <param name="acceptableMediaTypes">The acceptable media types used to determine the content type of the message. This is a comma separated list of content types as specified in RFC 2616, Section 14.1.</param>
/// <param name="acceptableCharSets"> The acceptable charsets to use to determine the encoding of the message. This is a comma separated list of charsets as specified in RFC 2616, Section 14.2 </param>
/// <remarks>Calling this method replaces any previously set content-type settings.</remarks>
public void SetContentType(string acceptableMediaTypes, string acceptableCharSets)
{
// Should accept json as application/json
this.acceptMediaTypes = string.Equals(acceptableMediaTypes, MimeConstants.MimeJsonSubType, StringComparison.OrdinalIgnoreCase) ? MimeConstants.MimeApplicationJson : acceptableMediaTypes;
this.acceptCharSets = acceptableCharSets;
this.format = null;
this.useFormat = false;
}
/// <summary>Sets the format to be used when writing the payload. This will automatically set a compatible content type header.</summary>
/// <param name="payloadFormat">The format to use for writing the payload.</param>
/// <remarks>Calling this method replaces any previously set content-type settings.</remarks>
public void SetContentType(ODataFormat payloadFormat)
{
this.acceptCharSets = null;
this.acceptMediaTypes = null;
this.format = payloadFormat;
this.useFormat = true;
}
internal static ODataMessageWriterSettings CreateWriterSettings(
IServiceProvider container,
ODataMessageWriterSettings other)
{
ODataMessageWriterSettings writerSettings;
if (container == null)
{
writerSettings = new ODataMessageWriterSettings();
}
else
{
writerSettings = container.GetRequiredService<ODataMessageWriterSettings>();
}
if (other != null)
{
writerSettings.CopyFrom(other);
}
return writerSettings;
}
/// <summary>Sets the URI of the metadata document.</summary>
/// <param name="serviceDocumentUri">The URI of the service document.</param>
internal void SetServiceDocumentUri(Uri serviceDocumentUri)
{
this.ODataUri.ServiceRoot = serviceDocumentUri;
}
/// <summary>
/// Determines if there is a JSON padding function defined.
/// </summary>
/// <returns>True if the JsonPCallback property is not null or empty.</returns>
internal bool HasJsonPaddingFunction()
{
return !string.IsNullOrEmpty(this.JsonPCallback);
}
/// <summary>
/// Returns true to indicate that the annotation with the name <paramref name="annotationName"/> should not be written, false otherwise.
/// </summary>
/// <param name="annotationName">The name of the annotation in question.</param>
/// <returns>Returns true to indicate that the annotation with the name <paramref name="annotationName"/> should not be written, false otherwise.</returns>
internal bool ShouldSkipAnnotation(string annotationName)
{
bool skipAnnotation = this.ShouldIncludeAnnotationInternal == null || !this.ShouldIncludeAnnotationInternal(annotationName);
// if annotation is not included by default, the caller ensure it's written using ShouldIncludeAnnotation
if (skipAnnotation && this.ShouldIncludeAnnotation != null)
{
return !this.ShouldIncludeAnnotation(annotationName);
}
return skipAnnotation;
}
private void CopyFrom(ODataMessageWriterSettings other)
{
ExceptionUtils.CheckArgumentNotNull(other, "other");
this.acceptCharSets = other.acceptCharSets;
this.acceptMediaTypes = other.acceptMediaTypes;
this.BaseUri = other.BaseUri;
this.EnableMessageStreamDisposal = other.EnableMessageStreamDisposal;
this.EnableCharactersCheck = other.EnableCharactersCheck;
this.format = other.format;
this.JsonPCallback = other.JsonPCallback;
this.messageQuotas = new ODataMessageQuotas(other.MessageQuotas);
this.ODataUri = other.ODataUri.Clone();
this.ShouldIncludeAnnotationInternal = other.ShouldIncludeAnnotationInternal;
this.ShouldIncludeAnnotation = other.ShouldIncludeAnnotation;
this.useFormat = other.useFormat;
this.Version = other.Version;
this.LibraryCompatibility = other.LibraryCompatibility;
this.AlwaysAddTypeAnnotationsForDerivedTypes = other.AlwaysAddTypeAnnotationsForDerivedTypes;
this.MetadataSelector = other.MetadataSelector;
this.IsIeee754Compatible = other.IsIeee754Compatible;
this.validations = other.validations;
this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata;
this.ThrowOnDuplicatePropertyNames = other.ThrowOnDuplicatePropertyNames;
this.ThrowOnUndeclaredPropertyForNonOpenType = other.ThrowOnUndeclaredPropertyForNonOpenType;
this.ArrayPool = other.ArrayPool;
this.BufferSize = other.BufferSize;
this.EnableWritingKeyAsSegment = other.EnableWritingKeyAsSegment;
this.enableWritingODataAnnotationWithoutPrefix = other.enableWritingODataAnnotationWithoutPrefix;
this.omitODataPrefix40 = other.omitODataPrefix40;
this.omitODataPrefix = other.omitODataPrefix;
}
}
}