Initial commit of SqlTools Service API

This commit is contained in:
Karl Burtram
2016-07-15 11:02:03 -07:00
parent 874ade9001
commit 790825cfab
112 changed files with 9782 additions and 1 deletions

View File

@@ -0,0 +1,52 @@
//
// 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.PowerShell.EditorServices.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();
}
}
}

View File

@@ -0,0 +1,85 @@
//
// 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.PowerShell.EditorServices.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
}
}

View File

@@ -0,0 +1,169 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.PowerShell.EditorServices.Utility
{
/// <summary>
/// Restricts the invocation of an operation to a specified time
/// interval. Can also cause previous requests to be cancelled
/// by new requests within that time window. Typically used for
/// buffering information for an operation or ensuring that an
/// operation only runs after some interval.
/// </summary>
/// <typeparam name="TInvokeArgs">The argument type for the Invoke method.</typeparam>
public abstract class AsyncDebouncer<TInvokeArgs>
{
#region Private Fields
private int flushInterval;
private bool restartOnInvoke;
private Task currentTimerTask;
private CancellationTokenSource timerCancellationSource;
private AsyncLock asyncLock = new AsyncLock();
#endregion
#region Public Methods
/// <summary>
/// Creates a new instance of the AsyncDebouncer class with the
/// specified flush interval. If restartOnInvoke is true, any
/// calls to Invoke will cancel previous calls which have not yet
/// passed the flush interval.
/// </summary>
/// <param name="flushInterval">
/// A millisecond interval to use for flushing prior Invoke calls.
/// </param>
/// <param name="restartOnInvoke">
/// If true, Invoke calls will reset prior calls which haven't passed the flush interval.
/// </param>
public AsyncDebouncer(int flushInterval, bool restartOnInvoke)
{
this.flushInterval = flushInterval;
this.restartOnInvoke = restartOnInvoke;
}
/// <summary>
/// Invokes the debouncer with the given input. The debouncer will
/// wait for the specified interval before calling the Flush method
/// to complete the operation.
/// </summary>
/// <param name="invokeArgument">
/// The argument for this implementation's Invoke method.
/// </param>
/// <returns>A Task to be awaited until the Invoke is queued.</returns>
public async Task Invoke(TInvokeArgs invokeArgument)
{
using (await this.asyncLock.LockAsync())
{
// Invoke the implementor
await this.OnInvoke(invokeArgument);
// If there's no timer, start one
if (this.currentTimerTask == null)
{
this.StartTimer();
}
else if (this.currentTimerTask != null && this.restartOnInvoke)
{
// Restart the existing timer
if (this.CancelTimer())
{
this.StartTimer();
}
}
}
}
/// <summary>
/// Flushes the latest state regardless of the current interval.
/// An AsyncDebouncer MUST NOT invoke its own Flush method otherwise
/// deadlocks could occur.
/// </summary>
/// <returns>A Task to be awaited until Flush completes.</returns>
public async Task Flush()
{
using (await this.asyncLock.LockAsync())
{
// Cancel the current timer
this.CancelTimer();
// Flush the current output
await this.OnFlush();
}
}
#endregion
#region Abstract Methods
/// <summary>
/// Implemented by the subclass to take the argument for the
/// future operation that will be performed by OnFlush.
/// </summary>
/// <param name="invokeArgument">
/// The argument for this implementation's OnInvoke method.
/// </param>
/// <returns>A Task to be awaited for the invoke to complete.</returns>
protected abstract Task OnInvoke(TInvokeArgs invokeArgument);
/// <summary>
/// Implemented by the subclass to complete the current operation.
/// </summary>
/// <returns>A Task to be awaited for the operation to complete.</returns>
protected abstract Task OnFlush();
#endregion
#region Private Methods
private void StartTimer()
{
this.timerCancellationSource = new CancellationTokenSource();
this.currentTimerTask =
Task.Delay(this.flushInterval, this.timerCancellationSource.Token)
.ContinueWith(
t =>
{
if (!t.IsCanceled)
{
return this.Flush();
}
else
{
return Task.FromResult(true);
}
});
}
private bool CancelTimer()
{
if (this.timerCancellationSource != null)
{
// Attempt to cancel the timer task
this.timerCancellationSource.Cancel();
}
// Was the task cancelled?
bool wasCancelled =
this.currentTimerTask == null ||
this.currentTimerTask.IsCanceled;
// Clear the current task so that another may be created
this.currentTimerTask = null;
return wasCancelled;
}
#endregion
}
}

View File

@@ -0,0 +1,103 @@
//
// 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.PowerShell.EditorServices.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
}
}

View File

@@ -0,0 +1,155 @@
//
// 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.PowerShell.EditorServices.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
}
}

View File

@@ -0,0 +1,34 @@
//
// 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.PowerShell.EditorServices.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;
}
}
}

View File

@@ -0,0 +1,229 @@
//
// 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.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace Microsoft.PowerShell.EditorServices.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;
/// <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 = "EditorServices.log",
LogLevel minimumLogLevel = LogLevel.Normal)
{
if (logWriter != null)
{
logWriter.Dispose();
}
// TODO: Parameterize this
logWriter =
new LogWriter(
minimumLogLevel,
logFilePath,
true);
}
/// <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)
{
if (logWriter != null)
{
logWriter.Write(
logLevel,
logMessage,
callerName,
callerSourceFile,
callerLineNumber);
}
}
}
internal class LogWriter : IDisposable
{
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(
// #if NanoServer
// AppContext.BaseDirectory,
// #else
// AppDomain.CurrentDomain.BaseDirectory,
// #endif
// 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(
// #if NanoServer
// Environment.GetEnvironmentVariable("TEMP"),
// #else
// Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
// #endif
// 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)
{
// 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;
}
}
}
}

View File

@@ -0,0 +1,77 @@
//
// 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.PowerShell.EditorServices.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
}
}

View File

@@ -0,0 +1,143 @@
//
// 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.PowerShell.EditorServices.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, 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 IsNotNullOrEmptyString(string parameterName, string valueToCheck)
{
if (string.IsNullOrWhiteSpace(valueToCheck))
{
throw new ArgumentException(
"Parameter contains a null, empty, or whitespace string.",
parameterName);
}
}
}
}