public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/admin/databases?name={_databaseName}"; url += "&replication-factor=" + _createDatabaseOperation._replicationFactor; var request = new HttpRequestMessage { Method = HttpMethod.Put, Content = new BlittableJsonContent(stream => { _context.Write(stream, _databaseDocument); }) }; return(request); }
// private static ConcurrentDictionary<string, Lazy<StreamWriter>> _writers = new ConcurrentDictionary<string, Lazy<StreamWriter>>(); private void Send(JsonOperationContext context, BlittableJsonReaderObject msg) { using (_disposerLock.EnsureNotDisposed()) using (var writer = new BlittableJsonTextWriter(context, _stream)) { /* var streamWriter = _writers.GetOrAdd(_dest, d => new Lazy<StreamWriter>(() => File.CreateText(d + ".log"))) * .Value; * lock (streamWriter) * { * streamWriter.WriteLine($"{DateTime.UtcNow:O} {_src} > {_dest}: - {msg}"); * streamWriter.Flush(); * } * //Console.WriteLine($"{DateTime.UtcNow:O} {_src} > {_dest}: - {msg}"); */ context.Write(writer, msg); } }
private void PrepareRequestWithMultipleCounters(StringBuilder pathBuilder, HttpRequestMessage request, JsonOperationContext ctx) { var uniqueNames = new HashSet <string>(_counters); // if it is too big, we drop to POST (note that means that we can't use the HTTP cache any longer) // we are fine with that, requests to load more than 1024 counters are going to be rare if (uniqueNames.Sum(x => x?.Length ?? 0) < 1024) { uniqueNames.ApplyIfNotNull(counter => pathBuilder.Append("&counter=").Append(Uri.EscapeDataString(counter ?? string.Empty))); } else { request.Method = HttpMethod.Post; var docOps = new DocumentCountersOperation { DocumentId = _docId, Operations = new List <CounterOperation>() }; foreach (var counter in uniqueNames) { docOps.Operations.Add(new CounterOperation { Type = CounterOperationType.Get, CounterName = counter }); } var batch = new CounterBatch { Documents = new List <DocumentCountersOperation> { docOps } }; request.Content = new BlittableJsonContent(stream => { var config = EntityToBlittable.ConvertCommandToBlittable(batch, ctx); ctx.Write(stream, config); }); } }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/databases/{node.Database}/subscriptions"; if (_id != null) { url += "?id=" + Uri.EscapeDataString(_id); } var request = new HttpRequestMessage { Method = HttpMethod.Put, Content = new BlittableJsonContent(stream => { ctx.Write(stream, DocumentConventions.Default.Serialization.DefaultConverter.ToBlittable(_options, ctx)); }) }; return(request); }
public override HttpRequestMessage CreateRequest(ServerNode node, out string url) { EnsureIsNotNullOrEmpty(Id, nameof(Id)); url = $"{node.Url}/databases/{node.Database}/docs?id={UrlEncode(Id)}"; IsReadRequest = false; var request = new HttpRequestMessage { Method = HttpMethod.Put, Content = new BlittableJsonContent(stream => { Context.Write(stream, Document); }), }; IsReadRequest = false; return(request); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/databases/{node.Database}{_url}"; var request = new HttpRequestMessage { Method = _method, Content = new BlittableJsonContent(stream => { if (_payload != null) { ctx.Write(stream, _payload); } }) }; return(request); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/admin/databases?name={_databaseName}"; url += "&replicationFactor=" + _replicationFactor; var databaseDocument = EntityToBlittable.ConvertCommandToBlittable(_databaseRecord, ctx); var request = new HttpRequestMessage { Method = HttpMethod.Put, Content = new BlittableJsonContent(stream => { ctx.Write(stream, databaseDocument); }) }; return(request); }
public static void WriteArray(this AbstractBlittableJsonTextWriter writer, string name, IEnumerable <DynamicJsonValue> items, JsonOperationContext context) { writer.WritePropertyName(name); writer.WriteStartArray(); var first = true; foreach (var item in items) { if (first == false) { writer.WriteComma(); } first = false; context.Write(writer, item); } writer.WriteEndArray(); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/admin/certificates/edit"; var definition = new CertificateDefinition { Thumbprint = _thumbprint, Permissions = _permissions, SecurityClearance = _clearance, Name = _name }; var request = new HttpRequestMessage { Method = HttpMethod.Post, Content = new BlittableJsonContent(stream => ctx.Write(stream, DocumentConventions.Default.Serialization.DefaultConverter.ToBlittable(definition, ctx))) }; return(request); }
private static void SendTcpVersionInfo(JsonOperationContext context, BlittableJsonTextWriter writer, TcpNegotiateParameters parameters, int currentVersion) { if (Log.IsInfoEnabled) { Log.Info($"Send negotiation for {parameters.Operation} in version {currentVersion}"); } context.Write(writer, new DynamicJsonValue { [nameof(TcpConnectionHeaderMessage.DatabaseName)] = parameters.Database, [nameof(TcpConnectionHeaderMessage.Operation)] = parameters.Operation.ToString(), [nameof(TcpConnectionHeaderMessage.SourceNodeTag)] = parameters.SourceNodeTag, [nameof(TcpConnectionHeaderMessage.OperationVersion)] = currentVersion, [nameof(TcpConnectionHeaderMessage.AuthorizeInfo)] = parameters.AuthorizeInfo?.ToJson(), [nameof(TcpConnectionHeaderMessage.ServerId)] = parameters.DestinationServerId, [nameof(TcpConnectionHeaderMessage.LicensedFeatures)] = parameters.LicensedFeatures?.ToJson() }); writer.Flush(); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/databases/{node.Database}/subscriptions"; if (_id != null) { url += "?id=" + _id; } var request = new HttpRequestMessage { Method = HttpMethod.Put, Content = new BlittableJsonContent(stream => { ctx.Write(stream, EntityToBlittable.ConvertCommandToBlittable(_options, ctx)); }) }; return(request); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/databases/{node.Database}/cmpxchg?key={_key}&index={_index}"; var djv = new DynamicJsonValue { ["Object"] = EntityToBlittable.ConvertToBlittableIfNeeded(_value, _conventions, ctx, _conventions.CreateSerializer(), documentInfo: null, removeIdentityProperty: false) }; var blittable = ctx.ReadObject(djv, _key); var request = new HttpRequestMessage { Method = HttpMethods.Put, Content = new BlittableJsonContent(stream => { ctx.Write(stream, blittable); }) }; return(request); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/admin/replication/conflicts/solver?name={_databaseName}"; var request = new HttpRequestMessage { Method = HttpMethod.Post, Content = new BlittableJsonContent(stream => { var solver = EntityToBlittable.ConvertEntityToBlittable(new ConflictSolver { ResolveByCollection = _solver.CollectionByScript, ResolveToLatest = _solver.ResolveToLatest, }, _conventions, ctx); ctx.Write(stream, solver); }) }; return(request); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/databases/{node.Database}/smuggler/import?operationId={_operationId}"; var form = new MultipartFormDataContent { { new BlittableJsonContent(stream => { ctx.Write(stream, _options); }), Constants.Smuggler.ImportOptions }, { new StreamContentWithConfirmation(_stream, _tcs, _parent), "file", "name" } }; return(new HttpRequestMessage { Headers = { TransferEncodingChunked = true }, Method = HttpMethod.Post, Content = form }); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/databases/{node.Database}/admin/tasks/external-replication"; var request = new HttpRequestMessage { Method = HttpMethod.Post, Content = new BlittableJsonContent(stream => { var json = new DynamicJsonValue { ["Watcher"] = _newWatcher.ToJson() }; ctx.Write(stream, ctx.ReadObject(json, "update-replication")); }) }; return(request); }
public static TcpConnectionHeaderMessage.SupportedFeatures NegotiateProtocolVersion(JsonOperationContext documentsContext, Stream stream, TcpNegotiateParamaters parameters) { using (var writer = new BlittableJsonTextWriter(documentsContext, stream)) { var currentVersion = parameters.Version; while (true) { documentsContext.Write(writer, new DynamicJsonValue { [nameof(TcpConnectionHeaderMessage.DatabaseName)] = parameters.Database, // _parent.Database.Name, [nameof(TcpConnectionHeaderMessage.Operation)] = parameters.Operation.ToString(), [nameof(TcpConnectionHeaderMessage.SourceNodeTag)] = parameters.NodeTag, [nameof(TcpConnectionHeaderMessage.OperationVersion)] = currentVersion }); writer.Flush(); var version = parameters.ReadResponseAndGetVersion(documentsContext, writer, stream, parameters.Url); //In this case we usally throw internaly but for completeness we better handle it if (version == -2) { return(TcpConnectionHeaderMessage.GetSupportedFeaturesFor(TcpConnectionHeaderMessage.OperationTypes.Drop, TcpConnectionHeaderMessage.DropBaseLine40000)); } var(supported, prevSupported) = TcpConnectionHeaderMessage.OperationVersionSupported(parameters.Operation, version); if (supported) { //We are done if (currentVersion == version) { return(TcpConnectionHeaderMessage.GetSupportedFeaturesFor(parameters.Operation, version)); } //Here we support the requested version but need to inform the otherside we agree currentVersion = version; continue; } if (prevSupported == -1) { return(TcpConnectionHeaderMessage.GetSupportedFeaturesFor(TcpConnectionHeaderMessage.OperationTypes.None, TcpConnectionHeaderMessage.NoneBaseLine40000)); } currentVersion = prevSupported; } } }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/databases/{node.Database}/admin/transactions/start-recording"; var request = new HttpRequestMessage { Method = HttpMethod.Post, Content = new BlittableJsonContent(stream => { var jsonReaderObject = EntityToBlittable.ConvertCommandToBlittable( new Parameters { File = _filePath }, ctx ); ctx.Write(stream, jsonReaderObject); }) }; return(request); }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/cluster/cmpxchg?key={_key}&index={_index}"; //var tuple = ("Object", _value); var tuple = new Dictionary <string, T> { ["Object"] = _value }; var blit = EntityToBlittable.ConvertEntityToBlittable(tuple, _conventions, ctx); var request = new HttpRequestMessage { Method = HttpMethods.Put, Content = new BlittableJsonContent(stream => { ctx.Write(stream, blit); }) }; return(request); }
private static async Task WriteForDatabase(ZipArchive archive, JsonOperationContext jsonOperationContext, LocalEndpointClient localEndpointClient, string databaseName, string path = null, CancellationToken token = default) { token.ThrowIfCancellationRequested(); var endpointParameters = new Dictionary <string, Microsoft.Extensions.Primitives.StringValues> { { "database", new Microsoft.Extensions.Primitives.StringValues(databaseName) } }; foreach (var route in DebugInfoPackageUtils.Routes.Where(x => x.TypeOfRoute == RouteInformation.RouteType.Databases)) { token.ThrowIfCancellationRequested(); try { var entry = archive.CreateEntry(DebugInfoPackageUtils.GetOutputPathFromRouteInformation(route, path ?? databaseName)); entry.ExternalAttributes = ((int)(FilePermissions.S_IRUSR | FilePermissions.S_IWUSR)) << 16; using (var entryStream = entry.Open()) using (var writer = new BlittableJsonTextWriter(jsonOperationContext, entryStream)) { using (var endpointOutput = await localEndpointClient.InvokeAndReadObjectAsync(route, jsonOperationContext, endpointParameters)) { jsonOperationContext.Write(writer, endpointOutput); writer.Flush(); await entryStream.FlushAsync(token); } } } catch (OperationCanceledException) { throw; } catch (Exception e) { DebugInfoPackageUtils.WriteExceptionAsZipEntry(e, archive, path ?? databaseName); } } }
public static void TrySaving(string topologyHash, ClusterTopologyResponse clusterTopology, DocumentConventions conventions, JsonOperationContext context) { try { if (conventions.DisableTopologyCache) { return; } var path = GetPath(topologyHash, conventions); if (clusterTopology == null) { Clear(path); return; } using (var stream = SafeFileStream.Create(path, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var writer = new BlittableJsonTextWriter(context, stream)) { var json = new DynamicJsonValue { [nameof(clusterTopology.Topology)] = clusterTopology.Topology.ToJson(), [nameof(clusterTopology.Leader)] = clusterTopology.Leader, [nameof(clusterTopology.NodeTag)] = clusterTopology.NodeTag, [nameof(clusterTopology.Etag)] = clusterTopology.Etag, ["PersistedAt"] = DateTimeOffset.UtcNow.ToString(DefaultFormat.DateTimeOffsetFormatsToWrite), }; context.Write(writer, json); writer.Flush(); } } catch (Exception e) { if (_logger.IsInfoEnabled) { _logger.Info("Could not persist the cluster topology", e); } } }
private async Task WriteDatabaseRecord(ZipArchive archive, string databaseName, JsonOperationContext jsonOperationContext, TransactionOperationContext transactionCtx, CancellationToken token = default) { var entryName = DebugInfoPackageUtils.GetOutputPathFromRouteInformation("/database-record", databaseName, "json"); try { var entry = archive.CreateEntry(entryName); entry.ExternalAttributes = ((int)(FilePermissions.S_IRUSR | FilePermissions.S_IWUSR)) << 16; await using (var entryStream = entry.Open()) await using (var writer = new AsyncBlittableJsonTextWriter(jsonOperationContext, entryStream)) { jsonOperationContext.Write(writer, GetDatabaseRecordForDebugPackage(transactionCtx, databaseName)); await writer.FlushAsync(); await entryStream.FlushAsync(token); } } catch (Exception e) { await DebugInfoPackageUtils.WriteExceptionAsZipEntryAsync(e, archive, entryName); } }
public override HttpRequestMessage CreateRequest(JsonOperationContext ctx, ServerNode node, out string url) { url = $"{node.Url}/admin/databases?name={_databaseName}"; url += "&replicationFactor=" + _replicationFactor; var databaseDocument = EntityToBlittable.ConvertCommandToBlittable(_databaseRecord, ctx); var request = new HttpRequestMessage { Method = HttpMethod.Put, Content = new BlittableJsonContent(stream => { ctx.Write(stream, databaseDocument); }) }; if (_etag.HasValue) { request.Headers.TryAddWithoutValidation(Constants.Headers.Etag, $"\"{_etag.ToInvariantString()}\""); } return(request); }
private async Task Send(RavenClientWebSocket webSocket, JsonOperationContext context, string command, string commandParameter) { if (Logger.IsInfoEnabled) { Logger.Info($"Sending WebSocket Authentication Command {command} - {commandParameter}"); } var json = new DynamicJsonValue { [command] = commandParameter }; using (var stream = new MemoryStream()) using (var writer = new BlittableJsonTextWriter(context, stream)) { context.Write(writer, json); writer.Flush(); ArraySegment <byte> bytes; stream.TryGetBuffer(out bytes); await webSocket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); } }
private static async Task WriteServerWide(ZipArchive archive, JsonOperationContext context, LocalEndpointClient localEndpointClient, string prefix, CancellationToken token = default) { token.ThrowIfCancellationRequested(); //theoretically this could be parallelized, //however ZipArchive allows only one archive entry to be open concurrently foreach (var route in DebugInfoPackageUtils.Routes.Where(x => x.TypeOfRoute == RouteInformation.RouteType.None)) { token.ThrowIfCancellationRequested(); var entryRoute = DebugInfoPackageUtils.GetOutputPathFromRouteInformation(route, prefix); try { var entry = archive.CreateEntry(entryRoute); entry.ExternalAttributes = ((int)(FilePermissions.S_IRUSR | FilePermissions.S_IWUSR)) << 16; await using (var entryStream = entry.Open()) await using (var writer = new AsyncBlittableJsonTextWriter(context, entryStream)) using (var endpointOutput = await localEndpointClient.InvokeAndReadObjectAsync(route, context)) { context.Write(writer, endpointOutput); await writer.FlushAsync(); await entryStream.FlushAsync(token); } } catch (OperationCanceledException) { throw; } catch (Exception e) { await DebugInfoPackageUtils.WriteExceptionAsZipEntryAsync(e, archive, entryRoute); } } }
public override HttpRequestMessage CreateRequest(ServerNode node, out string url) { var request = new HttpRequestMessage { Method = HttpMethod.Post, }; request.Content = new BlittableJsonContent(stream => { using (var writer = new BlittableJsonTextWriter(Context, stream)) { writer.WriteStartArray(); bool first = true; foreach (var command in Commands) { if (!(first)) { writer.WriteComma(); } first = false; Context.Write(writer, command); } writer.WriteEndArray(); } }); var sb = new StringBuilder($"{node.Url}/databases/{node.Database}/bulk_docs"); AppendOptions(sb); IsReadRequest = false; url = sb.ToString(); return(request); }
private static void WriteMemoryStats(BlittableJsonTextWriter writer, JsonOperationContext context, bool includeThreads, bool includeMappings) { writer.WriteStartObject(); var memInfo = MemoryInformation.GetMemoryInformationUsingOneTimeSmapsReader(); long managedMemoryInBytes = AbstractLowMemoryMonitor.GetManagedMemoryInBytes(); long totalUnmanagedAllocations = NativeMemory.TotalAllocatedMemory; var encryptionBuffers = EncryptionBuffersPool.Instance.GetStats(); var dirtyMemoryState = MemoryInformation.GetDirtyMemoryState(); var memoryUsageRecords = MemoryInformation.GetMemoryUsageRecords(); long totalMapping = 0; var fileMappingByDir = new Dictionary <string, Dictionary <string, ConcurrentDictionary <IntPtr, long> > >(); var fileMappingSizesByDir = new Dictionary <string, long>(); foreach (var mapping in NativeMemory.FileMapping) { var dir = Path.GetDirectoryName(mapping.Key); if (fileMappingByDir.TryGetValue(dir, out Dictionary <string, ConcurrentDictionary <IntPtr, long> > value) == false) { value = new Dictionary <string, ConcurrentDictionary <IntPtr, long> >(); fileMappingByDir[dir] = value; } value[mapping.Key] = mapping.Value.Value.Info; foreach (var singleMapping in mapping.Value.Value.Info) { fileMappingSizesByDir.TryGetValue(dir, out long prevSize); fileMappingSizesByDir[dir] = prevSize + singleMapping.Value; totalMapping += singleMapping.Value; } } var djv = new DynamicJsonValue { [nameof(MemoryInfo.PhysicalMemory)] = memInfo.TotalPhysicalMemory.ToString(), [nameof(MemoryInfo.WorkingSet)] = memInfo.WorkingSet.ToString(), [nameof(MemoryInfo.ManagedAllocations)] = Size.Humane(managedMemoryInBytes), [nameof(MemoryInfo.UnmanagedAllocations)] = Size.Humane(totalUnmanagedAllocations), [nameof(MemoryInfo.EncryptionBuffers)] = Size.Humane(encryptionBuffers.TotalSize), [nameof(MemoryInfo.MemoryMapped)] = Size.Humane(totalMapping), [nameof(MemoryInfo.ScratchDirtyMemory)] = memInfo.TotalScratchDirtyMemory.ToString(), [nameof(MemoryInfo.IsHighDirty)] = dirtyMemoryState.IsHighDirty, [nameof(MemoryInfo.DirtyMemory)] = Size.Humane(dirtyMemoryState.TotalDirtyInBytes), [nameof(MemoryInfo.AvailableMemory)] = Size.Humane(memInfo.AvailableMemory.GetValue(SizeUnit.Bytes)), [nameof(MemoryInfo.AvailableMemoryForProcessing)] = memInfo.AvailableMemoryForProcessing.ToString(), [nameof(MemoryInfo.HighMemLastOneMinute)] = memoryUsageRecords.High.LastOneMinute.ToString(), [nameof(MemoryInfo.LowMemLastOneMinute)] = memoryUsageRecords.Low.LastOneMinute.ToString(), [nameof(MemoryInfo.HighMemLastFiveMinute)] = memoryUsageRecords.High.LastFiveMinutes.ToString(), [nameof(MemoryInfo.LowMemLastFiveMinute)] = memoryUsageRecords.Low.LastFiveMinutes.ToString(), [nameof(MemoryInfo.HighMemSinceStartup)] = memoryUsageRecords.High.SinceStartup.ToString(), [nameof(MemoryInfo.LowMemSinceStartup)] = memoryUsageRecords.Low.SinceStartup.ToString(), }; writer.WritePropertyName(nameof(MemoryInformation)); context.Write(writer, djv); writer.WriteComma(); writer.WritePropertyName("Threads"); writer.WriteStartArray(); WriteThreads(includeThreads, writer, context); writer.WriteEndArray(); writer.WriteComma(); writer.WritePropertyName(nameof(MemoryInfo.Mappings)); writer.WriteStartArray(); WriteMappings(includeMappings, writer, context, fileMappingSizesByDir, fileMappingByDir); writer.WriteEndArray(); writer.WriteEndObject(); }
private static void WriteMappings(bool includeMappings, BlittableJsonTextWriter writer, JsonOperationContext context, Dictionary <string, long> fileMappingSizesByDir, Dictionary <string, Dictionary <string, ConcurrentDictionary <IntPtr, long> > > fileMappingByDir) { if (includeMappings == false) { return; } bool isFirst = true; var prefixLength = LongestCommonPrefixLength(new List <string>(fileMappingSizesByDir.Keys)); foreach (var sizes in fileMappingSizesByDir.OrderByDescending(x => x.Value)) { if (fileMappingByDir.TryGetValue(sizes.Key, out Dictionary <string, ConcurrentDictionary <IntPtr, long> > value) == false) { continue; } var details = new DynamicJsonValue(); var dir = new DynamicJsonValue { [nameof(MemoryInfoMappingItem.Directory)] = sizes.Key.Substring(prefixLength), [nameof(MemoryInfoMappingItem.TotalDirectorySize)] = sizes.Value, [nameof(MemoryInfoMappingItem.HumaneTotalDirectorySize)] = Size.Humane(sizes.Value), [nameof(MemoryInfoMappingItem.Details)] = details }; foreach (var file in value.OrderBy(x => x.Key)) { long totalMapped = 0; var dja = new DynamicJsonArray(); var dic = new Dictionary <long, long>(); foreach (var mapping in file.Value) { totalMapped += mapping.Value; dic.TryGetValue(mapping.Value, out long prev); dic[mapping.Value] = prev + 1; } foreach (var maps in dic) { dja.Add(new DynamicJsonValue { [nameof(MemoryInfoMappingDetails.Size)] = maps.Key, [nameof(MemoryInfoMappingDetails.Count)] = maps.Value }); } var fileSize = GetFileSize(file.Key); details[Path.GetFileName(file.Key)] = new DynamicJsonValue { [nameof(MemoryInfoMappingFileInfo.FileSize)] = fileSize, [nameof(MemoryInfoMappingFileInfo.HumaneFileSize)] = Size.Humane(fileSize), [nameof(MemoryInfoMappingFileInfo.TotalMapped)] = totalMapped, [nameof(MemoryInfoMappingFileInfo.HumaneTotalMapped)] = Size.Humane(totalMapped), [nameof(MemoryInfoMappingFileInfo.Mappings)] = dja }; } if (isFirst == false) { writer.WriteComma(); } isFirst = false; context.Write(writer, dir); } }
private void WriteHubPullReplications(List <PullReplicationDefinition> hubPullReplications) { if (hubPullReplications == null) { _writer.WriteNull(); return; } _writer.WriteStartArray(); var first = true; foreach (var pullReplication in hubPullReplications) { if (first == false) { _writer.WriteComma(); } first = false; _context.Write(_writer, pullReplication.ToJson()); } _writer.WriteEndArray(); }