(this T item, params T[] list)
{
return list.Contains(item);
}
///
/// A string is contained in a list of strings
///
/// string to look for
/// list of strings
///
///
public static bool Inlist(this string item, bool caseSensitive, params string[] list)
{
if (caseSensitive)
return list.Contains(item);
foreach (var listItem in list)
{
if (listItem.Equals(item, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/HtmlUtils.cs
================================================
using System;
using System.Text.RegularExpressions;
namespace Westwind.Utilities
{
///
/// Html string and formatting utilities
///
public static class HtmlUtils
{
///
/// Replaces and and Quote characters to HTML safe equivalents.
///
/// HTML to convert
/// Returns an HTML string of the converted text
public static string FixHTMLForDisplay(string html)
{
if (string.IsNullOrEmpty(html))
return html;
html = html.Replace("<", "<");
html = html.Replace(">", ">");
html = html.Replace("\"", """);
return html;
}
///
/// Strips HTML tags out of an HTML string and returns just the text.
///
/// Html String
///
public static string StripHtml(string html)
{
if (string.IsNullOrEmpty(html))
return html;
html = Regex.Replace(html, @"<(.|\n)*?>", string.Empty);
html = html.Replace("\t", " ");
html = html.Replace("\r\n", string.Empty);
html = html.Replace(" ", " ");
return html.Replace(" ", " ");
}
///
/// Fixes a plain text field for display as HTML by replacing carriage returns
/// with the appropriate br and p tags for breaks.
///
/// Input string
/// Fixed up string
public static string DisplayMemo(string htmlText)
{
if (htmlText == null)
return string.Empty;
htmlText = htmlText.Replace("\r\n", "\r");
htmlText = htmlText.Replace("\n", "\r");
//HtmlText = HtmlText.Replace("\r\r","");
htmlText = htmlText.Replace("\r", "
\r\n");
return htmlText;
}
///
/// Method that handles handles display of text by breaking text.
/// Unlike the non-encoded version it encodes any embedded HTML text
///
///
///
public static string DisplayMemoEncoded(string text)
{
if (text == null)
return string.Empty;
bool PreTag = false;
if (text.Contains("
"))
{
text = text.Replace("", "__pre__");
text = text.Replace("", "__/pre__");
PreTag = true;
}
// fix up line breaks into
text = DisplayMemo(System.Net.WebUtility.HtmlEncode(text)); //HttpUtility.HtmlEncode(Text));
if (PreTag)
{
text = text.Replace("__pre__", "
");
text = text.Replace("__/pre__", "");
}
return text;
}
///
/// HTML-encodes a string and returns the encoded string.
///
/// The text string to encode.
/// The HTML-encoded text.
[Obsolete("Use System.Net.WebUtility.HtmlEncode() instead.")]
public static string HtmlEncode(string text)
{
return System.Net.WebUtility.HtmlEncode(text);
//if (text == null)
// return string.Empty;
//StringBuilder sb = new StringBuilder(text.Length);
//int len = text.Length;
//for (int i = 0; i < len; i++)
//{
// switch (text[i])
// {
// case '<':
// sb.Append("<");
// break;
// case '>':
// sb.Append(">");
// break;
// case '"':
// sb.Append(""");
// break;
// case '&':
// sb.Append("&");
// break;
// case '\'':
// sb.Append("'");
// break;
// default:
// if (text[i] > 159)
// {
// // decimal numeric entity
// sb.Append("");
// sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture));
// sb.Append(";");
// }
// else
// sb.Append(text[i]);
// break;
// }
//}
//return sb.ToString();
}
///
/// Create an embedded image url for binary data like images and media
///
///
///
///
public static string BinaryToEmbeddedBase64(byte[] imageBytes, string mimeType = "image/png")
{
if (imageBytes == null) return null;
var data = $"data:{mimeType};base64," + Convert.ToBase64String(imageBytes, 0, imageBytes.Length);
return data;
}
#if NET6_0_OR_GREATER
///
/// Decoded an embedded base64 resource string into its binary content and mime type
///
/// Embedded Base64 data (data:mime/type;b64data)
///
public static (byte[] bytes, string mimeType) EmbeddedBase64ToBinary(string base64Data)
{
if (string.IsNullOrEmpty(base64Data))
return (null, null);
var parts = base64Data.Split(',');
if (parts.Length != 2)
return (null, null);
var mimeType = parts[0].Replace("data:", "").Replace(";base64", "");
var data = parts[1];
var bytes = Convert.FromBase64String(data);
return (bytes, mimeType);
}
#endif
///
/// Creates an Abstract from an HTML document. Strips the
/// HTML into plain text, then creates an abstract.
///
///
///
public static string HtmlAbstract(string html, int length)
{
if (string.IsNullOrEmpty(html))
return string.Empty;
return StringUtils.TextAbstract(StripHtml(html), length);
}
static string DefaultHtmlSanitizeTagBlackList { get; } = "script|iframe|object|embed|form";
static Regex _RegExScript = new Regex($@"(<({DefaultHtmlSanitizeTagBlackList})\b[^<]*(?:(?!<\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\/({DefaultHtmlSanitizeTagBlackList})>)",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
// strip javascript: and unicode representation of javascript:
// href='javascript:alert(\"gotcha\")'
// href='javascript:alert(\"gotcha\");'
static Regex _RegExJavaScriptHref = new Regex(
@"<[^>]*?\s(href|src|dynsrc|lowsrc)=.{0,20}((javascript:)|()).*?>",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
static Regex _RegExOnEventAttributes = new Regex(
@"<[^>]*?\s(on[^\s\\]{0,20}=([""].*?[""]|['].*?['])).*?(>|\/>)",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
///
/// Sanitizes HTML to some of the most of
///
///
/// This provides rudimentary HTML sanitation catching the most obvious
/// XSS script attack vectors. For mroe complete HTML Sanitation please look into
/// a dedicated HTML Sanitizer.
///
/// input html
/// A list of HTML tags that are stripped.
/// Sanitized HTML
public static string SanitizeHtml(string html, string htmlTagBlacklist = "script|iframe|object|embed|form")
{
if (string.IsNullOrEmpty(html))
return html;
if (string.IsNullOrEmpty(htmlTagBlacklist) || htmlTagBlacklist == DefaultHtmlSanitizeTagBlackList)
{
// Use the default list of tags Replace Script tags - reused expr is more efficient
html = _RegExScript.Replace(html, string.Empty);
}
else
{
// create a custom list including provided tags
html = Regex.Replace(html,
$@"(<({htmlTagBlacklist})\b[^<]*(?:(?!<\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\/({htmlTagBlacklist})>)",
"", RegexOptions.IgnoreCase | RegexOptions.Multiline);
}
// Remove javascript: directives
var matches = _RegExJavaScriptHref.Matches(html);
foreach (Match match in matches)
{
if (match.Groups.Count > 2)
{
var txt = match.Value.Replace(match.Groups[2].Value, "unsupported:");
html = html.Replace(match.Value, txt);
}
}
// Remove onEvent handlers from elements
matches = _RegExOnEventAttributes.Matches(html);
foreach (Match match in matches)
{
var txt = match.Value;
if (match.Groups.Count > 1)
{
var onEvent = match.Groups[1].Value;
txt = txt.Replace(onEvent, string.Empty);
if (!string.IsNullOrEmpty(txt))
html = html.Replace(match.Value, txt);
}
}
return html;
}
///
/// Create an Href HTML link.
///
///
///
/// For NetFx in ASPX applications ~ resolves to the application root
///
///
///
///
///
///
///
public static string Href(string text, string url, string target = null, string additionalMarkup = null)
{
#if NETFULL
if (url.StartsWith("~"))
url = ResolveUrl(url);
#endif
return "" + text + "";
}
///
/// Creates an HREF HTML Link
///
///
public static string Href(string url)
{
return Href(url, url, null, null);
}
///
/// Returns an IMG link as a string. If the image is null
/// or empty a blank string is returned.
///
///
/// any additional attributes added to the element
///
public static string ImgRef(string imageUrl, string additionalMarkup = null)
{
if (string.IsNullOrEmpty(imageUrl))
return string.Empty;
#if NETFULL
if (imageUrl.StartsWith("~"))
imageUrl = ResolveUrl(imageUrl);
#endif
string img = "
";
return img;
}
#if NETFULL
///
/// Resolves a URL based on the current HTTPContext
///
/// Note this method is added here internally only
/// to support the HREF() method and ~ expansion
/// on urls.
///
///
///
public static string ResolveUrl(string originalUrl)
{
if (string.IsNullOrEmpty(originalUrl))
return string.Empty;
// Absolute path - just return
if (originalUrl.IndexOf("://") != -1)
return originalUrl;
// Fix up image path for ~ root app dir directory
if (originalUrl.StartsWith("~"))
{
//return VirtualPathUtility.ToAbsolute(originalUrl);
string newUrl = "";
// Avoid pulling in System.Web reference
dynamic context = ReflectionUtils.GetStaticProperty( "System.Web.HttpContext", "Current");
if (context != null)
{
newUrl = context.Request.ApplicationPath +
originalUrl.Substring(1);
newUrl = newUrl.Replace("//", "/"); // must fix up for root path
}
else
// Not context: assume current directory is the base directory
throw new ArgumentException("Invalid URL: Relative Urls are only available in classic ASP.NET Web applications.");
// Just to be sure fix up any double slashes
return newUrl;
}
return originalUrl;
}
#endif
#region Url Parsing
///
/// Returns the base URL of a site from a full URL
///
/// An absolute scheme path (http:// or file:// or ftp:// etc)
///
public static string GetSiteBasePath(string url)
{
var uri = new Uri(url);
return $"{uri.Scheme}://{uri.Authority}/";
}
///
/// Returns the path portion of an absolute URL optionally with the query string and fragment
///
///
///
///
public static string GetRelativeUrlPath(string url, PathReturnOptions pathOptions = PathReturnOptions.PathOnly )
{
var uri = new Uri(url);
switch (pathOptions)
{
case PathReturnOptions.PathOnly:
return uri.AbsolutePath;
case PathReturnOptions.PathAndQuery:
return uri.PathAndQuery;
case PathReturnOptions.PathAndHash:
return uri.AbsolutePath + uri.Fragment;
case PathReturnOptions.PathAndQueryAndHash:
return uri.PathAndQuery + uri.Fragment;
}
return uri.PathAndQuery;
}
#endregion
}
public enum PathReturnOptions
{
PathOnly,
PathAndQuery,
PathAndHash,
PathAndQueryAndHash,
}
}
================================================
FILE: Westwind.Utilities/Utilities/HttpClientUtils.cs
================================================
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Westwind.Utilities
{
///
/// Http Client wrapper that provides single line access for common Http requests
/// that return string, Json or binary content.
///
/// Most methods have dual versions using simple parameters, or an
/// `HttpClientRequestSettings` configuration and results object that is
/// passed through on requests.
///
public class HttpClientUtils
{
public const string STR_MultipartBoundary = "----FormBoundary3vDSIXiW0WSTB551";
#region String Download
///
/// Runs an Http request and returns success results as a string or null
/// on failure or non-200/300 requests.
///
/// Failed requests return null and set the settings.ErrorMessage property
/// but you can can access the `settings.Response` or use the
/// `settings.GetResponseStringAsync()` method or friends to retrieve
/// content despite the error.
///
///
/// By default this method does not throw on errors or error status codes,
/// but returns null and an error message. For error requests you can check
/// settings.HasResponseContent and then either directly access settings.Response,
/// or use settings.GetResponseStringAsync() or friends to retrieve error content.
///
/// If you want the method to throw exceptions on errors an > 400 status codes,
/// use `settings.ThrowExceptions = true`.
///
/// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more
/// string of HTTP response
public static async Task DownloadStringAsync(HttpClientRequestSettings settings)
{
string content = null;
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = await client.SendAsync(settings.Request);
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
return null;
}
// Capture the response content
try
{
if (settings.Response.IsSuccessStatusCode)
{
// http 201 no content may return null and be success
if (settings.HasResponseContent)
{
if (settings.MaxResponseSize > 0)
{
using (var stream = await settings.Response.Content.ReadAsStreamAsync())
{
var buffer = new byte[settings.MaxResponseSize];
_ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize);
content = settings.Encoding.GetString(buffer);
}
}
else
{
content = await settings.Response.Content.ReadAsStringAsync();
}
}
return content;
}
settings.HasErrors = true;
settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " +
settings.Response.StatusCode.ToString();
if (settings.ThrowExceptions)
throw new HttpRequestException(settings.ErrorMessage);
// return null but allow for explicit response reading
return null;
}
catch (Exception ex)
{
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return null;
}
}
}
#if NET6_0_OR_GREATER
#endif
///
/// Runs an Http request and returns success results as a string or null
/// on failure or non-200/300 requests.
///
/// Failed requests return null and set the settings.ErrorMessage property
/// but you can can access the `settings.Response` or use the
/// `settings.GetResponseStringAsync()` method or friends to retrieve
/// content despite the error.
///
///
/// By default this method does not throw on errors or error status codes,
/// but returns null and an error message. For error requests you can check
/// settings.HasResponseContent and then either directly access settings.Response,
/// or use settings.GetResponseStringAsync() or friends to retrieve error content.
///
/// If you want the method to throw exceptions on errors an > 400 status codes,
/// use `settings.ThrowExceptions = true`.
///
/// The url to access
/// The data to send. String data is sent as is all other data is JSON encoded.
/// Optional Content type for the request
/// The HTTP Verb to use (GET,POST,PUT,DELETE etc.)
/// string of HTTP response
public static async Task DownloadStringAsync(string url, object data = null, string contentType = null,
string verb = null)
{
if (string.IsNullOrEmpty(verb))
{
if (data != null)
verb = "POST";
else
verb = "GET";
}
return await DownloadStringAsync(new HttpClientRequestSettings
{
Url = url,
HttpVerb = verb,
RequestContent = data,
RequestContentType = contentType
});
}
#if NET6_0_OR_GREATER
///
/// Synchronous version of `DownloadStringAsync`.
///
/// Request/Response settings instance
/// string or null on failure
public static string DownloadString(HttpClientRequestSettings settings)
{
string content = null;
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = client.Send(settings.Request);
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
return null;
}
// Capture the response content
try
{
if (settings.Response.IsSuccessStatusCode)
{
// http 201 no content may return null and be success
if (settings.HasResponseContent)
{
if (settings.MaxResponseSize > 0)
{
using (var stream = settings.Response.Content.ReadAsStream())
{
var buffer = new byte[settings.MaxResponseSize];
_ = stream.ReadAsync(buffer, 0, settings.MaxResponseSize);
content = settings.Encoding.GetString(buffer);
}
}
else
{
//content = await settings.Response.Content.ReadAsStringAsync();
using (var stream = settings.Response.Content.ReadAsStream())
{
var sr = new StreamReader(stream, true);
content = sr.ReadToEnd();
}
}
}
return content;
}
settings.HasErrors = true;
settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " +
settings.Response.StatusCode.ToString();
if (settings.ThrowExceptions)
throw new HttpRequestException(settings.ErrorMessage);
// return null but allow for explicit response reading
return null;
}
catch (Exception ex)
{
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return null;
}
}
}
///
/// Runs an Http request and returns success results as a string or null
/// on failure or non-200/300 requests.
///
/// Failed requests return null and set the settings.ErrorMessage property
/// but you can can access the `settings.Response` or use the
/// `settings.GetResponseStringAsync()` method or friends to retrieve
/// content despite the error.
///
///
/// By default this method does not throw on errors or error status codes,
/// but returns null and an error message. For error requests you can check
/// settings.HasResponseContent and then either directly access settings.Response,
/// or use settings.GetResponseStringAsync() or friends to retrieve error content.
///
/// If you want the method to throw exceptions on errors an > 400 status codes,
/// use `settings.ThrowExceptions = true`.
///
/// The url to access
/// The data to send. String data is sent as is all other data is JSON encoded.
/// Optional Content type for the request
/// The HTTP Verb to use (GET,POST,PUT,DELETE etc.)
/// string of HTTP response
public static string DownloadString(string url, object data = null, string contentType = null,
string verb = null)
{
if (string.IsNullOrEmpty(verb))
{
if (data != null)
verb = "POST";
else
verb = "GET";
}
return DownloadString(new HttpClientRequestSettings
{
Url = url,
HttpVerb = verb,
RequestContent = data,
RequestContentType = contentType
});
}
#endif
#endregion
#region File Download
///
/// Downloads a Url to a file.
///
/// Optional Url to download - settings.Url works too
/// Option filename to download to - settings.OutputFilename works too
/// Http request settings can be used in lieu of other parameters
///
/// true or fals
public static async Task DownloadFileAsync(HttpClientRequestSettings settings, string filename = null)
{
if (settings == null)
return false;
if (string.IsNullOrEmpty(settings.OutputFilename))
{
settings.HasErrors = true;
settings.ErrorMessage = "No ouput file provided. Provide `filename` parameter or `settings.OutputFilename`.";
return false;
}
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = await client.SendAsync(settings.Request);
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
return false;
}
// Capture the response content
try
{
if (settings.Response.IsSuccessStatusCode)
{
// http 201 no content may return null and be success
if (File.Exists(settings.OutputFilename))
File.Delete(settings.OutputFilename);
if (settings.HasResponseContent)
{
if (settings.MaxResponseSize > 0)
{
using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,
FileAccess.Write))
{
using (var stream = await settings.Response.Content.ReadAsStreamAsync())
{
var buffer = new byte[settings.MaxResponseSize];
_ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize);
await outputStream.WriteAsync(buffer, 0, buffer.Length);
}
}
}
else
{
using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,
FileAccess.Write))
{
using (var stream = await settings.Response.Content.ReadAsStreamAsync())
{
await stream.CopyToAsync(outputStream, 8 * 1024);
}
}
}
}
return true;
}
settings.HasErrors = true;
settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " +
settings.Response.StatusCode.ToString();
if (settings.ThrowExceptions)
throw new HttpRequestException(settings.ErrorMessage);
// return null but allow for explicit response reading
return false;
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return false;
}
}
}
///
/// Downloads a Url to a file.
///
/// Optional Url to download - settings.Url works too
/// Option filename to download to - settings.OutputFilename works too
/// Http request settings can be used in lieu of other parameters
///
/// true or fals
public static Task DownloadFileAsync(string url, string filename)
{
var settings = new HttpClientRequestSettings();
if (!string.IsNullOrEmpty(url))
settings.Url = url;
if (!string.IsNullOrEmpty(filename))
settings.OutputFilename = filename;
return DownloadFileAsync(settings);
}
#if NET6_0_OR_GREATER
///
/// Downloads a Url to a file.
///
/// Optional Url to download - settings.Url works too
/// Option filename to download to - settings.OutputFilename works too
/// Http request settings can be used in lieu of other parameters
///
/// true or fals
public static bool DownloadFile(HttpClientRequestSettings settings, string filename = null)
{
if (settings == null)
return false;
if (string.IsNullOrEmpty(settings.OutputFilename))
{
settings.HasErrors = true;
settings.ErrorMessage = "No ouput file provided. Provide `filename` parameter or `settings.OutputFilename`.";
return false;
}
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = client.Send(settings.Request);
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
return false;
}
// Capture the response content
try
{
if (settings.Response.IsSuccessStatusCode)
{
// http 201 no content may return null and be success
if (File.Exists(settings.OutputFilename))
File.Delete(settings.OutputFilename);
if (settings.HasResponseContent)
{
if (settings.MaxResponseSize > 0)
{
using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,
FileAccess.Write))
{
using (var stream = settings.Response.Content.ReadAsStream())
{
var buffer = new byte[settings.MaxResponseSize];
_ = stream.Read(buffer, 0, settings.MaxResponseSize);
outputStream.Write(buffer, 0, buffer.Length);
}
}
}
else
{
using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,
FileAccess.Write))
{
using (var stream =settings.Response.Content.ReadAsStream())
{
stream.CopyTo(outputStream, 8 * 1024);
}
}
}
}
return true;
}
settings.HasErrors = true;
settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " +
settings.Response.StatusCode.ToString();
if (settings.ThrowExceptions)
throw new HttpRequestException(settings.ErrorMessage);
// return null but allow for explicit response reading
return false;
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return false;
}
}
}
///
/// Downloads a Url to a file.
///
/// Optional Url to download - settings.Url works too
/// Option filename to download to - settings.OutputFilename works too
/// Http request settings can be used in lieu of other parameters
///
/// true or fals
public static bool DownloadFile(string url, string filename)
{
var settings = new HttpClientRequestSettings();
if (!string.IsNullOrEmpty(url))
settings.Url = url;
if (!string.IsNullOrEmpty(filename))
settings.OutputFilename = filename;
return DownloadFile(settings);
}
#endif
///
/// Downloads an image to a temporary file or a file you specify, automatically
/// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)
/// Always use the return value to receive the final image file name.
///
/// If you don't pass a file a temporary file is created in Temp Files folder.
/// You're responsible for cleaning up the file after you are done with it.
///
/// You should check the filename that is returned regardless of whether you
/// passed in a filename - if the file is of a different image type the
/// extension may be changed.
///
/// Url of image to download
///
/// Optional output image file name. Filename may change extension if the image format doesn't match the filename.
/// If not passed a temporary files file is created in the temp file location and you can move the file
/// manually. If using the temporary file, caller is responsible for cleaning up the file after creation.
///
/// Optional more detailed Http Settings for the request
/// file name that was created or null
public static async Task DownloadImageToFileAsync(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null)
{
if (settings == null)
{
if (string.IsNullOrEmpty(imageUrl))
return null;
settings = new();
}
if (!string.IsNullOrEmpty(imageUrl))
settings.Url = imageUrl;
imageUrl = settings.Url;
if (!string.IsNullOrEmpty(filename))
settings.OutputFilename = filename;
filename = settings.OutputFilename;
if (string.IsNullOrEmpty(imageUrl) ||
!imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://"))
return null;
// if no filename is specified at all use a temp file
if (string.IsNullOrEmpty(filename))
{
filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId());
}
// we download to a temp file
filename = Path.ChangeExtension(filename, "bin");
string newFilename;
try
{
settings.OutputFilename = filename;
await DownloadFileAsync(settings);
var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType);
if (ext == null)
{
if (File.Exists(filename))
File.Delete(filename);
return null; // invalid image type
}
newFilename = Path.ChangeExtension(filename, ext);
if (File.Exists(newFilename))
File.Delete(newFilename);
// rename the file
File.Move(filename, newFilename);
}
catch
{
if (File.Exists(filename))
File.Delete(filename);
return null;
}
return newFilename;
}
///
/// Downloads an image to a temporary file or a file you specify, automatically
/// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)
/// Always use the return value to receive the final image file name.
///
/// If you don't pass a file a temporary file is created in Temp Files folder.
/// You're responsible for cleaning up the file after you are done with it.
///
/// You should check the filename that is returned regardless of whether you
/// passed in a filename - if the file is of a different image type the
/// extension may be changed.
///
/// Must specify Url and optionally OutputFilename - see parametered version
/// file name that was created or null
public static async Task DownloadImageToFileAsync( HttpClientRequestSettings settings = null) => await DownloadImageToFileAsync(null, null, settings);
#if NET6_0_OR_GREATER
///
/// Downloads an image to a temporary file or a file you specify, automatically
/// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)
/// Always use the return value to receive the final image file name.
///
/// If you don't pass a file a temporary file is created in Temp Files folder.
/// You're responsible for cleaning up the file after you are done with it.
///
/// You should check the filename that is returned regardless of whether you
/// passed in a filename - if the file is of a different image type the
/// extension may be changed.
///
/// Url of image to download
///
/// Optional output image file name. Filename may change extension if the image format doesn't match the filename.
/// If not passed a temporary files file is created in the temp file location and you can move the file
/// manually. If using the temporary file, caller is responsible for cleaning up the file after creation.
///
/// Optional more detailed Http Settings for the request
/// file name that was created or null
public static string DownloadImageToFile(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null)
{
if (settings == null)
{
if (string.IsNullOrEmpty(imageUrl))
return null;
settings = new();
}
if (!string.IsNullOrEmpty(imageUrl))
settings.Url = imageUrl;
imageUrl = settings.Url;
if (!string.IsNullOrEmpty(filename))
settings.OutputFilename = filename;
filename = settings.OutputFilename;
if (string.IsNullOrEmpty(imageUrl) ||
!imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://"))
return null;
// if no filename is specified at all use a temp file
if (string.IsNullOrEmpty(filename))
{
filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId());
}
// we download to a temp file
filename = Path.ChangeExtension(filename, "bin");
string newFilename;
try
{
settings.OutputFilename = filename;
DownloadFile(settings);
var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType);
if (ext == null)
{
if (File.Exists(filename))
File.Delete(filename);
return null; // invalid image type
}
newFilename = Path.ChangeExtension(filename, ext);
if (File.Exists(newFilename))
File.Delete(newFilename);
// rename the file
File.Move(filename, newFilename);
}
catch
{
if (File.Exists(filename))
File.Delete(filename);
return null;
}
return newFilename;
}
///
/// Downloads an image to a temporary file or a file you specify, automatically
/// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)
/// Always use the return value to receive the final image file name.
///
/// If you don't pass a file a temporary file is created in Temp Files folder.
/// You're responsible for cleaning up the file after you are done with it.
///
/// You should check the filename that is returned regardless of whether you
/// passed in a filename - if the file is of a different image type the
/// extension may be changed.
///
/// Must specify Url and optionally OutputFilename - see parametered version
/// file name that was created or null
public static string DownloadImageToFile(HttpClientRequestSettings settings = null) => DownloadImageToFile(null, null, settings);
#endif
#endregion
#region Byte Data Download
///
/// Runs an Http Request and returns a byte array from the response or null on failure
///
/// Pass in a settings object
/// byte[] or null - if null check settings for errors
public static async Task DownloadBytesAsync(HttpClientRequestSettings settings)
{
byte[] content = null;
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = await client.SendAsync(settings.Request);
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
return null;
}
// Capture the response content
try
{
if (settings.Response.IsSuccessStatusCode)
{
// http 201 no content may return null and be success
if (settings.HasResponseContent)
{
if (settings.MaxResponseSize > 0)
{
using (var stream = await settings.Response.Content.ReadAsStreamAsync())
{
var buffer = new byte[settings.MaxResponseSize];
_ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize);
content = buffer;
}
}
else
{
content = await settings.Response.Content.ReadAsByteArrayAsync();
}
}
return content;
}
settings.HasErrors = true;
settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " +
settings.Response.StatusCode.ToString();
if (settings.ThrowExceptions)
throw new HttpRequestException(settings.ErrorMessage);
// return null but allow for explicit response reading
return null;
}
catch (Exception ex)
{
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return null;
}
}
}
#if Net6_0_OR_GREATER
public static string DownloadImageToFile(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null)
{
if (settings == null)
{
if (string.IsNullOrEmpty(imageUrl))
return null;
settings = new();
}
if (!string.IsNullOrEmpty(imageUrl))
settings.Url = imageUrl;
imageUrl = settings.Url;
if (!string.IsNullOrEmpty(filename))
settings.OutputFilename = filename;
filename = settings.OutputFilename;
if (string.IsNullOrEmpty(imageUrl) ||
!imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://"))
return null;
// if no filename is specified at all use a temp file
if (string.IsNullOrEmpty(filename))
{
filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId());
}
// we download to a temp file
filename = Path.ChangeExtension(filename, "bin");
string newFilename;
try
{
settings.OutputFilename = filename;
DownloadFile(settings);
var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType);
if (ext == null)
{
if (File.Exists(filename))
File.Delete(filename);
return null; // invalid image type
}
newFilename = Path.ChangeExtension(filename, ext);
if (File.Exists(newFilename))
File.Delete(newFilename);
// rename the file
File.Move(filename, newFilename);
}
catch
{
if (File.Exists(filename))
File.Delete(filename);
return null;
}
return newFilename;
}
#endif
public static async Task DownloadBytesAsync(string url, object data = null, string contentType = null,
string verb = null)
{
if (string.IsNullOrEmpty(verb))
{
if (data != null)
verb = "POST";
else
verb = "GET";
}
return await DownloadBytesAsync(new HttpClientRequestSettings
{
Url = url,
HttpVerb = verb,
RequestContent = data,
RequestContentType = contentType
});
}
#if NET6_0_OR_GREATER
///
/// Synchronous version of `DownloadBytesAsync`.
///
/// Request/Response settings instance
/// string or null on failure
public static byte[] DownloadBytes(HttpClientRequestSettings settings)
{
byte[] content = null;
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = client.Send(settings.Request);
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
return null;
}
// Capture the response content
try
{
if (settings.Response.IsSuccessStatusCode)
{
// http 201 no content may return null and be success
if (settings.HasResponseContent)
{
if (settings.MaxResponseSize > 0)
{
using (var stream = settings.Response.Content.ReadAsStream())
{
var buffer = new byte[settings.MaxResponseSize];
_ = stream.ReadAsync(buffer, 0, settings.MaxResponseSize);
content = buffer;
}
}
else
{
using (var stream = settings.Response.Content.ReadAsStream())
{
using (var ms = new MemoryStream())
{
stream.CopyTo(ms);
content = ms.ToArray();
}
}
}
}
return content;
}
settings.HasErrors = true;
settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " +
settings.Response.StatusCode.ToString();
if (settings.ThrowExceptions)
throw new HttpRequestException(settings.ErrorMessage);
// return null but allow for explicit response reading
return null;
}
catch (Exception ex)
{
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return null;
}
}
}
///
/// Synchronous version of `DownloadBytesAsync`.
///
/// Request URL
/// Request data
/// Request content type
/// HTTP verb (GET, POST, etc.)
/// string or null on failure
public static byte[] DownloadBytes(string url, object data = null, string contentType = null,
string verb = null)
{
if (string.IsNullOrEmpty(verb))
{
if (data != null)
verb = "POST";
else
verb = "GET";
}
return DownloadBytes(new HttpClientRequestSettings
{
Url = url,
HttpVerb = verb,
RequestContent = data,
RequestContentType = contentType
});
}
#endif
#endregion
#region Json
///
/// Makes a JSON request that returns a JSON result.
///
/// Result type to deserialize to
/// Configuration for this request
///
public static async Task DownloadJsonAsync(HttpClientRequestSettings settings)
{
settings.RequestContentType = "application/json";
settings.Encoding = Encoding.UTF8;
string json = await DownloadStringAsync(settings);
if (json == null)
{
return default;
}
try
{
return JsonConvert.DeserializeObject(json);
}
catch (Exception ex)
{
// original error has priority
if (settings.HasErrors)
return default;
settings.HasErrors = true;
settings.ErrorMessage = ex.GetBaseException().Message;
settings.ErrorException = ex;
}
return default;
}
public static async Task DownloadJsonAsync(string url, string verb = "GET",
object data = null)
{
return await DownloadJsonAsync(new HttpClientRequestSettings
{
Url = url,
HttpVerb = verb,
RequestContent = data,
RequestContentType = data != null ? "application/json" : null
});
}
#if NET6_0_OR_GREATER
///
/// Makes a JSON request that returns a JSON result.
///
/// Result type to deserialize to
/// Configuration for this request
/// Result or null - check ErrorMessage in settings on failure
public static TResult DownloadJson(HttpClientRequestSettings settings)
{
settings.RequestContentType = "application/json";
settings.Encoding = Encoding.UTF8;
string json = DownloadString(settings);
if (json == null)
{
return default;
}
try
{
return JsonConvert.DeserializeObject(json);
}
catch (Exception ex)
{
// original error has priority
if (settings.HasErrors)
return default;
settings.HasErrors = true;
settings.ErrorMessage = ex.GetBaseException().Message;
settings.ErrorException = ex;
}
return default;
}
///
/// Makes a JSON request that returns a JSON result.
///
/// Request URL
/// Http Verb to use. Defaults to GET on no data or POST when data is passed.
/// Data to be serialized to JSON for sending
/// result or null
public static TResult DownloadJson(string url, string verb = "GET", object data = null)
{
return DownloadJson(new HttpClientRequestSettings
{
Url = url,
HttpVerb = verb,
RequestContent = data,
RequestContentType = data != null ? "application/json" : null
});
}
#endif
#endregion
#region Http Response
///
/// Calls a URL and returns the raw, unretrieved HttpResponse. Also set on settings.Response and you
/// can read the response content from settings.Response.Content.ReadAsXXX() methods.
///
/// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more
/// string of HTTP response
public static async Task DownloadResponseMessageAsync(HttpClientRequestSettings settings)
{
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = await client.SendAsync(settings.Request);
if (settings.ThrowExceptions && !settings.Response.IsSuccessStatusCode)
throw new HttpRequestException(settings.ResponseStatusCode + " " +
settings.Response.ReasonPhrase);
return settings.Response;
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return null;
}
}
}
#if NET6_0_OR_GREATER
///
/// Calls a URL and returns the raw, unretrieved HttpResponse. Also set on settings.Response and you
/// can read the response content from settings.Response.Content.ReadAsXXX() methods.
///
/// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more
/// string of HTTP response
public static HttpResponseMessage DownloadResponseMessage(HttpClientRequestSettings settings)
{
using (var client = GetHttpClient(null, settings))
{
try
{
settings.Response = client.Send(settings.Request);
if (settings.ThrowExceptions && !settings.Response.IsSuccessStatusCode)
throw new HttpRequestException(settings.ResponseStatusCode + " " +
settings.Response.ReasonPhrase);
return settings.Response;
}
catch (Exception ex)
{
settings.HasErrors = true;
settings.ErrorException = ex;
settings.ErrorMessage = ex.GetBaseException().Message;
if (settings.ThrowExceptions)
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
return null;
}
}
}
#endif
#endregion
#region Helpers
///
/// Creates an instance of the HttpClient and sets the API Key
/// in the headers.
///
/// Configured HttpClient instance
public static HttpClient GetHttpClient(HttpClientHandler handler = null,
HttpClientRequestSettings settings = null)
{
if (settings == null)
settings = new HttpClientRequestSettings();
handler = handler ?? new HttpClientHandler()
{
Proxy = settings.Proxy,
Credentials = settings.Credentials,
};
#if NET6_0_OR_GREATER
if (settings.IgnoreCertificateErrors)
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
#endif
var client = new HttpClient(handler);
client.DefaultRequestHeaders.UserAgent.ParseAdd(settings.UserAgent);
ApplySettingsToRequest(settings);
return client;
}
///
/// Creates a new Request on the Settings object and assigns the settings values
/// to the request object.
///
/// Settings instance
public static void ApplySettingsToRequest(HttpClientRequestSettings settings)
{
settings.Request = new HttpRequestMessage
{
RequestUri = new Uri(settings.Url),
Method = new HttpMethod(settings.HttpVerb ?? "GET"),
Version = new Version(settings.HttpVersion)
};
foreach (var header in settings.Headers)
{
SetHttpHeader(settings.Request, header.Key, header.Value);
}
if (settings.HasPostData)
{
settings.RequestContentType = settings.RequestFormPostMode == HttpFormPostMode.MultiPart
? "multipart/form-data; boundary=" + HttpClientUtils.STR_MultipartBoundary
: "application/x-www-form-urlencoded";
settings.RequestContent = settings.GetPostBufferBytes();
}
if (settings.RequestContent != null &&
(settings.HttpVerb.Equals("POST", StringComparison.OrdinalIgnoreCase) ||
settings.HttpVerb.Equals("PUT", StringComparison.OrdinalIgnoreCase) ||
settings.HttpVerb.Equals("PATCH", StringComparison.OrdinalIgnoreCase))
)
{
HttpContent content = null;
if (settings.RequestContent is string)
{
if (!settings.IsRawData && settings.RequestContentType == "application/json")
{
var jsonString = JsonSerializationUtils.Serialize(settings.RequestContent);
content = new StringContent(jsonString, settings.Encoding, settings.RequestContentType);
}
else
content = new StringContent(settings.RequestContent as string, settings.Encoding,
settings.RequestContentType);
}
else if (settings.RequestContent is byte[])
{
content = new ByteArrayContent(settings.RequestContent as byte[]);
settings.Request.Content = content;
settings.Request.Content?.Headers.Add("Content-Type", settings.RequestContentType);
}
else
{
if (!settings.IsRawData)
{
var jsonString = JsonSerializationUtils.Serialize(settings.RequestContent);
content = new StringContent(jsonString, settings.Encoding, settings.RequestContentType);
}
}
if (content != null)
settings.Request.Content = content;
}
}
private static void SetHttpHeader(HttpRequestMessage req, string header, string value)
{
if (string.IsNullOrEmpty(header) || string.IsNullOrEmpty(value))
return;
var lheader = header.ToLower();
if (lheader == "content-length")
return; // auto-generated
if (lheader == "content-type")
{
var contentType = value;
if (value == "multipart/form" && !value.Contains("boundary"))
{
contentType = "multipart/form-data; boundary=" + STR_MultipartBoundary;
}
req.Content?.Headers.Add("Content-Type", contentType);
return;
}
// content-type, content-encoding etc.
if (lheader.StartsWith("content-"))
{
req.Content?.Headers.Add(header, value);
return;
}
// set above view property
// not handled at the moment
if (lheader == "proxy-connection")
return;
req.Headers.Add(header, value);
}
#endregion
}
///
/// Configuration object for Http Requests used by the HttpClientUtils
/// methods. Allows you to set the URL, verb, headers proxy and
/// credentials that are then passed to the HTTP client.
///
public class HttpClientRequestSettings
{
///
/// The URL to send the request to
///
public string Url { get; set; }
///
/// The HTTP verb to use when sending the request
///
public string HttpVerb { get; set; }
///
/// The Request content to send to the server.
/// Data can be either string or byte[] type
///
public object RequestContent { get; set; }
///
/// Content Encoding for the data sent to to server
///
public Encoding Encoding { get; set; }
///
/// When true data is not translated. For example
/// when using JSON Request if you want to send
/// raw POST data rather than a serialized object.
///
public bool IsRawData { get; set; }
///
/// The content type of any request data sent to the server
/// in the Data property.
///
public string RequestContentType { get; set; } = "application/json";
///
/// The request timeout in milliseconds. 0 for default (20 seconds typically)
///
public int Timeout { get; set; }
///
/// Any Http request headers you want to set for this request
///
public Dictionary Headers { get; set; }
///
/// Authentication information for this request
///
public NetworkCredential Credentials { get; set; }
///
/// Determines whether credentials pre-authenticate
///
public bool PreAuthenticate { get; set; }
///
/// An optional proxy to set for this request
///
public IWebProxy Proxy { get; set; }
///
/// Capture request string data that was actually sent to the server.
///
public string CapturedRequestContent { get; set; }
///
/// Captured string Response Data from the server
///
public string CapturedResponseContent { get; set; }
///
/// Output file name for file download operations
///
public string OutputFilename { get; set; }
///
/// Capture binary Response data from the server when
/// using the Data methods rather than string methods.
///
public byte[] ResponseByteData { get; set; }
///
/// The HTTP Status code of the HTTP response
///
public HttpStatusCode ResponseStatusCode
{
get
{
if (Response != null)
return Response.StatusCode;
return HttpStatusCode.Unused;
}
}
///
/// Content Type of the response - may be null if there is no content or the content type is not set by server
///
public string ResponseContentType => Response?.Content?.Headers.ContentType?.MediaType;
///
/// Content Length of the response. -1 if there is no result content
///
public long ResponseContentLength => Response?.Content?.Headers?.ContentLength ?? -1;
///
/// Response content headers (content type, size, charset etc.) -
/// check for null if request did not succeed or doesn't produce content
///
public HttpContentHeaders ResponseContentHeaders => Response?.Content?.Headers;
///
/// Non-Content Response headers - check for null if request did not succeed
///
public HttpResponseHeaders ResponseHeaders => Response?.Headers;
public bool HasResponseContent
{
get
{
if (Response?.Content?.Headers == null)
return false;
return Response.Content.Headers.ContentLength > 0;
}
}
///
/// The User Agent string sent to the server
///
public string UserAgent { get; set; }
///
/// By default (false) throws a Web Exception on 500 and 400 repsonses.
/// This is the default WebClient behavior.
///
/// If `true` doesn't throw, but instead returns the HTTP response.
/// Useful if you need to return error messages on 500 and 400 responses
/// from API requests.
///
public bool ThrowExceptions { get; set; }
///
/// Http Protocol Version 1.1
///
public string HttpVersion { get; set; } = "1.1";
public HttpRequestMessage Request { get; set; }
public HttpResponseMessage Response { get; set; }
///
/// Determines whether the request has errors or
/// didn't return a 200/300 result code
///
public bool HasErrors { get; set; }
///
/// Error message if one was set
///
public string ErrorMessage { get; set; }
///
/// The full Execption object if an error occurred
///
public Exception ErrorException { get; set; }
public int MaxResponseSize { get; set; }
///
/// if true ignores most certificate errors (expired, not trusted)
///
#if !NET6_0_OR_GREATER
[Obsolete("This property is not supported in .NET Framework.")]
#endif
public bool IgnoreCertificateErrors { get; set; }
public HttpClientRequestSettings()
{
HttpVerb = "GET";
Headers = new Dictionary();
Encoding = Encoding.UTF8;
UserAgent = "West Wind .NET Http Client";
}
#region POST data
///
/// Determines how data is POSTed when when using AddPostKey() and other methods
/// of posting data to the server. Support UrlEncoded, Multi-Part, XML and Raw modes.
///
public HttpFormPostMode RequestFormPostMode { get; set; } = HttpFormPostMode.UrlEncoded;
// member properties
//string cPostBuffer = string.Empty;
internal MemoryStream PostStream;
internal BinaryWriter PostData;
internal bool HasPostData;
///
/// Resets the Post buffer by clearing out all existing content
///
public void ResetPostData()
{
PostStream = new MemoryStream();
PostData = new BinaryWriter(PostStream);
}
public void SetPostStream(Stream postStream)
{
MemoryStream ms = new MemoryStream(1024);
FileUtils.CopyStream(postStream, ms, 1024);
ms.Flush();
ms.Position = 0;
PostStream = ms;
PostData = new BinaryWriter(ms);
}
///
/// Adds POST form variables to the request buffer.
/// PostMode determines how parms are handled.
///
/// Key value or raw buffer depending on post type
/// Value to store. Used only in key/value pair modes
public void AddPostKey(string key, byte[] value)
{
if (value == null)
return;
if (key == "RESET")
{
ResetPostData();
return;
}
HasPostData = true;
if (PostData == null)
{
PostStream = new MemoryStream();
PostData = new BinaryWriter(PostStream);
}
if (string.IsNullOrEmpty(key))
PostData.Write(value);
else if (RequestFormPostMode == HttpFormPostMode.UrlEncoded)
PostData.Write(
Encoding.Default.GetBytes(key + "=" +
StringUtils.UrlEncode(Encoding.Default.GetString(value)) +
"&"));
else if (RequestFormPostMode == HttpFormPostMode.MultiPart)
{
Encoding iso = Encoding.GetEncoding("ISO-8859-1");
PostData.Write(iso.GetBytes(
"--" + HttpClientUtils.STR_MultipartBoundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + key + "\"\r\n\r\n"));
PostData.Write(value);
PostData.Write(iso.GetBytes("\r\n"));
}
else // Raw or Xml, JSON modes
PostData.Write(value);
}
///
/// Adds POST form variables to the request buffer.
/// PostMode determines how parms are handled.
///
/// Key value or raw buffer depending on post type
/// Value to store. Used only in key/value pair modes
public void AddPostKey(string key, string value)
{
if (value == null)
return;
AddPostKey(key, Encoding.Default.GetBytes(value));
}
///
/// Adds a fully self contained POST buffer to the request.
/// Works for XML or previously encoded content.
///
/// String based full POST buffer
public void AddPostKey(string fullPostBuffer)
{
AddPostKey(null, fullPostBuffer);
}
///
/// Adds a fully self contained POST buffer to the request.
/// Works for XML or previously encoded content.
///
/// Byte array of a full POST buffer
public void AddPostKey(byte[] fullPostBuffer)
{
AddPostKey(null, fullPostBuffer);
}
///
/// Allows posting a file to the Web Server. Make sure that you
/// set PostMode
///
///
///
/// Content type of the file to upload. Default is application/octet-stream
/// Optional filename to use in the Content-Disposition header. If not specified uses the file name of the file being uploaded.
/// true or false. Fails if the file is not found or couldn't be encoded
public bool AddPostFile(string key, string filename, string contentType = "application/octet-stream",
string contentFilename = null)
{
byte[] lcFile;
if (RequestFormPostMode != HttpFormPostMode.MultiPart)
{
ErrorMessage = "File upload allowed only with Multi-part forms";
HasErrors = true;
return false;
}
HasPostData = true;
try
{
FileStream loFile = new FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read);
lcFile = new byte[loFile.Length];
_ = loFile.Read(lcFile, 0, (int)loFile.Length);
loFile.Close();
}
catch (Exception e)
{
ErrorMessage = e.Message;
HasErrors = true;
return false;
}
if (PostData == null)
{
PostStream = new MemoryStream();
PostData = new BinaryWriter(PostStream);
}
if (string.IsNullOrEmpty(contentFilename))
contentFilename = new FileInfo(filename).Name;
PostData.Write(Encoding.Default.GetBytes(
"--" + HttpClientUtils.STR_MultipartBoundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" +
contentFilename + "\"\r\n" +
"Content-Type: " + contentType +
"\r\n\r\n"));
PostData.Write(lcFile);
PostData.Write(Encoding.Default.GetBytes("\r\n"));
return true;
}
///
/// Retrieves the accumulated postbuffer as a byte array
/// when AddPostKey() or AddPostFile() have been called.
///
/// the Post buffer or null if empty or not using
/// form post mode
public string GetPostBuffer()
{
var bytes = PostStream?.ToArray();
if (bytes == null)
return null;
var data = Encoding.Default.GetString(bytes);
if (RequestFormPostMode == HttpFormPostMode.MultiPart)
{
if (PostStream == null)
return null;
// add final boundary
data += "\r\n--" + HttpClientUtils.STR_MultipartBoundary + "--\r\n";
}
return data;
}
///
/// Retrieves the accumulated postbuffer as a byte array
/// when AddPostKey() or AddPostFile() have been called.
///
/// For multi-part forms this buffer can only be returned
/// once as a footer needs to be appended and we don't want
/// copy the buffer and double memory usage.
///
/// encoded POST buffer
public byte[] GetPostBufferBytes()
{
if (RequestFormPostMode == HttpFormPostMode.MultiPart)
{
if (PostStream == null)
return null;
// add final boundary
PostData.Write(Encoding.Default.GetBytes("\r\n--" + HttpClientUtils.STR_MultipartBoundary + "--\r\n"));
PostStream?.Flush();
}
return PostStream?.ToArray();
}
#endregion
///
/// Retrieves the response as a
///
///
public async Task GetResponseStringAsync()
{
if (Response == null)
return null;
try
{
return await Response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
HasErrors = true;
ErrorMessage = ex.GetBaseException().Message;
ErrorException = ex;
return null;
}
}
///
/// Returns deserialized JSON from the Response
///
///
///
public async Task GetResponseJson()
{
var json = await GetResponseStringAsync();
if (json == null)
return default;
return JsonSerializationUtils.Deserialize(json);
}
///
/// Returns an error message from a JSON error object that
/// contains a message or Message property.
///
///
public async Task GetResponseErrorMessage()
{
var obj = await GetResponseJson();
if (obj == null)
return null;
if (obj.ContainsKey("message"))
return obj["message"].ToString();
if (obj.ContainsKey("Message"))
return obj["Message"].ToString();
return null;
}
///
/// Returns byte data from the response
///
///
public async Task GetResponseDataAsync()
{
if (Response == null)
return null;
try
{
return await Response.Content.ReadAsByteArrayAsync();
}
catch (Exception ex)
{
HasErrors = true;
ErrorMessage = ex.GetBaseException().Message;
ErrorException = ex;
return null;
}
}
public override string ToString()
{
return $"{HttpVerb} {Url} {ErrorMessage}";
}
}
public enum HttpFormPostMode
{
UrlEncoded,
MultiPart
};
}
================================================
FILE: Westwind.Utilities/Utilities/HttpUtils.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Westwind.Utilities
{
///
/// Simple HTTP request helper to let you retrieve data from a Web
/// server and convert it to something useful.
///
[Obsolete("Use HttpClientUtils if possible.")]
public static class HttpUtils
{
///
/// Retrieves and Http request and returns data as a string.
///
/// A url to call for a GET request without custom headers
/// string of HTTP response
public static string HttpRequestString(string url)
{
return HttpRequestString(new HttpRequestSettings() { Url = url });
}
///
/// Retrieves and Http request and returns data as a string.
///
/// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more
/// string of HTTP response
public static string HttpRequestString(HttpRequestSettings settings)
{
var client = new HttpUtilsWebClient(settings);
if (settings.Content != null)
{
if (!string.IsNullOrEmpty(settings.ContentType))
client.Headers["Content-type"] = settings.ContentType;
if (settings.Content is string)
{
settings.CapturedRequestContent = settings.Content as string;
settings.CapturedResponseContent = client.UploadString(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);
}
else if (settings.Content is byte[])
{
settings.ResponseByteData = client.UploadData(settings.Url, settings.Content as byte[]);
settings.CapturedResponseContent = Encoding.UTF8.GetString(settings.ResponseByteData);
}
else
throw new ArgumentException("Data must be either string or byte[].");
}
else
settings.CapturedResponseContent = client.DownloadString(settings.Url);
settings.Response = client.Response;
return settings.CapturedResponseContent;
}
///
/// Retrieves bytes from the server without any request customizations
///
/// Url to access
///
public static byte[] HttpRequestBytes(string url)
{
return HttpRequestBytes(new HttpRequestSettings() { Url = url });
}
///
/// Retrieves bytes from the server
///
///
///
public static byte[] HttpRequestBytes(HttpRequestSettings settings)
{
var client = new HttpUtilsWebClient(settings);
if (settings.Content != null)
{
if (!string.IsNullOrEmpty(settings.ContentType))
client.Headers["Content-type"] = settings.ContentType;
if (settings.Content is string)
{
settings.CapturedRequestContent = settings.Content as string;
settings.ResponseByteData = client.UploadData(settings.Url, settings.HttpVerb, settings.Encoding.GetBytes(settings.CapturedRequestContent));
}
else if (settings.Content is byte[])
{
settings.ResponseByteData = client.UploadData(settings.Url, settings.Content as byte[]);
}
else
throw new ArgumentException("Data must be either string or byte[].");
}
else
settings.ResponseByteData = client.DownloadData(settings.Url);
settings.Response = client.Response;
return settings.ResponseByteData;
}
///
/// Retrieves and Http request and returns data as a string.
///
/// The Url to access
/// string of HTTP response
public static async Task HttpRequestStringAsync(string url)
{
return await HttpRequestStringAsync(new HttpRequestSettings() { Url = url });
}
///
/// Retrieves and Http request and returns data as a string.
///
/// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more
/// string of HTTP response
public static async Task HttpRequestStringAsync(HttpRequestSettings settings)
{
var client = new HttpUtilsWebClient(settings);
if (settings.Content != null)
{
if (!string.IsNullOrEmpty(settings.ContentType))
client.Headers["Content-type"] = settings.ContentType;
if (settings.Content is string)
{
settings.CapturedRequestContent = settings.Content as string;
settings.CapturedResponseContent = await client.UploadStringTaskAsync(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);
}
else if (settings.Content is byte[])
{
settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.Content as byte[]);
settings.CapturedResponseContent = Encoding.UTF8.GetString(settings.ResponseByteData);
}
else
throw new ArgumentException("Data must be either string or byte[].");
}
else
settings.CapturedResponseContent = await client.DownloadStringTaskAsync(new Uri(settings.Url));
settings.Response = client.Response;
return settings.CapturedResponseContent;
}
///
/// Retrieves bytes from the server without any request customizations
///
/// Url to access
///
public static async Task HttpRequestBytesAsync(string url)
{
return await HttpRequestBytesAsync(new HttpRequestSettings() { Url = url });
}
///
/// Retrieves bytes from the server
///
///
///
public static async Task HttpRequestBytesAsync(HttpRequestSettings settings)
{
var client = new HttpUtilsWebClient(settings);
if (settings.Content != null)
{
if (!string.IsNullOrEmpty(settings.ContentType))
client.Headers["Content-type"] = settings.ContentType;
if (settings.Content is string)
{
settings.CapturedRequestContent = settings.Content as string;
settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.HttpVerb, settings.Encoding.GetBytes(settings.CapturedRequestContent));
}
else if (settings.Content is byte[])
{
settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.Content as byte[]);
}
else
throw new ArgumentException("Data must be either string or byte[].");
}
else
settings.ResponseByteData = await client.DownloadDataTaskAsync(settings.Url);
settings.Response = client.Response;
return settings.ResponseByteData;
}
///
/// Makes an HTTP with option JSON data serialized from an object
/// and parses the result from JSON back into an object.
/// Assumes that the service returns a JSON response
///
/// The type of the object returned
///
/// Configuration object for the HTTP request made to the server.
///
/// deserialized value/object from returned JSON data
public static TResultType JsonRequest(HttpRequestSettings settings)
{
var client = new HttpUtilsWebClient(settings);
client.Headers.Add("Accept", "application/json");
string jsonResult;
if (settings.Content != null)
{
if (!string.IsNullOrEmpty(settings.ContentType))
client.Headers["Content-type"] = settings.ContentType;
else
client.Headers["Content-type"] = "application/json;charset=utf-8;";
if (!settings.IsRawData)
{
settings.CapturedRequestContent = JsonSerializationUtils.Serialize(settings.Content,
throwExceptions: true);
}
else
settings.CapturedRequestContent = settings.Content as string;
jsonResult = client.UploadString(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);
if (jsonResult == null)
return default(TResultType);
}
else
jsonResult = client.DownloadString(settings.Url);
settings.CapturedResponseContent = jsonResult;
settings.Response = client.Response;
return (TResultType)JsonSerializationUtils.Deserialize(jsonResult, typeof(TResultType), true);
}
///
/// Makes an HTTP with option JSON data serialized from an object
/// and parses the result from JSON back into an object.
/// Assumes that the service returns a JSON response and that
/// any data sent is json.
///
/// The type of the object returned
///
/// Configuration object for the HTTP request made to the server.
///
/// deserialized value/object from returned JSON data
public static async Task JsonRequestAsync(HttpRequestSettings settings)
{
var client = new HttpUtilsWebClient(settings);
client.Headers.Add("Accept", "application/json");
string jsonResult;
if (settings.HttpVerb == "POST" || settings.HttpVerb == "PUT" || settings.HttpVerb == "PATCH")
{
if (!string.IsNullOrEmpty(settings.ContentType))
client.Headers["Content-type"] = settings.ContentType;
else
client.Headers["Content-type"] = "application/json";
if (!settings.IsRawData)
settings.CapturedRequestContent = JsonSerializationUtils.Serialize(settings.Content,
throwExceptions: true);
else
settings.CapturedRequestContent = settings.Content as string;
jsonResult = await client.UploadStringTaskAsync(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);
if (jsonResult == null)
return default(TResultType);
}
else
jsonResult = await client.DownloadStringTaskAsync(settings.Url);
settings.CapturedResponseContent = jsonResult;
settings.Response = client.Response;
return (TResultType)JsonSerializationUtils.Deserialize(jsonResult, typeof(TResultType), true);
}
///
/// Creates a temporary image file from a download from a URL
///
/// If you don't pass a file a temporary file is created in Temp Files folder.
/// You're responsible for cleaning up the file after you are done with it.
///
/// You should check the filename that is returned regardless of whether you
/// passed in a filename - if the file is of a different image type the
/// extension may be changed.
///
/// Url of image to download
/// Optional output image file. Filename may change extension if the image format doesn't match the filename.
/// If not passed a temporary files file is created. Caller is responsible for cleaning up this file.
///
/// Optional Http Settings for the request
/// image filename or null on failure. Note that the filename may have a different extension than the request filename parameter.
public static string DownloadImageToFile(string imageUrl, string filename = null, HttpRequestSettings settings = null)
{
if (string.IsNullOrEmpty(imageUrl) ||
!imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://") )
return null;
string newFilename;
if (string.IsNullOrEmpty(filename))
{
filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId());
}
filename = Path.ChangeExtension(filename, "bin");
var client = new HttpUtilsWebClient(settings);
try
{
client.DownloadFile(imageUrl, filename);
var ct = client.Response.ContentType; // works
if (string.IsNullOrEmpty(ct) || !ct.StartsWith("image/"))
return null;
var ext = ImageUtils.GetExtensionFromMediaType(ct);
if (ext == null)
return null; // invalid image type
newFilename = Path.ChangeExtension(filename, ext);
if (File.Exists(newFilename))
File.Delete(newFilename);
// rename the file
File.Move(filename, newFilename);
}
catch
{
if (File.Exists(filename))
File.Delete(filename);
return null;
}
return newFilename;
}
///
/// Downloads an image to a temporary file or a file you specify, automatically
/// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)
/// Always use the return value to receive the final image file name.
///
/// If you don't pass a file a temporary file is created in Temp Files folder.
/// You're responsible for cleaning up the file after you are done with it.
///
/// You should check the filename that is returned regardless of whether you
/// passed in a filename - if the file is of a different image type the
/// extension may be changed.
///
/// Url of image to download
///
/// Optional output image file name. Filename may change extension if the image format doesn't match the filename.
/// If not passed a temporary files file is created in the temp file location and you can move the file
/// manually. If using the temporary file, caller is responsible for cleaning up the file after creation.
///
/// Optional more detailed Http Settings for the request
/// file name that was created or null
public static async Task DownloadImageToFileAsync(string imageUrl, string filename = null, HttpRequestSettings settings = null)
{
if (string.IsNullOrEmpty(imageUrl) ||
!imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://") )
return null;
string newFilename;
if (string.IsNullOrEmpty(filename))
{
filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId());
}
filename = Path.ChangeExtension(filename, "bin");
var client = new HttpUtilsWebClient(settings);
try
{
await client.DownloadFileTaskAsync(imageUrl, filename);
var ct = client.Response.ContentType;
var ext = ImageUtils.GetExtensionFromMediaType(ct);
if (ext == null)
{
if(File.Exists(filename))
File.Delete(filename);
return null; // invalid image type
}
newFilename = Path.ChangeExtension(filename, ext);
if (File.Exists(newFilename))
File.Delete(newFilename);
// rename the file
File.Move(filename, newFilename);
}
catch
{
if (File.Exists(filename))
File.Delete(filename);
return null;
}
return newFilename;
}
///
/// Helper method that creates a proxy instance to store on the Proxy property
///
///
/// Proxy Address to create or "default" for Windows default proxy.
/// Null or empty means no proxy is set
///
///
/// Optional - bypass on local if you're specifying an explicit url
///
///
/// Optional list of root domain Urls that are bypassed
///
///
public static IWebProxy CreateWebProxy(string proxyAddress = null, bool bypassonLocal = false, string[] bypassList = null)
{
IWebProxy proxy = null;
if (string.IsNullOrEmpty(proxyAddress))
return null;
if (proxyAddress.Equals("default", StringComparison.OrdinalIgnoreCase))
{
proxy = WebRequest.GetSystemWebProxy();
}
else
{
proxy = new WebProxy(proxyAddress, bypassonLocal, bypassList);
}
return proxy;
}
}
///
/// Configuration object for Http Requests used by the HttpUtils
/// methods. Allows you to set the URL, verb, headers proxy and
/// credentials that are then passed to the HTTP client.
///
public class HttpRequestSettings
{
///
/// The URL to send the request to
///
public string Url { get; set; }
///
/// The HTTP verb to use when sending the request
///
public string HttpVerb { get; set; }
///
/// The Request content to send to the server.
/// Data can be either string or byte[] type
///
public object Content { get; set; }
///
/// Content Encoding for the data sent to to server
///
public Encoding Encoding { get; set; }
///
/// When true data is not translated. For example
/// when using JSON Request if you want to send
/// raw POST data rather than a serialized object.
///
public bool IsRawData { get; set; }
///
/// The content type of any request data sent to the server
/// in the Data property.
///
public string ContentType { get; set; }
///
/// The request timeout in milliseconds. 0 for default (20 seconds typically)
///
public int Timeout { get; set; }
///
/// Any Http request headers you want to set for this request
///
public Dictionary Headers { get; set; }
///
/// Authentication information for this request
///
public NetworkCredential Credentials { get; set; }
///
/// Determines whether credentials pre-authenticate
///
public bool PreAuthenticate { get; set; }
///
/// An optional proxy to set for this request
///
public IWebProxy Proxy { get; set; }
///
/// Capture request string data that was actually sent to the server.
///
public string CapturedRequestContent { get; set; }
///
/// Captured string Response Data from the server
///
public string CapturedResponseContent { get; set; }
///
/// Capture binary Response data from the server when
/// using the Data methods rather than string methods.
///
public byte[] ResponseByteData { get; set; }
///
/// The HTTP Status code of the HTTP response
///
public HttpStatusCode ResponseStatusCode
{
get
{
if (Response != null)
return Response.StatusCode;
return HttpStatusCode.OK;
}
}
///
/// Instance of the full HttpResponse object that gives access
/// to the full HttpWebResponse object to provide things
/// like Response headers, status etc.
///
public HttpWebResponse Response { get; set; }
///
/// The User Agent string sent to the server
///
public string UserAgent { get; set; }
///
/// By default (false) throws a Web Exception on 500 and 400 repsonses.
/// This is the default WebClient behavior.
///
/// If `true` doesn't throw, but instead returns the HTTP response.
/// Useful if you need to return error messages on 500 and 400 responses
/// from API requests.
///
public bool DontThrowOnErrorStatusCodes { get; set; }
///
/// Http Protocol Version 1.1
///
public string HttpVersion { get; set; } = "1.1";
public HttpRequestSettings()
{
HttpVerb = "GET";
Headers = new Dictionary();
Encoding = Encoding.UTF8;
UserAgent = "West Wind .NET Http Client";
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/HttpUtilsWebClient.cs
================================================
using System;
using System.Net;
using System.Web;
namespace Westwind.Utilities
{
///
/// Customized version of WebClient that provides access
/// to the Response object so we can read result data
/// from the Response.
///
public class HttpUtilsWebClient : WebClient
{
#pragma warning disable SYSLIB0014
///
/// Intializes this instance of WebClient with settings values
///
///
public HttpUtilsWebClient(HttpRequestSettings settings = null)
{
Settings = settings;
if (settings != null)
{
if (settings.Credentials != null)
Credentials = settings.Credentials;
if (settings.Proxy != null)
Proxy = settings.Proxy;
if (settings.Encoding != null)
Encoding = settings.Encoding;
if (settings.Headers != null)
{
foreach (var header in settings.Headers)
{
Headers[header.Key] = header.Value;
}
}
}
}
#pragma warning restore SYSLIB0014
internal HttpRequestSettings Settings { get; set; }
internal HttpWebResponse Response { get; set; }
internal HttpWebRequest Request { get; set; }
protected override WebRequest GetWebRequest(Uri address)
{
Request = base.GetWebRequest(address) as HttpWebRequest;
if (Settings != null)
{
if (Settings.Timeout > 0)
{
Request.Timeout = Settings.Timeout;
Request.ReadWriteTimeout = Settings.Timeout;
Request.PreAuthenticate = Settings.PreAuthenticate;
}
if (!string.IsNullOrEmpty(Settings.UserAgent))
Request.UserAgent = Settings.UserAgent;
if (!string.IsNullOrEmpty(Settings.HttpVerb))
Request.Method = Settings.HttpVerb;
}
return Request;
}
protected override WebResponse GetWebResponse(WebRequest request)
{
try
{
Response = base.GetWebResponse(request) as HttpWebResponse;
}
catch (WebException ex)
{
if(Settings.DontThrowOnErrorStatusCodes) {
Response = ex?.Response as HttpWebResponse;
return ex?.Response;
}
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
}
return Response;
}
protected override WebResponse GetWebResponse(WebRequest request, System.IAsyncResult result)
{
try
{
Response = base.GetWebResponse(request, result) as HttpWebResponse;
}
catch (WebException ex)
{
if (Settings.DontThrowOnErrorStatusCodes) {
Response = ex?.Response as HttpWebResponse;
return ex?.Response;
}
#pragma warning disable CA2200
throw;
#pragma warning restore CA2200
}
return Response;
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/ImageUtils.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* West Wind Technologies, 2009
* http://www.west-wind.com/
*
* Created: 09/12/2009
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
#if NETFULL
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
#endif
using System;
using System.Collections.Generic;
using System.IO;
namespace Westwind.Utilities
{
///
/// Summary description for wwImaging.
///
public static class ImageUtils
{
#if NETFULL
///
/// Creates a resized bitmap from an existing image on disk. Resizes the image by
/// creating an aspect ratio safe image. Image is sized to the larger size of width
/// height and then smaller size is adjusted by aspect ratio.
///
/// Image is returned as Bitmap - call Dispose() on the returned Bitmap object
///
///
///
///
/// Bitmap or null
public static Bitmap ResizeImage(string filename, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic)
{
try
{
using (Bitmap bmp = new Bitmap(filename))
{
return ResizeImage(bmp, width, height, mode);
}
}
catch
{
return null;
}
}
///
/// Resizes an image from byte array and returns a Bitmap.
/// Make sure you Dispose() the bitmap after you're done
/// with it!
///
///
///
///
///
public static Bitmap ResizeImage(byte[] data, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic)
{
try
{
using (Bitmap bmp = new Bitmap(new MemoryStream(data)))
{
return ResizeImage(bmp, width, height, mode);
}
}
catch
{
return null;
}
}
///
/// Resizes an image and saves the image to a file
///
///
///
///
///
///
///
/// If using a jpeg image
///
///
public static bool ResizeImage(string filename, string outputFilename,
int width, int height,
InterpolationMode mode = InterpolationMode.HighQualityBicubic,
int jpegCompressionMode = 85)
{
using (var bmpOut = ResizeImage(filename, width, height, mode))
{
var imageFormat = GetImageFormatFromFilename(filename);
if (imageFormat == ImageFormat.Emf)
imageFormat = bmpOut.RawFormat;
if(imageFormat == ImageFormat.Jpeg)
SaveJpeg(bmpOut, outputFilename, jpegCompressionMode);
else
bmpOut.Save(outputFilename, imageFormat);
}
return true;
}
///
/// Resizes an image from a bitmap.
/// Note image will resize to the larger of the two sides
///
/// Bitmap to resize
/// new width
/// new height
/// resized or original bitmap. Be sure to Dispose this bitmap
public static Bitmap ResizeImage(Bitmap bmp, int width, int height,
InterpolationMode mode = InterpolationMode.HighQualityBicubic)
{
Bitmap bmpOut = null;
try
{
decimal ratio;
int newWidth = 0;
int newHeight = 0;
// If the image is smaller than a thumbnail just return original size
if (bmp.Width < width && bmp.Height < height)
{
newWidth = bmp.Width;
newHeight = bmp.Height;
}
else
{
if (bmp.Width == bmp.Height)
{
if (height > width)
{
newHeight = height;
newWidth = height;
}
else
{
newHeight = width;
newWidth = width;
}
}
else if (bmp.Width >= bmp.Height)
{
ratio = (decimal)width / bmp.Width;
newWidth = width;
decimal lnTemp = bmp.Height * ratio;
newHeight = (int)lnTemp;
}
else
{
ratio = (decimal)height / bmp.Height;
newHeight = height;
decimal lnTemp = bmp.Width * ratio;
newWidth = (int)lnTemp;
}
}
bmpOut = new Bitmap(newWidth, newHeight);
bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
using (Graphics g = Graphics.FromImage(bmpOut))
{
g.InterpolationMode = mode;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.FillRectangle(Brushes.White, 0, 0, newWidth, newHeight);
g.DrawImage(bmp, 0, 0, newWidth, newHeight);
}
}
catch
{
return null;
}
return bmpOut;
}
///
/// Adjusts an image to a specific aspect ratio by clipping
/// from the center outward - essentially capturing the center
/// to fit the width/height of the aspect ratio.
///
/// Stream to an image
/// Aspect ratio default is 16:9
/// Optionally resize with to this width (if larger than height)
/// Optionally resize to this height (if larger than width)
/// Bitmap image - make sure to dispose this image
public static Bitmap AdjustImageToRatio(Stream imageStream, decimal ratio = 16M / 9M, int resizeWidth = 0,
int resizeHeight = 0)
{
if (imageStream == null)
return null;
decimal width = 0;
decimal height = 0;
Bitmap bmpOut = null;
Bitmap bitmap = null;
try
{
bitmap = new Bitmap(imageStream);
height = bitmap.Height;
width = bitmap.Width;
if (width >= height * ratio)
{
// clip width
decimal clipWidth = height * ratio;
decimal clipX = (width - clipWidth) / 2;
bmpOut = new Bitmap((int) clipWidth, (int) height);
bmpOut.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
using (Graphics g = Graphics.FromImage(bmpOut))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
var sourceRect = new Rectangle((int) clipX, 0, (int) clipWidth, (int) height);
var targetRect = new Rectangle(0, 0, (int) clipWidth, (int) height);
g.DrawImage(bitmap, targetRect, sourceRect, GraphicsUnit.Pixel);
}
}
else if (width < height * ratio)
{
// clip height
decimal clipHeight = width / ratio;
decimal clipY = (height - clipHeight) / 2;
bmpOut = new Bitmap((int) width, (int) clipHeight);
bmpOut.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
using (Graphics g = Graphics.FromImage(bmpOut))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
var sourceRect = new Rectangle(0, (int) clipY, (int) width, (int) clipHeight);
var targetRect = new Rectangle(0, 0, (int) width, (int) clipHeight);
g.DrawImage(bitmap, targetRect, sourceRect, GraphicsUnit.Pixel);
}
}
else
bmpOut = bitmap;
if (resizeWidth == 0 || resizeWidth == 0)
return bmpOut;
var resizedImage = ResizeImage(bmpOut, resizeWidth, resizeHeight);
return resizedImage;
}
finally
{
bitmap?.Dispose();
bmpOut?.Dispose();
}
}
///
/// Adjusts an image to a specific aspect ratio by clipping
/// from the center outward - essentially capturing the center
/// to fit the width/height of the aspect ratio.
///
///
///
///
///
///
public static Bitmap AdjustImageToRatio(byte[] imageContent, decimal ratio = 16M / 9M, int resizeWidth = 0,
int resizeHeight = 0)
{
using (var ms = new MemoryStream(imageContent))
{
return AdjustImageToRatio(ms, ratio, resizeWidth, resizeHeight);
}
}
///
/// Saves a jpeg BitMap to disk with a jpeg quality setting.
/// Does not dispose the bitmap.
///
/// Bitmap to save
/// file to write it to
///
///
public static bool SaveJpeg(Bitmap bmp, string outputFileName, long jpegQuality = 90)
{
try
{
//get the jpeg codec
ImageCodecInfo jpegCodec = null;
if (Encoders.ContainsKey("image/jpeg"))
jpegCodec = Encoders["image/jpeg"];
EncoderParameters encoderParams = null;
if (jpegCodec != null)
{
//create an encoder parameter for the image quality
EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, jpegQuality);
//create a collection of all parameters that we will pass to the encoder
encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
}
bmp.Save(outputFileName, jpegCodec, encoderParams);
}
catch
{
return false;
}
return true;
}
///
/// Saves a jpeg BitMap to disk with a jpeg quality setting.
/// Does not dispose the bitmap.
///
/// Bitmap to save
/// Binary stream to write image data to
///
///
public static bool SaveJpeg(Bitmap bmp, Stream imageStream, long jpegQuality = 90)
{
try
{
//get the jpeg codec
ImageCodecInfo jpegCodec = null;
if (Encoders.ContainsKey("image/jpeg"))
jpegCodec = Encoders["image/jpeg"];
EncoderParameters encoderParams = null;
if (jpegCodec != null)
{
//create an encoder parameter for the image quality
EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, jpegQuality);
//create a collection of all parameters that we will pass to the encoder
encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
}
bmp.Save(imageStream, jpegCodec, encoderParams);
}
catch
{
return false;
}
return true;
}
///
/// Rotates an image and writes out the rotated image to a file.
///
/// The original image to roatate
/// The output file of the rotated image file. If not passed the original file is overwritten
/// Type of rotation to perform
///
public static bool RoateImage(string filename, string outputFilename = null,
RotateFlipType type = RotateFlipType.Rotate90FlipNone,
int jpegCompressionMode = 85)
{
Bitmap bmpOut = null;
if (string.IsNullOrEmpty(outputFilename))
outputFilename = filename;
try
{
ImageFormat imageFormat;
using (Bitmap bmp = new Bitmap(filename))
{
imageFormat = GetImageFormatFromFilename(filename);
if (imageFormat == ImageFormat.Emf)
imageFormat = bmp.RawFormat;
bmp.RotateFlip(type);
using (bmpOut = new Bitmap(bmp.Width, bmp.Height))
{
bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
Graphics g = Graphics.FromImage(bmpOut);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(bmp, 0, 0, bmpOut.Width, bmpOut.Height);
if (imageFormat == ImageFormat.Jpeg)
SaveJpeg(bmpOut, outputFilename, jpegCompressionMode);
else
bmpOut.Save(outputFilename, imageFormat);
}
}
}
catch (Exception ex)
{
var msg = ex.GetBaseException();
return false;
}
return true;
}
public static byte[] RoateImage(byte[] data, RotateFlipType type = RotateFlipType.Rotate90FlipNone)
{
Bitmap bmpOut = null;
try
{
Bitmap bmp = new Bitmap(new MemoryStream(data));
ImageFormat imageFormat;
imageFormat = bmp.RawFormat;
bmp.RotateFlip(type);
bmpOut = new Bitmap(bmp.Width, bmp.Height);
bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
Graphics g = Graphics.FromImage(bmpOut);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(bmp, 0, 0, bmpOut.Width, bmpOut.Height);
bmp.Dispose();
using (var ms = new MemoryStream())
{
bmpOut.Save(ms, imageFormat);
bmpOut.Dispose();
ms.Flush();
return ms.ToArray();
}
}
catch (Exception ex)
{
var msg = ex.GetBaseException();
return null;
}
}
///
/// Opens the image and writes it back out, stripping any Exif data
///
/// Image to remove exif data from
/// image quality 0-100 (100 no compression)
public static void StripJpgExifData(string imageFile, int imageQuality = 90)
{
using (var bmp = new Bitmap(imageFile))
{
using (var bmp2 = new Bitmap(bmp, bmp.Width, bmp.Height))
{
bmp.Dispose();
SaveJpeg(bmp2, imageFile, imageQuality);
}
}
}
///
/// If the image contains image rotation Exif data, apply the image rotation and
/// remove the Exif data. Optionally also allows for image resizing in the same
/// operation.
///
/// Image file to work on
/// Jpg
///
///
public static void NormalizeJpgImageRotation(string imageFile, int imageQuality = 90, int width = -1, int height = -1)
{
using (var bmp = new Bitmap(imageFile))
{
Bitmap bmp2;
using (bmp2 = new Bitmap(bmp, bmp.Width, bmp.Height))
{
if (bmp.PropertyItems != null)
{
foreach (var item in bmp.PropertyItems)
{
if (item.Id == 0x112)
{
int orientation = item.Value[0];
if (orientation == 6)
bmp2.RotateFlip(RotateFlipType.Rotate90FlipNone);
if (orientation == 8)
bmp2.RotateFlip(RotateFlipType.Rotate270FlipNone);
}
}
}
bmp.Dispose();
if (width > 0 || height > 0)
bmp2 = ResizeImage(bmp2, width, height);
SaveJpeg(bmp2, imageFile, imageQuality);
}
}
}
///
/// A quick lookup for getting image encoders
///
public static Dictionary Encoders
{
//get accessor that creates the dictionary on demand
get
{
//if the quick lookup isn't initialised, initialise it
if (_encoders != null)
return _encoders;
_encoders = new Dictionary();
//get all the codecs
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
{
//add each codec to the quick lookup
_encoders.Add(codec.MimeType.ToLower(), codec);
}
//return the lookup
return _encoders;
}
}
private static Dictionary _encoders = null;
///
/// Tries to return an image format
///
///
/// Image format or ImageFormat.Emf if no match was found
public static ImageFormat GetImageFormatFromFilename(string filename)
{
string ext = Path.GetExtension(filename).ToLower();
ImageFormat imageFormat;
if (ext == ".jpg" || ext == ".jpeg")
imageFormat = ImageFormat.Jpeg;
else if (ext == ".png")
imageFormat = ImageFormat.Png;
else if (ext == ".gif")
imageFormat = ImageFormat.Gif;
else if (ext == ".bmp")
imageFormat = ImageFormat.Bmp;
else
imageFormat = ImageFormat.Emf;
return imageFormat;
}
#endif
///
/// Returns the image media type for a give file extension based
/// on a filename or url passed in.
///
///
///
public static string GetImageMediaTypeFromFilename(string file)
{
if (string.IsNullOrEmpty(file))
return file;
string ext = Path.GetExtension(file).ToLower();
if (ext == ".jpg" || ext == ".jpeg")
return "image/jpeg";
if (ext == ".png")
return "image/png";
if (ext == ".apng")
return "image/apgn";
if (ext == ".gif")
return "image/gif";
if (ext == ".bmp")
return "image/bmp";
if (ext == ".tif" || ext == ".tiff")
return "image/tiff";
if (ext == ".webp")
return "image/webp";
if (ext == ".ico")
return "image/x-icon";
return "application/image";
}
///
/// Returns a file extension for a media type/content type.
///
/// The media type to convert from
/// A file extension or null if no image type is found
public static string GetExtensionFromMediaType(string mediaType)
{
if (mediaType == "image/jpeg")
return "jpg";
if (mediaType == "image/png")
return "png";
if (mediaType == "image/apng")
return "apng";
if (mediaType == "image/bmp")
return "bmp";
if (mediaType == "image/gif")
return "gif";
if (mediaType == "image/tiff")
return "tif";
if (mediaType == "image/svg+xml")
return "svg";
if (mediaType == "image/webp")
return "webp";
if (mediaType == "image/x-icon")
return "ico";
return null;
}
///
/// Determines whether a filename has a typical image extension
///
/// File name to check for image
/// true or false
public static bool IsImage(string filename)
{
if (string.IsNullOrEmpty(filename))
return false;
string ext = Path.GetExtension(filename).ToLower();
return ext == ".jpg" || ext == ".jpeg" ||
ext == ".png" ||
ext == ".apng" ||
ext == ".gif" ||
ext == ".bmp" ||
ext == ".tif" || ext == ".tiff" ||
ext == ".webp" ||
ext == ".ico";
}
///
/// Determines whether a byte array is an image based
/// on binary signature bytes.
/// Supports PNG, JPEG, GIF, BMP, WebP and AVIF formats.
///
/// binary file.
///
/// The buffer only needs the first 15 bytes max for signature detection
///
/// true or false
public static bool IsImage(byte[] data)
{
if (data == null || data.Length < 4)
return false;
// PNG (also covers APNG, which is an extension of PNG)
if (data.Length >= 8 &&
data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E &&
data[3] == 0x47 && data[4] == 0x0D && data[5] == 0x0A &&
data[6] == 0x1A && data[7] == 0x0A)
{
return true;
}
// JPEG
if (data.Length >= 4 &&
data[0] == 0xFF && data[1] == 0xD8 &&
data[data.Length - 2] == 0xFF && data[data.Length - 1] == 0xD9)
{
return true;
}
// GIF
if (data.Length >= 6 &&
data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 &&
data[3] == 0x38 && (data[4] == 0x39 || data[4] == 0x37) && data[5] == 0x61)
{
return true;
}
// BMP
if (data.Length >= 2 &&
data[0] == 0x42 && data[1] == 0x4D)
{
return true;
}
// WebP: starts with 'RIFF' + 4 bytes + 'WEBP'
if (data.Length >= 12 &&
data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 &&
data[8] == 0x57 && data[9] == 0x45 && data[10] == 0x42 && data[11] == 0x50)
{
return true;
}
// AVIF: starts with 'ftyp' + 'avif' or 'avis' box brand
// ISO BMFF files start with 4-byte box size then 'ftyp'
if (data.Length >= 12 &&
data[4] == 0x66 && data[5] == 0x74 && data[6] == 0x79 && data[7] == 0x70)
{
// Check major brand
string brand = System.Text.Encoding.ASCII.GetString(data, 8, 4);
if (brand == "avif" || brand == "avis")
{
return true;
}
}
return false;
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/JsonSerializationUtils.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* West Wind Technologies, 2008 - 2009
* http://www.west-wind.com/
*
* Created: 09/08/2008
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace Westwind.Utilities
{
///
/// JSON Serialization helper class using Json.NET.
/// This class has simplified single line serialization
/// methods to serialize JSON to and from string and
/// files on disk with error handling and some common
/// optional configuration options.
///
public static class JsonSerializationUtils
{
//capture reused type instances
private static JsonSerializer JsonNet = null;
private static object SyncLock = new Object();
private static UTF8Encoding UTF8Encoding = new UTF8Encoding(false);
///
/// Serializes an object to an XML string. Unlike the other SerializeObject overloads
/// this methods *returns a string* rather than a bool result!
///
/// Value to serialize
/// Determines if a failure throws or returns null
///
/// null on error otherwise the Xml String.
///
///
/// If null is passed in null is also returned so you might want
/// to check for null before calling this method.
///
public static string Serialize(object value, bool throwExceptions = false, bool formatJsonOutput = false, bool camelCase = false)
{
if (value is null) return "null";
string jsonResult = null;
Type type = value.GetType();
JsonTextWriter writer = null;
try
{
var json = CreateJsonNet(throwExceptions, camelCase);
StringWriter sw = new StringWriter();
writer = new JsonTextWriter(sw);
if (formatJsonOutput)
writer.Formatting = Formatting.Indented;
writer.QuoteChar = '"';
json.Serialize(writer, value);
jsonResult = sw.ToString();
writer.Close();
}
catch
{
if (throwExceptions)
throw;
jsonResult = null;
}
finally
{
if (writer != null)
writer.Close();
}
return jsonResult;
}
///
/// Serializes an object instance to a JSON file.
///
/// the value to serialize
/// Full path to the file to write out with JSON.
/// Determines whether exceptions are thrown or false is returned
/// if true pretty-formats the JSON with line breaks
/// true or false
public static bool SerializeToFile(object value, string fileName, bool throwExceptions = false, bool formatJsonOutput = false, bool camelCase = false)
{
try
{
Type type = value.GetType();
var json = CreateJsonNet(throwExceptions, camelCase);
if (json == null)
return false;
using (FileStream fs = new FileStream(fileName, FileMode.Create))
{
using (StreamWriter sw = new StreamWriter(fs, UTF8Encoding))
{
using (var writer = new JsonTextWriter(sw))
{
if (formatJsonOutput)
writer.Formatting = Formatting.Indented;
writer.QuoteChar = '"';
json.Serialize(writer, value);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine("JsonSerializer Serialize error: " + ex.Message);
if (throwExceptions)
throw;
return false;
}
return true;
}
///
/// Deserializes an object, array or value from JSON string to an object or value
///
///
///
///
///
public static object Deserialize(string jsonText, Type type, bool throwExceptions = false)
{
var json = CreateJsonNet(throwExceptions);
if (json == null)
return null;
object result = null;
JsonTextReader reader = null;
try
{
StringReader sr = new StringReader(jsonText);
reader = new JsonTextReader(sr);
result = json.Deserialize(reader, type);
reader.Close();
}
catch (Exception ex)
{
Debug.WriteLine("JsonSerializer Deserialize error: " + ex.Message);
if (throwExceptions)
throw;
return null;
}
finally
{
if (reader != null)
reader.Close();
}
return result;
}
///
/// Deserializes an object, array or value from JSON string to an object or value
///
///
///
///
public static T Deserialize(string jsonText, bool throwExceptions = false)
{
var res = Deserialize(jsonText, typeof(T), throwExceptions);
if (res == null)
return default(T);
return (T) res;
}
///
/// Deserializes an object from file and returns a reference.
///
/// name of the file to serialize to
/// The Type of the object. Use typeof(yourobject class)
/// determines whether we use Xml or Binary serialization
/// determines whether failure will throw rather than return null on failure
/// Instance of the deserialized object or null. Must be cast to your object type
public static object DeserializeFromFile(string fileName, Type objectType, bool throwExceptions = false)
{
var json = CreateJsonNet(throwExceptions);
if (json == null)
return null;
object result;
JsonTextReader reader;
FileStream fs;
try
{
using (fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
using (var sr = new StreamReader(fs, Encoding.UTF8))
{
using (reader = new JsonTextReader(sr))
{
result = json.Deserialize(reader, objectType);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine("JsonNetSerialization Deserialization Error: " + ex.Message);
if (throwExceptions)
throw;
return null;
}
return result;
}
///
/// Deserializes an object from file and returns a reference.
///
/// name of the file to serialize to
/// determines whether we use Xml or Binary serialization
/// determines whether failure will throw rather than return null on failure
/// Instance of the deserialized object or null. Must be cast to your object type
public static T DeserializeFromFile(string fileName, bool throwExceptions = false)
{
var res = DeserializeFromFile(fileName, typeof(T), throwExceptions);
if (res == null)
return default(T);
return (T) res;
}
///
/// Takes a single line JSON string and pretty formats
/// it using indented formatting.
///
///
///
public static string FormatJsonString(string json)
{
return JToken.Parse(json).ToString(Formatting.Indented) as string;
}
///
/// Dynamically creates an instance of JSON.NET
///
/// If true throws exceptions otherwise returns null
/// Dynamic JsonSerializer instance
public static JsonSerializer CreateJsonNet(bool throwExceptions = true, bool camelCase = false)
{
if (JsonNet != null)
return JsonNet;
lock (SyncLock)
{
if (JsonNet != null)
return JsonNet;
// Try to create instance
JsonSerializer json;
try
{
json = new JsonSerializer();
}
catch
{
if (throwExceptions)
throw;
return null;
}
if (json == null)
return null;
json.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
// Enums as strings in JSON
var enumConverter = new StringEnumConverter();
json.Converters.Add(enumConverter);
if (camelCase)
json.ContractResolver = new CamelCasePropertyNamesContractResolver();
JsonNet = json;
}
return JsonNet;
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/KnownFolders.cs
================================================
using System;
using System.Runtime.InteropServices;
using static System.Environment;
namespace Westwind.Utilities
{
///
/// Class that returns special Windows file system paths. Extends the folder list
/// beyond what Environment.GetFolderPath(Environment.SpecialFolder) provides
/// with additional Windows known folders like Library, Downloads etc.
///
public static class KnownFolders
{
///
/// Gets the current path to the specified known folder as currently configured. This does
/// not require the folder to be existent.
///
/// The known folder which current path will be returned.
/// The default path of the known folder.
/// Thrown if the path
/// could not be retrieved.
public static string GetPath(KnownFolder knownFolder)
{
return GetPath(knownFolder, false);
}
///
/// Gets the current path to the specified known folder as currently configured. This does
/// not require the folder to be existent.
///
/// The known folder which current path will be returned.
/// Specifies if the paths of the default user (user profile
/// template) will be used. This requires administrative rights.
/// The default path of the known folder.
/// Thrown if the path
/// could not be retrieved.
public static string GetPath(KnownFolder knownFolder, bool defaultUser)
{
return GetPath(knownFolder, KnownFolderFlags.DontVerify, defaultUser);
}
///
/// Gets the default path to the specified known folder. This does not require the folder
/// to be existent.
///
/// The known folder which default path will be returned.
/// The current (and possibly redirected) path of the known folder.
/// Thrown if the path
/// could not be retrieved.
public static string GetDefaultPath(KnownFolder knownFolder)
{
return GetDefaultPath(knownFolder, false);
}
///
/// Gets the default path to the specified known folder. This does not require the folder
/// to be existent.
///
/// The known folder which default path will be returned.
/// Specifies if the paths of the default user (user profile
/// template) will be used. This requires administrative rights.
/// The current (and possibly redirected) path of the known folder.
/// Thrown if the path
/// could not be retrieved.
public static string GetDefaultPath(KnownFolder knownFolder, bool defaultUser)
{
return GetPath(knownFolder, KnownFolderFlags.DefaultPath | KnownFolderFlags.DontVerify,
defaultUser);
}
///
/// Creates and initializes the known folder.
///
/// The known folder which will be initialized.
/// Thrown if the known
/// folder could not be initialized.
public static void Initialize(KnownFolder knownFolder)
{
Initialize(knownFolder, false);
}
///
/// Creates and initializes the known folder.
///
/// The known folder which will be initialized.
/// Specifies if the paths of the default user (user profile
/// template) will be used. This requires administrative rights.
/// Thrown if the known
/// folder could not be initialized.
public static void Initialize(KnownFolder knownFolder, bool defaultUser)
{
GetPath(knownFolder, KnownFolderFlags.Create | KnownFolderFlags.Init, defaultUser);
}
private static string GetPath(KnownFolder knownFolder, KnownFolderFlags flags,
bool defaultUser)
{
// Handle SpecialFolder-mapped values
SpecialFolder? specialFolder = knownFolder switch
{
KnownFolder.UserProfile => SpecialFolder.UserProfile,
KnownFolder.ProgramFiles => SpecialFolder.ProgramFiles,
KnownFolder.ProgramFilesX86 => SpecialFolder.ProgramFilesX86,
KnownFolder.Programs => SpecialFolder.Programs,
KnownFolder.ApplicationData => SpecialFolder.ApplicationData,
KnownFolder.LocalApplicationData => SpecialFolder.LocalApplicationData,
KnownFolder.AdminTools => SpecialFolder.AdminTools,
KnownFolder.Startup => SpecialFolder.Startup,
KnownFolder.Recent => SpecialFolder.Recent,
KnownFolder.SendTo => SpecialFolder.SendTo,
KnownFolder.StartMenu => SpecialFolder.StartMenu,
KnownFolder.DesktopDirectory => SpecialFolder.DesktopDirectory,
KnownFolder.MyComputer => SpecialFolder.MyComputer,
KnownFolder.NetworkShortcuts => SpecialFolder.NetworkShortcuts,
KnownFolder.Fonts => SpecialFolder.Fonts,
KnownFolder.Templates => SpecialFolder.Templates,
KnownFolder.CommonStartMenu => SpecialFolder.CommonStartMenu,
KnownFolder.CommonPrograms => SpecialFolder.CommonPrograms,
KnownFolder.CommonStartup => SpecialFolder.CommonStartup,
KnownFolder.CommonDesktopDirectory => SpecialFolder.CommonDesktopDirectory,
KnownFolder.PrinterShortcuts => SpecialFolder.PrinterShortcuts,
KnownFolder.InternetCache => SpecialFolder.InternetCache,
KnownFolder.Cookies => SpecialFolder.Cookies,
KnownFolder.History => SpecialFolder.History,
KnownFolder.CommonApplicationData => SpecialFolder.CommonApplicationData,
KnownFolder.Windows => SpecialFolder.Windows,
KnownFolder.System => SpecialFolder.System,
KnownFolder.SystemX86 => SpecialFolder.SystemX86,
KnownFolder.CommonProgramFiles => SpecialFolder.CommonProgramFiles,
KnownFolder.CommonProgramFilesX86 => SpecialFolder.CommonProgramFilesX86,
KnownFolder.CommonTemplates => SpecialFolder.CommonTemplates,
KnownFolder.CommonDocuments => SpecialFolder.CommonDocuments,
KnownFolder.CommonAdminTools => SpecialFolder.CommonAdminTools,
KnownFolder.CommonMusic => SpecialFolder.CommonMusic,
KnownFolder.CommonPictures => SpecialFolder.CommonPictures,
KnownFolder.CommonVideos => SpecialFolder.CommonVideos,
KnownFolder.Resources => SpecialFolder.Resources,
KnownFolder.LocalizedResources => SpecialFolder.LocalizedResources,
KnownFolder.CommonOemLinks => SpecialFolder.CommonOemLinks,
KnownFolder.CDBurning => SpecialFolder.CDBurning,
_ => null
};
if (specialFolder != null)
return Environment.GetFolderPath(specialFolder.Value);
#if NET60_OR_GREATER
if (!OperatingSystem.IsWindows())
return string.Empty; // exit if not windows
#endif
// these only work on Windows via PInvoke to Windows API
IntPtr outPath;
int result = SHGetKnownFolderPath(new Guid(_knownFolderGuids[(int)knownFolder]),
(uint)flags, new IntPtr(defaultUser ? -1 : 0), out outPath);
if (result >= 0)
{
return Marshal.PtrToStringUni(outPath);
}
else
{
throw new ExternalException("Unable to retrieve the known folder path. It may not "
+ "be available on this system.", result);
}
}
///
/// Retrieves the full path of a known folder identified by the folder's KnownFolderID.
///
/// A KnownFolderID that identifies the folder.
/// Flags that specify special retrieval options. This value can be
/// 0; otherwise, one or more of the KnownFolderFlag values.
/// An access token that represents a particular user. If this
/// parameter is NULL, which is the most common usage, the function requests the known
/// folder for the current user. Assigning a value of -1 indicates the Default User.
/// The default user profile is duplicated when any new user account is created.
/// Note that access to the Default User folders requires administrator privileges.
///
/// When this method returns, contains the address of a string that
/// specifies the path of the known folder. The returned path does not include a
/// trailing backslash.
/// Returns S_OK if successful, or an error value otherwise.
[DllImport("Shell32.dll")]
private static extern int SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken,
out IntPtr ppszPath);
[Flags]
private enum KnownFolderFlags : uint
{
SimpleIDList = 0x00000100,
NotParentRelative = 0x00000200,
DefaultPath = 0x00000400,
Init = 0x00000800,
NoAlias = 0x00001000,
DontUnexpand = 0x00002000,
DontVerify = 0x00004000,
Create = 0x00008000,
NoAppcontainerRedirection = 0x00010000,
AliasOnly = 0x80000000
}
private static string[] _knownFolderGuids = new string[]
{
//Folder Ids: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx
"{56784854-C6CB-462B-8169-88E350ACB882}", // Contacts
"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}", // Desktop
"{FDD39AD0-238F-46AF-ADB4-6C85480369C7}", // Documents
"{374DE290-123F-4565-9164-39C4925E467B}", // Downloads
"{1777F761-68AD-4D8A-87BD-30B759FA33DD}", // Favorites
"{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}", // Links
"{4BD8D571-6D19-48D3-BE97-422220080E43}", // Music
"{33E28130-4E1E-4676-835A-98395C3BC3BB}", // Pictures
"{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}", // SavedGames
"{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}", // SavedSearches
"{18989B1D-99B5-455B-841C-AB7C74E4DDFC}", // Videos
"{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}", // DocumentsLibrary
"{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}" // Libraries
};
}
///
/// Standard folders registered with the system. These folders are installed with Windows Vista
/// and later operating systems, and a computer will have only folders appropriate to it
/// installed.
///
public enum KnownFolder
{
Contacts,
Desktop,
Documents,
Downloads,
Favorites,
Links,
Music,
Pictures,
SavedGames,
SavedSearches,
Videos,
DocumentsLibrary,
Libraries,
/* Separately handled */
UserProfile,
ProgramFiles,
ProgramFilesX86,
Programs,
ApplicationData,
LocalApplicationData,
AdminTools,
// Additional SpecialFolder values
Startup,
Recent,
SendTo,
StartMenu,
DesktopDirectory,
MyComputer,
NetworkShortcuts,
Fonts,
Templates,
CommonStartMenu,
CommonPrograms,
CommonStartup,
CommonDesktopDirectory,
PrinterShortcuts,
InternetCache,
Cookies,
History,
CommonApplicationData,
Windows,
System,
SystemX86,
CommonProgramFiles,
CommonProgramFilesX86,
CommonTemplates,
CommonDocuments,
CommonAdminTools,
CommonMusic,
CommonPictures,
CommonVideos,
Resources,
LocalizedResources,
CommonOemLinks,
CDBurning
}
}
================================================
FILE: Westwind.Utilities/Utilities/LanguageUtils.cs
================================================
using System;
namespace Westwind.Utilities
{
public static class LanguageUtils
{
///
/// Runs an operation and ignores any Exceptions that occur.
/// Returns true or falls depending on whether catch was
/// triggered
///
/// lambda that performs an operation that might throw
///
public static bool IgnoreErrors(Action operation)
{
if (operation == null)
return false;
try
{
operation.Invoke();
}
catch
{
return false;
}
return true;
}
///
/// Runs an function that returns a value and ignores any Exceptions that occur.
/// Returns true or falls depending on whether catch was
/// triggered
///
/// parameterless lamda that returns a value of T
/// Default value returned if operation fails
public static T IgnoreErrors(Func operation, T defaultValue = default(T))
{
if (operation == null)
return defaultValue;
T result;
try
{
result = operation.Invoke();
}
catch
{
result = defaultValue;
}
return result;
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/NetworkUtils.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace Westwind.Utilities
{
public static class NetworkUtils
{
///
/// Retrieves a base domain name from a full domain name.
/// For example: www.west-wind.com produces west-wind.com
///
/// Dns Domain name as a string
///
public static string GetBaseDomain(string domainName)
{
var tokens = domainName.Split('.');
// only split 3 urls like www.west-wind.com
if (tokens == null || tokens.Length != 3)
return domainName;
var tok = new List(tokens);
var remove = tokens.Length - 2;
tok.RemoveRange(0, remove);
return tok[0] + "." + tok[1]; ;
}
///
/// Returns the base domain from a domain name
/// Example: http://www.west-wind.com returns west-wind.com
///
///
///
public static string GetBaseDomain(this Uri uri)
{
if (uri.HostNameType == UriHostNameType.Dns)
return GetBaseDomain(uri.DnsSafeHost);
return uri.Host;
}
///
/// Checks to see if an IP Address or Domain is a local address
///
///
/// Do not use in high traffic situations, as this involves a
/// DNS lookup. If no local hostname is found it goes out to a
/// DNS server to retrieve IP Addresses.
///
/// Hostname or IP Address
/// true or false
public static bool IsLocalIpAddress(string hostOrIp)
{
if(string.IsNullOrEmpty(hostOrIp))
return false;
try
{
// get IP Mapped to passed host
IPAddress[] hostIPs = Dns.GetHostAddresses(hostOrIp);
// get local IP addresses
IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// check host ip addresses against local ip addresses for matches
foreach (IPAddress hostIP in hostIPs)
{
// check for localhost/127.0.0.1
if (IPAddress.IsLoopback(hostIP))
return true;
// Check if IP Address matches a local IP
foreach (IPAddress localIP in localIPs)
{
if (hostIP.Equals(localIP))
return true;
}
}
}
catch {}
return false;
}
///
/// Checks to see if an IP Address or Domain is a local address
///
///
/// Do not use in high traffic situations, as this involves a
/// DNS lookup. If no local hostname is found it goes out to a
/// DNS server to retrieve IP Addresses.
///
/// Pass a full URL as an Uri
///
public static bool IsLocalIpAddress(Uri uri)
{
if (uri == null)
return false;
var host = uri.Host;
return IsLocalIpAddress(host);
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/PasswordScrubber.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Westwind.Utilities
{
///
/// A very basic password scrubber that can scrub passwords from
/// JSON and Sql Connection strings.
///
/// This is a very basic implementation where you can provide the
/// keys to scrub or use default values.
///
public class PasswordScrubber
{
///
/// A static instance that can be used without instantiating first
///
public static PasswordScrubber Instance = new PasswordScrubber();
///
/// If set to a non-zero value displays the frist n characters
/// of the value that is being obscured.
///
public int ShowUnobscuredCharacterCount = 2;
///
/// Value displayed for obscured values. If choosing to display first characters
/// those are in addition to the obscured values.
///
public string ObscuredValueBaseDisplay = "****";
public string ScrubJsonValues(string configString, params string[] jsonKeys)
{
if (jsonKeys == null || jsonKeys.Length < 1) jsonKeys = new string[1] { "password" };
foreach (var token in jsonKeys)
{
var matchValue = $@"""{token}"":\s*""(.*?)""";
var match = Regex.Match(configString, matchValue, RegexOptions.Multiline | RegexOptions.IgnoreCase);
if (match.Success)
{
var group = match.Groups[1];
configString = configString.Replace(group.Value, ObscureValue(group.Value));
}
}
return configString;
}
public string ScrubSqlConnectionStringValues(string configString, params string[] connKeys)
{
if (connKeys == null || connKeys.Length < 1) connKeys = new string[] { "pwd", "password" };
foreach (var key in connKeys)
{
// Sql Connection String pwd
var extract = StringUtils.ExtractString(configString, $"{key}=", ";", allowMissingEndDelimiter: true, returnDelimiters: true);
if (!string.IsNullOrEmpty(extract))
{
var only = StringUtils.ExtractString(extract, $"{key}=", ";", allowMissingEndDelimiter: true, returnDelimiters: false);
configString = configString.Replace(extract, $"{key}=" + ObscureValue(only) + ";");
}
}
return configString;
}
public string ObscureValue(string value, int showUnobscuredCharacterCount = -1)
{
if (string.IsNullOrEmpty(value)) return value;
if (showUnobscuredCharacterCount < 0)
showUnobscuredCharacterCount = ShowUnobscuredCharacterCount;
// very short –just display the obscured value without any revealed characters
if (showUnobscuredCharacterCount > value.Length + 2)
return ObscuredValueBaseDisplay;
return value.Substring(0, showUnobscuredCharacterCount) + ObscuredValueBaseDisplay;
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/ReflectionUtils.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* West Wind Technologies, 2008 - 2009
* http://www.west-wind.com/
*
* Created: 09/08/2008
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.ComponentModel;
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Versioning;
namespace Westwind.Utilities
{
///
/// Collection of Reflection and type conversion related utility functions
///
public static class ReflectionUtils
{
///
/// Binding Flags constant to be reused for all Reflection access methods.
///
public const BindingFlags MemberAccess =
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase;
public const BindingFlags MemberAccessCom =
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
#region Type and Assembly Creation
///
/// Creates an instance from a type by calling the parameterless constructor.
///
/// Note this will not work with COM objects - continue to use the Activator.CreateInstance
/// for COM objects.
/// Class wwUtils
///
///
/// The type from which to create an instance.
///
/// object
public static object CreateInstanceFromType(Type typeToCreate, params object[] args)
{
if (args == null)
{
Type[] Parms = Type.EmptyTypes;
return typeToCreate.GetConstructor(Parms).Invoke(null);
}
return Activator.CreateInstance(typeToCreate, args);
}
///
/// Creates an instance of a type based on a string. Assumes that the type's
///
///
///
///
public static object CreateInstanceFromString(string typeName, params object[] args)
{
object instance = null;
try
{
var type = GetTypeFromName(typeName);
if (type == null)
return null;
instance = Activator.CreateInstance(type, args);
}
catch
{
return null;
}
return instance;
}
///
/// Helper routine that looks up a type name and tries to retrieve the
/// full type reference using GetType() and if not found looking
/// in the actively executing assemblies and optionally loading
/// the specified assembly name.
///
/// type to load
///
/// Optional assembly name to load from if type cannot be loaded initially.
/// Use for lazy loading of assemblies without taking a type dependency.
///
/// null
public static Type GetTypeFromName(string typeName, string assemblyName )
{
var type = Type.GetType(typeName, false);
if (type != null)
return type;
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
// try to find manually
foreach (Assembly asm in assemblies)
{
type = asm.GetType(typeName, false);
if (type != null)
break;
}
if (type != null)
return type;
// see if we can load the assembly
if (!string.IsNullOrEmpty(assemblyName))
{
var a = LoadAssembly(assemblyName);
if (a != null)
{
type = Type.GetType(typeName, false);
if (type != null)
return type;
}
}
return null;
}
///
/// Overload for backwards compatibility which only tries to load
/// assemblies that are already loaded in memory.
///
///
///
public static Type GetTypeFromName(string typeName)
{
return GetTypeFromName(typeName, null);
}
///
/// Creates a COM instance from a ProgID. Loads either
/// Exe or DLL servers.
///
///
///
#if NET6_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public static object CreateComInstance(string progId)
{
Type type = Type.GetTypeFromProgID(progId);
if (type == null)
return null;
return Activator.CreateInstance(type);
}
///
/// Try to load an assembly into the application's app domain.
/// Loads by name first then checks for filename
///
/// Assembly name or full path
/// null on failure
public static Assembly LoadAssembly(string assemblyName)
{
Assembly assembly = null;
try
{
assembly = Assembly.Load(assemblyName);
}
catch { }
if (assembly != null)
return assembly;
if (File.Exists(assemblyName))
{
assembly = Assembly.LoadFrom(assemblyName);
if (assembly != null)
return assembly;
}
return null;
}
///
/// Clones an object using a shallow cloning using private
/// MemberwiseClone operation.
///
/// Object to copy
/// A shallow copy: Value types are copies, reference types are left as references
public static object ShallowClone(object source)
{
return source.GetType()
.GetMethod("MemberwiseClone",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod)
.Invoke(source, null);
}
#endregion
#region Conversions
///
/// Turns a string into a typed value generically.
/// Explicitly assigns common types and falls back
/// on using type converters for unhandled types.
///
/// Common uses:
/// * UI -> to data conversions
/// * Parsers
/// Class ReflectionUtils
///
///
/// The string to convert from
///
///
/// The type to convert to
///
///
/// Culture used for numeric and datetime values.
///
/// object. Throws exception if it cannot be converted.
public static object StringToTypedValue(string sourceString, Type targetType, CultureInfo culture = null)
{
object result = null;
bool isEmpty = string.IsNullOrEmpty(sourceString);
if (culture == null)
culture = CultureInfo.CurrentCulture;
if (targetType == typeof(string))
result = sourceString;
else if (targetType == typeof(Int32) || targetType == typeof(int))
{
if (isEmpty)
result = 0;
else
result = Int32.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(Int64))
{
if (isEmpty)
result = (Int64)0;
else
result = Int64.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(Int16))
{
if (isEmpty)
result = (Int16)0;
else
result = Int16.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(decimal))
{
if (isEmpty)
result = 0M;
else
result = decimal.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(DateTime))
{
if (isEmpty)
result = DateTime.MinValue;
else
result = Convert.ToDateTime(sourceString, culture.DateTimeFormat);
}
else if (targetType == typeof(byte))
{
if (isEmpty)
result = 0;
else
result = Convert.ToByte(sourceString);
}
else if (targetType == typeof(double))
{
if (isEmpty)
result = 0F;
else
result = Double.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(Single))
{
if (isEmpty)
result = 0F;
else
result = Single.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(bool))
{
sourceString = sourceString.ToLower();
if (!isEmpty &&
sourceString == "true" || sourceString == "on" ||
sourceString == "1" || sourceString == "yes")
result = true;
else
result = false;
}
else if (targetType == typeof(Guid))
{
if (isEmpty)
result = Guid.Empty;
else
result = new Guid(sourceString);
}
else if (targetType.IsEnum)
result = Enum.Parse(targetType, sourceString);
else if (targetType == typeof(byte[]))
{
// TODO: Convert HexBinary string to byte array
result = null;
}
// Handle nullables explicitly since type converter won't handle conversions
// properly for things like decimal separators currency formats etc.
// Grab underlying type and pass value to that
else if (targetType.Name.StartsWith("Nullable`"))
{
if (sourceString.ToLower() == "null" || sourceString == string.Empty)
result = null;
else
{
targetType = Nullable.GetUnderlyingType(targetType);
result = StringToTypedValue(sourceString, targetType);
}
}
else
{
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
if (converter != null && converter.CanConvertFrom(typeof(string)))
result = converter.ConvertFromString(null, culture, sourceString);
else
{
Debug.Assert(false, string.Format("Type Conversion not handled in StringToTypedValue for {0} {1}",
targetType.Name, sourceString));
throw (new InvalidCastException("Type Conversion failed for: " + targetType.Name));
}
}
return result;
}
///
/// Generic version allow for automatic type conversion without the explicit type
/// parameter
///
/// Type to be converted to
/// input string value to be converted
/// Culture applied to conversion
///
public static T StringToTypedValue(string sourceString, CultureInfo culture = null)
{
return (T)StringToTypedValue(sourceString, typeof(T), culture);
}
///
/// Converts a type to string if possible. This method supports an optional culture generically on any value.
/// It calls the ToString() method on common types and uses a type converter on all other objects
/// if available
///
/// The Value or Object to convert to a string
/// Culture for numeric and DateTime values
/// Return string for unsupported types
/// string
public static string TypedValueToString(object rawValue, CultureInfo culture = null, string unsupportedReturn = null)
{
if (rawValue == null)
return string.Empty;
if (culture == null)
culture = CultureInfo.CurrentCulture;
Type valueType = rawValue.GetType();
string returnValue = null;
if (valueType == typeof(string))
returnValue = rawValue as string;
else if (valueType == typeof(int) || valueType == typeof(decimal) ||
valueType == typeof(double) || valueType == typeof(float) || valueType == typeof(Single))
returnValue = string.Format(culture.NumberFormat, "{0}", rawValue);
else if (valueType == typeof(DateTime))
returnValue = string.Format(culture.DateTimeFormat, "{0}", rawValue);
else if (valueType == typeof(bool) || valueType == typeof(Byte) || valueType.IsEnum)
returnValue = rawValue.ToString();
else if (valueType == typeof(Guid?))
{
if (rawValue == null)
returnValue = string.Empty;
else
return rawValue.ToString();
}
else
{
// Any type that supports a type converter
TypeConverter converter = TypeDescriptor.GetConverter(valueType);
if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)))
returnValue = converter.ConvertToString(null, culture, rawValue);
else
{
// Last resort - just call ToString() on unknown type
if (!string.IsNullOrEmpty(unsupportedReturn))
returnValue = unsupportedReturn;
else
returnValue = rawValue.ToString();
}
}
return returnValue;
}
#endregion
#region Member Access
///
/// Calls a method on an object dynamically. This version requires explicit
/// specification of the parameter type signatures.
///
/// Instance of object to call method on
/// The method to call as a stringToTypedValue
/// Specify each of the types for each parameter passed.
/// You can also pass null, but you may get errors for ambiguous methods signatures
/// when null parameters are passed
/// any variable number of parameters.
/// object
public static object CallMethod(object instance, string method, Type[] parameterTypes, params object[] parms)
{
if (parameterTypes == null && parms.Length > 0)
// Call without explicit parameter types - might cause problems with overloads
// occurs when null parameters were passed and we couldn't figure out the parm type
return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod).Invoke(instance, parms);
else
// Call with parameter types - works only if no null values were passed
return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod, null, parameterTypes, null).Invoke(instance, parms);
}
///
/// Calls a method on an object dynamically.
///
/// This version doesn't require specific parameter signatures to be passed.
/// Instead parameter types are inferred based on types passed. Note that if
/// you pass a null parameter, type inferrance cannot occur and if overloads
/// exist the call may fail. if so use the more detailed overload of this method.
///
/// Instance of object to call method on
/// The method to call as a stringToTypedValue
/// Specify each of the types for each parameter passed.
/// You can also pass null, but you may get errors for ambiguous methods signatures
/// when null parameters are passed
/// any variable number of parameters.
/// object
public static object CallMethod(object instance, string method, params object[] parms)
{
// Pick up parameter types so we can match the method properly
Type[] parameterTypes = null;
if (parms != null)
{
parameterTypes = new Type[parms.Length];
for (int x = 0; x < parms.Length; x++)
{
// if we have null parameters we can't determine parameter types - exit
if (parms[x] == null)
{
parameterTypes = null; // clear out - don't use types
break;
}
parameterTypes[x] = parms[x].GetType();
}
}
return CallMethod(instance, method, parameterTypes, parms);
}
///
/// Allows invoking an event from an external classes where direct access
/// is not allowed (due to 'Can only assign to left hand side of operation')
///
/// Instance of the object hosting the event
/// Name of the event to invoke
/// Optional parameters to the event handler to be invoked
public static void InvokeEvent(object instance, string eventName, params object[] parameters)
{
MulticastDelegate del =
(MulticastDelegate)instance?.GetType().GetField(eventName,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic)?.GetValue(instance);
if (del == null)
return;
Delegate[] delegates = del.GetInvocationList();
foreach (Delegate dlg in delegates)
{
dlg.Method.Invoke(dlg.Target, parameters);
}
}
///
/// Retrieve a property value from an object dynamically. This is a simple version
/// that uses Reflection calls directly. It doesn't support indexers.
///
/// Object to make the call on
/// Property to retrieve
/// Object - cast to proper type
public static object GetProperty(object instance, string property)
{
return instance.GetType().GetProperty(property).GetValue(instance, null);
}
///
/// Parses Properties and Fields including Array and Collection references.
/// Used internally for the 'Ex' Reflection methods.
///
///
///
///
private static object GetPropertyInternal(object Parent, string Property)
{
if (Property == "this" || Property == "me")
return Parent;
object result = null;
string pureProperty = Property;
string indexes = null;
bool isArrayOrCollection = false;
// Deal with Array Property
if (Property.IndexOf("[") > -1)
{
pureProperty = Property.Substring(0, Property.IndexOf("["));
indexes = Property.Substring(Property.IndexOf("["));
isArrayOrCollection = true;
}
var parentType = Parent.GetType();
if (string.IsNullOrEmpty(pureProperty))
{
// most likely an indexer
result = Parent;
}
else
{
// Get the member
MemberInfo member = Parent.GetType().GetMember(pureProperty, ReflectionUtils.MemberAccess)[0];
if (member.MemberType == MemberTypes.Property)
result = ((PropertyInfo) member).GetValue(Parent, null);
else
result = ((FieldInfo) member).GetValue(Parent);
}
if (isArrayOrCollection)
{
indexes = indexes.Replace("[", string.Empty).Replace("]", string.Empty);
if (result is Array)
{
int Index = -1;
int.TryParse(indexes, out Index);
result = CallMethod(result, "GetValue", Index);
}
else if (result is ICollection || result is System.Data.DataRow || result is System.Data.DataTable)
{
if (indexes.StartsWith("\""))
{
// String Index
indexes = indexes.Trim('\"');
result = CallMethod(result, "get_Item", indexes);
}
else
{
// assume numeric index
int index = -1;
int.TryParse(indexes, out index);
result = CallMethod(result, "get_Item", index);
}
}
}
return result;
}
///
/// Returns a PropertyInfo structure from an extended Property reference
///
///
///
///
public static PropertyInfo GetPropertyInfoInternal(object Parent, string Property)
{
if (Property == "this" || Property == "me")
return null;
string propertyName = Property;
// Deal with Array Property - strip off array indexer
if (Property.IndexOf("[") > -1)
propertyName = Property.Substring(0, Property.IndexOf("["));
// Get the member
return Parent.GetType().GetProperty(propertyName, ReflectionUtils.MemberAccess);
}
///
/// Retrieve a field dynamically from an object. This is a simple implementation that's
/// straight Reflection and doesn't support indexers.
///
/// Object to retreve Field from
/// name of the field to retrieve
///
public static object GetField(object Object, string Property)
{
return Object.GetType().GetField(Property, ReflectionUtils.MemberAccess | BindingFlags.GetField).GetValue(Object);
}
///
/// Sets the property on an object. This is a simple method that uses straight Reflection
/// and doesn't support indexers.
///
/// Object to set property on
/// Name of the property to set
/// value to set it to
public static void SetProperty(object obj, string property, object value)
{
obj.GetType().GetProperty(property, ReflectionUtils.MemberAccess).SetValue(obj, value, null);
}
///
/// Parses Properties and Fields including Array and Collection references.
///
///
///
///
private static object SetPropertyInternal(object Parent, string Property, object Value)
{
if (Property == "this" || Property == "me")
return Parent;
object Result = null;
string PureProperty = Property;
string Indexes = null;
bool IsArrayOrCollection = false;
// Deal with Array Property
if (Property.IndexOf("[") > -1)
{
PureProperty = Property.Substring(0, Property.IndexOf("["));
Indexes = Property.Substring(Property.IndexOf("["));
IsArrayOrCollection = true;
}
if (!IsArrayOrCollection)
{
// Get the member
MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0];
if (Member.MemberType == MemberTypes.Property)
{
var prop = (PropertyInfo) Member;
if (prop.CanWrite)
prop.SetValue(Parent, Value, null);
}
else
((FieldInfo)Member).SetValue(Parent, Value);
return null;
}
else
{
// Get the member
MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0];
if (Member.MemberType == MemberTypes.Property)
{
var prop = (PropertyInfo) Member;
if (prop.CanRead)
Result = prop.GetValue(Parent, null);
}
else
Result = ((FieldInfo)Member).GetValue(Parent);
}
if (IsArrayOrCollection)
{
Indexes = Indexes.Replace("[", string.Empty).Replace("]", string.Empty);
if (Result is Array)
{
int Index = -1;
int.TryParse(Indexes, out Index);
Result = CallMethod(Result, "SetValue", Value, Index);
}
else if (Result is ICollection)
{
if (Indexes.StartsWith("\""))
{
// String Index
Indexes = Indexes.Trim('\"');
Result = CallMethod(Result, "set_Item", Indexes, Value);
}
else
{
// assume numeric index
int Index = -1;
int.TryParse(Indexes, out Index);
Result = CallMethod(Result, "set_Item", Index, Value);
}
}
}
return Result;
}
///
/// Sets the field on an object. This is a simple method that uses straight Reflection
/// and doesn't support indexers.
///
/// Object to set property on
/// Name of the field to set
/// value to set it to
public static void SetField(object obj, string property, object value)
{
obj.GetType().GetField(property, ReflectionUtils.MemberAccess).SetValue(obj, value);
}
///
/// Returns a List of KeyValuePair object
///
///
///
public static List> GetEnumList(Type enumType, bool valueAsFieldValueNumber = false)
{
//string[] enumStrings = Enum.GetNames(enumType);
Array enumValues = Enum.GetValues(enumType);
List> items = new List>();
foreach (var enumValue in enumValues)
{
var strValue = enumValue.ToString();
if (!valueAsFieldValueNumber)
items.Add(new KeyValuePair(enumValue.ToString(), StringUtils.FromCamelCase(strValue)));
else
items.Add(new KeyValuePair(((int)enumValue).ToString(),
StringUtils.FromCamelCase(strValue)
));
}
return items;
}
#endregion
#region EX processing for nested operations
///
/// Calls a method on an object with extended . syntax (object: this Method: Entity.CalculateOrderTotal)
///
///
///
///
///
public static object CallMethodEx(object parent, string method, params object[] parms)
{
Type Type = parent.GetType();
// no more .s - we got our final object
int lnAt = method.IndexOf(".");
if (lnAt < 0)
{
return ReflectionUtils.CallMethod(parent, method, parms);
}
// Walk the . syntax
string Main = method.Substring(0, lnAt);
string Subs = method.Substring(lnAt + 1);
object Sub = GetPropertyInternal(parent, Main);
// Recurse until we get the lowest ref
return CallMethodEx(Sub, Subs, parms);
}
///
/// Returns a property or field value using a base object and sub members including . syntax.
/// For example, you can access: oCustomer.oData.Company with (this,"oCustomer.oData.Company")
/// This method also supports indexers in the Property value such as:
/// Customer.DataSet.Tables["Customers"].Rows[0]
///
/// Parent object to 'start' parsing from. Typically this will be the Page.
/// The property to retrieve. Example: 'Customer.Entity.Company'
///
public static object GetPropertyEx(object Parent, string Property)
{
Type type = Parent.GetType();
int at = Property.IndexOf(".");
if (at < 0)
{
// Complex parse of the property
return GetPropertyInternal(Parent, Property);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string main = Property.Substring(0, at);
string subs = Property.Substring(at + 1);
// Retrieve the next . section of the property
object sub = GetPropertyInternal(Parent, main);
// Now go parse the left over sections
return GetPropertyEx(sub, subs);
}
///
/// Returns a PropertyInfo object for a given dynamically accessed property
///
/// Property selection can be specified using . syntax ("Address.Street" or "DataTable[0].Rows[1]") hence the 'Ex' name for this function.
///
///
///
///
public static PropertyInfo GetPropertyInfoEx(object Parent, string Property)
{
Type type = Parent.GetType();
int at = Property.IndexOf(".");
if (at < 0)
{
// Complex parse of the property
return GetPropertyInfoInternal(Parent, Property);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string main = Property.Substring(0, at);
string subs = Property.Substring(at + 1);
// Retrieve the next . section of the property
object sub = GetPropertyInternal(Parent, main);
// Now go parse the left over sections
return GetPropertyInfoEx(sub, subs);
}
///
/// Sets a value on an object. Supports . syntax for named properties
/// (ie. Customer.Entity.Company) as well as indexers.
///
///
/// Object to set the property on.
///
///
/// Property to set. Can be an object hierarchy with . syntax and can
/// include indexers. Examples: Customer.Entity.Company,
/// Customer.DataSet.Tables["Customers"].Rows[0]
///
///
/// Value to set the property to
///
public static object SetPropertyEx(object parent, string property, object value)
{
Type Type = parent.GetType();
// no more .s - we got our final object
int lnAt = property.IndexOf(".");
if (lnAt < 0)
{
SetPropertyInternal(parent, property, value);
return null;
}
// Walk the . syntax
string Main = property.Substring(0, lnAt);
string Subs = property.Substring(lnAt + 1);
object Sub = GetPropertyInternal(parent, Main);
SetPropertyEx(Sub, Subs, value);
return null;
}
#endregion
#region Static Methods and Properties
///
/// Invokes a static method
///
///
///
///
///
public static object CallStaticMethod(string typeName, string method, params object[] parms)
{
Type type = GetTypeFromName(typeName);
if (type == null)
throw new ArgumentException("Invalid type: " + typeName);
try
{
return type.InvokeMember(method,
BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod,
null, type, parms);
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw ex.GetBaseException();
throw new ApplicationException("Failed to retrieve method signature or invoke method");
}
}
///
/// Retrieves a value from a static property by specifying a type full name and property
///
/// Full type name (namespace.class)
/// Property to get value from
///
public static object GetStaticProperty(string typeName, string property)
{
Type type = GetTypeFromName(typeName);
if (type == null)
return null;
return GetStaticProperty(type, property);
}
///
/// Returns a static property from a given type
///
/// Type instance for the static property
/// Property name as a string
///
public static object GetStaticProperty(Type type, string property)
{
object result = null;
try
{
result = type.InvokeMember(property, BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty, null, type, null);
}
catch
{
return null;
}
return result;
}
#endregion
#region COM Reflection Routines
///
/// Retrieve a dynamic 'non-typelib' property
///
/// Object to make the call on
/// Property to retrieve
///
public static object GetPropertyCom(object instance, string property)
{
return instance.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty, null,
instance, null);
}
///
/// Returns a property or field value using a base object and sub members including . syntax.
/// For example, you can access: oCustomer.oData.Company with (this,"oCustomer.oData.Company")
///
/// Parent object to 'start' parsing from.
/// The property to retrieve. Example: 'oBus.oData.Company'
///
public static object GetPropertyExCom(object parent, string property)
{
Type Type = parent.GetType();
int lnAt = property.IndexOf(".");
if (lnAt < 0)
{
if (property == "this" || property == "me")
return parent;
// Get the member
return parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null,
parent, null);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string Main = property.Substring(0, lnAt);
string Subs = property.Substring(lnAt + 1);
object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null,
parent, null);
// Recurse further into the sub-properties (Subs)
return ReflectionUtils.GetPropertyExCom(Sub, Subs);
}
///
/// Sets the property on an object.
///
/// Object to set property on
/// Name of the property to set
/// value to set it to
public static void SetPropertyCom(object inst, string Property, object Value)
{
inst.GetType().InvokeMember(Property, ReflectionUtils.MemberAccessCom | BindingFlags.SetProperty, null, inst, new object[1] { Value });
}
///
/// Sets the value of a field or property via Reflection. This method alws
/// for using '.' syntax to specify objects multiple levels down.
///
/// ReflectionUtils.SetPropertyEx(this,"Invoice.LineItemsCount",10)
///
/// which would be equivalent of:
///
/// Invoice.LineItemsCount = 10;
///
///
/// Object to set the property on.
///
///
/// Property to set. Can be an object hierarchy with . syntax.
///
///
/// Value to set the property to
///
public static object SetPropertyExCom(object parent, string property, object value)
{
Type Type = parent.GetType();
int lnAt = property.IndexOf(".");
if (lnAt < 0)
{
// Set the member
parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.SetProperty , null,
parent, new object[1] { value });
return null;
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string Main = property.Substring(0, lnAt);
string Subs = property.Substring(lnAt + 1);
object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null,
parent, null);
return SetPropertyExCom(Sub, Subs, value);
}
///
/// Wrapper method to call a 'dynamic' (non-typelib) method
/// on a COM object
///
///
///
/// 1st - Method name, 2nd - 1st parameter, 3rd - 2nd parm etc.
///
public static object CallMethodCom(object instance, string method, params object[] parms)
{
return instance.GetType().InvokeMember(method, ReflectionUtils.MemberAccessCom | BindingFlags.InvokeMethod, null, instance, parms);
}
///
/// Calls a method on a COM object with '.' syntax (Customer instance and Address.DoSomeThing method)
///
/// the object instance on which to call method
/// The method or . syntax path to the method (Address.Parse)
/// Any number of parameters
///
public static object CallMethodExCom(object parent, string method, params object[] parms)
{
Type Type = parent.GetType();
// no more .s - we got our final object
int at = method.IndexOf(".");
if (at < 0)
{
return ReflectionUtils.CallMethodCom(parent, method, parms);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string Main = method.Substring(0, at);
string Subs = method.Substring(at + 1);
object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty, null,
parent, null);
// Recurse until we get the lowest ref
return CallMethodExCom(Sub, Subs, parms);
}
#endregion
///
/// Determines whether a given type passed is anonymous
///
/// Pass either an object instance or a Type object
///
public static bool IsAnonoymousType(object objectOrType)
{
if (objectOrType == null)
return false;
if (objectOrType is Type type)
{
type = objectOrType as Type;
}
else
{
type = objectOrType.GetType();
}
if (!type.IsPublic &&
type.IsSealed &&
string.IsNullOrEmpty(type.Namespace))
{
return true;
}
return false;
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/SecurityUtils.cs
================================================
#if NETFULL
#region License
/*
**************************************************************
* Author: Rick Strahl
* West Wind Technologies, 2009
* http://www.west-wind.com/
*
* Created: 09/12/2009
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System.Runtime.InteropServices;
using System.Security.Principal;
using System;
namespace Westwind.Utilities
{
///
/// A set of utilities functions related to security.
///
public static class SecurityUtils
{
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_LOGON_SERVICE = 5;
const int LOGON32_LOGON_UNLOCK = 7;
const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
const int LOGON32_PROVIDER_DEFAULT = 0;
[DllImport("advapi32.dll", SetLastError = true)]
static extern int LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
[DllImport("kernel32.dll", SetLastError = true)]
static extern int CloseHandle(IntPtr hObject);
///
/// Logs on a user and changes the current process impersonation to that user.
///
/// IMPORTANT: Returns a WindowsImpersonationContext and you have to either
/// dispose this instance or call RevertImpersonation with it.
///
///
/// Requires Full Trust permissions in ASP.NET Web Applications.
///
///
///
///
/// WindowsImpersonation Context. Call RevertImpersonation() to clear the impersonation or Dispose() instance.
public static WindowsImpersonationContext ImpersonateUser(string username, string password, string domain)
{
IntPtr token = IntPtr.Zero;
try
{
int TResult = LogonUser(username, domain, password,
LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT,
out token);
WindowsImpersonationContext context = null;
context = WindowsIdentity.Impersonate(token);
CloseHandle(token);
return context;
}
catch
{
return null;
}
finally
{
if (token != IntPtr.Zero)
CloseHandle(token);
}
}
///
/// Releases an impersonation context and releases associated resources
///
/// WindowsImpersonation context created with ImpersonateUser
public static void RevertImpersonation(WindowsImpersonationContext context)
{
context.Undo();
context.Dispose();
}
}
}
#endif
================================================
FILE: Westwind.Utilities/Utilities/SerializationUtils.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* West Wind Technologies, 2008 - 2009
* http://www.west-wind.com/
*
* Created: 09/08/2008
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;
using System.Runtime.Serialization;
using Westwind.Utilities.Properties;
namespace Westwind.Utilities
{
// Serialization specific code
public static class SerializationUtils
{
///
/// Serializes an object instance to a file.
///
/// the object instance to serialize
///
/// determines whether XML serialization or binary serialization is used
///
public static bool SerializeObject(object instance, string fileName, bool binarySerialization)
{
bool retVal = true;
if (!binarySerialization)
{
XmlTextWriter writer = null;
try
{
XmlSerializer serializer =
new XmlSerializer(instance.GetType());
// Create an XmlTextWriter using a FileStream.
Stream fs = new FileStream(fileName, FileMode.Create);
writer = new XmlTextWriter(fs, new UTF8Encoding());
writer.Formatting = Formatting.Indented;
writer.IndentChar = ' ';
writer.Indentation = 3;
// Serialize using the XmlTextWriter.
serializer.Serialize(writer, instance);
}
catch(Exception ex)
{
Debug.Write("SerializeObject failed with : " + ex.Message, "West Wind");
retVal = false;
}
finally
{
if (writer != null)
writer.Close();
}
}
else
{
#if NETFULL
Stream fs = null;
try
{
BinaryFormatter serializer = new BinaryFormatter();
fs = new FileStream(fileName, FileMode.Create);
serializer.Serialize(fs, instance);
}
catch
{
retVal = false;
}
finally
{
if (fs != null)
fs.Close();
}
#else
throw new NotSupportedException( Resources.BinaryXmlSerializationNotSupported);
#endif
}
return retVal;
}
///
/// Overload that supports passing in an XML TextWriter.
///
///
/// Note the Writer is not closed when serialization is complete
/// so the caller needs to handle closing.
///
/// object to serialize
/// XmlTextWriter instance to write output to
/// Determines whether false is returned on failure or an exception is thrown
///
public static bool SerializeObject(object instance, XmlTextWriter writer, bool throwExceptions)
{
bool retVal = true;
try
{
XmlSerializer serializer =
new XmlSerializer(instance.GetType());
// Create an XmlTextWriter using a FileStream.
writer.Formatting = Formatting.Indented;
writer.IndentChar = ' ';
writer.Indentation = 3;
// Serialize using the XmlTextWriter.
serializer.Serialize(writer, instance);
}
catch (Exception ex)
{
Debug.Write("SerializeObject failed with : " + ex.GetBaseException().Message + "\r\n" + (ex.InnerException != null ? ex.InnerException.Message : ""), "West Wind");
if (throwExceptions)
throw;
retVal = false;
}
return retVal;
}
///
/// Serializes an object into an XML string variable for easy 'manual' serialization
///
/// object to serialize
/// resulting XML string passed as an out parameter
/// true or false
public static bool SerializeObject(object instance, out string xmlResultString)
{
return SerializeObject(instance, out xmlResultString, false);
}
///
/// Serializes an object into a string variable for easy 'manual' serialization
///
///
/// Out parm that holds resulting XML string
/// If true causes exceptions rather than returning false
///
public static bool SerializeObject(object instance, out string xmlResultString, bool throwExceptions)
{
xmlResultString = string.Empty;
MemoryStream ms = new MemoryStream();
XmlTextWriter writer = new XmlTextWriter(ms, new UTF8Encoding());
if (!SerializeObject(instance, writer,throwExceptions))
{
ms.Close();
return false;
}
xmlResultString = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
ms.Close();
writer.Close();
return true;
}
#if NETFULL
///
/// Serializes an object instance to a file.
///
/// the object instance to serialize
///
///
public static bool SerializeObject(object instance, out byte[] resultBuffer, bool throwExceptions = false)
{
bool retVal = true;
using (var ms = new MemoryStream())
{
try
{
BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(ms, instance);
}
catch (Exception ex)
{
Debug.Write("SerializeObject failed with : " + ex.GetBaseException().Message, "West Wind");
retVal = false;
if (throwExceptions)
throw;
}
ms.Position = 0;
resultBuffer = ms.ToArray();
return retVal;
}
}
#endif
///
/// Serializes an object to an XML string. Unlike the other SerializeObject overloads
/// this methods *returns a string* rather than a bool result!
///
///
/// Determines if a failure throws or returns null
///
/// null on error otherwise the Xml String.
///
///
/// If null is passed in null is also returned so you might want
/// to check for null before calling this method.
///
public static string SerializeObjectToString(object instance, bool throwExceptions = false)
{
string xmlResultString = string.Empty;
if (!SerializeObject(instance, out xmlResultString, throwExceptions))
return null;
return xmlResultString;
}
#if NETFULL
public static byte[] SerializeObjectToByteArray(object instance, bool throwExceptions = false)
{
byte[] byteResult = null;
if ( !SerializeObject(instance, out byteResult) )
return null;
return byteResult;
}
#endif
///
/// Deserializes an object from file and returns a reference.
///
/// name of the file to serialize to
/// The Type of the object. Use typeof(yourobject class)
/// determines whether we use Xml or Binary serialization
/// Instance of the deserialized object or null. Must be cast to your object type
public static object DeSerializeObject(string fileName, Type objectType, bool binarySerialization)
{
return DeSerializeObject(fileName, objectType, binarySerialization, false);
}
///
/// Deserializes an object from file and returns a reference.
///
/// name of the file to serialize to
/// The Type of the object. Use typeof(yourobject class)
/// determines whether we use Xml or Binary serialization
/// determines whether failure will throw rather than return null on failure
/// Instance of the deserialized object or null. Must be cast to your object type
public static object DeSerializeObject(string fileName, Type objectType, bool binarySerialization, bool throwExceptions)
{
object instance = null;
if (!binarySerialization)
{
XmlReader reader = null;
XmlSerializer serializer = null;
FileStream fs = null;
try
{
// Create an instance of the XmlSerializer specifying type and namespace.
serializer = new XmlSerializer(objectType);
// A FileStream is needed to read the XML document.
fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
reader = new XmlTextReader(fs);
instance = serializer.Deserialize(reader);
}
catch(Exception ex)
{
if (throwExceptions)
throw;
string message = ex.Message;
return null;
}
finally
{
if (fs != null)
fs.Close();
if (reader != null)
reader.Close();
}
}
else
{
#if NETFULL
BinaryFormatter serializer = null;
FileStream fs = null;
try
{
serializer = new BinaryFormatter();
fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
instance = serializer.Deserialize(fs);
}
catch
{
return null;
}
finally
{
if (fs != null)
fs.Close();
}
#else
throw new NotSupportedException(Resources.BinaryXmlSerializationNotSupported);
#endif
}
return instance;
}
///
/// Deserialize an object from an XmlReader object.
///
///
///
///
public static object DeSerializeObject(XmlReader reader, Type objectType)
{
XmlSerializer serializer = new XmlSerializer(objectType);
object Instance = serializer.Deserialize(reader);
reader.Close();
return Instance;
}
public static object DeSerializeObject(string xml, Type objectType)
{
XmlTextReader reader = new XmlTextReader(xml, XmlNodeType.Document, null);
return DeSerializeObject(reader, objectType);
}
#if NETFULL
///
/// Deseializes a binary serialized object from a byte array
///
///
///
///
///
public static object DeSerializeObject(byte[] buffer, Type objectType, bool throwExceptions = false)
{
BinaryFormatter serializer = null;
MemoryStream ms = null;
object Instance = null;
try
{
serializer = new BinaryFormatter();
ms = new MemoryStream(buffer);
Instance = serializer.Deserialize(ms);
}
catch
{
if (throwExceptions)
throw;
return null;
}
finally
{
if (ms != null)
ms.Close();
}
return Instance;
}
#endif
///
/// Returns a string of all the field value pairs of a given object.
/// Works only on non-statics.
///
///
///
///
public static string ObjectToString(object instanc, string separator, ObjectToStringTypes type)
{
FieldInfo[] fi = instanc.GetType().GetFields();
string output = string.Empty;
if (type == ObjectToStringTypes.Properties || type == ObjectToStringTypes.PropertiesAndFields)
{
foreach (PropertyInfo property in instanc.GetType().GetProperties())
{
try
{
output += property.Name + ":" + property.GetValue(instanc, null).ToString() + separator;
}
catch
{
output += property.Name + ": n/a" + separator;
}
}
}
if (type == ObjectToStringTypes.Fields || type == ObjectToStringTypes.PropertiesAndFields)
{
foreach (FieldInfo field in fi)
{
try
{
output = output + field.Name + ": " + field.GetValue(instanc).ToString() + separator;
}
catch
{
output = output + field.Name + ": n/a" + separator;
}
}
}
return output;
}
}
public enum ObjectToStringTypes
{
Properties,
PropertiesAndFields,
Fields
}
}
================================================
FILE: Westwind.Utilities/Utilities/ShellUtils.cs
================================================
#pragma warning disable SYSLIB0014
#region
/*
**************************************************************
* Author: Rick Strahl
* © West Wind Technologies, 2011
* http://www.west-wind.com/
*
* Created: 6/19/2011
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.Text;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Westwind.Utilities
{
///
/// Windows specific shell utility functions
///
public static class ShellUtils
{
#region Open in or Start Process
///
/// Executes a Windows process with given command line parameters
///
/// Executable to run
/// Command Line Parameters passed to executable
/// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.
/// Hidden, Normal etc.
/// process exit code or 0 if run and forget. 1460 for time out. -1 on error
public static int ExecuteProcess(string executable,
string arguments = null,
int timeoutMs = 0,
ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal)
{
Process process;
try
{
using (process = new Process())
{
process.StartInfo.FileName = executable;
process.StartInfo.Arguments = arguments;
process.StartInfo.WindowStyle = windowStyle;
if (windowStyle == ProcessWindowStyle.Hidden)
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.Start();
if (timeoutMs < 0)
timeoutMs = 99999999; // indefinitely
if (timeoutMs > 0)
{
if (!process.WaitForExit(timeoutMs))
{
Console.WriteLine("Process timed out.");
return 1460;
}
}
else // run and don't wait - no exit code
return 0;
return process.ExitCode;
}
}
catch (Exception ex)
{
Console.WriteLine("Error executing process: " + ex.Message);
return -1; // unhandled error
}
}
///
/// Executes a Windows process with given command line parameters
/// and captures console output into a string.
///
/// Writes command output to the output StringBuilder
/// from StdOut and StdError.
///
/// Executable to run
/// Command Line Parameters passed to executable
/// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.
/// Pass in a string reference that will receive StdOut and StdError output
/// Hidden, Normal, etc.
/// process exit code or 0 if run and forget. 1460 for time out. -1 on error
public static int ExecuteProcess(string executable,
string arguments,
int timeoutMs,
out StringBuilder output,
ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden,
Action completionDelegate = null)
{
return ExecuteProcess(executable, arguments, timeoutMs, out output, null, windowStyle, completionDelegate);
}
///
/// Executes a Windows process with given command line parameters
/// and captures console output into a string.
///
/// Pass in a String Action that receives output from
/// StdOut and StdError as it is written (one line at a time).
///
/// Executable to run
/// Command Line Parameters passed to executable
/// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.
/// Delegate to let you capture streaming output of the executable to stdout and stderror.
/// Hidden, Normal etc.
/// delegate that is called when execute completes. Passed true if success or false if timeout or failed
/// process exit code or 0 if run and forget. 1460 for time out. -1 on error
public static int ExecuteProcess(string executable,
string arguments,
int timeoutMs,
Action writeDelegate = null,
ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden,
Action completionDelegate = null)
{
return ExecuteProcess(executable, arguments, timeoutMs, out StringBuilder output, writeDelegate, windowStyle, completionDelegate);
}
private static Process process = null;
///
/// Executes a Windows process with given command line parameters
/// and captures console output into a string.
///
/// Writes original output into the application Console which you can
/// optionally redirect to capture output from the command line
/// operation using `Console.SetOut` or `Console.SetError`.
///
/// Executable to run
///
/// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.
/// StringBuilder that will receive StdOut and StdError output
/// Action to capture stdout and stderror output for you to handle
///
/// If waiting for completion you can be notified when the exection is complete
/// process exit code or 0 if run and forget. 1460 for time out. -1 on error
private static int ExecuteProcess(string executable,
string arguments,
int timeoutMs,
out StringBuilder output,
Action writeDelegate = null,
ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden,
Action completionDelegate = null)
{
try
{
using (process = new Process())
{
process.StartInfo.FileName = executable;
process.StartInfo.Arguments = arguments;
process.StartInfo.WindowStyle = windowStyle;
if (windowStyle == ProcessWindowStyle.Hidden)
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
var sb = new StringBuilder();
process.OutputDataReceived += (sender, args) =>
{
if (writeDelegate != null)
writeDelegate.Invoke(args.Data);
else
sb.AppendLine(args.Data);
};
process.ErrorDataReceived += (sender, args) =>
{
if (writeDelegate != null)
writeDelegate.Invoke(args.Data);
else
sb.AppendLine(args.Data);
};
if (completionDelegate != null)
{
process.Exited += (sender, args) =>
{
var proc = sender as Process;
completionDelegate?.Invoke(proc.ExitCode == 0);
};
}
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
if (timeoutMs < 0)
timeoutMs = 99999999; // indefinitely
if (timeoutMs > 0)
{
if (!process.WaitForExit(timeoutMs))
{
Console.WriteLine("Process timed out.");
output = null;
completionDelegate?.Invoke(false);
return 1460;
}
completionDelegate?.Invoke(true);
}
else
{
// no exit code
output = sb;
return 0;
}
output = sb;
return process.ExitCode;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error executing process: {ex.Message}");
output = null;
return -1; // unhandled error
}
}
#endregion
#region Shell Execute Apis, URL Openening
///
/// Uses the Shell Extensions to launch a program based on URL moniker or file name
/// Basically a wrapper around ShellExecute
///
/// Any URL Moniker that the Windows Shell understands (URL, Word Docs, PDF, Email links etc.)
///
public static bool GoUrl(string url, string workingFolder = null)
{
if (string.IsNullOrEmpty(workingFolder))
return OpenUrl(url);
string TPath = Path.GetTempPath();
ProcessStartInfo info = new ProcessStartInfo();
info.UseShellExecute = true;
info.Verb = "Open";
info.WorkingDirectory = TPath;
info.FileName = url;
bool result;
using (Process process = new Process())
{
process.StartInfo = info;
try
{
result = process.Start();
}
catch
{
return false;
}
}
return result;
}
///
/// Opens a URL in the browser. This version is specific to opening
/// a URL in a browser and it's cross platform enabled.
///
///
public static bool OpenUrl(string url)
{
bool success = true;
#if NET6_0_OR_GREATER
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.OSDescription.Contains("microsoft-standard");
#else
bool isWindows = true;
#endif
Process p = null;
try
{
var psi = new ProcessStartInfo(url);
psi.UseShellExecute = isWindows; // must be explicit -defaults changed in NETFX & NETCORE
p = Process.Start(psi);
}
catch
{
#if NET6_0_OR_GREATER
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (isWindows)
{
url = url.Replace("&", "^&");
try
{
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}") {CreateNoWindow = true});
}
catch
{
success = false;
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
p = Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
p = Process.Start("open", url);
}
else
{
success = false;
}
#else
success = false;
#endif
}
p?.Dispose();
return success;
}
///
/// Wrapper around the Shell Execute API. Windows specific.
///
///
///
///
///
///
public static int ShellExecute(string url, string arguments = null,
string workingFolder = null, string verb = "open")
{
ProcessStartInfo info = new ProcessStartInfo();
info.UseShellExecute = true;
info.Verb = verb;
info.FileName = url;
info.Arguments = arguments;
info.WorkingDirectory = workingFolder;
using (Process process = new Process())
{
process.StartInfo = info;
process.Start();
}
return 0;
}
///
/// Opens a File or Folder in Explorer. If the path is a file
/// Explorer is opened in the parent folder with the file selected
///
///
public static bool OpenFileInExplorer(string filename)
{
if (string.IsNullOrEmpty(filename))
return false;
// Is it a directory? Just open
if (Directory.Exists(filename))
ShellExecute(filename);
else
{
// required as command Explorer command line doesn't allow mixed slashes
filename = FileUtils.NormalizePath(filename);
if (!File.Exists(filename))
filename = Path.GetDirectoryName(filename);
try
{
Process.Start("explorer.exe", $"/select,\"{filename}\"");
}
catch
{
return false;
}
}
return true;
}
///
/// Opens a Terminal window in the specified folder
///
///
/// Powershell, Command or Bash
/// false if process couldn't be started - most likely invalid link
public static bool OpenTerminal(string folder, TerminalModes mode = TerminalModes.Powershell)
{
try
{
string cmd = null, args = null;
if (mode == TerminalModes.Powershell)
{
cmd = "powershell.exe";
args = "-noexit -command \"cd '{0}'\"";
}
else if(mode == TerminalModes.Command)
{
cmd = "cmd.exe";
args = "/k \"cd {0}\"";
}
Process.Start(cmd,string.Format(args, folder));
}
catch
{
return false;
}
return true;
}
///
/// Executes a Windows Command Line using Shell Execute as a
/// single command line with parameters. This method handles
/// parsing out the executable from the parameters.
///
/// Full command line - Executable plus arguments.
/// If the executable contains paths with spaces **make sure to add quotes around the executable** otherwise the executable may not be found.
///
/// Optional - the folder the executable runs in. If not specified uses current folder.
/// Optional - Number of milliseconds to wait for completion. 0 don't wait.
/// Optional - Shell verb to apply. Defaults to "Open"
/// Optional - Windows style for the launched application. Default style is normal
///
/// If the executable or parameters contain or **may contain spaces** make sure you use quotes (") or (') around the exec or parameters.
///
/// throws if the process fails to start or doesn't complete in time (if timeout is specified).
///
public static void ExecuteCommandLine(string fullCommandLine,
string workingFolder = null,
int waitForExitMs = 0,
string verb = "OPEN",
ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal,
bool useShellExecute = true)
{
string executable = fullCommandLine;
string args = null;
if (executable.StartsWith("\""))
{
int at = executable.IndexOf("\" ");
if (at > 0)
{
// take the args as provided
args = executable.Substring(at + 1);
// plain executable
executable = executable.Substring(0, at).Trim(' ', '\"');
}
}
else if (executable.StartsWith("\'"))
{
int at = executable.IndexOf("\' ");
if (at > 0)
{
// take the args as provided
args = executable.Substring(at + 1);
// plain executable
executable = executable.Substring(0, at).Trim(' ', '\'');
}
}
else
{
int at = executable.IndexOf(" ");
if (at > 0)
{
if (executable.Length > at + 1)
args = executable.Substring(at + 1).Trim();
executable = executable.Substring(0, at);
}
}
var pi = new ProcessStartInfo
{
Verb = verb,
WindowStyle = windowStyle,
FileName = executable,
WorkingDirectory = workingFolder,
Arguments = args,
UseShellExecute = true
};
Process p;
using (p = Process.Start(pi))
{
if (waitForExitMs > 0)
{
if (!p.WaitForExit(waitForExitMs))
throw new TimeoutException("Process failed to complete in time.");
}
}
}
///
/// Displays a string in in a browser as HTML. Optionally
/// provide an alternate extension to display in the appropriate
/// text viewer (ie. "txt" likely shows in NotePad)
///
///
///
///
public static bool ShowString(string text, string extension = null)
{
if (extension == null)
extension = "htm";
string File = Path.GetTempPath() + "\\__preview." + extension;
StreamWriter sw = new StreamWriter(File, false, Encoding.Default);
sw.Write(text);
sw.Close();
return GoUrl(File);
}
///
/// Shows a string as HTML
///
///
///
public static bool ShowHtml(string htmlString)
{
return ShowString(htmlString, null);
}
///
/// Displays a large Text string as a text file in the
/// systems' default text viewer (ie. NotePad)
///
///
///
public static bool ShowText(string TextString)
{
string File = Path.GetTempPath() + "\\__preview.txt";
StreamWriter sw = new StreamWriter(File, false);
sw.Write(TextString);
sw.Close();
return GoUrl(File);
}
#endregion
#region Simple HTTP Helpers
///
/// Simple method to retrieve HTTP content from the Web quickly
///
/// Url to access
/// Http response text or null
public static string HttpGet(string url)
{
string errorMessage;
return HttpGet(url, out errorMessage);
}
///
/// Simple method to retrieve HTTP content from the Web quickly
///
/// Url to access
///
///
public static string HttpGet(string url, out string errorMessage)
{
string responseText = string.Empty;
errorMessage = null;
using (WebClient Http = new WebClient())
{
try
{
responseText = Http.DownloadString(url);
}
catch (Exception ex)
{
errorMessage = ex.Message;
return null;
}
}
return responseText;
}
///
/// Retrieves a buffer of binary data from a URL using
/// a plain HTTP Get.
///
/// Url to access
/// Response bytes or null on error
public static byte[] HttpGetBytes(string url)
{
string errorMessage;
return HttpGetBytes(url,out errorMessage);
}
///
/// Retrieves a buffer of binary data from a URL using
/// a plain HTTP Get.
///
/// Url to access
/// ref parm to receive an error message
/// response bytes or null on error
public static byte[] HttpGetBytes(string url, out string errorMessage)
{
byte[] result = null;
errorMessage = null;
using (var http = new WebClient())
{
try
{
result = http.DownloadData(url);
}
catch (Exception ex)
{
errorMessage = ex.Message;
return null;
}
}
return result;
}
#endregion
}
public enum TerminalModes
{
Powershell,
Command,
Bash
}
}
#pragma warning restore SYSLIB0014
================================================
FILE: Westwind.Utilities/Utilities/StringUtils.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* (c) West Wind Technologies, 2008 - 2024
* http://www.west-wind.com/
*
* Created: 09/08/2008
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Westwind.Utilities.Properties;
using static System.Net.Mime.MediaTypeNames;
using static System.Net.WebRequestMethods;
namespace Westwind.Utilities
{
///
/// String utility class that provides a host of string related operations
///
public static class StringUtils
{
#region Basic String Tasks
///
/// Trims the beginning of a string by a matching string.
///
/// Overrides string behavior which only works with char.
///
/// Text to trim
/// Text to trim with
/// If true ignore case
/// Trimmed string if match is found
public static string TrimStart(this string text, string textToTrim, bool caseInsensitive = false)
{
if (string.IsNullOrEmpty(text) ||
string.IsNullOrEmpty(textToTrim) ||
text.Length < textToTrim.Length)
return text;
StringComparison comparison = caseInsensitive
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
// account for multiple instances of the text to trim
while (text.Length >= textToTrim.Length &&
text.Substring(0, textToTrim.Length).Equals(textToTrim, comparison))
{
text = text.Substring(textToTrim.Length);
}
return text;
}
///
/// Trims the end of a string with a matching string
///
/// Text to trim
/// Text to trim with
/// If true ignore case
/// Trimmed string if match is found
public static string TrimEnd(this string text, string textToTrim, bool caseInsensitive = false)
{
if (string.IsNullOrEmpty(text) ||
string.IsNullOrEmpty(textToTrim) ||
text.Length < textToTrim.Length)
return text;
while (true)
{
var idx = text.LastIndexOf(textToTrim);
if (idx == -1)
return text;
string match = text.Substring(text.Length - textToTrim.Length, textToTrim.Length);
if (match == textToTrim ||
(!caseInsensitive && match.Equals(textToTrim, StringComparison.OrdinalIgnoreCase)))
{
if (text.Length <= match.Length)
text = "";
else
text = text.Substring(0, idx);
}
else
break;
}
return text;
}
///
/// Trims a string to a specific number of max characters
///
///
///
///
[Obsolete("Please use the StringUtils.Truncate() method instead.")]
public static string TrimTo(string value, int charCount)
{
if (value == null)
return value;
if (value.Length > charCount)
return value.Substring(0, charCount);
return value;
}
///
/// Replicates an input string n number of times
///
///
///
///
public static string Replicate(string input, int charCount)
{
StringBuilder sb = new StringBuilder(input.Length * charCount);
for (int i = 0; i < charCount; i++)
sb.Append(input);
return sb.ToString();
}
///
/// Replicates a character n number of times and returns a string
/// You can use `new string(char, count)` directly though.
///
///
///
///
public static string Replicate(char character, int charCount)
{
return new string(character, charCount);
}
///
/// Finds the nth index of string in a string
///
///
///
///
///
public static int IndexOfNth(this string source, string matchString, int stringInstance, StringComparison stringComparison = StringComparison.CurrentCulture)
{
if (string.IsNullOrEmpty(source))
return -1;
int lastPos = 0;
int count = 0;
while (count < stringInstance)
{
var len = source.Length - lastPos;
lastPos = source.IndexOf(matchString, lastPos, len, stringComparison);
if (lastPos == -1)
break;
count++;
if (count == stringInstance)
return lastPos;
lastPos += matchString.Length;
}
return -1;
}
///
/// Returns the nth Index of a character in a string
///
///
///
///
///
public static int IndexOfNth(this string source, char matchChar, int charInstance)
{
if (string.IsNullOrEmpty(source))
return -1;
if (charInstance < 1)
return -1;
int count = 0;
for (int i = 0; i < source.Length; i++)
{
if (source[i] == matchChar)
{
count++;
if (count == charInstance)
return i;
}
}
return -1;
}
///
/// Finds the nth index of strting in a string
///
///
///
///
///
public static int LastIndexOfNth(this string source, string matchString, int charInstance, StringComparison stringComparison = StringComparison.CurrentCulture)
{
if (string.IsNullOrEmpty(source))
return -1;
int lastPos = source.Length;
int count = 0;
while (count < charInstance)
{
lastPos = source.LastIndexOf(matchString, lastPos, lastPos, stringComparison);
if (lastPos == -1)
break;
count++;
if (count == charInstance)
return lastPos;
}
return -1;
}
///
/// Finds the nth index of in a string from the end.
///
///
///
///
///
public static int LastIndexOfNth(this string source, char matchChar, int charInstance)
{
if (string.IsNullOrEmpty(source))
return -1;
int count = 0;
for (int i = source.Length - 1; i > -1; i--)
{
if (source[i] == matchChar)
{
count++;
if (count == charInstance)
return i;
}
}
return -1;
}
#endregion
#region String Casing
///
/// Compares to strings for equality ignoring case.
/// Uses OrdinalIgnoreCase
///
///
///
///
public static bool EqualsNoCase(this string text, string compareTo)
{
if (text == null && compareTo == null)
return true;
if (text == null || compareTo == null)
return false;
return text.Equals(compareTo, StringComparison.OrdinalIgnoreCase);
}
///
/// Return a string in proper Case format
///
///
///
public static string ProperCase(string Input)
{
if (Input == null)
return null;
return Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(Input);
}
///
/// Takes a phrase and turns it into CamelCase text.
/// White Space, punctuation and separators are stripped
///
/// Text to convert to CamelCase
public static string ToCamelCase(string phrase)
{
if (phrase == null)
return string.Empty;
StringBuilder sb = new StringBuilder(phrase.Length);
// First letter is always upper case
bool nextUpper = true;
foreach (char ch in phrase)
{
if (char.IsWhiteSpace(ch) || char.IsPunctuation(ch) || char.IsSeparator(ch) || ch > 32 && ch < 48)
{
nextUpper = true;
continue;
}
if (char.IsDigit(ch))
{
sb.Append(ch);
nextUpper = true;
continue;
}
if (nextUpper)
sb.Append(char.ToUpper(ch));
else
sb.Append(char.ToLower(ch));
nextUpper = false;
}
return sb.ToString();
}
///
/// Tries to create a phrase string from CamelCase text
/// into Proper Case text. Will place spaces before capitalized
/// letters.
///
/// Note that this method may not work for round tripping
/// ToCamelCase calls, since ToCamelCase strips more characters
/// than just spaces.
///
/// Camel Case Text: firstName -> First Name
///
public static string FromCamelCase(string camelCase)
{
if (string.IsNullOrEmpty(camelCase))
return camelCase;
StringBuilder sb = new StringBuilder(camelCase.Length + 10);
bool first = true;
char lastChar = '\0';
foreach (char ch in camelCase)
{
if (!first &&
lastChar != ' ' && !char.IsSymbol(lastChar) && !char.IsPunctuation(lastChar) &&
((char.IsUpper(ch) && !char.IsUpper(lastChar)) ||
char.IsDigit(ch) && !char.IsDigit(lastChar)))
sb.Append(' ');
sb.Append(ch);
first = false;
lastChar = ch;
}
return sb.ToString(); ;
}
///
/// Attempts to convert a string that is encoded in camel or snake case or
/// and convert it into a proper case string. This is useful for converting
///
///
///
public static string BreakIntoWords(string text)
{
// if the text contains spaces it's already real text
if (string.IsNullOrEmpty(text) || text.Contains(" ") || text.Contains("\t"))
return text;
if (text.Contains("-"))
text = text.Replace("-", " ").Trim();
if (text.Contains("_"))
text = text.Replace("_", " ").Trim();
char c = text[0];
// assume file name was valid as a 'title'
if (char.IsUpper(c))
{
// Split before uppercase letters, but not if preceded by another uppercase letter
// and followed by a lowercase letter (to handle acronyms properly)
string[] words = Regex.Split(text, @"(?
/// Extracts a string from between a pair of delimiters. Only the first
/// instance is found.
///
/// Input String to work on
/// Beginning delimiter
/// ending delimiter
/// Determines whether the search for delimiters is case sensitive
///
///
/// Extracted string or string.Empty on no match
public static string ExtractString(this string source,
string beginDelim,
string endDelim,
bool caseSensitive = false,
bool allowMissingEndDelimiter = false,
bool returnDelimiters = false)
{
int at1, at2;
if (string.IsNullOrEmpty(source))
return string.Empty;
if (caseSensitive)
{
at1 = source.IndexOf(beginDelim, StringComparison.CurrentCulture);
if (at1 == -1)
return string.Empty;
at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.CurrentCulture);
}
else
{
//string Lower = source.ToLower();
at1 = source.IndexOf(beginDelim, 0, source.Length, StringComparison.OrdinalIgnoreCase);
if (at1 == -1)
return string.Empty;
at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.OrdinalIgnoreCase);
}
if (allowMissingEndDelimiter && at2 < 0)
{
if (!returnDelimiters)
return source.Substring(at1 + beginDelim.Length);
return source.Substring(at1);
}
if (at1 > -1 && at2 > 1)
{
if (!returnDelimiters)
return source.Substring(at1 + beginDelim.Length, at2 - at1 - beginDelim.Length);
return source.Substring(at1, at2 - at1 + endDelim.Length);
}
return string.Empty;
}
///
/// Strips characters of a string that follow the specified delimiter
///
/// String to work with
/// String to search for from end of string
/// by default ignores case, set to true to care
/// stripped string, or original string if delimiter was not found
public static string StripAfter(this string value, string delimiter, bool caseSensitive = false)
{
if (string.IsNullOrEmpty(value))
return value;
var pos = caseSensitive ?
value.LastIndexOf(delimiter) :
value.LastIndexOf(delimiter, StringComparison.OrdinalIgnoreCase);
if (pos < 0)
return value;
return value.Substring(0, pos);
}
///
/// String replace function that supports replacing a specific instance with
/// case insensitivity
///
/// Original input string
/// The string that is to be replaced
/// The replacement string
/// Instance of the FindString that is to be found. 1 based. If Instance = -1 all are replaced
/// Case insensitivity flag
/// updated string or original string if no matches
public static string ReplaceStringInstance(string origString, string findString,
string replaceWith, int instance, bool caseInsensitive)
{
if (string.IsNullOrEmpty(origString) || string.IsNullOrEmpty(findString))
return origString; // nothing to do
if (instance == -1) // all instances
#if NET6_0_OR_GREATER
// use native if possible - can only replace all instances
return origString.Replace(findString, replaceWith, StringComparison.OrdinalIgnoreCase);
#else
return ReplaceString(origString, findString, replaceWith, caseInsensitive);
#endif
int at1 = 0;
for (int x = 0; x < instance; x++)
{
if (caseInsensitive)
at1 = origString.IndexOf(findString, at1, origString.Length - at1, StringComparison.OrdinalIgnoreCase);
else
at1 = origString.IndexOf(findString, at1);
if (at1 == -1)
return origString;
if (x < instance - 1)
at1 += findString.Length;
}
return origString.Substring(0, at1) + replaceWith + origString.Substring(at1 + findString.Length);
}
///
/// Replaces a substring within a string with another substring with optional case sensitivity turned off.
///
/// String to do replacements on
/// The string to find
/// The string to replace found string wiht
/// If true case insensitive search is performed
/// updated string or original string if no matches
#if NET6_0_OR_GREATER
[Obsolete("You can use native `string.Replace()` with StringComparison in .NET Core")]
#endif
public static string ReplaceString(string origString, string findString, string replaceString, bool caseInsensitive)
{
if (string.IsNullOrEmpty(origString) || string.IsNullOrEmpty(findString))
return origString; // nothing to do
int at1 = 0;
while (true)
{
if (caseInsensitive)
at1 = origString.IndexOf(findString, at1, origString.Length - at1, StringComparison.OrdinalIgnoreCase);
else
at1 = origString.IndexOf(findString, at1);
if (at1 == -1)
break;
origString = origString.Substring(0, at1) + replaceString + origString.Substring(at1 + findString.Length);
at1 += replaceString.Length;
}
return origString;
}
///
/// Replaces the last nth occurrence of a string within a string with another string
///
/// Souce string
/// Value to replace
/// Value to replace with
/// The instance from the end to replace
/// String comparison mode
/// replaced string or original string if replacement is not found
public static string ReplaceLastNthInstance(string source, string oldValue, string newValue, int instanceFromEnd = 1, StringComparison compare = StringComparison.CurrentCulture)
{
if (instanceFromEnd <= 0 || source == null || oldValue == null) return source; // Invalid n value
int lastIndex = source.Length;
// Traverse the string backwards
while (instanceFromEnd > 0)
{
lastIndex = source.LastIndexOf(oldValue, lastIndex - 1, compare);
if (lastIndex == -1) return source; // If not found, return the original string
instanceFromEnd--;
}
// Replace the found occurrence
return source.Substring(0, lastIndex) + newValue + source.Substring(lastIndex + oldValue.Length);
}
///
/// Truncate a string to maximum length.
///
/// Text to truncate
/// Maximum length
/// Trimmed string
public static string Truncate(this string text, int maxLength)
{
if (string.IsNullOrEmpty(text) || text.Length <= maxLength)
return text;
return text.Substring(0, maxLength);
}
///
/// Returns an abstract of the provided text by returning up to Length characters
/// of a text string. If the text is truncated a ... is appended.
///
/// Note: Linebreaks are converted into spaces.
///
/// Text to abstract
/// Number of characters to abstract to
/// string
public static string TextAbstract(string text, int length)
{
if (string.IsNullOrEmpty(text))
return string.Empty;
if (text.Length > length)
{
text = text.Substring(0, length);
var idx = text.LastIndexOf(' ');
if (idx > -1)
text = text.Substring(0, idx) + "…";
}
if (!text.Contains("\n"))
return text;
// linebreaks to spaces
StringBuilder sb = new StringBuilder(text.Length);
foreach (var s in GetLines(text))
sb.Append(s.Trim() + " ");
return sb.ToString().Trim();
}
///
/// Terminates a string with the given end string/character, but only if the
/// text specified doesn't already exist and the string is not empty.
///
/// String to terminate
/// String to terminate the text string with
///
public static string TerminateString(string value, string terminatorString)
{
if (string.IsNullOrEmpty(value))
return terminatorString;
if (value.EndsWith(terminatorString))
return value;
return value + terminatorString;
}
///
/// Returns the number or right characters specified
///
/// full string to work with
/// number of right characters to return
///
public static string Right(string full, int rightCharCount)
{
if (string.IsNullOrEmpty(full) || full.Length < rightCharCount || full.Length - rightCharCount < 0)
return full;
return full.Substring(full.Length - rightCharCount);
}
#endregion
#region String 'Any' and 'Many' Operations
///
/// Checks many a string for multiple string values to start with
///
/// String to check
/// Values to check in string
///
public static bool StartsWithAny(this string str, params string[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
if (str.StartsWith(value))
return true;
}
return false;
}
///
/// Checks many a string for multiple string values to start with
///
/// String to check
/// Comparision mode
/// Values to check in string
///
public static bool StartsWithAny(this string str, StringComparison compare, params string[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
if (str.StartsWith(value, compare))
return true;
}
return false;
}
///
/// Checks a string form multiple contained string values
///
/// String to match
/// Matches to find in the string
///
public static bool ContainsAny(this string str, params string[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
if (str.Contains(value))
return true;
}
return false;
}
///
/// Checks a string form multiple contained string values
///
/// String to match
/// Type of comparison
/// Matches to find in the string
///
public static bool ContainsAny(this string str, StringComparison compare, params string[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
if (str.Contains(value, compare))
return true;
}
return false;
}
///
/// Checks a string form multiple contained string values
///
/// String to match
/// Matches to find in the string
///
public static bool ContainsAny(this string str, params char[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
if (str.Contains(value))
return true;
}
return false;
}
#if NET6_0_OR_GREATER
///
/// Checks a string form multiple contained string values
///
/// String to match
/// Type of comparison
/// Matches to find in the string
///
public static bool ContainsAny(this string str, StringComparison compare, params char[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
if (str.Contains(value, compare))
return true;
}
return false;
}
#endif
///
/// Checks to see if a string contains any of a set of values.
///
/// String to compare
/// String values to compare to
/// null values in matchValues are ignored
///
public static bool EqualsAny(this string str, params string[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
// null values are ignored
if (value == null) continue;
if (str.Equals(value))
return true;
}
return false;
}
///
/// Checks to see if a string contains any of a set of values
///
/// String to check
/// Comparison mode
/// Strings to check for
///
public static bool EqualsAny(this string str, StringComparison compare, params string[] matchValues)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return false;
foreach (var value in matchValues)
{
// null values are ignored
if (value == null) continue;
if (str.Equals(value, compare))
return true;
}
return false;
}
///
/// Replaces multiple matches with a single new value
///
/// This version takes an array of strings as input
///
/// String to work on
/// String values to match
/// String to replace with
///
public static string ReplaceMany(this string str, string[] matchValues, string replaceWith)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return str;
foreach (var value in matchValues)
{
// null values are ignored
if (value == null) continue;
str = str.Replace(value, replaceWith);
}
return str;
}
///
/// Replaces multiple matches with a single new value.
///
/// This version takes a comma delimited list of strings
///
/// String to work on
/// Comma delimited list of values. Values are start and end trimmed
/// String to replace with
///
public static string ReplaceMany(this string str, string valuesToMatch, string replaceWith)
{
if (string.IsNullOrEmpty(valuesToMatch))
return str;
#if NET6_0_OR_GREATER
var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
#else
var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(v => v.Trim())
.ToArray();
#endif
return ReplaceMany(str, matchValues, replaceWith);
}
#if NET6_0_OR_GREATER
///
/// Replaces multiple matches with a single new value
///
/// This version takes an array of strings as input
///
/// String to work on
/// String values to match
/// String to replace with
/// String comparison mode
///
public static string ReplaceMany(this string str, string[] matchValues, string replaceWith, StringComparison compare)
{
if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)
return str;
foreach (var value in matchValues)
{
str = str.Replace(value, replaceWith, compare);
}
return str;
}
///
/// Replaces multiple matches with a single new value.
///
/// This version takes a comma delimited list of strings
///
/// String to work on
/// Comma delimited list of values. Values are start and end trimmed
/// String to replace with
/// String comparison mode
///
public static string ReplaceMany(this string str, string valuesToMatch, string replaceWith, StringComparison compare)
{
if (string.IsNullOrEmpty(valuesToMatch))
return str;
#if NET6_0_OR_GREATER
var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
#else
var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(v => v.Trim())
.ToArray();
#endif
return ReplaceMany(str, matchValues, replaceWith, compare);
}
#endif
#endregion
#region String Parsing
///
/// Determines if a string is contained in a list of other strings
///
///
///
///
public static bool Inlist(this string s, params string[] list)
{
if (string.IsNullOrEmpty(s) || list == null || list.Length < 1)
return false;
return list.Contains(s);
}
#if NET6_0_OR_GREATER
///
/// Determines if a string is contained in a list of other strings
///
///
///
///
public static bool Inlist(string s, StringComparison compare, params string[] list)
{
if (string.IsNullOrEmpty(s) || list == null || list.Length < 1)
return false;
foreach (var item in list)
{
if (item.Equals(s, compare))
return true;
}
return false;
}
#endif
///
/// Checks to see if value is part of a delimited list of values.
/// Example: IsStringInList("value1,value2,value3","value3");
///
/// A list of delimited strings (ie. value1, value2, value3) with or without spaces (values are trimmed)
/// value to match against the list
/// Character that separates the list values
/// If true ignores case for the list value matches
public static bool IsStringInList(string stringList, string valueToFind, char separator = ',', bool ignoreCase = false)
{
var tokens = stringList.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length == 0)
return false;
var comparer = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
foreach (var tok in tokens)
{
if (tok.Trim().Equals(valueToFind, comparer))
return true;
}
return false;
}
///
/// String.Contains() extension method that allows to specify case
///
/// Input text
/// text to search for
/// Case sensitivity options
///
public static bool Contains(this string text, string searchFor, StringComparison stringComparison)
{
return text.IndexOf(searchFor, stringComparison) > -1;
}
///
/// Parses a string into an array of lines broken
/// by \r\n or \n
///
/// String to check for lines
/// Optional - max number of lines to return
/// array of strings, or null if the string passed was a null
public static string[] GetLines(this string s, int maxLines = 0)
{
if (s == null)
return new string[] { };
s = s.Replace("\r\n", "\n").Replace("\r", "\n");
if (maxLines < 1)
return s.Split(new char[] { '\n' });
return s.Split(new char[] { '\n' }).Take(maxLines).ToArray();
}
///
/// Returns a line count for a string
///
/// string to count lines for
///
public static int CountLines(this string s)
{
if (string.IsNullOrEmpty(s))
return 0;
return s.Split('\n').Length;
}
///
/// Counts the number of times a character occurs
/// in a given string
///
/// input string
/// character to match
///
public static int Occurs(string source, char match)
{
if (string.IsNullOrEmpty(source)) return 0;
int count = 0;
foreach (char c in source)
if (c == match)
count++;
return count;
}
///
/// Counts the number of times a sub string occurs
/// in a given string
///
/// input string
/// string to match
///
public static int Occurs(string source, string match)
{
if (string.IsNullOrEmpty(source)) return 0;
return source.Split(new[] { match }, StringSplitOptions.None).Length - 1;
}
///
/// Returns a string that has the max amount of characters of the source string.
/// If the string is shorter than the max length the entire string is returned.
/// If the string is longer it's truncated.
/// If empty the original value is returned (null or string.Empty)
/// If the startPosition is greater than the length of the string null is returned
///
/// source string to work on
/// Maximum number of characters to return
/// Optional start position. If not specified uses entire string (0)
///
public static string GetMaxCharacters(this string s, int maxCharacters, int startPosition = 0)
{
if (string.IsNullOrEmpty(s) || startPosition == 0 && maxCharacters > s.Length)
return s;
if (startPosition > s.Length - 1)
return null;
var available = s.Length - startPosition;
return s.Substring(startPosition, Math.Min(available, maxCharacters));
}
///
/// Retrieves the last n characters from the end of a string up to the
/// number of characters specified. If there are fewer characters
/// the original string is returned otherwise the last n characters
/// are returned.
///
/// input string
/// number of characters to retrieve from end of string
/// Up to the last n characters of the string. Empty string on empty or null
public static string GetLastCharacters(this string s, int characterCount)
{
if (string.IsNullOrEmpty(s) || s.Length < characterCount)
return s ?? string.Empty;
return s.Substring(s.Length - characterCount);
}
///
/// Strips all non digit values from a string and only
/// returns the numeric string.
///
///
///
public static string StripNonNumber(string input)
{
var chars = input.ToCharArray();
StringBuilder sb = new StringBuilder();
foreach (var chr in chars)
{
if (char.IsNumber(chr) || char.IsSeparator(chr))
sb.Append(chr);
}
return sb.ToString();
}
static Regex tokenizeRegex = new Regex("{{.*?}}");
///
/// Tokenizes a string based on a start and end string. Replaces the values with a token
/// text (#@#1#@# for example).
///
/// You can use Detokenize to get the original values back using DetokenizeString
/// using the same token replacement text.
///
/// Text to search
/// starting match string
/// ending match string
/// token replacement text - make sure this string is a value that is unique and **doesn't occur in the document**
/// A list of extracted string tokens that have been replaced in `ref text` with the replace delimiter
public static List TokenizeString(ref string text, string startMatch, string endMatch, string replaceDelimiter = "#@#")
{
var strings = new List();
var matches = tokenizeRegex.Matches(text);
int i = 0;
foreach (Match match in matches)
{
tokenizeRegex = new Regex(Regex.Escape(match.Value));
text = tokenizeRegex.Replace(text, $"{replaceDelimiter}{i}{replaceDelimiter}", 1);
strings.Add(match.Value);
i++;
}
return strings;
}
///
/// Detokenizes a string tokenized with TokenizeString. Requires the collection created
/// by detokenization
///
/// Text to work with
/// list of previously extracted tokens
/// the token replacement string that replaced the captured tokens
///
public static string DetokenizeString(string text, IEnumerable tokens, string replaceDelimiter = "#@#")
{
int i = 0;
foreach (string token in tokens)
{
text = text.Replace($"{replaceDelimiter}{i}{replaceDelimiter}", token);
i++;
}
return text;
}
///
/// Parses an string into an integer. If the text can't be parsed
/// a default text is returned instead
///
/// Input numeric string to be parsed
/// Optional default text if parsing fails
/// Optional NumberFormat provider. Defaults to current culture's number format
///
public static int ParseInt(string input, int defaultValue = 0, IFormatProvider numberFormat = null)
{
if (numberFormat == null)
numberFormat = CultureInfo.CurrentCulture.NumberFormat;
int val = defaultValue;
if (input == null)
return defaultValue;
if (!int.TryParse(input, NumberStyles.Any, numberFormat, out val))
return defaultValue;
return val;
}
///
/// Parses an string into an decimal. If the text can't be parsed
/// a default text is returned instead
///
///
///
///
public static decimal ParseDecimal(string input, decimal defaultValue = 0M, IFormatProvider numberFormat = null)
{
numberFormat = numberFormat ?? CultureInfo.CurrentCulture.NumberFormat;
decimal val = defaultValue;
if (input == null)
return defaultValue;
if (!decimal.TryParse(input, NumberStyles.Any, numberFormat, out val))
return defaultValue;
return val;
}
#endregion
#region String Ids
///
/// Creates short string id based on a GUID hashcode.
/// Not guaranteed to be unique across machines, but unlikely
/// to duplicate in medium volume situations.
///
///
public static string NewStringId()
{
return Guid.NewGuid().ToString().GetHashCode().ToString("x");
}
///
/// Creates a new random string of upper, lower case letters and digits.
/// Very useful for generating random data for storage in test data.
///
/// The number of characters of the string to generate
/// randomized string
public static string RandomString(int size, bool includeNumbers = false)
{
StringBuilder builder = new StringBuilder(size);
char ch;
int num;
for (int i = 0; i < size; i++)
{
if (includeNumbers)
num = Convert.ToInt32(Math.Floor(62 * random.NextDouble()));
else
num = Convert.ToInt32(Math.Floor(52 * random.NextDouble()));
if (num < 26)
ch = Convert.ToChar(num + 65);
// lower case
else if (num > 25 && num < 52)
ch = Convert.ToChar(num - 26 + 97);
// numbers
else
ch = Convert.ToChar(num - 52 + 48);
builder.Append(ch);
}
return builder.ToString();
}
private static Random random = new Random((int)DateTime.Now.Ticks);
#endregion
#region Encodings
///
/// UrlEncodes a string without the requirement for System.Web
///
///
///
// [Obsolete("Use System.Uri.EscapeDataString instead")]
public static string UrlEncode(string text)
{
if (string.IsNullOrEmpty(text))
return string.Empty;
return Uri.EscapeDataString(text);
}
///
/// Encodes a few additional characters for use in paths
/// Encodes: . #
///
///
///
public static string UrlEncodePathSafe(string text)
{
string escaped = UrlEncode(text);
return escaped.Replace(".", "%2E").Replace("#", "%23");
}
///
/// UrlDecodes a string without requiring System.Web
///
/// String to decode.
/// decoded string
public static string UrlDecode(string text)
{
if (string.IsNullOrEmpty(text)) return text;
// pre-process for + sign space formatting since System.Uri doesn't handle it
// plus literals are encoded as %2b normally so this should be safe
text = text.Replace("+", " ");
string decoded = Uri.UnescapeDataString(text);
return decoded;
}
///
/// Retrieves a text by key from a UrlEncoded string.
///
/// UrlEncoded String
/// Key to retrieve text for
/// returns the text or "" if the key is not found or the text is blank
public static string GetUrlEncodedKey(string urlEncoded, string key)
{
if (string.IsNullOrEmpty(urlEncoded) || string.IsNullOrEmpty(key) ) return string.Empty;
urlEncoded = "&" + urlEncoded + "&";
int Index = urlEncoded.IndexOf("&" + key + "=", StringComparison.OrdinalIgnoreCase);
if (Index < 0)
return string.Empty;
int lnStart = Index + 2 + key.Length;
int Index2 = urlEncoded.IndexOf("&", lnStart);
if (Index2 < 0)
return string.Empty;
return UrlDecode(urlEncoded.Substring(lnStart, Index2 - lnStart));
}
///
/// Allows setting of a text in a UrlEncoded string. If the key doesn't exist
/// a new one is set, if it exists it's replaced with the new text.
///
/// A UrlEncoded string of key text pairs
///
///
///
public static string SetUrlEncodedKey(string urlEncoded, string key, string value)
{
if (!urlEncoded.EndsWith("?") && !urlEncoded.EndsWith("&"))
urlEncoded += "&";
Match match = Regex.Match(urlEncoded, "[?|&]" + key + "=.*?&");
if (match == null || string.IsNullOrEmpty(match.Value))
urlEncoded = urlEncoded + key + "=" + UrlEncode(value) + "&";
else
urlEncoded = urlEncoded.Replace(match.Value, match.Value.Substring(0, 1) + key + "=" + UrlEncode(value) + "&");
return urlEncoded.TrimEnd('&');
}
#endregion
#region Binary Encoding
///
/// Turns a BinHex string that contains raw byte values
/// into a byte array
///
/// BinHex string (just two byte hex digits strung together)
///
public static byte[] BinHexToBinary(string hex)
{
int offset = hex.StartsWith("0x") ? 2 : 0;
if ((hex.Length % 2) != 0)
throw new ArgumentException(string.Format(Resources.InvalidHexStringLength, hex.Length));
byte[] ret = new byte[(hex.Length - offset) / 2];
for (int i = 0; i < ret.Length; i++)
{
ret[i] = (byte)((ParseHexChar(hex[offset]) << 4)
| ParseHexChar(hex[offset + 1]));
offset += 2;
}
return ret;
}
///
/// Converts a byte array into a BinHex string.
/// BinHex is two digit hex byte values squished together
/// into a string.
///
/// Raw data to send
/// BinHex string or null if input is null
public static string BinaryToBinHex(byte[] data)
{
if (data == null)
return null;
char[] c = new char[data.Length * 2];
int b;
for (int i = 0; i < data.Length; i++)
{
b = data[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = data[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
return new string(c).ToLower();
}
///
/// Converts a string into bytes for storage in any byte[] types
/// buffer or stream format (like MemoryStream).
///
///
/// The character encoding to use. Defaults to Unicode
///
public static byte[] StringToBytes(string text, Encoding encoding = null)
{
if (text == null)
return null;
if (encoding == null)
encoding = Encoding.Unicode;
return encoding.GetBytes(text);
}
///
/// Converts a byte array to a stringUtils
///
/// raw string byte data
/// Character encoding to use. Defaults to Unicode
///
public static string BytesToString(byte[] buffer, Encoding encoding = null)
{
if (buffer == null)
return null;
if (encoding == null)
encoding = Encoding.Unicode;
return encoding.GetString(buffer);
}
///
/// Converts a string to a Base64 string
///
/// A string to convert to base64
/// Optional encoding - if not passed assumed to be Unicode
/// Base 64 or null
public static string ToBase64String(string text, Encoding encoding = null)
{
var bytes = StringToBytes(text, encoding);
if (bytes == null)
return null;
return Convert.ToBase64String(bytes);
}
///
/// Converts a base64 string back to a string
///
/// A base 64 string
/// Optional encoding - if not passed assumed to be Unicode
///
public static string FromBase64String(string base64, Encoding encoding = null)
{
var bytes = Convert.FromBase64String(base64);
if (bytes == null)
return null;
return BytesToString(bytes, encoding);
}
static int ParseHexChar(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
throw new ArgumentException(Resources.InvalidHexDigit + c);
}
static char[] base36CharArray = "0123456789abcdefghijklmnopqrstuvwxyz".ToCharArray();
static string base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz";
///
/// Encodes an integer into a string by mapping to alpha and digits (36 chars)
/// chars are embedded as lower case
///
/// Example: 4zx12ss
///
///
///
public static string Base36Encode(long value)
{
string returnValue = "";
bool isNegative = value < 0;
if (isNegative)
value = value * -1;
do
{
returnValue = base36CharArray[value % base36CharArray.Length] + returnValue;
value /= 36;
} while (value != 0);
return isNegative ? returnValue + "-" : returnValue;
}
///
/// Decodes a base36 encoded string to an integer
///
///
///
public static long Base36Decode(string input)
{
bool isNegative = false;
if (input.EndsWith("-"))
{
isNegative = true;
input = input.Substring(0, input.Length - 1);
}
char[] arrInput = input.ToCharArray();
Array.Reverse(arrInput);
long returnValue = 0;
for (long i = 0; i < arrInput.Length; i++)
{
long valueindex = base36Chars.IndexOf(arrInput[i]);
returnValue += Convert.ToInt64(valueindex * Math.Pow(36, i));
}
return isNegative ? returnValue * -1 : returnValue;
}
#endregion
#region Miscellaneous
///
/// Normalizes linefeeds to the appropriate
///
/// The text to fix up
/// Type of linefeed to fix up to
///
public static string NormalizeLineFeeds(string text, LineFeedTypes type = LineFeedTypes.Auto)
{
if (string.IsNullOrEmpty(text))
return text;
if (type == LineFeedTypes.Auto)
{
if (Environment.NewLine.Contains('\r'))
type = LineFeedTypes.CrLf;
else
type = LineFeedTypes.Lf;
}
if (type == LineFeedTypes.Lf)
return text.Replace("\r\n", "\n");
return text.Replace("\r\n", "\n").Replace("\n", "\r\n");
}
///
/// Strips any common white space from all lines of text that have the same
/// common white space text. Effectively removes common code indentation from
/// code blocks for example so you can get a left aligned code snippet.
///
/// Text to normalize
///
public static string NormalizeIndentation(string code)
{
if (string.IsNullOrEmpty(code))
return string.Empty;
// normalize tabs to 3 spaces
string text = code.Replace("\t", " ");
string[] lines = text.Split(new string[3] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
// keep track of the smallest indent
int minPadding = 1000;
foreach (var line in lines)
{
if (line.Length == 0) // ignore blank lines
continue;
int count = 0;
foreach (char chr in line)
{
if (chr == ' ' && count < minPadding)
count++;
else
break;
}
if (count == 0)
return code;
minPadding = count;
}
string strip = new String(' ', minPadding);
StringBuilder sb = new StringBuilder();
foreach (var line in lines)
{
sb.AppendLine(StringUtils.ReplaceStringInstance(line, strip, "", 1, false));
}
return sb.ToString();
}
///
/// Simple Logging method that allows quickly writing a string to a file
///
///
///
/// if not specified used UTF-8
public static void LogString(string output, string filename, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.UTF8;
lock (_logLock)
{
var writer = new StreamWriter(filename, true, encoding);
writer.WriteLine(DateTime.Now + " - " + output);
writer.Close();
}
}
private static object _logLock = new object();
///
/// Creates a Stream from a string. Internally creates
/// a memory stream and returns that.
///
/// Note: stream returned should be disposed!
///
///
///
///
public static Stream StringToStream(string text, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.Default;
var ms = new MemoryStream(text.Length * 2);
byte[] data = encoding.GetBytes(text);
ms.Write(data, 0, data.Length);
ms.Position = 0;
return ms;
}
///
/// Creates a string from a text based stream
///
/// input stream (not closed by operation)
/// Optional encoding - if not specified assumes 'Encoding.Default'
///
///
public static string StreamToString(Stream stream, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.Default;
if (!stream.CanRead)
throw new InvalidOleVariantTypeException("Stream cannot be read.");
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
///
/// Retrieves a string value from an XML-like string collection that was stored via SetProperty()
///
/// String of XML like values (not proper XML)
/// The key of the property to return or empty string
///
public static string GetProperty(string propertyString, string key)
{
var value = StringUtils.ExtractString(propertyString, "<" + key + ">", "" + key + ">");
return value;
}
///
/// Sets a property value in an XML-like structure that can be used to store properties
/// in a string.
///
/// String of XML like values (not proper XML)
/// a key in that string
/// the string value to store
///
public static string SetProperty(string propertyString, string key, string value)
{
string extract = StringUtils.ExtractString(propertyString, "<" + key + ">", "" + key + ">");
if (string.IsNullOrEmpty(value) && extract != string.Empty)
{
return propertyString.Replace(extract, "");
}
// NOTE: Value is not XML encoded - we only retrieve based on named nodes so no conflict
string xmlLine = "<" + key + ">" + value + "" + key + ">";
// replace existing
if (extract != string.Empty)
return propertyString.Replace(extract, xmlLine);
// add new
return propertyString + xmlLine + "\r\n";
}
///
/// A helper to generate a JSON string from a string value
///
/// Use this to avoid bringing in a full JSON Serializer for
/// scenarios of string serialization.
///
///
/// JSON encoded string ("text"), empty ("") or "null".
public static string ToJsonString(string text)
{
if (text is null)
return "null";
var sb = new StringBuilder(text.Length);
sb.Append("\"");
var ct = text.Length;
for (int x = 0; x < ct; x++)
{
var c = text[x];
switch (c)
{
case '\"':
sb.Append("\\\"");
break;
case '\\':
sb.Append("\\\\");
break;
case '\b':
sb.Append("\\b");
break;
case '\f':
sb.Append("\\f");
break;
case '\n':
sb.Append("\\n");
break;
case '\r':
sb.Append("\\r");
break;
case '\t':
sb.Append("\\t");
break;
default:
uint i = c;
if (i < 32) // || i > 255
sb.Append($"\\u{i:x4}");
else
sb.Append(c);
break;
}
}
sb.Append("\"");
return sb.ToString();
}
#endregion
}
public enum LineFeedTypes
{
// Linefeed \n only
Lf,
// Carriage Return and Linefeed \r\n
CrLf,
// Platform default Environment.NewLine
Auto
}
}
================================================
FILE: Westwind.Utilities/Utilities/TimeUtils.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* West Wind Technologies, 2008 - 2009
* http://www.west-wind.com/
*
* Created: 09/08/2008
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.Globalization;
using Westwind.Utilities.Properties;
namespace Westwind.Utilities
{
///
/// Time Utilities class provides date and time related routines.
///
public static class TimeUtils
{
public static DateTime MIN_DATE_VALUE = new DateTime(1900, 1, 1, 0, 0, 0, 0, CultureInfo.InvariantCulture.Calendar, DateTimeKind.Utc);
///
/// Displays a date in friendly format.
///
///
///
/// Today,Yesterday,Day of week or a string day (Jul 15, 2008)
public static string FriendlyDateString(DateTime date, bool showTime = false, string timeSeparator = "-")
{
if (date < TimeUtils.MIN_DATE_VALUE)
return string.Empty;
string FormattedDate = string.Empty;
if (date.Date.Equals(DateTime.Today))
FormattedDate = Resources.Today;
else if (date.Date == DateTime.Today.AddDays(-1))
FormattedDate = Resources.Yesterday;
else if (date.Date > DateTime.Today.AddDays(-6))
// Show the Day of the week
FormattedDate = date.ToString("dddd").ToString();
else
FormattedDate = date.ToString("MMMM dd, yyyy");
if (showTime)
FormattedDate += " " + timeSeparator + " " + date.ToString("t").ToLower().Replace(" ","");
return FormattedDate;
}
///
/// Returns a short date time string
///
/// The date to format
/// If true displays simpe time (hour and minutes)
/// Date and time separator character
///
public static string ShortDateString(DateTime date, bool showTime=false, string separator = "-")
{
if (date < TimeUtils.MIN_DATE_VALUE)
return string.Empty;
string dateString = date.ToString("MMM dd, yyyy");
if (!showTime)
return dateString;
return dateString + " " + separator + " " + date.ToString("t").Replace(" ","").ToLower();
}
///
/// Returns a short date time string
///
///
///
///
public static string ShortDateString(DateTime? date, bool ShowTime)
{
if (date == null || !date.HasValue)
return string.Empty;
return ShortDateString(date.Value, ShowTime);
}
///
/// Short date time format that shows hours and minutes.
/// Culture adjusted but packs down US dates.
///
/// Formatted date
public static string ShortTimeString(DateTime date)
{
return date.ToString("t").Replace(" ", "").ToLower();
}
///
/// Displays a number of milliseconds as friendly seconds, hours, minutes
/// Pass -1 to get a blank date.
///
/// Note: English only!
///
/// the elapsed milliseconds to display time for
/// string in format of just now or 1m ago, 2h ago
public static string FriendlyElapsedTimeString(int milliSeconds)
{
return FriendlyElapsedTimeString(Convert.ToDouble( milliSeconds));
}
///
/// Displays a number of milliseconds as friendly seconds, hours, minutes
/// Pass -1 to get a blank date.
///
/// Note: English only!
///
/// the elapsed milliseconds to display time for
/// string in format of just now or 1m ago, 2h ago
public static string FriendlyElapsedTimeString(double milliSeconds)
{
if (milliSeconds == 0)
return string.Empty;
milliSeconds = Math.Abs(milliSeconds);
if (milliSeconds < 20000)
return "just now";
if (milliSeconds < 60000)
return ((int)(milliSeconds / 1000)) + "s ago";
if (milliSeconds < 3600000)
return ((int)(milliSeconds / 60000)) + "m ago";
if (milliSeconds < 86400000 * 2) // 2 days
return ((int)(milliSeconds / 3600000)) + "h ago";
if (milliSeconds < 86400000F * 30F) // 30 days
return ((int)(milliSeconds / 86400000 )) + "d ago";
if (milliSeconds < 86400000F * 365F ) // 365 days
return (Math.Round(milliSeconds / 2592000000)) + "mo ago";
return (Math.Round(milliSeconds / (60000F * 60F * 24F * 365F)) ) + "y ago";
}
///
/// Displays the elapsed time friendly seconds, hours, minutes
///
/// Timespan of elapsed time
/// string in format of just now or 1m ago, 2h ago
public static string FriendlyElapsedTimeString(TimeSpan elapsed)
{
return FriendlyElapsedTimeString(elapsed.TotalMilliseconds);
}
///
/// Converts a fractional hour value like 1.25 to 1:15 hours:minutes format
///
/// Decimal hour value
/// An optional format string where {0} is hours and {1} is minutes (ie: "{0}h:{1}m").
///
public static string FractionalHoursToString(decimal hours, string format)
{
if (string.IsNullOrEmpty(format))
format = "{0}:{1}";
TimeSpan tspan = TimeSpan.FromHours((double)hours);
// Account for rounding error
int minutes = tspan.Minutes;
if (tspan.Seconds > 29)
minutes++;
return string.Format(format, tspan.Hours + tspan.Days * 24, minutes);
}
///
/// Converts a fractional hour value like 1.25 to 1:15 hours:minutes format
///
/// Decimal hour value
public static string FractionalHoursToString(decimal hours)
{
return FractionalHoursToString(hours, null);
}
///
/// Rounds an hours value to a minute interval
/// 0 means no rounding
///
/// Minutes to round up or down to
///
public static decimal RoundDateToMinuteInterval(decimal hours, int minuteInterval,
RoundingDirection direction)
{
if (minuteInterval == 0)
return hours;
decimal fraction = 60 / minuteInterval;
switch (direction)
{
case RoundingDirection.Round:
return Math.Round(hours * fraction, 0) / fraction;
case RoundingDirection.RoundDown:
return Math.Truncate(hours * fraction) / fraction;
}
return Math.Ceiling(hours * fraction) / fraction;
}
///
/// Rounds a date value to a given minute interval
///
/// Original time value
/// Number of minutes to round up or down to
///
public static DateTime RoundDateToMinuteInterval(DateTime time, int minuteInterval,
RoundingDirection direction)
{
if (minuteInterval == 0)
return time;
decimal interval = (decimal)minuteInterval;
decimal actMinute = (decimal)time.Minute;
if (actMinute == 0.00M)
return time;
int newMinutes = 0;
switch (direction)
{
case RoundingDirection.Round:
newMinutes = (int)(Math.Round(actMinute / interval, 0) * interval);
break;
case RoundingDirection.RoundDown:
newMinutes = (int)(Math.Truncate(actMinute / interval) * interval);
break;
case RoundingDirection.RoundUp:
newMinutes = (int)(Math.Ceiling(actMinute / interval) * interval);
break;
}
// strip time
time = time.AddMinutes(time.Minute * -1);
time = time.AddSeconds(time.Second * -1);
time = time.AddMilliseconds(time.Millisecond * -1);
// add new minutes back on
return time.AddMinutes(newMinutes);
}
///
/// Creates a DateTime value from date and time input values
///
///
///
///
public static DateTime DateTimeFromDateAndTime(string Date, string Time)
{
return DateTime.Parse(Date + " " + Time);
}
///
/// Creates a DateTime Value from a DateTime date and a string time value.
///
///
///
///
public static DateTime DateTimeFromDateAndTime(DateTime Date, string Time)
{
return DateTime.Parse(Date.ToShortDateString() + " " + Time);
}
///
/// Converts the passed date time value to Mime formatted time string
///
///
public static string MimeDateTime(DateTime Time)
{
TimeSpan Offset = TimeZoneInfo.Local.GetUtcOffset(Time);
//TimeSpan Offset = TimeZone.CurrentTimeZone.GetUtcOffset(Time);
string sOffset = null;
if (Offset.Hours < 0)
sOffset = "-" + (Offset.Hours * -1).ToString().PadLeft(2, '0');
else
sOffset = "+" + Offset.Hours.ToString().PadLeft(2, '0');
sOffset += Offset.Minutes.ToString().PadLeft(2, '0');
return "Date: " + Time.ToString("ddd, dd MMM yyyy HH:mm:ss",
System.Globalization.CultureInfo.InvariantCulture) +
" " + sOffset;
}
///
/// Returns whether a date time is between two other dates. Optionally
/// can compare date only or date and time.
///
///
///
///
/// If true compare date and time, otherwise just date
/// true or false
public static bool IsBetween(this DateTime date, DateTime startDate, DateTime endDate, bool includeTime = true)
{
return includeTime ?
date >= startDate && date <= endDate :
date.Date >= startDate.Date && date.Date <= endDate.Date;
}
///
/// Returns whether a timespan is between two dates
///
///
///
///
/// true or false
public static bool IsBetween(this TimeSpan date, DateTime startDate, DateTime endDate)
{
return date.CompareTo(endDate) <= 0 && date.CompareTo(endDate) >= 0;
}
///
/// Truncates a DateTime value to the nearest partial value.
///
///
/// From: http://stackoverflow.com/questions/1004698/how-to-truncate-milliseconds-off-of-a-net-datetime
///
///
///
///
public static DateTime Truncate(DateTime date, DateTimeResolution resolution = DateTimeResolution.Second)
{
switch (resolution)
{
case DateTimeResolution.Year:
return new DateTime(date.Year, 1, 1, 0, 0, 0, 0, date.Kind);
case DateTimeResolution.Month:
return new DateTime(date.Year, date.Month, 1, 0, 0, 0, date.Kind);
case DateTimeResolution.Day:
return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, date.Kind);
case DateTimeResolution.Hour:
return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerHour));
case DateTimeResolution.Minute:
return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerMinute));
case DateTimeResolution.Second:
return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerSecond));
case DateTimeResolution.Millisecond:
return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerMillisecond));
case DateTimeResolution.Tick:
return date.AddTicks(0);
default:
throw new ArgumentException("unrecognized resolution", "resolution");
}
}
///
/// Returns TimeZone adjusted time for a given from a Utc or local time.
/// Date is first converted to UTC then adjusted.
///
///
///
///
public static DateTime ToTimeZoneTime(this DateTime time, string timeZoneId = "Pacific Standard Time")
{
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
return time.ToTimeZoneTime(tzi);
}
///
/// Returns TimeZone adjusted time for a given from a Utc or local time.
/// Date is first converted to UTC then adjusted.
///
///
///
///
public static DateTime ToTimeZoneTime(this DateTime time, TimeZoneInfo tzi)
{
return TimeZoneInfo.ConvertTimeFromUtc(time, tzi);
}
}
///
/// Determines how date time values are rounded
///
public enum RoundingDirection
{
RoundUp,
RoundDown,
Round
}
public enum DateTimeResolution
{
Year, Month, Day, Hour, Minute, Second, Millisecond, Tick
}
}
================================================
FILE: Westwind.Utilities/Utilities/VersionUtils.cs
================================================
using System;
using System.Linq;
using System.Net;
namespace Westwind.Utilities
{
public static class VersionExtensions
{
///
/// Formats a version by stripping all zero values
/// up to the trimTokens count provided. By default
/// displays Major.Minor and then displays any
/// Build and/or revision if non-zero
///
/// More info: https://weblog.west-wind.com/posts/2024/Jun/13/C-Version-Formatting
///
/// Version to format
/// Minimum number of component tokens of the version to display
/// Maximum number of component tokens of the version to display
public static string FormatVersion(this Version version, int minTokens = 2, int maxTokens = 2)
{
if (minTokens < 1)
minTokens = 1;
if (minTokens > 4)
minTokens = 4;
if (maxTokens < minTokens)
maxTokens = minTokens;
if (maxTokens > 4)
maxTokens = 4;
var items = new [] { version.Major, version.Minor, version.Build, version.Revision };
int tokens = maxTokens;
while (tokens > minTokens && items[tokens - 1] == 0)
{
tokens--;
}
return version.ToString(tokens);
}
///
/// Formats a version by stripping all zero values
/// up to the trimTokens count provided. By default
/// displays Major.Minor and then displays any
/// Build and/or revision if non-zero
///
/// Version to format
/// Minimum number of component tokens to display
/// Maximum number of component tokens to display
public static string FormatVersion(string version, int minTokens = 2, int maxTokens = 2)
{
var ver = new Version(version);
return ver.FormatVersion(minTokens, maxTokens);
}
///
/// Compare two version strings.
///
/// Semantic Version string
/// Semantic Version string
/// 0 - equal, 1 - greater than compareAgainst, -1 - smaller than, -2 - Version Format error
public static int CompareVersions(string versionToCompare, string versionToCompareAgainst)
{
try
{
var v1 = new Version(versionToCompare);
var v2 = new Version(versionToCompareAgainst);
return v1.CompareTo(v2);
}
catch
{
return -2;
}
}
}
}
================================================
FILE: Westwind.Utilities/Utilities/XmlUtils.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* © West Wind Technologies, 2008 - 2009
* http://www.west-wind.com/
*
* Created: 09/08/2008
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.Globalization;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Westwind.Utilities.Properties;
namespace Westwind.Utilities
{
///
/// String utility class that provides a host of string related operations
///
public static class XmlUtils
{
///
/// Turns a string into a properly XML Encoded string.
/// Uses simple string replacement.
///
/// Also see XmlUtils.XmlString() which uses XElement
/// to handle additional extended characters.
///
/// Plain text to convert to XML Encoded string
///
/// If true encodes single and double quotes.
/// When embedding element values quotes don't need to be encoded.
/// When embedding attributes quotes need to be encoded.
///
/// XML encoded string
/// Invalid character in XML string
public static string XmlString(string text, bool isAttribute = false)
{
if (string.IsNullOrEmpty(text))
return text;
var sb = new StringBuilder(text.Length);
foreach (var chr in text)
{
if (chr == '<')
sb.Append("<");
else if (chr == '>')
sb.Append(">");
else if (chr == '&')
sb.Append("&");
if (isAttribute)
{
// special handling for quotes
if (chr == '\"')
sb.Append(""");
else if (chr == '\'')
sb.Append("'");
// Legal sub-chr32 characters
else if (chr == '\n')
sb.Append("
");
else if (chr == '\r')
sb.Append("
");
else if (chr == '\t')
sb.Append(" ");
}
else
{
if (chr < 32)
throw new InvalidOperationException("Invalid character in Xml String. Chr " +
Convert.ToInt16(chr) + " is illegal.");
sb.Append(chr);
}
}
return sb.ToString();
}
///
/// Retrieves a result string from an XPATH query. Null if not found.
///
///
///
///
///
public static XmlNode GetXmlNode(XmlNode node, string xPath, XmlNamespaceManager ns = null)
{
return node.SelectSingleNode(xPath, ns);
}
///
/// Retrieves a result string from an XPATH query. Null if not found.
///
/// The base node to search from
/// XPath to drill into to find the target node. if not provided or null, returns current node
/// namespace to search in (optional)
/// node text
public static string GetXmlString(XmlNode node, string xPath = null, XmlNamespaceManager ns=null)
{
if (node == null)
return null;
if (string.IsNullOrEmpty(xPath))
return node?.InnerText;
XmlNode selNode = node.SelectSingleNode(xPath,ns);
return selNode?.InnerText;
}
///
/// Gets an Enum value from an xml node. Returns enum
/// type value. Either flag or string based keys will work
///
///
///
///
///
///
public static T GetXmlEnum(XmlNode node, string xPath, XmlNamespaceManager ns = null)
{
string val = GetXmlString(node, xPath,ns);
if (!string.IsNullOrEmpty(val))
return (T)Enum.Parse(typeof(T), val, true);
return default(T);
}
///
/// Retrieves a result int value from an XPATH query. 0 if not found.
///
///
///
///
public static int GetXmlInt(XmlNode node, string XPath, XmlNamespaceManager ns = null)
{
string val = GetXmlString(node, XPath, ns);
if (val == null)
return 0;
int result = 0;
int.TryParse(val, out result);
return result;
}
///
/// Retrieves a result decimal value from an XPATH query. 0 if not found.
///
///
///
///
public static decimal GetXmlDecimal(XmlNode node, string XPath, XmlNamespaceManager ns = null)
{
string val = GetXmlString(node, XPath, ns);
if (val == null)
return 0;
decimal result = 0;
decimal.TryParse(val, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
return result;
}
///
/// Retrieves a result bool from an XPATH query. false if not found.
///
///
///
///
public static bool GetXmlBool(XmlNode node, string xPath,XmlNamespaceManager ns = null)
{
string val = GetXmlString(node, xPath, ns);
if (val == null)
return false;
if (val == "1" || val == "true" || val == "True")
return true;
return false;
}
///
/// Retrieves a result DateTime from an XPATH query. 1/1/1900 if not found.
///
///
///
///
///
public static DateTime GetXmlDateTime(XmlNode node, string xPath, XmlNamespaceManager ns = null)
{
DateTime dtVal = new DateTime(1900, 1, 1, 0, 0, 0);
string val = GetXmlString(node, xPath, ns);
if (val == null)
return dtVal;
try
{
dtVal = XmlConvert.ToDateTime(val,XmlDateTimeSerializationMode.Utc);
}
catch { }
return dtVal;
}
///
/// Gets an attribute by name
///
///
///
/// value or null if not available
public static string GetXmlAttributeString(XmlNode node, string attributeName)
{
XmlAttribute att = node.Attributes[attributeName];
if (att == null)
return null;
return att.InnerText;
}
///
/// Returns an integer value from an attribute
///
///
///
///
///
public static int GetXmlAttributeInt(XmlNode node, string attributeName, int defaultValue)
{
string val = GetXmlAttributeString(node, attributeName);
if (val == null)
return defaultValue;
return XmlConvert.ToInt32(val);
}
///
/// Returns an bool value from an attribute
///
///
///
///
///
public static bool? GetXmlAttributeBool(XmlNode node, string attributeName)
{
string val = GetXmlAttributeString(node, attributeName);
if (val == null)
return null;
return XmlConvert.ToBoolean(val);
}
///
/// Converts a .NET type into an XML compatible type - roughly
///
///
///
public static string MapTypeToXmlType(Type type)
{
if (type == null)
return null;
if (type == typeof(string) || type == typeof(char) )
return "string";
if (type == typeof(int) || type== typeof(Int32) )
return "integer";
if (type == typeof(Int16) || type == typeof(byte) )
return "short";
if (type == typeof(long) || type == typeof(Int64) )
return "long";
if (type == typeof(bool))
return "boolean";
if (type == typeof(DateTime))
return "datetime";
if (type == typeof(float))
return "float";
if (type == typeof(decimal))
return "decimal";
if (type == typeof(double))
return "double";
if (type == typeof(Single))
return "single";
if (type == typeof(byte))
return "byte";
if (type == typeof(byte[]))
return "base64Binary";
return null;
// *** hope for the best
//return type.ToString().ToLower();
}
public static Type MapXmlTypeToType(string xmlType)
{
xmlType = xmlType.ToLower();
if (xmlType == "string")
return typeof(string);
if (xmlType == "integer")
return typeof(int);
if (xmlType == "long")
return typeof(long);
if (xmlType == "boolean")
return typeof(bool);
if (xmlType == "datetime")
return typeof(DateTime);
if (xmlType == "float")
return typeof(float);
if (xmlType == "decimal")
return typeof(decimal);
if (xmlType == "double")
return typeof(Double);
if (xmlType == "single")
return typeof(Single);
if (xmlType == "byte")
return typeof(byte);
if (xmlType == "base64binary")
return typeof(byte[]);
// return null if no match is found
// don't throw so the caller can decide more efficiently what to do
// with this error result
return null;
}
///
/// Creates an Xml NamespaceManager for an XML document by looking
/// at all of the namespaces defined on the document root element.
///
/// The XmlDom instance to attach the namespacemanager to
/// The prefix to use for prefix-less nodes (which are not supported if any namespaces are used in XmlDoc).
///
public static XmlNamespaceManager CreateXmlNamespaceManager(XmlDocument doc, string defaultNamespace)
{
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
foreach (XmlAttribute attr in doc.DocumentElement.Attributes)
{
if (attr.Prefix == "xmlns")
nsmgr.AddNamespace(attr.LocalName, attr.Value);
if (attr.Name == "xmlns")
// default namespace MUST use a prefix
nsmgr.AddNamespace(defaultNamespace, attr.Value);
}
return nsmgr;
}
}
}
================================================
FILE: Westwind.Utilities/Westwind.Utilities.csproj
================================================
net10.0;net8.0;net472;netstandard2.0
5.2.8.1
Rick Strahl
false
en-US
Westwind.Utilities
Westwind.Utilities
en-US
Westwind.Utilities
Westwind.Utilities
West Wind Utilities
.NET utility library that includes Application Configuration, lightweight ADO.NET Data Access Layer, logging, utility classes include: StringUtils, ReflectionUtils, FileUtils, DataUtils, SerializationUtils, TimeUtils, SecurityUtils and XmlUtils. These classes are useful in any kind of .NET project.
Small library of general purpose utilities for .NET development that almost every application can use. Used as a core reference library for other West Wind libraries.
Rick Strahl, West Wind Technologies 2007-2026
Westwind ApplicationConfiguration StringUtils ReflectionUtils DataUtils FileUtils TimeUtils SerializationUtils ImageUtils Logging DAL Sql ADO.NET
http://github.com/rickstrahl/westwind.utilities
icon.png
LICENSE.MD
true
Rick Strahl, West Wind Technologies, 2010-2026
West Wind Technologies
latest
TRACE;DEBUG;
embedded
$(NoWarn);CS1591;CS1572;CS1573
true
True
./nupkg
true
RELEASE
NETCORE;NETSTANDARD;NETSTANDARD2_0
NETFULL
embedded
true
True
True
Resources.resx
PublicResXFileCodeGenerator
Resources.Designer.cs
================================================
FILE: Westwind.Utilities/Westwind.Utilities.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Westwind.Utilities", "Westwind.Utilities.csproj", "{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3A29F986-14FE-4001-B3F7-3470BDE9EDB0}
EndGlobalSection
EndGlobal
================================================
FILE: Westwind.Utilities/publish-nuget.ps1
================================================
if (test-path ./nupkg) {
remove-item ./nupkg -Force -Recurse
}
dotnet build -c Release
$filename = Get-ChildItem "./nupkg/*.nupkg" | sort LastWriteTime | select -last 1 | select -ExpandProperty "Name"
Write-host $filename
$len = $filename.length
if ($len -gt 0) {
Write-Host "signing... $filename"
#nuget sign ".\nupkg\$filename" -CertificateSubject "West Wind Technologies" -timestamper " http://timestamp.digicert.com"
nuget push ".\nupkg\$filename" -source "https://nuget.org"
Write-Host "Done."
}
================================================
FILE: Westwind.Utilities.Data/Configuration/SqlServerConfigurationProvider.cs
================================================
#region License
/*
**************************************************************
* Author: Rick Strahl
* West Wind Technologies, 2009-2013
* http://www.west-wind.com/
*
* Created: 09/12/2009
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
// TODO: Doesn't work due to missing SqlDbClientFactories which should be added later
#if NETCORE
using Microsoft.Data.SqlClient;
#else
using System.Data.SqlClient;
#endif
using Westwind.Utilities.Data;
using System.Data.Common;
using System;
//using System.Data.SqlServerCe;
namespace Westwind.Utilities.Configuration
{
///
/// Reads and Writes configuration settings in .NET config files and
/// sections. Allows reading and writing to default or external files
/// and specification of the configuration section that settings are
/// applied to.
///
/// This implementation doesn't support Read and Write operation that
/// don't return a string value. Only Read(string) and WriteAsString()
/// should be used to read and write string values.
///
public class SqlServerConfigurationProvider : ConfigurationProviderBase
where TAppConfiguration : AppConfiguration, new()
{
///
/// The raw SQL connection string or connectionstrings name
/// for the database connection.
///
public string ConnectionString
{
get { return _ConnectionString; }
set { _ConnectionString = value; }
}
private string _ConnectionString = string.Empty;
#if NETFULL
///
/// The data provider used to access the database
///
public string ProviderName { get; set; } = "System.Data.SqlClient";
#else
///
/// If ProviderName is missing
///
public DbProviderFactory ProviderFactory { get; set; } = SqlClientFactory.Instance;
#endif
///
/// Table in the database that holds configuration data
/// Table must have ID(int) and ConfigData (nText) fields
///
public string Tablename
{
get { return _Tablename; }
set { _Tablename = value; }
}
private string _Tablename = "ConfigurationSettings";
///
/// The key of the record into which the config
/// data is written. Defaults to 1.
///
/// If you need to read or write multiple different
/// configuration records you have to change it on
/// this provider before calling the Read()/Write()
/// methods.
///
public int Key
{
get { return _Key; }
set { _Key = value; }
}
private int _Key = 1;
///
/// Reads configuration data into a new instance from SQL Server
/// that is returned.
///
///
///
public override T Read()
{
#if NETFULL
using (SqlDataAccess data = new SqlDataAccess(ConnectionString, ProviderName) )
#else
using (SqlDataAccess data = new SqlDataAccess(ConnectionString, ProviderFactory))
#endif
{
string sql = "select * from [" + Tablename + "] where id=" + Key.ToString();
DbDataReader reader = null;
try
{
DbCommand command = data.CreateCommand(sql);
if (command == null)
{
SetError(data.ErrorMessage);
return null;
}
reader = command.ExecuteReader();
if (reader == null)
{
SetError(data.ErrorMessage);
return null;
}
}
catch (SqlException ex)
{
if (ex.Number == 208)
{
sql =
@"CREATE TABLE [" + Tablename + @"]
( [id] [int] , [ConfigData] [ntext] COLLATE SQL_Latin1_General_CP1_CI_AS)";
try
{
data.ExecuteNonQuery(sql);
}
catch
{
return null;
}
// try again if we were able to create the table
return Read();
}
}
catch (DbException dbEx)
{
// SQL CE Table doesn't exist
if (dbEx.ErrorCode == -2147467259)
{
sql = String.Format(
@"CREATE TABLE [{0}] ( [id] [int] , [ConfigData] [ntext] )",
Tablename);
try
{
data.ExecuteNonQuery(sql);
}
catch
{
return null;
}
// try again if we were able to create the table
var inst = Read();
// if we got it write it to the db
Write(inst);
return inst;
}
return null;
}
catch (Exception ex)
{
this.SetError(ex);
if (reader != null)
reader.Close();
data.CloseConnection();
return null;
}
string xmlConfig = null;
if (reader.Read())
xmlConfig = (string)reader["ConfigData"];
reader.Close();
data.CloseConnection();
if (string.IsNullOrEmpty(xmlConfig))
{
T newInstance = new T();
newInstance.Provider = this;
return newInstance;
}
T instance = Read(xmlConfig);
return instance;
}
}
///
/// Reads configuration data from Sql Server into an existing
/// instance updating its fields.
///
///
///
public override bool Read(AppConfiguration config)
{
TAppConfiguration newConfig = Read();
if (newConfig == null)
return false;
DataUtils.CopyObjectData(newConfig, config,"Provider,ErrorMessage");
return true;
}
public override bool Write(AppConfiguration config)
{
#if NETFULL
SqlDataAccess data = new SqlDataAccess(ConnectionString,ProviderName);
#else
SqlDataAccess data = new SqlDataAccess(ConnectionString, SqlClientFactory.Instance);
#endif
string sql = String.Format(
"Update [{0}] set ConfigData=@ConfigData where id={1}",
Tablename, Key);
string xml = WriteAsString(config);
int result = 0;
try
{
result = data.ExecuteNonQuery(sql, data.CreateParameter("@ConfigData", xml));
}
catch
{
result = -1;
}
// try to create the table
if (result == -1)
{
sql = String.Format(
@"CREATE TABLE [{0}] ( [id] [int] , [ConfigData] [ntext] )",
Tablename);
try
{
result = data.ExecuteNonQuery(sql);
if (result > -1)
result = 0;
}
catch (Exception ex)
{
SetError(ex);
return false;
}
}
// Check for missing record
if (result == 0)
{
sql = "Insert [" + Tablename + "] (id,configdata) values (" + Key.ToString() + ",@ConfigData)";
try
{
result = data.ExecuteNonQuery(sql, data.CreateParameter("@ConfigData", xml));
}
catch (Exception ex)
{
SetError(ex);
return false;
}
if (result == 0)
{
return false;
}
}
if (result < 0)
return false;
return true;
}
}
}
================================================
FILE: Westwind.Utilities.Data/ConnectionStringInfo.cs
================================================
using System;
using System.Configuration;
using System.Data.Common;
#if NETCORE
using Microsoft.Data.SqlClient;
#else
using System.Data.SqlClient;
#endif
using Westwind.Utilities.Properties;
namespace Westwind.Utilities.Data
{
///
/// Used to parse a connection string or connection string name
/// into a the base connection string and dbProvider.
///
/// If a connection string is passed that's just used.
/// If a ConnectionString entry name is passed the connection
/// string is extracted and the provider parsed.
///
public class ConnectionStringInfo
{
///
/// The default connection string provider
///
public static string DefaultProviderName = "System.Data.SqlClient";
///
/// The connection string parsed
///
public string ConnectionString { get; set; }
///
/// The DbProviderFactory parsed from the connection string
/// or default provider
///
public DbProviderFactory Provider { get; set; }
///
/// Figures out the Provider and ConnectionString from either a connection string
/// name in a config file or full ConnectionString and provider.
///
/// Config file connection name or full connection string
/// optional provider name. If not passed with a connection string is considered Sql Server
/// optional provider factory. Use for .NET Core to pass actual provider instance since DbproviderFactories doesn't exist
public static ConnectionStringInfo GetConnectionStringInfo(string connectionString, string providerName = null, DbProviderFactory factory = null)
{
var info = new ConnectionStringInfo();
if (string.IsNullOrEmpty(connectionString))
throw new InvalidOperationException(Resources.AConnectionStringMustBePassedToTheConstructor);
if (!connectionString.Contains("="))
{
#if NETFULL
connectionString = RetrieveConnectionStringFromConfig(connectionString, info);
#else
throw new ArgumentException("Connection string names are not supported with .NET Standard. Please use a full connectionstring.");
#endif
}
else
{
info.Provider = factory;
if (factory == null)
{
if (providerName == null)
providerName = DefaultProviderName;
// TODO: DbProviderFactories This should get fixed by release of .NET 2.0
#if NETFULL
info.Provider = DbProviderFactories.GetFactory(providerName);
#else
info.Provider = SqlClientFactory.Instance;
#endif
}
}
info.ConnectionString = connectionString;
return info;
}
#if NETFULL
///
/// Retrieves a connection string from the Connection Strings configuration settings
///
///
///
/// Throws when connection string doesn't exist
///
public static string RetrieveConnectionStringFromConfig(string connectionStringName, ConnectionStringInfo info)
{
// it's a connection string entry
var connInfo = ConfigurationManager.ConnectionStrings[connectionStringName];
if (connInfo != null)
{
if (!string.IsNullOrEmpty(connInfo.ProviderName))
info.Provider = DbProviderFactories.GetFactory(connInfo.ProviderName);
else
info.Provider = DbProviderFactories.GetFactory(DefaultProviderName);
connectionStringName = connInfo.ConnectionString;
}
else
throw new InvalidOperationException(Resources.InvalidConnectionStringName + ": " + connectionStringName);
return connectionStringName;
}
#endif
}
}
================================================
FILE: Westwind.Utilities.Data/DataAccessBase.cs
================================================
#region License
//#define SupportWebRequestProvider
/*
**************************************************************
* Author: Rick Strahl
* © West Wind Technologies, 2009
* http://www.west-wind.com/
*
* Created: 09/12/2009
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Westwind.Utilities.Properties;
#if NETCORE
using Microsoft.Data.SqlClient;
#else
using System.Data.SqlClient;
#endif
namespace Westwind.Utilities.Data
{
///
/// Base Data Access Layer (DAL) for ADO.NET SQL operations.
/// Provides easy, single method operations to retrieve DataReader,
/// DataTable, DataSet and Entities, perform non-query operations,
/// call stored procedures.
///
/// This abstract class implements most data operations using a
/// configured DbProvider. Subclasses implement specific database
/// providers and override a few methods that might have provider
/// specific SQL Syntax.
///
[DebuggerDisplay("{ ErrorMessage } {ConnectionString} {LastSql}")]
public abstract class DataAccessBase : IDisposable
{
///
/// Default constructor that should be called back to
/// by subclasses. Parameterless assumes default provider
/// and no connection string which must be explicitly set.
///
protected DataAccessBase()
{
dbProvider = SqlClientFactory.Instance;
}
///
/// Most common constructor that expects a connection string or
/// connection string name from a .config file. If a connection
/// string is provided the default provider is used.
///
///
protected DataAccessBase(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
throw new InvalidOperationException(Resources.AConnectionStringMustBePassedToTheConstructor);
// sets dbProvider and ConnectionString properties
// based on connectionString Name or full connection string
GetConnectionInfo(connectionString,null);
}
#if NETFULL
///
/// Constructor that expects a full connection string and provider
/// for creating a SQL instance. To be called by the same implementation
/// on a subclass.
///
///
///
protected DataAccessBase(string connectionString, string providerName)
{
ConnectionString = connectionString;
dbProvider = DbProviderFactories.GetFactory(providerName);
}
#endif
///
/// Constructor that expects a full connection string and provider
/// for creating a SQL instance. To be called by the same implementation
/// on a subclass.
///
///
///
protected DataAccessBase(string connectionString, DbProviderFactory provider)
{
ConnectionString = connectionString;
dbProvider = provider;
}
///
/// Holds the last Identity Key if running insert statements that return
/// the identity key (save entity)
///
public object LastIdentityResult { get; set; }
///
/// Command to retrieve the last identity key from the database -
/// appended to auto-generated insert commands
///
public string GetIdentityKeySqlCommand { get; set; } = "select SCOPE_IDENTITY()";
///
/// Create a DataAccess component with a specific database provider
///
///
///
public DataAccessBase(string connectionString, DataAccessProviderTypes providerType)
{
ConnectionString = connectionString;
DbProviderFactory instance = SqlUtils.GetDbProviderFactory(providerType);
dbProvider = instance ?? throw new InvalidOperationException("Can't load database provider: " + providerType.ToString());
}
///
/// Figures out the dbProvider and Connection string from a
/// connectionString name in a config file or explicit
/// ConnectionString and provider.
///
/// Config file connection name or full connection string
/// optional provider name. If not passed with a connection string is considered Sql Server
public void GetConnectionInfo(string connectionString, string providerName = null)
{
// throws if connection string is invalid or missing
var connInfo = ConnectionStringInfo.GetConnectionStringInfo(connectionString, providerName);
ConnectionString = connInfo.ConnectionString;
dbProvider = connInfo.Provider;
}
///
/// The internally used dbProvider
///
public DbProviderFactory dbProvider = null;
///
/// An error message if a method fails
///
public virtual string ErrorMessage { get; set; } = string.Empty;
///
/// Optional error number returned by failed SQL commands
///
public int ErrorNumber { get; set; } = 0;
public Exception ErrorException { get; set; }
public bool ThrowExceptions { get; set; } = false;
///
/// The prefix used by the provider
///
public string ParameterPrefix { get; set; } = "@";
///
/// Determines whether parameters are positional or named. Positional
/// parameters are added without adding the name using just the ParameterPrefix
///
public bool UsePositionalParameters { get; set; } = false;
///
/// Character used for the left bracket on field names. Can be empty or null to use none
///
public string LeftFieldBracket { get; set; } = "[";
///
/// Character used for the right bracket on field names. Can be empty or null to use none
///
public string RightFieldBracket { get; set; } = "]";
///
/// ConnectionString for the data access component
///
public virtual string ConnectionString { get; set; } = string.Empty;
///
/// A SQL Transaction object that may be active. You can
/// also set this object explcitly
///
public virtual DbTransaction Transaction { get; set; }
///
/// The SQL Connection object used for connections
///
public virtual DbConnection Connection
{
get { return _Connection; }
set { _Connection = value; }
}
protected DbConnection _Connection = null;
///
/// The Sql Command execution Timeout in seconds.
/// Set to -1 for whatever the system default is.
/// Set to 0 to never timeout (not recommended).
///
public int Timeout { get; set; } = -1;
///
/// Determines whether extended schema information is returned for
/// queries from the server. Useful if schema needs to be returned
/// as part of DataSet XML creation
///
public virtual bool ExecuteWithSchema { get; set; } = false;
///
/// Holds the last SQL string executed
///
public string LastSql { get; set; }
#region Connection Operations
///
/// Opens a Sql Connection based on the connection string.
/// Called internally but externally accessible. Sets the internal
/// _Connection property.
///
///
///
/// Opens a Sql Connection based on the connection string.
/// Called internally but externally accessible. Sets the internal
/// _Connection property.
///
///
public virtual bool OpenConnection()
{
try
{
if (_Connection == null)
{
if (ConnectionString.Contains("="))
{
_Connection = dbProvider.CreateConnection();
_Connection.ConnectionString = ConnectionString;
}
else
{
var connInfo = ConnectionStringInfo.GetConnectionStringInfo(ConnectionString);
if (connInfo == null)
{
SetError(Resources.InvalidConnectionString);
if (ThrowExceptions)
throw new ApplicationException(ErrorMessage);
return false;
}
dbProvider = connInfo.Provider;
ConnectionString = connInfo.ConnectionString;
_Connection = dbProvider.CreateConnection();
_Connection.ConnectionString = ConnectionString;
}
}
if (_Connection.State != ConnectionState.Open)
_Connection.Open();
}
catch (SqlException ex)
{
SetError(string.Format(Resources.ConnectionOpeningFailure, ex.Message));
ErrorException = ex;
return false;
}
catch (DbException ex)
{
SetError(string.Format(Resources.ConnectionOpeningFailure, ex.Message));
ErrorException = ex;
return false;
}
catch (Exception ex)
{
SetError(string.Format(Resources.ConnectionOpeningFailure, ex.GetBaseException().Message));
ErrorException = ex;
return false;
}
return true;
}
///
/// Closes an active connection. If a transaction is pending the
/// connection is held open.
///
public virtual void CloseConnection()
{
if (Transaction != null)
return;
if (_Connection != null &&
_Connection.State != ConnectionState.Closed)
_Connection.Close();
_Connection = null;
}
///
/// Closes a connection on a command
///
///
public virtual void CloseConnection(DbCommand Command)
{
if (Transaction != null)
return;
if (Command.Connection != null &&
Command.Connection.State != ConnectionState.Closed)
Command.Connection.Close();
_Connection = null;
}
#endregion
#region Core Operations
///
/// Creates a Command object and opens a connection
///
/// Sql string to execute
/// Either values mapping to @0,@1,@2 etc. or DbParameter objects created with CreateParameter()
/// Command object or null on error
public virtual DbCommand CreateCommand(string sql, CommandType commandType, params object[] parameters)
{
SetError();
DbCommand command = dbProvider.CreateCommand();
command.CommandType = commandType;
command.CommandText = sql;
if (Timeout > -1)
command.CommandTimeout = Timeout;
try
{
if (Transaction != null)
{
command.Transaction = Transaction;
command.Connection = Transaction.Connection;
}
else
{
if (!OpenConnection())
return null;
command.Connection = _Connection;
}
}
catch (Exception ex)
{
SetError(ex);
return null;
}
if (parameters != null)
AddParameters(command,parameters);
return command;
}
///
/// Creates a Command object and opens a connection
///
/// Sql string to execute
/// Either values mapping to @0,@1,@2 etc. or DbParameter objects created with CreateParameter()
/// command object
public virtual DbCommand CreateCommand(string sql, params object[] parameters)
{
return CreateCommand(sql, CommandType.Text, parameters);
}
///
/// Adds parameters to a DbCommand instance. Parses value and DbParameter parameters
/// properly into the command's Parameters collection.
///
/// A preconfigured DbCommand object that should have all connection information set
///
/// DbParameters (CreateParameter()) for named parameters
/// or use @0,@1 parms in SQL and plain values
///
protected void AddParameters(DbCommand command, object[] parameters)
{
if (parameters != null && parameters.Length > 0)
{
// check for anonymous type
if (parameters.Length == 1 && ReflectionUtils.IsAnonoymousType(parameters[0]))
{
ParseObjectParameters(command, parameters[0]);
return;
}
var parmCount = 0;
foreach (var parameter in parameters)
{
if (parameter is DbParameter)
command.Parameters.Add(parameter);
else
{
var parm = CreateParameter(ParameterPrefix + parmCount, parameter);
command.Parameters.Add(parm);
parmCount++;
}
}
}
}
///
/// Parses an anonymous object map into a set of DbParameters
///
///
///
///
public DbParameterCollection ParseObjectParameters(DbCommand command, object parameter)
{
var props = parameter.GetType().GetProperties();
var parmCount = 0;
foreach (var prop in props)
{
object value = prop.GetValue(parameter, null);
if (value is DbParameter)
command.Parameters.Add(value);
else
{
var parm = CreateParameter(ParameterPrefix + prop.Name, value);
command.Parameters.Add(parm);
parmCount++;
}
}
return command.Parameters;
}
///
/// Used to create named parameters to pass to commands or the various
/// methods of this class.
///
///
///
///
///
public virtual DbParameter CreateParameter(string parameterName, object value)
{
DbParameter parm = dbProvider.CreateParameter();
parm.ParameterName = parameterName;
if (value == null)
value = DBNull.Value;
parm.Value = value;
return parm;
}
///
/// Used to create named parameters to pass to commands or the various
/// methods of this class.
///
///
///
///
///
public virtual DbParameter CreateParameter(string parameterName, object value, ParameterDirection parameterDirection = ParameterDirection.Input)
{
DbParameter parm = CreateParameter(parameterName, value);
parm.Direction = parameterDirection;
return parm;
}
///
/// Used to create named parameters to pass to commands or the various
/// methods of this class.
///
///
///
///
///
public virtual DbParameter CreateParameter(string parameterName, object value, int size)
{
DbParameter parm = CreateParameter(parameterName, value);
parm.Size = size;
return parm;
}
///
/// Used to create named parameters to pass to commands or the various
/// methods of this class.
///
///
///
///
///
public virtual DbParameter CreateParameter(string parameterName, object value, DbType type)
{
DbParameter parm = CreateParameter(parameterName, value);
parm.DbType = type;
return parm;
}
///
/// Used to create named parameters to pass to commands or the various
/// methods of this class.
///
///
///
///
///
///
public virtual DbParameter CreateParameter(string parameterName, object value, DbType type, int size)
{
DbParameter parm = CreateParameter(parameterName, value);
parm.DbType = type;
parm.Size = size;
return parm;
}
#endregion
#region Transactions
///
/// Starts a new transaction on this connection/instance
///
/// Opens a Connection and keeps it open for the duration of the transaction. Calls to `.CloseConnection` while the transaction is active have no effect.
///
public virtual bool BeginTransaction()
{
if (_Connection == null)
{
if (!OpenConnection())
return false;
}
Transaction = _Connection.BeginTransaction();
if (Transaction == null)
return false;
return true;
}
///
/// Commits all changes to the database and ends the transaction
///
/// Closes Connection
///
public virtual bool CommitTransaction()
{
if (Transaction == null)
{
SetError(Resources.NoActiveTransactionToCommit);
if (ThrowExceptions)
new InvalidOperationException(Resources.NoActiveTransactionToCommit);
return false;
}
Transaction.Commit();
Transaction = null;
CloseConnection();
return true;
}
///
/// Rolls back a transaction
///
/// Closes Connection
///
public virtual bool RollbackTransaction()
{
if (Transaction == null)
return true;
Transaction.Rollback();
Transaction = null;
CloseConnection();
return true;
}
#endregion
#region Non-list Sql Commands
///
/// Executes a non-query command and returns the affected records
///
/// Command should be created with GetSqlCommand to have open connection
/// Affected Record count or -1 on error
public virtual int ExecuteNonQuery(DbCommand Command)
{
SetError();
int RecordCount = 0;
try
{
LastSql = Command.CommandText;
RecordCount = Command.ExecuteNonQuery();
if (RecordCount == -1)
RecordCount = 0;
}
catch (DbException ex)
{
RecordCount = -1;
SetError(ex);;
}
catch (Exception ex)
{
RecordCount = -1;
SetError(ex);
}
finally
{
CloseConnection();
}
return RecordCount;
}
///
/// Executes a command that doesn't return any data. The result
/// returns the number of records affected or -1 on error.
///
/// SQL statement as a string
///
/// DbParameters (CreateParameter()) for named parameters
/// or use @0,@1 parms in SQL and plain values
/// or new { parm1=value, parm2 = value2}
///
///
///
/// Executes a command that doesn't return a data result. You can return
/// output parameters and you do receive an AffectedRecords counter.
/// .setItem("list_html", JSON.stringify(data));
///
public virtual int ExecuteNonQuery(string sql, params object[] parameters)
{
DbCommand command = CreateCommand(sql,parameters);
if (command == null)
return -1;
return ExecuteNonQuery(command);
}
///
/// Executes a non-query command and returns the affected records
///
/// Command should be created with GetSqlCommand to have open connection
/// Affected Record count or -1 on error
public virtual async Task ExecuteNonQueryAsync(DbCommand Command)
{
SetError();
int RecordCount = 0;
try
{
LastSql = Command.CommandText;
RecordCount = await Command.ExecuteNonQueryAsync();
if (RecordCount == -1)
RecordCount = 0;
}
catch (DbException ex)
{
RecordCount = -1;
SetError(ex); ;
}
catch (Exception ex)
{
RecordCount = -1;
SetError(ex);
}
finally
{
CloseConnection();
}
return RecordCount;
}
///
/// Executes a command that doesn't return any data. The result
/// returns the number of records affected or -1 on error.
///
/// SQL statement as a string
///
/// DbParameters (CreateParameter()) for named parameters
/// or use @0,@1 parms in SQL and plain values
///
///
///
/// Executes a command that doesn't return a data result. You can return
/// output parameters and you do receive an AffectedRecords counter.
/// .setItem("list_html", JSON.stringify(data));
///
public virtual async Task ExecuteNonQueryAsync(string sql, params object[] parameters)
{
DbCommand command = CreateCommand(sql, parameters);
if (command == null)
return -1;
int result = await ExecuteNonQueryAsync(command);
return result;
}
///
/// Executes a command and returns a scalar value from it
///
/// DbCommand containing command to run
///
/// DbParameters (CreateParameter()) for named parameters
/// or use @0,@1 parms in SQL and plain values
///
/// value or null on failure
public virtual object ExecuteScalar(DbCommand command, params object[] parameters)
{
SetError();
AddParameters(command, parameters);
object Result = null;
try
{
LastSql = command.CommandText;
Result = command.ExecuteScalar();
}
catch (Exception ex)
{
SetError(ex.GetBaseException());
}
finally
{
CloseConnection();
}
return Result;
}
///
/// Executes a Sql command and returns a single value from it.
///
/// Sql string to execute
///
/// DbParameters (CreateParameter()) for named parameters
/// or use @0,@1 parms in SQL and plain values
///
/// Result value or null. Check ErrorMessage on Null if unexpected
public virtual object ExecuteScalar(string sql, params object[] parameters)
{
SetError();
DbCommand command = CreateCommand(sql, parameters);
if (command == null)
return null;
return ExecuteScalar(command, null);
}
///
/// Executes a command and returns a scalar value from it
///
/// DbCommand containing command to run
///
/// DbParameters (CreateParameter()) for named parameters
/// or use @0,@1 parms in SQL and plain values
///
/// value or null on failure
public virtual async Task