Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling serialization customization through CosmosSerializerOptions #650

Merged
merged 29 commits into from
Aug 10, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e25d205
Made JsonSerializerSettings internal. Exposed a new constructor with …
Aug 7, 2019
2adf438
Fixed contract UT and updated changelog
Aug 7, 2019
82f1b4a
Made CosmosJsonDotNetSerializer internal. Added CosmosSerializerOptio…
Aug 7, 2019
8077e8e
Fixed naming
Aug 7, 2019
87b9993
Updating file name and changelog
Aug 7, 2019
885b5dc
Merge remote-tracking branch 'origin/master' into users/jawilley/seri…
Aug 7, 2019
e121a93
Update contract test
Aug 7, 2019
530b883
Fixed spacing
Aug 7, 2019
52dd1c7
Fixing contract enforcement test
Aug 8, 2019
18e50c2
Updated naming
Aug 8, 2019
b3ef7a4
Additional contract checks
Aug 8, 2019
7413ec0
Merge to latest
Aug 8, 2019
b8697d0
Adding additional info to ValidateAsync test to understand transient …
Aug 8, 2019
d9cc7fd
Updating contract from VS2017
Aug 8, 2019
c220603
Fixed contract test for VS2019 to skip is IsReadOnlyAttribute
Aug 8, 2019
e17ba0b
Update Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializerOptions.cs
j82w Aug 8, 2019
a90ff94
Update Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializerOptions.cs
j82w Aug 8, 2019
fd468ef
Update Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializerOptions.cs
j82w Aug 8, 2019
e176b82
Renamed CosmosSerializerOptions to CosmosSerializationOptions
Aug 8, 2019
3cdb52b
Merge to latest
Aug 8, 2019
5de82a6
Added more comments
Aug 8, 2019
febd523
Reverting DocumentClientWithUriParameters changes
Aug 9, 2019
c43f7a8
Revert "Reverting DocumentClientWithUriParameters changes"
Aug 9, 2019
3151f60
Reverting accidental changes
Aug 9, 2019
c7ac8db
Adding comment examples, Converted options to be nullable
Aug 9, 2019
9a340fd
Updated contract test
Aug 9, 2019
fa1c98c
Fixed Unit test
Aug 9, 2019
02185ef
Converted to a class with default values and updated changelog
Aug 9, 2019
5fa508e
Updated contract api
Aug 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Microsoft.Azure.Cosmos/src/CosmosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ protected CosmosClient()
/// <seealso href="https://docs.microsoft.com/azure/cosmos-db/troubleshoot-dot-net-sdk"/>
/// </remarks>
public CosmosClient(
string connectionString,
string connectionString,
j82w marked this conversation as resolved.
Show resolved Hide resolved
CosmosClientOptions clientOptions = null)
: this(
CosmosClientOptions.GetAccountEndpoint(connectionString),
Expand Down Expand Up @@ -612,18 +612,19 @@ internal void Init(

this.RequestHandler = clientPipelineBuilder.Build();

CosmosSerializer userSerializer = this.ClientOptions.GetCosmosSerializerWithWrapperOrDefault();
this.ResponseFactory = new CosmosResponseFactory(
defaultJsonSerializer: this.ClientOptions.PropertiesSerializer,
userJsonSerializer: this.ClientOptions.CosmosSerializerWithWrapperOrDefault);
userJsonSerializer: userSerializer);

CosmosSerializer sqlQuerySpecSerializer = CosmosSqlQuerySpecJsonConverter.CreateSqlQuerySpecSerializer(
this.ClientOptions.CosmosSerializerWithWrapperOrDefault,
this.ClientOptions.PropertiesSerializer);
cosmosSerializer: userSerializer,
propertiesSerializer: this.ClientOptions.PropertiesSerializer);

this.ClientContext = new ClientContextCore(
client: this,
clientOptions: this.ClientOptions,
userJsonSerializer: this.ClientOptions.CosmosSerializerWithWrapperOrDefault,
userJsonSerializer: userSerializer,
defaultJsonSerializer: this.ClientOptions.PropertiesSerializer,
sqlQuerySpecSerializer: sqlQuerySpecSerializer,
cosmosResponseFactory: this.ResponseFactory,
Expand All @@ -632,7 +633,7 @@ internal void Init(
documentQueryClient: new DocumentQueryClient(this.DocumentClient));
}

internal async virtual Task<ConsistencyLevel> GetAccountConsistencyLevelAsync()
internal virtual async Task<ConsistencyLevel> GetAccountConsistencyLevelAsync()
{
if (!this.accountConsistencyLevel.HasValue)
{
Expand Down
73 changes: 54 additions & 19 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
Expand Down Expand Up @@ -39,11 +38,12 @@ public class CosmosClientOptions
/// </summary>
private static readonly CosmosSerializer propertiesSerializer = new CosmosJsonSerializerWrapper(new CosmosJsonDotNetSerializer());

private readonly Collection<RequestHandler> customHandlers;
private readonly string currentEnvironmentInformation;

private int gatewayModeMaxConnectionLimit;
private string applicationName;
private CosmosSerializerOptions? serializerOptions;
private CosmosSerializer serializer;

/// <summary>
/// Creates a new CosmosClientOptions
Expand All @@ -59,7 +59,7 @@ public CosmosClientOptions()
this.ConnectionMode = CosmosClientOptions.DefaultConnectionMode;
this.ConnectionProtocol = CosmosClientOptions.DefaultProtocol;
this.ApiType = CosmosClientOptions.DefaultApiType;
this.customHandlers = new Collection<RequestHandler>();
this.CustomHandlers = new Collection<RequestHandler>();
j82w marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand Down Expand Up @@ -132,10 +132,7 @@ public int GatewayModeMaxConnectionLimit
/// </summary>
/// <seealso cref="CosmosClientBuilder.AddCustomHandlers(RequestHandler[])"/>
[JsonConverter(typeof(ClientOptionJsonConverter))]
public Collection<RequestHandler> CustomHandlers
{
get => this.customHandlers;
}
public Collection<RequestHandler> CustomHandlers { get; }

/// <summary>
/// Get or set the connection mode used by the client when connecting to the Azure Cosmos DB service.
Expand All @@ -157,7 +154,7 @@ public Collection<RequestHandler> CustomHandlers
public ConsistencyLevel? ConsistencyLevel { get; set; }

/// <summary>
/// Get ot set the number of times client should retry on rate throttled requests.
/// Get or set the number of times client should retry on rate throttled requests.
/// </summary>
/// <seealso cref="CosmosClientBuilder.WithThrottlingRetryOptions(TimeSpan, int)"/>
public int? MaxRetryAttemptsOnRateLimitedRequests { get; set; }
Expand All @@ -171,6 +168,24 @@ public Collection<RequestHandler> CustomHandlers
/// <seealso cref="CosmosClientBuilder.WithThrottlingRetryOptions(TimeSpan, int)"/>
public TimeSpan? MaxRetryWaitTimeOnRateLimitedRequests { get; set; }

/// <summary>
/// Get to set an optional serializer options.
j82w marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public CosmosSerializerOptions? SerializerOptions
kirankumarkolli marked this conversation as resolved.
Show resolved Hide resolved
{
get => this.serializerOptions;
set
{
if (this.Serializer != null)
{
throw new ArgumentException(
$"{nameof(this.SerializerOptions)} is not compatible with {nameof(this.Serializer)}. Only one can be set. ");
}

this.serializerOptions = value;
}
}

/// <summary>
/// Get to set an optional JSON serializer. The client will use it to serialize or de-serialize user's cosmos request/responses.
/// SDK owned types such as DatabaseProperties and ContainerProperties will always use the SDK default serializer.
Expand All @@ -182,10 +197,7 @@ public Collection<RequestHandler> CustomHandlers
/// <example>
/// // An example on how to configure the serializer to ignore null values
/// CosmosSerializer ignoreNullSerializer = new CosmosJsonDotNetSerializer(
/// new JsonSerializerSettings()
/// {
/// NullValueHandling = NullValueHandling.Ignore
/// });
/// NullValueHandling = NullValueHandling.Ignore);
///
/// CosmosClientOptions clientOptions = new CosmosClientOptions()
/// {
Expand All @@ -195,7 +207,20 @@ public Collection<RequestHandler> CustomHandlers
/// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions);
/// </example>
[JsonConverter(typeof(ClientOptionJsonConverter))]
public CosmosSerializer Serializer { get; set; }
public CosmosSerializer Serializer
{
get => this.serializer;
set
{
if (this.SerializerOptions != null)
{
throw new ArgumentException(
$"{nameof(this.Serializer)} is not compatible with {nameof(this.SerializerOptions)}. Only one can be set. ");
}

this.serializer = value;
}
}

/// <summary>
/// A JSON serializer used by the CosmosClient to serialize or de-serialize cosmos request/responses.
Expand All @@ -205,12 +230,6 @@ public Collection<RequestHandler> CustomHandlers
[JsonConverter(typeof(ClientOptionJsonConverter))]
internal CosmosSerializer PropertiesSerializer => CosmosClientOptions.propertiesSerializer;

/// <summary>
/// Gets the user json serializer with the CosmosJsonSerializerWrapper or the default
/// </summary>
[JsonIgnore]
internal CosmosSerializer CosmosSerializerWithWrapperOrDefault => this.Serializer == null ? this.PropertiesSerializer : new CosmosJsonSerializerWrapper(this.Serializer);

/// <summary>
/// Gets or sets the connection protocol when connecting to the Azure Cosmos service.
/// </summary>
Expand Down Expand Up @@ -309,6 +328,22 @@ public Collection<RequestHandler> CustomHandlers
/// </summary>
internal bool? EnableCpuMonitor { get; set; }

/// <summary>
/// Gets the user json serializer with the CosmosJsonSerializerWrapper or the default
/// </summary>
internal CosmosSerializer GetCosmosSerializerWithWrapperOrDefault()
{
if (this.SerializerOptions != null)
{
CosmosJsonDotNetSerializer cosmosJsonDotNetSerializer = new CosmosJsonDotNetSerializer(this.SerializerOptions.Value);
return new CosmosJsonSerializerWrapper(cosmosJsonDotNetSerializer);
}
else
{
return this.Serializer == null ? this.PropertiesSerializer : new CosmosJsonSerializerWrapper(this.Serializer);
}
}

internal CosmosClientOptions Clone()
{
CosmosClientOptions cloneConfiguration = (CosmosClientOptions)this.MemberwiseClone();
Expand Down
24 changes: 11 additions & 13 deletions Microsoft.Azure.Cosmos/src/DocumentClientWithUriParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ namespace Microsoft.Azure.Cosmos
{
using System;
using System.Globalization;
using System.IO;
j82w marked this conversation as resolved.
Show resolved Hide resolved
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Linq;
using Microsoft.Azure.Cosmos.Scripts;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;

Expand Down Expand Up @@ -64,7 +62,7 @@ public Task<ResourceResponse<DocumentCollection>> CreateDocumentCollectionAsync(
/// <returns>The <see cref="DocumentCollection"/> that was created contained within a <see cref="System.Threading.Tasks.Task"/> object representing the service response for the asynchronous operation.</returns>
public Task<ResourceResponse<DocumentCollection>> CreateDocumentCollectionIfNotExistsAsync(Uri databaseUri, DocumentCollection documentCollection, Documents.Client.RequestOptions options = null)
{
return TaskHelper.InlineIfPossible(() => CreateDocumentCollectionIfNotExistsPrivateAsync(databaseUri, documentCollection, options), null);
return TaskHelper.InlineIfPossible(() => this.CreateDocumentCollectionIfNotExistsPrivateAsync(databaseUri, documentCollection, options), null);
}

private async Task<ResourceResponse<DocumentCollection>> CreateDocumentCollectionIfNotExistsPrivateAsync(
Expand All @@ -74,7 +72,7 @@ private async Task<ResourceResponse<DocumentCollection>> CreateDocumentCollectio
{
throw new ArgumentNullException("databaseUri");
}

if (documentCollection == null)
{
throw new ArgumentNullException("documentCollection");
Expand All @@ -84,7 +82,7 @@ private async Task<ResourceResponse<DocumentCollection>> CreateDocumentCollectio
databaseUri.OriginalString, Paths.CollectionsPathSegment, Uri.EscapeUriString(documentCollection.Id)), UriKind.Relative);

try
{
{
return await this.ReadDocumentCollectionAsync(documentCollectionUri, options);
}
catch (DocumentClientException dce)
Expand Down Expand Up @@ -403,9 +401,9 @@ public Task<ResourceResponse<DocumentCollection>> ReplaceDocumentCollectionAsync

IDocumentClientRetryPolicy retryPolicyInstance = this.ResetSessionTokenRetryPolicy.GetRequestPolicy();
return TaskHelper.InlineIfPossible(() => this.ReplaceDocumentCollectionPrivateAsync(
documentCollection,
options,
retryPolicyInstance,
documentCollection,
options,
retryPolicyInstance,
documentCollectionUri.OriginalString), retryPolicyInstance);
}

Expand All @@ -425,11 +423,11 @@ public Task<ResourceResponse<StoredProcedure>> ReplaceStoredProcedureAsync(Uri s

IDocumentClientRetryPolicy retryPolicyInstance = this.ResetSessionTokenRetryPolicy.GetRequestPolicy();
return TaskHelper.InlineIfPossible(() => this.ReplaceStoredProcedurePrivateAsync(
storedProcedure,
options,
storedProcedure,
options,
retryPolicyInstance,
storedProcedureUri.OriginalString), retryPolicyInstance);
}
}

/// <summary>
/// Replaces a trigger as an asynchronous operation in the Azure Cosmos DB service.
Expand Down Expand Up @@ -465,7 +463,7 @@ public Task<ResourceResponse<UserDefinedFunction>> ReplaceUserDefinedFunctionAsy

IDocumentClientRetryPolicy retryPolicyInstance = this.ResetSessionTokenRetryPolicy.GetRequestPolicy();
return TaskHelper.InlineIfPossible(() => this.ReplaceUserDefinedFunctionPrivateAsync(function, options, retryPolicyInstance, userDefinedFunctionUri.OriginalString), retryPolicyInstance);
}
}

/// <summary>
/// Replaces a user defined type as an asynchronous operation in the Azure Cosmos DB service.
Expand Down Expand Up @@ -1234,7 +1232,7 @@ internal Task<DocumentFeedResponse<Schema>> ReadSchemaFeedAsync(Uri schemasUri,
/// <param name="databaseUri">the URI to the database.</param>
/// <param name="feedOptions">The options for processing the query results feed.</param>
/// <returns>The query result set.</returns>
public IOrderedQueryable<DocumentCollection> CreateDocumentCollectionQuery(Uri databaseUri, FeedOptions feedOptions = null)
public IOrderedQueryable<DocumentCollection> CreateDocumentCollectionQuery(Uri databaseUri, FeedOptions feedOptions = null)
j82w marked this conversation as resolved.
Show resolved Hide resolved
{
if (databaseUri == null)
{
Expand Down
14 changes: 14 additions & 0 deletions Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,20 @@ public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeO
return this;
}

/// <summary>
/// Set a custom serializer option.
/// </summary>
/// <param name="cosmosSerializerOptions">The custom class that implements <see cref="CosmosSerializer"/> </param>
/// <returns>The <see cref="CosmosClientBuilder"/> object</returns>
/// <seealso cref="CosmosSerializer"/>
/// <seealso cref="CosmosClientOptions.SerializerOptions"/>
public CosmosClientBuilder WithSerializerOptions(
CosmosSerializerOptions cosmosSerializerOptions)
j82w marked this conversation as resolved.
Show resolved Hide resolved
{
this.clientOptions.SerializerOptions = cosmosSerializerOptions;
return this;
}

/// <summary>
/// Set a custom JSON serializer.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,62 @@ namespace Microsoft.Azure.Cosmos
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

/// <summary>
/// The default Cosmos JSON.NET serializer
/// The default Cosmos JSON.NET serializer.
/// </summary>
public sealed class CosmosJsonDotNetSerializer : CosmosSerializer
internal sealed class CosmosJsonDotNetSerializer : CosmosSerializer
{
private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);
private readonly JsonSerializer Serializer;

/// <summary>
/// Create a serializer that uses the JSON.net serializer
/// </summary>
/// <param name="jsonSerializerSettings">Optional serializer settings</param>
public CosmosJsonDotNetSerializer(JsonSerializerSettings jsonSerializerSettings = null)
/// <remarks>
/// This is internal to reduce exposure of JSON.net types so
/// it is easier to convert to System.Text.Json
/// </remarks>
internal CosmosJsonDotNetSerializer()
{
if (jsonSerializerSettings == null)
this.Serializer = JsonSerializer.Create();
}

/// <summary>
/// Create a serializer that uses the JSON.net serializer
/// </summary>
/// <remarks>
/// This is internal to reduce exposure of JSON.net types so
/// it is easier to convert to System.Text.Json
/// </remarks>
internal CosmosJsonDotNetSerializer(
CosmosSerializerOptions cosmosSerializerOptions)
{
JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings()
{
jsonSerializerSettings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Include
};
}
NullValueHandling = cosmosSerializerOptions.IgnoreNullValues ? NullValueHandling.Ignore : NullValueHandling.Include,
Formatting = cosmosSerializerOptions.Indented ? Formatting.Indented : Formatting.None,
ContractResolver = cosmosSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase
? new CamelCasePropertyNamesContractResolver() : null,
j82w marked this conversation as resolved.
Show resolved Hide resolved
};

this.Serializer = JsonSerializer.Create(jsonSerializerSettings);
}

/// <summary>
/// Create a serializer that uses the JSON.net serializer
/// </summary>
/// <remarks>
/// This is internal to reduce exposure of JSON.net types so
/// it is easier to convert to System.Text.Json
/// </remarks>
internal CosmosJsonDotNetSerializer(
j82w marked this conversation as resolved.
Show resolved Hide resolved
JsonSerializerSettings jsonSerializerSettings)
{
this.Serializer = JsonSerializer.Create(jsonSerializerSettings);
}

/// <summary>
/// Convert a Stream to the passed in type.
/// </summary>
Expand All @@ -45,7 +75,7 @@ public override T FromStream<T>(Stream stream)
{
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)(stream);
return (T)(object)stream;
}

using (StreamReader sr = new StreamReader(stream))
Expand Down
Loading