mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-04 17:24:56 -05:00
Create MS.SqlTools.Credentials project (#249)
* Initial credential service files * Clean-up hostloader * Update build scripts to build credentials archive * Move hosting files to new assembly * Add credentials files to MS.SqlTools.Credentials * Remove duplicate files * Update namespace in program.cs * Fix test build breaks * Update extensions visibility. * Remove unused resource strings * Add xproj files to SLN for appveyor builds * Fix appveyor build break in test project * Fix extensibility tests * Fix various typos in latest iteration * Add settings for Integration build * Fix codecoverage.bat to use full pdb for new projects * Fix bug when packing in folder with native images * Fix typos in xproj * Reset XLF to fix build.cmd
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Simplifies the setup of a SynchronizationContext for the use
|
||||
/// of async calls in the current thread.
|
||||
/// </summary>
|
||||
public static class AsyncContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts a new ThreadSynchronizationContext, attaches it to
|
||||
/// the thread, and then runs the given async main function.
|
||||
/// </summary>
|
||||
/// <param name="asyncMainFunc">
|
||||
/// The Task-returning Func which represents the "main" function
|
||||
/// for the thread.
|
||||
/// </param>
|
||||
public static void Start(Func<Task> asyncMainFunc)
|
||||
{
|
||||
// Is there already a synchronization context?
|
||||
if (SynchronizationContext.Current != null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A SynchronizationContext is already assigned on this thread.");
|
||||
}
|
||||
|
||||
// Create and register a synchronization context for this thread
|
||||
var threadSyncContext = new ThreadSynchronizationContext();
|
||||
SynchronizationContext.SetSynchronizationContext(threadSyncContext);
|
||||
|
||||
// Get the main task and act on its completion
|
||||
Task asyncMainTask = asyncMainFunc();
|
||||
asyncMainTask.ContinueWith(
|
||||
t => threadSyncContext.EndLoop(),
|
||||
TaskScheduler.Default);
|
||||
|
||||
// Start the synchronization context's request loop and
|
||||
// wait for the main task to complete
|
||||
threadSyncContext.RunLoopOnCurrentThread();
|
||||
asyncMainTask.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a simplified interface for creating a new thread
|
||||
/// and establishing an AsyncContext in it.
|
||||
/// </summary>
|
||||
public class AsyncContextThread
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private Task threadTask;
|
||||
private string threadName;
|
||||
private CancellationTokenSource threadCancellationToken =
|
||||
new CancellationTokenSource();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the AsyncContextThread class.
|
||||
/// </summary>
|
||||
/// <param name="threadName">
|
||||
/// The name of the thread for debugging purposes.
|
||||
/// </param>
|
||||
public AsyncContextThread(string threadName)
|
||||
{
|
||||
this.threadName = threadName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Runs a task on the AsyncContextThread.
|
||||
/// </summary>
|
||||
/// <param name="taskReturningFunc">
|
||||
/// A Func which returns the task to be run on the thread.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A Task which can be used to monitor the thread for completion.
|
||||
/// </returns>
|
||||
public Task Run(Func<Task> taskReturningFunc)
|
||||
{
|
||||
// Start up a long-running task with the action as the
|
||||
// main entry point for the thread
|
||||
this.threadTask =
|
||||
Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
// Set the thread's name to help with debugging
|
||||
Thread.CurrentThread.Name = "AsyncContextThread: " + this.threadName;
|
||||
|
||||
// Set up an AsyncContext to run the task
|
||||
AsyncContext.Start(taskReturningFunc);
|
||||
},
|
||||
this.threadCancellationToken.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
|
||||
return this.threadTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the thread task.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
this.threadCancellationToken.Cancel();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a simple wrapper over a SemaphoreSlim to allow
|
||||
/// synchronization locking inside of async calls. Cannot be
|
||||
/// used recursively.
|
||||
/// </summary>
|
||||
public class AsyncLock
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private Task<IDisposable> lockReleaseTask;
|
||||
private SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the AsyncLock class.
|
||||
/// </summary>
|
||||
public AsyncLock()
|
||||
{
|
||||
this.lockReleaseTask =
|
||||
Task.FromResult(
|
||||
(IDisposable)new LockReleaser(this));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Locks
|
||||
/// </summary>
|
||||
/// <returns>A task which has an IDisposable</returns>
|
||||
public Task<IDisposable> LockAsync()
|
||||
{
|
||||
return this.LockAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains or waits for a lock which can be used to synchronize
|
||||
/// access to a resource. The wait may be cancelled with the
|
||||
/// given CancellationToken.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">
|
||||
/// A CancellationToken which can be used to cancel the lock.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Task waitTask = lockSemaphore.WaitAsync(cancellationToken);
|
||||
|
||||
return waitTask.IsCompleted ?
|
||||
this.lockReleaseTask :
|
||||
waitTask.ContinueWith(
|
||||
(t, releaser) =>
|
||||
{
|
||||
return (IDisposable)releaser;
|
||||
},
|
||||
this.lockReleaseTask.Result,
|
||||
cancellationToken,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Classes
|
||||
|
||||
/// <summary>
|
||||
/// Provides an IDisposable wrapper around an AsyncLock so
|
||||
/// that it can easily be used inside of a 'using' block.
|
||||
/// </summary>
|
||||
private class LockReleaser : IDisposable
|
||||
{
|
||||
private AsyncLock lockToRelease;
|
||||
|
||||
internal LockReleaser(AsyncLock lockToRelease)
|
||||
{
|
||||
this.lockToRelease = lockToRelease;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.lockToRelease.lockSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a synchronized queue which can be used from within async
|
||||
/// operations. This is primarily used for producer/consumer scenarios.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item contained in the queue.</typeparam>
|
||||
public class AsyncQueue<T>
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private AsyncLock queueLock = new AsyncLock();
|
||||
private Queue<T> itemQueue;
|
||||
private Queue<TaskCompletionSource<T>> requestQueue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the queue is currently empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an empty instance of the AsyncQueue class.
|
||||
/// </summary>
|
||||
public AsyncQueue() : this(Enumerable.Empty<T>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of the AsyncQueue class, pre-populated
|
||||
/// with the given collection of items.
|
||||
/// </summary>
|
||||
/// <param name="initialItems">
|
||||
/// An IEnumerable containing the initial items with which the queue will
|
||||
/// be populated.
|
||||
/// </param>
|
||||
public AsyncQueue(IEnumerable<T> initialItems)
|
||||
{
|
||||
this.itemQueue = new Queue<T>(initialItems);
|
||||
this.requestQueue = new Queue<TaskCompletionSource<T>>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an item onto the end of the queue.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be added to the queue.</param>
|
||||
/// <returns>
|
||||
/// A Task which can be awaited until the synchronized enqueue
|
||||
/// operation completes.
|
||||
/// </returns>
|
||||
public async Task EnqueueAsync(T item)
|
||||
{
|
||||
using (await queueLock.LockAsync())
|
||||
{
|
||||
TaskCompletionSource<T> requestTaskSource = null;
|
||||
|
||||
// Are any requests waiting?
|
||||
while (this.requestQueue.Count > 0)
|
||||
{
|
||||
// Is the next request cancelled already?
|
||||
requestTaskSource = this.requestQueue.Dequeue();
|
||||
if (!requestTaskSource.Task.IsCanceled)
|
||||
{
|
||||
// Dispatch the item
|
||||
requestTaskSource.SetResult(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No more requests waiting, queue the item for a later request
|
||||
this.itemQueue.Enqueue(item);
|
||||
this.IsEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues an item from the queue or waits asynchronously
|
||||
/// until an item is available.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A Task which can be awaited until a value can be dequeued.
|
||||
/// </returns>
|
||||
public Task<T> DequeueAsync()
|
||||
{
|
||||
return this.DequeueAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues an item from the queue or waits asynchronously
|
||||
/// until an item is available. The wait can be cancelled
|
||||
/// using the given CancellationToken.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">
|
||||
/// A CancellationToken with which a dequeue wait can be cancelled.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A Task which can be awaited until a value can be dequeued.
|
||||
/// </returns>
|
||||
public async Task<T> DequeueAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Task<T> requestTask;
|
||||
|
||||
using (await queueLock.LockAsync(cancellationToken))
|
||||
{
|
||||
if (this.itemQueue.Count > 0)
|
||||
{
|
||||
// Items are waiting to be taken so take one immediately
|
||||
T item = this.itemQueue.Dequeue();
|
||||
this.IsEmpty = this.itemQueue.Count == 0;
|
||||
|
||||
return item;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue the request for the next item
|
||||
var requestTaskSource = new TaskCompletionSource<T>();
|
||||
this.requestQueue.Enqueue(requestTaskSource);
|
||||
|
||||
// Register the wait task for cancel notifications
|
||||
cancellationToken.Register(
|
||||
() => requestTaskSource.TrySetCanceled());
|
||||
|
||||
requestTask = requestTaskSource.Task;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the request task to complete outside of the lock
|
||||
return await requestTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
EnableLogging = true;
|
||||
break;
|
||||
case "-locale":
|
||||
setLocale(argProperty);
|
||||
SetLocale(argProperty);
|
||||
break;
|
||||
case "h":
|
||||
case "-help":
|
||||
@@ -109,7 +109,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocale(string locale){
|
||||
private void SetLocale(string locale)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Creating cultureInfo from our given locale
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
internal static class ObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to evaluate an object's ToString() method in an exception safe way. This will
|
||||
/// extension method will not throw.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object on which to call ToString()</param>
|
||||
/// <returns>The ToString() return value or a suitable error message is that throws.</returns>
|
||||
public static string SafeToString(this object obj)
|
||||
{
|
||||
string str;
|
||||
|
||||
try
|
||||
{
|
||||
str = obj.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
str = $"<Error converting poperty value to string - {ex.Message}>";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a boolean to a "1" or "0" string. Particularly helpful when sending telemetry
|
||||
/// </summary>
|
||||
public static string ToOneOrZeroString(this bool isTrue)
|
||||
{
|
||||
return isTrue ? "1" : "0";
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NullableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to evaluate a bool? and determine if it has the value and is true.
|
||||
/// This way we avoid throwing if the bool? doesn't have a value.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <c>bool?</c> to process</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="obj"/> has a value and it is <c>true</c>
|
||||
/// <c>false</c> otherwise.
|
||||
/// </returns>
|
||||
public static bool HasTrue(this bool? obj)
|
||||
{
|
||||
return obj.HasValue && obj.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the level indicators for log messages.
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates a verbose log message.
|
||||
/// </summary>
|
||||
Verbose,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a normal, non-verbose log message.
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a warning message.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates an error message.
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a simple logging interface. May be replaced with a
|
||||
/// more robust solution at a later date.
|
||||
/// </summary>
|
||||
public static class Logger
|
||||
{
|
||||
private static LogWriter logWriter;
|
||||
|
||||
private static bool isEnabled;
|
||||
|
||||
private static bool isInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Logger for the current session.
|
||||
/// </summary>
|
||||
/// <param name="logFilePath">
|
||||
/// Optional. Specifies the path at which log messages will be written.
|
||||
/// </param>
|
||||
/// <param name="minimumLogLevel">
|
||||
/// Optional. Specifies the minimum log message level to write to the log file.
|
||||
/// </param>
|
||||
public static void Initialize(
|
||||
string logFilePath = "sqltools",
|
||||
LogLevel minimumLogLevel = LogLevel.Normal,
|
||||
bool isEnabled = true)
|
||||
{
|
||||
Logger.isEnabled = isEnabled;
|
||||
|
||||
// return if the logger is not enabled or already initialized
|
||||
if (!Logger.isEnabled || Logger.isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.isInitialized = true;
|
||||
|
||||
// get a unique number to prevent conflicts of two process launching at the same time
|
||||
int uniqueId;
|
||||
try
|
||||
{
|
||||
uniqueId = Process.GetCurrentProcess().Id;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// if the pid look up fails for any reason, just use a random number
|
||||
uniqueId = new Random().Next(1000, 9999);
|
||||
}
|
||||
|
||||
// make the log path unique
|
||||
string fullFileName = string.Format(
|
||||
"{0}_{1,4:D4}{2,2:D2}{3,2:D2}{4,2:D2}{5,2:D2}{6,2:D2}{7}.log",
|
||||
logFilePath,
|
||||
DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
|
||||
DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second,
|
||||
uniqueId);
|
||||
|
||||
if (logWriter != null)
|
||||
{
|
||||
logWriter.Dispose();
|
||||
}
|
||||
|
||||
// TODO: Parameterize this
|
||||
logWriter =
|
||||
new LogWriter(
|
||||
minimumLogLevel,
|
||||
fullFileName,
|
||||
true);
|
||||
|
||||
Logger.Write(LogLevel.Normal, "Initializing SQL Tools Service Host logger");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the Logger.
|
||||
/// </summary>
|
||||
public static void Close()
|
||||
{
|
||||
if (logWriter != null)
|
||||
{
|
||||
logWriter.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a message to the log file.
|
||||
/// </summary>
|
||||
/// <param name="logLevel">The level at which the message will be written.</param>
|
||||
/// <param name="logMessage">The message text to be written.</param>
|
||||
/// <param name="callerName">The name of the calling method.</param>
|
||||
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
|
||||
/// <param name="callerLineNumber">The line number of the calling method.</param>
|
||||
public static void Write(
|
||||
LogLevel logLevel,
|
||||
string logMessage,
|
||||
[CallerMemberName] string callerName = null,
|
||||
[CallerFilePath] string callerSourceFile = null,
|
||||
[CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
// return if the logger is not enabled or not initialized
|
||||
if (!Logger.isEnabled || !Logger.isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (logWriter != null)
|
||||
{
|
||||
logWriter.Write(
|
||||
logLevel,
|
||||
logMessage,
|
||||
callerName,
|
||||
callerSourceFile,
|
||||
callerLineNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LogWriter : IDisposable
|
||||
{
|
||||
private object logLock = new object();
|
||||
private TextWriter textWriter;
|
||||
private LogLevel minimumLogLevel = LogLevel.Verbose;
|
||||
|
||||
public LogWriter(LogLevel minimumLogLevel, string logFilePath, bool deleteExisting)
|
||||
{
|
||||
this.minimumLogLevel = minimumLogLevel;
|
||||
|
||||
// Ensure that we have a usable log file path
|
||||
if (!Path.IsPathRooted(logFilePath))
|
||||
{
|
||||
logFilePath =
|
||||
Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
logFilePath);
|
||||
}
|
||||
|
||||
|
||||
if (!this.TryOpenLogFile(logFilePath, deleteExisting))
|
||||
{
|
||||
// If the log file couldn't be opened at this location,
|
||||
// try opening it in a more reliable path
|
||||
this.TryOpenLogFile(
|
||||
Path.Combine(
|
||||
Environment.GetEnvironmentVariable("TEMP"),
|
||||
Path.GetFileName(logFilePath)),
|
||||
deleteExisting);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(
|
||||
LogLevel logLevel,
|
||||
string logMessage,
|
||||
string callerName = null,
|
||||
string callerSourceFile = null,
|
||||
int callerLineNumber = 0)
|
||||
{
|
||||
if (this.textWriter != null &&
|
||||
logLevel >= this.minimumLogLevel)
|
||||
{
|
||||
// System.IO is not thread safe
|
||||
lock (this.logLock)
|
||||
{
|
||||
// Print the timestamp and log level
|
||||
this.textWriter.WriteLine(
|
||||
"{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
|
||||
DateTime.Now,
|
||||
logLevel.ToString().ToUpper(),
|
||||
callerName,
|
||||
callerLineNumber,
|
||||
callerSourceFile);
|
||||
|
||||
// Print out indented message lines
|
||||
foreach (var messageLine in logMessage.Split('\n'))
|
||||
{
|
||||
this.textWriter.WriteLine(" " + messageLine.TrimEnd());
|
||||
}
|
||||
|
||||
// Finish with a newline and flush the writer
|
||||
this.textWriter.WriteLine();
|
||||
this.textWriter.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.textWriter != null)
|
||||
{
|
||||
this.textWriter.Flush();
|
||||
this.textWriter.Dispose();
|
||||
this.textWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryOpenLogFile(
|
||||
string logFilePath,
|
||||
bool deleteExisting)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Make sure the log directory exists
|
||||
Directory.CreateDirectory(
|
||||
Path.GetDirectoryName(
|
||||
logFilePath));
|
||||
|
||||
// Open the log file for writing with UTF8 encoding
|
||||
this.textWriter =
|
||||
new StreamWriter(
|
||||
new FileStream(
|
||||
logFilePath,
|
||||
deleteExisting ?
|
||||
FileMode.Create :
|
||||
FileMode.Append),
|
||||
Encoding.UTF8);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is UnauthorizedAccessException ||
|
||||
e is IOException)
|
||||
{
|
||||
// This exception is thrown when we can't open the file
|
||||
// at the path in logFilePath. Return false to indicate
|
||||
// that the log file couldn't be created.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unexpected exception, rethrow it
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection class that permits storage of over <c>int.MaxValue</c> items. This is performed
|
||||
/// by using a 2D list of lists. The internal lists are only initialized as necessary. This
|
||||
/// collection implements IEnumerable to make it easier to run LINQ queries against it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is based on code from $\Data Tools\SSMS_Main\sql\ssms\core\DataStorage\ArrayList64.cs
|
||||
/// with additions to bring it up to .NET 4.5 standards
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">Type of the values to store</typeparam>
|
||||
public class LongList<T> : IEnumerable<T>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private int expandListSize = int.MaxValue;
|
||||
private List<List<T>> expandedList;
|
||||
private readonly List<T> shortList;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new long list
|
||||
/// </summary>
|
||||
public LongList()
|
||||
{
|
||||
shortList = new List<T>();
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The total number of elements in the array
|
||||
/// </summary>
|
||||
public long Count { get; private set; }
|
||||
|
||||
public T this[long index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetItem(index);
|
||||
}
|
||||
}
|
||||
|
||||
public int ExpandListSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.expandListSize;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
this.expandListSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified value to the end of the list
|
||||
/// </summary>
|
||||
/// <param name="val">Value to add to the list</param>
|
||||
/// <returns>Index of the item that was just added</returns>
|
||||
public long Add(T val)
|
||||
{
|
||||
if (Count <= this.ExpandListSize)
|
||||
{
|
||||
shortList.Add(val);
|
||||
}
|
||||
else // need to split values into several arrays
|
||||
{
|
||||
if (expandedList == null)
|
||||
{
|
||||
// very inefficient so delay as much as possible
|
||||
// immediately add 0th array
|
||||
expandedList = new List<List<T>> {shortList};
|
||||
}
|
||||
|
||||
int arrayIndex = (int)(Count / this.ExpandListSize); // 0 based
|
||||
|
||||
List<T> arr;
|
||||
if (expandedList.Count <= arrayIndex) // need to make a new array
|
||||
{
|
||||
arr = new List<T>();
|
||||
expandedList.Add(arr);
|
||||
}
|
||||
else // use existing array
|
||||
{
|
||||
arr = expandedList[arrayIndex];
|
||||
}
|
||||
arr.Add(val);
|
||||
}
|
||||
return (++Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the item at the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the item to return</param>
|
||||
/// <returns>The item at the index specified</returns>
|
||||
public T GetItem(long index)
|
||||
{
|
||||
T val = default(T);
|
||||
|
||||
if (Count <= this.ExpandListSize)
|
||||
{
|
||||
int i32Index = Convert.ToInt32(index);
|
||||
val = shortList[i32Index];
|
||||
}
|
||||
else
|
||||
{
|
||||
int iArray32Index = (int) (Count / this.ExpandListSize);
|
||||
if (expandedList.Count > iArray32Index)
|
||||
{
|
||||
List<T> arr = expandedList[iArray32Index];
|
||||
|
||||
int i32Index = (int) (Count % this.ExpandListSize);
|
||||
if (arr.Count > i32Index)
|
||||
{
|
||||
val = arr[i32Index];
|
||||
}
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item at the specified location and shifts all the items after the provided
|
||||
/// index up by one.
|
||||
/// </summary>
|
||||
/// <param name="index">The index to remove from the list</param>
|
||||
public void RemoveAt(long index)
|
||||
{
|
||||
if (Count <= this.ExpandListSize)
|
||||
{
|
||||
int iArray32MemberIndex = Convert.ToInt32(index); // 0 based
|
||||
shortList.RemoveAt(iArray32MemberIndex);
|
||||
}
|
||||
else // handle the case of multiple arrays
|
||||
{
|
||||
// find out which array it is in
|
||||
int arrayIndex = (int) (index / this.ExpandListSize);
|
||||
List<T> arr = expandedList[arrayIndex];
|
||||
|
||||
// find out index into this array
|
||||
int iArray32MemberIndex = (int) (index % this.ExpandListSize);
|
||||
arr.RemoveAt(iArray32MemberIndex);
|
||||
|
||||
// now shift members of the array back one
|
||||
int iArray32TotalIndex = (int) (Count / this.ExpandListSize);
|
||||
for (int i = arrayIndex + 1; i < iArray32TotalIndex; i++)
|
||||
{
|
||||
List<T> arr1 = expandedList[i - 1];
|
||||
List<T> arr2 = expandedList[i];
|
||||
|
||||
arr1.Add(arr2[this.ExpandListSize - 1]);
|
||||
arr2.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
--Count;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable<object> Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Returns a generic enumerator for enumeration of this LongList
|
||||
/// </summary>
|
||||
/// <returns>Enumerator for LongList</returns>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return new LongListEnumerator<T>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator for enumeration of this LongList
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class LongListEnumerator<TEt> : IEnumerator<TEt>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
/// <summary>
|
||||
/// The index into the list of the item that is the current item
|
||||
/// </summary>
|
||||
private long index;
|
||||
|
||||
/// <summary>
|
||||
/// The current list that we're iterating over.
|
||||
/// </summary>
|
||||
private readonly LongList<TEt> localList;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new enumerator for a given LongList
|
||||
/// </summary>
|
||||
/// <param name="list">The list to enumerate</param>
|
||||
public LongListEnumerator(LongList<TEt> list)
|
||||
{
|
||||
localList = list;
|
||||
index = 0;
|
||||
Current = default(TEt);
|
||||
}
|
||||
|
||||
#region IEnumerator Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current item in the enumeration
|
||||
/// </summary>
|
||||
public TEt Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next item in the list we're iterating over
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the move was successful</returns>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (index < localList.Count)
|
||||
{
|
||||
Current = localList[index];
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
Current = default(TEt);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the enumeration
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
index = 0;
|
||||
Current = default(TEt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposal method. Does nothing.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
public static class TextUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Find the position of the cursor in the SQL script content buffer and return previous new line position
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow">parameter is 0-based</param>
|
||||
/// <param name="startColumn">parameter is 0-based</param>
|
||||
/// <param name="prevNewLine">parameter is 0-based</param>
|
||||
public static int PositionOfCursor(string sql, int startRow, int startColumn, out int prevNewLine)
|
||||
{
|
||||
prevNewLine = 0;
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < startRow; ++i)
|
||||
{
|
||||
while (prevNewLine < sql.Length && sql[prevNewLine] != '\n')
|
||||
{
|
||||
++prevNewLine;
|
||||
}
|
||||
++prevNewLine;
|
||||
}
|
||||
|
||||
return startColumn + prevNewLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the position of the previous delimeter for autocomplete token replacement.
|
||||
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow">parameter is 0-based</param>
|
||||
/// <param name="startColumn">parameter is 0-based</param>
|
||||
/// <param name="tokenText"></param>
|
||||
public static int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
||||
{
|
||||
int prevNewLine;
|
||||
int delimeterPos = PositionOfCursor(sql, startRow, startColumn, out prevNewLine);
|
||||
|
||||
if (delimeterPos - 1 < sql.Length)
|
||||
{
|
||||
while (--delimeterPos >= prevNewLine)
|
||||
{
|
||||
if (IsCharacterDelimeter(sql[delimeterPos]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delimeterPos = delimeterPos + 1 - prevNewLine;
|
||||
}
|
||||
|
||||
return delimeterPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the position of the next delimeter for autocomplete token replacement.
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow">parameter is 0-based</param>
|
||||
/// <param name="startColumn">parameter is 0-based</param>
|
||||
public static int PositionOfNextDelimeter(string sql, int startRow, int startColumn)
|
||||
{
|
||||
int prevNewLine;
|
||||
int delimeterPos = PositionOfCursor(sql, startRow, startColumn, out prevNewLine);
|
||||
|
||||
while (delimeterPos < sql.Length)
|
||||
{
|
||||
if (IsCharacterDelimeter(sql[delimeterPos]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
++delimeterPos;
|
||||
}
|
||||
|
||||
return delimeterPos - prevNewLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the character is a SQL token delimiter
|
||||
/// </summary>
|
||||
/// <param name="ch"></param>
|
||||
private static bool IsCharacterDelimeter(char ch)
|
||||
{
|
||||
return ch == ' '
|
||||
|| ch == '\t'
|
||||
|| ch == '\n'
|
||||
|| ch == '.'
|
||||
|| ch == '+'
|
||||
|| ch == '-'
|
||||
|| ch == '*'
|
||||
|| ch == '>'
|
||||
|| ch == '<'
|
||||
|| ch == '='
|
||||
|| ch == '/'
|
||||
|| ch == '%'
|
||||
|| ch == ','
|
||||
|| ch == ';'
|
||||
|| ch == '('
|
||||
|| ch == ')';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove square bracket syntax from a token string
|
||||
/// </summary>
|
||||
/// <param name="tokenText"></param>
|
||||
/// <returns> string with outer brackets removed</returns>
|
||||
public static string RemoveSquareBracketSyntax(string tokenText)
|
||||
{
|
||||
if(tokenText.StartsWith("[") && tokenText.EndsWith("]"))
|
||||
{
|
||||
return tokenText.Substring(1, tokenText.Length - 2);
|
||||
}
|
||||
return tokenText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a SynchronizationContext implementation that can be used
|
||||
/// in console applications or any thread which doesn't have its
|
||||
/// own SynchronizationContext.
|
||||
/// </summary>
|
||||
public class ThreadSynchronizationContext : SynchronizationContext
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private BlockingCollection<Tuple<SendOrPostCallback, object>> requestQueue =
|
||||
new BlockingCollection<Tuple<SendOrPostCallback, object>>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Posts a request for execution to the SynchronizationContext.
|
||||
/// This will be executed on the SynchronizationContext's thread.
|
||||
/// </summary>
|
||||
/// <param name="callback">
|
||||
/// The callback to be invoked on the SynchronizationContext's thread.
|
||||
/// </param>
|
||||
/// <param name="state">
|
||||
/// A state object to pass along to the callback when executed through
|
||||
/// the SynchronizationContext.
|
||||
/// </param>
|
||||
public override void Post(SendOrPostCallback callback, object state)
|
||||
{
|
||||
// Add the request to the queue
|
||||
this.requestQueue.Add(
|
||||
new Tuple<SendOrPostCallback, object>(
|
||||
callback, state));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Starts the SynchronizationContext message loop on the current thread.
|
||||
/// </summary>
|
||||
public void RunLoopOnCurrentThread()
|
||||
{
|
||||
Tuple<SendOrPostCallback, object> request;
|
||||
|
||||
while (this.requestQueue.TryTake(out request, Timeout.Infinite))
|
||||
{
|
||||
// Invoke the request's callback
|
||||
request.Item1(request.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the SynchronizationContext message loop.
|
||||
/// </summary>
|
||||
public void EndLoop()
|
||||
{
|
||||
// Tell the blocking queue that we're done
|
||||
this.requestQueue.CompleteAdding();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides common validation methods to simplify method
|
||||
/// parameter checks.
|
||||
/// </summary>
|
||||
public static class Validate
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws ArgumentNullException if value is null.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNull(string parameterName, object valueToCheck)
|
||||
{
|
||||
if (valueToCheck == null)
|
||||
{
|
||||
throw new ArgumentNullException(parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentOutOfRangeException if the value is outside
|
||||
/// of the given lower and upper limits.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
/// <param name="lowerLimit">The lower limit which the value should not be less than.</param>
|
||||
/// <param name="upperLimit">The upper limit which the value should not be greater than.</param>
|
||||
public static void IsWithinRange(
|
||||
string parameterName,
|
||||
int valueToCheck,
|
||||
int lowerLimit,
|
||||
int upperLimit)
|
||||
{
|
||||
// TODO: Debug assert here if lowerLimit >= upperLimit
|
||||
|
||||
if (valueToCheck < lowerLimit || valueToCheck > upperLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
valueToCheck,
|
||||
string.Format(
|
||||
"Value is not between {0} and {1}",
|
||||
lowerLimit,
|
||||
upperLimit));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentOutOfRangeException if the value is greater than or equal
|
||||
/// to the given upper limit.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
/// <param name="upperLimit">The upper limit which the value should be less than.</param>
|
||||
public static void IsLessThan(
|
||||
string parameterName,
|
||||
int valueToCheck,
|
||||
int upperLimit)
|
||||
{
|
||||
if (valueToCheck >= upperLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
valueToCheck,
|
||||
string.Format(
|
||||
"Value is greater than or equal to {0}",
|
||||
upperLimit));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentOutOfRangeException if the value is less than or equal
|
||||
/// to the given lower limit.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
/// <param name="lowerLimit">The lower limit which the value should be greater than.</param>
|
||||
public static void IsGreaterThan(
|
||||
string parameterName,
|
||||
int valueToCheck,
|
||||
int lowerLimit)
|
||||
{
|
||||
if (valueToCheck < lowerLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
valueToCheck,
|
||||
string.Format(
|
||||
"Value is less than or equal to {0}",
|
||||
lowerLimit));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is equal to the undesired value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of value to be validated.</typeparam>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="undesiredValue">The value that valueToCheck should not equal.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotEqual<TValue>(
|
||||
string parameterName,
|
||||
TValue valueToCheck,
|
||||
TValue undesiredValue)
|
||||
{
|
||||
if (EqualityComparer<TValue>.Default.Equals(valueToCheck, undesiredValue))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
string.Format(
|
||||
"The given value '{0}' should not equal '{1}'",
|
||||
valueToCheck,
|
||||
undesiredValue),
|
||||
parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is null or an empty string.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNullOrEmptyString(string parameterName, string valueToCheck)
|
||||
{
|
||||
if (string.IsNullOrEmpty(valueToCheck))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Parameter contains a null, empty, or whitespace string.",
|
||||
parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is null, an empty string,
|
||||
/// or a string containing only whitespace.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNullOrWhitespaceString(string parameterName, string valueToCheck)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(valueToCheck))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Parameter contains a null, empty, or whitespace string.",
|
||||
parameterName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user